diff --git a/korman/exporter/utils.py b/korman/exporter/utils.py index 12db7e8..872cb6e 100644 --- a/korman/exporter/utils.py +++ b/korman/exporter/utils.py @@ -20,6 +20,7 @@ import bpy import mathutils from contextlib import contextmanager +import enum from typing import * from PyHSPlasma import * @@ -122,7 +123,12 @@ def create_camera_object(name: str) -> bpy.types.Object: bpy.context.scene.objects.link(cam_obj) return cam_obj -def create_cube_region(name: str, size: float, owner_object: bpy.types.Object) -> bpy.types.Object: +class CubeRegionOrigin(enum.Enum): + center = enum.auto() + bottom = enum.auto() + + +def create_cube_region(name: str, size: float, owner_object: bpy.types.Object, origin: CubeRegionOrigin = CubeRegionOrigin.center) -> bpy.types.Object: """Create a cube shaped region object""" region_object = BMeshObject(name) region_object.plasma_object.enabled = True @@ -130,11 +136,12 @@ def create_cube_region(name: str, size: float, owner_object: bpy.types.Object) - region_object.hide_render = True with region_object as bm: bmesh.ops.create_cube(bm, size=(size)) + origin = owner_object.matrix_world.translation - region_object.matrix_world.translation + if origin == CubeRegionOrigin.bottom: + origin.z += size * 0.5 bmesh.ops.transform( bm, - matrix=mathutils.Matrix.Translation( - owner_object.matrix_world.translation - region_object.matrix_world.translation - ), + matrix=mathutils.Matrix.Translation(origin), space=region_object.matrix_world, verts=bm.verts ) return region_object.release() diff --git a/korman/properties/modifiers/logic.py b/korman/properties/modifiers/logic.py index 1dec936..b4daeeb 100644 --- a/korman/properties/modifiers/logic.py +++ b/korman/properties/modifiers/logic.py @@ -13,16 +13,26 @@ # You should have received a copy of the GNU General Public License # along with Korman. If not, see . +from __future__ import annotations + import bmesh import bpy from bpy.props import * import mathutils from PyHSPlasma import * +from typing import * + +if TYPE_CHECKING: + from ...exporter import Exporter + from ...nodes.node_conditions import * + from ...nodes.node_messages import * + from ...nodes.node_responder import * from ...addon_prefs import game_versions from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz from ...exporter import ExportError, utils from ... import idprops +from .physics import bounds_type_index, bounds_type_str, bounds_types class PlasmaVersionedNodeTree(idprops.IDPropMixin, bpy.types.PropertyGroup): version = EnumProperty(name="Version", @@ -76,7 +86,7 @@ class PlasmaAdvancedLogic(PlasmaModifierProperties): return any((i.node_tree.requires_actor for i in self.logic_groups if i.node_tree)) -class PlasmaSpawnPoint(PlasmaModifierProperties): +class PlasmaSpawnPoint(PlasmaModifierProperties, PlasmaModifierLogicWiz): pl_id = "spawnpoint" bl_category = "Logic" @@ -84,10 +94,90 @@ class PlasmaSpawnPoint(PlasmaModifierProperties): bl_description = "Point at which avatars link into the Age" bl_object_types = {"EMPTY"} + def _get_bounds(self) -> int: + if self.exit_region is not None: + return bounds_type_index(self.exit_region.plasma_modifiers.collision.bounds_type) + return bounds_type_index("hull") + + def _set_bounds(self, value: int) -> None: + if self.exit_region is not None: + self.exit_region.plasma_modifiers.collision.bounds_type = bounds_type_str(value) + + entry_camera = PointerProperty( + name="Entry Camera", + description="Camera to use when the player spawns at this location", + type=bpy.types.Object, + poll=idprops.poll_camera_objects + ) + + exit_region = PointerProperty( + name="Exit Region", + description="Pop the camera when the player exits this region", + type=bpy.types.Object, + poll=idprops.poll_mesh_objects + ) + + bounds_type = EnumProperty( + name="Bounds", + description="", + items=bounds_types, + get=_get_bounds, + set=_set_bounds, + default="hull" + ) + + def pre_export(self, exporter: Exporter, bo: bpy.types.Object) -> None: + if self.entry_camera is None: + return + + if self.exit_region is None: + self.exit_region = yield utils.create_cube_region( + f"{self.key_name}_ExitRgn", 6.0, + bo, utils.CubeRegionOrigin.bottom + ) + + yield self.convert_logic(bo) + + def logicwiz(self, bo, tree): + nodes = tree.nodes + + # Generate two responders. The first responder will push the entry camera + # onto the camera stack when we first enter the region - usually on link in. + # Then, disable the entry sensor once it's fired. The second responder will + # always pop the entry camera + self._create_nodes(nodes, enter=True, exit=False, camCmd="push", enable="disable") + self._create_nodes(nodes, enter=False, exit=True, camCmd="pop") + + def _create_nodes(self, nodes, *, enter: Optional[bool] = None, camCmd: Optional[str] = None, enable: Optional[str] = None): + volume_sensor: PlasmaVolumeSensorNode = nodes.new("PlasmaVolumeSensorNode") + volume_sensor.bounds = self.bounds_type + if enter is not None: + volume_sensor.find_input_socket("enter").allow = enter + if exit is not None: + volume_sensor.find_input_socket("exit").allow = exit + + responder: PlasmaResponderNode = nodes.new("PlasmaResponderNode") + responder.link_input(volume_sensor, "satisfies", "condition") + + responder_state: PlasmaResponderStateNode = nodes.new("PlasmaResponderStateNode") + responder_state.link_input(responder, "state_refs", "resp") + + camera_msg: PlasmaCameraMsgNode = nodes.new("PlasmaCameraMsgNode") + assert camCmd in {"push", "pop"} + camera_msg.cmd = camCmd + camera_msg.camera = self.entry_camera + camera_msg.link_input(responder_state, "msgs", "sender") + + if enable is not None: + enable_msg: PlasmaEnableMsgNode = nodes.new("PlasmaEnableMsgNode") + enable_LUT = {"enable": "kEnable", "disable": "kDisable"} + enable_msg.cmd = enable_LUT[enable] + enable_msg.settings = {"kModifiers"} + enable_msg.link_input(responder_state, "msgs", "sender") + enable_msg.link_output(volume_sensor, "receivers", "message") + def export(self, exporter, bo, so): - # Not much to this modifier... It's basically a flag that tells the engine, "hey, this is a - # place the avatar can show up." Nice to have a simple one to get started with. - spawn = exporter.mgr.add_object(pl=plSpawnModifier, so=so, name=self.key_name) + exporter.mgr.add_object(pl=plSpawnModifier, so=so, name=self.key_name) @property def requires_actor(self): diff --git a/korman/ui/modifiers/logic.py b/korman/ui/modifiers/logic.py index c2da1fd..a99b169 100644 --- a/korman/ui/modifiers/logic.py +++ b/korman/ui/modifiers/logic.py @@ -13,8 +13,15 @@ # You should have received a copy of the GNU General Public License # along with Korman. If not, see . +from __future__ import annotations + import bpy +from typing import * + +if TYPE_CHECKING: + from ...properties.modifiers.logic import * + from .. import ui_list class LogicListUI(bpy.types.UIList): @@ -36,8 +43,18 @@ def advanced_logic(modifier, layout, context): layout.row().prop_menu_enum(logic, "version") layout.prop(logic, "node_tree", icon="NODETREE") -def spawnpoint(modifier, layout, context): +def spawnpoint(modifier: PlasmaSpawnPoint, layout, context): layout.label(text="Avatar faces negative Y.") + layout.separator() + + col = layout.column() + col.prop(modifier, "entry_camera", icon="CAMERA_DATA") + sub = col.row() + sub.active = modifier.entry_camera is not None + sub.prop(modifier, "exit_region", icon="MESH_DATA") + sub = col.row() + sub.active = modifier.entry_camera is not None and modifier.exit_region is not None + sub.prop(modifier, "bounds_type") def maintainersmarker(modifier, layout, context): layout.label(text="Positive Y is North, positive Z is up.")