Browse Source

Responder Command node upgrader/deprecate

Adds a framework for dealing with deprecated logic nodes. First
implementation is for Responder Commands, which hooks the messages
directly onto the Responder States. Note that the old socket definitions
are left alone because the upgrader will need that data.
pull/95/head
Adam Johnson 7 years ago
parent
commit
b30f03eafc
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 7
      korman/nodes/__init__.py
  2. 5
      korman/nodes/node_conditions.py
  3. 41
      korman/nodes/node_core.py
  4. 131
      korman/nodes/node_deprecated.py
  5. 104
      korman/nodes/node_responder.py

7
korman/nodes/__init__.py

@ -22,6 +22,7 @@ import nodeitems_utils
from .node_avatar import * from .node_avatar import *
from .node_conditions import * from .node_conditions import *
from .node_core import * from .node_core import *
from .node_deprecated import *
from .node_logic import * from .node_logic import *
from .node_messages import * from .node_messages import *
from .node_python import * from .node_python import *
@ -53,7 +54,11 @@ _kategory_names = {
_kategories = {} _kategories = {}
for cls in dict(globals()).values(): for cls in dict(globals()).values():
if inspect.isclass(cls): if inspect.isclass(cls):
if not issubclass(cls, PlasmaNodeBase) or not issubclass(cls, bpy.types.Node): if not issubclass(cls, PlasmaNodeBase):
continue
if not issubclass(cls, bpy.types.Node):
continue
if issubclass(cls, PlasmaDeprecatedNode):
continue continue
else: else:
continue continue

5
korman/nodes/node_conditions.py

@ -82,6 +82,11 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
interface.addIntfKey(logicmod) interface.addIntfKey(logicmod)
logicmod = logicmod.object logicmod = logicmod.object
# If we receive an enable message, this is a one-shot type deal that needs to be disabled
# while the attached responder is running.
if self.find_input("message", "PlasmaEnableMsgNode") is not None:
logicmod.setLogicFlag(plLogicModifier.kOneShot, True)
# Try to figure out the appropriate bounds type for the clickable.... # Try to figure out the appropriate bounds type for the clickable....
phys_mod = clickable_bo.plasma_modifiers.collision phys_mod = clickable_bo.plasma_modifiers.collision
bounds = phys_mod.bounds if phys_mod.enabled else self.bounds bounds = phys_mod.bounds if phys_mod.enabled else self.bounds

41
korman/nodes/node_core.py

@ -65,11 +65,22 @@ class PlasmaNodeBase:
if idname is None or idname == node.bl_idname: if idname is None or idname == node.bl_idname:
yield node yield node
def find_input_socket(self, key): def find_input_socket(self, key, spawn_empty=False):
# In the case that this socket will be used to make new input linkage,
# we might want to allow the spawning of a new input socket... :)
# This will only be done if the node's socket definitions allow it.
options = self._socket_defs[0].get(key, {})
spawn_empty = spawn_empty and options.get("spawn_empty", False)
for i in self.inputs: for i in self.inputs:
if i.alias == key: if i.alias == key:
if spawn_empty and i.is_linked:
continue
return i return i
raise KeyError(key) if spawn_empty:
return self._spawn_socket(key, options, self.inputs)
else:
raise KeyError(key)
def find_input_sockets(self, key, idname=None): def find_input_sockets(self, key, idname=None):
for i in self.inputs: for i in self.inputs:
@ -118,7 +129,7 @@ class PlasmaNodeBase:
def link_input(self, node, out_key, in_key): def link_input(self, node, out_key, in_key):
"""Links a given Node's output socket to a given input socket on this Node""" """Links a given Node's output socket to a given input socket on this Node"""
if isinstance(in_key, str): if isinstance(in_key, str):
in_socket = self.find_input_socket(in_key) in_socket = self.find_input_socket(in_key, spawn_empty=True)
else: else:
in_socket = in_key in_socket = in_key
if isinstance(out_key, str): if isinstance(out_key, str):
@ -130,7 +141,7 @@ class PlasmaNodeBase:
def link_output(self, node, out_key, in_key): def link_output(self, node, out_key, in_key):
"""Links a given Node's input socket to a given output socket on this Node""" """Links a given Node's input socket to a given output socket on this Node"""
if isinstance(in_key, str): if isinstance(in_key, str):
in_socket = node.find_input_socket(in_key) in_socket = node.find_input_socket(in_key, spawn_empty=True)
else: else:
in_socket = in_key in_socket = in_key
if isinstance(out_key, str): if isinstance(out_key, str):
@ -161,6 +172,15 @@ class PlasmaNodeBase:
return (getattr(self.__class__, "input_sockets", {}), return (getattr(self.__class__, "input_sockets", {}),
getattr(self.__class__, "output_sockets", {})) getattr(self.__class__, "output_sockets", {}))
def _spawn_socket(self, key, options, sockets):
socket = sockets.new(options["type"], options["text"], key)
link_limit = options.get("link_limit", None)
if link_limit is not None:
socket.link_limit = link_limit
socket.hide = options.get("hidden", False)
socket.hide_value = options.get("hidden", False)
return socket
def _tattle(self, socket, link, reason): def _tattle(self, socket, link, reason):
direction = "->" if socket.is_output else "<-" direction = "->" if socket.is_output else "<-"
print("Removing {} {} {} {}".format(link.from_node.name, direction, link.to_node.name, reason)) print("Removing {} {} {} {}".format(link.from_node.name, direction, link.to_node.name, reason))
@ -187,6 +207,8 @@ class PlasmaNodeBase:
link_limit = options.get("link_limit", None) link_limit = options.get("link_limit", None)
if link_limit is not None: if link_limit is not None:
socket.link_limit = link_limit socket.link_limit = link_limit
socket.hide = options.get("hidden", False)
socket.hide_value = options.get("hidden", False)
# Make sure the link is good # Make sure the link is good
allowed_sockets = options.get("valid_link_sockets", None) allowed_sockets = options.get("valid_link_sockets", None)
@ -237,11 +259,12 @@ class PlasmaNodeBase:
# Create any new sockets # Create any new sockets
for alias in (j for j in defs if j not in done): for alias in (j for j in defs if j not in done):
options = defs[alias] self._spawn_socket(alias, defs[alias], sockets)
socket = sockets.new(options["type"], options["text"], alias)
link_limit = options.get("link_limit", None) def _whine(self, msg, *args):
if link_limit is not None: if args:
socket.link_limit = link_limit msg = msg.format(*args)
print("'{}' Node '{}': Whinging about {}".format(self.bl_idname, self.name, msg))
class PlasmaTreeOutputNodeBase(PlasmaNodeBase): class PlasmaTreeOutputNodeBase(PlasmaNodeBase):

131
korman/nodes/node_deprecated.py

@ -0,0 +1,131 @@
# 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
from bpy.props import *
from collections import OrderedDict
from .node_core import *
class PlasmaDeprecatedNode(PlasmaNodeBase):
def upgrade(self):
raise NotImplementedError()
class PlasmaRespCommandSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.451, 0.0, 0.263, 1.0)
class PlasmaResponderCommandNode(PlasmaDeprecatedNode, bpy.types.Node):
bl_category = "LOGIC"
bl_idname = "PlasmaResponderCommandNode"
bl_label = "Responder Command"
input_sockets = OrderedDict([
("whodoneit", {
"text": "Condition",
"type": "PlasmaRespCommandSocket",
}),
])
output_sockets = OrderedDict([
("msg", {
"link_limit": 1,
"text": "Message",
"type": "PlasmaMessageSocket",
}),
("trigger", {
"text": "Trigger",
"type": "PlasmaRespCommandSocket",
}),
("reenable", {
"text": "Local Reenable",
"type": "PlasmaEnableMessageSocket",
}),
])
def _find_message_sender_node(self, parentCmdNode=None):
if parentCmdNode is None:
parentCmdNode = self
else:
if self == parentCmdNode:
self._whine("responder tree is circular")
return None
if parentCmdNode.bl_idname == "PlasmaResponderStateNode":
return parentCmdNode
elif parentCmdNode.bl_idname == "PlasmaResponderCommandNode":
# potentially a responder wait command... let's see if the message can wait
if parentCmdNode != self:
cmdMsgNode = parentCmdNode.find_output("msg")
if cmdMsgNode is not None and cmdMsgNode.has_callbacks:
return cmdMsgNode
# can't wait on this command/message, so go up the tree...
grandParentCmdNode = parentCmdNode.find_input("whodoneit")
if grandParentCmdNode is None:
self._whine("orphaned responder command")
return None
return self._find_message_sender_node(grandParentCmdNode)
else:
self._whine("unexpected command node type '{}'", parentCmdNode.bl_idname)
return None
def upgrade(self):
senderNode = self._find_message_sender_node()
if senderNode is None:
return
msgNode = self.find_output("msg")
if msgNode is not None:
senderNode.link_output(msgNode, "msgs", "sender")
else:
self._whine("command node does not send a message?")
if self.find_output("reenable") is not None:
tree = self.id_data
enableMsgNode = tree.nodes.new("PlasmaEnableMsgNode")
enableMsgNode.cmd = "kEnable"
if msgNode.has_callbacks:
msgNode.link_output(enableMsgNode, "msgs", "sender")
else:
senderNode.link_output(enableMsgNode, "msgs", "sender")
fromSocket = enableMsgNode.find_output_socket("receivers")
for link in self.find_output_socket("reenable").links:
if not link.is_valid:
continue
tree.links.new(link.to_socket, fromSocket)
@bpy.app.handlers.persistent
def _upgrade_node_trees(dummy):
for tree in bpy.data.node_groups:
if tree.bl_idname != "PlasmaNodeTree":
continue
# ensure node sockets match what we expect
for node in tree.nodes:
node.update()
nuke = []
# upgrade to new node types/linkages
for node in tree.nodes:
if isinstance(node, PlasmaDeprecatedNode):
node.upgrade()
nuke.append(node)
# toss deprecated nodes
for node in nuke:
tree.nodes.remove(node)
bpy.app.handlers.load_post.append(_upgrade_node_trees)

