@ -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 " }