# 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 . import bpy from bpy.props import * import mathutils from PyHSPlasma import * from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz from ...exporter.explosions import ExportError from ...helpers import find_modifier from ... import idprops class PlasmaLadderModifier(PlasmaModifierProperties): pl_id = "laddermod" bl_category = "Avatar" bl_label = "Ladder" bl_description = "Climbable Ladder" bl_icon = "COLLAPSEMENU" is_enabled = BoolProperty(name="Enabled", description="Ladder enabled by default at Age start", default=True) direction = EnumProperty(name="Direction", description="Direction of climb", items=[("UP", "Up", "The avatar will mount the ladder and climb upward"), ("DOWN", "Down", "The avatar will mount the ladder and climb downward"),], default="DOWN") num_loops = IntProperty(name="Loops", description="How many full animation loops after the first to play before dismounting", min=0, default=4) facing_object = PointerProperty(name="Facing Object", description="Target object the avatar must be facing through this region to trigger climb (optional)", type=bpy.types.Object, poll=idprops.poll_mesh_objects) def export(self, exporter, bo, so): # Create the ladder modifier mod = exporter.mgr.find_create_object(plAvLadderMod, so=so, name=self.key_name) mod.type = plAvLadderMod.kBig mod.loops = self.num_loops mod.enabled = self.is_enabled mod.goingUp = self.direction == "UP" # Create vector pointing from the Facing Object to the Detector. # Animation only activates if the avatar is facing it within # engine-defined (45 degree) tolerance if self.facing_object is not None: # Use object if one has been selected ladderVec = self.facing_object.matrix_world.translation - bo.matrix_world.translation else: # Make our own artificial target -1.0 units back on the local Y axis. ladderVec = mathutils.Vector((0, -1, 0)) * bo.matrix_world.inverted() mod.ladderView = hsVector3(ladderVec.x, ladderVec.y, 0.0) mod.ladderView.normalize() # Generate the detector's physical bounds bounds = "hull" if not bo.plasma_modifiers.collision.enabled else bo.plasma_modifiers.collision.bounds exporter.physics.generate_physical(bo, so, bounds=bounds, member_group="kGroupDetector", report_groups=["kGroupAvatar"], properties=["kPinned"]) @property def requires_actor(self): return True sitting_approach_flags = [("kApproachFront", "Front", "Approach from the font"), ("kApproachLeft", "Left", "Approach from the left"), ("kApproachRight", "Right", "Approach from the right"), ("kApproachRear", "Rear", "Approach from the rear guard")] class PlasmaSittingBehavior(idprops.IDPropObjectMixin, PlasmaModifierProperties, PlasmaModifierLogicWiz): pl_id = "sittingmod" bl_category = "Avatar" bl_label = "Sitting Behavior" bl_description = "Avatar sitting position" approach = EnumProperty(name="Approach", description="Directions an avatar can approach the seat from", items=sitting_approach_flags, default={"kApproachFront", "kApproachLeft", "kApproachRight"}, options={"ENUM_FLAG"}) clickable_object = PointerProperty(name="Clickable", description="Object that defines the clickable area", type=bpy.types.Object, poll=idprops.poll_mesh_objects) region_object = PointerProperty(name="Region", description="Object that defines the region mesh", type=bpy.types.Object, poll=idprops.poll_mesh_objects) facing_enabled = BoolProperty(name="Avatar Facing", description="The avatar must be facing the clickable's Y-axis", default=True) facing_degrees = IntProperty(name="Tolerance", description="How far away we will tolerate the avatar facing the clickable", min=-180, max=180, default=45) def harvest_actors(self): if self.facing_enabled: yield self.clickable_object.name def logicwiz(self, bo, tree): nodes = tree.nodes # Sitting Modifier sittingmod = nodes.new("PlasmaSittingBehaviorNode") sittingmod.approach = self.approach sittingmod.name = "SittingBeh" # Clickable clickable = nodes.new("PlasmaClickableNode") clickable.link_output(sittingmod, "satisfies", "condition") clickable.clickable_object = self.clickable_object clickable.bounds = find_modifier(self.clickable_object, "collision").bounds # Avatar Region (optional) region_phys = find_modifier(self.region_object, "collision") if region_phys is not None: region = nodes.new("PlasmaClickableRegionNode") region.link_output(clickable, "satisfies", "region") region.name = "ClickableAvRegion" region.region_object = self.region_object region.bounds = region_phys.bounds # Facing Target (optional) if self.facing_enabled: facing = nodes.new("PlasmaFacingTargetNode") facing.link_output(clickable, "satisfies", "facing") facing.name = "FacingClickable" facing.directional = True facing.tolerance = self.facing_degrees else: # this socket must be explicitly disabled, otherwise it automatically generates a default # facing target conditional for us. isn't that nice? clickable.find_input_socket("facing").allow_simple = False @classmethod def _idprop_mapping(cls): return {"clickable_object": "clickable_obj", "region_object": "region_obj"} @property def key_name(self): return "{}_SitBeh".format(self.id_data.name) @property def requires_actor(self): # This should be an empty, really... return True def sanity_check(self): # The user absolutely MUST specify a clickable or this won't export worth crap. if self.clickable_object is None: raise ExportError("'{}': Sitting Behavior's clickable object is invalid".format(self.key_name))