Browse Source

Merge remote-tracking branch 'hoikas/logic-nodes'

pull/6/head
Adam Johnson 10 years ago
parent
commit
41b53a9c31
  1. 3
      korman/__init__.py
  2. 6
      korman/exporter/convert.py
  3. 18
      korman/exporter/manager.py
  4. 2
      korman/exporter/rtlight.py
  5. 67
      korman/nodes/__init__.py
  6. 178
      korman/nodes/node_conditions.py
  7. 139
      korman/nodes/node_core.py
  8. 46
      korman/nodes/node_messages.py
  9. 234
      korman/nodes/node_responder.py
  10. 25
      korman/operators/op_modifier.py
  11. 15
      korman/properties/modifiers/base.py
  12. 19
      korman/properties/modifiers/logic.py
  13. 93
      korman/properties/modifiers/region.py
  14. 5
      korman/ui/modifiers/logic.py
  15. 4
      korman/ui/modifiers/region.py

3
korman/__init__.py

@ -16,6 +16,7 @@
import bpy import bpy
from . import exporter, render from . import exporter, render
from . import properties, ui from . import properties, ui
from . import nodes
from . import operators from . import operators
bl_info = { bl_info = {
@ -37,6 +38,7 @@ def register():
bpy.utils.register_module(__name__) bpy.utils.register_module(__name__)
# Sigh... Blender isn't totally automated. # Sigh... Blender isn't totally automated.
nodes.register()
operators.register() operators.register()
properties.register() properties.register()
@ -44,6 +46,7 @@ def register():
def unregister(): def unregister():
"""Unregisters all Blender operators and GUI items""" """Unregisters all Blender operators and GUI items"""
bpy.utils.unregister_module(__name__) bpy.utils.unregister_module(__name__)
nodes.unregister()
operators.unregister() operators.unregister()

6
korman/exporter/convert.py

@ -111,7 +111,7 @@ class Exporter:
# Instead of exporting a skeleton now, we'll just make an orphaned CI. # Instead of exporting a skeleton now, we'll just make an orphaned CI.
# The bl_obj export will make this work. # 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) parent_ci.addChild(so.key)
else: else:
self.report.warn("You have parented Plasma Object '{}' to '{}', which has not been marked for export. \ 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""" """Ensures that the SceneObject has a CoordinateInterface"""
if not so.coord: if not so.coord:
print(" Exporting CoordinateInterface") 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 # Now we have the "fun" work of filling in the CI
ci.localToWorld = utils.matrix44(bo.matrix_basis) ci.localToWorld = utils.matrix44(bo.matrix_basis)
@ -148,7 +148,7 @@ class Exporter:
# Create a sceneobject if one does not exist. # 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 # Before we call the export_fn, we need to determine if this object is an actor of any
# sort, and barf out a CI. # 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) self._export_actor(sceneobject, bl_obj)
export_fn(sceneobject, bl_obj) export_fn(sceneobject, bl_obj)

18
korman/exporter/manager.py

@ -91,7 +91,7 @@ class ExportManager:
if isinstance(pl, plObjInterface): if isinstance(pl, plObjInterface):
if so is None: if so is None:
key = self.find_key(bl, plSceneObject) key = self.find_key(plSceneObject, bl)
# prevent race conditions # prevent race conditions
if key is None: if key is None:
so = self.add_object(plSceneObject, name=name, loc=location) so = self.add_object(plSceneObject, name=name, loc=location)
@ -158,22 +158,26 @@ class ExportManager:
self._nodes[location] = None self._nodes[location] = None
return location return location
def find_create_key(self, bl_obj, pClass, so=None): def find_create_key(self, pClass, bl=None, name=None, so=None):
key = self.find_key(bl_obj, pClass, so=so) key = self.find_key(pClass, bl, name, so)
if key is None: 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 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""" """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: if so is None:
location = self._pages[bl_obj.plasma_object.page] location = self._pages[bl.plasma_object.page]
else: else:
location = so.key.location location = so.key.location
if name is None:
name = bl.name
index = plFactory.ClassIndex(pClass.__name__) index = plFactory.ClassIndex(pClass.__name__)
for key in self.mgr.getKeys(location, index): for key in self.mgr.getKeys(location, index):
if bl_obj.name == key.name: if name == key.name:
return key return key
return None return None

2
korman/exporter/rtlight.py

@ -92,7 +92,7 @@ class LightConverter:
def _create_light_key(self, bo, bl_light, so): def _create_light_key(self, bo, bl_light, so):
try: try:
xlate = _BL2PL[bl_light.type] 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: except LookupError:
raise BlenderOptionNotSupported("Object ('{}') lamp type '{}'".format(bo.name, bl_light.type)) raise BlenderOptionNotSupported("Object ('{}') lamp type '{}'".format(bo.name, bl_light.type))

67
korman/nodes/__init__.py

@ -0,0 +1,67 @@
# 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
import inspect
from nodeitems_utils import NodeCategory, NodeItem
import nodeitems_utils
# Put all Korman node modules here...
from .node_conditions import *
from .node_core import *
from .node_messages import *
from .node_responder import *
class PlasmaNodeCategory(NodeCategory):
"""Plasma Node Category"""
@classmethod
def poll(cls, context):
return (context.space_data.tree_type == "PlasmaNodeTree")
# Here's what you need to know about this...
# If you add a new category, put the pretty name here!
# If you're making a new Node, ensure that your bl_idname attribute is present AND matches
# the class name. Otherwise, absolutely fascinating things will happen. Don't expect for me
# to come and rescue you from it, either.
_kategory_names = {
"CONDITIONS": "Conditions",
"LOGIC": "Logic",
"MSG": "Message",
}
# Now, generate the categories as best we can...
_kategories = {}
for cls in dict(globals()).values():
if inspect.isclass(cls):
if not issubclass(cls, PlasmaNodeBase) or not issubclass(cls, bpy.types.Node):
continue
else:
continue
try:
_kategories[cls.bl_category].append(cls)
except LookupError:
_kategories[cls.bl_category] = [cls,]
_actual_kategories = []
for i in sorted(_kategories.keys(), key=lambda x: _kategory_names[x]):
# Note that even though we're sorting the category names, Blender appears to not care...
_kat_items = [NodeItem(j.bl_idname) for j in sorted(_kategories[i], key=lambda x: x.bl_label)]
_actual_kategories.append(PlasmaNodeCategory(i, _kategory_names[i], items=_kat_items))
def register():
nodeitems_utils.register_node_categories("PLASMA_NODES", _actual_kategories)
def unregister():
nodeitems_utils.unregister_node_categories("PLASMA_NODES")

178
korman/nodes/node_conditions.py

@ -0,0 +1,178 @@
# 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 PyHSPlasma import *
from .node_core import PlasmaNodeBase, PlasmaNodeSocketBase
from ..properties.modifiers.physics import bounds_types
class PlasmaConditionSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.188, 0.086, 0.349, 1.0)
class PlasmaVolumeReportNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "CONDITIONS"
bl_idname = "PlasmaVoumeReportNode"
bl_label = "Region Trigger Settings"
report_when = EnumProperty(name="When",
description="When the region should trigger",
items=[("each", "Each Event", "The region will trigger on every enter/exit"),
("first", "First Event", "The region will trigger on the first event only"),
("count", "Population", "When the region has a certain number of objects inside it")])
threshold = IntProperty(name="Threshold",
description="How many objects should be in the region for it to trigger",
min=1)
def init(self, context):
self.outputs.new("PlasmaVolumeSettingsSocketOut", "Trigger Settings")
def draw_buttons(self, context, layout):
layout.prop(self, "report_when")
if self.report_when == "count":
row = layout.row()
row.label("Threshold: ")
row.prop(self, "threshold", text="")
class PlasmaVolumeSensorNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "CONDITIONS"
bl_idname = "PlasmaVolumeSensorNode"
bl_label = "Region Sensor"
bl_width_default = 190
# Region Mesh
region = StringProperty(name="Region",
description="Object that defines the region mesh")
bounds = EnumProperty(name="Bounds",
description="Physical object's bounds",
items=bounds_types)
# Detector Properties
report_on = EnumProperty(name="Triggerers",
description="What triggers this region?",
options={"ANIMATABLE", "ENUM_FLAG"},
items=[("avatar", "Avatars", "Avatars trigger this region"),
("dynamics", "Dynamics", "Any non-avatar dynamic physical object (eg kickables)")],
default={"avatar"})
def init(self, context):
self.inputs.new("PlasmaVolumeSettingsSocketIn", "Trigger on Enter", "enter")
self.inputs.new("PlasmaVolumeSettingsSocketIn", "Trigger on Exit", "exit")
self.outputs.new("PlasmaConditionSocket", "Satisfies", "satisfies")
def draw_buttons(self, context, layout):
layout.prop(self, "report_on")
# Okay, if they changed the name of the ObData, that's THEIR problem...
layout.prop_search(self, "region", bpy.data, "objects", icon="MESH_DATA")
layout.prop(self, "bounds")
def export(self, exporter, tree, bo, so):
interface = exporter.mgr.add_object(plInterfaceInfoModifier, name=self.create_key_name(tree), so=so)
# Region Enters
enter_simple = self.find_input_socket("enter").allow
enter_settings = self.find_input("enter", "PlasmaVolumeReportNode")
if enter_simple or enter_settings is not None:
key = self._export_volume_event(exporter, tree, bo, so, plVolumeSensorConditionalObject.kTypeEnter, enter_settings)
interface.addIntfKey(key)
# Region Exits
exit_simple = self.find_input_socket("exit").allow
exit_settings = self.find_input("exit", "PlasmaVolumeReportNode")
if exit_simple or exit_settings is not None:
key = self._export_volume_event(exporter, tree, bo, so, plVolumeSensorConditionalObject.kTypeExit, exit_settings)
interface.addIntfKey(key)
# Don't forget to export the physical object itself!
# [trollface.jpg]
simIface, physical = exporter.physics.generate_physical(bo, so, "{}_VolumeSensor".format(bo.name))
phys_bo = bpy.data.objects[self.region]
exporter.physics.export(phys_bo, physical, self.bounds)
physical.memberGroup = plSimDefs.kGroupDetector
if "avatar" in self.report_on:
physical.reportGroup |= 1 << plSimDefs.kGroupAvatar
if "dynamics" in self.report_on:
physical.reportGroup |= 1 << plSimDefs.kGroupDynamic
def _export_volume_event(self, exporter, tree, bo, so, event, settings):
if event == plVolumeSensorConditionalObject.kTypeEnter:
suffix = "Enter"
else:
suffix = "Exit"
theName = "{}_{}_{}".format(tree.name, self.name, suffix)
print(" [LogicModifier '{}']".format(theName))
logicKey = exporter.mgr.find_create_key(plLogicModifier, name=theName, so=so)
logicmod = logicKey.object
logicmod.setLogicFlag(plLogicModifier.kMultiTrigger, True)
# LogicMod notification... This is one of the cases where the linked node needs to match
# exactly one key...
notify = plNotifyMsg()
notify.BCastFlags = (plMessage.kNetPropagate | plMessage.kLocalPropagate)
for i in self.find_outputs("satisfies"):
key = i.get_key(exporter, tree, so)
if key is None:
print(" WARNING: '{}' Node '{}' doesn't expose a key. It won't be triggered!".format(i.bl_idname, i.name))
else:
notify.addReceiver(key)
logicmod.notify = notify
# Now, the detector objects
print(" [ObjectInVolumeDetector '{}']".format(theName))
detKey = exporter.mgr.find_create_key(plObjectInVolumeDetector, name=theName, so=so)
det = detKey.object
print(" [VolumeSensorConditionalObject '{}']".format(theName))
volKey = exporter.mgr.find_create_key(plVolumeSensorConditionalObject, name=theName, so=so)
volsens = volKey.object
volsens.type = event
if settings is not None:
if settings.report_when == "first":
volsens.first = True
elif settings.report_when == "threshold":
volsens.trigNum = settings.threshold
# There appears to be a mandatory order for these keys...
det.addReceiver(volKey)
det.addReceiver(logicKey)
# End mandatory order
logicmod.addCondition(volKey)
return logicKey
class PlasmaVolumeSettingsSocket(PlasmaNodeSocketBase):
bl_color = (43.1, 24.7, 0.0, 1.0)
class PlasmaVolumeSettingsSocketIn(PlasmaVolumeSettingsSocket, bpy.types.NodeSocket):
allow = BoolProperty()
def draw(self, context, layout, node, text):
if not self.is_linked:
layout.prop(self, "allow", text="")
layout.label(text)
class PlasmaVolumeSettingsSocketOut(PlasmaVolumeSettingsSocket, bpy.types.NodeSocket):
pass

139
korman/nodes/node_core.py

@ -0,0 +1,139 @@
# 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 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:
if i.links:
node = i.links[0].from_node
if idname is not None and idname != node.bl_idname:
return None
return node
else:
return None
raise KeyError(key)
def find_input_socket(self, key):
for i in self.inputs:
if i.identifier == key:
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:
for j in i.links:
node = j.to_node
if idname is not None and idname != node.bl_idname:
continue
yield node
def find_output_socket(self, key):
for i in self.outputs:
if i.identifier == key:
return i
raise KeyError(key)
def link_input(self, tree, node, out_key, in_key):
"""Links a given Node's output socket to a given input socket on this Node"""
if isinstance(in_key, str):
in_socket = self.find_input_socket(in_key)
else:
in_socket = in_key
if isinstance(out_key, str):
out_socket = node.find_output_socket(out_key)
else:
out_socket = out_key
link = tree.links.new(in_socket, out_socket)
def link_output(self, tree, node, out_key, in_key):
"""Links a given Node's input socket to a given output socket on this Node"""
if isinstance(in_key, str):
in_socket = node.find_input_socket(in_key)
else:
in_socket = in_key
if isinstance(out_key, str):
out_socket = self.find_output_socket(out_key)
else:
out_socket = out_key
link = tree.links.new(in_socket, out_socket)
@classmethod
def poll(cls, context):
return (context.bl_idname == "PlasmaNodeTree")
class PlasmaNodeVariableInput(PlasmaNodeBase):
def ensure_sockets(self, idname, name, identifier=None):
"""Ensures there is one (and only one) empty input socket"""
empty = [i for i in self.inputs if i.bl_idname == idname and not i.links]
if not empty:
if identifier is None:
self.inputs.new(idname, name)
else:
self.inputs.new(idname, name, identifier)
while len(empty) > 1:
self.inputs.remove(empty.pop())
class PlasmaNodeSocketBase:
def draw(self, context, layout, node, text):
layout.label(text)
def draw_color(self, context, node):
# It's so tempting to just do RGB sometimes... Let's be nice.
if len(self.bl_color) == 3:
return tuple(self.bl_color[0], self.bl_color[1], self.bl_color[2], 1.0)
return self.bl_color
class PlasmaNodeTree(bpy.types.NodeTree):
bl_idname = "PlasmaNodeTree"
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")

46
korman/nodes/node_messages.py

@ -0,0 +1,46 @@
# 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 PyHSPlasma import *
from .node_core import *
from ..properties.modifiers.region import footstep_surfaces, footstep_surface_ids
class PlasmaMessageSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.004, 0.282, 0.349, 1.0)
class PlasmaFootstepSoundMsgNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "MSG"
bl_idname = "PlasmaFootstepSoundMsgNode"
bl_label = "Footstep Sound"
surface = EnumProperty(name="Surface",
description="What kind of surface are we walking on?",
items=footstep_surfaces,
default="stone")
def init(self, context):
self.inputs.new("PlasmaMessageSocket", "Sender", "sender")
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

234
korman/nodes/node_responder.py

@ -0,0 +1,234 @@
# 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 PyHSPlasma import *
import uuid
from .node_core import *
class PlasmaResponderNode(PlasmaNodeVariableInput, bpy.types.Node):
bl_category = "LOGIC"
bl_idname = "PlasmaResponderNode"
bl_label = "Responder"
bl_width_default = 145
detect_trigger = BoolProperty(name="Detect Trigger",
description="When notified, trigger the Responder",
default=True)
detect_untrigger = BoolProperty(name="Detect UnTrigger",
description="When notified, untrigger the Responder",
default=False)
no_ff_sounds = BoolProperty(name="Don't F-Fwd Sounds",
description="When fast-forwarding, play sound effects",
default=False)
def init(self, context):
self.inputs.new("PlasmaConditionSocket", "Condition", "condition")
self.outputs.new("PlasmaRespStateSocket", "States", "states")
def draw_buttons(self, context, layout):
self.ensure_sockets("PlasmaConditionSocket", "Condition", "condition")
layout.prop(self, "detect_trigger")
layout.prop(self, "detect_untrigger")
layout.prop(self, "no_ff_sounds")
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")
if self.detect_trigger:
responder.flags |= plResponderModifier.kDetectTrigger
if self.detect_untrigger:
responder.flags |= plResponderModifier.kDetectUnTrigger
if self.no_ff_sounds:
responder.flags |= plResponderModifier.kSkipFFSound
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"
bl_idname = "PlasmaResponderStateNode"
bl_label = "Responder State"
default_state = BoolProperty(name="Default State",
description="This state is the responder's default",
default=False)
def init(self, context):
self.inputs.new("PlasmaRespStateSocket", "Condition", "condition")
self.outputs.new("PlasmaRespCommandSocket", "Commands", "cmds")
self.outputs.new("PlasmaRespStateSocket", "Trigger", "gotostate").link_limit = 1
def draw_buttons(self, context, layout):
# This actually draws nothing, but it makes sure we have at least one empty input slot
# We need this because it's possible that multiple OTHER states can call us
self.ensure_sockets("PlasmaRespStateSocket", "Condition", "condition")
# 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)
class PlasmaResponderCommandNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "LOGIC"
bl_idname = "PlasmaResponderCommandNode"
bl_label = "Responder Command"
def init(self, context):
self.inputs.new("PlasmaRespCommandSocket", "Condition", "whodoneit")
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)