104
korman/nodes/node_responder.py

@ -126,6 +126,14 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
]) ])
output_sockets = OrderedDict([ output_sockets = OrderedDict([
# This socket has been deprecated.
("cmds", {
"text": "Commands",
"type": "PlasmaRespCommandSocket",
"hidden": True,
}),
# These sockets are valid.
("msgs", { ("msgs", {
"text": "Send Message", "text": "Send Message",
"type": "PlasmaMessageSocket", "type": "PlasmaMessageSocket",
@ -233,99 +241,3 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
class PlasmaRespStateSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket): class PlasmaRespStateSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.388, 0.78, 0.388, 1.0) bl_color = (0.388, 0.78, 0.388, 1.0)
class PlasmaResponderCommandNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "LOGIC"
bl_idname = "PlasmaResponderCommandNode"
bl_label = "Responder Command"
input_sockets = OrderedDict([
("whodoneit", {
"text": "Condition",
"type": "PlasmaRespCommandSocket",
}),
])
output_sockets = OrderedDict([
("msg", {
"link_limit": 1,
"text": "Message",
"type": "PlasmaMessageSocket",
}),
("trigger", {
"text": "Trigger",
"type": "PlasmaRespCommandSocket",
}),
("reenable", {
"text": "Local Reenable",
"type": "PlasmaEnableMessageSocket",
}),
])
def convert_command(self, exporter, so, responder, commandMgr, waitOn=-1):
raise RuntimeError()
def prepare_message(exporter, so, responder, commandMgr, waitOn, msg):
idx, command = commandMgr.add_command(self, waitOn)
if msg.sender is None:
msg.sender = responder.key
msg.BCastFlags |= plMessage.kLocalPropagate
command.msg = msg
return (idx, command)
# If this command has no message, there is no need to export it...
msgNode = self.find_output("msg")
if msgNode is not None:
# HACK: Some message nodes may need to sneakily send multiple messages. So, convert_message
# is therefore now a generator. We will ASSume that the first message generated is the
# primary msg that we should use for callbacks, if applicable
if inspect.isgeneratorfunction(msgNode.convert_message):
messages = tuple(msgNode.convert_message(exporter, so))
msg = messages[0]
for i in messages[1:]:
prepare_message(exporter, so, responder, commandMgr, waitOn, i)
else:
msg = msgNode.convert_message(exporter, so)
idx, command = prepare_message(exporter, so, responder, commandMgr, waitOn, 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 or \
self.find_output("reenable") is not None
if msgNode.has_callbacks and haveChildren:
childWaitOn = commandMgr.add_wait(idx)
msgNode.convert_callback_message(exporter, so, msg, responder.key, childWaitOn)
else:
childWaitOn = waitOn
else:
childWaitOn = waitOn
# If they linked us back to a condition or something that exports a LogicModifier, that
# means we need to reenable it here... NOTE: it would be incredibly stupid to do this
# if we're not waiting on anything to complete
if childWaitOn != -1:
for child in self.find_outputs("reenable"):
key = child.get_key(exporter, so)
if key is None:
continue
logicmod = key.object
if not isinstance(logicmod, plLogicModifier):
continue
logicmod.setLogicFlag(plLogicModifier.kOneShot, True)
# Yep, this is an entirely new ResponderCommand that sends a plEnableMsg
enableMsg = plEnableMsg()
enableMsg.addReceiver(key)
enableMsg.sender = responder.key
enableMsg.BCastFlags |= plMessage.kLocalPropagate
enableMsg.setCmd(plEnableMsg.kEnable, True)
logicCmdIdx, logicCmd = commandMgr.add_command(self, childWaitOn)
logicCmd.msg = enableMsg
# Export any child commands
for i in self.find_outputs("trigger", "PlasmaResponderCommandNode"):
i.convert_command(exporter, so, responder, commandMgr, childWaitOn)
class PlasmaRespCommandSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.451, 0.0, 0.263, 1.0)

Loading…
Cancel
Save