Browse Source

Merge pull request #366 from Hoikas/volume_facing

Allow facing detectors for region sensors.
pull/369/head
Adam Johnson 2 years ago committed by GitHub
parent
commit
ba6c89eb62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 141
      korman/nodes/node_conditions.py
  2. 18
      korman/nodes/node_core.py

141
korman/nodes/node_conditions.py

@ -13,11 +13,14 @@
# 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/>.
from __future__ import annotations
import bpy import bpy
from bpy.props import * from bpy.props import *
from collections import OrderedDict from collections import OrderedDict
import math import math
from PyHSPlasma import * from PyHSPlasma import *
from typing import *
from .node_core import * from .node_core import *
from ..properties.modifiers.physics import bounds_types from ..properties.modifiers.physics import bounds_types
@ -226,24 +229,85 @@ class PlasmaFacingTargetNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "CONDITIONS" bl_category = "CONDITIONS"
bl_idname = "PlasmaFacingTargetNode" bl_idname = "PlasmaFacingTargetNode"
bl_label = "Facing Target" bl_label = "Facing Target"
bl_width_default = 200
directional = BoolProperty(name="Directional", def _get_directional(self) -> bool:
description="TODO", output_node = self.find_output("satisfies")
default=True) if output_node is not None and output_node.bl_idname == "PlasmaVolumeSensorNode":
tolerance = IntProperty(name="Degrees", return True
description="How far away from the target the avatar can turn (in degrees)", return self.directional
min=-180, max=180, default=45)
def _set_directional(self, value: bool):
self.directional = value
# Volume Sensors use view ortientation testing, so if we're connected to a volume sensor node,
# then we need to indicate that in the UI.
directional = BoolProperty(default=False, options={"HIDDEN"})
directional_ui = BoolProperty(name="Directional",
description="Use the object's orientation for facing tests",
get=_get_directional, set=_set_directional)
def _get_moving_forward(self) -> bool:
output_node = self.find_output("satisfies")
if output_node is not None and output_node.bl_idname != "PlasmaVolumeSensorNode":
return False
return self.moving_forward
def _set_moving_forward(self, value: bool):
self.moving_forward = value
# Only volume sensors allow the moving forward setting, so show this as disabled for connections
# that are not volume sensors.
moving_forward = BoolProperty(options={"HIDDEN"})
moving_forward_ui = BoolProperty(name="Forward Motion",
description="The player must be moving forward (eg walking) for the condition to trigger",
get=_get_moving_forward, set=_set_moving_forward)
def _get_tolerance(self) -> float:
return math.radians(self.tolerance)
def _set_tolerance(self, value: float) -> None:
self.tolerance = math.degrees(value)
# Legacy storage property... this exists as the storage for the tolerance value to prevent
# breaking old blend files. NOTE: This property is stored in degrees.
tolerance = FloatProperty(min=-180.0, max=180.0, default=45.0, options={"HIDDEN"})
# New property for usage in the UI. NOTE: This property is stored in radians.
tolerance_ui = FloatProperty(name="Tolerance",
description="How far away from the target the avatar can turn",
min=-180.0, max=180.0, precision=0,
get=_get_tolerance, set=_set_tolerance,
subtype="ANGLE", options=set())
output_sockets = OrderedDict([ output_sockets = OrderedDict([
("satisfies", { ("satisfies", {
"text": "Satisfies", "text": "Satisfies",
"type": "PlasmaFacingTargetSocket", "type": "PlasmaFacingTargetSocket",
"link_limit": 1,
}), }),
]) ])
def _draw_sub_prop(self, layout, prop_name, *, active=True, sidebar=False, **kwargs):
sub = layout.row() if sidebar else layout.column()
sub.enabled = active
sub.prop(self, prop_name, **kwargs)
def _draw(self, layout, *, sidebar):
output_node = self.find_output("satisfies")
is_regular_condition = output_node is None or output_node.bl_idname != "PlasmaVolumeSensorNode"
is_volume_sensor = output_node is None or output_node.bl_idname == "PlasmaVolumeSensorNode"
sub = layout if sidebar else layout.split()
self._draw_sub_prop(sub, "directional_ui", active=is_regular_condition)
self._draw_sub_prop(sub, "moving_forward_ui", active=is_volume_sensor, text="Moving")
layout.prop(self, "tolerance_ui")
def draw_buttons(self, context, layout): def draw_buttons(self, context, layout):
layout.prop(self, "directional") self._draw(layout, sidebar=False)
layout.prop(self, "tolerance")
def draw_buttons_ext(self, context, layout):
self._draw(layout, sidebar=True)
class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket): class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
@ -258,7 +322,7 @@ class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
layout.prop(self, "allow_simple", text="") layout.prop(self, "allow_simple", text="")
layout.label(text) layout.label(text)
def convert_subcondition(self, exporter, bo, so, logicmod): def convert_subcondition(self, exporter, bo, so, logicmod: Union[plLogicModifier, plObjectInVolumeAndFacingDetector]):
assert not self.is_output assert not self.is_output
if not self.enable_condition: if not self.enable_condition:
return return
@ -266,26 +330,34 @@ class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
# First, gather the schtuff from the appropriate blah blah blah # First, gather the schtuff from the appropriate blah blah blah
if self.simple_mode: if self.simple_mode:
node = self.node node = self.node
directional = True directional = False
tolerance = 45 moving_forward = False
tolerance = math.radians(45.0)
elif self.is_linked: elif self.is_linked:
node = self.links[0].from_node node = self.links[0].from_node
directional = node.directional directional = node.directional
moving_forward = node.moving_forward
tolerance = node.tolerance tolerance = node.tolerance
else: else:
# This is a programmer failure, so we need a traceback. # This is a programmer failure, so we need a traceback.
raise RuntimeError("Tried to export an unused PlasmaFacingTargetSocket") raise RuntimeError("Tried to export an unused PlasmaFacingTargetSocket")
facing_key = node._find_create_key(plFacingConditionalObject, exporter, bl=bo, so=so) if isinstance(logicmod, plLogicModifier):
facing = facing_key.object facing_key = node._find_create_key(plFacingConditionalObject, exporter, bl=bo, so=so)
facing.directional = directional facing = facing_key.object
facing.satisfied = True facing.directional = directional
facing.tolerance = math.radians(tolerance) facing.satisfied = True
logicmod.addCondition(facing_key) facing.tolerance = math.radians(tolerance)
logicmod.addCondition(facing_key)
elif isinstance(logicmod, plObjectInVolumeAndFacingDetector):
logicmod.facingTolerance = math.cos(math.radians(tolerance))
logicmod.needWalkingForward = moving_forward
else:
raise ValueError("logicmod")
@property @property
def enable_condition(self): def enable_condition(self):
return ((self.simple_mode and self.allow_simple) or self.is_linked) return self.enabled and ((self.simple_mode and self.allow_simple) or self.is_linked)
@property @property
def simple_mode(self): def simple_mode(self):
@ -330,6 +402,16 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
bl_label = "Region Sensor" bl_label = "Region Sensor"
bl_width_default = 190 bl_width_default = 190
def _update_report_on(self, context):
# Facing target properties only make sense if we trigger on avatars being present
# in the region. Furthermore, the engine explicitly disallows other physicals triggering
# the region sensor if the facing condition is enabled. So, remove the facing option if
# avatars are not selected or if dynamics are selected. NOTE: The socket is hidden
# when it is disabled.
include_avatars = "kGroupAvatar" in self.report_on
include_physicals = "kGroupDynamic" in self.report_on
self.find_input_socket("facing").enabled = include_avatars and not include_physicals
# These are the Python attributes we can fill in # These are the Python attributes we can fill in
pl_attrib = {"ptAttribActivator", "ptAttribActivatorList", "ptAttribNamedActivator"} pl_attrib = {"ptAttribActivator", "ptAttribActivatorList", "ptAttribNamedActivator"}
@ -348,9 +430,14 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
options={"ANIMATABLE", "ENUM_FLAG"}, options={"ANIMATABLE", "ENUM_FLAG"},
items=[("kGroupAvatar", "Avatars", "Avatars trigger this region"), items=[("kGroupAvatar", "Avatars", "Avatars trigger this region"),
("kGroupDynamic", "Dynamics", "Any non-avatar dynamic physical object (eg kickables)")], ("kGroupDynamic", "Dynamics", "Any non-avatar dynamic physical object (eg kickables)")],
default={"kGroupAvatar"}) default={"kGroupAvatar"},
update=_update_report_on)
input_sockets = OrderedDict([ input_sockets = OrderedDict([
("facing", {
"text": "Avatar Facing Target",
"type": "PlasmaFacingTargetSocket",
}),
("enter", { ("enter", {
"text": "Trigger on Enter", "text": "Trigger on Enter",
"type": "PlasmaVolumeSettingsSocketIn", "type": "PlasmaVolumeSettingsSocketIn",
@ -376,6 +463,11 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
}), }),
]) ])
def init(self, context):
# The default value for the facing socket is a bit silly for this node type.
# Reset it to False.
self.find_input_socket("facing").allow_simple = False
def draw_buttons(self, context, layout): def draw_buttons(self, context, layout):
layout.prop(self, "report_on") layout.prop(self, "report_on")
@ -448,7 +540,12 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
logicmod.notify = self.generate_notify_msg(exporter, parent_so, "satisfies") logicmod.notify = self.generate_notify_msg(exporter, parent_so, "satisfies")
# Now, the detector objects # Now, the detector objects
det = self._find_create_object(plObjectInVolumeDetector, exporter, suffix=suffix, bl=region_bo, so=region_so) facing_socket: PlasmaFacingTargetSocket = self.find_input_socket("facing")
if facing_socket.enable_condition:
det = self._find_create_object(plObjectInVolumeAndFacingDetector, exporter, suffix=suffix, bl=region_bo, so=region_so)
facing_socket.convert_subcondition(exporter, region_bo, region_so, det)
else:
det = self._find_create_object(plObjectInVolumeDetector, exporter, suffix=suffix, bl=region_bo, so=region_so)
volKey = self._find_create_key(plVolumeSensorConditionalObject, exporter, suffix=suffix, bl=region_bo, so=region_so) volKey = self._find_create_key(plVolumeSensorConditionalObject, exporter, suffix=suffix, bl=region_bo, so=region_so)
volsens = volKey.object volsens = volKey.object
@ -472,6 +569,10 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
def export_once(self): def export_once(self):
return True return True
def harvest_actors(self):
if self.region_object and self.find_input_socket("facing").enable_condition:
yield self.region_object.name
@classmethod @classmethod
def _idprop_mapping(cls): def _idprop_mapping(cls):
return {"region_object": "region"} return {"region_object": "region"}

18
korman/nodes/node_core.py

@ -110,15 +110,19 @@ class PlasmaNodeBase:
options = self._socket_defs[0].get(key, {}) options = self._socket_defs[0].get(key, {})
spawn_empty = spawn_empty and options.get("spawn_empty", False) spawn_empty = spawn_empty and options.get("spawn_empty", False)
for i in self.inputs: matching_sockets = filter(lambda x: x.alias == key, self.inputs)
if i.alias == key:
if spawn_empty and i.is_linked:
continue
return i
if spawn_empty: if spawn_empty:
unused_socket = next(filter(lambda x: not x.is_linked, matching_sockets), None)
if unused_socket is not None:
return unused_socket
return self._spawn_socket(key, options, self.inputs) return self._spawn_socket(key, options, self.inputs)
else:
raise KeyError(key) matching_socket = next(matching_sockets, None)
if matching_socket is not None:
return matching_socket
if options:
return self._spawn_socket(key, options, self.inputs)
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:

Loading…
Cancel
Save