diff --git a/korman/exporter/utils.py b/korman/exporter/utils.py
index 4235d81..a5258a0 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 *
@@ -130,21 +131,54 @@ 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 RegionOrigin(enum.Enum):
+ center = enum.auto()
+ bottom = enum.auto()
+
+
+def create_cube_region(name: str, size: float, owner_object: bpy.types.Object, origin: RegionOrigin = RegionOrigin.center) -> bpy.types.Object:
"""Create a cube shaped region object"""
+ # Proxy over to the generic rectangular prism implementation to
+ # ensure that it is well tested.
+ return create_box_region(name, (size, size, size), owner_object, origin)
+
+def create_box_region(
+ name: str, size: Union[Tuple[float, float, float], mathutils.Vector],
+ owner_object: bpy.types.Object,
+ origin: RegionOrigin = RegionOrigin.center
+) -> bpy.types.Object:
+ """Create a rectangular prism region object"""
+ if not isinstance(size, mathutils.Vector):
+ size = mathutils.Vector(size)
+
region_object = BMeshObject(name)
region_object.plasma_object.enabled = True
region_object.plasma_object.page = owner_object.plasma_object.page
region_object.hide_render = True
with region_object as bm:
- bmesh.ops.create_cube(bm, size=(size))
- bmesh.ops.transform(
- bm,
- matrix=mathutils.Matrix.Translation(
- owner_object.matrix_world.translation - region_object.matrix_world.translation
- ),
- space=region_object.matrix_world, verts=bm.verts
- )
+ center = owner_object.matrix_world.translation
+ if origin == RegionOrigin.bottom:
+ center = center.copy()
+ center.z += size.z * 0.5
+ vert_src = [
+ (center.x + size.x * 0.5, center.y + size.y * 0.5, center.z + size.z * 0.5),
+ (center.x + size.x * 0.5, center.y + size.y * 0.5, center.z - size.z * 0.5),
+ (center.x + size.x * 0.5, center.y - size.y * 0.5, center.z + size.z * 0.5),
+ (center.x + size.x * 0.5, center.y - size.y * 0.5, center.z - size.z * 0.5),
+ (center.x - size.x * 0.5, center.y + size.y * 0.5, center.z + size.z * 0.5),
+ (center.x - size.x * 0.5, center.y + size.y * 0.5, center.z - size.z * 0.5),
+ (center.x - size.x * 0.5, center.y - size.y * 0.5, center.z + size.z * 0.5),
+ (center.x - size.x * 0.5, center.y - size.y * 0.5, center.z - size.z * 0.5),
+ ]
+ verts = [bm.verts.new(i) for i in vert_src]
+
+ new_face = bm.faces.new
+ new_face((verts[0], verts[1], verts[3], verts[2])) # X+
+ new_face((verts[4], verts[5], verts[7], verts[6])) # X-
+ new_face((verts[0], verts[1], verts[5], verts[4])) # Y+
+ new_face((verts[2], verts[3], verts[7], verts[6])) # Y-
+ new_face((verts[0], verts[2], verts[6], verts[4])) # Z+
+ new_face((verts[1], verts[3], verts[7], verts[5])) # Z-
return region_object.release()
@contextmanager
diff --git a/korman/properties/modifiers/base.py b/korman/properties/modifiers/base.py
index ed2b76e..2a984a2 100644
--- a/korman/properties/modifiers/base.py
+++ b/korman/properties/modifiers/base.py
@@ -13,12 +13,17 @@
# 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 bpy.props import *
import abc
import itertools
-from typing import Any, Dict, FrozenSet, Optional
+from typing import *
+
+if TYPE_CHECKING:
+ from ...nodes.node_python import *
from ... import helpers
@@ -202,24 +207,23 @@ class PlasmaModifierLogicWiz:
pfm_node.update()
return pfm_node
- def _create_python_attribute(self, pfm_node, attribute_name: str, attribute_type: Optional[str] = None, **kwargs):
+ def _create_python_attribute(self, pfm_node: PlasmaPythonFileNode, attribute_name: str, **kwargs):
"""Creates and links a Python Attribute Node to the Python File Node given by `pfm_node`.
- This will automatically handle simple attribute types such as numbers and strings, however,
- for object linkage, you should specify the optional `attribute_type` to ensure the proper
- attribute type is found. For attribute nodes that require multiple values, the `value` may
- be set to None and handled in your code."""
- from ...nodes.node_python import PlasmaAttribute, PlasmaAttribNodeBase
- if attribute_type is None:
- assert len(kwargs) == 1 and "value" in kwargs, \
- "In order to deduce the attribute_type, exactly one attribute value must be passed as a kw named `value`"
- attribute_type = PlasmaAttribute.type_LUT.get(kwargs["value"].__class__)
+ For attribute nodes that require multiple values, the `value` may be set to None and
+ handled in your code."""
+ attribute_defn = next((i for i in pfm_node.attributes if i.attribute_name == attribute_name), None)
+ if attribute_defn is None:
+ raise KeyError(attribute_name)
+
+ from ...nodes.node_python import PlasmaAttribNodeBase
+ attribute_type = attribute_defn.attribute_type
node_cls = next((i for i in PlasmaAttribNodeBase.__subclasses__() if attribute_type in i.pl_attrib), None)
- assert node_cls is not None, "'{}': Unable to find attribute node type for '{}' ('{}')".format(
- self.id_data.name, attribute_name, attribute_type
- )
+ assert node_cls is not None, \
+ f"'{self.id_data.name}': Unable to find attribute node type for '{attribute_name}' ('{attribute_type}')"
node = pfm_node.id_data.nodes.new(node_cls.bl_idname)
node.link_output(pfm_node, "pfm", attribute_name)
+ assert kwargs
for i, j in kwargs.items():
setattr(node, i, j)
return node
diff --git a/korman/properties/modifiers/logic.py b/korman/properties/modifiers/logic.py
index 675b4fe..4cb4fd9 100644
--- a/korman/properties/modifiers/logic.py
+++ b/korman/properties/modifiers/logic.py
@@ -13,16 +13,35 @@
# 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
+
+entry_cam_pfm = {
+ "filename": "xEntryCam.py",
+ "attribs": (
+ { 'id': 1, 'type': "ptAttribActivator", 'name': "actRegionSensor" },
+ { 'id': 2, 'type': "ptAttribSceneobject", 'name': "camera" },
+ { 'id': 3, 'type': "ptAttribBoolean", 'name': "undoFirstPerson" },
+ )
+}
class PlasmaVersionedNodeTree(idprops.IDPropMixin, bpy.types.PropertyGroup):
version = EnumProperty(name="Version",
@@ -76,7 +95,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 +103,73 @@ 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_box_region(
+ f"{self.key_name}_ExitRgn", (2.0, 2.0, 6.0),
+ bo, utils.RegionOrigin.bottom
+ )
+
+ yield self.convert_logic(bo)
+
+ def logicwiz(self, bo, tree):
+ pfm_node = self._create_python_file_node(
+ tree,
+ entry_cam_pfm["filename"],
+ entry_cam_pfm["attribs"]
+ )
+
+ volume_sensor: PlasmaVolumeSensorNode = tree.nodes.new("PlasmaVolumeSensorNode")
+ volume_sensor.find_input_socket("enter").allow = True
+ volume_sensor.find_input_socket("exit").allow = True
+ volume_sensor.region_object = self.exit_region
+ volume_sensor.bounds = self.bounds_type
+ volume_sensor.link_output(pfm_node, "satisfies", "actRegionSensor")
+
+ self._create_python_attribute(
+ pfm_node,
+ "camera",
+ target_object=self.entry_camera
+ )
+
+
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.")