25
korman/operators/op_modifier.py

@ -15,6 +15,7 @@
import bpy import bpy
from bpy.props import * from bpy.props import *
import time
from ..properties import modifiers from ..properties import modifiers
@ -123,3 +124,27 @@ class ModifierMoveDownOperator(ModifierMoveOperator, bpy.types.Operator):
if self.active_modifier < last: if self.active_modifier < last:
self.swap_modifier_ids(plmods, self.active_modifier, self.active_modifier+1) self.swap_modifier_ids(plmods, self.active_modifier, self.active_modifier+1)
return {"FINISHED"} return {"FINISHED"}
class ModifierLogicWizOperator(ModifierOperator, bpy.types.Operator):
bl_idname = "object.plasma_logicwiz"
bl_label = "Plasma LogicWiz"
bl_description = "Generates logic nodes from a given modifier on the active object"
modifier = StringProperty(name="Modifier", default="footstep")
def execute(self, context):
obj = context.active_object
mod = getattr(obj.plasma_modifiers, self.modifier)
print("--- Plasma LogicWiz ---")
print("Object: '{}'".format(obj.name))
print("Modifier: '{}'".format(self.modifier))
if not mod.enabled:
print("WRN: This modifier is not actually enabled!")
start = time.process_time()
mod.logicwiz(obj)
end = time.process_time()
print("\nLogicWiz finished in {:.2f} seconds".format(end-start))
return {"FINISHED"}

