Browse Source

Responder Nodes v2

This introduces the ability to version nodes. Responders have been
bumped to version 2. The difference is that v2 responder nodes maintain
a list (hopefully) all responder states on the responder itself. Each
state has its own socket and displays its index for usage in python
scripts. For better predictability, any states that are only linked to
another state's gotostate socket will be exported at the end of the
state list.
pull/107/head
Adam Johnson 6 years ago
parent
commit
32dac5472f
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 39
      korman/nodes/node_core.py
  2. 11
      korman/nodes/node_deprecated.py
  3. 144
      korman/nodes/node_responder.py

39
korman/nodes/node_core.py

@ -113,12 +113,32 @@ class PlasmaNodeBase:
continue
yield node
def find_output_socket(self, key):
def find_output_socket(self, key, spawn_empty=False):
# In the case that this socket will be used to make new output linkage,
# we might want to allow the spawning of a new output socket... :)
# This will only be done if the node's socket definitions allow it.
options = self._socket_defs[1].get(key, {})
spawn_empty = spawn_empty and options.get("spawn_empty", False)
for i in self.outputs:
if i.alias == key:
if spawn_empty and i.is_linked:
continue
return i
if spawn_empty:
return self._spawn_socket(key, options, self.outputs)
raise KeyError(key)
def find_output_sockets(self, key, idname=None):
for i in self.outputs:
if i.alias == key:
if idname is None:
yield i
elif i.links:
node = i.links[0].from_node
if idname == node.bl_idname:
yield i
def harvest_actors(self):
return set()
@ -133,7 +153,7 @@ class PlasmaNodeBase:
else:
in_socket = in_key
if isinstance(out_key, str):
out_socket = node.find_output_socket(out_key)
out_socket = node.find_output_socket(out_key, spawn_empty=True)
else:
out_socket = out_key
link = self.id_data.links.new(in_socket, out_socket)
@ -145,7 +165,7 @@ class PlasmaNodeBase:
else:
in_socket = in_key
if isinstance(out_key, str):
out_socket = self.find_output_socket(out_key)
out_socket = self.find_output_socket(out_key, spawn_empty=True)
else:
out_socket = out_key
link = self.id_data.links.new(in_socket, out_socket)
@ -185,6 +205,15 @@ class PlasmaNodeBase:
direction = "->" if socket.is_output else "<-"
print("Removing {} {} {} {}".format(link.from_node.name, direction, link.to_node.name, reason))
def unlink_outputs(self, alias, reason=None):
links = self.id_data.links
from_socket = next((i for i in self.outputs if i.alias == alias))
i = 0
while i < len(from_socket.links):
link = from_socket.links[i]
self._tattle(from_socket, link, reason if reason else "socket unlinked")
links.remove(link)
def update(self):
"""Ensures that sockets are linked appropriately and there are enough inputs"""
input_defs, output_defs = self._socket_defs
@ -255,8 +284,8 @@ class PlasmaNodeBase:
pass
continue
# If this is a multiple input node, make sure we have exactly one empty socket
if (not socket.is_output and options.get("spawn_empty", False) and not socket.alias in done):
# If this is a spawn empty socket, make sure we have exactly one empty socket
if options.get("spawn_empty", False) and not socket.alias in done:
empty_sockets = [j for j in sockets if j.bl_idname == socket.bl_idname and not j.is_used]
if not empty_sockets:
idx = len(sockets)

11
korman/nodes/node_deprecated.py

@ -24,6 +24,15 @@ class PlasmaDeprecatedNode(PlasmaNodeBase):
raise NotImplementedError()
class PlasmaVersionedNode(PlasmaNodeBase):
@classmethod
def register(cls):
cls.version = IntProperty(name="Node Version", default=1, options=set())
def upgrade(self, from_version):
raise NotImplementedError()
class PlasmaRespCommandSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.451, 0.0, 0.263, 1.0)
@ -125,6 +134,8 @@ def _upgrade_node_trees(dummy):
if isinstance(node, PlasmaDeprecatedNode):
node.upgrade()
nuke.append(node)
elif isinstance(node, PlasmaVersionedNode):
node.upgrade()
# toss deprecated nodes
for node in nuke:
tree.nodes.remove(node)

