diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index fc24434..4d3dfa6 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -111,7 +111,7 @@ class Exporter: # Instead of exporting a skeleton now, we'll just make an orphaned CI. # The bl_obj export will make this work. - parent_ci = self.mgr.find_create_key(parent, plCoordinateInterface).object + parent_ci = self.mgr.find_create_key(plCoordinateInterface, bl=bo, so=so).object parent_ci.addChild(so.key) else: self.report.warn("You have parented Plasma Object '{}' to '{}', which has not been marked for export. \ @@ -122,7 +122,7 @@ class Exporter: """Ensures that the SceneObject has a CoordinateInterface""" if not so.coord: print(" Exporting CoordinateInterface") - ci = self.mgr.find_create_key(bo, plCoordinateInterface).object + ci = self.mgr.find_create_key(plCoordinateInterface, bl=bo, so=so).object # Now we have the "fun" work of filling in the CI ci.localToWorld = utils.matrix44(bo.matrix_basis) @@ -148,7 +148,7 @@ class Exporter: # Create a sceneobject if one does not exist. # Before we call the export_fn, we need to determine if this object is an actor of any # sort, and barf out a CI. - sceneobject = self.mgr.find_create_key(bl_obj, plSceneObject).object + sceneobject = self.mgr.find_create_key(plSceneObject, bl=bl_obj).object self._export_actor(sceneobject, bl_obj) export_fn(sceneobject, bl_obj) diff --git a/korman/exporter/manager.py b/korman/exporter/manager.py index 7706ea7..fa7e10d 100644 --- a/korman/exporter/manager.py +++ b/korman/exporter/manager.py @@ -91,7 +91,7 @@ class ExportManager: if isinstance(pl, plObjInterface): if so is None: - key = self.find_key(bl, plSceneObject) + key = self.find_key(plSceneObject, bl) # prevent race conditions if key is None: so = self.add_object(plSceneObject, name=name, loc=location) @@ -158,22 +158,26 @@ class ExportManager: self._nodes[location] = None return location - def find_create_key(self, bl_obj, pClass, so=None): - key = self.find_key(bl_obj, pClass, so=so) + def find_create_key(self, pClass, bl=None, name=None, so=None): + key = self.find_key(pClass, bl, name, so) if key is None: - key = self.add_object(pl=pClass, bl=bl_obj, so=so).key + key = self.add_object(pl=pClass, name=name, bl=bl, so=so).key return key - def find_key(self, bl_obj, pClass, so=None): + def find_key(self, pClass, bl=None, name=None, so=None): """Given a blender Object and a Plasma class, find (or create) an exported plKey""" + assert (bl or name) and (bl or so) + if so is None: - location = self._pages[bl_obj.plasma_object.page] + location = self._pages[bl.plasma_object.page] else: location = so.key.location + if name is None: + name = bl.name index = plFactory.ClassIndex(pClass.__name__) for key in self.mgr.getKeys(location, index): - if bl_obj.name == key.name: + if name == key.name: return key return None diff --git a/korman/exporter/rtlight.py b/korman/exporter/rtlight.py index 0fc8bbf..7cf29df 100644 --- a/korman/exporter/rtlight.py +++ b/korman/exporter/rtlight.py @@ -92,7 +92,7 @@ class LightConverter: def _create_light_key(self, bo, bl_light, so): try: xlate = _BL2PL[bl_light.type] - return self.mgr.find_create_key(bo, xlate, so=so) + return self.mgr.find_create_key(xlate, bl=bo, so=so) except LookupError: raise BlenderOptionNotSupported("Object ('{}') lamp type '{}'".format(bo.name, bl_light.type)) diff --git a/korman/nodes/node_core.py b/korman/nodes/node_core.py index 574fecb..f214310 100644 --- a/korman/nodes/node_core.py +++ b/korman/nodes/node_core.py @@ -13,9 +13,19 @@ # You should have received a copy of the GNU General Public License # along with Korman. If not, see . +import abc import bpy class PlasmaNodeBase: + def create_key_name(self, tree): + return "{}_{}".format(tree.name, self.name) + + def get_key(self, exporter, tree, so): + return None + + def export(self, exporter, tree, bo, so): + pass + def find_input(self, key, idname=None): for i in self.inputs: if i.identifier == key: @@ -34,6 +44,18 @@ class PlasmaNodeBase: return i raise KeyError(key) + def find_output(self, key, idname=None): + for i in self.outputs: + if i.identifier == key: + if i.links: + node = i.links[0].to_node + if idname is not None and idname != node.bl_idname: + return None + return node + else: + return None + raise KeyError(key) + def find_outputs(self, key, idname=None): for i in self.outputs: if i.identifier == key: @@ -107,6 +129,11 @@ class PlasmaNodeTree(bpy.types.NodeTree): bl_label = "Plasma" bl_icon = "NODETREE" + def export(self, exporter, bo, so): + # just pass it off to each node + for node in self.nodes: + node.export(exporter, self, bo, so) + @classmethod def poll(cls, context): return (context.scene.render.engine == "PLASMA_GAME") diff --git a/korman/nodes/node_logic.py b/korman/nodes/node_logic.py deleted file mode 100644 index 48971cc..0000000 --- a/korman/nodes/node_logic.py +++ /dev/null @@ -1,31 +0,0 @@ -# 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 - -from .node_core import * - -class PlasmaLogicTriggerNode(PlasmaNodeBase, bpy.types.Node): - bl_category = "LOGIC" - bl_idname = "PlasmaLogicTriggerNode" - bl_label = "Logic Trigger" - - def init(self, context): - self.inputs.new("PlasmaConditionSocket", "Condition", "condition") - self.outputs.new("PlasmaRespTriggerSocket", "Trigger", "trigger") - - -class PlasmaRespTriggerSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket): - bl_color = (0.384, 0.239, 0.239, 1.0) diff --git a/korman/nodes/node_messages.py b/korman/nodes/node_messages.py index bec5981..ccb8080 100644 --- a/korman/nodes/node_messages.py +++ b/korman/nodes/node_messages.py @@ -15,6 +15,7 @@ import bpy from bpy.props import * +from PyHSPlasma import * from .node_core import * from ..properties.modifiers.region import footstep_surfaces, footstep_surface_ids @@ -38,3 +39,8 @@ class PlasmaFootstepSoundMsgNode(PlasmaNodeBase, bpy.types.Node): def draw_buttons(self, context, layout): layout.prop(self, "surface") + + def convert_message(self, exporter): + msg = plArmatureEffectStateMsg() + msg.surface = footstep_surface_ids[self.surface] + return msg diff --git a/korman/nodes/node_responder.py b/korman/nodes/node_responder.py index 36ed13a..ca71fe2 100644 --- a/korman/nodes/node_responder.py +++ b/korman/nodes/node_responder.py @@ -15,6 +15,7 @@ import bpy from bpy.props import * +from PyHSPlasma import * import uuid from .node_core import * @@ -28,6 +29,40 @@ class PlasmaResponderNode(PlasmaNodeBase, bpy.types.Node): self.inputs.new("PlasmaRespTriggerSocket", "Trigger", "whodoneit") self.outputs.new("PlasmaRespStateSocket", "States", "states") + def get_key(self, exporter, tree, so): + return exporter.mgr.find_create_key(plResponderModifier, name=self.create_key_name(tree), so=so) + + def export(self, exporter, tree, bo, so): + responder = self.get_key(exporter, tree, so).object + if not bo.plasma_net.manual_sdl: + responder.setExclude("Responder") + + class ResponderStateMgr: + def __init__(self, respNode, respMod): + self.states = [] + self.parent = respNode + self.responder = respMod + + def get_state(self, node): + for idx, (theNode, theState) in enumerate(self.states): + if theNode == node: + return (idx, theState) + state = plResponderModifier_State() + self.states.append((node, state)) + return (len(self.states) - 1, state) + + def save(self): + resp = self.responder + resp.clearStates() + for node, state in self.states: + resp.addState(state) + + # Convert the Responder states + stateMgr = ResponderStateMgr(self, responder) + for stateNode in self.find_outputs("states", "PlasmaResponderStateNode"): + stateNode.convert_state(exporter, stateMgr) + stateMgr.save() + class PlasmaResponderStateNode(PlasmaNodeVariableInput, bpy.types.Node): bl_category = "LOGIC" @@ -50,6 +85,59 @@ class PlasmaResponderStateNode(PlasmaNodeVariableInput, bpy.types.Node): # Now draw a prop layout.prop(self, "default_state") + def convert_state(self, exporter, stateMgr): + idx, state = stateMgr.get_state(self) + + # No sanity checking here. Hopefully nothing crazy has happened in the UI. + if self.default_state: + stateMgr.responder.curState = idx + + # Where do we go from heah? + toStateNode = self.find_output("gotostate", "PlasmaResponderStateNode") + if toStateNode is None: + state.switchToState = idx + else: + toIdx, toState = stateMgr.get_state(toStateNode) + state.switchToState = toIdx + + class CommandMgr: + def __init__(self): + self.commands = [] + self.waits = {} + + def add_command(self, node): + cmd = type("ResponderCommand", (), {"msg": None, "waitOn": -1}) + self.commands.append((node, cmd)) + return (len(self.commands) - 1, cmd) + + def add_wait(self, parentCmd): + try: + idx = self.commands.index(parentCmd) + except ValueError: + # The parent command didn't export for some reason... Probably no message. + # So, wait on nothing! + return -1 + else: + wait = len(self.waits) + self.waits[wait] = idx + return idx + + def save(self, state): + for node, cmd in self.commands: + # Amusing, PyHSPlasma doesn't actually want a plResponderModifier_Cmd + # Meh, I'll let this one slide. + state.addCommand(cmd.msg, cmd.waitOn) + state.numCallbacks = len(self.waits) + state.waitToCmd = self.waits + + # Convert the commands + commands = CommandMgr() + for i in self.find_outputs("cmds", "PlasmaResponderCommandNode"): + # slight optimization--commands attached to states can't wait on other commands + # namely because it's impossible to wait on a command that doesn't exist... + i.convert_command(exporter, stateMgr.responder, commands, True) + commands.save(state) + class PlasmaRespStateSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket): bl_color = (0.388, 0.78, 0.388, 1.0) @@ -65,6 +153,55 @@ class PlasmaResponderCommandNode(PlasmaNodeBase, bpy.types.Node): self.outputs.new("PlasmaMessageSocket", "Message", "msg") self.outputs.new("PlasmaRespCommandSocket", "Trigger", "trigger") + def convert_command(self, exporter, responder, commandMgr, forceNoWait=False): + # If this command has no message, there is no need to export it... + msgNode = self.find_output("msg") + if msgNode is not None: + idx, command = commandMgr.add_command(self) + + # If the thingthatdoneit is another command, we need to register a wait. + # We could hack and assume the parent is idx-1, but that won't work if the parent has many + # child commands. Le whoops! + if not forceNoWait: + parentCmd = self.find_input("whodoneit", "PlasmaResponderCommandNode") + if parentCmd is not None: + command.waitOn = commandMgr.add_wait(parentCmd) + + # Finally, convert our message... + msg = msgNode.convert_message(exporter) + self._finalize_message(exporter, responder, msg) + + # If we have child commands, we need to make sure that we support chaining this message as a callback + # If not, we'll export our children and tell them to not actually wait on us. + haveChildren = self.find_output("trigger", "PlasmaResponderCommandNode") is not None + if haveChildren: + nowait = not self._add_msg_callback(exporter, responder, msg) + command.msg = msg + else: + nowait = True + + # Export any child commands + for i in self.find_outputs("trigger", "PlasmaResponderCommandNode"): + i.convert_command(exporter, responder, commandMgr, nowait) + + _bcast_flags = { + plArmatureEffectStateMsg: (plMessage.kPropagateToModifiers | plMessage.kNetPropagate), + } + + def _finalize_message(self, exporter, responder, msg): + msg.sender = responder.key + + # BCast Flags are pretty common... + _cls = msg.__class__ + if _cls in self._bcast_flags: + msg.BCastFlags = self._bcast_flags[_cls] + msg.BCastFlags |= plMessage.kLocalPropagate + + def _add_msg_callback(self, exporter, responder, msg): + """Prepares a given message to be a callback to the responder""" + # We do not support callback messages ATM + return False + class PlasmaRespCommandSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket): bl_color = (0.451, 0.0, 0.263, 1.0) diff --git a/korman/properties/modifiers/region.py b/korman/properties/modifiers/region.py index c239eec..407d454 100644 --- a/korman/properties/modifiers/region.py +++ b/korman/properties/modifiers/region.py @@ -74,6 +74,13 @@ class PlasmaFootstepRegion(PlasmaModifierProperties, PlasmaModifierLogicWiz): def created(self, obj): self.display_name = "{}_FootRgn".format(obj.name) + def export(self, exporter, bo, so): + # Generate the logic nodes now + self.logicwiz(bo) + + # Now, export the node tree + self.node_tree.export(exporter, bo, so) + def logicwiz(self, bo): tree = self.node_tree nodes = tree.nodes