Browse Source

Fix #360.

This allows Region Sensor nodes to export
`plObjectInVolumeAndFacingDetector`, which only triggers the region
sensor when an avatar is in the region, facing a certain direction
(within a tolerance amount), and (optionally) moving forward.

BREAKING CHANGE: The Facing Target node can now only link to a single
condition node.
pull/366/head
Adam Johnson 2 years ago
parent
commit
6ccc51075d
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 127
      korman/nodes/node_conditions.py

127
korman/nodes/node_conditions.py

@ -13,11 +13,14 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
from bpy.props import *
from collections import OrderedDict
import math
from PyHSPlasma import *
from typing import *
from .node_core import *
from ..properties.modifiers.physics import bounds_types
@ -226,24 +229,85 @@ class PlasmaFacingTargetNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "CONDITIONS"
bl_idname = "PlasmaFacingTargetNode"
bl_label = "Facing Target"
bl_width_default = 200
directional = BoolProperty(name="Directional",
description="TODO",
default=True)
tolerance = IntProperty(name="Degrees",
description="How far away from the target the avatar can turn (in degrees)",
min=-180, max=180, default=45)
def _get_directional(self) -> bool:
output_node = self.find_output("satisfies")
if output_node is not None and output_node.bl_idname == "PlasmaVolumeSensorNode":
return True
return self.directional
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([
("satisfies", {
"text": "Satisfies",
"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):
layout.prop(self, "directional")
layout.prop(self, "tolerance")
self._draw(layout, sidebar=False)
def draw_buttons_ext(self, context, layout):
self._draw(layout, sidebar=True)
class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
@ -258,7 +322,7 @@ class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
layout.prop(self, "allow_simple", 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
if not self.enable_condition:
return
@ -266,26 +330,34 @@ class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
# First, gather the schtuff from the appropriate blah blah blah
if self.simple_mode:
node = self.node
directional = True
tolerance = 45
directional = False
moving_forward = False
tolerance = math.radians(45.0)
elif self.is_linked:
node = self.links[0].from_node
directional = node.directional
moving_forward = node.moving_forward
tolerance = node.tolerance
else:
# This is a programmer failure, so we need a traceback.
raise RuntimeError("Tried to export an unused PlasmaFacingTargetSocket")
if isinstance(logicmod, plLogicModifier):
facing_key = node._find_create_key(plFacingConditionalObject, exporter, bl=bo, so=so)
facing = facing_key.object
facing.directional = directional
facing.satisfied = True
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
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
def simple_mode(self):
@ -330,6 +402,16 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
bl_label = "Region Sensor"
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
pl_attrib = {"ptAttribActivator", "ptAttribActivatorList", "ptAttribNamedActivator"}
@ -348,9 +430,14 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
options={"ANIMATABLE", "ENUM_FLAG"},
items=[("kGroupAvatar", "Avatars", "Avatars trigger this region"),
("kGroupDynamic", "Dynamics", "Any non-avatar dynamic physical object (eg kickables)")],
default={"kGroupAvatar"})
default={"kGroupAvatar"},
update=_update_report_on)
input_sockets = OrderedDict([
("facing", {
"text": "Avatar Facing Target",
"type": "PlasmaFacingTargetSocket",
}),
("enter", {
"text": "Trigger on Enter",
"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):
layout.prop(self, "report_on")
@ -448,6 +540,11 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
logicmod.notify = self.generate_notify_msg(exporter, parent_so, "satisfies")
# Now, the detector objects
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)
@ -472,6 +569,10 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
def export_once(self):
return True
def harvest_actors(self):
if self.region_object and self.find_input_socket("facing").enable_condition:
yield self.region_object.name
@classmethod
def _idprop_mapping(cls):
return {"region_object": "region"}

Loading…
Cancel
Save