144
korman/nodes/node_responder.py

@ -21,8 +21,9 @@ from PyHSPlasma import *
import uuid
from .node_core import *
from .node_deprecated import PlasmaVersionedNode
class PlasmaResponderNode(PlasmaNodeBase, bpy.types.Node):
class PlasmaResponderNode(PlasmaVersionedNode, bpy.types.Node):
bl_category = "LOGIC"
bl_idname = "PlasmaResponderNode"
bl_label = "Responder"
@ -40,6 +41,8 @@ class PlasmaResponderNode(PlasmaNodeBase, bpy.types.Node):
no_ff_sounds = BoolProperty(name="Don't F-Fwd Sounds",
description="When fast-forwarding, play sound effects",
default=False)
default_state = IntProperty(name="Default State Index",
options=set())
input_sockets = OrderedDict([
("condition", {
@ -55,9 +58,22 @@ class PlasmaResponderNode(PlasmaNodeBase, bpy.types.Node):
"type": "PlasmaPythonReferenceNodeSocket",
"valid_link_nodes": {"PlasmaPythonFileNode"},
}),
("state_refs", {
"text": "State",
"type": "PlasmaRespStateRefSocket",
"valid_link_nodes": "PlasmaResponderStateNode",
"valid_link_sockets": "PlasmaRespStateRefSocket",
"link_limit": 1,
"spawn_empty": True,
}),
# This version of the states socket has been deprecated.
# We need to be able to track 1 socket -> 1 state to manage
# responder state IDs
("states", {
"text": "States",
"type": "PlasmaRespStateSocket",
"hidden": True,
}),
])
@ -80,6 +96,7 @@ class PlasmaResponderNode(PlasmaNodeBase, bpy.types.Node):
responder.flags |= plResponderModifier.kDetectUnTrigger
if self.no_ff_sounds:
responder.flags |= plResponderModifier.kSkipFFSound
responder.curState = self.default_state
class ResponderStateMgr:
def __init__(self, respNode, respMod):
@ -87,25 +104,58 @@ class PlasmaResponderNode(PlasmaNodeBase, bpy.types.Node):
self.parent = respNode
self.responder = respMod
def convert_states(self, exporter, so):
# This could implicitly export more states...
i = 0
while i < len(self.states):
node, state = self.states[i]
node.convert_state(exporter, so, state, i, self)
i += 1
resp = self.responder
resp.clearStates()
for node, state in self.states:
resp.addState(state)
def get_state(self, node):
for idx, (theNode, theState) in enumerate(self.states):
if theNode == node:
return (idx, theState, True)
return (idx, theState)
state = plResponderModifier_State()
self.states.append((node, state))
return (len(self.states) - 1, state, False)
return (len(self.states) - 1, state)
def save(self):
resp = self.responder
resp.clearStates()
for node, state in self.states:
resp.addState(state)
def register_state(self, node):
self.states.append((node, plResponderModifier_State()))
# Convert the Responder states
stateMgr = ResponderStateMgr(self, responder)
for stateNode in self.find_outputs("states", "PlasmaResponderStateNode"):
stateNode.convert_state(exporter, so, stateMgr)
stateMgr.save()
for stateNode in self.find_outputs("state_refs", "PlasmaResponderStateNode"):
stateMgr.register_state(stateNode)
stateMgr.convert_states(exporter, so)
def upgrade(self):
# In version 1 responder nodes, responder states could be linked to the responder
# or to subsequent responder state nodes and be exported. The problem with this
# is that to use responder states in Python attributes, we need to be able to
# inform the user as to what the ID of the responder state will be.
# Version 2 make it slightly more mandatory that states be linked to a responder
# and will display the ID of each state linked to the responder. Any states only
# linked to other states will be converted at the end of the list.
if self.version == 1:
states = set()
def _link_states(state):
if state in states:
return
states.add(state)
self.link_output(state, "state_refs", "resp")
goto = state.find_output("gotostate")
if goto is not None:
_link_states(goto)
for i in self.find_outputs("states"):
_link_states(i)
self.unlink_outputs("states", "socket deprecated (upgrade complete)")
self.version = 2
class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
@ -113,16 +163,45 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
bl_idname = "PlasmaResponderStateNode"
bl_label = "Responder State"
def _get_default_state(self):
resp_node = self.find_input("resp")
if resp_node is not None:
try:
state_idx = next((idx for idx, node in enumerate(resp_node.find_outputs("state_refs")) if node == self))
except StopIteration:
return False
else:
return resp_node.default_state == state_idx
return False
def _set_default_state(self, value):
if value:
resp_node = self.find_input("resp")
if resp_node is not None:
try:
state_idx = next((idx for idx, node in enumerate(resp_node.find_outputs("state_refs")) if node == self))
except StopIteration:
self._whine("unable to set default state on responder")
else:
resp_node.default_state = state_idx
default_state = BoolProperty(name="Default State",
description="This state is the responder's default",
default=False)
get=_get_default_state,
set=_set_default_state,
options=set())
input_sockets = OrderedDict([
("condition", {
"text": "Condition",
"text": "Triggers State",
"type": "PlasmaRespStateSocket",
"spawn_empty": True,
}),
("resp", {
"text": "Responder",
"type": "PlasmaRespStateRefSocket",
"valid_link_nodes": "PlasmaResponderNode",
"valid_link_sockets": "PlasmaRespStateRefSocket",
}),
])
output_sockets = OrderedDict([
@ -141,30 +220,23 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
}),
("gotostate", {
"link_limit": 1,
"text": "Trigger",
"text": "Triggers State",
"type": "PlasmaRespStateSocket",
}),
])
def draw_buttons(self, context, layout):
layout.active = self.find_input("resp") is not None
layout.prop(self, "default_state")
def convert_state(self, exporter, so, stateMgr):
idx, state, converted = stateMgr.get_state(self)
# No sanity checking here. Hopefully nothing crazy has happened in the UI.
if self.default_state:
stateMgr.responder.curState = idx
def convert_state(self, exporter, so, state, idx, stateMgr):
# Where do we go from heah?
toStateNode = self.find_output("gotostate", "PlasmaResponderStateNode")
if toStateNode is None:
state.switchToState = idx
else:
toIdx, toState, converted = stateMgr.get_state(toStateNode)
toIdx, toState = stateMgr.get_state(toStateNode)
state.switchToState = toIdx
if not converted:
toStateNode.convert_state(exporter, so, stateMgr)
class CommandMgr:
def __init__(self):
@ -229,15 +301,21 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
for i in msgNode.find_outputs("msgs"):
self._generate_command(exporter, so, responder, commandMgr, i, childWaitOn)
def update(self):
super().update()
# Check to see if we're the default state
if not self.default_state:
inputs = list(self.find_input_sockets("condition", "PlasmaResponderNode"))
if len(inputs) == 1:
self.default_state = True
class PlasmaRespStateSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.388, 0.78, 0.388, 1.0)
class PlasmaRespStateRefSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (1.00, 0.980, 0.322, 1.0)
def draw(self, context, layout, node, text):
if isinstance(node, PlasmaResponderNode):
try:
idx = next((idx for idx, socket in enumerate(node.find_output_sockets("state_refs")) if socket == self))
except StopIteration:
layout.label(text)
else:
layout.label("State (ID: {})".format(idx))
else:
layout.label(text)

Loading…
Cancel
Save