15
korman/properties/modifiers/base.py

@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
import abc
import bpy import bpy
from bpy.props import * from bpy.props import *
@ -48,3 +49,17 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup):
"default": True, "default": True,
"options": {"HIDDEN"}}) "options": {"HIDDEN"}})
} }
class PlasmaModifierLogicWiz:
@property
def node_tree(self):
name = self.display_name
try:
return bpy.data.node_groups[name]
except LookupError:
return bpy.data.node_groups.new(name, "PlasmaNodeTree")
@abc.abstractmethod
def logicwiz(self, bo):
pass

19
korman/properties/modifiers/logic.py

@ -14,10 +14,29 @@
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
from bpy.props import *
from PyHSPlasma import * from PyHSPlasma import *
from .base import PlasmaModifierProperties from .base import PlasmaModifierProperties
class PlasmaAdvancedLogic(PlasmaModifierProperties):
pl_id = "advanced_logic"
bl_category = "Logic"
bl_label = "Advanced"
bl_description = "Plasma Logic Nodes"
bl_icon = "NODETREE"
tree_name = StringProperty(name="Node Tree", description="Plasma Logic Nodes")
def created(self, obj):
self.display_name = "Advanced Logic"
def export(self, exporter, bo, so):
tree = bpy.data.node_groups[self.tree_name]
tree.export(exporter, bo, so)
class PlasmaSpawnPoint(PlasmaModifierProperties): class PlasmaSpawnPoint(PlasmaModifierProperties):
pl_id = "spawnpoint" pl_id = "spawnpoint"

