Browse Source

Merge pull request #107 from Hoikas/resp-nodes

Responder State IDs
pull/114/head
Adam Johnson 6 years ago committed by GitHub
parent
commit
a071dff261
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 39
      korman/nodes/node_core.py
  2. 11
      korman/nodes/node_deprecated.py
  3. 144
      korman/nodes/node_responder.py
  4. 3
      korman/properties/modifiers/region.py

39
korman/nodes/node_core.py

@ -113,12 +113,32 @@ class PlasmaNodeBase:
continue continue
yield node 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: for i in self.outputs:
if i.alias == key: if i.alias == key:
if spawn_empty and i.is_linked:
continue
return i return i
if spawn_empty:
return self._spawn_socket(key, options, self.outputs)
raise KeyError(key) 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): def harvest_actors(self):
return set() return set()
@ -133,7 +153,7 @@ class PlasmaNodeBase:
else: else:
in_socket = in_key in_socket = in_key
if isinstance(out_key, str): 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: else:
out_socket = out_key out_socket = out_key
link = self.id_data.links.new(in_socket, out_socket) link = self.id_data.links.new(in_socket, out_socket)
@ -145,7 +165,7 @@ class PlasmaNodeBase:
else: else:
in_socket = in_key in_socket = in_key
if isinstance(out_key, str): 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: else:
out_socket = out_key out_socket = out_key
link = self.id_data.links.new(in_socket, out_socket) link = self.id_data.links.new(in_socket, out_socket)
@ -185,6 +205,15 @@ class PlasmaNodeBase:
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))
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): def update(self):
"""Ensures that sockets are linked appropriately and there are enough inputs""" """Ensures that sockets are linked appropriately and there are enough inputs"""
input_defs, output_defs = self._socket_defs input_defs, output_defs = self._socket_defs
@ -255,8 +284,8 @@ class PlasmaNodeBase:
pass pass
continue continue
# If this is a multiple input node, make sure we have exactly one empty socket # If this is a spawn empty socket, 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 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] empty_sockets = [j for j in sockets if j.bl_idname == socket.bl_idname and not j.is_used]
if not empty_sockets: if not empty_sockets:
idx = len(sockets) idx = len(sockets)

11
korman/nodes/node_deprecated.py

@ -24,6 +24,15 @@ class PlasmaDeprecatedNode(PlasmaNodeBase):
raise NotImplementedError() 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): class PlasmaRespCommandSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.451, 0.0, 0.263, 1.0) bl_color = (0.451, 0.0, 0.263, 1.0)
@ -125,6 +134,8 @@ def _upgrade_node_trees(dummy):
if isinstance(node, PlasmaDeprecatedNode): if isinstance(node, PlasmaDeprecatedNode):
node.upgrade() node.upgrade()
nuke.append(node) nuke.append(node)
elif isinstance(node, PlasmaVersionedNode):
node.upgrade()
# toss deprecated nodes # toss deprecated nodes
for node in nuke: for node in nuke:
tree.nodes.remove(node) tree.nodes.remove(node)

144
korman/nodes/node_responder.py

