|
|
|
@ -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") |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
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,7 +540,12 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
|
|
|
|
|
logicmod.notify = self.generate_notify_msg(exporter, parent_so, "satisfies") |
|
|
|
|
|
|
|
|
|
# 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) |
|
|
|
|
volsens = volKey.object |
|
|
|
@ -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"} |
|
|
|
|