93
korman/properties/modifiers/region.py

@ -17,7 +17,98 @@ import bpy
from bpy.props import * from bpy.props import *
from PyHSPlasma import * from PyHSPlasma import *
from .base import PlasmaModifierProperties from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
from .physics import bounds_types
footstep_surface_ids = {
"dirt": 0,
# 1 = NULL
"puddle": 2,
# 3 = tile (NULL in MOUL)
"metal": 4,
"woodbridge": 5,
"rope": 6,
"grass": 7,
# 8 = NULL
"woodfloor": 9,
"rug": 10,
"stone": 11,
# 12 = NULL
# 13 = metal ladder (dupe of metal)
"woodladder": 14,
"water": 15,
# 16 = maintainer's glass (NULL in PotS)
# 17 = maintainer's metal grating (NULL in PotS)
# 18 = swimming (why would you want this?)
}
footstep_surfaces = [("dirt", "Dirt", "Dirt"),
("grass", "Grass", "Grass"),
("metal", "Metal", "Metal Catwalk"),
("puddle", "Puddle", "Shallow Water"),
("rope", "Rope", "Rope Ladder"),
("rug", "Rug", "Carpet Rug"),
("stone", "Stone", "Stone Tile"),
("water", "Water", "Deep Water"),
("woodbridge", "Wood Bridge", "Wood Bridge"),
("woodfloor", "Wood Floor", "Wood Floor"),
("woodladder", "Wood Ladder", "Wood Ladder")]
class PlasmaFootstepRegion(PlasmaModifierProperties, PlasmaModifierLogicWiz):
pl_id = "footstep"
bl_category = "Region"
bl_label = "Footstep"
bl_description = "Footstep Region"
surface = EnumProperty(name="Surface",
description="What kind of surface are we walking on?",
items=footstep_surfaces,
default="stone")
bounds = EnumProperty(name="Region Bounds",
description="Physical object's bounds",
items=bounds_types,
default="hull")
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
nodes.clear()
# Region Sensor
volsens = nodes.new("PlasmaVolumeSensorNode")
volsens.name = "RegionSensor"
volsens.region = bo.name
volsens.bounds = self.bounds
volsens.find_input_socket("enter").allow = True
volsens.find_input_socket("exit").allow = True
# Responder
respmod = nodes.new("PlasmaResponderNode")
respmod.name = "Resp"
respmod.link_input(tree, volsens, "satisfies", "condition")
respstate = nodes.new("PlasmaResponderStateNode")
respstate.link_input(tree, respmod, "states", "condition")
respstate.default_state = True
respcmd = nodes.new("PlasmaResponderCommandNode")
respcmd.link_input(tree, respstate, "cmds", "whodoneit")
# ArmatureEffectStateMsg
msg = nodes.new("PlasmaFootstepSoundMsgNode")
msg.link_input(tree, respcmd, "msg", "sender")
msg.surface = self.surface
class PlasmaPanicLinkRegion(PlasmaModifierProperties): class PlasmaPanicLinkRegion(PlasmaModifierProperties):
pl_id = "paniclink" pl_id = "paniclink"

5
korman/ui/modifiers/logic.py

@ -13,6 +13,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
def advanced_logic(modifier, layout, context):
layout.prop_search(modifier, "tree_name", bpy.data, "node_groups", icon="NODETREE")
def spawnpoint(modifier, layout, context): def spawnpoint(modifier, layout, context):
layout.label(text="The Y axis indicates the direction the avatar is facing.") layout.label(text="The Y axis indicates the direction the avatar is facing.")

4
korman/ui/modifiers/region.py

@ -13,6 +13,10 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
def footstep(modifier, layout, context):
layout.prop(modifier, "bounds")
layout.prop(modifier, "surface")
def paniclink(modifier, layout, context): def paniclink(modifier, layout, context):
split = layout.split() split = layout.split()
col = split.column() col = split.column()

Loading…
Cancel
Save