@ -21,8 +21,9 @@ from PyHSPlasma import *
import uuid import uuid
from .node_core import * 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_category = "LOGIC"
bl_idname = "PlasmaResponderNode" bl_idname = "PlasmaResponderNode"
bl_label = "Responder" bl_label = "Responder"
@ -40,6 +41,8 @@ class PlasmaResponderNode(PlasmaNodeBase, bpy.types.Node):
no_ff_sounds = BoolProperty(name="Don't F-Fwd Sounds", no_ff_sounds = BoolProperty(name="Don't F-Fwd Sounds",
description="When fast-forwarding, play sound effects", description="When fast-forwarding, play sound effects",
default=False) default=False)
default_state = IntProperty(name="Default State Index",
options=set())
input_sockets = OrderedDict([ input_sockets = OrderedDict([
("condition", { ("condition", {
@ -55,9 +58,22 @@ class PlasmaResponderNode(PlasmaNodeBase, bpy.types.Node):
"type": "PlasmaPythonReferenceNodeSocket", "type": "PlasmaPythonReferenceNodeSocket",
"valid_link_nodes": {"PlasmaPythonFileNode"}, "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", { ("states", {
"text": "States", "text": "States",
"type": "PlasmaRespStateSocket", "type": "PlasmaRespStateSocket",
"hidden": True,
}), }),
]) ])
@ -80,6 +96,7 @@ class PlasmaResponderNode(PlasmaNodeBase, bpy.types.Node):
responder.flags |= plResponderModifier.kDetectUnTrigger responder.flags |= plResponderModifier.kDetectUnTrigger
if self.no_ff_sounds: if self.no_ff_sounds:
responder.flags |= plResponderModifier.kSkipFFSound responder.flags |= plResponderModifier.kSkipFFSound
responder.curState = self.default_state
class ResponderStateMgr: class ResponderStateMgr:
def __init__(self, respNode, respMod): def __init__(self, respNode, respMod):
@ -87,25 +104,58 @@ class PlasmaResponderNode(PlasmaNodeBase, bpy.types.Node):
self.parent = respNode self.parent = respNode
self.responder = respMod 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): def get_state(self, node):
for idx, (theNode, theState) in enumerate(self.states): for idx, (theNode, theState) in enumerate(self.states):
if theNode == node: if theNode == node:
return (idx, theState, True) return (idx, theState)
state = plResponderModifier_State() state = plResponderModifier_State()
self.states.append((node, state)) self.states.append((node, state))
return (len(self.states) - 1, state, False) return (len(self.states) - 1, state)
def save(self): def register_state(self, node):
resp = self.responder self.states.append((node, plResponderModifier_State()))
resp.clearStates()
for node, state in self.states:
resp.addState(state)
# Convert the Responder states # Convert the Responder states
stateMgr = ResponderStateMgr(self, responder) stateMgr = ResponderStateMgr(self, responder)
for stateNode in self.find_outputs("states", "PlasmaResponderStateNode"): for stateNode in self.find_outputs("state_refs", "PlasmaResponderStateNode"):
stateNode.convert_state(exporter, so, stateMgr) stateMgr.register_state(stateNode)
stateMgr.save() 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): class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
@ -113,16 +163,45 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
bl_idname = "PlasmaResponderStateNode" bl_idname = "PlasmaResponderStateNode"
bl_label = "Responder State" 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", default_state = BoolProperty(name="Default State",
description="This state is the responder's default", description="This state is the responder's default",
default=False) get=_get_default_state,
set=_set_default_state,
options=set())
input_sockets = OrderedDict([ input_sockets = OrderedDict([
("condition", { ("condition", {
"text": "Condition", "text": "Triggers State",
"type": "PlasmaRespStateSocket", "type": "PlasmaRespStateSocket",
"spawn_empty": True, "spawn_empty": True,
}), }),
("resp", {
"text": "Responder",
"type": "PlasmaRespStateRefSocket",
"valid_link_nodes": "PlasmaResponderNode",
"valid_link_sockets": "PlasmaRespStateRefSocket",
}),
]) ])
output_sockets = OrderedDict([ output_sockets = OrderedDict([
@ -141,30 +220,23 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
}), }),
("gotostate", { ("gotostate", {
"link_limit": 1, "link_limit": 1,
"text": "Trigger", "text": "Triggers State",
"type": "PlasmaRespStateSocket", "type": "PlasmaRespStateSocket",
}), }),
]) ])
def draw_buttons(self, context, layout): def draw_buttons(self, context, layout):
layout.active = self.find_input("resp") is not None
layout.prop(self, "default_state") layout.prop(self, "default_state")
def convert_state(self, exporter, so, stateMgr): def convert_state(self, exporter, so, state, idx, 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
# Where do we go from heah? # Where do we go from heah?
toStateNode = self.find_output("gotostate", "PlasmaResponderStateNode") toStateNode = self.find_output("gotostate", "PlasmaResponderStateNode")
if toStateNode is None: if toStateNode is None:
state.switchToState = idx state.switchToState = idx
else: else:
toIdx, toState, converted = stateMgr.get_state(toStateNode) toIdx, toState = stateMgr.get_state(toStateNode)
state.switchToState = toIdx state.switchToState = toIdx
if not converted:
toStateNode.convert_state(exporter, so, stateMgr)
class CommandMgr: class CommandMgr:
def __init__(self): def __init__(self):
@ -229,15 +301,21 @@ class PlasmaResponderStateNode(PlasmaNodeBase, bpy.types.Node):
for i in msgNode.find_outputs("msgs"): for i in msgNode.find_outputs("msgs"):
self._generate_command(exporter, so, responder, commandMgr, i, childWaitOn) 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): 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 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)

3
korman/properties/modifiers/region.py

@ -99,8 +99,7 @@ class PlasmaFootstepRegion(PlasmaModifierProperties, PlasmaModifierLogicWiz):
respmod.name = "Resp" respmod.name = "Resp"
respmod.link_input(volsens, "satisfies", "condition") respmod.link_input(volsens, "satisfies", "condition")
respstate = nodes.new("PlasmaResponderStateNode") respstate = nodes.new("PlasmaResponderStateNode")
respstate.link_input(respmod, "states", "condition") respstate.link_input(respmod, "state_refs", "resp")
respstate.default_state = True
# ArmatureEffectStateMsg # ArmatureEffectStateMsg
msg = nodes.new("PlasmaFootstepSoundMsgNode") msg = nodes.new("PlasmaFootstepSoundMsgNode")

Loading…
Cancel
Save