diff --git a/Assets/Items.meta b/Assets/Items.meta
new file mode 100644
index 0000000..9cef3be
--- /dev/null
+++ b/Assets/Items.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 94c8f7f884ccd0e47a66f83dc8fe8b0e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Items/BhopBoots_Item.asset b/Assets/Items/BhopBoots_Item.asset
new file mode 100644
index 0000000..9ff50fd
--- /dev/null
+++ b/Assets/Items/BhopBoots_Item.asset
@@ -0,0 +1,20 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: b549159f64e1b164082e2e4ae4d28ac1, type: 3}
+ m_Name: BhopBoots_Item
+ m_EditorClassIdentifier: Assembly-CSharp::ItemDefinition
+ itemName: Bunny Hop Boots
+ icon: {fileID: 0}
+ type: 0
+ weaponPrefab: {fileID: 0}
+ isEquippable: 1
+ description: Illegal footwear. Gives 200 stamina while worn. Do not wear near cliffs.
diff --git a/Assets/Items/BhopBoots_Item.asset.meta b/Assets/Items/BhopBoots_Item.asset.meta
new file mode 100644
index 0000000..1825a76
--- /dev/null
+++ b/Assets/Items/BhopBoots_Item.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e09f7b05a155ba8449a0f0d8412ebe9d
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Items/GunSplat_Item.asset b/Assets/Items/GunSplat_Item.asset
new file mode 100644
index 0000000..93d3e28
--- /dev/null
+++ b/Assets/Items/GunSplat_Item.asset
@@ -0,0 +1,19 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: b549159f64e1b164082e2e4ae4d28ac1, type: 3}
+ m_Name: GunSplat_Item
+ m_EditorClassIdentifier: Assembly-CSharp::ItemDefinition
+ itemName: Gun Splat
+ icon: {fileID: 0}
+ type: 2
+ weaponPrefab: {fileID: 8985654986098955209, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ description: A janky lidar-scanned sidearm. Shoots first, looks weird always.
diff --git a/Assets/Items/GunSplat_Item.asset.meta b/Assets/Items/GunSplat_Item.asset.meta
new file mode 100644
index 0000000..398a208
--- /dev/null
+++ b/Assets/Items/GunSplat_Item.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2b5a641a42945604c9ffa88754a9f6d1
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Models/LidarScans/material_0 8.mat b/Assets/Models/LidarScans/material_0 8.mat
index 5c96f19..2be6572 100644
--- a/Assets/Models/LidarScans/material_0 8.mat
+++ b/Assets/Models/LidarScans/material_0 8.mat
@@ -1,5 +1,18 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &-809172725653918854
+MonoBehaviour:
+ m_ObjectHideFlags: 11
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
+ version: 10
--- !u!21 &2100000
Material:
serializedVersion: 8
@@ -30,8 +43,22 @@ Material:
m_Texture: {fileID: -5390149515647955136, guid: 24bb6e32322f940e59304941b7849582, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
+ - unity_Lightmaps:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - unity_LightmapsInd:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - unity_ShadowMasks:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
+ - _AlphaClip: 0
+ - _AlphaToMask: 0
- _BUILTIN_AlphaClip: 0
- _BUILTIN_Blend: 0
- _BUILTIN_CullMode: 2
@@ -43,6 +70,19 @@ Material:
- _BUILTIN_ZTest: 4
- _BUILTIN_ZWrite: 1
- _BUILTIN_ZWriteControl: 0
+ - _Blend: 0
+ - _CastShadows: 1
+ - _Cull: 2
+ - _DstBlend: 0
+ - _DstBlendAlpha: 0
+ - _QueueControl: 1
+ - _QueueOffset: 0
+ - _SrcBlend: 1
+ - _SrcBlendAlpha: 1
+ - _Surface: 0
+ - _ZTest: 4
+ - _ZWrite: 1
+ - _ZWriteControl: 0
- alphaCutoff: 0
- baseColorTexture_texCoord: 0
m_Colors:
diff --git a/Assets/Prefabs/Pickups.meta b/Assets/Prefabs/Pickups.meta
new file mode 100644
index 0000000..82c0cda
--- /dev/null
+++ b/Assets/Prefabs/Pickups.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c47b29375f392f14385951a3f2e89a56
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab b/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab
new file mode 100644
index 0000000..ff678b2
--- /dev/null
+++ b/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab
@@ -0,0 +1,161 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &976112319745431805
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 4275644691617312389}
+ - component: {fileID: 5061255202814967808}
+ - component: {fileID: 1990460956270860801}
+ m_Layer: 0
+ m_Name: Visual
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &4275644691617312389
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 976112319745431805}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 0.4, y: 0.2, z: 0.7}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 9221872812818225285}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &5061255202814967808
+MeshFilter:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 976112319745431805}
+ m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!23 &1990460956270860801
+MeshRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 976112319745431805}
+ m_Enabled: 1
+ m_CastShadows: 1
+ m_ReceiveShadows: 1
+ m_DynamicOccludee: 1
+ m_StaticShadowCaster: 0
+ m_MotionVectors: 1
+ m_LightProbeUsage: 1
+ m_ReflectionProbeUsage: 1
+ m_RayTracingMode: 2
+ m_RayTraceProcedural: 0
+ m_RayTracingAccelStructBuildFlagsOverride: 0
+ m_RayTracingAccelStructBuildFlags: 1
+ m_SmallMeshCulling: 1
+ m_ForceMeshLod: -1
+ m_MeshLodSelectionBias: 0
+ m_RenderingLayerMask: 1
+ m_RendererPriority: 0
+ m_Materials:
+ - {fileID: 0}
+ m_StaticBatchInfo:
+ firstSubMesh: 0
+ subMeshCount: 0
+ m_StaticBatchRoot: {fileID: 0}
+ m_ProbeAnchor: {fileID: 0}
+ m_LightProbeVolumeOverride: {fileID: 0}
+ m_ScaleInLightmap: 1
+ m_ReceiveGI: 1
+ m_PreserveUVs: 1
+ m_IgnoreNormalsForChartDetection: 0
+ m_ImportantGI: 0
+ m_StitchLightmapSeams: 1
+ m_SelectedEditorRenderState: 3
+ m_MinimumChartSize: 4
+ m_AutoUVMaxDistance: 0.5
+ m_AutoUVMaxAngle: 89
+ m_LightmapParameters: {fileID: 0}
+ m_GlobalIlluminationMeshLod: 0
+ m_SortingLayerID: 0
+ m_SortingLayer: 0
+ m_SortingOrder: 0
+ m_MaskInteraction: 0
+ m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &5967924902892500278
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 9221872812818225285}
+ - component: {fileID: 8317664859568879488}
+ - component: {fileID: 6478899211359510473}
+ m_Layer: 0
+ m_Name: BhopBoots_Pickup
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &9221872812818225285
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 5967924902892500278}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 4275644691617312389}
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &8317664859568879488
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 5967924902892500278}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 95709fa8fd6c75045b1ef8e4f505c152, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::PickupItem
+ definition: {fileID: 11400000, guid: e09f7b05a155ba8449a0f0d8412ebe9d, type: 2}
+ itemName: Item
+ itemIcon: {fileID: 0}
+ spinSpeed: 140
+ bobHeight: 0.2
+ bobSpeed: 2.8
+ pickupRadius: 2.2
+ pickupKey: 101
+ pickupParticlesPrefab: {fileID: 0}
+ pickupSound: {fileID: 0}
+--- !u!114 &6478899211359510473
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 5967924902892500278}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 1ce88c8ee6fd8a0469215a541f72f835, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::StaminaBoostPickup
+ newMaxStamina: 200
diff --git a/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab.meta b/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab.meta
new file mode 100644
index 0000000..54fc949
--- /dev/null
+++ b/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 9ead08e094f7bc74c86e58083dfcdf75
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Prefabs/Weapons.meta b/Assets/Prefabs/Weapons.meta
new file mode 100644
index 0000000..532f1a1
--- /dev/null
+++ b/Assets/Prefabs/Weapons.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ef5d1bcacc1d9e346b6e53fbcec62ad8
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Prefabs/Weapons/GunSplat.prefab b/Assets/Prefabs/Weapons/GunSplat.prefab
new file mode 100644
index 0000000..1cd8dd4
--- /dev/null
+++ b/Assets/Prefabs/Weapons/GunSplat.prefab
@@ -0,0 +1,141 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &8985654986098955209
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 6346070617587720957}
+ - component: {fileID: 8585139455581857007}
+ - component: {fileID: 5387598106901245531}
+ m_Layer: 0
+ m_Name: GunSplat
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &6346070617587720957
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 8985654986098955209}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 6241336930636794420}
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &8585139455581857007
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 8985654986098955209}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e348b4e172ba23d45960c0071cc09b1f, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::SimpleGun
+ damage: 30
+ range: 120
+ fireRate: 6
+ maxAmmo: 24
+ currentAmmo: 0
+ isAutomatic: 0
+ recoilKickback: 0.08
+ recoilKickUp: 0.04
+ recoilRecoverySpeed: 12
+ enableInternalBob: 0
+ bobFrequency: 10
+ bobHorizontalAmplitude: 0.05
+ bobVerticalAmplitude: 0.03
+ sprintBobMultiplier: 1.5
+ bobReturnSpeed: 6
+ fpsCam: {fileID: 0}
+--- !u!114 &5387598106901245531
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 8985654986098955209}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 59433c687536a60418803799027ddcc3, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::WeaponViewmodel
+ positionOffset: {x: 0.13, y: -0.25, z: 0.45}
+ rotationOffset: {x: 0.03, y: 0, z: 0}
+ scale: {x: 1, y: 1, z: 1}
+--- !u!1001 &8738524764220850214
+PrefabInstance:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Modification:
+ serializedVersion: 3
+ m_TransformParent: {fileID: 6346070617587720957}
+ m_Modifications:
+ - target: {fileID: -8098169881513260187, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_Name
+ value: Visual
+ objectReference: {fileID: 0}
+ - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_LocalPosition.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_LocalPosition.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_LocalPosition.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_LocalRotation.w
+ value: 1
+ objectReference: {fileID: 0}
+ - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_LocalRotation.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_LocalRotation.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_LocalRotation.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.z
+ value: 0
+ objectReference: {fileID: 0}
+ m_RemovedComponents: []
+ m_RemovedGameObjects: []
+ m_AddedGameObjects: []
+ m_AddedComponents: []
+ m_SourcePrefab: {fileID: 3150474306388093854, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+--- !u!4 &6241336930636794420 stripped
+Transform:
+ m_CorrespondingSourceObject: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
+ m_PrefabInstance: {fileID: 8738524764220850214}
+ m_PrefabAsset: {fileID: 0}
diff --git a/Assets/Prefabs/Weapons/GunSplat.prefab.meta b/Assets/Prefabs/Weapons/GunSplat.prefab.meta
new file mode 100644
index 0000000..b171bfd
--- /dev/null
+++ b/Assets/Prefabs/Weapons/GunSplat.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: aa9cce2c129fa1a41bf5a539b136f7a6
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scenes/Testing Scenes/Movement.unity b/Assets/Scenes/Testing Scenes/Movement.unity
index 9330936..db3c93a 100644
--- a/Assets/Scenes/Testing Scenes/Movement.unity
+++ b/Assets/Scenes/Testing Scenes/Movement.unity
@@ -340,9 +340,22 @@ PrefabInstance:
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
- m_RemovedGameObjects: []
+ m_RemovedGameObjects:
+ - {fileID: 2029899304289424255, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3}
m_AddedGameObjects: []
- m_AddedComponents: []
+ m_AddedComponents:
+ - targetCorrespondingSourceObject: {fileID: 6882903375964528458, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 2039163533}
+ - targetCorrespondingSourceObject: {fileID: 6882903375964528458, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 2039163534}
+ - targetCorrespondingSourceObject: {fileID: 6882903375964528458, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 2039163535}
+ - targetCorrespondingSourceObject: {fileID: 6882903375964528458, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 2039163536}
m_SourcePrefab: {fileID: 100100000, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3}
--- !u!1001 &197010634
PrefabInstance:
@@ -6459,52 +6472,6 @@ MeshCollider:
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 0}
---- !u!1 &487503082
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 487503084}
- - component: {fileID: 487503083}
- m_Layer: 0
- m_Name: GameObject (1)
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!114 &487503083
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 487503082}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner
- enemyType: 2
- healthOverride: 0
---- !u!4 &487503084
-Transform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 487503082}
- serializedVersion: 2
- m_LocalRotation: {x: 0.07095997, y: 0.5789249, z: -0.050682172, w: 0.8107047}
- m_LocalPosition: {x: 243.82, y: -35.34, z: 91.07}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
- m_Children: []
- m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &571790233 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: -8098169881513260187, guid: 3df8bb31de452e64fa85a1265898d698, type: 3}
@@ -6532,52 +6499,6 @@ MeshCollider:
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 1050908021570271116, guid: 3df8bb31de452e64fa85a1265898d698, type: 3}
---- !u!1 &606146705
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 606146707}
- - component: {fileID: 606146706}
- m_Layer: 0
- m_Name: GameObject (2)
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!114 &606146706
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 606146705}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner
- enemyType: 1
- healthOverride: 0
---- !u!4 &606146707
-Transform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 606146705}
- serializedVersion: 2
- m_LocalRotation: {x: 0.07095997, y: 0.5789249, z: -0.050682172, w: 0.8107047}
- m_LocalPosition: {x: 244.40219, y: -35.34, z: 89.64255}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
- m_Children: []
- m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &624019478 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: -8220620780705863596, guid: 9e848a421785b4212abcb7cf4ae082f3, type: 3}
@@ -6732,6 +6653,33 @@ MonoBehaviour:
m_ShadowLayerMask: 1
m_RenderingLayers: 1
m_ShadowRenderingLayers: 1
+--- !u!1 &715417260 stripped
+GameObject:
+ m_CorrespondingSourceObject: {fileID: -8098169881513260187, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3}
+ m_PrefabInstance: {fileID: 791320992}
+ m_PrefabAsset: {fileID: 0}
+--- !u!114 &715417264
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 715417260}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 95709fa8fd6c75045b1ef8e4f505c152, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::PickupItem
+ definition: {fileID: 11400000, guid: e09f7b05a155ba8449a0f0d8412ebe9d, type: 2}
+ itemName: Item
+ itemIcon: {fileID: 0}
+ spinSpeed: 120
+ bobHeight: 0.18
+ bobSpeed: 2.2
+ pickupRadius: 2
+ pickupKey: 101
+ pickupParticlesPrefab: {fileID: 0}
+ pickupSound: {fileID: 0}
--- !u!1001 &791320992
PrefabInstance:
m_ObjectHideFlags: 0
@@ -6758,7 +6706,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3}
propertyPath: m_LocalPosition.x
- value: 365.609
+ value: 363.888
objectReference: {fileID: 0}
- target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3}
propertyPath: m_LocalPosition.y
@@ -6766,7 +6714,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3}
propertyPath: m_LocalPosition.z
- value: 156.609
+ value: 157.267
objectReference: {fileID: 0}
- target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3}
propertyPath: m_LocalRotation.w
@@ -6799,7 +6747,10 @@ PrefabInstance:
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
- m_AddedComponents: []
+ m_AddedComponents:
+ - targetCorrespondingSourceObject: {fileID: -8098169881513260187, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 715417264}
m_SourcePrefab: {fileID: 3150474306388093854, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3}
--- !u!1001 &806276174
PrefabInstance:
@@ -6919,6 +6870,66 @@ MeshCollider:
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 1050908021570271116, guid: 1e096925873fc492b854ee214231ab86, type: 3}
+--- !u!1001 &1007367441
+PrefabInstance:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Modification:
+ serializedVersion: 3
+ m_TransformParent: {fileID: 0}
+ m_Modifications:
+ - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_LocalPosition.x
+ value: 355.01
+ objectReference: {fileID: 0}
+ - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_LocalPosition.y
+ value: -18.848
+ objectReference: {fileID: 0}
+ - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_LocalPosition.z
+ value: 167.46
+ objectReference: {fileID: 0}
+ - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_LocalRotation.w
+ value: 1
+ objectReference: {fileID: 0}
+ - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_LocalRotation.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_LocalRotation.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_LocalRotation.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 8985654986098955209, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ propertyPath: m_Name
+ value: GunSplat
+ objectReference: {fileID: 0}
+ m_RemovedComponents: []
+ m_RemovedGameObjects: []
+ m_AddedGameObjects: []
+ m_AddedComponents:
+ - targetCorrespondingSourceObject: {fileID: 8985654986098955209, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 2128421432}
+ m_SourcePrefab: {fileID: 100100000, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
--- !u!1001 &1022368674
PrefabInstance:
m_ObjectHideFlags: 0
@@ -7125,6 +7136,60 @@ MeshCollider:
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 1050908021570271116, guid: d26387bf4a33f4f03b916907f6b50828, type: 3}
+--- !u!1 &1114521747
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1114521749}
+ - component: {fileID: 1114521748}
+ m_Layer: 0
+ m_Name: gunsplat
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &1114521748
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1114521747}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 95709fa8fd6c75045b1ef8e4f505c152, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::PickupItem
+ definition: {fileID: 11400000, guid: 2b5a641a42945604c9ffa88754a9f6d1, type: 2}
+ itemName: Weapon
+ itemIcon: {fileID: 0}
+ spinSpeed: 120
+ bobHeight: 0.18
+ bobSpeed: 2.2
+ pickupRadius: 2
+ pickupKey: 101
+ pickupParticlesPrefab: {fileID: 0}
+ pickupSound: {fileID: 0}
+--- !u!4 &1114521749
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1114521747}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 355.188, y: -16.728, z: 155.372}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1121149997 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: -8098169881513260187, guid: 3c8a5d44028bd40d3abe5e22cafcb901, type: 3}
@@ -7569,11 +7634,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
propertyPath: m_LocalPosition.x
- value: 304.18
+ value: -186547
objectReference: {fileID: 0}
- target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
propertyPath: m_LocalPosition.y
- value: -146.63
+ value: -74119
objectReference: {fileID: 0}
- target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3}
propertyPath: m_LocalPosition.z
@@ -8155,52 +8220,6 @@ PrefabInstance:
insertIndex: -1
addedObject: {fileID: 277384082}
m_SourcePrefab: {fileID: 3150474306388093854, guid: 4e219defa86804242a4efe4306a4b26b, type: 3}
---- !u!1 &1882479419
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 1882479421}
- - component: {fileID: 1882479420}
- m_Layer: 0
- m_Name: GameObject (3)
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!114 &1882479420
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1882479419}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner
- enemyType: 0
- healthOverride: 0
---- !u!4 &1882479421
-Transform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1882479419}
- serializedVersion: 2
- m_LocalRotation: {x: 0.07095997, y: 0.5789249, z: -0.050682172, w: 0.8107047}
- m_LocalPosition: {x: 243.82, y: -35.34, z: 91.07}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
- m_Children: []
- m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &2026185005
PrefabInstance:
m_ObjectHideFlags: 0
@@ -8277,52 +8296,115 @@ PrefabInstance:
insertIndex: -1
addedObject: {fileID: 990201665}
m_SourcePrefab: {fileID: 3150474306388093854, guid: 1e096925873fc492b854ee214231ab86, type: 3}
---- !u!1 &2073272903
+--- !u!1 &2039163524 stripped
GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
+ m_CorrespondingSourceObject: {fileID: 6882903375964528458, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3}
+ m_PrefabInstance: {fileID: 110786869}
m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 2073272905}
- - component: {fileID: 2073272904}
- m_Layer: 0
- m_Name: GameObject
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!114 &2073272904
+--- !u!114 &2039163533
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 2073272903}
+ m_GameObject: {fileID: 2039163524}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3}
+ m_Script: {fileID: 11500000, guid: 514ba78e0d4cf8b4787471c2db0c6976, type: 3}
m_Name:
- m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner
- enemyType: 1
- healthOverride: 0
---- !u!4 &2073272905
-Transform:
+ m_EditorClassIdentifier: Assembly-CSharp::Inventory
+ items: []
+ toggleKey: 105
+ equipKey: 102
+ columns: 4
+ maxSlots: 20
+ colBackground: {r: 0.05, g: 0.05, b: 0.05, a: 0.92}
+ colPanel: {r: 0.1, g: 0.1, b: 0.1, a: 0.95}
+ colSlotEmpty: {r: 0.18, g: 0.18, b: 0.18, a: 1}
+ colSlotFilled: {r: 0.22, g: 0.28, b: 0.22, a: 1}
+ colSlotHover: {r: 0.35, g: 0.45, b: 0.35, a: 1}
+ colSlotSelected: {r: 0.45, g: 0.75, b: 0.45, a: 1}
+ colSlotEquipped: {r: 0.6, g: 0.4, b: 0.1, a: 1}
+ colBorder: {r: 0.4, g: 0.65, b: 0.4, a: 1}
+ colText: {r: 0.85, g: 0.95, b: 0.85, a: 1}
+ colDim: {r: 0.5, g: 0.6, b: 0.5, a: 1}
+ colAccent: {r: 0.5, g: 0.9, b: 0.5, a: 1}
+ colWeaponAccent: {r: 0.95, g: 0.75, b: 0.2, a: 1}
+ colCtxBg: {r: 0.08, g: 0.1, b: 0.08, a: 0.98}
+ colCtxBorder: {r: 0.4, g: 0.65, b: 0.4, a: 1}
+ colCtxHover: {r: 0.2, g: 0.35, b: 0.2, a: 1}
+ colCtxText: {r: 0.85, g: 0.95, b: 0.85, a: 1}
+ colCtxDestructive: {r: 0.85, g: 0.25, b: 0.2, a: 1}
+--- !u!114 &2039163534
+MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 2073272903}
- serializedVersion: 2
- m_LocalRotation: {x: 0.07095997, y: 0.5789249, z: -0.050682172, w: 0.8107047}
- m_LocalPosition: {x: 243.82, y: -35.34, z: 91.07}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
- m_Children: []
- m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_GameObject: {fileID: 2039163524}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 18715d388d5da7544b10c5d7a385cbee, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::WeaponManager
+ weaponHolder: {fileID: 0}
+ weaponPositionOffset: {x: 0.2, y: -0.25, z: 0.45}
+ weaponRotationOffset: {x: 0, y: 0, z: 0}
+ weaponScale: {x: 1, y: 1, z: 1}
+ allowScrollSwitch: 1
+ allowNumberKeys: 1
+--- !u!114 &2039163535
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2039163524}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 1d84d3e6c1cf6794da6ff84203edb291, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::PlayerHUD
+ barWidth: 220
+ barHeight: 18
+ barSpacing: 10
+ edgePadX: 18
+ edgePadY: 18
+ anchor: 0
+ colHealthFull: {r: 0.15, g: 0.8, b: 0.25, a: 1}
+ colHealthMid: {r: 0.9, g: 0.75, b: 0.1, a: 1}
+ colHealthLow: {r: 0.9, g: 0.15, b: 0.1, a: 1}
+ healthMidThreshold: 0.5
+ healthLowThreshold: 0.25
+ colStaminaNormal: {r: 0.2, g: 0.55, b: 0.95, a: 1}
+ colStaminaRegen: {r: 0.2, g: 0.55, b: 0.95, a: 0.45}
+ colStaminaExhaust: {r: 0.6, g: 0.18, b: 0.18, a: 1}
+ colBarBackground: {r: 0.06, g: 0.06, b: 0.06, a: 0.9}
+ colBarBorder: {r: 0.28, g: 0.28, b: 0.28, a: 1}
+ colLabel: {r: 0.8, g: 0.8, b: 0.8, a: 1}
+ colLabelCritical: {r: 1, g: 0.25, b: 0.25, a: 1}
+ borderThickness: 1.5
+ pulseSpeed: 4
+ showSpeedometer: 1
+ colSpeedo: {r: 0.2, g: 0.95, b: 0.4, a: 1}
+ colSpeedoFast: {r: 0.95, g: 0.8, b: 0.1, a: 1}
+ colSpeedoCritical: {r: 0.95, g: 0.2, b: 0.2, a: 1}
+ speedoFastThreshold: 60
+ speedoCritThreshold: 75
+--- !u!114 &2039163536
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2039163524}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5680222822977cd4a988c93d7b8881bc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::BootsEffect
+ bootsItemName: Bunny Hop Boots
+ boostedMaxStamina: 200
--- !u!1001 &2100225160
PrefabInstance:
m_ObjectHideFlags: 0
@@ -8445,6 +8527,33 @@ MeshCollider:
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 0}
+--- !u!1 &2128421431 stripped
+GameObject:
+ m_CorrespondingSourceObject: {fileID: 8985654986098955209, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3}
+ m_PrefabInstance: {fileID: 1007367441}
+ m_PrefabAsset: {fileID: 0}
+--- !u!114 &2128421432
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2128421431}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 95709fa8fd6c75045b1ef8e4f505c152, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::PickupItem
+ definition: {fileID: 11400000, guid: 2b5a641a42945604c9ffa88754a9f6d1, type: 2}
+ itemName: Item
+ itemIcon: {fileID: 0}
+ spinSpeed: 120
+ bobHeight: 0.18
+ bobSpeed: 2.2
+ pickupRadius: 2
+ pickupKey: 101
+ pickupParticlesPrefab: {fileID: 0}
+ pickupSound: {fileID: 0}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
@@ -8454,10 +8563,6 @@ SceneRoots:
- {fileID: 1719075356}
- {fileID: 1410308539}
- {fileID: 343869061}
- - {fileID: 2073272905}
- - {fileID: 487503084}
- - {fileID: 606146707}
- - {fileID: 1882479421}
- {fileID: 1360218911}
- {fileID: 271705868}
- {fileID: 1022368674}
@@ -8478,3 +8583,5 @@ SceneRoots:
- {fileID: 1716730849}
- {fileID: 2026185005}
- {fileID: 65142585}
+ - {fileID: 1114521749}
+ - {fileID: 1007367441}
diff --git a/Assets/Scripts/BootsEffect.cs b/Assets/Scripts/BootsEffect.cs
new file mode 100644
index 0000000..f90b13e
--- /dev/null
+++ b/Assets/Scripts/BootsEffect.cs
@@ -0,0 +1,46 @@
+using UnityEngine;
+
+///
+/// Attach to the Player. Watches the inventory for the Bunny Hop Boots being
+/// equipped/unequipped and adjusts max stamina accordingly.
+///
+public class BootsEffect : MonoBehaviour
+{
+ [Tooltip("Must match the ItemDefinition itemName exactly.")]
+ public string bootsItemName = "Bunny Hop Boots";
+ public float boostedMaxStamina = 200f;
+
+ private Inventory _inventory;
+ private Player _player;
+ private bool _boosted = false;
+
+ void Start()
+ {
+ _inventory = GetComponent();
+ _player = GetComponent();
+ }
+
+ void Update()
+ {
+ if (_inventory == null || _player == null) return;
+
+ // Find the boots entry in inventory
+ var entry = _inventory.items.Find(e => e.DisplayName == bootsItemName);
+ bool shouldBoost = entry != null && entry.isEquipped;
+
+ if (shouldBoost && !_boosted)
+ {
+ _player.maxStamina = boostedMaxStamina;
+ _player.stamina = Mathf.Min(_player.stamina + (boostedMaxStamina - 100f), boostedMaxStamina);
+ _boosted = true;
+ Debug.Log("[BootsEffect] Boots equipped — stamina boosted to " + boostedMaxStamina);
+ }
+ else if (!shouldBoost && _boosted)
+ {
+ _player.maxStamina = 100f;
+ _player.stamina = Mathf.Min(_player.stamina, 100f);
+ _boosted = false;
+ Debug.Log("[BootsEffect] Boots unequipped — stamina restored to 100");
+ }
+ }
+}
diff --git a/Assets/Scripts/BootsEffect.cs.meta b/Assets/Scripts/BootsEffect.cs.meta
new file mode 100644
index 0000000..c0df0e9
--- /dev/null
+++ b/Assets/Scripts/BootsEffect.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 5680222822977cd4a988c93d7b8881bc
\ No newline at end of file
diff --git a/Assets/Scripts/Editor.meta b/Assets/Scripts/Editor.meta
new file mode 100644
index 0000000..3f89efa
--- /dev/null
+++ b/Assets/Scripts/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 384e9957bd5199549870be11d0838d80
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/Editor/BhopBootsSetup.cs b/Assets/Scripts/Editor/BhopBootsSetup.cs
new file mode 100644
index 0000000..9b2f89a
--- /dev/null
+++ b/Assets/Scripts/Editor/BhopBootsSetup.cs
@@ -0,0 +1,100 @@
+using UnityEngine;
+using UnityEditor;
+using System.IO;
+
+///
+/// OGG → Setup → Create Bunny Hop Boots
+/// Creates the ItemDefinition and a world pickup for the bhop boots.
+///
+public static class BhopBootsSetup
+{
+ private const string ITEM_DEF_PATH = "Assets/Items/BhopBoots_Item.asset";
+ private const string PICKUP_PREFAB = "Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab";
+
+ [MenuItem("OGG/Setup/Create Bunny Hop Boots")]
+ public static void Create()
+ {
+ EnsureFolder("Assets/Items");
+ EnsureFolder("Assets/Prefabs/Pickups");
+
+ // ── ItemDefinition ────────────────────────────────────────────
+ ItemDefinition item = AssetDatabase.LoadAssetAtPath(ITEM_DEF_PATH);
+ bool isNew = item == null;
+ if (isNew) item = ScriptableObject.CreateInstance();
+
+ item.itemName = "Bunny Hop Boots";
+ item.type = ItemDefinition.ItemType.Misc;
+ item.isEquippable = true;
+ item.description = "Illegal footwear. Gives 200 stamina while worn. Do not wear near cliffs.";
+
+ if (isNew)
+ AssetDatabase.CreateAsset(item, ITEM_DEF_PATH);
+ else
+ EditorUtility.SetDirty(item);
+
+ AssetDatabase.SaveAssets();
+
+ // ── Pickup prefab — procedural boot shape ─────────────────────
+ GameObject root = new GameObject("BhopBoots_Pickup");
+
+ // Simple visual: a squashed cube (boot-ish)
+ GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cube);
+ body.name = "Visual";
+ body.transform.SetParent(root.transform, false);
+ body.transform.localPosition = new Vector3(0f, 0f, 0f);
+ body.transform.localScale = new Vector3(0.4f, 0.2f, 0.7f);
+ Object.DestroyImmediate(body.GetComponent());
+
+ // Bright accent colour so it stands out
+ var rend = body.GetComponent();
+ if (rend != null)
+ {
+ rend.material = new Material(Shader.Find("Universal Render Pipeline/Lit"));
+ rend.material.color = new Color(0.1f, 0.9f, 0.4f);
+ }
+
+ PickupItem pickup = root.AddComponent();
+ pickup.definition = item;
+ pickup.spinSpeed = 140f;
+ pickup.bobHeight = 0.2f;
+ pickup.bobSpeed = 2.8f;
+ pickup.pickupRadius = 2.2f;
+ pickup.pickupKey = KeyCode.E;
+
+ StaminaBoostPickup boost = root.AddComponent();
+ boost.newMaxStamina = 200f;
+
+ if (File.Exists(Application.dataPath + "/../" + PICKUP_PREFAB))
+ AssetDatabase.DeleteAsset(PICKUP_PREFAB);
+
+ GameObject saved = PrefabUtility.SaveAsPrefabAsset(root, PICKUP_PREFAB);
+ Object.DestroyImmediate(root);
+
+ AssetDatabase.Refresh();
+
+ EditorUtility.DisplayDialog(
+ "Bunny Hop Boots ✓",
+ $"Created:\n• {ITEM_DEF_PATH}\n• {PICKUP_PREFAB}\n\n" +
+ "Drag BhopBoots_Pickup into the scene.\n" +
+ "Pick it up with [E] to unlock bunny hopping.\n\n" +
+ "Hold SPACE while landing to chain hops and build speed.\n" +
+ "Strafe left/right mid-air to steer.",
+ "Let's go");
+
+ EditorGUIUtility.PingObject(saved);
+ Selection.activeObject = saved;
+ }
+
+ static void EnsureFolder(string path)
+ {
+ string[] parts = path.Split('/');
+ string current = parts[0];
+ for (int i = 1; i < parts.Length; i++)
+ {
+ string next = current + "/" + parts[i];
+ if (!AssetDatabase.IsValidFolder(next))
+ AssetDatabase.CreateFolder(current, parts[i]);
+ current = next;
+ }
+ }
+}
diff --git a/Assets/Scripts/Editor/BhopBootsSetup.cs.meta b/Assets/Scripts/Editor/BhopBootsSetup.cs.meta
new file mode 100644
index 0000000..bd5124d
--- /dev/null
+++ b/Assets/Scripts/Editor/BhopBootsSetup.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 92ff81f98e809934bab9c5b8f160276a
\ No newline at end of file
diff --git a/Assets/Scripts/Editor/GunSplatSetup.cs b/Assets/Scripts/Editor/GunSplatSetup.cs
new file mode 100644
index 0000000..9e0eb9c
--- /dev/null
+++ b/Assets/Scripts/Editor/GunSplatSetup.cs
@@ -0,0 +1,151 @@
+using UnityEngine;
+using UnityEditor;
+using System.IO;
+
+///
+/// OGG → Setup → Create Gun Splat Weapon
+/// Builds:
+/// • Assets/Prefabs/Weapons/GunSplat.prefab — the held weapon (SimpleGun)
+/// • Assets/Prefabs/Pickups/GunSplat_Pickup.prefab — world pickup (spins/bobs, press E)
+/// • Assets/Items/GunSplat_Item.asset — ItemDefinition (type = Weapon)
+///
+public static class GunSplatSetup
+{
+ private const string GLB_PATH = "Assets/Models/LidarScans/Gun Splat.glb";
+ private const string WEAPON_PREFAB = "Assets/Prefabs/Weapons/GunSplat.prefab";
+ private const string PICKUP_PREFAB = "Assets/Prefabs/Pickups/GunSplat_Pickup.prefab";
+ private const string ITEM_DEF_PATH = "Assets/Items/GunSplat_Item.asset";
+
+ [MenuItem("OGG/Setup/Create Gun Splat Weapon")]
+ public static void CreateGunSplat()
+ {
+ // ── 1. Ensure output folders exist ──────────────────────────
+ EnsureFolder("Assets/Prefabs/Weapons");
+ EnsureFolder("Assets/Prefabs/Pickups");
+ EnsureFolder("Assets/Items");
+
+ // ── 2. Load the GLB model ────────────────────────────────────
+ GameObject modelAsset = AssetDatabase.LoadAssetAtPath(GLB_PATH);
+ if (modelAsset == null)
+ {
+ Debug.LogError($"[GunSplatSetup] Could not find GLB at: {GLB_PATH}");
+ EditorUtility.DisplayDialog("Gun Splat Setup", $"Could not find model at:\n{GLB_PATH}", "OK");
+ return;
+ }
+
+ // ── 3. Build the held-weapon prefab ──────────────────────────
+ GameObject weaponRoot = new GameObject("GunSplat");
+ AttachVisual(weaponRoot, modelAsset);
+
+ SimpleGun gun = weaponRoot.AddComponent();
+ gun.damage = 30f;
+ gun.range = 120f;
+ gun.fireRate = 6f;
+ gun.maxAmmo = 24;
+ gun.isAutomatic = false;
+
+ if (File.Exists(DataRelative(WEAPON_PREFAB)))
+ AssetDatabase.DeleteAsset(WEAPON_PREFAB);
+
+ GameObject savedWeapon = PrefabUtility.SaveAsPrefabAsset(weaponRoot, WEAPON_PREFAB);
+ Object.DestroyImmediate(weaponRoot);
+
+ if (savedWeapon == null)
+ {
+ Debug.LogError("[GunSplatSetup] Failed to save weapon prefab.");
+ return;
+ }
+ Debug.Log($"[GunSplatSetup] Weapon prefab → {WEAPON_PREFAB}");
+
+ // ── 4. Create / update ItemDefinition ────────────────────────
+ ItemDefinition item = AssetDatabase.LoadAssetAtPath(ITEM_DEF_PATH);
+ bool isNew = item == null;
+ if (isNew) item = ScriptableObject.CreateInstance();
+
+ item.itemName = "Gun Splat";
+ item.type = ItemDefinition.ItemType.Weapon;
+ item.weaponPrefab = savedWeapon;
+ item.description = "A janky lidar-scanned sidearm. Shoots first, looks weird always.";
+
+ if (isNew)
+ AssetDatabase.CreateAsset(item, ITEM_DEF_PATH);
+ else
+ EditorUtility.SetDirty(item);
+
+ AssetDatabase.SaveAssets();
+ Debug.Log($"[GunSplatSetup] ItemDefinition → {ITEM_DEF_PATH}");
+
+ // ── 5. Build the world pickup prefab ─────────────────────────
+ GameObject pickupRoot = new GameObject("GunSplat_Pickup");
+
+ // Visual child — the GLB model
+ AttachVisual(pickupRoot, modelAsset);
+
+ // PickupItem component
+ PickupItem pickup = pickupRoot.AddComponent();
+ pickup.definition = item;
+ pickup.spinSpeed = 90f;
+ pickup.bobHeight = 0.15f;
+ pickup.bobSpeed = 2.0f;
+ pickup.pickupRadius = 2.2f;
+ pickup.pickupKey = KeyCode.E;
+
+ if (File.Exists(DataRelative(PICKUP_PREFAB)))
+ AssetDatabase.DeleteAsset(PICKUP_PREFAB);
+
+ GameObject savedPickup = PrefabUtility.SaveAsPrefabAsset(pickupRoot, PICKUP_PREFAB);
+ Object.DestroyImmediate(pickupRoot);
+
+ if (savedPickup == null)
+ {
+ Debug.LogError("[GunSplatSetup] Failed to save pickup prefab.");
+ return;
+ }
+ Debug.Log($"[GunSplatSetup] Pickup prefab → {PICKUP_PREFAB}");
+
+ AssetDatabase.Refresh();
+
+ // ── 6. Done ──────────────────────────────────────────────────
+ EditorUtility.DisplayDialog(
+ "Gun Splat Setup ✓",
+ "Created:\n" +
+ $"• {WEAPON_PREFAB}\n" +
+ $"• {PICKUP_PREFAB}\n" +
+ $"• {ITEM_DEF_PATH}\n\n" +
+ "Drag GunSplat_Pickup into the scene wherever you want it to spawn.\n" +
+ "It will spin, bob, and show an [E] prompt when the player gets close.",
+ "Sick");
+
+ EditorGUIUtility.PingObject(savedPickup);
+ Selection.activeObject = savedPickup;
+ }
+
+ // ─── Helpers ─────────────────────────────────────────────────────
+
+ static void AttachVisual(GameObject parent, GameObject modelAsset)
+ {
+ GameObject visual = (GameObject)PrefabUtility.InstantiatePrefab(modelAsset);
+ if (visual == null) visual = Object.Instantiate(modelAsset);
+ visual.name = "Visual";
+ visual.transform.SetParent(parent.transform, false);
+ visual.transform.localPosition = Vector3.zero;
+ visual.transform.localRotation = Quaternion.identity;
+ visual.transform.localScale = Vector3.one;
+ }
+
+ static string DataRelative(string assetPath) =>
+ Application.dataPath + "/../" + assetPath;
+
+ static void EnsureFolder(string path)
+ {
+ string[] parts = path.Split('/');
+ string current = parts[0];
+ for (int i = 1; i < parts.Length; i++)
+ {
+ string next = current + "/" + parts[i];
+ if (!AssetDatabase.IsValidFolder(next))
+ AssetDatabase.CreateFolder(current, parts[i]);
+ current = next;
+ }
+ }
+}
diff --git a/Assets/Scripts/Editor/GunSplatSetup.cs.meta b/Assets/Scripts/Editor/GunSplatSetup.cs.meta
new file mode 100644
index 0000000..5c0aac5
--- /dev/null
+++ b/Assets/Scripts/Editor/GunSplatSetup.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: a0ceea0a64e3fa344b83004be51fe457
\ No newline at end of file
diff --git a/Assets/Scripts/Editor/WeaponManagerEditor.cs b/Assets/Scripts/Editor/WeaponManagerEditor.cs
new file mode 100644
index 0000000..09bc96f
--- /dev/null
+++ b/Assets/Scripts/Editor/WeaponManagerEditor.cs
@@ -0,0 +1,92 @@
+using UnityEngine;
+using UnityEditor;
+
+[CustomEditor(typeof(WeaponManager))]
+public class WeaponManagerEditor : Editor
+{
+ public override void OnInspectorGUI()
+ {
+ DrawDefaultInspector();
+
+ WeaponManager wm = (WeaponManager)target;
+
+ if (!Application.isPlaying) return;
+
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField("── Live Positioning ──", EditorStyles.boldLabel);
+
+ // Show what's active
+ string activeName = wm.ActiveWeaponName;
+ if (string.IsNullOrEmpty(activeName))
+ {
+ EditorGUILayout.HelpBox("No weapon currently equipped.", MessageType.Info);
+ return;
+ }
+
+ EditorGUILayout.HelpBox($"Active: {activeName}", MessageType.None);
+
+ // Select the live GO so you can drag gizmos in Scene view
+ if (GUILayout.Button("🎯 Select Active Weapon in Scene"))
+ {
+ var slot = wm.slots.Find(s => s.itemName == activeName);
+ if (slot != null)
+ {
+ Selection.activeGameObject = slot.instance;
+ SceneView.lastActiveSceneView?.Focus();
+ }
+ }
+
+ EditorGUILayout.Space();
+
+ // Sync the current transform back into the WeaponViewmodel (or fallback global offset)
+ if (GUILayout.Button("⬆ Sync Transform → Offset Fields"))
+ {
+ var slot = wm.slots.Find(s => s.itemName == activeName);
+ if (slot != null)
+ {
+ var vm = slot.instance.GetComponent();
+ if (vm != null)
+ {
+ Undo.RecordObject(vm, "Sync Weapon Viewmodel");
+ vm.SyncFromTransform();
+ EditorUtility.SetDirty(vm);
+
+ // Also save back to the source prefab asset
+ var prefabAsset = PrefabUtility.GetCorrespondingObjectFromSource(slot.instance);
+ if (prefabAsset != null)
+ {
+ var prefabVm = prefabAsset.GetComponent();
+ if (prefabVm != null)
+ {
+ Undo.RecordObject(prefabVm, "Sync Weapon Viewmodel Prefab");
+ prefabVm.positionOffset = vm.positionOffset;
+ prefabVm.rotationOffset = vm.rotationOffset;
+ prefabVm.scale = vm.scale;
+ EditorUtility.SetDirty(prefabVm);
+ AssetDatabase.SaveAssets();
+ }
+ }
+ Debug.Log($"[WeaponManager] Synced to WeaponViewmodel: pos={vm.positionOffset} rot={vm.rotationOffset} scale={vm.scale}");
+ }
+ else
+ {
+ // Fallback: no viewmodel, write to global offsets
+ Undo.RecordObject(wm, "Sync Weapon Offset");
+ wm.weaponPositionOffset = slot.instance.transform.localPosition;
+ wm.weaponRotationOffset = slot.instance.transform.localRotation.eulerAngles;
+ wm.weaponScale = slot.instance.transform.localScale;
+ EditorUtility.SetDirty(wm);
+ Debug.Log($"[WeaponManager] Synced to global offset: pos={wm.weaponPositionOffset}");
+ }
+ }
+ }
+
+ EditorGUILayout.HelpBox(
+ "1. Hit Play & equip the gun\n" +
+ "2. Click 'Select Active Weapon'\n" +
+ "3. Use Move/Rotate gizmos in Scene view\n" +
+ "4. Click 'Sync Transform → Offset Fields'\n" +
+ "5. Stop Play — values are saved",
+ MessageType.Info);
+ }
+}
diff --git a/Assets/Scripts/Editor/WeaponManagerEditor.cs.meta b/Assets/Scripts/Editor/WeaponManagerEditor.cs.meta
new file mode 100644
index 0000000..cd3d70d
--- /dev/null
+++ b/Assets/Scripts/Editor/WeaponManagerEditor.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: fe67de8a32d4bf5449ee8def34a1bb8a
\ No newline at end of file
diff --git a/Assets/Scripts/FirstPersonController.cs b/Assets/Scripts/FirstPersonController.cs
index d6a0f32..c71b5d4 100644
--- a/Assets/Scripts/FirstPersonController.cs
+++ b/Assets/Scripts/FirstPersonController.cs
@@ -4,33 +4,31 @@ using UnityEngine;
public class FirstPersonController : MonoBehaviour
{
[Header("Movement Settings")]
- public float walkSpeed = 50f; // Boosted from 8f to overcome collision issues
- public float runSpeed = 80f; // Boosted from 14f
+ public float walkSpeed = 50f;
+ public float runSpeed = 80f;
public float jumpHeight = 2.5f;
- public float gravity = -20f;
+ public float gravity = -20f;
[Header("Mouse Look Settings")]
public float mouseSensitivity = 3f;
- public float maxLookAngle = 90f;
+ public float maxLookAngle = 90f;
[Header("References")]
public Camera playerCamera;
// Private variables
private CharacterController controller;
- private Vector3 velocity;
- private bool isGrounded;
- private float xRotation = 0f;
+ private Inventory inventory;
+ private Player player;
+ private Vector3 velocity;
+ private bool isGrounded;
+ private float xRotation = 0f;
void Start()
{
- Debug.Log("Starting game");
-
- // FORCE NORMAL TIME (in case something external changed it)
Time.timeScale = 1f;
controller = GetComponent();
-
if (controller == null)
{
Debug.LogError("FirstPersonController: No CharacterController found!");
@@ -40,85 +38,58 @@ public class FirstPersonController : MonoBehaviour
if (playerCamera == null)
playerCamera = GetComponentInChildren();
+ inventory = GetComponent();
+ player = GetComponent();
+
Cursor.lockState = CursorLockMode.Locked;
- Cursor.visible = false;
+ Cursor.visible = false;
}
void Update()
{
- if( controller == null )
- return;
+ if (controller == null) return;
isGrounded = controller.isGrounded;
if (isGrounded && velocity.y < 0)
velocity.y = -2f;
- // ─── Movement via direct KeyCode ───
- float moveX = 0f;
- float moveZ = 0f;
-
- if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) moveZ += 1f;
- if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) moveZ -= 1f;
- if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) moveX -= 1f;
- if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) moveX += 1f;
-
- // DEBUG: Log once when W is first pressed
- if (Input.GetKeyDown(KeyCode.W))
- {
- Debug.Log($"[FPC DEBUG] W pressed | controller.enabled={controller.enabled} | position={transform.position} | isGrounded={isGrounded}");
- Debug.Log($"[FPC DEBUG] Time.timeScale={Time.timeScale} | Time.deltaTime={Time.deltaTime} | walkSpeed={walkSpeed}");
- }
+ float moveX = 0f, moveZ = 0f;
+ if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) moveZ += 1f;
+ if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) moveZ -= 1f;
+ if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) moveX -= 1f;
+ if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) moveX += 1f;
Vector3 move = transform.right * moveX + transform.forward * moveZ;
+ if (move.magnitude > 1f) move.Normalize();
- if (move.magnitude > 1f)
- move.Normalize();
+ bool wantSprint = Input.GetKey(KeyCode.LeftShift) && move.magnitude > 0f;
+ bool isSprinting = wantSprint && (player == null || player.CanSprint());
+ if (player != null) player.isSprinting = isSprinting;
- float currentSpeed = Input.GetKey(KeyCode.LeftShift) ? runSpeed : walkSpeed;
-
- Vector3 posBefore = transform.position;
+ float currentSpeed = isSprinting ? runSpeed : walkSpeed;
controller.Move(move * currentSpeed * Time.deltaTime);
- // DEBUG: Log if we tried to move but didn't
- if (move.magnitude > 0f && Input.GetKeyDown(KeyCode.W))
- {
- Vector3 posAfter = transform.position;
- //Debug.Log($"[FPC DEBUG] Move attempt: delta={move * currentSpeed * Time.deltaTime} | actualDelta={(posAfter - posBefore)} | controller.height={controller.height} | controller.radius={controller.radius}");
-
- // Check what we're colliding with
- Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, controller.radius + 0.5f);
- //Debug.Log($"[FPC DEBUG] Found {nearbyColliders.Length} colliders near player");
- foreach (Collider col in nearbyColliders)
- {
- if (col != controller && !(col is CharacterController))
- Debug.LogWarning($"[FPC DEBUG] Nearby collider: {col.gameObject.name} on layer {LayerMask.LayerToName(col.gameObject.layer)}");
- }
- }
-
- // Jumping
if (Input.GetKey(KeyCode.Space) && isGrounded)
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
- // Gravity
velocity.y += gravity * Time.deltaTime;
- controller.Move(velocity * Time.deltaTime);
+ controller.Move(new Vector3(0f, velocity.y, 0f) * Time.deltaTime);
- // Mouse look
- HandleMouseLook();
+ bool inventoryOpen = inventory != null && inventory.IsOpen;
+ if (!inventoryOpen)
+ HandleMouseLook();
- // Escape to unlock cursor
if (Input.GetKeyDown(KeyCode.Escape))
{
Cursor.lockState = CursorLockMode.None;
- Cursor.visible = true;
+ Cursor.visible = true;
}
- // Click to re-lock cursor
- if (Input.GetMouseButtonDown(0) && Cursor.lockState == CursorLockMode.None)
+ if (Input.GetMouseButtonDown(0) && Cursor.lockState == CursorLockMode.None && !inventoryOpen)
{
Cursor.lockState = CursorLockMode.Locked;
- Cursor.visible = false;
+ Cursor.visible = false;
}
}
@@ -128,9 +99,8 @@ public class FirstPersonController : MonoBehaviour
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;
xRotation -= mouseY;
- xRotation = Mathf.Clamp(xRotation, -maxLookAngle, maxLookAngle);
+ xRotation = Mathf.Clamp(xRotation, -maxLookAngle, maxLookAngle);
playerCamera.transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
-
transform.Rotate(Vector3.up * mouseX);
}
}
diff --git a/Assets/Scripts/Game/Input/InputData.cs b/Assets/Scripts/Game/Input/InputData.cs
index 5492156..99d727b 100644
--- a/Assets/Scripts/Game/Input/InputData.cs
+++ b/Assets/Scripts/Game/Input/InputData.cs
@@ -45,7 +45,7 @@ public static class InputData
new MouseButton( Defines.Input.kLeftMouseButton),
new KeyboardButton( KeyCode.LeftControl ),
} ) );
- reload = AddButton( new KeyboardButton( KeyCode.Space ) );
+ reload = AddButton( new KeyboardButton( KeyCode.R ) );
jump = AddButton( new KeyboardButton( KeyCode.Space ) );
horizontalMovement = AddAxis( new MultiAxis(
diff --git a/Assets/Scripts/HumanoidEnemy.cs b/Assets/Scripts/HumanoidEnemy.cs
index 759c383..b3db006 100644
--- a/Assets/Scripts/HumanoidEnemy.cs
+++ b/Assets/Scripts/HumanoidEnemy.cs
@@ -187,8 +187,13 @@ public class HumanoidEnemy : MonoBehaviour
// Punch animation
StartCoroutine(PunchAnimation());
- // TODO: Hook into player health system when you have one
- // player.GetComponent()?.TakeDamage(attackDamage);
+ // Damage the player via Player.health
+ Player playerHealth = player.GetComponent();
+ if (playerHealth != null)
+ {
+ playerHealth.health -= attackDamage;
+ Debug.Log($"[Enemy] Player health: {playerHealth.health}");
+ }
}
}
diff --git a/Assets/Scripts/Inventory.cs b/Assets/Scripts/Inventory.cs
new file mode 100644
index 0000000..dafbef9
--- /dev/null
+++ b/Assets/Scripts/Inventory.cs
@@ -0,0 +1,632 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+///
+/// Inventory system with a toggleable HUD (press I) and right-click context menu.
+/// Attach to the Player GameObject alongside WeaponManager.
+///
+public class Inventory : MonoBehaviour
+{
+ // ─── Item data ────────────────────────────────────────────────────
+ [System.Serializable]
+ public class InventoryEntry
+ {
+ public ItemDefinition definition;
+ public string itemName;
+ public Sprite icon;
+ public int count;
+ public bool isEquipped;
+
+ public InventoryEntry(string name, Sprite icon)
+ {
+ this.itemName = name;
+ this.icon = icon;
+ this.count = 1;
+ this.isEquipped = false;
+ }
+
+ public InventoryEntry(ItemDefinition def)
+ {
+ this.definition = def;
+ this.itemName = def.itemName;
+ this.icon = def.icon;
+ this.count = 1;
+ this.isEquipped = false;
+ }
+
+ public bool _isWeaponSlot;
+
+ public bool IsWeapon =>
+ (definition != null && definition.type == ItemDefinition.ItemType.Weapon)
+ || _isWeaponSlot;
+
+ // True for weapons AND equippable misc items
+ public bool IsEquippable =>
+ IsWeapon || (definition != null && definition.isEquippable);
+
+ public string DisplayName => definition != null ? definition.itemName : itemName;
+ }
+
+ public List items = new List();
+
+ // ─── HUD config ───────────────────────────────────────────────────
+ [Header("HUD")]
+ public KeyCode toggleKey = KeyCode.I;
+ public KeyCode equipKey = KeyCode.F;
+ public int columns = 4;
+ public int maxSlots = 20;
+
+ [Header("HUD Colours")]
+ public Color colBackground = new Color(0.05f, 0.05f, 0.05f, 0.92f);
+ public Color colPanel = new Color(0.10f, 0.10f, 0.10f, 0.95f);
+ public Color colSlotEmpty = new Color(0.18f, 0.18f, 0.18f, 1.00f);
+ public Color colSlotFilled = new Color(0.22f, 0.28f, 0.22f, 1.00f);
+ public Color colSlotHover = new Color(0.35f, 0.45f, 0.35f, 1.00f);
+ public Color colSlotSelected = new Color(0.45f, 0.75f, 0.45f, 1.00f);
+ public Color colSlotEquipped = new Color(0.60f, 0.40f, 0.10f, 1.00f);
+ public Color colBorder = new Color(0.40f, 0.65f, 0.40f, 1.00f);
+ public Color colText = new Color(0.85f, 0.95f, 0.85f, 1.00f);
+ public Color colDim = new Color(0.50f, 0.60f, 0.50f, 1.00f);
+ public Color colAccent = new Color(0.50f, 0.90f, 0.50f, 1.00f);
+ public Color colWeaponAccent = new Color(0.95f, 0.75f, 0.20f, 1.00f);
+
+ [Header("Context Menu Colours")]
+ public Color colCtxBg = new Color(0.08f, 0.10f, 0.08f, 0.98f);
+ public Color colCtxBorder = new Color(0.40f, 0.65f, 0.40f, 1.00f);
+ public Color colCtxHover = new Color(0.20f, 0.35f, 0.20f, 1.00f);
+ public Color colCtxText = new Color(0.85f, 0.95f, 0.85f, 1.00f);
+ public Color colCtxDestructive = new Color(0.85f, 0.25f, 0.20f, 1.00f);
+
+ // ─── Private state ────────────────────────────────────────────────
+ private bool _open = false;
+ private int _selectedIndex = 0;
+ private int _hoveredIndex = -1;
+ private WeaponManager _weaponManager;
+
+ // ─── Context menu state ───────────────────────────────────────────
+ private bool _ctxOpen = false;
+ private int _ctxItemIndex = -1;
+ private Vector2 _ctxPos;
+
+ private const float kCtxItemH = 28f;
+ private const float kCtxWidth = 140f;
+ private const float kCtxPadX = 10f;
+
+ // Context menu action labels — built per item
+ private struct CtxAction
+ {
+ public string label;
+ public bool isDestructive;
+ public System.Action callback;
+ }
+ private List _ctxActions = new List();
+
+ // ─── Layout constants ─────────────────────────────────────────────
+ private const float kSlotSize = 80f;
+ private const float kSlotPad = 8f;
+ private const float kPanelPad = 20f;
+ private const float kHeaderH = 40f;
+ private const float kDetailPanelH = 120f;
+
+ // ─────────────────────────────────────────────────────────────────
+ void Start()
+ {
+ _weaponManager = GetComponent();
+ if (_weaponManager != null)
+ Invoke(nameof(SyncStartingWeapons), 0.1f);
+ }
+
+ void SyncStartingWeapons()
+ {
+ if (_weaponManager == null) return;
+ foreach (var slot in _weaponManager.slots)
+ {
+ if (slot.definition != null)
+ AddItem(slot.definition, autoEquip: false);
+ else
+ AddItemRaw(slot.itemName, null, isWeaponSlot: true);
+ }
+ SyncEquippedState();
+ }
+
+ // ─── Public API ───────────────────────────────────────────────────
+ public void AddItem(ItemDefinition def, bool autoEquip = false)
+ {
+ if (def == null) return;
+ InventoryEntry existing = items.Find(e => e.definition == def);
+ if (existing != null)
+ {
+ if (def.type != ItemDefinition.ItemType.Weapon) existing.count++;
+ Debug.Log($"[Inventory] Already have: {def.itemName}");
+ }
+ else
+ {
+ var entry = new InventoryEntry(def);
+ items.Add(entry);
+ Debug.Log($"[Inventory] Picked up: {def.itemName}");
+ if (def.type == ItemDefinition.ItemType.Weapon)
+ {
+ _weaponManager?.AddWeapon(def);
+ if (autoEquip) EquipItem(items.Count - 1);
+ }
+ }
+ }
+
+ public void AddItem(string itemName, Sprite icon = null) => AddItemRaw(itemName, icon, false);
+
+ private void AddItemRaw(string itemName, Sprite icon, bool isWeaponSlot)
+ {
+ InventoryEntry existing = items.Find(e => e.itemName == itemName && e.definition == null);
+ if (existing != null) { existing.count++; return; }
+ var entry = new InventoryEntry(itemName, icon);
+ entry._isWeaponSlot = isWeaponSlot;
+ items.Add(entry);
+ }
+
+ public bool RemoveItem(string itemName, int amount = 1)
+ {
+ InventoryEntry entry = items.Find(e => e.DisplayName == itemName);
+ if (entry == null) return false;
+ entry.count -= amount;
+ if (entry.count <= 0) items.Remove(entry);
+ return true;
+ }
+
+ public bool HasItem(string itemName) => items.Exists(e => e.DisplayName == itemName);
+ public int CountOf(string itemName)
+ {
+ var e = items.Find(x => x.DisplayName == itemName);
+ return e != null ? e.count : 0;
+ }
+
+ public bool IsOpen => _open;
+
+ // ─── Equip / Unequip ──────────────────────────────────────────────
+ void EquipItem(int index)
+ {
+ if (index < 0 || index >= items.Count) return;
+ InventoryEntry entry = items[index];
+ if (!entry.IsEquippable) return;
+
+ if (entry.IsWeapon)
+ {
+ // Unequip other weapons
+ foreach (var e in items) { if (e.IsWeapon) e.isEquipped = false; }
+ entry.isEquipped = true;
+ _weaponManager?.EquipByName(entry.DisplayName);
+ }
+ else
+ {
+ // Toggle equip for misc equippables (boots, etc.)
+ entry.isEquipped = true;
+ }
+ Debug.Log($"[Inventory] Equipped: {entry.DisplayName}");
+ }
+
+ void UnequipItem(int index)
+ {
+ if (index < 0 || index >= items.Count) return;
+ InventoryEntry entry = items[index];
+ if (!entry.IsEquippable || !entry.isEquipped) return;
+ entry.isEquipped = false;
+
+ if (entry.IsWeapon && _weaponManager != null)
+ {
+ var slot = _weaponManager.slots.Find(s => s.itemName == entry.DisplayName);
+ if (slot != null) slot.instance.SetActive(false);
+ _weaponManager.Unequip();
+ }
+ Debug.Log($"[Inventory] Unequipped: {entry.DisplayName}");
+ }
+
+ void DropItem(int index)
+ {
+ if (index < 0 || index >= items.Count) return;
+ InventoryEntry entry = items[index];
+ if (entry.isEquipped) UnequipItem(index);
+ items.RemoveAt(index);
+ if (_selectedIndex >= items.Count) _selectedIndex = items.Count - 1;
+ Debug.Log($"[Inventory] Dropped: {entry.DisplayName}");
+ // TODO: optionally spawn the pickup back in the world here
+ }
+
+ void SyncEquippedState()
+ {
+ if (_weaponManager == null) return;
+ string active = _weaponManager.ActiveWeaponName;
+ foreach (var e in items)
+ {
+ // Only sync weapon equipped state — leave misc equippables alone
+ if (e.IsWeapon)
+ e.isEquipped = (e.DisplayName == active);
+ }
+ }
+
+ // ─── Context menu builder ─────────────────────────────────────────
+ void OpenContextMenu(int itemIndex, Vector2 screenPos)
+ {
+ if (itemIndex < 0 || itemIndex >= items.Count) return;
+
+ _ctxItemIndex = itemIndex;
+ _ctxActions.Clear();
+
+ InventoryEntry entry = items[itemIndex];
+
+ if (entry.IsEquippable)
+ {
+ if (entry.isEquipped)
+ {
+ int captured = itemIndex;
+ _ctxActions.Add(new CtxAction
+ {
+ label = "Unequip",
+ isDestructive = false,
+ callback = () => UnequipItem(captured)
+ });
+ }
+ else
+ {
+ int captured = itemIndex;
+ _ctxActions.Add(new CtxAction
+ {
+ label = "Equip",
+ isDestructive = false,
+ callback = () => EquipItem(captured)
+ });
+ }
+ }
+
+ // Consumables / misc could have a "Use" action here in future
+ // For now everyone gets Inspect (just selects and focuses the detail panel)
+ {
+ int captured = itemIndex;
+ _ctxActions.Add(new CtxAction
+ {
+ label = "Inspect",
+ isDestructive = false,
+ callback = () => _selectedIndex = captured
+ });
+ }
+
+ {
+ int captured = itemIndex;
+ _ctxActions.Add(new CtxAction
+ {
+ label = "Drop",
+ isDestructive = true,
+ callback = () => DropItem(captured)
+ });
+ }
+
+ // Clamp so the menu doesn't go off screen
+ float menuH = _ctxActions.Count * kCtxItemH + 8f;
+ float clampedX = Mathf.Min(screenPos.x, Screen.width - kCtxWidth - 4f);
+ float clampedY = Mathf.Min(screenPos.y, Screen.height - menuH - 4f);
+ _ctxPos = new Vector2(clampedX, clampedY);
+ _ctxOpen = true;
+ }
+
+ void CloseContextMenu() { _ctxOpen = false; _ctxItemIndex = -1; }
+
+ // ─── Toggle ───────────────────────────────────────────────────────
+ void Update()
+ {
+ if (Input.GetKeyDown(toggleKey))
+ {
+ CloseContextMenu();
+ SetOpen(!_open);
+ }
+
+ if (_open && Input.GetKeyDown(equipKey))
+ {
+ if (_selectedIndex < items.Count && items[_selectedIndex].IsWeapon)
+ EquipItem(_selectedIndex);
+ CloseContextMenu();
+ }
+
+ if (!_open)
+ SyncEquippedState();
+ }
+
+ void SetOpen(bool open)
+ {
+ _open = open;
+ SyncEquippedState();
+ Cursor.lockState = open ? CursorLockMode.None : CursorLockMode.Locked;
+ Cursor.visible = open;
+ }
+
+ // ─── GUI ──────────────────────────────────────────────────────────
+ void OnGUI()
+ {
+ if (!_open) return;
+
+ int rows = Mathf.CeilToInt((float)maxSlots / columns);
+ float gridW = columns * (kSlotSize + kSlotPad) - kSlotPad;
+ float gridH = rows * (kSlotSize + kSlotPad) - kSlotPad;
+ float panelW = gridW + kPanelPad * 2f;
+ float panelH = kHeaderH + kPanelPad + gridH + kPanelPad + kDetailPanelH + kPanelPad;
+ float panelX = (Screen.width - panelW) * 0.5f;
+ float panelY = (Screen.height - panelH) * 0.5f;
+
+ Vector2 mouse = Event.current.mousePosition;
+
+ // ── Click outside context menu to close it ────────────────────
+ if (_ctxOpen && Event.current.type == EventType.MouseDown)
+ {
+ Rect ctxRect = GetCtxRect();
+ if (!ctxRect.Contains(mouse))
+ CloseContextMenu();
+ }
+
+ // ── Backdrop ──────────────────────────────────────────────────
+ DrawRect(new Rect(0, 0, Screen.width, Screen.height), new Color(0, 0, 0, 0.55f));
+
+ // ── Main panel ────────────────────────────────────────────────
+ DrawBorderedBox(new Rect(panelX, panelY, panelW, panelH), colPanel, colBorder, 2f);
+
+ GUIStyle headerStyle = MakeStyle(18, FontStyle.Bold, colAccent, TextAnchor.MiddleCenter);
+ GUI.Label(new Rect(panelX, panelY + 8f, panelW, 28f), "[ INVENTORY ]", headerStyle);
+ DrawRect(new Rect(panelX + 10f, panelY + kHeaderH - 2f, panelW - 20f, 1f), colBorder);
+
+ if (_weaponManager != null && _weaponManager.ActiveWeaponName != "")
+ {
+ GUIStyle activeStyle = MakeStyle(11, FontStyle.Normal, colWeaponAccent, TextAnchor.MiddleRight);
+ GUI.Label(new Rect(panelX, panelY + 10f, panelW - 10f, 20f),
+ $"EQUIPPED: {_weaponManager.ActiveWeaponName}", activeStyle);
+ }
+
+ float gx = panelX + kPanelPad;
+ float gy = panelY + kHeaderH + kPanelPad;
+
+ _hoveredIndex = -1;
+
+ // ── Slot grid ─────────────────────────────────────────────────
+ for (int i = 0; i < maxSlots; i++)
+ {
+ int col = i % columns;
+ int row = i / columns;
+ float sx = gx + col * (kSlotSize + kSlotPad);
+ float sy = gy + row * (kSlotSize + kSlotPad);
+ Rect slotRect = new Rect(sx, sy, kSlotSize, kSlotSize);
+
+ bool hasItem = i < items.Count;
+ bool isHover = slotRect.Contains(mouse) && !_ctxOpen;
+ bool isSel = (i == _selectedIndex);
+ bool isEquipped = hasItem && items[i].isEquipped;
+ bool isWeapon = hasItem && items[i].IsWeapon;
+
+ if (isHover) _hoveredIndex = i;
+
+ // Left-click: select
+ if (isHover && Event.current.type == EventType.MouseDown
+ && Event.current.button == 0 && hasItem)
+ {
+ _selectedIndex = i;
+ CloseContextMenu();
+ }
+
+ // Left double-click: equip weapon
+ if (isHover && Event.current.type == EventType.MouseDown
+ && Event.current.button == 0 && Event.current.clickCount == 2 && isWeapon)
+ EquipItem(i);
+
+ // Right-click: open context menu
+ if (isHover && Event.current.type == EventType.MouseDown
+ && Event.current.button == 1 && hasItem)
+ {
+ _selectedIndex = i;
+ OpenContextMenu(i, mouse);
+ }
+
+ // Slot colour
+ Color slotCol;
+ if (isEquipped) slotCol = colSlotEquipped;
+ else if (isSel) slotCol = colSlotSelected;
+ else if (isHover) slotCol = colSlotHover;
+ else if (hasItem) slotCol = colSlotFilled;
+ else slotCol = colSlotEmpty;
+
+ Color borderCol = isEquipped ? colWeaponAccent : (isSel ? colAccent : colBorder);
+ DrawBorderedBox(slotRect, slotCol, borderCol, (isSel || isEquipped) ? 2f : 1f);
+
+ if (!hasItem) continue;
+
+ InventoryEntry entry = items[i];
+
+ // Icon or letter placeholder
+ if (entry.icon != null)
+ {
+ float ip = 10f;
+ GUI.DrawTexture(new Rect(sx + ip, sy + ip, kSlotSize - ip * 2f, kSlotSize - 28f),
+ entry.icon.texture, ScaleMode.ScaleToFit, true);
+ }
+ else
+ {
+ Color lc = isEquipped ? colWeaponAccent : (isSel ? Color.white : colAccent);
+ GUI.Label(new Rect(sx, sy + 8f, kSlotSize, kSlotSize - 24f),
+ entry.DisplayName.Substring(0, 1).ToUpper(), MakeStyle(26, FontStyle.Bold, lc, TextAnchor.MiddleCenter));
+ }
+
+ // Weapon tag
+ if (isWeapon)
+ {
+ Color tc = isEquipped ? colWeaponAccent : new Color(0.6f, 0.5f, 0.2f, 1f);
+ GUI.Label(new Rect(sx + 3f, sy + 2f, 40f, 12f),
+ isEquipped ? "EQUIP" : "WPN", MakeStyle(8, FontStyle.Bold, tc, TextAnchor.UpperLeft));
+ }
+
+ // Right-click hint on hovered slot
+ if (isHover)
+ {
+ GUIStyle hint = MakeStyle(8, FontStyle.Normal, new Color(0.6f, 0.6f, 0.6f, 0.9f), TextAnchor.LowerRight);
+ GUI.Label(new Rect(sx + 2f, sy + 2f, kSlotSize - 4f, kSlotSize - 4f), "RMB ▾", hint);
+ }
+
+ // Item name
+ string dn = entry.DisplayName.Length > 10 ? entry.DisplayName.Substring(0, 9) + "…" : entry.DisplayName;
+ Color nc = isEquipped ? colWeaponAccent : (isSel ? Color.white : colText);
+ GUI.Label(new Rect(sx + 2f, sy + kSlotSize - 28f, kSlotSize - 4f, 16f),
+ dn, MakeStyle(9, FontStyle.Bold, nc, TextAnchor.LowerCenter));
+
+ // Count badge
+ if (entry.count > 1)
+ {
+ DrawRect(new Rect(sx + kSlotSize - 22f, sy + kSlotSize - 18f, 20f, 16f), new Color(0, 0, 0, 0.7f));
+ GUI.Label(new Rect(sx + kSlotSize - 22f, sy + kSlotSize - 18f, 19f, 16f),
+ $"x{entry.count}", MakeStyle(10, FontStyle.Bold, Color.white, TextAnchor.LowerRight));
+ }
+ }
+
+ // ── Detail panel ──────────────────────────────────────────────
+ float detailY = gy + gridH + kPanelPad;
+ Rect detailRect = new Rect(panelX + kPanelPad, detailY, panelW - kPanelPad * 2f, kDetailPanelH - kPanelPad);
+ DrawBorderedBox(detailRect, new Color(0.08f, 0.08f, 0.08f, 1f), colBorder, 1f);
+
+ if (_selectedIndex < items.Count)
+ {
+ InventoryEntry sel = items[_selectedIndex];
+ float dy = detailRect.y + 8f;
+ float dx = detailRect.x + 10f;
+ float dw = detailRect.width - 20f;
+
+ GUI.Label(new Rect(dx, dy, dw, 16f), "SELECTED", MakeStyle(10, FontStyle.Normal, colDim, TextAnchor.UpperLeft));
+ GUI.Label(new Rect(dx, dy + 16f, dw, 22f), sel.DisplayName, MakeStyle(14, FontStyle.Bold, colText, TextAnchor.UpperLeft));
+
+ string desc = (sel.definition != null && sel.definition.description != "")
+ ? sel.definition.description : (sel.IsWeapon ? "Weapon" : "Item");
+ GUI.Label(new Rect(dx, dy + 38f, dw * 0.65f, 30f), desc, MakeStyle(10, FontStyle.Normal, colDim, TextAnchor.UpperLeft));
+
+ if (sel.IsEquippable)
+ {
+ Color wc = sel.isEquipped ? colWeaponAccent : colDim;
+ string tag = sel.isEquipped ? "▶ EQUIPPED" : "right-click or [F] to equip";
+ GUI.Label(new Rect(dx, dy + 68f, dw, 18f), tag, MakeStyle(11, FontStyle.Bold, wc, TextAnchor.UpperLeft));
+ }
+ else if (sel.count > 1)
+ {
+ GUI.Label(new Rect(dx, dy + 68f, dw, 18f), $"Quantity: {sel.count}",
+ MakeStyle(11, FontStyle.Bold, colAccent, TextAnchor.UpperLeft));
+ }
+
+ // Equip / Unequip button bottom-right of detail panel
+ if (sel.IsEquippable)
+ {
+ Rect btnRect = new Rect(detailRect.x + detailRect.width - 110f,
+ detailRect.y + detailRect.height - 34f, 100f, 26f);
+ if (sel.isEquipped)
+ {
+ DrawBorderedBox(btnRect, new Color(0.4f, 0.15f, 0.05f, 1f), colWeaponAccent, 1f);
+ GUI.Label(btnRect, "[ F ] UNEQUIP", MakeStyle(11, FontStyle.Bold, Color.white, TextAnchor.MiddleCenter));
+ if (GUI.Button(btnRect, "", GUIStyle.none)) UnequipItem(_selectedIndex);
+ }
+ else
+ {
+ DrawBorderedBox(btnRect, colSlotEquipped, colWeaponAccent, 1f);
+ GUI.Label(btnRect, "[ F ] EQUIP", MakeStyle(11, FontStyle.Bold, Color.white, TextAnchor.MiddleCenter));
+ if (GUI.Button(btnRect, "", GUIStyle.none)) EquipItem(_selectedIndex);
+ }
+ }
+ }
+ else
+ {
+ GUI.Label(detailRect, "No item selected", MakeStyle(11, FontStyle.Normal, colDim, TextAnchor.MiddleCenter));
+ }
+
+ // Hints
+ GUIStyle hintStyle = MakeStyle(10, FontStyle.Normal, colDim, TextAnchor.LowerRight);
+ GUI.Label(new Rect(panelX, panelY + panelH - 18f, panelW - 8f, 16f), "[ I ] close | RMB slot for options", hintStyle);
+
+ // ── Context menu (drawn last so it's on top) ──────────────────
+ if (_ctxOpen)
+ DrawContextMenu(mouse);
+ }
+
+ // ─── Context menu draw ────────────────────────────────────────────
+ Rect GetCtxRect()
+ {
+ float menuH = _ctxActions.Count * kCtxItemH + 8f;
+ return new Rect(_ctxPos.x, _ctxPos.y, kCtxWidth, menuH);
+ }
+
+ void DrawContextMenu(Vector2 mouse)
+ {
+ float menuH = _ctxActions.Count * kCtxItemH + 8f;
+ Rect bgRect = new Rect(_ctxPos.x, _ctxPos.y, kCtxWidth, menuH);
+
+ // Shadow
+ DrawRect(new Rect(bgRect.x + 3f, bgRect.y + 3f, bgRect.width, bgRect.height),
+ new Color(0f, 0f, 0f, 0.45f));
+
+ // Background + border
+ DrawBorderedBox(bgRect, colCtxBg, colCtxBorder, 1.5f);
+
+ // Header strip — item name
+ if (_ctxItemIndex >= 0 && _ctxItemIndex < items.Count)
+ {
+ string title = items[_ctxItemIndex].DisplayName;
+ DrawRect(new Rect(bgRect.x + 1.5f, bgRect.y + 1.5f, bgRect.width - 3f, 20f),
+ new Color(0.15f, 0.22f, 0.15f, 1f));
+ GUI.Label(new Rect(bgRect.x + kCtxPadX, bgRect.y + 3f, kCtxWidth - kCtxPadX * 2f, 16f),
+ title.ToUpper(), MakeStyle(9, FontStyle.Bold, colWeaponAccent, TextAnchor.MiddleLeft));
+ }
+
+ // Action rows
+ for (int i = 0; i < _ctxActions.Count; i++)
+ {
+ float rowY = bgRect.y + 22f + i * kCtxItemH;
+ Rect rowRect = new Rect(bgRect.x + 1.5f, rowY, bgRect.width - 3f, kCtxItemH);
+ bool hover = rowRect.Contains(mouse);
+
+ if (hover)
+ DrawRect(rowRect, colCtxHover);
+
+ Color textColor = _ctxActions[i].isDestructive
+ ? (hover ? Color.white : colCtxDestructive)
+ : (hover ? Color.white : colCtxText);
+
+ GUI.Label(new Rect(bgRect.x + kCtxPadX, rowY + 2f, kCtxWidth - kCtxPadX * 2f, kCtxItemH - 4f),
+ _ctxActions[i].label, MakeStyle(12, FontStyle.Normal, textColor, TextAnchor.MiddleLeft));
+
+ // Separator line between items
+ if (i < _ctxActions.Count - 1)
+ DrawRect(new Rect(bgRect.x + 6f, rowY + kCtxItemH - 1f, bgRect.width - 12f, 1f),
+ new Color(1f, 1f, 1f, 0.06f));
+
+ // Click to fire action
+ if (hover && Event.current.type == EventType.MouseDown && Event.current.button == 0)
+ {
+ _ctxActions[i].callback?.Invoke();
+ CloseContextMenu();
+ Event.current.Use();
+ }
+ }
+ }
+
+ // ─── Style / Draw helpers ─────────────────────────────────────────
+ static GUIStyle MakeStyle(int size, FontStyle style, Color color, TextAnchor align)
+ {
+ var s = new GUIStyle();
+ s.fontSize = size;
+ s.fontStyle = style;
+ s.normal.textColor = color;
+ s.alignment = align;
+ return s;
+ }
+
+ static void DrawRect(Rect r, Color c)
+ {
+ Color prev = GUI.color;
+ GUI.color = c;
+ GUI.DrawTexture(r, Texture2D.whiteTexture);
+ GUI.color = prev;
+ }
+
+ static void DrawBorderedBox(Rect r, Color fill, Color border, float thickness)
+ {
+ DrawRect(r, border);
+ DrawRect(new Rect(r.x + thickness, r.y + thickness,
+ r.width - thickness * 2f,
+ r.height - thickness * 2f), fill);
+ }
+}
diff --git a/Assets/Scripts/Inventory.cs.meta b/Assets/Scripts/Inventory.cs.meta
new file mode 100644
index 0000000..57093dd
--- /dev/null
+++ b/Assets/Scripts/Inventory.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 514ba78e0d4cf8b4787471c2db0c6976
\ No newline at end of file
diff --git a/Assets/Scripts/ItemDefinition.cs b/Assets/Scripts/ItemDefinition.cs
new file mode 100644
index 0000000..4c44ebc
--- /dev/null
+++ b/Assets/Scripts/ItemDefinition.cs
@@ -0,0 +1,28 @@
+using UnityEngine;
+
+///
+/// Create via right-click → Create → OGG → Item Definition
+/// Drag onto PickupItem components in the scene.
+///
+[CreateAssetMenu(fileName = "New Item", menuName = "OGG/Item Definition")]
+public class ItemDefinition : ScriptableObject
+{
+ public enum ItemType { Misc, Consumable, Weapon }
+
+ [Header("Identity")]
+ public string itemName = "Unnamed Item";
+ public Sprite icon;
+ public ItemType type = ItemType.Misc;
+
+ [Header("Weapon (only if type == Weapon)")]
+ [Tooltip("The prefab that gets spawned in the weapon holder when this is equipped.")]
+ public GameObject weaponPrefab;
+
+ [Header("Equipment")]
+ [Tooltip("If true, this item can be equipped/unequipped from the inventory even if it isn't a weapon.")]
+ public bool isEquippable = false;
+
+ [Header("Flavour")]
+ [TextArea(2, 4)]
+ public string description = "";
+}
diff --git a/Assets/Scripts/ItemDefinition.cs.meta b/Assets/Scripts/ItemDefinition.cs.meta
new file mode 100644
index 0000000..d06fa61
--- /dev/null
+++ b/Assets/Scripts/ItemDefinition.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: b549159f64e1b164082e2e4ae4d28ac1
\ No newline at end of file
diff --git a/Assets/Scripts/PickupItem.cs b/Assets/Scripts/PickupItem.cs
new file mode 100644
index 0000000..a07305a
--- /dev/null
+++ b/Assets/Scripts/PickupItem.cs
@@ -0,0 +1,156 @@
+using UnityEngine;
+
+///
+/// Attach to any pickup object. Spins + bobs like MGS rations.
+/// Assign an ItemDefinition asset for full weapon/equip support,
+/// or just fill itemName for a simple non-weapon pickup.
+///
+public class PickupItem : MonoBehaviour
+{
+ [Header("Item")]
+ [Tooltip("Preferred: drag an ItemDefinition ScriptableObject here.")]
+ public ItemDefinition definition;
+
+ [Tooltip("Fallback name if no ItemDefinition is set.")]
+ public string itemName = "Item";
+ public Sprite itemIcon;
+
+ [Header("Spin & Bob")]
+ public float spinSpeed = 120f;
+ public float bobHeight = 0.18f;
+ public float bobSpeed = 2.2f;
+
+ [Header("Pickup")]
+ public float pickupRadius = 2.0f;
+ public KeyCode pickupKey = KeyCode.E;
+
+ [Header("FX")]
+ public GameObject pickupParticlesPrefab;
+ public AudioClip pickupSound;
+
+ // ─────────────────────────────────────────
+ private Vector3 _startPos;
+ private Transform _playerTransform;
+ private Inventory _inventory;
+ private AudioSource _audioSource;
+ private bool _collected = false;
+
+ // Prompt display
+ private bool _inRange = false;
+
+ void Start()
+ {
+ _startPos = transform.position;
+
+ Player player = FindObjectOfType();
+ if (player != null)
+ {
+ _playerTransform = player.transform;
+ _inventory = player.GetComponent();
+ }
+
+ _audioSource = gameObject.AddComponent();
+ _audioSource.spatialBlend = 1f;
+ _audioSource.playOnAwake = false;
+
+ // If we have a definition, use its name/icon
+ if (definition != null)
+ {
+ itemName = definition.itemName;
+ itemIcon = definition.icon;
+ }
+ }
+
+ void Update()
+ {
+ if (_collected) return;
+
+ // ── Spin ──────────────────────────────
+ transform.Rotate(Vector3.up, spinSpeed * Time.deltaTime, Space.World);
+
+ // ── Bob ───────────────────────────────
+ float newY = _startPos.y + Mathf.Sin(Time.time * bobSpeed * Mathf.PI * 2f) * bobHeight;
+ transform.position = new Vector3(transform.position.x, newY, transform.position.z);
+
+ // ── Pickup check ─────────────────────
+ if (_playerTransform == null) return;
+
+ float dist = Vector3.Distance(transform.position, _playerTransform.position);
+ _inRange = dist <= pickupRadius;
+
+ if (_inRange && Input.GetKeyDown(pickupKey))
+ Collect();
+ }
+
+ void OnGUI()
+ {
+ if (_collected || !_inRange) return;
+
+ // World-to-screen prompt
+ Vector3 screenPos = Camera.main != null
+ ? Camera.main.WorldToScreenPoint(_startPos + Vector3.up * (bobHeight + 0.4f))
+ : Vector3.zero;
+
+ if (screenPos.z < 0) return; // behind camera
+
+ float sx = screenPos.x;
+ float sy = Screen.height - screenPos.y; // flip y
+
+ string label = definition != null ? definition.itemName : itemName;
+ bool isWeapon = definition != null && definition.type == ItemDefinition.ItemType.Weapon;
+ string prompt = $"[{pickupKey}] Pick up {label}";
+
+ GUIStyle bg = new GUIStyle(GUI.skin.box);
+ bg.fontSize = 12;
+ bg.fontStyle = FontStyle.Bold;
+ bg.normal.textColor = isWeapon ? new Color(1f, 0.85f, 0.2f) : Color.white;
+
+ Vector2 size = bg.CalcSize(new GUIContent(prompt));
+ Rect bgRect = new Rect(sx - size.x * 0.5f - 6f, sy - size.y - 4f, size.x + 12f, size.y + 6f);
+
+ // Dark backing box
+ Color prev = GUI.color;
+ GUI.color = new Color(0, 0, 0, 0.65f);
+ GUI.DrawTexture(bgRect, Texture2D.whiteTexture);
+ GUI.color = prev;
+
+ GUI.Label(bgRect, " " + prompt + " ", bg);
+ }
+
+ void OnDrawGizmosSelected()
+ {
+ Gizmos.color = Color.yellow;
+ Gizmos.DrawWireSphere(transform.position, pickupRadius);
+ }
+
+ void Collect()
+ {
+ _collected = true;
+ _inRange = false;
+
+ if (_inventory != null)
+ {
+ if (definition != null)
+ _inventory.AddItem(definition, autoEquip: true);
+ else
+ _inventory.AddItem(itemName, itemIcon);
+ }
+ else
+ {
+ Debug.LogWarning("PickupItem: No Inventory found on player!");
+ }
+
+ if (pickupParticlesPrefab != null)
+ Instantiate(pickupParticlesPrefab, transform.position, Quaternion.identity);
+
+ if (pickupSound != null)
+ {
+ _audioSource.PlayOneShot(pickupSound);
+ Destroy(gameObject, pickupSound.length);
+ }
+ else
+ {
+ Destroy(gameObject);
+ }
+ }
+}
diff --git a/Assets/Scripts/PickupItem.cs.meta b/Assets/Scripts/PickupItem.cs.meta
new file mode 100644
index 0000000..b907c42
--- /dev/null
+++ b/Assets/Scripts/PickupItem.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 95709fa8fd6c75045b1ef8e4f505c152
\ No newline at end of file
diff --git a/Assets/Scripts/Player.cs b/Assets/Scripts/Player.cs
index c16e024..6e6954b 100644
--- a/Assets/Scripts/Player.cs
+++ b/Assets/Scripts/Player.cs
@@ -1,6 +1,65 @@
using UnityEngine;
+///
+/// Core player stats: health and stamina.
+/// Stamina drains while sprinting, regens when not.
+/// If stamina hits zero the player is exhausted and can't sprint again until it fully refills.
+///
public class Player : MonoBehaviour
{
- public float health;
+ [Header("Health")]
+ public float maxHealth = 100f;
+ public float health = 100f;
+
+ [Header("Stamina")]
+ public float maxStamina = 100f;
+ public float stamina = 100f;
+ public float staminaDrain = 22f; // per second while sprinting
+ public float staminaRegen = 12f; // per second while not sprinting
+ public float regenDelay = 1.2f; // seconds after stopping sprint before regen kicks in
+
+ // ─── State ───────────────────────────────────────────────────────
+ [HideInInspector] public bool isSprinting = false;
+ [HideInInspector] public bool isExhausted = false; // true until stamina fully refills
+
+ private float _regenTimer = 0f;
+
+ // ─────────────────────────────────────────────────────────────────
+ void Update()
+ {
+ if (isSprinting && !isExhausted)
+ {
+ stamina -= staminaDrain * Time.deltaTime;
+ _regenTimer = 0f;
+
+ if (stamina <= 0f)
+ {
+ stamina = 0f;
+ isExhausted = true;
+ }
+ }
+ else
+ {
+ // Count down regen delay
+ _regenTimer += Time.deltaTime;
+
+ if (_regenTimer >= regenDelay)
+ {
+ stamina = Mathf.MoveTowards(stamina, maxStamina, staminaRegen * Time.deltaTime);
+
+ // Clear exhaustion only once fully refilled
+ if (isExhausted && stamina >= maxStamina)
+ isExhausted = false;
+ }
+ }
+
+ health = Mathf.Clamp(health, 0f, maxHealth);
+ stamina = Mathf.Clamp(stamina, 0f, maxStamina);
+ }
+
+ // ─── Helpers other scripts can call ──────────────────────────────
+ public bool CanSprint() => !isExhausted && stamina > 0f;
+
+ public float HealthFraction => health / maxHealth;
+ public float StaminaFraction => stamina / maxStamina;
}
diff --git a/Assets/Scripts/PlayerHUD.cs b/Assets/Scripts/PlayerHUD.cs
new file mode 100644
index 0000000..c020909
--- /dev/null
+++ b/Assets/Scripts/PlayerHUD.cs
@@ -0,0 +1,281 @@
+using UnityEngine;
+
+///
+/// Draws the player health and stamina bars.
+/// Attach to the Player GameObject alongside Player.cs
+///
+/// Deliberately ugly. This is a Cruelty Squad game.
+///
+public class PlayerHUD : MonoBehaviour
+{
+ [Header("Layout")]
+ public float barWidth = 220f;
+ public float barHeight = 16f;
+ public float barSpacing = 10f; // gap between health and stamina bars
+ public float edgePadX = 18f;
+ public float edgePadY = 18f;
+ public AnchorCorner anchor = AnchorCorner.BottomLeft;
+
+ public enum AnchorCorner { BottomLeft, BottomRight, TopLeft, TopRight }
+
+ [Header("Health Bar")]
+ public Color colHealthFull = new Color(0.15f, 0.80f, 0.25f, 1f);
+ public Color colHealthMid = new Color(0.85f, 0.75f, 0.05f, 1f);
+ public Color colHealthLow = new Color(0.90f, 0.12f, 0.12f, 1f);
+ public float healthMidThreshold = 0.5f;
+ public float healthLowThreshold = 0.25f;
+
+ [Header("Stamina Bar")]
+ public Color colStaminaNormal = new Color(0.20f, 0.55f, 0.95f, 1f);
+ public Color colStaminaRegen = new Color(0.20f, 0.55f, 0.95f, 0.45f); // dimmed while regenerating
+ public Color colStaminaExhaust = new Color(0.60f, 0.18f, 0.18f, 1f); // red flash when tapped out
+
+ [Header("Shared Style")]
+ public Color colBarBackground = new Color(0.06f, 0.06f, 0.06f, 0.90f);
+ public Color colBarBorder = new Color(0.28f, 0.28f, 0.28f, 1.00f);
+ public Color colLabel = new Color(0.80f, 0.80f, 0.80f, 1.00f);
+ public Color colLabelCritical = new Color(1.00f, 0.25f, 0.25f, 1.00f);
+ public float borderThickness = 1.5f;
+
+ [Header("Pulse")]
+ [Tooltip("The low-health bar pulses opacity when below the low threshold.")]
+ public float pulseSpeed = 3.5f;
+
+ [Header("Speedometer")]
+ public bool showSpeedometer = true;
+ public Color colSpeedo = new Color(0.20f, 0.95f, 0.40f, 1f);
+ public Color colSpeedoFast = new Color(0.95f, 0.80f, 0.10f, 1f);
+ public Color colSpeedoCritical = new Color(0.95f, 0.20f, 0.20f, 1f);
+ public float speedoFastThreshold = 60f;
+ public float speedoCritThreshold = 75f;
+
+ // ─── Private ─────────────────────────────────────────────────────
+ private Player _player;
+ private CharacterController _cc;
+ private Texture2D _white;
+
+ // Smooth display values so bars glide rather than snap
+ private float _displayHealth = 1f;
+ private float _displayStamina = 1f;
+ private const float kSmoothSpeed = 8f;
+
+ // Speed tracking
+ private Vector3 _lastPos;
+ private float _displaySpeed;
+
+ // ─────────────────────────────────────────────────────────────────
+ void Start()
+ {
+ _player = GetComponent();
+ _cc = GetComponent();
+ _white = Texture2D.whiteTexture;
+ _lastPos = transform.position;
+
+ if (_player == null)
+ Debug.LogWarning("[PlayerHUD] No Player component found on this GameObject!");
+ }
+
+ void Update()
+ {
+ if (_player == null) return;
+
+ // Smooth bar fill values towards actual values
+ _displayHealth = Mathf.Lerp(_displayHealth, _player.HealthFraction, Time.deltaTime * kSmoothSpeed);
+ _displayStamina = Mathf.Lerp(_displayStamina, _player.StaminaFraction, Time.deltaTime * kSmoothSpeed);
+
+ // Calculate speed from position delta (horizontal only)
+ Vector3 pos = transform.position;
+ float rawSpeed = new Vector3(pos.x - _lastPos.x, 0f, pos.z - _lastPos.z).magnitude / Time.deltaTime;
+ _displaySpeed = Mathf.Lerp(_displaySpeed, rawSpeed, Time.deltaTime * 10f);
+ _lastPos = pos;
+ }
+
+ void OnGUI()
+ {
+ if (_player == null) return;
+
+ float totalH = barHeight * 2f + barSpacing + 20f; // label heights included
+ float totalW = barWidth;
+
+ float ox, oy;
+ switch (anchor)
+ {
+ case AnchorCorner.BottomLeft:
+ ox = edgePadX;
+ oy = Screen.height - totalH - edgePadY;
+ break;
+ case AnchorCorner.BottomRight:
+ ox = Screen.width - totalW - edgePadX;
+ oy = Screen.height - totalH - edgePadY;
+ break;
+ case AnchorCorner.TopLeft:
+ ox = edgePadX;
+ oy = edgePadY;
+ break;
+ default: // TopRight
+ ox = Screen.width - totalW - edgePadX;
+ oy = edgePadY;
+ break;
+ }
+
+ float cursor = oy;
+
+ // ── Health bar ───────────────────────────────────────────────
+ cursor = DrawStatBar(
+ ox, cursor,
+ label: "HEALTH",
+ value: _player.health,
+ maxValue: _player.maxHealth,
+ displayFraction: _displayHealth,
+ fillColor: GetHealthColor(),
+ pulse: _player.HealthFraction <= healthLowThreshold,
+ labelCritical: _player.HealthFraction <= healthLowThreshold
+ );
+
+ cursor += barSpacing;
+
+ // ── Stamina bar ──────────────────────────────────────────────
+ Color staminaColor;
+ if (_player.isExhausted)
+ staminaColor = colStaminaExhaust;
+ else if (!_player.isSprinting && _player.StaminaFraction < 1f)
+ staminaColor = colStaminaRegen;
+ else
+ staminaColor = colStaminaNormal;
+
+ string staminaLabel = _player.isExhausted ? "STAMINA [EXHAUSTED]" : "STAMINA";
+
+ DrawStatBar(
+ ox, cursor,
+ label: staminaLabel,
+ value: _player.stamina,
+ maxValue: _player.maxStamina,
+ displayFraction: _displayStamina,
+ fillColor: staminaColor,
+ pulse: _player.isExhausted,
+ labelCritical: _player.isExhausted
+ );
+
+ // ── Speedometer ──────────────────────────────────────────────
+ if (showSpeedometer && _cc != null)
+ DrawSpeedometer(ox, oy - 90f);
+ }
+
+ void DrawSpeedometer(float x, float y)
+ {
+ float speed = _displaySpeed;
+
+ Color col;
+ if (speed >= speedoCritThreshold) col = colSpeedoCritical;
+ else if (speed >= speedoFastThreshold) col = colSpeedoFast;
+ else col = colSpeedo;
+
+ float w = 130f;
+ float h = 70f;
+
+ // Dark backing box
+ DrawTex(new Rect(x - 6f, y - 6f, w + 12f, h + 12f), new Color(0f, 0f, 0f, 0.55f));
+ // Coloured left edge accent
+ DrawTex(new Rect(x - 6f, y - 6f, 3f, h + 12f), col);
+
+ // "SPEED" label
+ GUIStyle labelStyle = new GUIStyle();
+ labelStyle.fontSize = 10;
+ labelStyle.fontStyle = FontStyle.Bold;
+ labelStyle.normal.textColor = new Color(col.r, col.g, col.b, 0.75f);
+ GUI.Label(new Rect(x, y, w, 16f), "SPEED", labelStyle);
+
+ // Big number
+ GUIStyle numStyle = new GUIStyle();
+ numStyle.fontSize = 36;
+ numStyle.fontStyle = FontStyle.Bold;
+ numStyle.normal.textColor = col;
+ GUI.Label(new Rect(x, y + 14f, w, 42f), $"{speed:F1}", numStyle);
+
+ // Unit
+ GUIStyle unitStyle = new GUIStyle();
+ unitStyle.fontSize = 10;
+ unitStyle.fontStyle = FontStyle.Normal;
+ unitStyle.normal.textColor = new Color(col.r, col.g, col.b, 0.55f);
+ GUI.Label(new Rect(x, y + 54f, w, 16f), "units / sec", unitStyle);
+ }
+
+ // ─── Bar renderer ─────────────────────────────────────────────────
+ /// Draws one labelled bar. Returns the Y position after the bar.
+ float DrawStatBar(
+ float x, float y,
+ string label,
+ float value, float maxValue,
+ float displayFraction,
+ Color fillColor,
+ bool pulse,
+ bool labelCritical)
+ {
+ float labelH = 14f;
+ float innerW = barWidth - borderThickness * 2f;
+ float innerH = barHeight - borderThickness * 2f;
+
+ // ── Label ────────────────────────────────────────────────────
+ GUIStyle labelStyle = new GUIStyle();
+ labelStyle.fontSize = 10;
+ labelStyle.fontStyle = FontStyle.Bold;
+ labelStyle.normal.textColor = labelCritical ? colLabelCritical : colLabel;
+
+ string valueStr = $"{Mathf.CeilToInt(value)} / {Mathf.CeilToInt(maxValue)}";
+ GUI.Label(new Rect(x, y, barWidth * 0.6f, labelH), label, labelStyle);
+
+ GUIStyle valStyle = new GUIStyle(labelStyle);
+ valStyle.alignment = TextAnchor.UpperRight;
+ valStyle.normal.textColor = labelCritical ? colLabelCritical : colLabel;
+ GUI.Label(new Rect(x, y, barWidth, labelH), valueStr, valStyle);
+
+ float barY = y + labelH + 2f;
+
+ // ── Background ───────────────────────────────────────────────
+ DrawTex(new Rect(x, barY, barWidth, barHeight), colBarBorder);
+ DrawTex(new Rect(x + borderThickness, barY + borderThickness, innerW, innerH), colBarBackground);
+
+ // ── Fill ─────────────────────────────────────────────────────
+ float fillW = Mathf.Max(0f, displayFraction * innerW);
+
+ // Pulse alpha on low health / exhaustion
+ Color drawColor = fillColor;
+ if (pulse)
+ {
+ float alpha = Mathf.Lerp(0.35f, 1f, (Mathf.Sin(Time.time * pulseSpeed) + 1f) * 0.5f);
+ drawColor = new Color(fillColor.r, fillColor.g, fillColor.b, fillColor.a * alpha);
+ }
+
+ if (fillW > 0f)
+ DrawTex(new Rect(x + borderThickness, barY + borderThickness, fillW, innerH), drawColor);
+
+ // ── Segment tick marks (every 25%) ───────────────────────────
+ for (int i = 1; i < 4; i++)
+ {
+ float tickX = x + borderThickness + innerW * (i / 4f);
+ DrawTex(new Rect(tickX - 0.5f, barY + borderThickness, 1f, innerH),
+ new Color(0f, 0f, 0f, 0.45f));
+ }
+
+ return barY + barHeight; // bottom of bar
+ }
+
+ // ─── Helpers ─────────────────────────────────────────────────────
+ Color GetHealthColor()
+ {
+ float f = _player.HealthFraction;
+ if (f <= healthLowThreshold)
+ return colHealthLow;
+ if (f <= healthMidThreshold)
+ return Color.Lerp(colHealthLow, colHealthMid, (f - healthLowThreshold) / (healthMidThreshold - healthLowThreshold));
+ return Color.Lerp(colHealthMid, colHealthFull, (f - healthMidThreshold) / (1f - healthMidThreshold));
+ }
+
+ void DrawTex(Rect r, Color c)
+ {
+ Color prev = GUI.color;
+ GUI.color = c;
+ GUI.DrawTexture(r, _white);
+ GUI.color = prev;
+ }
+}
diff --git a/Assets/Scripts/PlayerHUD.cs.meta b/Assets/Scripts/PlayerHUD.cs.meta
new file mode 100644
index 0000000..e00207e
--- /dev/null
+++ b/Assets/Scripts/PlayerHUD.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 1d84d3e6c1cf6794da6ff84203edb291
\ No newline at end of file
diff --git a/Assets/Scripts/SimpleGun.cs b/Assets/Scripts/SimpleGun.cs
index 2a6c245..ad100e9 100644
--- a/Assets/Scripts/SimpleGun.cs
+++ b/Assets/Scripts/SimpleGun.cs
@@ -16,6 +16,8 @@ public class SimpleGun : MonoBehaviour
public float recoilRecoverySpeed = 12f;
[Header("Weapon Bob Settings")]
+ [Tooltip("Set false if WeaponBob component is on the Camera — they'll double-stack otherwise.")]
+ public bool enableInternalBob = false;
public float bobFrequency = 10f;
public float bobHorizontalAmplitude = 0.05f;
public float bobVerticalAmplitude = 0.03f;
@@ -48,6 +50,10 @@ public class SimpleGun : MonoBehaviour
// Fire timing
private float nextTimeToFire = 0f;
+ // Cached refs for inventory check
+ private Inventory _inventory;
+ private WeaponManager _weaponManager;
+
void Start()
{
currentAmmo = maxAmmo;
@@ -58,7 +64,12 @@ public class SimpleGun : MonoBehaviour
fpsCam = GetComponentInParent();
playerController = GetComponentInParent();
- originalLocalPos = transform.localPosition;
+ _inventory = GetComponentInParent();
+ _weaponManager = GetComponentInParent();
+ // originalLocalPos is driven by WeaponManager's offset — don't cache here
+ originalLocalPos = _weaponManager != null
+ ? _weaponManager.weaponPositionOffset
+ : transform.localPosition;
// NUCLEAR OPTION: Remove ANY collider on this object or children
@@ -93,6 +104,10 @@ public class SimpleGun : MonoBehaviour
void Update()
{
+ // Don't shoot while the inventory is open
+ bool inventoryOpen = _inventory != null && _inventory.IsOpen;
+ if (inventoryOpen) return;
+
// Shooting
if (isAutomatic ? InputData.leftMouseButton.down : InputData.leftMouseButton.pressed)
{
@@ -107,11 +122,18 @@ public class SimpleGun : MonoBehaviour
if (InputData.reload.pressed)
Reload();
+ // Keep base position in sync with WeaponManager's live offset
+ if (_weaponManager != null)
+ originalLocalPos = _weaponManager.weaponPositionOffset;
+
// Recover from recoil
recoilOffset = Vector3.Lerp(recoilOffset, Vector3.zero, Time.deltaTime * recoilRecoverySpeed);
- // Weapon bob
- UpdateBob();
+ // Weapon bob (only if internal bob is enabled — disable if WeaponBob is on the Camera)
+ if (enableInternalBob)
+ UpdateBob();
+ else
+ bobOffset = Vector3.zero;
// Apply combined position: original + recoil + bob
transform.localPosition = originalLocalPos + recoilOffset + bobOffset;
diff --git a/Assets/Scripts/StaminaBoostPickup.cs b/Assets/Scripts/StaminaBoostPickup.cs
new file mode 100644
index 0000000..04ff082
--- /dev/null
+++ b/Assets/Scripts/StaminaBoostPickup.cs
@@ -0,0 +1,36 @@
+using UnityEngine;
+
+///
+/// Add to a pickup GameObject alongside PickupItem.
+/// When the item is collected, applies a one-time boost to the player's max stamina.
+///
+[RequireComponent(typeof(PickupItem))]
+public class StaminaBoostPickup : MonoBehaviour
+{
+ [Tooltip("Max stamina will be set to this value when picked up.")]
+ public float newMaxStamina = 200f;
+
+ void Start()
+ {
+ // Hook into PickupItem's collect event via a simple poll
+ var pickup = GetComponent();
+ // We override by watching PickupItem's collected state each frame
+ StartCoroutine(WatchForCollect(pickup));
+ }
+
+ System.Collections.IEnumerator WatchForCollect(PickupItem pickup)
+ {
+ // Wait until the pickup is destroyed (collected)
+ while (pickup != null)
+ yield return null;
+
+ // Find player and apply boost
+ Player p = FindObjectOfType();
+ if (p != null)
+ {
+ p.maxStamina = newMaxStamina;
+ p.stamina = newMaxStamina;
+ Debug.Log($"[StaminaBoost] Max stamina set to {newMaxStamina}");
+ }
+ }
+}
diff --git a/Assets/Scripts/StaminaBoostPickup.cs.meta b/Assets/Scripts/StaminaBoostPickup.cs.meta
new file mode 100644
index 0000000..ae73f6f
--- /dev/null
+++ b/Assets/Scripts/StaminaBoostPickup.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 1ce88c8ee6fd8a0469215a541f72f835
\ No newline at end of file
diff --git a/Assets/Scripts/WeaponManager.cs b/Assets/Scripts/WeaponManager.cs
new file mode 100644
index 0000000..d5792ba
--- /dev/null
+++ b/Assets/Scripts/WeaponManager.cs
@@ -0,0 +1,197 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+///
+/// Attach to the Player GameObject.
+/// Manages all instantiated weapon instances and switching between them.
+/// Also registers any weapon already in the scene (e.g. the starting gun).
+///
+public class WeaponManager : MonoBehaviour
+{
+ [Header("References")]
+ [Tooltip("The transform weapons are parented to — usually the Camera or a WeaponHolder child.")]
+ public Transform weaponHolder;
+
+ [Header("Weapon Position")]
+ [Tooltip("Local position offset applied to all equipped weapons.")]
+ public Vector3 weaponPositionOffset = new Vector3(0.2f, -0.25f, 0.45f);
+ [Tooltip("Local euler rotation offset applied to all equipped weapons.")]
+ public Vector3 weaponRotationOffset = new Vector3(0f, 0f, 0f);
+ [Tooltip("Scale applied to all equipped weapons.")]
+ public Vector3 weaponScale = new Vector3(1f, 1f, 1f);
+
+ [Header("Switching")]
+ public bool allowScrollSwitch = true;
+ public bool allowNumberKeys = true;
+
+ // ─── internal slot ───────────────────────────────────────────────
+ public class WeaponSlot
+ {
+ public string itemName;
+ public GameObject instance; // the live GO in weaponHolder
+ public ItemDefinition definition; // may be null for the starting gun
+ }
+
+ public List slots = new List();
+ public int activeIndex { get; private set; } = -1;
+
+ // ─────────────────────────────────────────────────────────────────
+ void Start()
+ {
+ // Auto-find weapon holder if not set
+ if (weaponHolder == null)
+ {
+ Camera cam = GetComponentInChildren();
+ weaponHolder = cam != null ? cam.transform : transform;
+ }
+
+ // Register any SimpleGun already present as children of the holder
+ SimpleGun[] existing = weaponHolder.GetComponentsInChildren(true);
+ foreach (SimpleGun gun in existing)
+ {
+ RegisterExistingGun(gun.gameObject);
+ }
+ }
+
+ private Inventory _inventory;
+
+ void Update()
+ {
+ if (slots.Count == 0) return;
+
+ // Live-update active weapon transform so Inspector tweaks take effect immediately
+ if (activeIndex >= 0 && activeIndex < slots.Count)
+ {
+ var vm = slots[activeIndex].instance.GetComponent();
+ if (vm != null)
+ vm.Apply();
+ else
+ {
+ Transform t = slots[activeIndex].instance.transform;
+ t.localPosition = weaponPositionOffset;
+ t.localRotation = Quaternion.Euler(weaponRotationOffset);
+ t.localScale = weaponScale;
+ }
+ }
+
+ // Don't switch weapons while the inventory UI is open
+ if (_inventory == null) _inventory = GetComponent();
+ if (_inventory != null && _inventory.IsOpen) return;
+
+ // Scroll wheel
+ if (allowScrollSwitch)
+ {
+ float scroll = Input.GetAxis("Mouse ScrollWheel");
+ if (scroll > 0f) CycleWeapon(1);
+ if (scroll < 0f) CycleWeapon(-1);
+ }
+
+ // Number keys 1–9
+ if (allowNumberKeys)
+ {
+ for (int i = 0; i < Mathf.Min(slots.Count, 9); i++)
+ {
+ if (Input.GetKeyDown(KeyCode.Alpha1 + i))
+ EquipByIndex(i);
+ }
+ }
+ }
+
+ // ─── Public API ──────────────────────────────────────────────────
+
+ /// Register a weapon that already exists in the scene (starting gun).
+ public void RegisterExistingGun(GameObject gunGO, ItemDefinition def = null)
+ {
+ string name = def != null ? def.itemName : gunGO.name;
+
+ // Don't double-register
+ if (slots.Exists(s => s.instance == gunGO)) return;
+
+ slots.Add(new WeaponSlot { itemName = name, instance = gunGO, definition = def });
+ int idx = slots.Count - 1;
+
+ // Always start inactive — player must pick up / equip from inventory
+ gunGO.SetActive(false);
+
+ Debug.Log($"[WeaponManager] Registered existing weapon: {name} at slot {idx} (inactive)");
+ }
+
+ /// Add a new weapon from a definition (called by Inventory on first equip).
+ public int AddWeapon(ItemDefinition def)
+ {
+ if (def == null || def.weaponPrefab == null)
+ {
+ Debug.LogWarning("[WeaponManager] AddWeapon: definition or prefab is null.");
+ return -1;
+ }
+
+ // Already registered?
+ int existing = slots.FindIndex(s => s.definition == def);
+ if (existing >= 0) return existing;
+
+ GameObject instance = Instantiate(def.weaponPrefab, weaponHolder);
+ var vm = instance.GetComponent();
+ if (vm != null)
+ vm.Apply();
+ else
+ {
+ instance.transform.localPosition = weaponPositionOffset;
+ instance.transform.localRotation = Quaternion.Euler(weaponRotationOffset);
+ instance.transform.localScale = weaponScale;
+ }
+ instance.SetActive(false);
+
+ // Strip colliders so they don't mess with the CharacterController
+ foreach (Collider col in instance.GetComponentsInChildren())
+ if (!(col is CharacterController)) Destroy(col);
+
+ slots.Add(new WeaponSlot { itemName = def.itemName, instance = instance, definition = def });
+ int newIdx = slots.Count - 1;
+ Debug.Log($"[WeaponManager] Added weapon: {def.itemName} at slot {newIdx}");
+ return newIdx;
+ }
+
+ /// Equip weapon by slot index.
+ public void EquipByIndex(int index)
+ {
+ if (index < 0 || index >= slots.Count) return;
+
+ // Deactivate current
+ if (activeIndex >= 0 && activeIndex < slots.Count)
+ slots[activeIndex].instance.SetActive(false);
+
+ activeIndex = index;
+ slots[activeIndex].instance.SetActive(true);
+ Debug.Log($"[WeaponManager] Equipped: {slots[activeIndex].itemName}");
+ }
+
+ /// Equip weapon by item name.
+ public void EquipByName(string name)
+ {
+ int idx = slots.FindIndex(s => s.itemName == name);
+ if (idx >= 0)
+ EquipByIndex(idx);
+ else
+ Debug.LogWarning($"[WeaponManager] No slot found for: {name}");
+ }
+
+ /// Cycle forward (+1) or backward (-1).
+ public void CycleWeapon(int dir)
+ {
+ if (slots.Count == 0) return;
+ int next = (activeIndex + dir + slots.Count) % slots.Count;
+ EquipByIndex(next);
+ }
+
+ /// Deactivate the current weapon without switching to another.
+ public void Unequip()
+ {
+ if (activeIndex >= 0 && activeIndex < slots.Count)
+ slots[activeIndex].instance.SetActive(false);
+ activeIndex = -1;
+ Debug.Log("[WeaponManager] Unequipped all weapons.");
+ }
+
+ public string ActiveWeaponName =>
+ (activeIndex >= 0 && activeIndex < slots.Count) ? slots[activeIndex].itemName : "";
+}
diff --git a/Assets/Scripts/WeaponManager.cs.meta b/Assets/Scripts/WeaponManager.cs.meta
new file mode 100644
index 0000000..bf010c4
--- /dev/null
+++ b/Assets/Scripts/WeaponManager.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 18715d388d5da7544b10c5d7a385cbee
\ No newline at end of file
diff --git a/Assets/Scripts/WeaponViewmodel.cs b/Assets/Scripts/WeaponViewmodel.cs
new file mode 100644
index 0000000..96ccd1e
--- /dev/null
+++ b/Assets/Scripts/WeaponViewmodel.cs
@@ -0,0 +1,29 @@
+using UnityEngine;
+
+///
+/// Add to a weapon prefab to define how it sits in the player's hands.
+/// WeaponManager reads these values when the weapon is equipped.
+///
+public class WeaponViewmodel : MonoBehaviour
+{
+ [Header("View Model Transform")]
+ public Vector3 positionOffset = new Vector3(0.2f, -0.25f, 0.45f);
+ public Vector3 rotationOffset = new Vector3(0f, 0f, 0f);
+ public Vector3 scale = new Vector3(1f, 1f, 1f);
+
+ // Called by WeaponManager to apply these values
+ public void Apply()
+ {
+ transform.localPosition = positionOffset;
+ transform.localRotation = Quaternion.Euler(rotationOffset);
+ transform.localScale = scale;
+ }
+
+ // Called by WeaponManager to sync back after gizmo editing
+ public void SyncFromTransform()
+ {
+ positionOffset = transform.localPosition;
+ rotationOffset = transform.localRotation.eulerAngles;
+ scale = transform.localScale;
+ }
+}
diff --git a/Assets/Scripts/WeaponViewmodel.cs.meta b/Assets/Scripts/WeaponViewmodel.cs.meta
new file mode 100644
index 0000000..b805958
--- /dev/null
+++ b/Assets/Scripts/WeaponViewmodel.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 59433c687536a60418803799027ddcc3
\ No newline at end of file
diff --git a/DEV_NOTES.md b/DEV_NOTES.md
new file mode 100644
index 0000000..3f26f00
--- /dev/null
+++ b/DEV_NOTES.md
@@ -0,0 +1,353 @@
+# OGG — Dev Notes & Systems Guide
+
+> Boomer shooter. Cruelty Squad vibes. Jank is a feature.
+> This doc covers all current systems.
+
+---
+
+## Scene Hierarchy
+
+```
+Player [CharacterController, FirstPersonController, SimpleCrosshair]
+ │ [Player, PlayerHUD, Inventory, WeaponManager, BootsEffect]
+ └── Camera [Camera, AudioListener, CameraShake, WeaponBob]
+```
+
+Pickups are standalone GameObjects placed anywhere in the scene — **not** children of the player.
+
+---
+
+## Scripts Overview
+
+| Script | Lives On | What it does |
+|---|---|---|
+| `FirstPersonController` | Player | WASD move, mouse look, jump, sprint, cursor lock |
+| `Player` | Player | Health + stamina stats, drain/regen logic, sprint gating |
+| `PlayerHUD` | Player | Health bar, stamina bar, speedometer (IMGUI) |
+| `SimpleGun` | Weapon prefabs | Shooting, ammo, recoil, muzzle flash, bullet decals |
+| `CameraShake` | Camera | Singleton shake impulses, called by SimpleGun on fire |
+| `WeaponBob` | Camera | Procedural weapon sway while moving |
+| `WeaponViewmodel` | Weapon prefabs | Per-weapon position/rotation/scale in the player's hands |
+| `SimpleCrosshair` | Player | Draws the crosshair via OnGUI |
+| `EnemyHealth` | Enemy objects | Takes damage from SimpleGun raycasts, flashes red, dies |
+| `EnemySpawner` | Any object | Spawns enemies at runtime |
+| `HumanoidEnemy` | Enemy objects | Enemy AI / behaviour |
+| `ItemDefinition` | ScriptableObject asset | Data definition for any item: name, icon, type, weapon prefab, equippable flag |
+| `PickupItem` | Pickup GameObjects | Spin/bob animation, proximity prompt, E-to-collect |
+| `Inventory` | Player | Item list, [I] HUD, right-click context menu, equip logic |
+| `WeaponManager` | Player | Instantiates weapon prefabs, handles slot switching |
+| `BootsEffect` | Player | Watches inventory for Bunny Hop Boots equipped state, applies stamina boost |
+| `StaminaBoostPickup` | Pickup GameObjects | Legacy — superseded by BootsEffect. Left in for reference. |
+
+---
+
+## 1. Player Stats — `Player` + `PlayerHUD`
+
+`Player.cs` is the data container for all player stats. `PlayerHUD.cs` reads from it and draws the HUD.
+
+**Health fields:**
+- `maxHealth` / `health` — enemies subtract from `health` directly, no auto-regen
+
+**Stamina fields:**
+- `maxStamina` / `stamina` — depletes while sprinting
+- `staminaDrain` — units lost per second while sprinting (default 22)
+- `staminaRegen` — units gained per second when resting (default 12)
+- `regenDelay` — seconds of non-sprinting before regen starts (default 1.2)
+- When stamina hits 0 the player is **exhausted** — sprint locked out until stamina fully refills
+
+**HUD Layout:**
+```
+[bottom-left corner]
+SPEED
+42.3
+units / sec
+
+HEALTH 85 / 100
+[████████████░░░]
+STAMINA 62 / 100
+[██████░░░░░░░░░]
+```
+
+**Speedometer:**
+- Sits above the health/stamina bars, bottom-left
+- Speed calculated from actual position delta per frame (horizontal only)
+- Colour shifts: green → yellow → red as speed increases
+- Thresholds configurable in Inspector (`speedoFastThreshold`, `speedoCritThreshold`)
+- Can be toggled off via `showSpeedometer` bool
+
+---
+
+## 2. Movement — `FirstPersonController`
+
+Standard FPS controller.
+
+**Inspector fields:**
+- `walkSpeed` / `runSpeed` — intentionally high (50/80) due to CharacterController collision tuning
+- `mouseSensitivity` — linear, no acceleration
+- `playerCamera` — auto-found if empty, best to assign explicitly
+
+**Controls:**
+| Key | Action |
+|---|---|
+| W A S D / Arrows | Move |
+| Left Shift | Sprint (drains stamina) |
+| Space | Jump |
+| Mouse | Look |
+| Escape | Unlock cursor |
+| Left Click (unlocked) | Re-lock cursor |
+
+---
+
+## 3. Shooting — `SimpleGun`
+
+Raycast-based hitscan. No projectiles.
+
+**Key fields:**
+- `damage` — per shot
+- `range` — raycast distance in units
+- `fireRate` — shots per second
+- `isAutomatic` — hold vs click
+- `recoilKickback` / `recoilKickUp` — feel tuning
+- `fpsCam` — auto-found if null
+
+**Internals:**
+- Muzzle flash: child Quad + Point Light, shown ~50ms per shot
+- Bullet decals: pooled (30 max), oldest reused when full
+- Spark particles: spawned and self-destroyed on impact
+- Calls `CameraShake.Instance.Shake()` on every shot
+- Shooting blocked when inventory is open
+- Position driven by `WeaponManager.weaponPositionOffset` each frame so recoil applies on top correctly
+
+**Ammo:** `currentAmmo` / `maxAmmo`. Press **R** to reload (instant).
+
+---
+
+## 4. Item Definitions — `ItemDefinition` (ScriptableObject)
+
+**Create:** Right-click in Project → **Create → OGG → Item Definition**
+
+| Field | Notes |
+|---|---|
+| `itemName` | Display name everywhere — must be unique |
+| `icon` | Sprite shown in inventory slots |
+| `type` | `Misc`, `Consumable`, or `Weapon` |
+| `isEquippable` | Tick for non-weapon items that can be equipped (e.g. boots) — shows equip button in inventory |
+| `weaponPrefab` | Weapon type only — prefab spawned in weapon holder when equipped |
+| `description` | Shown in inventory detail panel |
+
+---
+
+## 5. Pickups — `PickupItem`
+
+Attach to any GameObject to make it a spinning, bobbing pickup.
+
+**Setup:**
+1. Place a GameObject in the scene with any mesh
+2. Add `PickupItem` component
+3. Drag an `ItemDefinition` into the `Definition` slot
+
+**Inspector fields:**
+| Field | Notes |
+|---|---|
+| `definition` | The ItemDefinition asset |
+| `spinSpeed` | Degrees/sec Y rotation (default 120) |
+| `bobHeight` | Vertical travel in units (default 0.18) |
+| `bobSpeed` | Bob cycles per second (default 2.2) |
+| `pickupRadius` | Proximity trigger distance |
+| `pickupKey` | Default E |
+| `pickupParticlesPrefab` | Optional — spawned on collect |
+| `pickupSound` | Optional — played on collect |
+
+**On pickup:** Calls `Inventory.AddItem(definition, autoEquip: true)` — picking up a weapon automatically equips it.
+
+---
+
+## 6. Inventory — `Inventory`
+
+Lives on the Player. Press **I** to open/close. Cursor unlocks when open.
+
+**HUD Layout:**
+```
+┌──────────────────────────────┐
+│ [ INVENTORY ] │ ← header, shows active weapon top-right
+├──────────────────────────────┤
+│ [ ] [ ] [ ] [ ] │
+│ [ ] [ ] [ ] [ ] │ ← item grid (4×5 by default)
+├──────────────────────────────┤
+│ SELECTED: Item Name │ ← detail panel
+│ Description │
+│ ▶ EQUIPPED [ F ] UNEQUIP│ ← shown for weapons AND equippable items
+└──────────────────────────────┘
+```
+
+**Right-click context menu** on any slot:
+- **Equip** / **Unequip** — shown for weapons and items with `isEquippable = true`
+- **Inspect** — selects the item in the detail panel
+- **Drop** — removes from inventory
+
+**Controls:**
+| Input | Action |
+|---|---|
+| I | Toggle inventory open/close |
+| Click slot | Select item |
+| Double-click weapon slot | Equip it |
+| Right-click slot | Open context menu |
+| F | Equip selected item |
+| Scroll Wheel (closed) | Cycle weapons |
+| 1–9 (closed) | Equip weapon by slot number |
+
+**Slot colours:**
+- Dark grey = empty
+- Dark green = has item
+- Bright green = selected
+- Orange = equipped
+
+**Useful methods:**
+```csharp
+inventory.HasItem("Medkit") // bool
+inventory.CountOf("Grenade") // int
+inventory.RemoveItem("Medkit", 1) // bool
+inventory.AddItem(definition) // add by ItemDefinition
+inventory.AddItem("Raw Name", sprite) // add by name
+inventory.IsOpen // bool — checked by gun + WeaponManager
+```
+
+---
+
+## 7. Weapon Manager — `WeaponManager`
+
+Lives on the Player. Manages weapon instances and which is active.
+
+**Setup:**
+- Add `WeaponManager` to Player
+- Set `Weapon Holder` to the Camera transform
+- Any `SimpleGun` child of the holder is auto-registered but starts **inactive** — player must pick up and equip from inventory
+
+**Weapon Position fields:**
+- `weaponPositionOffset` / `weaponRotationOffset` / `weaponScale` — global fallback if the weapon has no `WeaponViewmodel` component
+- Per-weapon positioning is preferred — add `WeaponViewmodel` to the weapon prefab instead
+
+**Live positioning workflow (in Editor):**
+1. Hit Play, equip the gun
+2. On WeaponManager Inspector → click **"Select Active Weapon in Scene"**
+3. Use gizmos in Scene view to position it
+4. Click **"Sync Transform → Offset Fields"** — saves to the `WeaponViewmodel` on the prefab automatically
+5. Stop Play — values persist
+
+**Useful methods:**
+```csharp
+weaponManager.EquipByIndex(0)
+weaponManager.EquipByName("Gun Splat")
+weaponManager.CycleWeapon(1) // next
+weaponManager.CycleWeapon(-1) // previous
+weaponManager.ActiveWeaponName // string
+weaponManager.Unequip() // holster all
+```
+
+---
+
+## 8. WeaponViewmodel — `WeaponViewmodel`
+
+Add to a **weapon prefab** to define how it sits in the player's hands. WeaponManager reads from it on equip instead of using the global offset.
+
+**Fields:**
+- `positionOffset` — local position relative to weapon holder
+- `rotationOffset` — euler angles
+- `scale` — local scale
+
+Each weapon prefab gets its own values, so Gun Splat can sit differently from any future weapon.
+
+---
+
+## 9. Equippable Items — `BootsEffect`
+
+**Add `BootsEffect` to the Player** for the Bunny Hop Boots to work.
+
+Lives on the Player. Watches `Inventory.items` each frame for the boots' `isEquipped` state and adjusts `Player.maxStamina` accordingly.
+
+| State | Effect |
+|---|---|
+| Boots equipped | `maxStamina` → 200, stamina topped up |
+| Boots unequipped | `maxStamina` → 100, stamina clamped back down |
+
+The stamina bar stretches/shrinks live to reflect the new max.
+
+**Inspector fields:**
+- `bootsItemName` — must match the ItemDefinition `itemName` exactly (default `"Bunny Hop Boots"`)
+- `boostedMaxStamina` — default 200
+
+---
+
+## Setup Scripts (Editor Menu: OGG → Setup)
+
+### Create Gun Splat Weapon
+Creates:
+- `Assets/Prefabs/Weapons/GunSplat.prefab` — held weapon with `SimpleGun`
+- `Assets/Prefabs/Pickups/GunSplat_Pickup.prefab` — world pickup with spin/bob
+- `Assets/Items/GunSplat_Item.asset` — `ItemDefinition` (type = Weapon)
+
+### Create Bunny Hop Boots
+Creates:
+- `Assets/Items/BhopBoots_Item.asset` — `ItemDefinition` (type = Misc, isEquippable = true)
+- `Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab` — world pickup
+
+**After running either script:** drag the pickup prefab into your scene.
+
+---
+
+## Adding a New Weapon — Step by Step
+
+1. Create an `ItemDefinition` asset — set `type = Weapon`, assign your gun prefab to `weaponPrefab`
+2. Add `SimpleGun` to the gun prefab root
+3. Add `WeaponViewmodel` to the gun prefab — set position/rotation/scale or dial it in with the live Editor workflow
+4. Create a pickup: any mesh + `PickupItem` component + drag in the ItemDefinition
+5. Place the pickup in the scene
+
+---
+
+## Adding a New Equippable Item
+
+1. Create an `ItemDefinition` — set `type = Misc`, tick `isEquippable = true`
+2. Write a component that lives on the Player, watches `inventory.items` for `entry.isEquipped`, and applies/removes the effect
+3. Place a pickup in the scene referencing the definition
+
+---
+
+## Adding a Consumable
+
+```csharp
+Inventory inv = GetComponent();
+if (inv.HasItem("Medkit")) {
+ health += 50f;
+ inv.RemoveItem("Medkit");
+}
+```
+
+---
+
+## Known Rough Edges
+
+- **No weapon switch animation** — instant swap. Hook into `WeaponManager.EquipByIndex()` when ready.
+- **No save system** — inventory is runtime only, nothing persists between sessions.
+- **Starting gun** — any `SimpleGun` that's a child of the Camera at Start gets auto-registered but starts inactive. Player needs to find it as a pickup in the scene.
+- **Inventory is IMGUI** — not Canvas. Performance fine at this scale. All data logic is decoupled from drawing so a Canvas swap later would be straightforward.
+- **`SimpleGun` assumes it's always active** — non-SimpleGun weapons will slot-switch fine but need their own input handling to shoot.
+
+---
+
+## Controls Reference
+
+| Key | Action |
+|---|---|
+| W A S D | Move |
+| Left Shift | Sprint |
+| Space | Jump |
+| Left Click | Shoot |
+| Right Click | Context menu (inventory open) |
+| R | Reload |
+| E | Pick up nearby item |
+| I | Open / close inventory |
+| F | Equip selected item |
+| Scroll Wheel | Cycle weapons |
+| 1 – 9 | Equip weapon by slot |
+| Escape | Unlock cursor |
diff --git a/OGG.slnx b/OGG.slnx
index 20919c7..d629b17 100644
--- a/OGG.slnx
+++ b/OGG.slnx
@@ -1,3 +1,4 @@
+