diff --git a/.gitignore b/.gitignore
index d46fc38e..b9f0a467 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,3 +49,6 @@ obj~/
# SSH credentials, DO NOT COMMIT
/scripts/credentials
+
+# For the Blender addon
+__pycache__
diff --git a/Blender/Blender.csproj b/Blender/Blender.csproj
new file mode 100644
index 00000000..c61f2790
--- /dev/null
+++ b/Blender/Blender.csproj
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+ $(MSBuildProjectDirectory)/../Build/MapStation_Blender_Addon
+ $(MSBuildProjectDirectory)/../Build/MapStation_Blender_Addon.zip
+
+
+
+
+
+ <_Assets Include="$(MSBuildProjectDirectory)/mapstation/**/*" Exclude="__pycache__" />
+
+
+
+
+
+
+
+
+
diff --git a/Blender/mapstation/__init__.py b/Blender/mapstation/__init__.py
new file mode 100644
index 00000000..08ebf2af
--- /dev/null
+++ b/Blender/mapstation/__init__.py
@@ -0,0 +1,32 @@
+import importlib
+
+# To support auto-reload of local imports: https://blenderartists.org/t/is-reload-scripts-not-working-for-anyone-else-for-addon-development/1146804
+if "bpy" in locals():
+ importlib.reload(mapstation.auto_assign_guids)
+ importlib.reload(mapstation.hint_ui)
+else:
+ import mapstation.auto_assign_guids
+ import mapstation.hint_ui
+
+import bpy
+
+bl_info = {
+ "name": "MapStation",
+ "description": "Mapping tools for Bomb Rush Cyberfunk",
+ "author": "cspotcode",
+ "version": (1, 0),
+ "blender": (4, 0, 0),
+ "location": "",
+ "category": "Object"
+}
+
+def register():
+ print("Register")
+ mapstation.auto_assign_guids.register()
+
+def unregister():
+ print("Unregister")
+ mapstation.auto_assign_guids.unregister()
+
+if __name__ == "__main__":
+ register()
diff --git a/Blender/mapstation/auto_assign_guids.py b/Blender/mapstation/auto_assign_guids.py
new file mode 100644
index 00000000..9f44ce02
--- /dev/null
+++ b/Blender/mapstation/auto_assign_guids.py
@@ -0,0 +1,33 @@
+import bpy
+import uuid
+guid_prop = "guid"
+
+def gen_guid():
+ return str(uuid.uuid4())
+
+def assign_guids():
+ guids = set()
+ for obj in bpy.data.objects:
+ if guid_prop in obj:
+ guid = obj[guid_prop]
+ if guid in guids:
+ guid = gen_guid()
+ obj[guid_prop] = guid
+ else:
+ guid = gen_guid()
+ obj[guid_prop] = guid
+ guids.add(guid)
+
+ # return delay so timer calls us again
+ return 1
+
+def register():
+ print("register auto_assign_guids: " + __file__)
+ print("hi there")
+ bpy.app.timers.register(assign_guids)
+
+def unregister():
+ bpy.app.timers.unregister(assign_guids)
+
+if __name__ == "__main__":
+ register()
diff --git a/Blender/mapstation/hint_ui.py b/Blender/mapstation/hint_ui.py
new file mode 100644
index 00000000..9aef8c8e
--- /dev/null
+++ b/Blender/mapstation/hint_ui.py
@@ -0,0 +1,72 @@
+import bpy
+
+# Define the hints array
+hints = ["_Grind", "_NonStableSurface", "_Spawn"]
+
+class ObjectHintPanel(bpy.types.Panel):
+ bl_label = "BRC MapStation Hints"
+ bl_idname = "OBJECT_PT_hint_panel"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = 'Object'
+
+ def draw(self, context):
+ layout = self.layout
+
+ obj = context.active_object
+
+ if obj:
+ layout.label(text="Selected Object: " + obj.name)
+ layout.separator()
+
+ for hint in hints:
+ # Check if the hint is present in the object name
+ has_hint = hint in obj.name
+
+ # Toggle hint button
+ if has_hint:
+ layout.operator("object.remove_hint_operator", text="Remove " + hint).hint = hint
+ else:
+ layout.operator("object.add_hint_operator", text="Add " + hint).hint = hint
+ else:
+ layout.label(text="No object selected")
+
+
+class AddHintOperator(bpy.types.Operator):
+ bl_idname = "object.add_hint_operator"
+ bl_label = "Add Hint"
+
+ hint: bpy.props.StringProperty()
+
+ def execute(self, context):
+ obj = context.active_object
+ obj.name += self.hint
+ return {'FINISHED'}
+
+
+class RemoveHintOperator(bpy.types.Operator):
+ bl_idname = "object.remove_hint_operator"
+ bl_label = "Remove Hint"
+
+ hint: bpy.props.StringProperty()
+
+ def execute(self, context):
+ obj = context.active_object
+ obj.name = obj.name.replace(self.hint, "")
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_class(ObjectHintPanel)
+ bpy.utils.register_class(AddHintOperator)
+ bpy.utils.register_class(RemoveHintOperator)
+
+
+def unregister():
+ bpy.utils.unregister_class(ObjectHintPanel)
+ bpy.utils.unregister_class(AddHintOperator)
+ bpy.utils.unregister_class(RemoveHintOperator)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/MapStation.Editor/Assets/Maps/cspotcode.demo/VertexandCurve.blend b/MapStation.Editor/Assets/Maps/cspotcode.demo/VertexandCurve.blend
new file mode 100644
index 00000000..a8c80bf6
Binary files /dev/null and b/MapStation.Editor/Assets/Maps/cspotcode.demo/VertexandCurve.blend differ
diff --git a/MapStation.Editor/Assets/Maps/cspotcode.demo/VertexandCurve.blend.meta b/MapStation.Editor/Assets/Maps/cspotcode.demo/VertexandCurve.blend.meta
new file mode 100644
index 00000000..8e4bedee
--- /dev/null
+++ b/MapStation.Editor/Assets/Maps/cspotcode.demo/VertexandCurve.blend.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 0d1037da6b6fb6747a23937f64788ffa
+ModelImporter:
+ serializedVersion: 21300
+ internalIDToNameTable: []
+ externalObjects: {}
+ materials:
+ materialImportMode: 2
+ materialName: 0
+ materialSearch: 1
+ materialLocation: 1
+ animations:
+ legacyGenerateAnimations: 4
+ bakeSimulation: 0
+ resampleCurves: 1
+ optimizeGameObjects: 0
+ removeConstantScaleCurves: 1
+ motionNodeName:
+ rigImportErrors:
+ rigImportWarnings:
+ animationImportErrors:
+ animationImportWarnings:
+ animationRetargetingWarnings:
+ animationDoRetargetingWarnings: 0
+ importAnimatedCustomProperties: 0
+ importConstraints: 0
+ animationCompression: 1
+ animationRotationError: 0.5
+ animationPositionError: 0.5
+ animationScaleError: 0.5
+ animationWrapMode: 0
+ extraExposedTransformPaths: []
+ extraUserProperties: []
+ clipAnimations: []
+ isReadable: 0
+ meshes:
+ lODScreenPercentages: []
+ globalScale: 1
+ meshCompression: 0
+ addColliders: 1
+ useSRGBMaterialColor: 1
+ sortHierarchyByName: 1
+ importVisibility: 1
+ importBlendShapes: 1
+ importCameras: 1
+ importLights: 1
+ nodeNameCollisionStrategy: 1
+ fileIdsGeneration: 2
+ swapUVChannels: 0
+ generateSecondaryUV: 0
+ useFileUnits: 1
+ keepQuads: 0
+ weldVertices: 1
+ bakeAxisConversion: 0
+ preserveHierarchy: 0
+ skinWeightsMode: 0
+ maxBonesPerVertex: 4
+ minBoneWeight: 0.001
+ optimizeBones: 1
+ meshOptimizationFlags: 0
+ indexFormat: 0
+ secondaryUVAngleDistortion: 8
+ secondaryUVAreaDistortion: 15.000001
+ secondaryUVHardAngle: 88
+ secondaryUVMarginMethod: 1
+ secondaryUVMinLightmapResolution: 40
+ secondaryUVMinObjectScale: 1
+ secondaryUVPackMargin: 4
+ useFileScale: 1
+ tangentSpace:
+ normalSmoothAngle: 60
+ normalImportMode: 0
+ tangentImportMode: 3
+ normalCalculationMode: 4
+ legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
+ blendShapeNormalImportMode: 1
+ normalSmoothingSource: 0
+ referencedClips: []
+ importAnimation: 1
+ humanDescription:
+ serializedVersion: 3
+ human: []
+ skeleton: []
+ armTwist: 0.5
+ foreArmTwist: 0.5
+ upperLegTwist: 0.5
+ legTwist: 0.5
+ armStretch: 0.05
+ legStretch: 0.05
+ feetSpacing: 0
+ globalScale: 1
+ rootMotionBoneName:
+ hasTranslationDoF: 0
+ hasExtraRoot: 0
+ skeletonHasParents: 1
+ lastHumanDescriptionAvatarSource: {instanceID: 0}
+ autoGenerateAvatarMappingIfUnspecified: 1
+ animationType: 2
+ humanoidOversampling: 1
+ avatarSetup: 0
+ addHumanoidExtraRootOnlyWhenUsingAvatar: 1
+ remapMaterialsIfMaterialImportModeIsNone: 0
+ additionalBone: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MapStation.Editor/Assets/Maps/cspotcode.demo/newblend.blend b/MapStation.Editor/Assets/Maps/cspotcode.demo/newblend.blend
new file mode 100644
index 00000000..fb99a8ad
Binary files /dev/null and b/MapStation.Editor/Assets/Maps/cspotcode.demo/newblend.blend differ
diff --git a/MapStation.Editor/Assets/Maps/cspotcode.demo/newblend.blend.meta b/MapStation.Editor/Assets/Maps/cspotcode.demo/newblend.blend.meta
new file mode 100644
index 00000000..35659246
--- /dev/null
+++ b/MapStation.Editor/Assets/Maps/cspotcode.demo/newblend.blend.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: a5573d021026a1f43a5b46f85deafa84
+ModelImporter:
+ serializedVersion: 21300
+ internalIDToNameTable: []
+ externalObjects: {}
+ materials:
+ materialImportMode: 2
+ materialName: 0
+ materialSearch: 1
+ materialLocation: 1
+ animations:
+ legacyGenerateAnimations: 4
+ bakeSimulation: 0
+ resampleCurves: 1
+ optimizeGameObjects: 0
+ removeConstantScaleCurves: 1
+ motionNodeName:
+ rigImportErrors:
+ rigImportWarnings:
+ animationImportErrors:
+ animationImportWarnings:
+ animationRetargetingWarnings:
+ animationDoRetargetingWarnings: 0
+ importAnimatedCustomProperties: 0
+ importConstraints: 0
+ animationCompression: 1
+ animationRotationError: 0.5
+ animationPositionError: 0.5
+ animationScaleError: 0.5
+ animationWrapMode: 0
+ extraExposedTransformPaths: []
+ extraUserProperties: []
+ clipAnimations: []
+ isReadable: 1
+ meshes:
+ lODScreenPercentages: []
+ globalScale: 1
+ meshCompression: 0
+ addColliders: 1
+ useSRGBMaterialColor: 1
+ sortHierarchyByName: 1
+ importVisibility: 1
+ importBlendShapes: 1
+ importCameras: 1
+ importLights: 1
+ nodeNameCollisionStrategy: 1
+ fileIdsGeneration: 2
+ swapUVChannels: 0
+ generateSecondaryUV: 0
+ useFileUnits: 1
+ keepQuads: 1
+ weldVertices: 1
+ bakeAxisConversion: 0
+ preserveHierarchy: 1
+ skinWeightsMode: 0
+ maxBonesPerVertex: 4
+ minBoneWeight: 0.001
+ optimizeBones: 1
+ meshOptimizationFlags: 0
+ indexFormat: 0
+ secondaryUVAngleDistortion: 8
+ secondaryUVAreaDistortion: 15.000001
+ secondaryUVHardAngle: 88
+ secondaryUVMarginMethod: 1
+ secondaryUVMinLightmapResolution: 40
+ secondaryUVMinObjectScale: 1
+ secondaryUVPackMargin: 4
+ useFileScale: 1
+ tangentSpace:
+ normalSmoothAngle: 60
+ normalImportMode: 0
+ tangentImportMode: 3
+ normalCalculationMode: 4
+ legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
+ blendShapeNormalImportMode: 1
+ normalSmoothingSource: 0
+ referencedClips: []
+ importAnimation: 1
+ humanDescription:
+ serializedVersion: 3
+ human: []
+ skeleton: []
+ armTwist: 0.5
+ foreArmTwist: 0.5
+ upperLegTwist: 0.5
+ legTwist: 0.5
+ armStretch: 0.05
+ legStretch: 0.05
+ feetSpacing: 0
+ globalScale: 1
+ rootMotionBoneName:
+ hasTranslationDoF: 0
+ hasExtraRoot: 0
+ skeletonHasParents: 1
+ lastHumanDescriptionAvatarSource: {instanceID: 0}
+ autoGenerateAvatarMappingIfUnspecified: 1
+ animationType: 2
+ humanoidOversampling: 1
+ avatarSetup: 0
+ addHumanoidExtraRootOnlyWhenUsingAvatar: 1
+ remapMaterialsIfMaterialImportModeIsNone: 0
+ additionalBone: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MapStation.Editor/Assets/Maps/cspotcode.demo/spline-from-blender.blend b/MapStation.Editor/Assets/Maps/cspotcode.demo/spline-from-blender.blend
new file mode 100644
index 00000000..d0cc043a
Binary files /dev/null and b/MapStation.Editor/Assets/Maps/cspotcode.demo/spline-from-blender.blend differ
diff --git a/MapStation.Editor/Assets/Maps/cspotcode.demo/spline-from-blender.blend.meta b/MapStation.Editor/Assets/Maps/cspotcode.demo/spline-from-blender.blend.meta
new file mode 100644
index 00000000..f638d73f
--- /dev/null
+++ b/MapStation.Editor/Assets/Maps/cspotcode.demo/spline-from-blender.blend.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 04f0a475453fe684ab32c717251ff5e4
+ModelImporter:
+ serializedVersion: 21300
+ internalIDToNameTable: []
+ externalObjects: {}
+ materials:
+ materialImportMode: 2
+ materialName: 0
+ materialSearch: 1
+ materialLocation: 1
+ animations:
+ legacyGenerateAnimations: 4
+ bakeSimulation: 0
+ resampleCurves: 1
+ optimizeGameObjects: 0
+ removeConstantScaleCurves: 1
+ motionNodeName:
+ rigImportErrors:
+ rigImportWarnings:
+ animationImportErrors:
+ animationImportWarnings:
+ animationRetargetingWarnings:
+ animationDoRetargetingWarnings: 0
+ importAnimatedCustomProperties: 0
+ importConstraints: 0
+ animationCompression: 1
+ animationRotationError: 0.5
+ animationPositionError: 0.5
+ animationScaleError: 0.5
+ animationWrapMode: 0
+ extraExposedTransformPaths: []
+ extraUserProperties: []
+ clipAnimations: []
+ isReadable: 0
+ meshes:
+ lODScreenPercentages: []
+ globalScale: 1
+ meshCompression: 0
+ addColliders: 0
+ useSRGBMaterialColor: 1
+ sortHierarchyByName: 1
+ importVisibility: 1
+ importBlendShapes: 1
+ importCameras: 1
+ importLights: 1
+ nodeNameCollisionStrategy: 1
+ fileIdsGeneration: 2
+ swapUVChannels: 0
+ generateSecondaryUV: 0
+ useFileUnits: 1
+ keepQuads: 0
+ weldVertices: 1
+ bakeAxisConversion: 0
+ preserveHierarchy: 0
+ skinWeightsMode: 0
+ maxBonesPerVertex: 4
+ minBoneWeight: 0.001
+ optimizeBones: 1
+ meshOptimizationFlags: -1
+ indexFormat: 0
+ secondaryUVAngleDistortion: 8
+ secondaryUVAreaDistortion: 15.000001
+ secondaryUVHardAngle: 88
+ secondaryUVMarginMethod: 1
+ secondaryUVMinLightmapResolution: 40
+ secondaryUVMinObjectScale: 1
+ secondaryUVPackMargin: 4
+ useFileScale: 1
+ tangentSpace:
+ normalSmoothAngle: 60
+ normalImportMode: 0
+ tangentImportMode: 3
+ normalCalculationMode: 4
+ legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
+ blendShapeNormalImportMode: 1
+ normalSmoothingSource: 0
+ referencedClips: []
+ importAnimation: 1
+ humanDescription:
+ serializedVersion: 3
+ human: []
+ skeleton: []
+ armTwist: 0.5
+ foreArmTwist: 0.5
+ upperLegTwist: 0.5
+ legTwist: 0.5
+ armStretch: 0.05
+ legStretch: 0.05
+ feetSpacing: 0
+ globalScale: 1
+ rootMotionBoneName:
+ hasTranslationDoF: 0
+ hasExtraRoot: 0
+ skeletonHasParents: 1
+ lastHumanDescriptionAvatarSource: {instanceID: 0}
+ autoGenerateAvatarMappingIfUnspecified: 1
+ animationType: 2
+ humanoidOversampling: 1
+ avatarSetup: 0
+ addHumanoidExtraRootOnlyWhenUsingAvatar: 1
+ remapMaterialsIfMaterialImportModeIsNone: 0
+ additionalBone: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MapStation.Tools/Editor/ImporterThing.cs b/MapStation.Tools/Editor/ImporterThing.cs
new file mode 100644
index 00000000..2d2e0784
--- /dev/null
+++ b/MapStation.Tools/Editor/ImporterThing.cs
@@ -0,0 +1,39 @@
+using System.IO;
+using Reptile;
+using UnityEditor;
+using UnityEngine;
+
+class CustomImportProcessor : AssetPostprocessor {
+
+ // void OnPostprocessGameObjectWithUserProperties(GameObject go, string[] names, System.Object[] values) {
+ // ModelImporter importer = (ModelImporter)assetImporter;
+ // var asset_name = Path.GetFileName(importer.assetPath);
+ // Debug.LogFormat("OnPostprocessGameObjectWithUserProperties(go = {0}) asset = {1} path = {2}", go.name, asset_name, importer.assetPath);
+ // Vector3 vec3 = Vector3.zero;
+ // for (int i = 0; i < names.Length; i++) {
+ // var name = names[i];
+ // var val = values[i];
+ // Debug.Log($"{name}={val}");
+ // }
+ // var teleport = go.AddComponent();
+ // teleport.teleportTo = go.transform;
+ // }
+ //
+ // void OnPostprocessMeshHierarchy(GameObject go) {
+ // Debug.Log("OnPostprocessMeshHierarchy");
+ // var teleport = go.AddComponent();
+ // teleport.teleportTo = go.transform;
+ //
+ // }
+ // void OnPostprocessModel(GameObject go) {
+ // Debug.Log("OnPostprocessModel");
+ // var teleport = go.AddComponent();
+ //
+ // }
+ // void OnPostprocessPrefab(GameObject go) {
+ // Debug.Log("OnPostprocessPrefab");
+ // var teleport = go.AddComponent();
+ // teleport.teleportTo = go.transform;
+ //
+ // }
+}
diff --git a/MapStation.Tools/Editor/ImporterThing.cs.meta b/MapStation.Tools/Editor/ImporterThing.cs.meta
new file mode 100644
index 00000000..bc965c5a
--- /dev/null
+++ b/MapStation.Tools/Editor/ImporterThing.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 50aef77b27197814fad1693af611252b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MapStation.sln b/MapStation.sln
index 6c47e61a..b148e76a 100644
--- a/MapStation.sln
+++ b/MapStation.sln
@@ -18,6 +18,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapStation.Tools", "MapStat
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlopCrewClient", "libs\SlopCrewClient\SlopCrewClient\SlopCrewClient.csproj", "{A148FFDA-2617-45B5-AD09-6E1369617520}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blender", "Blender\Blender.csproj", "{AB57FECC-D0A5-4E4D-BA0F-BB2860D9033D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -52,6 +54,8 @@ Global
{A148FFDA-2617-45B5-AD09-6E1369617520}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A148FFDA-2617-45B5-AD09-6E1369617520}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A148FFDA-2617-45B5-AD09-6E1369617520}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB57FECC-D0A5-4E4D-BA0F-BB2860D9033D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB57FECC-D0A5-4E4D-BA0F-BB2860D9033D}.Debug|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE