diff --git a/cwxml/ymap.py b/cwxml/ymap.py
index 0262804..2b0dde1 100644
--- a/cwxml/ymap.py
+++ b/cwxml/ymap.py
@@ -1,6 +1,7 @@
from abc import ABC as AbstractClass, abstractmethod
from typing import Union, Type
from xml.etree import ElementTree as ET
+from ..sollumz_properties import SollumzGame
from .element import (
AttributeProperty,
ElementProperty,
@@ -18,6 +19,7 @@
Vector4Property,
)
+current_game = SollumzGame.GTA
class YMAP:
@@ -25,6 +27,11 @@ class YMAP:
@staticmethod
def from_xml_file(filepath):
+ global current_game
+ if ".rsc" in filepath:
+ current_game = SollumzGame.RDR
+ else:
+ current_game = SollumzGame.GTA
return CMapData.from_xml_file(filepath)
@staticmethod
@@ -344,6 +351,51 @@ def __init__(self):
self.max_z_offset = ValueProperty("maxZOffset")
self.object_hash = ValueProperty("objectHash")
self.flags = ValueProperty("flags")
+
+
+class StepInstance(ElementTree):
+ tag_name = "Item"
+
+ def __init__(self):
+ super().__init__()
+ self.position = VectorProperty("position")
+ self.width = ValueProperty("width")
+ self.depth = ValueProperty("depth")
+ self.height = ValueProperty("height")
+ self.forward = VectorProperty("forward")
+ self.right = VectorProperty("right")
+
+
+class StepInstanceList(ListProperty):
+ list_type = StepInstance
+ tag_name = "steps"
+ item_type = "qbgDfAA_0xA15F529D"
+
+ def __init__(self, tag_name=None, value=None):
+ super().__init__(tag_name, value)
+ self.item_type = AttributeProperty("itemType", self.item_type)
+
+ @staticmethod
+ def from_xml(element: ET.Element):
+ new = StepInstanceList()
+ for child in element.iter():
+ if len(child.attrib) <= 0:
+ step = StepInstance
+ new.value.append(step.from_xml(child))
+
+ return new
+
+
+class ExtensionStairs(Extension):
+ type = "CExtensionDefStairs"
+
+ def __init__(self):
+ super().__init__()
+ self.bottom = VectorProperty("bottom")
+ self.top = VectorProperty("top")
+ self.bound_min = VectorProperty("boundMin")
+ self.bound_max = VectorProperty("boundMax")
+ self.steps = StepInstanceList()
class ExtensionsList(ListProperty):
@@ -382,6 +434,8 @@ def get_extension_xml_class_from_type(ext_type: str) -> Union[Type[Extension], N
return ExtensionProcObject
elif ext_type == ExtensionScriptEntityId.type:
return ExtensionScriptEntityId
+ elif ext_type == ExtensionStairs.type:
+ return ExtensionStairs
return None
@@ -431,10 +485,22 @@ def __init__(self):
self.tint_value = ValueProperty("tintValue", 0)
+class EntityRDR(Entity):
+ def __init__(self):
+ super().__init__()
+ self.blend_age_layer = ValueProperty("blendAgeLayer", 0)
+ self.blend_age_dirt = ValueProperty("blendAgeDirt", 0)
+
+
class EntityList(ListPropertyRequired):
list_type = Entity
tag_name = "entities"
+ def __init__(self, current_game):
+ if current_game == SollumzGame.RDR:
+ self.list_type = Entity
+ super().__init__()
+
class ContainerLodsList(ElementTree):
"""This is not used by GTA5 but added for completion"""
@@ -636,7 +702,7 @@ def __init__(self):
self.streaming_extents_max = VectorProperty("streamingExtentsMax")
self.entities_extents_min = VectorProperty("entitiesExtentsMin")
self.entities_extents_max = VectorProperty("entitiesExtentsMax")
- self.entities = EntityList()
+ self.entities = EntityList(current_game)
self.container_lods = ContainerLodsList()
self.box_occluders = BoxOccludersList()
self.occlude_models = OccludeModelsList()
diff --git a/cwxml/ytyp.py b/cwxml/ytyp.py
index 9585539..b2a0e8e 100644
--- a/cwxml/ytyp.py
+++ b/cwxml/ytyp.py
@@ -61,7 +61,7 @@ def __init__(self):
self.extensions = ExtensionsList()
if current_game == SollumzGame.RDR:
self.guid = ValueProperty("guid")
- self.unknown_1 = TextProperty("iypiqkia_0x07d164a8")
+ self.unknown_1 = TextProperty("zqNiUDA_0x07D164A8")
class TimeArchetype(BaseArchetype):
def __init__(self):
@@ -260,7 +260,7 @@ def __init__(self):
super().__init__()
self.name = TextProperty("name")
self.locations = LocationsBuffer()
- self.entities = EntityList()
+ self.entities = EntityList(current_game)
class EntitySetsList(ListProperty):
@@ -298,7 +298,7 @@ def __init__(self):
super().__init__()
self.type = AttributeProperty("type", "CMloArchetypeDef")
self.mlo_flags = ValueProperty("mloFlags")
- self.entities = EntityList()
+ self.entities = EntityList(current_game)
self.rooms = RoomsList()
self.portals = PortalsList()
self.entity_sets = EntitySetsList()
diff --git a/sollumz_preferences.py b/sollumz_preferences.py
index 047fd79..82aaf13 100644
--- a/sollumz_preferences.py
+++ b/sollumz_preferences.py
@@ -8,7 +8,7 @@
import os
import ast
from typing import Any
-from .sollumz_properties import SollumType
+from .sollumz_properties import SollumType, SollumzGame, items_from_enums
from configparser import ConfigParser
from typing import Optional
@@ -363,6 +363,14 @@ class SollumzAddonPreferences(bpy.types.AddonPreferences):
update=_save_preferences
)
+ default_game: bpy.props.EnumProperty(
+ items=items_from_enums(SollumzGame),
+ name="Default game:",
+ description="Sets the game with which Blender will start",
+ default=SollumzGame.GTA,
+ update=_save_preferences
+ )
+
shared_textures_directories: CollectionProperty(
name="Shared Textures",
type=SzSharedTexturesDirectory,
@@ -390,6 +398,7 @@ def draw(self, context):
layout.prop(self, "extra_color_swatches")
layout.prop(self, "sollumz_icon_header")
layout.prop(self, "use_text_name_as_mat_name")
+ layout.prop(self, "default_game")
from .sollumz_ui import draw_list_with_add_remove
layout.separator()
@@ -463,7 +472,12 @@ def _apply_preferences(data_block: bpy.types.ID, config: ConfigParser, section:
continue
value_str = config.get(section, key)
- value = ast.literal_eval(value_str)
+
+ #it crashes for enum
+ if key == "default_game":
+ value = value_str
+ else:
+ value = ast.literal_eval(value_str)
if key == "shared_textures_directories":
# Special case to handle CollectionProperty
diff --git a/sollumz_properties.py b/sollumz_properties.py
index 5222759..1a0d217 100644
--- a/sollumz_properties.py
+++ b/sollumz_properties.py
@@ -610,6 +610,11 @@ class EntityProperties:
artificial_ambient_occlusion: bpy.props.FloatProperty(
name="Artificial Ambient Occlusion", default=255)
tint_value: bpy.props.FloatProperty(name="Tint Value")
+ #RDR
+ blend_age_layer: bpy.props.FloatProperty(
+ name="Blend Age Layer", default=255)
+ blend_age_dirt: bpy.props.FloatProperty(
+ name="Blend Age Dirt", default=255)
class ObjectEntityProperties(bpy.types.PropertyGroup, EntityProperties):
@@ -621,17 +626,19 @@ def updateSceneSollumzGame(self, context):
context.scene.sollum_collision_material_game_type = context.scene.sollum_game_type
def register():
+ from .sollumz_preferences import get_addon_preferences
+ preferences = get_addon_preferences(bpy.context)
bpy.types.Object.sollum_game_type = bpy.props.EnumProperty(
items=items_from_enums(SollumzGame),
name="Sollumz Game",
- default=SollumzGame.GTA,
+ default=preferences.default_game,
options={"HIDDEN"}
)
bpy.types.Scene.sollum_game_type = bpy.props.EnumProperty(
items=items_from_enums(SollumzGame),
name="Sollumz Game",
- default=SollumzGame.GTA,
+ default=preferences.default_game,
options={"HIDDEN"},
update=updateSceneSollumzGame
)
diff --git a/tests/test_obj_reader.py b/tests/test_obj_reader.py
index 440546a..ea815bd 100644
--- a/tests/test_obj_reader.py
+++ b/tests/test_obj_reader.py
@@ -91,6 +91,7 @@ def test_obj_as_vertices_only():
"../ytyp/gizmos/models/SpawnPoint.obj",
"../ytyp/gizmos/models/SpawnPointOverride.obj",
"../ytyp/gizmos/models/WindDisturbance.obj",
+ "../ytyp/gizmos/models/Stair.obj",
))
def test_obj_read_sollumz_builtin_asset(obj_relative_file_path: str):
obj_path = Path(__file__).parent.joinpath(obj_relative_file_path)
diff --git a/ybn/flag_presets.xml b/ybn/flag_presets.xml
index 5845766..8f47359 100644
--- a/ybn/flag_presets.xml
+++ b/ybn/flag_presets.xml
@@ -9,6 +9,18 @@
cf_map_type_horse, cf_cover_type, cf_map_type_mover, cf_map_type_vehicle, cf_map_type_weapon
cf_vehicle_non_bvh_type, cf_vehicle_bvh_type, cf_ped_type, cf_ragdoll_type, cf_horse_type, cf_horse_ragdoll_type, cf_object_type, cf_plant_type, cf_projectile_type, cf_explosion_type, cf_forklift_forks_type, cf_weapon_test, cf_camera_test, cf_ai_test, cf_script_test, cf_wheel_test, cf_glass_type
+ -
+ cf_horse_avoidance
+ cf_horse_type, cf_horse_ragdoll_type, cf_ai_test
+
+ -
+ cf_stair_slope_type
+ cf_ped_type, cf_horse_type, cf_weapon_test, cf_camera_test, cf_ai_test, cf_script_test, cf_wheel_test
+
+ -
+ cf_river_type
+ cf_weapon_test, cf_camera_test, cf_ai_test, cf_script_test, cf_wheel_test
+
-
map_animal, map_cover, map_dynamic, map_vehicle
vehicle_not_bvh, vehicle_bvh, ped, ragdoll, animal, animal_ragdoll, object, plant, projectile, explosion, forklift_forks, test_weapon, test_camera, test_ai, test_script, test_vehicle_wheel, glass
diff --git a/ybn/operators.py b/ybn/operators.py
index b68e786..289233d 100644
--- a/ybn/operators.py
+++ b/ybn/operators.py
@@ -356,6 +356,8 @@ class SOLLUMZ_OT_delete_flag_preset(SOLLUMZ_OT_base, bpy.types.Operator):
"Stair plane",
"Stair mesh",
"Deep surface",
+ "Horse Avoidance",
+ "River Type",
]
def run(self, context):
@@ -430,6 +432,8 @@ def run(self, context):
flag_preset = FlagPreset()
flag_preset.name = self.name
+ flag_preset.game = obj.sollum_game_type
+
for prop in dir(obj.composite_flags1):
value = getattr(obj.composite_flags1, prop)
if value is True:
@@ -472,11 +476,13 @@ def run(self, context):
preset = flag_presets.presets[index]
if obj.sollum_game_type == SollumzGame.RDR:
- preset = flag_presets.presets[1]
+ if not preset or preset.game != obj.sollum_game_type:
+ preset = flag_presets.presets[1]
flags_class = RDRBoundFlags
type_flags, include_flags = obj.type_flags, obj.include_flags
else:
- preset = flag_presets.presets[0]
+ if not preset or preset.game != obj.sollum_game_type:
+ preset = flag_presets.presets[0]
flags_class = BoundFlags
type_flags, include_flags = obj.composite_flags1, obj.composite_flags2
@@ -486,7 +492,6 @@ def run(self, context):
type_flags[flag_name] = flag_in_preset1
include_flags[flag_name] = flag_in_preset2
- include_flags[flag_name] = flag_in_preset2
tag_redraw(context)
self.message(
@@ -514,9 +519,14 @@ def run(self, context):
return False
if aobj.sollum_type in BOUND_TYPES:
- for flag_name in BoundFlags.__annotations__.keys():
- aobj.composite_flags1[flag_name] = False
- aobj.composite_flags2[flag_name] = False
+ if aobj.sollum_game_type == SollumzGame.RDR:
+ for flag_name in RDRBoundFlags.__annotations__.keys():
+ aobj.type_flags[flag_name] = False
+ aobj.include_flags[flag_name] = False
+ else:
+ for flag_name in BoundFlags.__annotations__.keys():
+ aobj.composite_flags1[flag_name] = False
+ aobj.composite_flags2[flag_name] = False
tag_redraw(context)
diff --git a/ytyp/gizmos/extensions.py b/ytyp/gizmos/extensions.py
index 95b4ee4..47ed71e 100644
--- a/ytyp/gizmos/extensions.py
+++ b/ytyp/gizmos/extensions.py
@@ -65,6 +65,7 @@ def _load_extension_model(extension_type: ExtensionType, file_name: str):
(ExtensionType.SPAWN_POINT, "SpawnPoint.obj"),
(ExtensionType.SPAWN_POINT_OVERRIDE, "SpawnPointOverride.obj"),
(ExtensionType.WIND_DISTURBANCE, "WindDisturbance.obj"),
+ (ExtensionType.Stairs, "Stair.obj")
):
_load_extension_model(ext_type, model_file_name)
return shapes
@@ -91,6 +92,45 @@ def get_cube_shape() -> object:
(0.5, 0.5, -0.5), (0.5, 0.5, 0.5),
))
+@functools.cache
+def get_bound_box_shape(min_b, max_b) -> object:
+ """A cube as a bounding box with origin in the middle"""
+ return bpy.types.Gizmo.new_custom_shape("LINES", (
+ # bottom face
+ (min_b[0],min_b[1],min_b[2]),(min_b[0],max_b[1],min_b[2]),
+ (min_b[0],min_b[1],min_b[2]),(max_b[0],min_b[1],min_b[2]),
+ (max_b[0],min_b[1],min_b[2]),(max_b[0],max_b[1],min_b[2]),
+ (min_b[0],max_b[1],min_b[2]),(max_b[0],max_b[1],min_b[2]),
+ # top face
+ (min_b[0],min_b[1],max_b[2]),(min_b[0],max_b[1],max_b[2]),
+ (min_b[0],min_b[1],max_b[2]),(max_b[0],min_b[1],max_b[2]),
+ (max_b[0],min_b[1],max_b[2]),(max_b[0],max_b[1],max_b[2]),
+ (min_b[0],max_b[1],max_b[2]),(max_b[0],max_b[1],max_b[2]),
+ # connect top and bottom faces
+ (min_b[0],min_b[1],min_b[2]),(min_b[0],min_b[1],max_b[2]),
+ (min_b[0],max_b[1],min_b[2]),(min_b[0],max_b[1],max_b[2]),
+ (max_b[0],min_b[1],min_b[2]),(max_b[0],min_b[1],max_b[2]),
+ (max_b[0],max_b[1],min_b[2]),(max_b[0],max_b[1],max_b[2]),
+ ))
+
+@functools.cache
+def get_axis_shape() -> object:
+ """A representation off the axis on the origin"""
+ return bpy.types.Gizmo.new_custom_shape("LINES", (
+ (0, 0, -0.5), (0, 0, 0.5),
+ (0, -0.5, 0), (0, 0.5, 0),
+ (-0.5, 0, 0), (0.5, 0, 0),
+ ))
+
+@functools.cache
+def get_arrow_shape() -> object:
+ """A representation off the axis on the origin"""
+ return bpy.types.Gizmo.new_custom_shape("LINES", (
+ (0, 0, 0), (0.5, 0, 0),
+ (0.5, 0, 0), (0.4, 0.05, 0),
+ (0.5, 0, 0), (0.4, -0.05, 0),
+ ))
+
@functools.cache
def get_square_shape() -> object:
@@ -291,6 +331,7 @@ def draw_select(self, context, select_id=None):
is_active = selected_archetype == archetype and selected_extension == ext
is_ladder = ext.extension_type == ExtensionType.LADDER
is_light_shaft = ext.extension_type == ExtensionType.LIGHT_SHAFT
+ is_stair = ext.extension_type == ExtensionType.Stairs
theme = context.preferences.themes[0]
@@ -338,6 +379,25 @@ def draw_select(self, context, select_id=None):
self.draw_ladder_gizmo(context, select_id)
elif is_light_shaft:
self.draw_light_shaft_gizmo(context, select_id)
+ elif is_stair:
+ self.draw_custom_shape(get_bound_box_shape(tuple(ext_props.bound_min), tuple(ext_props.bound_max)), matrix=gizmo_matrix, select_id=select_id)
+ self.draw_custom_shape(get_axis_shape(), matrix=gizmo_matrix @ Matrix.Translation(ext_props.top), select_id=select_id)
+ self.draw_custom_shape(get_axis_shape(), matrix=gizmo_matrix @ Matrix.Translation(ext_props.bottom), select_id=select_id)
+ for step in ext_props.steps:
+ step_prop = step.step_properties
+ self.color = theme.view_3d.object_selected if step != ext_props.selected_step or not is_active else theme.view_3d.object_active
+ self.color_highlight = self.color
+ self.draw_custom_shape(get_cube_shape(),
+ matrix=gizmo_matrix @ Matrix.Translation(step_prop.position)
+ @ Matrix.Rotation(step_prop.rotation, 4, Vector([0,0,1]))
+ @ Matrix.Scale(step_prop.depth, 4, Vector([1,0,0]))
+ @ Matrix.Scale(step_prop.width, 4, Vector([0,1,0]))
+ @ Matrix.Scale(step_prop.height, 4, Vector([0,0,1])),
+ select_id=select_id)
+ self.draw_custom_shape(get_arrow_shape(),
+ matrix=gizmo_matrix @ Matrix.Translation(step_prop.position)
+ @ Matrix.Rotation(step_prop.rotation, 4, Vector([0,0,1])),
+ select_id=select_id)
def draw_ladder_gizmo(self, context, select_id):
archetype = self.linked_archetype
diff --git a/ytyp/gizmos/models/FakeDoor.obj b/ytyp/gizmos/models/FakeDoor.obj
new file mode 100644
index 0000000..cde5ffe
--- /dev/null
+++ b/ytyp/gizmos/models/FakeDoor.obj
@@ -0,0 +1,270 @@
+# Blender 4.3.0
+# www.blender.org
+o DefFakeDoor
+v 0.027470 -0.312138 0.197172
+v 0.027470 0.423864 0.144484
+v -0.027470 0.134895 0.144484
+v -0.027470 0.134895 -0.144484
+v 0.027470 -0.312138 -0.197172
+v 0.027470 0.423864 -0.144484
+v 0.027470 0.134895 -0.144484
+v 0.027470 0.134895 0.144484
+v -0.027470 -0.312138 0.197172
+v -0.027470 0.423864 0.144484
+v -0.027470 0.423864 -0.144484
+v -0.027470 -0.312138 -0.197172
+v -0.027470 0.082207 0.197172
+v -0.027470 0.082207 -0.197172
+v 0.027470 0.082207 -0.197172
+v 0.027470 0.082207 0.197172
+v 0.027470 0.476551 -0.197172
+v 0.027470 0.476551 0.197172
+v -0.027470 0.476551 -0.197172
+v -0.027470 0.476551 0.197172
+v -0.027470 0.134895 -0.000000
+v 0.027470 0.134895 -0.000000
+v 0.027470 0.423864 -0.000000
+v -0.027470 0.279379 -0.144484
+v -0.027470 0.279379 0.144484
+v 0.027470 0.279379 -0.144484
+v -0.027470 0.423864 -0.000000
+v 0.027470 0.279379 0.144484
+v 0.027470 0.279379 -0.000000
+v -0.027470 0.279379 -0.000000
+v -0.027470 0.292116 0.012737
+v -0.027470 0.292116 0.131747
+v -0.027470 0.411127 0.131747
+v -0.027470 0.411127 0.012737
+v 0.027470 0.292116 0.012737
+v 0.027470 0.411127 0.012737
+v 0.027470 0.411127 0.131747
+v 0.027470 0.292116 0.131747
+v 0.027470 0.147632 0.012737
+v 0.027470 0.266642 0.012737
+v 0.027470 0.266642 0.131747
+v 0.027470 0.147632 0.131747
+v 0.027470 0.147632 -0.131747
+v 0.027470 0.266642 -0.131747
+v 0.027470 0.266642 -0.012737
+v 0.027470 0.147632 -0.012737
+v 0.027470 0.292116 -0.131747
+v 0.027470 0.411127 -0.131747
+v 0.027470 0.411127 -0.012737
+v 0.027470 0.292116 -0.012737
+v -0.027470 0.292116 -0.131747
+v -0.027470 0.292116 -0.012737
+v -0.027470 0.411127 -0.012737
+v -0.027470 0.411127 -0.131747
+v -0.027470 0.147632 -0.131747
+v -0.027470 0.147632 -0.012737
+v -0.027470 0.266642 -0.012737
+v -0.027470 0.266642 -0.131747
+v -0.027470 0.147632 0.012737
+v -0.027470 0.147632 0.131747
+v -0.027470 0.266642 0.131747
+v -0.027470 0.266642 0.012737
+v -0.043514 0.095534 -0.028027
+v -0.043514 0.064967 0.003005
+v -0.043514 0.126785 0.002754
+v -0.043514 0.096218 0.033787
+v 0.043514 0.095534 -0.028027
+v 0.043514 0.064967 0.003005
+v 0.043514 0.126785 0.002754
+v 0.043514 0.096218 0.033787
+v -0.043514 0.441530 -0.379296
+v -0.043514 0.472781 -0.348515
+v 0.043514 0.472781 -0.348515
+v 0.043514 0.441530 -0.379296
+v -0.043514 -0.255735 -0.374024
+v 0.043514 -0.255735 -0.374024
+v 0.043514 -0.286302 -0.342991
+v -0.043514 -0.286302 -0.342991
+v -0.043514 0.478054 0.348751
+v -0.043514 0.447487 0.379784
+v 0.043514 0.447487 0.379784
+v 0.043514 0.478054 0.348751
+v 0.043514 -0.249778 0.385056
+v -0.043514 -0.249778 0.385056
+v -0.043514 -0.281029 0.354275
+v 0.043514 -0.281029 0.354275
+s 0
+f 40 59 39
+f 9 14 12
+f 39 60 42
+f 5 9 12
+f 15 1 5
+f 17 14 19
+f 16 9 1
+f 15 12 14
+f 19 18 17
+f 18 13 16
+f 21 14 13
+f 22 16 15
+f 23 17 18
+f 24 19 14
+f 25 13 20
+f 26 15 17
+f 27 20 19
+f 28 18 16
+f 42 61 41
+f 41 62 40
+f 32 35 31
+f 31 36 34
+f 34 37 33
+f 33 38 32
+f 25 31 30
+f 10 32 25
+f 27 33 10
+f 30 34 27
+f 23 35 29
+f 2 36 23
+f 28 37 2
+f 29 38 28
+f 29 39 22
+f 28 40 29
+f 28 42 41
+f 22 42 8
+f 26 43 7
+f 29 44 26
+f 29 46 45
+f 22 43 46
+f 6 47 26
+f 23 48 6
+f 29 49 23
+f 29 47 50
+f 30 51 24
+f 27 52 30
+f 27 54 53
+f 24 54 11
+f 21 55 4
+f 30 56 21
+f 30 58 57
+f 24 55 58
+f 3 59 21
+f 25 60 3
+f 30 61 25
+f 30 59 62
+f 45 58 44
+f 46 57 45
+f 43 56 46
+f 44 55 43
+f 49 54 48
+f 50 53 49
+f 47 52 50
+f 48 51 47
+f 40 62 59
+f 9 13 14
+f 39 59 60
+f 5 1 9
+f 15 16 1
+f 17 15 14
+f 16 13 9
+f 15 5 12
+f 19 20 18
+f 18 20 13
+f 13 3 21
+f 21 4 14
+f 15 7 22
+f 22 8 16
+f 18 2 23
+f 23 6 17
+f 14 4 24
+f 24 11 19
+f 20 10 25
+f 25 3 13
+f 17 6 26
+f 26 7 15
+f 19 11 27
+f 27 10 20
+f 16 8 28
+f 28 2 18
+f 42 60 61
+f 41 61 62
+f 32 38 35
+f 31 35 36
+f 34 36 37
+f 33 37 38
+f 25 32 31
+f 10 33 32
+f 27 34 33
+f 30 31 34
+f 23 36 35
+f 2 37 36
+f 28 38 37
+f 29 35 38
+f 29 40 39
+f 28 41 40
+f 28 8 42
+f 22 39 42
+f 26 44 43
+f 29 45 44
+f 29 22 46
+f 22 7 43
+f 6 48 47
+f 23 49 48
+f 29 50 49
+f 29 26 47
+f 30 52 51
+f 27 53 52
+f 27 11 54
+f 24 51 54
+f 21 56 55
+f 30 57 56
+f 30 24 58
+f 24 4 55
+f 3 60 59
+f 25 61 60
+f 30 62 61
+f 30 21 59
+f 45 57 58
+f 46 56 57
+f 43 55 56
+f 44 58 55
+f 49 53 54
+f 50 52 53
+f 47 51 52
+f 48 54 51
+f 66 63 64
+f 70 67 69
+f 72 74 71
+f 65 71 63
+f 73 65 69
+f 74 69 67
+f 71 67 63
+f 76 78 75
+f 76 63 67
+f 68 76 67
+f 78 68 64
+f 75 64 63
+f 80 82 79
+f 66 79 65
+f 81 66 70
+f 82 70 69
+f 79 69 65
+f 84 86 83
+f 84 70 66
+f 64 84 66
+f 86 64 68
+f 83 68 70
+f 66 65 63
+f 70 68 67
+f 72 73 74
+f 65 72 71
+f 73 72 65
+f 74 73 69
+f 71 74 67
+f 76 77 78
+f 76 75 63
+f 68 77 76
+f 78 77 68
+f 75 78 64
+f 80 81 82
+f 66 80 79
+f 81 80 66
+f 82 81 70
+f 79 82 69
+f 84 85 86
+f 84 83 70
+f 64 85 84
+f 86 85 64
+f 83 86 68
diff --git a/ytyp/gizmos/models/Stair.obj b/ytyp/gizmos/models/Stair.obj
new file mode 100644
index 0000000..acbef80
--- /dev/null
+++ b/ytyp/gizmos/models/Stair.obj
@@ -0,0 +1,114 @@
+# Blender 4.3.0
+# www.blender.org
+o DefStair
+v -0.197290 0.065763 0.131526
+v -0.197290 0.131526 0.131526
+v -0.197290 0.000000 0.065763
+v -0.197290 0.197290 0.131526
+v -0.197290 0.065763 0.065763
+v -0.197290 -0.197290 -0.131526
+v -0.197290 0.131526 0.065763
+v -0.197290 -0.065763 0.000000
+v -0.197290 0.000000 0.000000
+v -0.197290 0.065763 -0.000000
+v -0.197290 -0.131526 -0.065763
+v -0.197290 -0.065763 -0.065763
+v -0.197290 -0.000000 -0.065763
+v -0.197290 -0.131526 -0.131526
+v -0.197290 -0.065763 -0.131526
+v -0.197290 0.131526 0.197290
+v -0.197290 -0.131526 -0.197290
+v 0.197290 -0.197290 -0.131526
+v 0.197290 -0.131526 -0.065763
+v 0.197290 0.197290 0.131526
+v 0.197290 -0.197290 -0.197290
+v 0.197290 0.197290 0.197290
+v -0.197290 -0.197290 -0.197290
+v -0.197290 0.197290 0.197290
+v 0.197290 0.131526 0.131526
+v 0.197290 0.065763 0.131526
+v 0.197290 0.131526 0.065763
+v 0.197290 0.065763 0.065763
+v 0.197290 0.000000 0.065763
+v 0.197290 0.065763 -0.000000
+v 0.197290 0.000000 0.000000
+v 0.197290 -0.065763 0.000000
+v 0.197290 -0.000000 -0.065763
+v 0.197290 -0.065763 -0.065763
+v 0.197290 -0.065763 -0.131526
+v 0.197290 -0.131526 -0.131526
+v 0.197290 0.131526 0.197290
+v 0.197290 -0.131526 -0.197290
+s 0
+f 21 6 23
+f 27 26 28
+f 29 28 26
+f 14 23 6
+f 30 29 31
+f 32 31 29
+f 33 32 34
+f 19 34 32
+f 4 22 20
+f 22 16 37
+f 20 37 25
+f 17 21 23
+f 26 25 37
+f 12 14 11
+f 11 14 6
+f 9 12 8
+f 8 12 11
+f 5 9 3
+f 3 9 8
+f 2 5 1
+f 1 5 3
+f 16 2 1
+f 35 19 36
+f 18 36 19
+f 38 18 21
+f 2 20 25
+f 7 25 27
+f 5 27 28
+f 24 2 16
+f 10 28 30
+f 9 30 31
+f 13 31 33
+f 12 33 34
+f 34 15 12
+f 18 11 6
+f 14 35 36
+f 36 17 14
+f 1 37 16
+f 26 3 29
+f 29 8 32
+f 32 11 19
+f 21 18 6
+f 27 25 26
+f 14 17 23
+f 30 28 29
+f 33 31 32
+f 4 24 22
+f 22 24 16
+f 20 22 37
+f 17 38 21
+f 12 15 14
+f 9 13 12
+f 5 10 9
+f 2 7 5
+f 35 34 19
+f 38 36 18
+f 2 4 20
+f 7 2 25
+f 5 7 27
+f 24 4 2
+f 10 5 28
+f 9 10 30
+f 13 9 31
+f 12 13 33
+f 34 35 15
+f 18 19 11
+f 14 15 35
+f 36 38 17
+f 1 26 37
+f 26 1 3
+f 29 3 8
+f 32 8 11
diff --git a/ytyp/operators/extensions.py b/ytyp/operators/extensions.py
index 3c0b777..732514d 100644
--- a/ytyp/operators/extensions.py
+++ b/ytyp/operators/extensions.py
@@ -4,6 +4,42 @@
from ..utils import get_selected_archetype, get_selected_entity, get_selected_ytyp, get_selected_extension
from ...tools.blenderhelper import tag_redraw
+class SOLLUMZ_OT_add_archetype_extension_step(bpy.types.Operator):
+ """Add an step to the stair"""
+ bl_idname = "sollumz.addarchetypeextensionstep"
+ bl_options = {"UNDO"}
+ bl_label = "Add Step"
+
+ @classmethod
+ def poll(cls, context):
+ return get_selected_extension(context) is not None
+
+ def execute(self, context):
+ selected_stair = get_selected_extension(context).stairs_extension_properties
+ selected_stair.new_step()
+ tag_redraw(context, space_type="VIEW_3D", region_type="UI")
+ tag_redraw(context, space_type="VIEW_3D", region_type="TOOL_PROPS")
+ tag_redraw(context, space_type="VIEW_3D", region_type="TOOL_HEADER")
+ return {"FINISHED"}
+
+class SOLLUMZ_OT_delete_archetype_extension_step(bpy.types.Operator):
+ """Delete the selected step from the stair"""
+ bl_idname = "sollumz.deletearchetypeextensionstep"
+ bl_options = {"UNDO"}
+ bl_label = "Delete Step"
+
+ @classmethod
+ def poll(cls, context):
+ selected_extension = get_selected_extension(context).stairs_extension_properties
+ return selected_extension is not None
+
+ def execute(self, context):
+ selected_stair = get_selected_extension(context).stairs_extension_properties
+ selected_stair.delete_selected_stair()
+ tag_redraw(context, space_type="VIEW_3D", region_type="UI")
+ tag_redraw(context, space_type="VIEW_3D", region_type="TOOL_PROPS")
+ tag_redraw(context, space_type="VIEW_3D", region_type="TOOL_HEADER")
+ return {"FINISHED"}
class SOLLUMZ_OT_add_archetype_extension(bpy.types.Operator):
"""Add an extension to the archetype"""
@@ -280,3 +316,84 @@ def execute(self, context):
def set_extension_props(cls, context: bpy.types.Context, verts_location: Vector):
light_shaft_props = get_selected_extension(context).light_shaft_extension_properties
light_shaft_props.offset_position = verts_location
+
+class SOLLUMZ_OT_update_bound_box_stair(bpy.types.Operator):
+ """Update bound box from all steps"""
+ bl_idname = "sollumz.updateboundboxstair"
+ bl_options = {"UNDO"}
+ bl_label = "Update Bound Box"
+
+ @classmethod
+ def poll(cls, context):
+ return get_selected_extension(context) is not None
+
+ def execute(self, context):
+ get_selected_extension(context).stairs_extension_properties.update_bound_box()
+
+ return {"FINISHED"}
+
+class SOLLUMZ_OT_update_step_size_and_location(bpy.types.Operator):
+ """Update step size, location and rotation from selection"""
+ bl_idname = "sollumz.updatestepsizeandlocation"
+ bl_options = {"UNDO"}
+ bl_label = "Update Step size"
+
+ @classmethod
+ def poll(cls, context):
+ return get_selected_extension(context) is not None
+
+ def execute(self, context):
+ aobj = context.active_object
+ aobj.update_from_editmode()
+
+ me = aobj.data
+ selected_edges = [e.key for e in me.edges if e.select]
+
+ if len(selected_edges) != 12:
+ self.report({"INFO"}, "You must select exactly 12 edges.")
+ return {"CANCELLED"}
+
+ get_selected_extension(context).stairs_extension_properties.selected_step.step_properties.update_step(me, selected_edges)
+
+ return {"FINISHED"}
+
+
+class SOLLUMZ_OT_generate_steps_from_faces(bpy.types.Operator):
+ """Generate steps from selected faces"""
+ bl_idname = "sollumz.generatestepsfromfaces"
+ bl_options = {"UNDO"}
+ bl_label = "Generate Steps from Faces"
+
+ @classmethod
+ def poll(cls, context):
+ return get_selected_extension(context) is not None
+
+ def execute(self, context):
+ aobj = context.active_object
+ aobj.update_from_editmode()
+
+ me = aobj.data
+ selected_vertices = [v.co for v in me.vertices if v.select]
+
+ if len(selected_vertices) < 4:
+ self.report({"INFO"}, "You must select atleast 4 edges.")
+ return {"CANCELLED"}
+
+ get_selected_extension(context).stairs_extension_properties.generate_steps(selected_vertices)
+
+ return {"FINISHED"}
+
+class SOLLUMZ_OT_mirror_step(bpy.types.Operator):
+ """Mirrors the selected step, the vector will point in the oposite direction"""
+ bl_idname = "sollumz.mirrorstep"
+ bl_options = {"UNDO"}
+ bl_label = "Mirror Step"
+
+ @classmethod
+ def poll(cls, context):
+ return get_selected_extension(context) is not None
+
+ def execute(self, context):
+ get_selected_extension(context).stairs_extension_properties.selected_step.step_properties.mirror_step()
+
+ return {"FINISHED"}
\ No newline at end of file
diff --git a/ytyp/properties/extensions.py b/ytyp/properties/extensions.py
index 8226c65..0d60c90 100644
--- a/ytyp/properties/extensions.py
+++ b/ytyp/properties/extensions.py
@@ -3,6 +3,9 @@
from enum import Enum
from ...tools.utils import get_list_item
from ...ydr.light_flashiness import Flashiness, LightFlashinessEnumItems
+from mathutils import Vector
+import math
+from ..utils import get_selected_extension
class ExtensionType(str, Enum):
@@ -20,6 +23,7 @@ class ExtensionType(str, Enum):
PROC_OBJECT = "CExtensionDefProcObject"
EXPRESSION = "CExtensionDefExpression"
SCRIPT_ID = "CExtensionDefScriptEntityId"
+ Stairs = "CExtensionDefStairs"
ExtensionTypeEnumItems = (
@@ -37,6 +41,7 @@ class ExtensionType(str, Enum):
(ExtensionType.PROC_OBJECT, "Procedural Object", "", 11),
(ExtensionType.EXPRESSION, "Expression", "", 12),
(ExtensionType.SCRIPT_ID, "Script ID", "", 13),
+ (ExtensionType.Stairs, "Stairs", "", 14),
)
@@ -285,6 +290,218 @@ class ProcObjectExtensionProperties(bpy.types.PropertyGroup, BaseExtensionProper
flags: bpy.props.IntProperty(name="Flags", subtype="UNSIGNED")
+def set_float_vector_property(self, value, name, relativ_object = None):
+ if not relativ_object:
+ relativ_object = self
+ if relativ_object.show_relative:
+ self[name] = value
+ else:
+ self[name] = Vector(value) - relativ_object.offset_position
+
+
+def get_float_vector_property(self, name, relativ_object = None):
+ if not relativ_object:
+ relativ_object = self
+ if relativ_object.show_relative:
+ return self[name]
+ else:
+ return Vector(self[name]) + relativ_object.offset_position
+
+
+class StepsExtensionProperties(bpy.types.PropertyGroup):
+ position: bpy.props.FloatVectorProperty(name="Position", subtype="TRANSLATION")
+ position_ui: bpy.props.FloatVectorProperty(name="Position",
+ subtype="TRANSLATION",
+ get=lambda self: get_float_vector_property(self, "position", get_selected_extension(bpy.context).stairs_extension_properties),
+ set=lambda self, value: set_float_vector_property(self, value, "position", get_selected_extension(bpy.context).stairs_extension_properties))
+ width: bpy.props.FloatProperty(name="Width")
+ depth: bpy.props.FloatProperty(name="Depth")
+ height: bpy.props.FloatProperty(name="Height")
+ rotation: bpy.props.FloatProperty(name="Rotation", subtype="ANGLE")
+
+ def mirror_step(self):
+ if(self.rotation > math.pi):
+ self.rotation -= math.pi
+ else:
+ self.rotation += math.pi
+
+ def update_step(self, mesh, edges):
+ stair_porperties = get_selected_extension(bpy.context).stairs_extension_properties
+
+ selected_vertices = [v.co for v in mesh.vertices if v.select]
+ self.position = sum(selected_vertices, Vector()) / len(selected_vertices) - stair_porperties.offset_position
+
+
+ vertices = [v.co for v in mesh.vertices]
+ #t = Vector()
+ #t.an
+ verts = []
+
+ relevant_edges = set()
+ for edge in edges:
+ vec = vertices[edge[1]] - vertices[edge[0]]
+ relevant_edges.add(vec.to_tuple())
+ verts += list(vertices[edge[1]], )
+
+ if len(relevant_edges) != 3:
+ print("not exactly 3 unique edges")
+ print(relevant_edges)
+ return
+
+ relevant_edges = list(relevant_edges)
+ edges_length = [Vector(e).length for e in relevant_edges]
+ edges_vector = [Vector(e) for e in relevant_edges]
+ width = max(max(edges_length[0], edges_length[1]), edges_length[2])
+ height = min(min(edges_length[0], edges_length[1]), edges_length[2])
+ angle_vector = edges_vector[edges_length.index(width)]
+ edges_length.remove(width)
+ edges_length.remove(height)
+ depth = edges_length[0]
+
+ self.width = width
+ self.height = height
+ self.depth = depth
+ self.rotation = -math.atan2(angle_vector.x, angle_vector.y)
+
+
+class StepsExtensionListProperties(bpy.types.PropertyGroup):
+ step_properties: bpy.props.PointerProperty(
+ type=StepsExtensionProperties)
+ name: bpy.props.StringProperty(name="Name", default="Step")
+
+
+class StairsExtensionProperties(bpy.types.PropertyGroup, BaseExtensionProperties):
+ show_relative: bpy.props.BoolProperty(name="Show Relative", default=False)
+ bottom: bpy.props.FloatVectorProperty(name="Bottom", subtype="TRANSLATION")
+ bottom_ui: bpy.props.FloatVectorProperty(name="Bottom",
+ subtype="TRANSLATION",
+ get=lambda self: get_float_vector_property(self, "bottom"),
+ set=lambda self, value: set_float_vector_property(self, value, "bottom"))
+ top: bpy.props.FloatVectorProperty(name="Top", subtype="TRANSLATION")
+ top_ui: bpy.props.FloatVectorProperty(name="Top",
+ subtype="TRANSLATION",
+ get=lambda self: get_float_vector_property(self, "top"),
+ set=lambda self, value: set_float_vector_property(self, value, "top"))
+ bound_min: bpy.props.FloatVectorProperty(name="Bound Min", subtype="TRANSLATION")
+ bound_min_ui: bpy.props.FloatVectorProperty(name="Bound Min",
+ subtype="TRANSLATION",
+ get=lambda self: get_float_vector_property(self, "bound_min"),
+ set=lambda self, value: set_float_vector_property(self, value, "bound_min"))
+ bound_max: bpy.props.FloatVectorProperty(name="Bound Max", subtype="TRANSLATION")
+ bound_max_ui: bpy.props.FloatVectorProperty(name="Bound Max",
+ subtype="TRANSLATION",
+ get=lambda self: get_float_vector_property(self, "bound_max"),
+ set=lambda self, value: set_float_vector_property(self, value, "bound_max"))
+ steps: bpy.props.CollectionProperty(
+ type=StepsExtensionListProperties, name="Steps")
+ steps_index: bpy.props.IntProperty(name="Step")
+
+ @property
+ def selected_step(self) -> Union[StepsExtensionListProperties, None]:
+ return get_list_item(self.steps, self.steps_index)
+
+ def new_step(self) -> StepsExtensionProperties:
+ item: StepsExtensionProperties = self.steps.add()
+ item.step_properties.position = (0.0, 0.0, 0.0)
+ return item
+
+ def delete_selected_stair(self):
+ if not self.selected_step:
+ return
+
+ self.steps.remove(self.steps_index)
+ self.steps_index = max(self.steps_index - 1, 0)
+
+ def flip_forward_vector(self):
+ if self.step_properties.rotation > math.pi:
+ self.step_properties.rotation -= math.pi
+ else:
+ self.step_properties.rotation += math.pi
+
+ def update_bound_box(self):
+ if len(self.steps) <= 0:
+ return
+
+ bound_min = bound_max = self.steps[0].step_properties.position
+ step_min = step_max = self.steps[0].step_properties
+
+ rotate = lambda x, y, angle: (x * math.sin(angle) - y * math.cos(angle), x * math.cos(angle) + y * math.sin(angle))
+ for step in self.steps:
+ prop = step.step_properties
+ step_min = step_min if step_min.position[2] <= prop.position[2] else prop
+ step_max = step_max if step_max.position[2] >= prop.position[2] else prop
+
+ half_width = prop.width / 2
+ half_depth = prop.depth / 2
+ corners = [
+ rotate(-half_width, -half_depth, prop.rotation),
+ rotate(half_width, -half_depth, prop.rotation),
+ rotate(half_width, half_depth, prop.rotation),
+ rotate(-half_width, half_depth, prop.rotation)]
+
+ for corner in corners:
+ bound_min = (min(bound_min[0], corner[0] + prop.position[0]), min(bound_min[1], corner[1] + prop.position[1]), min(bound_min[2], prop.position[2] - prop.height/2))
+ bound_max = (max(bound_max[0], corner[0] + prop.position[0]), max(bound_max[1], corner[1] + prop.position[1]), step_max.position[2])
+ self.bound_min = bound_min
+ self.bound_max = bound_max
+
+ self.top = step_max.position
+ self.bottom = (step_min.position[0], step_min.position[1], bound_min[2])
+
+ def generate_steps(self, selected_vertices):
+ vertices = dict()
+ for vertx in selected_vertices:
+ if vertx.z in vertices.keys():
+ vertices[vertx.z].append(vertx)
+ else:
+ vertices[vertx.z] = [vertx]
+
+ first_step = None
+ heights = []
+ vertices = sorted(list(vertices.values()), key=lambda v: v[0].z, reverse = False)
+ for i, vertx in enumerate(vertices):
+ if len(vertx) < 4:
+ print("Not 4 vertecies. Skipping...")
+ continue
+ new = self.new_step()
+ new.step_properties.position = sum(vertx, Vector())/len(vertx) - self.offset_position
+ if i != 0:
+ height = vertx[0].z - vertices[i-1][0].z
+ new.step_properties.height = height
+ new.step_properties.position.z -= height/2
+ heights.append(height)
+ else:
+ first_step = new
+ edges = []
+ for j in range(len(vertx) - 1):
+ edges.append(vertx[j] - vertx[j + 1])
+ edges.append(vertx[0] - vertx[-1])
+
+ edges.sort(key=lambda e: e.length, reverse=True)
+ print(edges)
+ width = Vector(edges[0])
+ new.step_properties.width = width.length
+ new.step_properties.depth = edges[-2].length
+ angle1 = -math.atan2(width.x, width.y)
+ angle2 = -math.atan2(edges[1].x, edges[1].y)
+ print(angle1, angle2)
+ new.step_properties.rotation = (angle1 + angle2)/2
+ height = sum(heights)/len(heights)
+ first_step.step_properties.position.z -= height/2
+ first_step.step_properties.height = height
+
+ #group by z toleranz 0.025
+ #find corner verts
+ #
+ #find unique edges
+ # controll inverted
+
+
+
+
+
+
+
class ExtensionProperties(bpy.types.PropertyGroup):
def get_properties(self) -> BaseExtensionProperties:
if self.extension_type == ExtensionType.DOOR:
@@ -315,6 +532,8 @@ def get_properties(self) -> BaseExtensionProperties:
return self.spawn_point_override_properties
elif self.extension_type == ExtensionType.WIND_DISTURBANCE:
return self.wind_disturbance_properties
+ elif self.extension_type == ExtensionType.Stairs:
+ return self.stairs_extension_properties
extension_type: bpy.props.EnumProperty(name="Type", items=ExtensionTypeEnumItems)
name: bpy.props.StringProperty(name="Name", default="Extension")
@@ -347,6 +566,8 @@ def get_properties(self) -> BaseExtensionProperties:
type=WindDisturbanceExtensionProperties)
proc_object_extension_properties: bpy.props.PointerProperty(
type=ProcObjectExtensionProperties)
+ stairs_extension_properties: bpy.props.PointerProperty(
+ type=StairsExtensionProperties)
class ExtensionsContainer:
@@ -371,6 +592,11 @@ def new_extension(self, ext_type=None) -> ExtensionProperties:
ladder_props = item.ladder_extension_properties
ladder_props.bottom = 0.0, 0.0, -2.5
+ stair_props = item.stairs_extension_properties
+ for name, value in stair_props.__class__.__annotations__.items():
+ if value.function == bpy.props.FloatVectorProperty:
+ setattr(stair_props, name, (0.0, 0.0, 0.0))
+
return item
def delete_selected_extension(self):
diff --git a/ytyp/ui/entities.py b/ytyp/ui/entities.py
index 1989f22..48cebb3 100644
--- a/ytyp/ui/entities.py
+++ b/ytyp/ui/entities.py
@@ -1,9 +1,10 @@
import bpy
from ...tabbed_panels import TabbedPanelHelper, TabPanel
from ...sollumz_ui import BasicListHelper, FlagsPanel, FilterListHelper, draw_list_with_add_remove
+from ...sollumz_properties import SollumzGame
from ..properties.ytyp import ArchetypeType
from ..properties.mlo import EntityProperties, MloEntityProperties
-from ..utils import get_selected_archetype, get_selected_entity
+from ..utils import get_selected_archetype, get_selected_entity, get_selected_ytyp
from .extensions import ExtensionsListHelper, ExtensionsPanelHelper
from .mlo import SOLLUMZ_PT_MLO_PANEL
@@ -133,6 +134,7 @@ def draw(self, context):
layout.use_property_decorate = False
selected_archetype = get_selected_archetype(context)
selected_entity = get_selected_entity(context)
+ selected_ytyp = get_selected_ytyp(context)
layout.prop(selected_entity, "linked_object")
@@ -162,6 +164,8 @@ def draw(self, context):
for prop_name in EntityProperties.__annotations__:
if prop_name == "flags":
continue
+ elif prop_name in {"blend_age_layer", "blend_age_dirt"} and selected_ytyp.game != SollumzGame.RDR:
+ continue
layout.prop(selected_entity, prop_name)
diff --git a/ytyp/ui/extensions.py b/ytyp/ui/extensions.py
index 5271fd1..1659320 100644
--- a/ytyp/ui/extensions.py
+++ b/ytyp/ui/extensions.py
@@ -1,7 +1,7 @@
import bpy
from ...tabbed_panels import TabPanel
from ...sollumz_ui import BasicListHelper, draw_list_with_add_remove
-from ..properties.extensions import ExtensionsContainer, ExtensionType
+from ..properties.extensions import ExtensionsContainer, ExtensionType, ExtensionProperties
from ..operators.extensions import (
SOLLUMZ_OT_update_bottom_from_selected,
SOLLUMZ_OT_update_corner_a_location,
@@ -13,6 +13,10 @@
SOLLUMZ_OT_update_particle_effect_location,
SOLLUMZ_OT_calculate_light_shaft_center_offset_location,
SOLLUMZ_OT_update_light_shaft_direction,
+ SOLLUMZ_OT_update_bound_box_stair,
+ SOLLUMZ_OT_update_step_size_and_location,
+ SOLLUMZ_OT_generate_steps_from_faces,
+ SOLLUMZ_OT_mirror_step,
)
from ..utils import get_selected_archetype, get_selected_extension
from .archetype import SOLLUMZ_PT_ARCHETYPE_TABS_PANEL
@@ -53,8 +57,8 @@ def draw(self, context):
extensions_container = self.get_extensions_container(context)
_, side_col = draw_list_with_add_remove(layout, self.ADD_OPERATOR_ID, self.DELETE_OPERATOR_ID,
- self.EXTENSIONS_LIST_ID, "", extensions_container, "extensions",
- extensions_container, "extension_index")
+ self.EXTENSIONS_LIST_ID, "", extensions_container, "extensions",
+ extensions_container, "extension_index")
side_col.separator()
side_col.operator(self.DUPLICATE_OPERATOR_ID, text="", icon="DUPLICATE")
@@ -105,18 +109,58 @@ def draw(self, context):
elif is_light_shaft and (prop_name.startswith("flag_") or prop_name == "scale_by_sun_intensity"):
# skip light shaft flag props, drawn above
# and skip scale_by_sun_intensity because it is the same as flag_5
- continue
+ continue
else:
if prop_name in {'direction_amount', 'cornerA'}:
layout.separator()
+ elif prop_name == "steps":
+ self.draw_steps(selected_extension)
+ break
+ elif prop_name in ["bottom", "top", "bound_min", "bound_max"]:
+ continue
row = layout.row()
row.prop(extension_properties, prop_name)
+ def draw_steps(self, selected_extension: ExtensionProperties):
+ extension_properties = selected_extension.get_properties()
+
+ layout = self.layout
+ layout.separator()
+ row = layout.row()
+ row.operator(SOLLUMZ_OT_update_bound_box_stair.bl_idname)
+ row = layout.row()
+ row.operator(SOLLUMZ_OT_generate_steps_from_faces.bl_idname)
+ layout.separator()
+
+ draw_list_with_add_remove(layout, "sollumz.addarchetypeextensionstep", "sollumz.deletearchetypeextensionstep",
+ "SOLLUMZ_UL_ARCHETYPE_EXTENSIONS_STAIR_LIST", "",
+ extension_properties, "steps", extension_properties, "steps_index")
+
+ if selected_extension.stairs_extension_properties:
+ if selected_extension.stairs_extension_properties.selected_step:
+ selected_step = selected_extension.stairs_extension_properties.selected_step
+ if selected_step.step_properties:
+ row = layout.row()
+ row.operator(SOLLUMZ_OT_update_step_size_and_location.bl_idname)
+ row = layout.row()
+ row.operator(SOLLUMZ_OT_mirror_step.bl_idname)
+
+ step = selected_step.step_properties
+ for prop_name in step.__class__.__annotations__:
+ if prop_name == "position":
+ continue
+ row = layout.row()
+ row.prop(step, prop_name)
+
class SOLLUMZ_UL_ARCHETYPE_EXTENSIONS_LIST(BasicListHelper, bpy.types.UIList):
bl_idname = "SOLLUMZ_UL_ARCHETYPE_EXTENSIONS_LIST"
icon = "CON_TRACKTO"
+class SOLLUMZ_UL_ARCHETYPE_EXTENSIONS_STAIR_LIST(BasicListHelper, bpy.types.UIList):
+ bl_idname = "SOLLUMZ_UL_ARCHETYPE_EXTENSIONS_STAIR_LIST"
+ icon = "CON_TRACKTO"
+
class SOLLUMZ_PT_ARCHETYPE_EXTENSIONS_PANEL(TabPanel, ExtensionsPanelHelper, bpy.types.Panel):
bl_label = "Extensions"
diff --git a/ytyp/ui/ytyp.py b/ytyp/ui/ytyp.py
index 602b76b..087664e 100644
--- a/ytyp/ui/ytyp.py
+++ b/ytyp/ui/ytyp.py
@@ -1,6 +1,6 @@
import bpy
from ...sollumz_ui import BasicListHelper, SollumzFileSettingsPanel, draw_list_with_add_remove
-from ...sollumz_properties import ArchetypeType
+from ...sollumz_properties import ArchetypeType, SOLLUMZ_UI_NAMES
from ...sollumz_preferences import (
get_import_settings,
get_export_settings,
@@ -50,6 +50,10 @@ def draw(self, context):
row = list_col.row()
row.operator("sollumz.importytyp", icon="IMPORT")
row.operator("sollumz.exportytyp", icon="EXPORT")
+ selected_ytyp = get_selected_ytyp(context)
+ if selected_ytyp:
+ row = list_col.row()
+ row.label(text=SOLLUMZ_UI_NAMES[selected_ytyp.game])
class SOLLUMZ_PT_import_ytyp(bpy.types.Panel, SollumzFileSettingsPanel):
diff --git a/ytyp/ytypexport.py b/ytyp/ytypexport.py
index 91fc0bb..ba3845b 100644
--- a/ytyp/ytypexport.py
+++ b/ytyp/ytypexport.py
@@ -1,6 +1,7 @@
from typing import Iterable
import bpy
from mathutils import Euler, Vector, Quaternion, Matrix
+from math import cos, sin
from ..cwxml import ytyp as ytypxml, ymap as ymapxml
from ..sollumz_properties import ArchetypeType, AssetType, EntityLodLevel, EntityPriorityLevel, SollumzGame, MapEntityType
@@ -92,6 +93,8 @@ def create_entity_xml(entity: MloEntityProperties) -> ymapxml.Entity:
"""Create xml mlo entity from an entity data-block."""
entity_xml = ymapxml.Entity()
+ if current_game == SollumzGame.RDR:
+ entity_xml = ymapxml.EntityRDR()
entity_obj = entity.linked_object
if entity_obj:
set_entity_xml_transforms_from_object(entity_obj, entity_xml)
@@ -106,6 +109,9 @@ def create_entity_xml(entity: MloEntityProperties) -> ymapxml.Entity:
entity_xml.ambient_occlusion_multiplier = entity.ambient_occlusion_multiplier
entity_xml.artificial_ambient_occlusion = entity.artificial_ambient_occlusion
entity_xml.tint_value = entity.tint_value
+ if isinstance(entity_xml, ymapxml.EntityRDR):
+ entity_xml.blend_age_layer = entity.blend_age_layer
+ entity_xml.blend_age_dirt = entity.blend_age_dirt
lod_level = next(name for name, value in vars(
EntityLodLevel).items() if value == (entity.lod_level))
@@ -188,6 +194,12 @@ def set_extension_xml_props(extension: ExtensionProperties, extension_xml: ymapx
ignored_props = getattr(extension_properties.__class__, "ignored_in_import_export", None) # see LightShaftExtensionProperties
+ #if type(extension_xml) == ymapxml.ExtensionStairs:
+ #extension_properties.bottom += extension_properties.offset_position
+ #extension_properties.top += extension_properties.offset_position
+ #extension_properties.bound_min += extension_properties.offset_position
+ #extension_properties.bound_max += extension_properties.offset_position
+
for prop_name in extension_properties.__class__.__annotations__:
if ignored_props is not None and prop_name in ignored_props:
continue
@@ -218,6 +230,20 @@ def set_extension_xml_props(extension: ExtensionProperties, extension_xml: ymapx
elif prop_name == "flashiness":
# convert enum back to int
prop_value = Flashiness[prop_value].value
+
+ elif prop_name == "steps":
+ prop_value = ymapxml.StepInstanceList()
+ for step in extension_properties.steps:
+ step_properties = step.step_properties
+ step_xml = ymapxml.StepInstance()
+ for prop_name_step in vars(step_xml).keys():
+ if hasattr(step_properties, prop_name_step):
+ setattr(step_xml, prop_name_step, getattr(step_properties, prop_name_step))
+ step_xml.position = extension_properties.offset_position
+ step_xml.forward = Vector((round(cos(step_properties.rotation), 6), round(sin(step_properties.rotation), 6), 0))
+ step_xml.right = Vector((-step_xml.forward.y, step_xml.forward.x, 0))
+ prop_value.value.append(step_xml)
+
setattr(extension_xml, prop_name, prop_value)
diff --git a/ytyp/ytypimport.py b/ytyp/ytypimport.py
index 79b6c25..07513d4 100644
--- a/ytyp/ytypimport.py
+++ b/ytyp/ytypimport.py
@@ -2,6 +2,7 @@
from typing import Union
from mathutils import Vector, Quaternion
+from math import atan2
from ..cwxml import ytyp as ytypxml, ymap as ymapxml
from ..sollumz_properties import ArchetypeType, AssetType, EntityLodLevel, EntityPriorityLevel, SollumzGame, MapEntityType
@@ -36,6 +37,7 @@ def create_mlo_entity_set(entity_set_xml: ytypxml.EntitySet, archetype: Archetyp
entity.attached_room_id = str(archetype.rooms[location].id)
+# maybe combine with create_mlo_entity()
def create_entity_set_entity(entity_xml: ymapxml.Entity, entity_set: EntitySetProperties):
"""Create an mlo entity from an xml for the provided archetype data-block."""
@@ -59,6 +61,9 @@ def create_entity_set_entity(entity_xml: ymapxml.Entity, entity_set: EntitySetPr
entity.ambient_occlusion_multiplier = entity_xml.ambient_occlusion_multiplier
entity.artificial_ambient_occlusion = entity_xml.artificial_ambient_occlusion
entity.tint_value = entity_xml.tint_value
+ if isinstance(entity_xml, ymapxml.EntityRDR):
+ entity.blend_age_layer = entity_xml.blend_age_layer
+ entity.blend_age_dirt = entity_xml.blend_age_dirt
for extension_xml in entity_xml.extensions:
create_extension(extension_xml, entity)
@@ -154,6 +159,9 @@ def create_mlo_entity(entity_xml: ymapxml.Entity, archetype: ArchetypeProperties
entity.ambient_occlusion_multiplier = entity_xml.ambient_occlusion_multiplier
entity.artificial_ambient_occlusion = entity_xml.artificial_ambient_occlusion
entity.tint_value = entity_xml.tint_value
+ if isinstance(entity_xml, ymapxml.EntityRDR):
+ entity.blend_age_layer = entity_xml.blend_age_layer
+ entity.blend_age_dirt = entity_xml.blend_age_dirt
for extension_xml in entity_xml.extensions:
create_extension(extension_xml, entity)
@@ -170,6 +178,12 @@ def set_extension_props(extension_xml: ymapxml.Extension, extension: ExtensionPr
ignored_props = getattr(extension_properties.__class__, "ignored_in_import_export", None) # see LightShaftExtensionProperties
+ if type(extension_xml) == ymapxml.ExtensionStairs:
+ extension_xml.bottom -= extension_xml.offset_position
+ extension_xml.top -= extension_xml.offset_position
+ extension_xml.bound_min -= extension_xml.offset_position
+ extension_xml.bound_max -= extension_xml.offset_position
+
for prop_name in extension_properties.__class__.__annotations__:
if ignored_props is not None and prop_name in ignored_props:
continue
@@ -202,6 +216,17 @@ def set_extension_props(extension_xml: ymapxml.Extension, extension: ExtensionPr
# `flashiness` is now an enum property, we need the enum as string
prop_value = Flashiness(prop_value).name
+ elif prop_name == "steps":
+ for step in extension_xml.steps:
+ new_step = extension_properties.new_step()
+ step.rotation = atan2(step.forward.y, step.forward.x)
+ step.position -= extension_xml.offset_position
+ for name in new_step.step_properties.__class__.__annotations__:
+ val = getattr(step, name)
+ if val:
+ setattr(new_step.step_properties, name, val)
+ continue
+
setattr(extension_properties, prop_name, prop_value)
@@ -333,7 +358,8 @@ def create_archetype(archetype_xml: ytypxml.BaseArchetype, ytyp: CMapTypesProper
archetype.name = archetype_xml.name
archetype.flags.total = str(archetype_xml.flags)
- archetype.special_attribute = SpecialAttribute(archetype_xml.special_attribute).name
+ if current_game == SollumzGame.RDR:
+ archetype.special_attribute = SpecialAttribute(archetype_xml.special_attribute).name
archetype.hd_texture_dist = archetype_xml.hd_texture_dist
archetype.texture_dictionary = archetype_xml.texture_dictionary
archetype.clip_dictionary = archetype_xml.clip_dictionary
@@ -347,8 +373,10 @@ def create_archetype(archetype_xml: ytypxml.BaseArchetype, ytyp: CMapTypesProper
archetype.asset_type = get_asset_type_enum(archetype_xml.asset_type)
if current_game == SollumzGame.RDR:
- archetype.load_flags = int(archetype_xml.load_flags, 0)
- archetype.guid = int(archetype_xml.guid, 0)
+ archetype.load_flags = archetype_xml.load_flags if isinstance(
+ archetype_xml.load_flags, int) else int(archetype_xml.load_flags, 0)
+ archetype.guid = archetype_xml.guid if isinstance(
+ archetype_xml.guid, int) else int(archetype_xml.guid, 0)
archetype.unknown_1 = get_map_entity_type_enum(archetype_xml.unknown_1)
find_and_set_archetype_asset(archetype)