commit 35790cbd34b1ec006fa1f068a8aee8a8cf94d0be Author: Valera <108022376+kekobka@users.noreply.github.com> Date: Wed Jun 11 20:19:35 2025 +0700 first commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..56b5fc1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,100 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.{cs,razor}] +indent_style = tab +indent_size = 4 +tab_size = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = true + + +#### C# Coding Conventions #### + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Code-block preferences +csharp_prefer_braces = true:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = no_change +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = true +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = true +csharp_space_between_parentheses = control_flow_statements +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88f7c75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ + +# This file describes files and paths that should not be tracked by Git version control +# https://git-scm.com/docs/gitignore + +# Auto-generated code editor files +.vs/* +.vscode/* +*.csproj +obj +bin +Properties/* +code/obj/* +code/Properties/* + +# Auto-generated asset related files +.sbox/* +*.generated.* +*.*_c +!*.shader_c +*.los +*.vpk +*launchSettings.json +*.sln + +*idea + +# Exported / standalone games +Exports/ diff --git a/Assets/Materials/library_material.vmat b/Assets/Materials/library_material.vmat new file mode 100644 index 0000000..8e21dcc --- /dev/null +++ b/Assets/Materials/library_material.vmat @@ -0,0 +1,22 @@ +"Layer0" +{ + "shader" "shaders/complex.shader" + "g_flAmbientOcclusionDirectDiffuse" "0.000000" + "g_flAmbientOcclusionDirectSpecular" "0.000000" + "TextureAmbientOcclusion" "materials/default/default_ao.tga" + "g_flModelTintAmount" "1.000000" + "g_vColorTint" "[1.000000 1.000000 1.000000 0.000000]" + "TextureColor" "materials/default/default_color.tga" + "g_flFadeExponent" "1.000000" + "g_bFogEnabled" "1" + "g_flDirectionalLightmapMinZ" "0.050000" + "g_flDirectionalLightmapStrength" "1.000000" + "g_flMetalness" "0.000000" + "TextureNormal" "materials/default/default_normal.tga" + "TextureRoughness" "materials/default/default_rough.tga" + "g_nScaleTexCoordUByModelScaleAxis" "0" + "g_nScaleTexCoordVByModelScaleAxis" "0" + "g_vTexCoordOffset" "[0.000 0.000]" + "g_vTexCoordScale" "[1.000 1.000]" + "g_vTexCoordScrollSpeed" "[0.000 0.000]" +} diff --git a/Assets/effects/metal_impact.prefab b/Assets/effects/metal_impact.prefab new file mode 100644 index 0000000..02f8ece --- /dev/null +++ b/Assets/effects/metal_impact.prefab @@ -0,0 +1,363 @@ +{ + "RootObject": { + "__guid": "bbc29007-9ae0-455d-9bde-62bae3dd88e9", + "__version": 1, + "Flags": 0, + "Name": "metal_impact", + "Position": "0,0,0", + "Rotation": "0,0,0,1", + "Scale": "1,1,1", + "Tags": "particles", + "Enabled": true, + "NetworkMode": 2, + "NetworkInterpolation": true, + "NetworkOrphaned": 0, + "OwnerTransfer": 1, + "Components": [ + { + "__type": "Sandbox.ParticleEffect", + "__guid": "4e3d505f-af05-4c8f-85ee-c5a264ff23b6", + "__enabled": true, + "__version": 1, + "Alpha": { + "Type": "Curve", + "Evaluation": "Life", + "CurveA": [ + { + "x": 0, + "y": 1, + "in": 0, + "out": 0, + "mode": "Mirrored" + }, + { + "x": 1, + "y": 0, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "CurveB": [ + { + "x": 0.5, + "y": 0.5, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "Constants": "1,0,0,0" + }, + "ApplyAlpha": false, + "ApplyColor": true, + "ApplyRotation": true, + "ApplyShape": true, + "Bounce": 1, + "Brightness": 1, + "Bumpiness": 0, + "Collision": false, + "CollisionIgnore": "player,vehicle", + "CollisionPrefab": [], + "CollisionPrefabAlign": false, + "CollisionPrefabChance": 1, + "CollisionPrefabRotation": 0, + "CollisionRadius": 1, + "Damping": 0, + "DieOnCollisionChance": 0, + "FollowerPrefab": [], + "FollowerPrefabChance": 1, + "FollowerPrefabKill": true, + "Force": true, + "ForceDirection": "0,0,-300", + "ForceScale": 1, + "ForceSpace": "World", + "Friction": 1, + "Gradient": { + "Type": "Constant", + "Evaluation": "Particle", + "GradientA": { + "blend": "Linear", + "color": [ + { + "t": 0, + "c": "0.79581,0.8093,0,1" + }, + { + "t": 0.33, + "c": "1,0.93333,0,1" + }, + { + "t": 0.66, + "c": "1,0.64706,0,1" + }, + { + "t": 1, + "c": "1,1,0,1" + }, + { + "t": 1, + "c": "1,0.55,0,1" + } + ], + "alpha": [ + { + "t": 0, + "a": 1 + }, + { + "t": 1, + "a": 1 + } + ] + }, + "GradientB": { + "blend": "Linear", + "color": [ + { + "t": 0.5, + "c": "1,1,1,1" + } + ], + "alpha": [] + }, + "ConstantA": "1,1,1,1", + "ConstantB": "1,0,0,1" + }, + "Lifetime": { + "Type": "Range", + "Evaluation": "Particle", + "CurveA": [ + { + "x": 0.5, + "y": 0.5, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "CurveB": [ + { + "x": 0.5, + "y": 0.5, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "Constants": "0.1,2,0,0" + }, + "MaxParticles": 500, + "OnComponentDestroy": null, + "OnComponentDisabled": null, + "OnComponentEnabled": null, + "OnComponentFixedUpdate": null, + "OnComponentStart": null, + "OnComponentUpdate": null, + "OnParticleCreated": null, + "OnParticleDestroyed": null, + "OrbitalForce": { + "X": 0, + "Y": 0, + "Z": 0 + }, + "OrbitalPull": 0, + "PerParticleTimeScale": 1, + "Pitch": 0, + "PreWarm": 0, + "PushStrength": 0, + "Roll": { + "Type": "Range", + "Evaluation": "Particle", + "CurveA": [ + { + "x": 0.5, + "y": 0.5, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "CurveB": [ + { + "x": 0.5, + "y": 0.5, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "Constants": "0,360,0,0" + }, + "Scale": { + "Type": "Curve", + "Evaluation": "Life", + "CurveA": [ + { + "x": 0, + "y": 0, + "in": -9.333329, + "out": 9.333329, + "mode": "Mirrored" + }, + { + "x": 0.12527578, + "y": 1, + "in": -0.056075174, + "out": 0.056075174, + "mode": "Mirrored" + }, + { + "x": 0.36042944, + "y": 0.40260965, + "in": 1.4467248, + "out": -1.4467248, + "mode": "Mirrored" + }, + { + "x": 1, + "y": 0, + "in": -0.39285707, + "out": 0.39285707, + "mode": "Mirrored" + } + ], + "CurveB": [ + { + "x": 0.5, + "y": 0.5, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "Constants": "1,0,0,0" + }, + "SequenceId": 0, + "SequenceSpeed": 1, + "SequenceTime": 1, + "SheetSequence": false, + "Space": "World", + "StartDelay": 0, + "StartVelocity": { + "Type": "Range", + "Evaluation": "Particle", + "CurveA": [ + { + "x": 0.5, + "y": 0.5, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "CurveB": [ + { + "x": 0.5, + "y": 0.5, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "Constants": "10,70,0,0" + }, + "Stretch": 0, + "TimeScale": 1, + "Timing": "GameTime", + "Tint": "1,1,1,1", + "UsePrefabFeature": false, + "Yaw": 0 + }, + { + "__type": "Sandbox.ParticleSpriteRenderer", + "__guid": "53350178-5acf-40f4-82a7-fc9cf56f9209", + "__enabled": true, + "Additive": false, + "Alignment": "LookAtCamera", + "BlurAmount": 1, + "BlurOpacity": 0.61, + "BlurSpacing": 1, + "DepthFeather": 0, + "FaceVelocity": false, + "FogStrength": 1, + "LeadingTrail": true, + "Lighting": true, + "MotionBlur": true, + "OnComponentDestroy": null, + "OnComponentDisabled": null, + "OnComponentEnabled": null, + "OnComponentFixedUpdate": null, + "OnComponentStart": null, + "OnComponentUpdate": null, + "Opaque": false, + "Pivot": "0.5,0.5", + "RenderOptions": { + "GameLayer": true, + "OverlayLayer": false, + "BloomLayer": false, + "AfterUILayer": false + }, + "RotationOffset": 0, + "Scale": 1, + "Shadows": false, + "SortMode": "Unsorted", + "Texture": "materials/particles/shapes/spark2.vtex" + }, + { + "__type": "Sandbox.ParticleConeEmitter", + "__guid": "e190fd95-5525-4287-9587-ad1a46170627", + "__enabled": true, + "Burst": 50, + "CenterBias": 0, + "CenterBiasVelocity": 0, + "ConeAngle": 30, + "ConeFar": 1, + "ConeNear": 1, + "Delay": 0, + "DestroyOnEnd": true, + "Duration": 1, + "InVolume": false, + "Loop": false, + "OnComponentDestroy": null, + "OnComponentDisabled": null, + "OnComponentEnabled": null, + "OnComponentFixedUpdate": null, + "OnComponentStart": null, + "OnComponentUpdate": null, + "OnEdge": false, + "Rate": 0, + "RateOverDistance": 0, + "VelocityMultiplier": 1, + "VelocityRandom": 0 + } + ], + "Children": [], + "__properties": { + "NetworkInterpolation": true, + "TimeScale": 1, + "WantsSystemScene": true, + "Metadata": {}, + "NavMesh": { + "Enabled": false, + "IncludeStaticBodies": true, + "IncludeKeyframedBodies": true, + "EditorAutoUpdate": true, + "AgentHeight": 64, + "AgentRadius": 16, + "AgentStepSize": 18, + "AgentMaxSlope": 40, + "ExcludedBodies": "", + "IncludedBodies": "" + } + }, + "__variables": [] + }, + "ResourceVersion": 2, + "ShowInMenu": false, + "MenuPath": null, + "MenuIcon": null, + "DontBreakAsTemplate": false, + "__references": [], + "__version": 2 +} \ No newline at end of file diff --git a/Assets/sounds/collisions/boat_heavy_1.wav b/Assets/sounds/collisions/boat_heavy_1.wav new file mode 100644 index 0000000..4d1ea41 Binary files /dev/null and b/Assets/sounds/collisions/boat_heavy_1.wav differ diff --git a/Assets/sounds/collisions/boat_heavy_2.wav b/Assets/sounds/collisions/boat_heavy_2.wav new file mode 100644 index 0000000..b6e3fb1 Binary files /dev/null and b/Assets/sounds/collisions/boat_heavy_2.wav differ diff --git a/Assets/sounds/collisions/boat_heavy_3.wav b/Assets/sounds/collisions/boat_heavy_3.wav new file mode 100644 index 0000000..ae6b42c Binary files /dev/null and b/Assets/sounds/collisions/boat_heavy_3.wav differ diff --git a/Assets/sounds/collisions/body_1.wav b/Assets/sounds/collisions/body_1.wav new file mode 100644 index 0000000..34d2ce2 Binary files /dev/null and b/Assets/sounds/collisions/body_1.wav differ diff --git a/Assets/sounds/collisions/body_2.wav b/Assets/sounds/collisions/body_2.wav new file mode 100644 index 0000000..68ae99c Binary files /dev/null and b/Assets/sounds/collisions/body_2.wav differ diff --git a/Assets/sounds/collisions/body_3.wav b/Assets/sounds/collisions/body_3.wav new file mode 100644 index 0000000..4c72022 Binary files /dev/null and b/Assets/sounds/collisions/body_3.wav differ diff --git a/Assets/sounds/collisions/body_4.wav b/Assets/sounds/collisions/body_4.wav new file mode 100644 index 0000000..16d940f Binary files /dev/null and b/Assets/sounds/collisions/body_4.wav differ diff --git a/Assets/sounds/collisions/body_5.wav b/Assets/sounds/collisions/body_5.wav new file mode 100644 index 0000000..f7e46e8 Binary files /dev/null and b/Assets/sounds/collisions/body_5.wav differ diff --git a/Assets/sounds/collisions/body_6.wav b/Assets/sounds/collisions/body_6.wav new file mode 100644 index 0000000..c5f6230 Binary files /dev/null and b/Assets/sounds/collisions/body_6.wav differ diff --git a/Assets/sounds/collisions/car_heavy.sound b/Assets/sounds/collisions/car_heavy.sound new file mode 100644 index 0000000..2dc6881 --- /dev/null +++ b/Assets/sounds/collisions/car_heavy.sound @@ -0,0 +1,59 @@ +{ + "UI": false, + "Volume": "1", + "Pitch": "0.8 1.2", + "Decibels": 70, + "SelectionMode": "Random", + "Sounds": [ + "sounds/collisions/car_heavy_6.vsnd", + "sounds/collisions/car_heavy_5.vsnd", + "sounds/collisions/car_heavy_4.vsnd", + "sounds/collisions/car_heavy_3.vsnd", + "sounds/collisions/car_heavy_2.vsnd", + "sounds/collisions/car_heavy_1.vsnd", + "sounds/collisions/car_heavy_7.vsnd", + "sounds/collisions/car_heavy_8.vsnd" + ], + "Occlusion": true, + "AirAbsorption": true, + "Transmission": true, + "OcclusionRadius": 64, + "DistanceAttenuation": true, + "Distance": 8500, + "Falloff": [ + { + "x": 0, + "y": 1, + "in": 0, + "out": -1.8, + "mode": "Mirrored" + }, + { + "x": 0.05, + "y": 0.22, + "in": 3.5, + "out": -3.5, + "mode": "Mirrored" + }, + { + "x": 0.2, + "y": 0.04, + "in": 0.16, + "out": -0.16, + "mode": "Mirrored" + }, + { + "x": 1, + "y": 0, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "DefaultMixer": { + "Name": "game", + "Id": "37db45ef-34ec-4948-b1d7-4509bc8e97e2" + }, + "__references": [], + "__version": 1 +} \ No newline at end of file diff --git a/Assets/sounds/collisions/car_heavy_1.wav b/Assets/sounds/collisions/car_heavy_1.wav new file mode 100644 index 0000000..4b1ff5b Binary files /dev/null and b/Assets/sounds/collisions/car_heavy_1.wav differ diff --git a/Assets/sounds/collisions/car_heavy_2.wav b/Assets/sounds/collisions/car_heavy_2.wav new file mode 100644 index 0000000..1477679 Binary files /dev/null and b/Assets/sounds/collisions/car_heavy_2.wav differ diff --git a/Assets/sounds/collisions/car_heavy_3.wav b/Assets/sounds/collisions/car_heavy_3.wav new file mode 100644 index 0000000..a0d8a6f Binary files /dev/null and b/Assets/sounds/collisions/car_heavy_3.wav differ diff --git a/Assets/sounds/collisions/car_heavy_4.wav b/Assets/sounds/collisions/car_heavy_4.wav new file mode 100644 index 0000000..9af2626 Binary files /dev/null and b/Assets/sounds/collisions/car_heavy_4.wav differ diff --git a/Assets/sounds/collisions/car_heavy_5.wav b/Assets/sounds/collisions/car_heavy_5.wav new file mode 100644 index 0000000..eaaa246 Binary files /dev/null and b/Assets/sounds/collisions/car_heavy_5.wav differ diff --git a/Assets/sounds/collisions/car_heavy_6.wav b/Assets/sounds/collisions/car_heavy_6.wav new file mode 100644 index 0000000..6996bbb Binary files /dev/null and b/Assets/sounds/collisions/car_heavy_6.wav differ diff --git a/Assets/sounds/collisions/car_heavy_7.wav b/Assets/sounds/collisions/car_heavy_7.wav new file mode 100644 index 0000000..e9ae405 Binary files /dev/null and b/Assets/sounds/collisions/car_heavy_7.wav differ diff --git a/Assets/sounds/collisions/car_heavy_8.wav b/Assets/sounds/collisions/car_heavy_8.wav new file mode 100644 index 0000000..44bc8b6 Binary files /dev/null and b/Assets/sounds/collisions/car_heavy_8.wav differ diff --git a/Assets/sounds/collisions/car_light.sound b/Assets/sounds/collisions/car_light.sound new file mode 100644 index 0000000..e57ffbf --- /dev/null +++ b/Assets/sounds/collisions/car_light.sound @@ -0,0 +1,55 @@ +{ + "UI": false, + "Volume": "1", + "Pitch": "0.8 1.2", + "Decibels": 70, + "SelectionMode": "Random", + "Sounds": [ + "sounds/collisions/car_light_4.vsnd", + "sounds/collisions/car_light_3.vsnd", + "sounds/collisions/car_light_2.vsnd", + "sounds/collisions/car_light_1.vsnd" + ], + "Occlusion": true, + "AirAbsorption": true, + "Transmission": true, + "OcclusionRadius": 64, + "DistanceAttenuation": true, + "Distance": 8500, + "Falloff": [ + { + "x": 0, + "y": 1, + "in": 0, + "out": -1.8, + "mode": "Mirrored" + }, + { + "x": 0.05, + "y": 0.22, + "in": 3.5, + "out": -3.5, + "mode": "Mirrored" + }, + { + "x": 0.2, + "y": 0.04, + "in": 0.16, + "out": -0.16, + "mode": "Mirrored" + }, + { + "x": 1, + "y": 0, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "DefaultMixer": { + "Name": "game", + "Id": "37db45ef-34ec-4948-b1d7-4509bc8e97e2" + }, + "__references": [], + "__version": 1 +} \ No newline at end of file diff --git a/Assets/sounds/collisions/car_light_1.wav b/Assets/sounds/collisions/car_light_1.wav new file mode 100644 index 0000000..3b93dd1 Binary files /dev/null and b/Assets/sounds/collisions/car_light_1.wav differ diff --git a/Assets/sounds/collisions/car_light_2.wav b/Assets/sounds/collisions/car_light_2.wav new file mode 100644 index 0000000..19e0f28 Binary files /dev/null and b/Assets/sounds/collisions/car_light_2.wav differ diff --git a/Assets/sounds/collisions/car_light_3.wav b/Assets/sounds/collisions/car_light_3.wav new file mode 100644 index 0000000..9531932 Binary files /dev/null and b/Assets/sounds/collisions/car_light_3.wav differ diff --git a/Assets/sounds/collisions/car_light_4.wav b/Assets/sounds/collisions/car_light_4.wav new file mode 100644 index 0000000..ee7a275 Binary files /dev/null and b/Assets/sounds/collisions/car_light_4.wav differ diff --git a/Assets/sounds/collisions/land_on_water_1.wav b/Assets/sounds/collisions/land_on_water_1.wav new file mode 100644 index 0000000..ff1f873 Binary files /dev/null and b/Assets/sounds/collisions/land_on_water_1.wav differ diff --git a/Assets/sounds/collisions/land_on_water_2.wav b/Assets/sounds/collisions/land_on_water_2.wav new file mode 100644 index 0000000..9384467 Binary files /dev/null and b/Assets/sounds/collisions/land_on_water_2.wav differ diff --git a/Assets/sounds/collisions/land_on_water_3.wav b/Assets/sounds/collisions/land_on_water_3.wav new file mode 100644 index 0000000..b4bbdf5 Binary files /dev/null and b/Assets/sounds/collisions/land_on_water_3.wav differ diff --git a/Assets/sounds/collisions/land_on_water_4.wav b/Assets/sounds/collisions/land_on_water_4.wav new file mode 100644 index 0000000..5116678 Binary files /dev/null and b/Assets/sounds/collisions/land_on_water_4.wav differ diff --git a/Assets/sounds/collisions/metal_scrape.sound b/Assets/sounds/collisions/metal_scrape.sound new file mode 100644 index 0000000..ab44859 --- /dev/null +++ b/Assets/sounds/collisions/metal_scrape.sound @@ -0,0 +1,53 @@ +{ + "UI": false, + "Volume": "1", + "Pitch": "0.8 1.2", + "Decibels": 70, + "SelectionMode": "Random", + "Sounds": [ + "sounds/collisions/metal_scrape_1.vsnd", + "sounds/collisions/metal_scrape_2.vsnd" + ], + "Occlusion": true, + "AirAbsorption": true, + "Transmission": true, + "OcclusionRadius": 64, + "DistanceAttenuation": true, + "Distance": 8500, + "Falloff": [ + { + "x": 0, + "y": 1, + "in": 0, + "out": -1.8, + "mode": "Mirrored" + }, + { + "x": 0.05, + "y": 0.22, + "in": 3.5, + "out": -3.5, + "mode": "Mirrored" + }, + { + "x": 0.2, + "y": 0.04, + "in": 0.16, + "out": -0.16, + "mode": "Mirrored" + }, + { + "x": 1, + "y": 0, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "DefaultMixer": { + "Name": "music", + "Id": "ee991a4a-8016-40e5-93e6-cbf0d964a401" + }, + "__references": [], + "__version": 1 +} \ No newline at end of file diff --git a/Assets/sounds/collisions/metal_scrape_1.wav b/Assets/sounds/collisions/metal_scrape_1.wav new file mode 100644 index 0000000..6f0c659 Binary files /dev/null and b/Assets/sounds/collisions/metal_scrape_1.wav differ diff --git a/Assets/sounds/collisions/metal_scrape_2.wav b/Assets/sounds/collisions/metal_scrape_2.wav new file mode 100644 index 0000000..8263854 Binary files /dev/null and b/Assets/sounds/collisions/metal_scrape_2.wav differ diff --git a/Assets/sounds/streams/pickup/high.wav b/Assets/sounds/streams/pickup/high.wav new file mode 100644 index 0000000..bddbc73 Binary files /dev/null and b/Assets/sounds/streams/pickup/high.wav differ diff --git a/Assets/sounds/streams/pickup/high.wav.meta b/Assets/sounds/streams/pickup/high.wav.meta new file mode 100644 index 0000000..59c3d77 --- /dev/null +++ b/Assets/sounds/streams/pickup/high.wav.meta @@ -0,0 +1,8 @@ +{ + "loop": true, + "start": 0, + "end": 0, + "rate": 44100, + "compress": false, + "bitrate": 256 +} \ No newline at end of file diff --git a/Assets/sounds/streams/pickup/idle.wav b/Assets/sounds/streams/pickup/idle.wav new file mode 100644 index 0000000..42f359a Binary files /dev/null and b/Assets/sounds/streams/pickup/idle.wav differ diff --git a/Assets/sounds/streams/pickup/idle.wav.meta b/Assets/sounds/streams/pickup/idle.wav.meta new file mode 100644 index 0000000..59c3d77 --- /dev/null +++ b/Assets/sounds/streams/pickup/idle.wav.meta @@ -0,0 +1,8 @@ +{ + "loop": true, + "start": 0, + "end": 0, + "rate": 44100, + "compress": false, + "bitrate": 256 +} \ No newline at end of file diff --git a/Assets/sounds/streams/pickup/low.wav b/Assets/sounds/streams/pickup/low.wav new file mode 100644 index 0000000..35ce72f Binary files /dev/null and b/Assets/sounds/streams/pickup/low.wav differ diff --git a/Assets/sounds/streams/pickup/low.wav.meta b/Assets/sounds/streams/pickup/low.wav.meta new file mode 100644 index 0000000..59c3d77 --- /dev/null +++ b/Assets/sounds/streams/pickup/low.wav.meta @@ -0,0 +1,8 @@ +{ + "loop": true, + "start": 0, + "end": 0, + "rate": 44100, + "compress": false, + "bitrate": 256 +} \ No newline at end of file diff --git a/Assets/sounds/streams/pickup/pickup.engstr b/Assets/sounds/streams/pickup/pickup.engstr new file mode 100644 index 0000000..3acc32b --- /dev/null +++ b/Assets/sounds/streams/pickup/pickup.engstr @@ -0,0 +1,139 @@ +{ + "Parameters": { + "Pitch": 1, + "Volume": 1, + "FadeDist": 1500, + "RedlineFrequency": 55, + "RedlineStrength": 0.2, + "WobbleFrequency": 25, + "WobbleStrength": 0.13 + }, + "Layers": { + "rpm_high": { + "AudioPath": "sounds/streams/pickup/high.vsnd", + "UseRedline": true, + "Controllers": [ + [ + "Throttle", + 0, + 1, + "Volume", + 0, + 1 + ], + [ + "RpmFraction", + 0, + 1, + "Pitch", + 0.49, + 1.05 + ], + [ + "RpmFraction", + 0.2, + 0.75, + "Volume", + 0, + 1 + ] + ] + }, + "idle": { + "AudioPath": "sounds/streams/pickup/idle.vsnd", + "UseRedline": true, + "Controllers": [ + [ + "Throttle", + 0, + 1, + "Volume", + 1, + 0 + ], + [ + "RpmFraction", + 0, + 0.3, + "Volume", + 1, + 0 + ], + [ + "RpmFraction", + 0, + 0.38, + "Pitch", + 1, + 2 + ] + ] + }, + "rpm_low": { + "AudioPath": "sounds/streams/pickup/low.vsnd", + "UseRedline": true, + "Controllers": [ + [ + "Throttle", + 0, + 1, + "Volume", + 0, + 1 + ], + [ + "RpmFraction", + 0, + 1, + "Pitch", + 0.75, + 1.59 + ], + [ + "RpmFraction", + 0.3, + 0.85, + "Volume", + 1, + 0 + ] + ] + }, + "throttle_low": { + "AudioPath": "sounds/streams/pickup/throttle_low.vsnd", + "UseRedline": true, + "Controllers": [ + [ + "Throttle", + 0.35, + 1, + "Volume", + 0.85, + 0 + ], + [ + "RpmFraction", + 0, + 1, + "Pitch", + 0.63, + 1.33 + ], + [ + "RpmFraction", + 0, + 0.2, + "Volume", + 0, + 1 + ] + ] + } + }, + "Parent": null, + "Throttle": 0, + "RpmFraction": 0, + "IsRedlining": false, + "__references": [], + "__version": 0 +} \ No newline at end of file diff --git a/Assets/sounds/streams/pickup/throttle_low.wav b/Assets/sounds/streams/pickup/throttle_low.wav new file mode 100644 index 0000000..8d689db Binary files /dev/null and b/Assets/sounds/streams/pickup/throttle_low.wav differ diff --git a/Assets/sounds/streams/pickup/throttle_low.wav.meta b/Assets/sounds/streams/pickup/throttle_low.wav.meta new file mode 100644 index 0000000..59c3d77 --- /dev/null +++ b/Assets/sounds/streams/pickup/throttle_low.wav.meta @@ -0,0 +1,8 @@ +{ + "loop": true, + "start": 0, + "end": 0, + "rate": 44100, + "compress": false, + "bitrate": 256 +} \ No newline at end of file diff --git a/Assets/sounds/streams/v8_c63/Acc_1560_ADPCM.wav b/Assets/sounds/streams/v8_c63/Acc_1560_ADPCM.wav new file mode 100644 index 0000000..f76c552 Binary files /dev/null and b/Assets/sounds/streams/v8_c63/Acc_1560_ADPCM.wav differ diff --git a/Assets/sounds/streams/v8_c63/Acc_2197_ADPCM.wav b/Assets/sounds/streams/v8_c63/Acc_2197_ADPCM.wav new file mode 100644 index 0000000..cd2f538 Binary files /dev/null and b/Assets/sounds/streams/v8_c63/Acc_2197_ADPCM.wav differ diff --git a/Assets/sounds/streams/v8_c63/Acc_4130_ADPCM.wav b/Assets/sounds/streams/v8_c63/Acc_4130_ADPCM.wav new file mode 100644 index 0000000..e80dc70 Binary files /dev/null and b/Assets/sounds/streams/v8_c63/Acc_4130_ADPCM.wav differ diff --git a/Assets/sounds/streams/v8_c63/Acc_6803_ADPCM.wav b/Assets/sounds/streams/v8_c63/Acc_6803_ADPCM.wav new file mode 100644 index 0000000..1af400a Binary files /dev/null and b/Assets/sounds/streams/v8_c63/Acc_6803_ADPCM.wav differ diff --git a/Assets/sounds/streams/v8_c63/Dec_3951_ADPCM.wav b/Assets/sounds/streams/v8_c63/Dec_3951_ADPCM.wav new file mode 100644 index 0000000..c054bcd Binary files /dev/null and b/Assets/sounds/streams/v8_c63/Dec_3951_ADPCM.wav differ diff --git a/Assets/sounds/streams/v8_c63/acc_1560_adpcm.wav.meta b/Assets/sounds/streams/v8_c63/acc_1560_adpcm.wav.meta new file mode 100644 index 0000000..59c3d77 --- /dev/null +++ b/Assets/sounds/streams/v8_c63/acc_1560_adpcm.wav.meta @@ -0,0 +1,8 @@ +{ + "loop": true, + "start": 0, + "end": 0, + "rate": 44100, + "compress": false, + "bitrate": 256 +} \ No newline at end of file diff --git a/Assets/sounds/streams/v8_c63/acc_2197_adpcm.wav.meta b/Assets/sounds/streams/v8_c63/acc_2197_adpcm.wav.meta new file mode 100644 index 0000000..59c3d77 --- /dev/null +++ b/Assets/sounds/streams/v8_c63/acc_2197_adpcm.wav.meta @@ -0,0 +1,8 @@ +{ + "loop": true, + "start": 0, + "end": 0, + "rate": 44100, + "compress": false, + "bitrate": 256 +} \ No newline at end of file diff --git a/Assets/sounds/streams/v8_c63/acc_4130_adpcm.wav.meta b/Assets/sounds/streams/v8_c63/acc_4130_adpcm.wav.meta new file mode 100644 index 0000000..59c3d77 --- /dev/null +++ b/Assets/sounds/streams/v8_c63/acc_4130_adpcm.wav.meta @@ -0,0 +1,8 @@ +{ + "loop": true, + "start": 0, + "end": 0, + "rate": 44100, + "compress": false, + "bitrate": 256 +} \ No newline at end of file diff --git a/Assets/sounds/streams/v8_c63/acc_6803_adpcm.wav.meta b/Assets/sounds/streams/v8_c63/acc_6803_adpcm.wav.meta new file mode 100644 index 0000000..59c3d77 --- /dev/null +++ b/Assets/sounds/streams/v8_c63/acc_6803_adpcm.wav.meta @@ -0,0 +1,8 @@ +{ + "loop": true, + "start": 0, + "end": 0, + "rate": 44100, + "compress": false, + "bitrate": 256 +} \ No newline at end of file diff --git a/Assets/sounds/streams/v8_c63/dec_3951_adpcm.wav.meta b/Assets/sounds/streams/v8_c63/dec_3951_adpcm.wav.meta new file mode 100644 index 0000000..59c3d77 --- /dev/null +++ b/Assets/sounds/streams/v8_c63/dec_3951_adpcm.wav.meta @@ -0,0 +1,8 @@ +{ + "loop": true, + "start": 0, + "end": 0, + "rate": 44100, + "compress": false, + "bitrate": 256 +} \ No newline at end of file diff --git a/Assets/sounds/streams/v8_c63/v8_c63.engstr b/Assets/sounds/streams/v8_c63/v8_c63.engstr new file mode 100644 index 0000000..f8d8119 --- /dev/null +++ b/Assets/sounds/streams/v8_c63/v8_c63.engstr @@ -0,0 +1,174 @@ +{ + "Parameters": { + "Pitch": 1, + "Volume": 1, + "FadeDist": 1500, + "RedlineFrequency": 55, + "RedlineStrength": 0.2, + "WobbleFrequency": 25, + "WobbleStrength": 0.13 + }, + "Layers": { + "mid": { + "AudioPath": "sounds/streams/v8_c63/acc_4130_adpcm.vsnd", + "UseRedline": true, + "Controllers": [ + [ + "Throttle", + 0, + 1, + "Volume", + 0.1, + 1.1 + ], + [ + "RpmFraction", + 0, + 1, + "Pitch", + 0.71, + 1.26 + ], + [ + "RpmFraction", + 0.79, + 1, + "Volume", + 1, + 0 + ], + [ + "RpmFraction", + 0.05, + 0.5, + "Volume", + 0, + 1.1 + ] + ] + }, + "throttle_low": { + "AudioPath": "sounds/streams/v8_c63/dec_3951_adpcm.vsnd", + "UseRedline": true, + "Controllers": [ + [ + "RpmFraction", + 0, + 1, + "Pitch", + 0.74, + 1.3 + ], + [ + "Throttle", + 0, + 1, + "Volume", + 1, + 0.1 + ], + [ + "RpmFraction", + 0, + 0.25, + "Volume", + 0, + 0.8 + ] + ] + }, + "low": { + "AudioPath": "sounds/streams/v8_c63/acc_2197_adpcm.vsnd", + "UseRedline": true, + "Controllers": [ + [ + "Throttle", + 0, + 1, + "Volume", + 0, + 1.05 + ], + [ + "RpmFraction", + 0, + 1, + "Pitch", + 0.9, + 1.59 + ], + [ + "RpmFraction", + 0.2, + 0.75, + "Volume", + 1, + 0 + ] + ] + }, + "idle": { + "AudioPath": "sounds/streams/v8_c63/acc_1560_adpcm.vsnd", + "UseRedline": true, + "Controllers": [ + [ + "Throttle", + 0, + 1, + "Volume", + 1, + 0 + ], + [ + "RpmFraction", + 0, + 0.4, + "Volume", + 1.2, + 0 + ], + [ + "RpmFraction", + 0, + 0.4, + "Pitch", + 1, + 2 + ] + ] + }, + "high": { + "AudioPath": "sounds/streams/v8_c63/acc_6803_adpcm.vsnd", + "UseRedline": true, + "Controllers": [ + [ + "Throttle", + 0, + 1, + "Volume", + 0.35, + 1 + ], + [ + "RpmFraction", + 0.5, + 1, + "Volume", + 0, + 1.15 + ], + [ + "RpmFraction", + 0, + 1, + "Pitch", + 0.57, + 1 + ] + ] + } + }, + "Paused": false, + "__references": [], + "__version": 0 +} \ No newline at end of file diff --git a/Assets/sounds/suspension/compress_heavy.sound b/Assets/sounds/suspension/compress_heavy.sound new file mode 100644 index 0000000..7d08ab8 --- /dev/null +++ b/Assets/sounds/suspension/compress_heavy.sound @@ -0,0 +1,54 @@ +{ + "UI": false, + "Volume": "1", + "Pitch": "1 1.2", + "Decibels": 70, + "SelectionMode": "Random", + "Sounds": [ + "sounds/suspension/compress_heavy_3.vsnd", + "sounds/suspension/compress_heavy_2.vsnd", + "sounds/suspension/compress_heavy_1.vsnd" + ], + "Occlusion": false, + "AirAbsorption": true, + "Transmission": true, + "OcclusionRadius": 64, + "DistanceAttenuation": true, + "Distance": 8500, + "Falloff": [ + { + "x": 0, + "y": 1, + "in": 0, + "out": -1.8, + "mode": "Mirrored" + }, + { + "x": 0.05, + "y": 0.22, + "in": 3.5, + "out": -3.5, + "mode": "Mirrored" + }, + { + "x": 0.2, + "y": 0.04, + "in": 0.16, + "out": -0.16, + "mode": "Mirrored" + }, + { + "x": 1, + "y": 0, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "DefaultMixer": { + "Name": "game", + "Id": "37db45ef-34ec-4948-b1d7-4509bc8e97e2" + }, + "__references": [], + "__version": 1 +} \ No newline at end of file diff --git a/Assets/sounds/suspension/compress_heavy_1.wav b/Assets/sounds/suspension/compress_heavy_1.wav new file mode 100644 index 0000000..eb48439 Binary files /dev/null and b/Assets/sounds/suspension/compress_heavy_1.wav differ diff --git a/Assets/sounds/suspension/compress_heavy_2.wav b/Assets/sounds/suspension/compress_heavy_2.wav new file mode 100644 index 0000000..7c57f2e Binary files /dev/null and b/Assets/sounds/suspension/compress_heavy_2.wav differ diff --git a/Assets/sounds/suspension/compress_heavy_3.wav b/Assets/sounds/suspension/compress_heavy_3.wav new file mode 100644 index 0000000..f810b3f Binary files /dev/null and b/Assets/sounds/suspension/compress_heavy_3.wav differ diff --git a/Assets/sounds/suspension/compress_truck.sound b/Assets/sounds/suspension/compress_truck.sound new file mode 100644 index 0000000..377a27d --- /dev/null +++ b/Assets/sounds/suspension/compress_truck.sound @@ -0,0 +1,55 @@ +{ + "UI": false, + "Volume": "1", + "Pitch": "1", + "Decibels": 70, + "SelectionMode": "Random", + "Sounds": [ + "sounds/suspension/compress_truck_4.vsnd", + "sounds/suspension/compress_truck_3.vsnd", + "sounds/suspension/compress_truck_2.vsnd", + "sounds/suspension/compress_truck_1.vsnd" + ], + "Occlusion": true, + "AirAbsorption": true, + "Transmission": true, + "OcclusionRadius": 64, + "DistanceAttenuation": true, + "Distance": 15000, + "Falloff": [ + { + "x": 0, + "y": 1, + "in": 0, + "out": -1.8, + "mode": "Mirrored" + }, + { + "x": 0.05, + "y": 0.22, + "in": 3.5, + "out": -3.5, + "mode": "Mirrored" + }, + { + "x": 0.2, + "y": 0.04, + "in": 0.16, + "out": -0.16, + "mode": "Mirrored" + }, + { + "x": 1, + "y": 0, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "DefaultMixer": { + "Name": "unknown", + "Id": "00000000-0000-0000-0000-000000000000" + }, + "__references": [], + "__version": 1 +} \ No newline at end of file diff --git a/Assets/sounds/suspension/compress_truck_1.wav b/Assets/sounds/suspension/compress_truck_1.wav new file mode 100644 index 0000000..92d5916 Binary files /dev/null and b/Assets/sounds/suspension/compress_truck_1.wav differ diff --git a/Assets/sounds/suspension/compress_truck_2.wav b/Assets/sounds/suspension/compress_truck_2.wav new file mode 100644 index 0000000..0d08711 Binary files /dev/null and b/Assets/sounds/suspension/compress_truck_2.wav differ diff --git a/Assets/sounds/suspension/compress_truck_3.wav b/Assets/sounds/suspension/compress_truck_3.wav new file mode 100644 index 0000000..d99351e Binary files /dev/null and b/Assets/sounds/suspension/compress_truck_3.wav differ diff --git a/Assets/sounds/suspension/compress_truck_4.wav b/Assets/sounds/suspension/compress_truck_4.wav new file mode 100644 index 0000000..cc54ad3 Binary files /dev/null and b/Assets/sounds/suspension/compress_truck_4.wav differ diff --git a/Assets/sounds/suspension/pneumatic_down.sound b/Assets/sounds/suspension/pneumatic_down.sound new file mode 100644 index 0000000..b0639a1 --- /dev/null +++ b/Assets/sounds/suspension/pneumatic_down.sound @@ -0,0 +1,54 @@ +{ + "UI": false, + "Volume": "1", + "Pitch": "1 1.2", + "Decibels": 70, + "SelectionMode": "Random", + "Sounds": [ + "sounds/suspension/pneumatic_down_3.vsnd", + "sounds/suspension/pneumatic_down_2.vsnd", + "sounds/suspension/pneumatic_down_1.vsnd" + ], + "Occlusion": true, + "AirAbsorption": true, + "Transmission": true, + "OcclusionRadius": 64, + "DistanceAttenuation": true, + "Distance": 7000, + "Falloff": [ + { + "x": 0, + "y": 1, + "in": 0, + "out": -1.8, + "mode": "Mirrored" + }, + { + "x": 0.05, + "y": 0.22, + "in": 3.5, + "out": -3.5, + "mode": "Mirrored" + }, + { + "x": 0.2, + "y": 0.04, + "in": 0.16, + "out": -0.16, + "mode": "Mirrored" + }, + { + "x": 1, + "y": 0, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "DefaultMixer": { + "Name": "game", + "Id": "37db45ef-34ec-4948-b1d7-4509bc8e97e2" + }, + "__references": [], + "__version": 1 +} \ No newline at end of file diff --git a/Assets/sounds/suspension/pneumatic_down_1.wav b/Assets/sounds/suspension/pneumatic_down_1.wav new file mode 100644 index 0000000..ccb2b8c Binary files /dev/null and b/Assets/sounds/suspension/pneumatic_down_1.wav differ diff --git a/Assets/sounds/suspension/pneumatic_down_2.wav b/Assets/sounds/suspension/pneumatic_down_2.wav new file mode 100644 index 0000000..447d9e7 Binary files /dev/null and b/Assets/sounds/suspension/pneumatic_down_2.wav differ diff --git a/Assets/sounds/suspension/pneumatic_down_3.wav b/Assets/sounds/suspension/pneumatic_down_3.wav new file mode 100644 index 0000000..4111292 Binary files /dev/null and b/Assets/sounds/suspension/pneumatic_down_3.wav differ diff --git a/Assets/sounds/suspension/pneumatic_up.sound b/Assets/sounds/suspension/pneumatic_up.sound new file mode 100644 index 0000000..8a65967 --- /dev/null +++ b/Assets/sounds/suspension/pneumatic_up.sound @@ -0,0 +1,54 @@ +{ + "UI": false, + "Volume": "1", + "Pitch": "1 1.2", + "Decibels": 70, + "SelectionMode": "Random", + "Sounds": [ + "sounds/suspension/pneumatic_up_3.vsnd", + "sounds/suspension/pneumatic_up_2.vsnd", + "sounds/suspension/pneumatic_up_1.vsnd" + ], + "Occlusion": true, + "AirAbsorption": true, + "Transmission": true, + "OcclusionRadius": 64, + "DistanceAttenuation": true, + "Distance": 7000, + "Falloff": [ + { + "x": 0, + "y": 1, + "in": 0, + "out": -1.8, + "mode": "Mirrored" + }, + { + "x": 0.05, + "y": 0.22, + "in": 3.5, + "out": -3.5, + "mode": "Mirrored" + }, + { + "x": 0.2, + "y": 0.04, + "in": 0.16, + "out": -0.16, + "mode": "Mirrored" + }, + { + "x": 1, + "y": 0, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "DefaultMixer": { + "Name": "game", + "Id": "37db45ef-34ec-4948-b1d7-4509bc8e97e2" + }, + "__references": [], + "__version": 1 +} \ No newline at end of file diff --git a/Assets/sounds/suspension/pneumatic_up_1.wav b/Assets/sounds/suspension/pneumatic_up_1.wav new file mode 100644 index 0000000..22ba5a1 Binary files /dev/null and b/Assets/sounds/suspension/pneumatic_up_1.wav differ diff --git a/Assets/sounds/suspension/pneumatic_up_2.wav b/Assets/sounds/suspension/pneumatic_up_2.wav new file mode 100644 index 0000000..211d7f3 Binary files /dev/null and b/Assets/sounds/suspension/pneumatic_up_2.wav differ diff --git a/Assets/sounds/suspension/pneumatic_up_3.wav b/Assets/sounds/suspension/pneumatic_up_3.wav new file mode 100644 index 0000000..53b85ce Binary files /dev/null and b/Assets/sounds/suspension/pneumatic_up_3.wav differ diff --git a/Assets/sounds/suspension/stress.sound b/Assets/sounds/suspension/stress.sound new file mode 100644 index 0000000..b919fa7 --- /dev/null +++ b/Assets/sounds/suspension/stress.sound @@ -0,0 +1,54 @@ +{ + "UI": false, + "Volume": "1", + "Pitch": "1 1.1", + "Decibels": 70, + "SelectionMode": "Random", + "Sounds": [ + "sounds/suspension/stress_3.vsnd", + "sounds/suspension/stress_2.vsnd", + "sounds/suspension/stress_1.vsnd" + ], + "Occlusion": true, + "AirAbsorption": true, + "Transmission": true, + "OcclusionRadius": 64, + "DistanceAttenuation": true, + "Distance": 8500, + "Falloff": [ + { + "x": 0, + "y": 1, + "in": 0, + "out": -1.8, + "mode": "Mirrored" + }, + { + "x": 0.05, + "y": 0.22, + "in": 3.5, + "out": -3.5, + "mode": "Mirrored" + }, + { + "x": 0.2, + "y": 0.04, + "in": 0.16, + "out": -0.16, + "mode": "Mirrored" + }, + { + "x": 1, + "y": 0, + "in": 0, + "out": 0, + "mode": "Mirrored" + } + ], + "DefaultMixer": { + "Name": "game", + "Id": "37db45ef-34ec-4948-b1d7-4509bc8e97e2" + }, + "__references": [], + "__version": 1 +} \ No newline at end of file diff --git a/Assets/sounds/suspension/stress_1.wav b/Assets/sounds/suspension/stress_1.wav new file mode 100644 index 0000000..438633c Binary files /dev/null and b/Assets/sounds/suspension/stress_1.wav differ diff --git a/Assets/sounds/suspension/stress_2.wav b/Assets/sounds/suspension/stress_2.wav new file mode 100644 index 0000000..706277f Binary files /dev/null and b/Assets/sounds/suspension/stress_2.wav differ diff --git a/Assets/sounds/suspension/stress_3.wav b/Assets/sounds/suspension/stress_3.wav new file mode 100644 index 0000000..00e36e9 Binary files /dev/null and b/Assets/sounds/suspension/stress_3.wav differ diff --git a/Code/Base/VeloXBase.Input.cs b/Code/Base/VeloXBase.Input.cs new file mode 100644 index 0000000..ec6fee8 --- /dev/null +++ b/Code/Base/VeloXBase.Input.cs @@ -0,0 +1,11 @@ +using Sandbox; + +namespace VeloX; + +public abstract partial class VeloXBase +{ + //[Property, Feature( "Input" ), InputAction] string ThrottleInput { get; set; } = "Forward"; + [Property, Feature( "Input" )] internal InputResolver Input { get; set; } = new(); + [Property, Feature( "Input" )] public GameObject Driver { get => Input.Driver; set => Input.Driver = value; } + +} diff --git a/Code/Base/VeloXBase.Phys.cs b/Code/Base/VeloXBase.Phys.cs new file mode 100644 index 0000000..35e9f2e --- /dev/null +++ b/Code/Base/VeloXBase.Phys.cs @@ -0,0 +1,43 @@ +using Sandbox; + +namespace VeloX; + +public abstract partial class VeloXBase +{ + + private Vector3 linForce; + private Vector3 angForce; + + private void PhysicsSimulate() + { + var drag = AngularDrag; + var mass = Body.Mass; + var angVel = Body.AngularVelocity; + + linForce.x = 0; + linForce.y = 0; + linForce.z = 0; + + angForce.x = angVel.x * drag.x * mass; + angForce.y = angVel.y * drag.y * mass; + angForce.z = angVel.z * drag.z * mass; + + + if ( Wheels.Count > 0 ) + { + Vector3 vehVel = Body.Velocity; + Vector3 vehAngVel = Body.AngularVelocity; + + var dt = Time.Delta; + foreach ( var v in Wheels ) + v.DoPhysics( this, ref vehVel, ref vehAngVel, ref linForce, ref angForce, in dt ); + + Body.Velocity = vehVel; + Body.AngularVelocity = vehAngVel; + } + + + Body.ApplyForce( linForce ); + Body.ApplyTorque( angForce ); + } +} diff --git a/Code/Base/VeloXBase.Sound.cs b/Code/Base/VeloXBase.Sound.cs new file mode 100644 index 0000000..a5b0942 --- /dev/null +++ b/Code/Base/VeloXBase.Sound.cs @@ -0,0 +1,62 @@ +using Sandbox; +using Sandbox.Audio; +using System; +using System.Text.Json.Serialization; +using VeloX.Audio; +using static Sandbox.Utility.Noise; +using static VeloX.EngineStream; + +namespace VeloX; + + +public abstract partial class VeloXBase : Component, Component.ICollisionListener +{ + + [Property, Feature( "Effects" ), Group( "Sound" )] + public SoundEvent SuspensionHeavySound { get; protected set; } = ResourceLibrary.Get( "sounds/suspension/compress_heavy.sound" ); + + [Property, Feature( "Effects" ), Group( "Sound" )] + public SoundEvent SuspensionDownSound { get; protected set; } = ResourceLibrary.Get( "sounds/suspension/pneumatic_down.sound" ); + + [Property, Feature( "Effects" ), Group( "Sound" )] + public SoundEvent SuspensionUpSound { get; protected set; } = ResourceLibrary.Get( "sounds/suspension/pneumatic_up.sound" ); + + [Property, Feature( "Effects" ), Group( "Sound" )] + public SoundEvent SoftCollisionSound { get; protected set; } = ResourceLibrary.Get( "sounds/collisions/car_light.sound" ); + + [Property, Feature( "Effects" ), Group( "Sound" )] + public SoundEvent HardCollisionSound { get; protected set; } = ResourceLibrary.Get( "sounds/collisions/car_heavy.sound" ); + + [Property, Feature( "Effects" ), Group( "Sound" )] + public SoundEvent VehicleScrapeSound { get; protected set; } = ResourceLibrary.Get( "sounds/collisions/metal_scrape.sound" ); + + [Property, Feature( "Effects" ), Group( "Particle" )] + public GameObject MetalImpactEffect { get; protected set; } = GameObject.GetPrefab( "effects/metal_impact.prefab" ); + void ICollisionListener.OnCollisionStart( Collision collision ) + { + + var speed = MathF.Abs( collision.Contact.NormalSpeed ); + var surfaceNormal = collision.Contact.Normal; + if ( speed < 100 ) + return; + + var isHardHit = speed > 300; + var volume = Math.Clamp( speed / 400f, 0, 1 ); + var sound = Sound.Play( SoftCollisionSound, WorldPosition ); + sound.Volume = volume; + + var a = MetalImpactEffect.Clone( new CloneConfig() { StartEnabled = true, Transform = new( collision.Contact.Point, surfaceNormal.Cross( surfaceNormal ).EulerAngles ) } ); + a.Components.Get().Burst = speed / 2; + + if ( isHardHit ) + { + var hardSound = Sound.Play( HardCollisionSound, WorldPosition ); + hardSound.Volume = volume; + } + else if ( surfaceNormal.Dot( -collision.Contact.Speed.Normal ) < 0.5f ) + { + var scrapSound = Sound.Play( VehicleScrapeSound, WorldPosition ); + scrapSound.Volume = 0.4f; + } + } +} diff --git a/Code/Base/VeloXBase.Wheel.cs b/Code/Base/VeloXBase.Wheel.cs new file mode 100644 index 0000000..79dbfdc --- /dev/null +++ b/Code/Base/VeloXBase.Wheel.cs @@ -0,0 +1,20 @@ +using Sandbox; +using System.Collections.Generic; + +namespace VeloX; + +public abstract partial class VeloXBase +{ + + [Property] public List Wheels { get; set; } + [Property] public TagSet WheelIgnoredTags { get; set; } + + public List FindWheels() => [.. Components.GetAll()]; + + [Button] + public void SetupWheels() + { + Wheels = FindWheels(); + } + +} diff --git a/Code/Base/VeloXBase.cs b/Code/Base/VeloXBase.cs new file mode 100644 index 0000000..fb29afe --- /dev/null +++ b/Code/Base/VeloXBase.cs @@ -0,0 +1,38 @@ +using Sandbox; + +namespace VeloX; + + +public abstract partial class VeloXBase : Component +{ + [Sync] public EngineState EngineState { get; set; } + [Sync] public WaterState WaterState { get; set; } + [Sync] public bool IsEngineOnFire { get; set; } + [Sync, Range( 0, 1 ), Property] public float Brake { get; set; } + [Sync( SyncFlags.Interpolate ), Range( 0, 1 ), Property] public float Throttle { get; set; } + + [Property] public Vector3 AngularDrag { get; set; } = new( -0.1f, -0.1f, -3 ); + [Property] public float Mass { get; set; } = 900; + [Property, Group( "Components" )] public Rigidbody Body { get; protected set; } + [Property, Group( "Components" )] public Collider Collider { get; protected set; } + + [Sync] public Angles SteerAngle { get; set; } + + public Vector3 LocalVelocity; + public float ForwardSpeed; + public float TotalSpeed; + + protected override void OnFixedUpdate() + { + if ( IsProxy ) + return; + + LocalVelocity = WorldTransform.PointToLocal( WorldPosition + Body.Velocity ); + ForwardSpeed = LocalVelocity.x; + TotalSpeed = LocalVelocity.Length; + Body.PhysicsBody.Mass = Mass; + + PhysicsSimulate(); + } + +} diff --git a/Code/Base/Wheel/VeloXWheel.Gizmo.cs b/Code/Base/Wheel/VeloXWheel.Gizmo.cs new file mode 100644 index 0000000..79b8425 --- /dev/null +++ b/Code/Base/Wheel/VeloXWheel.Gizmo.cs @@ -0,0 +1,74 @@ +using Sandbox; + +namespace VeloX; + + +public partial class VeloXWheel : Component +{ + protected override void DrawGizmos() + { + + if ( !Gizmo.IsSelected ) + return; + + Gizmo.Draw.IgnoreDepth = true; + + // + // Suspension length + // + { + var suspensionStart = Vector3.Zero; + var suspensionEnd = Vector3.Zero + Vector3.Down * SuspensionLength; + + Gizmo.Draw.Color = Color.Cyan; + Gizmo.Draw.LineThickness = 0.25f; + + Gizmo.Draw.Line( suspensionStart, suspensionEnd ); + + Gizmo.Draw.Line( suspensionStart + Vector3.Forward, suspensionStart + Vector3.Backward ); + Gizmo.Draw.Line( suspensionEnd + Vector3.Forward, suspensionEnd + Vector3.Backward ); + } + var widthOffset = Vector3.Right * Width * 0.5f; + // + // Wheel radius + // + { + Gizmo.Draw.LineThickness = 0.5f; + Gizmo.Draw.Color = Color.White; + + Gizmo.Draw.LineCylinder( widthOffset, -widthOffset, Radius, Radius, 16 ); + } + + // + // Wheel width + // + { + var circlePosition = Vector3.Zero; + + Gizmo.Draw.LineThickness = 0.25f; + Gizmo.Draw.Color = Color.White; + + for ( float i = 0; i < 16; i++ ) + { + + var pos = circlePosition + Vector3.Up.RotateAround( Vector3.Zero, new Angles( i / 16 * 360, 0, 0 ) ) * Radius; + + Gizmo.Draw.Line( new Line( pos - widthOffset, pos + widthOffset ) ); + + var pos2 = circlePosition + Vector3.Up.RotateAround( Vector3.Zero, new Angles( (i + 1) / 16 * 360, 0, 0 ) ) * Radius; + Gizmo.Draw.Line( pos - widthOffset, pos2 + widthOffset ); + } + } + + //// + //// Forward direction + //// + //{ + // var arrowStart = Vector3.Forward * Radius; + // var arrowEnd = arrowStart + Vector3.Forward * 8f; + + // Gizmo.Draw.Color = Color.Red; + // Gizmo.Draw.Arrow( arrowStart, arrowEnd, 4, 1 ); + //} + } +} diff --git a/Code/Base/Wheel/VeloXWheel.Suspension.cs b/Code/Base/Wheel/VeloXWheel.Suspension.cs new file mode 100644 index 0000000..e3476bc --- /dev/null +++ b/Code/Base/Wheel/VeloXWheel.Suspension.cs @@ -0,0 +1,18 @@ +using Sandbox; + +namespace VeloX; + +public partial class VeloXWheel +{ + [Property] float BrakePowerMax { get; set; } = 3000; + [Property, Group( "Suspension" )] float SuspensionLength { get; set; } = 10; + [Property, Group( "Suspension" )] float SpringStrength { get; set; } = 800; + [Property, Group( "Suspension" )] float SpringDamper { get; set; } = 3000; + + [Property, Group( "Traction" )] float ForwardTractionMax { get; set; } = 2600; + [Property, Group( "Traction" )] float SideTractionMultiplier { get; set; } = 20; + [Property, Group( "Traction" )] float SideTractionMaxAng { get; set; } = 25; + [Property, Group( "Traction" )] float SideTractionMax { get; set; } = 2400; + [Property, Group( "Traction" )] float SideTractionMin { get; set; } = 800; + +} diff --git a/Code/Base/Wheel/VeloXWheel.cs b/Code/Base/Wheel/VeloXWheel.cs new file mode 100644 index 0000000..9b97bda --- /dev/null +++ b/Code/Base/Wheel/VeloXWheel.cs @@ -0,0 +1,240 @@ +using Sandbox; +using System; +using System.Runtime.Intrinsics.Arm; +using System.Text.RegularExpressions; + +namespace VeloX; + +[Group( "VeloX" )] +[Title( "VeloX - Wheel" )] +public partial class VeloXWheel : Component +{ + + [Property] public float Radius { get; set; } = 15; + [Property] public float Width { get; set; } = 6; + [Sync] public float SideSlip { get; private set; } + [Sync] public float ForwardSlip { get; private set; } + [Sync, Range( 0, 1 )] public float BrakePower { get; set; } + [Sync] public float Torque { get; set; } + [Sync, Range( 0, 1 )] public float Brake { get; set; } + [Property] public bool IsFront { get; protected set; } + [Property] public float SteerMultiplier { get; set; } + public float Spin { get; private set; } + + public float RPM { get => angularVelocity * 60f / MathF.Tau; set => angularVelocity = value / (60 / MathF.Tau); } + internal float DistributionFactor { get; set; } + + private Vector3 StartPos { get; set; } + private static Rotation CylinderOffset => Rotation.FromRoll( 90 ); + + public SceneTraceResult Trace { get; private set; } + public bool IsOnGround => Trace.Hit; + + private float lastSpringOffset; + private Vector2 tractionCycle; + private float angularVelocity; + private float lastFraction; + private RealTimeUntil expandSoundCD; + private RealTimeUntil contractSoundCD; + + + protected override void OnAwake() + { + base.OnAwake(); + if ( StartPos.IsNearZeroLength ) + StartPos = LocalPosition; + } + + private static float TractionRamp( float slipAngle, float sideTractionMaxAng, float sideTractionMax, float sideTractionMin ) + { + sideTractionMaxAng /= 90; // Convert max slip angle to the 0 - 1 range + var x = (slipAngle - sideTractionMaxAng) / (1 - sideTractionMaxAng); + + + return slipAngle < sideTractionMaxAng ? sideTractionMax : (sideTractionMax * (1 - x)) + (sideTractionMin * x); + } + + private void DoSuspensionSounds( VeloXBase vehicle, float change ) + { + if ( change > 0.1f && expandSoundCD ) + { + expandSoundCD = 0.3f; + var sound = Sound.Play( vehicle.SuspensionUpSound, WorldPosition ); + sound.Volume = Math.Clamp( Math.Abs( change ) * 5f, 0, 0.5f ); + } + + if ( change < -0.1f && contractSoundCD ) + { + contractSoundCD = 0.3f; + change = MathF.Abs( change ); + var sound = Sound.Play( change > 0.3f ? vehicle.SuspensionHeavySound : vehicle.SuspensionDownSound, WorldPosition ); + sound.Volume = Math.Clamp( change * 5f, 0, 1 ); + } + } + + internal void Update( VeloXBase vehicle, in float dt ) + { + UpdateVisuals( vehicle, dt ); + + if ( !IsOnGround ) + { + angularVelocity += (Torque / 20) * dt; + angularVelocity = MathX.Approach( angularVelocity, 0, dt * 4 ); + } + + } + + private void UpdateVisuals( VeloXBase vehicle, in float dt ) + { + //Rotate the wheel around the axle axis + Spin = (Spin - MathX.RadianToDegree( angularVelocity ) * dt) % 360; + + WorldRotation = vehicle.WorldTransform.RotationToWorld( vehicle.SteerAngle * SteerMultiplier ).RotateAroundAxis( Vector3.Right, Spin ); + + } + + public void DoPhysics( VeloXBase vehicle, ref Vector3 vehVel, ref Vector3 vehAngVel, ref Vector3 outLin, ref Vector3 outAng, in float dt ) + { + + var pos = vehicle.WorldTransform.PointToWorld( StartPos ); + + var ang = vehicle.WorldTransform.RotationToWorld( vehicle.SteerAngle * SteerMultiplier ); + + var fw = ang.Forward; + var rt = ang.Right; + var up = ang.Up; + + var maxLen = SuspensionLength; + + var endPos = pos + ang.Down * maxLen; + Trace = Scene.Trace.IgnoreGameObjectHierarchy( vehicle.GameObject ) + .FromTo( pos, endPos ) + .Cylinder( Width, Radius ) + .Rotated( vehicle.WorldTransform.Rotation * CylinderOffset ) + .UseRenderMeshes( false ) + .UseHitPosition( false ) + .WithoutTags( vehicle.WheelIgnoredTags ) + .Run(); + + var fraction = Trace.Fraction; + + var contactPos = pos - maxLen * fraction * up; + + LocalPosition = vehicle.WorldTransform.PointToLocal( contactPos ); + + DoSuspensionSounds( vehicle, fraction - lastFraction ); + lastFraction = fraction; + + if ( !IsOnGround ) + { + SideSlip = 0; + ForwardSlip = 0; + return; + } + var normal = Trace.Normal; + + var vel = vehicle.Body.GetVelocityAtPoint( pos ); + // Split that velocity among our local directions + + var velF = fw.Dot( vel ); + + var velR = rt.Dot( vel ); + + var velU = normal.Dot( vel ); + + var absVelR = Math.Abs( velR ); + + + //Make forward forces be perpendicular to the surface normal + + fw = normal.Cross( rt ); + + //Suspension spring force &damping + + var offset = maxLen - (fraction * maxLen); + + var springForce = (offset * SpringStrength); + + var damperForce = (lastSpringOffset - offset) * SpringDamper; + lastSpringOffset = offset; + var force = (springForce - damperForce) * up.Dot( normal ) * normal; + + //If the suspension spring is going to be fully compressed on the next frame... + + if ( velU < 0 && offset + Math.Abs( velU * dt ) > SuspensionLength ) + { + var (linearVel, angularVel) = vehicle.Body.PhysicsBody.CalculateVelocityOffset( (-velU / dt) * normal, pos ); + vehVel += linearVel; + vehAngVel += angularVel; + } + + //Rolling resistance + force += 0.05f * -velF * fw; + + //Brake and torque forces + var surfaceGrip = 1; + + var maxTraction = ForwardTractionMax * surfaceGrip * 1; + //Grip loss logic + + var brakeForce = MathX.Clamp( -velF, -Brake, Brake ) * BrakePowerMax * surfaceGrip; + + var forwardForce = Torque + brakeForce; + + var signForwardForce = forwardForce > 0 ? 1 : (forwardForce < 0 ? -1 : 0); + + // Given an amount of sideways slippage( up to the max.traction ) + // and the forward force, calculate how much grip we are losing. + + tractionCycle.x = Math.Min( absVelR, maxTraction ); + tractionCycle.y = forwardForce; + + var gripLoss = Math.Max( tractionCycle.Length - maxTraction, 0 ); + + + // Reduce the forward force by the amount of grip we lost, + + // but still allow some amount of brake force to apply regardless. + + forwardForce += -(gripLoss * signForwardForce) + MathX.Clamp( brakeForce * 0.5f, -maxTraction, maxTraction ); + + force += fw * forwardForce; + + // Get how fast the wheel would be spinning if it had never lost grip + + var groundAngularVelocity = MathF.Tau * (velF / (Radius * MathF.Tau)); + + // Add our grip loss to our spin velocity + var _angvel = groundAngularVelocity + gripLoss * (Torque > 0 ? 1 : (Torque < 0 ? -1 : 0)); + + // Smoothly match our current angular velocity to the angular velocity affected by grip loss + + angularVelocity = MathX.Approach( angularVelocity, _angvel, dt * 200 ); + ForwardSlip = groundAngularVelocity - angularVelocity; + + // Calculate side slip angle + var slipAngle = MathF.Atan2( velR, MathF.Abs( velF ) ) / MathF.PI * 2; + SideSlip = slipAngle * MathX.Clamp( vehicle.TotalSpeed * 0.005f, 0, 1 ) * 2; + + //Sideways traction ramp + + slipAngle = MathF.Abs( slipAngle * slipAngle ); + + maxTraction = TractionRamp( slipAngle, SideTractionMaxAng, SideTractionMax, SideTractionMin ); + + var sideForce = -rt.Dot( vel * SideTractionMultiplier ); + + // Reduce sideways traction force as the wheel slips forward + sideForce *= 1 - Math.Clamp( MathF.Abs( gripLoss ) * 0.1f, 0, 1 ) * 0.9f; + + // Apply sideways traction force + force += Math.Clamp( sideForce, -maxTraction, maxTraction ) * surfaceGrip * rt; + + force += velR * SideTractionMultiplier * -0.1f * rt; + //Apply the forces at the axle / ground contact position + + vehicle.Body.ApplyForceAt( pos, force / dt ); + + + } +} diff --git a/Code/Car/VeloXCar.Engine.cs b/Code/Car/VeloXCar.Engine.cs new file mode 100644 index 0000000..702fab5 --- /dev/null +++ b/Code/Car/VeloXCar.Engine.cs @@ -0,0 +1,358 @@ +using Sandbox; +using System; +using System.Collections.Generic; + +namespace VeloX; + +public partial class VeloXCar +{ + [Property, Feature( "Engine" )] public EngineStream Stream { get; set; } + public EngineStreamPlayer StreamPlayer { get; set; } + + [Property, Feature( "Engine" )] public float MinRPM { get; set; } = 800; + [Property, Feature( "Engine" )] public float MaxRPM { get; set; } = 7000; + [Property, Feature( "Engine" ), Range( -1, 1 )] + public float PowerDistribution + { + get => powerDistribution; set + { + powerDistribution = value; + UpdatePowerDistribution(); + } + } + [Property, Feature( "Engine" )] public float FlyWheelMass { get; set; } = 80f; + [Property, Feature( "Engine" )] public float FlyWheelRadius { get; set; } = 0.5f; + [Property, Feature( "Engine" )] public float FlywheelFriction { get; set; } = -6000; + [Property, Feature( "Engine" )] public float FlywheelTorque { get; set; } = 20000; + [Property, Feature( "Engine" )] public float EngineBrakeTorque { get; set; } = 2000; + [Property, Feature( "Engine" )] + public Dictionary Gears { get; set; } = new() + { + [-1] = 2.5f, + [0] = 0f, + [1] = 2.8f, + [2] = 1.7f, + [3] = 1.2f, + [4] = 0.9f, + [5] = 0.75f, + [6] = 0.7f + }; + [Property, Feature( "Engine" )] public float DifferentialRatio { get; set; } = 1f; + [Property, Feature( "Engine" ), Range( 0, 1 )] public float TransmissionEfficiency { get; set; } = 0.8f; + [Property, Feature( "Engine" )] private float MinRPMTorque { get; set; } = 5000f; + [Property, Feature( "Engine" )] private float MaxRPMTorque { get; set; } = 8000f; + [Sync] public int Gear { get; set; } = 0; + [Sync] public float Clutch { get; set; } = 1; + [Sync( SyncFlags.Interpolate )] public float EngineRPM { get; set; } + public float RPMPercent => (EngineRPM - MinRPM) / MaxRPM; + + private const float TAU = MathF.Tau; + + private int MinGear { get; set; } + private int MaxGear { get; set; } + + [Sync] public bool IsRedlining { get; private set; } + + private float flywheelVelocity; + private TimeUntil switchCD = 0; + private float groundedCount; + private float burnout; + + private float frontBrake; + private float rearBrake; + + private float availableFrontTorque; + private float availableRearTorque; + + private float avgSideSlip; + private float avgPoweredRPM; + private float avgForwardSlip; + + private float inputThrottle, inputBrake; + private bool inputHandbrake; + private float transmissionRPM; + private float powerDistribution; + + public float FlywheelRPM + { + get => flywheelVelocity * 60 / TAU; + set + { + flywheelVelocity = value * TAU / 60; EngineRPM = value; + } + } + private void UpdateGearList() + { + int minGear = 0; + int maxGear = 0; + + foreach ( var (gear, ratio) in Gears ) + { + + if ( gear < minGear ) + minGear = gear; + + if ( gear > maxGear ) + maxGear = gear; + + if ( minGear != 0 || maxGear != 0 ) + { + SwitchGear( 0, false ); + } + } + + MinGear = minGear; + MaxGear = maxGear; + + } + + + public void SwitchGear( int index, bool cooldown = true ) + { + if ( Gear == index ) return; + index = Math.Clamp( index, MinGear, MaxGear ); + + if ( index == 0 || !cooldown ) + switchCD = 0; + else + switchCD = 0.3f; + Clutch = 1; + Gear = index; + } + + public float TransmissionToEngineRPM( int gear ) => avgPoweredRPM * Gears[gear] * DifferentialRatio * 60 / TAU; + public float GetTransmissionMaxRPM( int gear ) => FlywheelRPM / Gears[gear] / DifferentialRatio; + + private void UpdatePowerDistribution() + { + if ( Wheels is null ) return; + int frontCount = 0, rearCount = 0; + + foreach ( var wheel in Wheels ) + { + if ( wheel.IsFront ) + frontCount++; + else + rearCount++; + + } + + float frontDistribution = 0.5f + PowerDistribution * 0.5f; + + float rearDistribution = 1 - frontDistribution; + + frontDistribution /= frontCount; + rearDistribution /= rearCount; + + foreach ( var wheel in Wheels ) + if ( wheel.IsFront ) + wheel.DistributionFactor = frontDistribution; + else + wheel.DistributionFactor = rearDistribution; + } + + private void EngineAccelerate( float torque, float dt ) + { + var inertia = 0.5f * FlyWheelMass * FlyWheelRadius * FlyWheelRadius; + var angularAcceleration = torque / inertia; + + flywheelVelocity += angularAcceleration * dt; + } + + private float GetTransmissionTorque( int gear, float minTorque, float maxTorque ) + { + var torque = FlywheelRPM.Remap( MinRPM, MaxRPM, minTorque, maxTorque, true ); + torque *= (1 - Clutch); + torque = torque * Gears[gear] * DifferentialRatio * TransmissionEfficiency; + + return gear == -1 ? -torque : torque; + } + + private void AutoGearSwitch() + { + if ( ForwardSpeed < 100 && Input.Down( "Backward" ) ) + { + SwitchGear( -1, false ); + return; + } + + var currentGear = Gear; + + if ( currentGear < 0 && ForwardSpeed < -100 ) + return; + + + if ( Math.Abs( avgForwardSlip ) > 10 ) + return; + + var gear = Math.Clamp( currentGear, 1, MaxGear ); + + float minRPM = MinRPM, maxRPM = MaxRPM; + + + maxRPM *= 0.98f; + + float gearRPM; + + + for ( int i = 1; i <= MaxGear; i++ ) + { + gearRPM = TransmissionToEngineRPM( i ); + + if ( (i == 1 && gearRPM < minRPM) || (gearRPM > minRPM && gearRPM < maxRPM) ) + { + gear = i; + break; + } + } + + var threshold = minRPM + (maxRPM - minRPM) * (0.5 - Throttle * 0.3); + if ( gear < currentGear && gear > currentGear - 2 && EngineRPM > threshold ) + return; + + SwitchGear( gear ); + } + + private float EngineClutch( float dt ) + { + if ( !switchCD ) + { + inputThrottle = 0; + + return 0; + } + + if ( inputHandbrake ) + return 1; + + var absForwardSpeed = Math.Abs( ForwardSpeed ); + + if ( groundedCount < 1 && absForwardSpeed > 30 ) + return 1; + + if ( ForwardSpeed < -50 && inputBrake > 0 && Gear < 0 ) + return 1; + + if ( absForwardSpeed > 200 ) + return 0; + + return inputThrottle > 0.1f ? 0 : 1; + } + + private void EngineThink( float dt ) + { + inputThrottle = Input.Down( "Forward" ) ? 1 : 0; + inputBrake = Input.Down( "Backward" ) ? 1 : 0; + inputHandbrake = Input.Down( "Jump" ); + if ( burnout > 0 ) + { + SwitchGear( 1, false ); + if ( inputThrottle < 0.1f || inputBrake < 0.1f ) + burnout = 0; + } + else + AutoGearSwitch(); + + if ( Gear < 0 ) + (inputBrake, inputThrottle) = (inputThrottle, inputBrake); + + var rpm = FlywheelRPM; + + var clutch = EngineClutch( dt ); + + if ( inputThrottle > 0.1 && inputBrake > 0.1 && Math.Abs( ForwardSpeed ) < 50 ) + { + burnout = MathX.Approach( burnout, 1, dt * 2 ); + Clutch = 0; + + } + else if ( inputHandbrake ) + { + frontBrake = 0f; + rearBrake = 0.5f; + Clutch = 1; + clutch = 1; + } + else + { + if ( (Gear == -1 || Gear == 1) && inputThrottle < 0.05f && inputBrake < 0.1f && groundedCount > 1 && rpm < MinRPM * 1.2f ) + inputBrake = 0.2f; + + frontBrake = inputBrake * 0.5f; + rearBrake = inputBrake * 0.5f; + } + + clutch = MathX.Approach( Clutch, clutch, dt * ((Gear < 2 && inputThrottle > 0.1f) ? 6 : 2) ); + + Clutch = clutch; + + var isRedlining = false; + transmissionRPM = 0; + + if ( Gear != 0 ) + { + transmissionRPM = TransmissionToEngineRPM( Gear ); + transmissionRPM = Gear < 0 ? -transmissionRPM : transmissionRPM; + rpm = (rpm * clutch) + (MathF.Max( 0, transmissionRPM ) * (1 - clutch)); + } + var throttle = Throttle; + + var gearTorque = GetTransmissionTorque( Gear, MinRPMTorque, MaxRPMTorque ); + + var availableTorque = gearTorque * throttle; + + if ( transmissionRPM < 0 ) + { + availableTorque += gearTorque * 2; + } + else + { + var engineBrakeTorque = GetTransmissionTorque( Gear, EngineBrakeTorque, EngineBrakeTorque ); + + availableTorque -= engineBrakeTorque * (1 - throttle) * 0.5f; + } + + var maxRPM = MaxRPM; + + if ( rpm < MinRPM ) + { + rpm = MinRPM; + } + else if ( rpm > maxRPM ) + { + + if ( rpm > maxRPM * 1.2f ) + availableTorque = 0; + + rpm = maxRPM; + + if ( Gear != MaxGear || groundedCount < Wheels.Count ) + isRedlining = true; + } + + FlywheelRPM = Math.Clamp( rpm, 0, maxRPM ); + + if ( burnout > 0 ) + availableTorque += availableTorque * burnout * 0.1f; + + var front = 0.5f + PowerDistribution * 0.5f; + var rear = 1 - front; + + availableFrontTorque = availableTorque * front; + availableRearTorque = availableTorque * rear; + + throttle = MathX.Approach( throttle, inputThrottle, dt * 4 ); + + + EngineAccelerate( FlywheelFriction + FlywheelTorque * throttle, dt ); + + Throttle = throttle; + + IsRedlining = (isRedlining && inputThrottle > 0); + } + + public void StreamEngineUpdate() + { + + } +} diff --git a/Code/Car/VeloXCar.Steering.cs b/Code/Car/VeloXCar.Steering.cs new file mode 100644 index 0000000..818c588 --- /dev/null +++ b/Code/Car/VeloXCar.Steering.cs @@ -0,0 +1,46 @@ +using Sandbox; +using System; +using System.Threading; + +namespace VeloX; + +public partial class VeloXCar +{ + public static float ExpDecay( float a, float b, float decay, float dt ) => b + (a - b) * MathF.Exp( -decay * dt ); + [Property, Feature( "Steer" )] public float SteerConeMaxSpeed { get; set; } = 1800; + [Property, Feature( "Steer" )] public float SteerConeMaxAngle { get; set; } = 0.25f; + [Property, Feature( "Steer" )] public float SteerConeChangeRate { get; set; } = 8; + [Property, Feature( "Steer" )] public float CounterSteer { get; set; } = 0.1f; + [Property, Feature( "Steer" )] public float MaxSteerAngle { get; set; } = 35f; + + [Sync] public float Steering { get; private set; } + private float jTurnMultiplier; + + private float inputSteer; + private void UpdateSteering( float dt ) + { + var inputSteer = Input.AnalogMove.y; + var absInputSteer = Math.Abs( inputSteer ); + + var sideSlip = Math.Clamp( avgSideSlip, -1, 1 ); + + var steerConeFactor = Math.Clamp( TotalSpeed / SteerConeMaxSpeed, 0, 1 ); + var steerCone = 1 - steerConeFactor * (1 - SteerConeMaxAngle); + + steerCone = Math.Clamp( steerCone, Math.Abs( sideSlip ), 1 ); + + inputSteer = ExpDecay( this.inputSteer, inputSteer * steerCone, SteerConeChangeRate, dt ); + this.inputSteer = inputSteer; + var counterSteer = sideSlip * steerConeFactor * (1 - absInputSteer); + counterSteer = Math.Clamp( counterSteer, -1, 1 ) * CounterSteer; + + inputSteer = Math.Clamp( inputSteer + counterSteer, -1, 1 ); + Steering = inputSteer; + SteerAngle = new( 0, inputSteer * MaxSteerAngle, 0 ); + + if ( ForwardSpeed < -100 ) + jTurnMultiplier = 0.5f; + else + jTurnMultiplier = ExpDecay( jTurnMultiplier, 1, 2, dt ); + } +} diff --git a/Code/Car/VeloXCar.Wheel.cs b/Code/Car/VeloXCar.Wheel.cs new file mode 100644 index 0000000..5b9f403 --- /dev/null +++ b/Code/Car/VeloXCar.Wheel.cs @@ -0,0 +1,46 @@ +namespace VeloX; + +public partial class VeloXCar +{ + + private void WheelThink( in float dt ) + { + var maxRPM = GetTransmissionMaxRPM( Gear ); + + + var frontTorque = availableFrontTorque; + var rearTorque = availableRearTorque; + + + groundedCount = 0; + float avgRPM = 0, totalSideSlip = 0, totalForwardSlip = 0; + + foreach ( var w in Wheels ) + { + w.Update( this, dt ); + + totalSideSlip += w.SideSlip; + totalForwardSlip += w.ForwardSlip; + var rpm = w.RPM; + + avgRPM += rpm * w.DistributionFactor; + + w.Torque = w.DistributionFactor * (w.IsFront ? frontTorque : rearTorque); + w.Brake = w.IsFront ? frontBrake : rearBrake; + + if ( inputHandbrake && !w.IsFront ) + w.RPM = 0; + + if ( rpm > maxRPM ) + w.RPM = maxRPM; + + if ( w.IsOnGround ) + groundedCount++; + + } + avgPoweredRPM = avgRPM; + avgSideSlip = totalSideSlip / Wheels.Count; + avgForwardSlip = totalForwardSlip / Wheels.Count; + + } +} diff --git a/Code/Car/VeloXCar.cs b/Code/Car/VeloXCar.cs new file mode 100644 index 0000000..2619c00 --- /dev/null +++ b/Code/Car/VeloXCar.cs @@ -0,0 +1,60 @@ +using Sandbox; +using System; + +namespace VeloX; + +[Group( "VeloX" )] +[Title( "VeloX - Car" )] +public partial class VeloXCar : VeloXBase +{ + protected override void OnAwake() + { + base.OnAwake(); + + StreamPlayer = new( Stream ); + } + protected override void OnStart() + { + base.OnStart(); + + if ( !IsProxy ) + { + UpdateGearList(); + UpdatePowerDistribution(); + } + } + + protected override void OnUpdate() + { + base.OnUpdate(); + if ( StreamPlayer is not null ) + { + + StreamPlayer.Throttle = Throttle; + StreamPlayer.RPMPercent = RPMPercent; + StreamPlayer.EngineState = EngineState; + StreamPlayer.IsRedlining = IsRedlining; + + if ( Driver.IsValid() && Driver.IsProxy ) + StreamPlayer.Update( Time.Delta, Scene.Camera.WorldPosition ); + else + StreamPlayer.Update( Time.Delta, WorldPosition ); + } + + } + protected override void OnFixedUpdate() + { + if ( IsProxy ) + return; + + base.OnFixedUpdate(); + + var dt = Time.Delta; + EngineThink( dt ); + WheelThink( dt ); + UpdateSteering( dt ); + + Brake = Math.Clamp( frontBrake + rearBrake + (Input.Down( "Jump" ) ? 1 : 0), 0, 1 ); + } + +} diff --git a/Code/Enum/EngineState.cs b/Code/Enum/EngineState.cs new file mode 100644 index 0000000..03b4948 --- /dev/null +++ b/Code/Enum/EngineState.cs @@ -0,0 +1,9 @@ +namespace VeloX; + +public enum EngineState +{ + Off, + Starting, + Running, + ShuttingDown, +} diff --git a/Code/Enum/WaterState.cs b/Code/Enum/WaterState.cs new file mode 100644 index 0000000..e694dcf --- /dev/null +++ b/Code/Enum/WaterState.cs @@ -0,0 +1,9 @@ +namespace VeloX; + +public enum WaterState +{ + NotOnWater, // 0: Не на воде + PartialContact, // 1: Хотя бы одна точка плавучести на воде + HalfSubmerged, // 2: Хотя бы половина точек плавучести на воде + FullySubmerged // 3: Полностью погружён +} diff --git a/Code/Input/InputResolver.cs b/Code/Input/InputResolver.cs new file mode 100644 index 0000000..50e97be --- /dev/null +++ b/Code/Input/InputResolver.cs @@ -0,0 +1,47 @@ +using Sandbox; + +namespace VeloX; +public class InputResolver +{ + public GameObject Driver { get; internal set; } + + + private bool IsDriverActive => Driver.IsValid(); + + + public Vector2 MouseDelta => IsDriverActive ? Input.MouseDelta : default; + public Vector2 MouseWheel => IsDriverActive ? Input.MouseWheel : default; + public Angles AnalogLook => IsDriverActive ? Input.AnalogLook : default; + public Vector3 AnalogMove => IsDriverActive ? Input.AnalogMove : default; + + public bool Down( string action ) + { + return IsDriverActive && Input.Down( action ); + } + + public bool Pressed( string action ) + { + return IsDriverActive && Input.Pressed( action ); + } + + public bool Released( string action ) + { + return IsDriverActive && Input.Released( action ); + } + + public void TriggerHaptics( float leftMotor, float rightMotor, float leftTrigger = 0f, float rightTrigger = 0f, int duration = 500 ) + { + if ( IsDriverActive ) + { + Input.TriggerHaptics( leftMotor, rightMotor, leftTrigger, rightTrigger, duration ); + } + } + + public void StopAllHaptics() + { + if ( IsDriverActive ) + { + Input.StopAllHaptics(); + } + } +} diff --git a/Code/Utils/EngineStream/ControllerConverter.cs b/Code/Utils/EngineStream/ControllerConverter.cs new file mode 100644 index 0000000..7591f89 --- /dev/null +++ b/Code/Utils/EngineStream/ControllerConverter.cs @@ -0,0 +1,87 @@ +using Sandbox; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace VeloX.Audio; + +[JsonConverter( typeof( ControllerConverter ) )] +public sealed class Controller +{ + public enum InputTypes + { + Throttle, + RpmFraction, + } + public enum OutputTypes + { + Volume, + Pitch, + } + + public required InputTypes InputParameter { get; set; } + [Property] public ValueRange InputRange { get; set; } + public required OutputTypes OutputParameter { get; set; } + public ValueRange OutputRange { get; set; } +} + +public record struct ValueRange +{ + public ValueRange( float inputMin, float inputMax ) + { + Min = inputMin; + Max = inputMax; + } + + [Range( 0, 1 )] public float Min { get; set; } + [Range( 0, 1 )] public float Max { get; set; } + + public readonly float Remap( float value, float newMin, float newMax ) + { + var normalized = (value - Min) / (Max - Min); + return newMin + normalized * (newMax - newMin); + } +}; + +public sealed class ControllerConverter : JsonConverter +{ + public override Controller Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options ) + { + if ( reader.TokenType != JsonTokenType.StartArray ) + throw new JsonException( "Expected array for Controller" ); + + reader.Read(); + string inputParam = reader.GetString() ?? string.Empty; + reader.Read(); + float inputMin = reader.GetSingle(); + reader.Read(); + float inputMax = reader.GetSingle(); + reader.Read(); + string outputParam = reader.GetString() ?? string.Empty; + reader.Read(); + float outputMin = reader.GetSingle(); + reader.Read(); + float outputMax = reader.GetSingle(); + reader.Read(); // End of array + + return new Controller + { + InputParameter = Enum.Parse( inputParam, true ), + InputRange = new ValueRange( inputMin, inputMax ), + OutputParameter = Enum.Parse( outputParam, true ), + OutputRange = new ValueRange( outputMin, outputMax ) + }; + } + + public override void Write( Utf8JsonWriter writer, Controller value, JsonSerializerOptions options ) + { + writer.WriteStartArray(); + writer.WriteStringValue( value.InputParameter.ToString() ); + writer.WriteNumberValue( value.InputRange.Min ); + writer.WriteNumberValue( value.InputRange.Max ); + writer.WriteStringValue( value.OutputParameter.ToString() ); + writer.WriteNumberValue( value.OutputRange.Min ); + writer.WriteNumberValue( value.OutputRange.Max ); + writer.WriteEndArray(); + } +} diff --git a/Code/Utils/EngineStream/EngineStream.cs b/Code/Utils/EngineStream/EngineStream.cs new file mode 100644 index 0000000..f5905e3 --- /dev/null +++ b/Code/Utils/EngineStream/EngineStream.cs @@ -0,0 +1,35 @@ +using Sandbox; +using System.Collections.Generic; +using VeloX.Audio; + +namespace VeloX; + +[GameResource( "Engine Stream", "engstr", "Engine Sound", Category = "VeloX", Icon = "time_to_leave" )] +public sealed class EngineStream : GameResource +{ + public sealed class Layer + { + [Property, + Title( "Sound File" ), + Description( "Sound asset for this layer" )] + public SoundFile AudioPath { get; set; } + + [Property, Title( "Use Redline Effect" ), + Description( "Enable RPM-based vibration effect" )] + public bool UseRedline { get; set; } = true; + + [Property, Title( "Controllers" ), + Description( "Audio parameter controllers" )] + public List Controllers { get; set; } + [Property, Title( "Is Muted" )] + public bool IsMuted { get; set; } + } + + [Property, Title( "Stream Parameters" ), + Description( "Global engine sound parameters" )] + public StreamParameters Parameters { get; set; } + + [Property, Title( "Sound Layers" ), + Description( "Individual sound layers" )] + public Dictionary Layers { get; set; } +} diff --git a/Code/Utils/EngineStream/EngineStreamPlayer.cs b/Code/Utils/EngineStream/EngineStreamPlayer.cs new file mode 100644 index 0000000..cdc4070 --- /dev/null +++ b/Code/Utils/EngineStream/EngineStreamPlayer.cs @@ -0,0 +1,119 @@ +using Sandbox; +using Sandbox.Audio; +using System; +using System.Collections.Generic; +using System.IO; +using VeloX.Audio; +using static VeloX.EngineStream; + +namespace VeloX; + +public class EngineStreamPlayer( EngineStream stream ) : IDisposable +{ + + private static readonly Mixer EngineMixer = Mixer.FindMixerByName( "Car Engine" ); + public EngineStream Stream { get; set; } = stream; + + public EngineState EngineState { get; set; } + public bool EngineSoundPaused => EngineState != EngineState.Running; + + public float Throttle { get; set; } + public bool IsRedlining { get; set; } + public float RPMPercent { get; set; } + + private float _wobbleTime; + + public readonly Dictionary EngineSounds = []; + + public void Update( float deltaTime, Vector3 position, bool isLocal = false ) + { + var globalPitch = 1.0f; + + // Gear wobble effect + if ( _wobbleTime > 0 ) + { + _wobbleTime -= deltaTime * (0.1f + Throttle); + globalPitch += MathF.Cos( _wobbleTime * Stream.Parameters.WobbleFrequency ) * _wobbleTime * (1 - _wobbleTime) * Stream.Parameters.WobbleStrength; + } + + globalPitch *= Stream.Parameters.Pitch; + + // Redline effect + var redlineVolume = 1.0f; + if ( IsRedlining ) + { + redlineVolume = 1 - Stream.Parameters.RedlineStrength + + MathF.Cos( RealTime.Now * Stream.Parameters.RedlineFrequency ) * + Stream.Parameters.RedlineStrength; + + } + // Process layers + foreach ( var (id, layer) in Stream.Layers ) + { + EngineSounds.TryGetValue( layer, out var channel ); + + if ( !channel.IsValid() ) + { + channel = Sound.PlayFile( layer.AudioPath ); + EngineSounds[layer] = channel; + } + + if ( channel.Paused && (EngineSoundPaused || layer.IsMuted) ) + continue; + + // Reset controller outputs + float layerVolume = 1.0f; + float layerPitch = 1.0f; + + // Apply all controllers + foreach ( var controller in layer.Controllers ) + { + var inputValue = controller.InputParameter switch + { + Controller.InputTypes.Throttle => Throttle, + Controller.InputTypes.RpmFraction => RPMPercent, + _ => 0.0f + }; + + var normalized = Math.Clamp( inputValue, controller.InputRange.Min, controller.InputRange.Max ); + var outputValue = controller.InputRange.Remap( + normalized, + controller.OutputRange.Min, + controller.OutputRange.Max + ); + + // Apply to correct parameter + switch ( controller.OutputParameter ) + { + case Controller.OutputTypes.Volume: + layerVolume *= outputValue; + break; + case Controller.OutputTypes.Pitch: + layerPitch *= outputValue; + break; + } + } + + // Apply redline effect if needed + layerVolume *= layer.UseRedline ? redlineVolume : 1.0f; + layerPitch *= globalPitch; + // Update audio channel + channel.Pitch = layerPitch; + channel.Volume = layerVolume * Stream.Parameters.Volume; + channel.Position = position; + channel.ListenLocal = isLocal; + channel.Paused = EngineSoundPaused || layer.IsMuted; + channel.TargetMixer = EngineMixer; + + } + } + + public void Dispose() + { + foreach ( var item in EngineSounds ) + { + item.Value?.Stop(); + item.Value?.Dispose(); + } + } +} diff --git a/Code/Utils/EngineStream/StreamParameters.cs b/Code/Utils/EngineStream/StreamParameters.cs new file mode 100644 index 0000000..99a6226 --- /dev/null +++ b/Code/Utils/EngineStream/StreamParameters.cs @@ -0,0 +1,24 @@ +using Sandbox; + +namespace VeloX; + +public sealed class StreamParameters +{ + [Property, Range( 0.1f, 2.0f ), Description( "Base pitch multiplier" )] + public float Pitch { get; set; } = 1.0f; + + [Property, Range( 0.0f, 2.0f ), Description( "Base volume level" )] + public float Volume { get; set; } = 1.0f; + + [Property, Range( 1f, 100f ), Description( "Vibration frequency at max RPM" )] + public float RedlineFrequency { get; set; } = 55.0f; + + [Property, Range( 0.0f, 1.0f ), Description( "Vibration intensity at max RPM" )] + public float RedlineStrength { get; set; } = 0.2f; + + [Property, Range( 1f, 50f ), Description( "Idle vibration frequency" )] + public float WobbleFrequency { get; set; } = 25.0f; + + [Property, Range( 0.0f, 0.5f ), Description( "Idle vibration intensity" )] + public float WobbleStrength { get; set; } = 0.13f; +} diff --git a/Code/Utils/PhysicsExtensions.cs b/Code/Utils/PhysicsExtensions.cs new file mode 100644 index 0000000..27aace7 --- /dev/null +++ b/Code/Utils/PhysicsExtensions.cs @@ -0,0 +1,78 @@ + +using Sandbox; + +namespace VeloX; +public static class PhysicsExtensions +{ + /// + /// Calculates the linear and angular velocities on the center of mass for an offset impulse. + /// + /// The physics object + /// The impulse acting on the object in kg*units/s (World frame) + /// The location of the impulse in world coordinates + /// + /// Vector1: Linear velocity from the impulse (World frame) + /// Vector2: Angular velocity from the impulse (Local frame) + /// + public static (Vector3 LinearVelocity, Vector3 AngularVelocity) CalculateVelocityOffset( this PhysicsBody physObj, Vector3 impulse, Vector3 position ) + { + if ( !physObj.IsValid() || !physObj.MotionEnabled ) + return (Vector3.Zero, Vector3.Zero); + + + Vector3 linearVelocity = impulse / physObj.Mass; + + Vector3 centerOfMass = physObj.MassCenter; + Vector3 relativePosition = position - centerOfMass; + Vector3 torque = relativePosition.Cross( impulse ); + + Rotation bodyRotation = physObj.Transform.Rotation; + Vector3 localTorque = bodyRotation.Inverse * torque; + + Vector3 localInverseInertia = physObj.Inertia.Inverse; + + Vector3 localAngularVelocity = new( + localTorque.x * localInverseInertia.x, + localTorque.y * localInverseInertia.y, + localTorque.z * localInverseInertia.z ); + + return (linearVelocity, localAngularVelocity); + } + + /// + /// Calculates the linear and angular impulses on the object's center of mass for an offset impulse. + /// + /// The physics object + /// The impulse acting on the object in kg*units/s (World frame) + /// The location of the impulse in world coordinates + /// + /// Vector1: Linear impulse on center of mass (World frame) + /// Vector2: Angular impulse on center of mass (Local frame) + /// + public static (Vector3 LinearImpulse, Vector3 AngularImpulse) CalculateForceOffset( + this PhysicsBody physObj, + Vector3 impulse, + Vector3 position ) + { + if ( !physObj.IsValid() || !physObj.MotionEnabled ) + { + return (Vector3.Zero, Vector3.Zero); + } + + // 1. Linear impulse is the same as the input impulse (conservation of momentum) + Vector3 linearImpulse = impulse; + + // 2. Calculate angular impulse (torque) from the offset force + // τ = r × F (cross product of position relative to COM and force) + Vector3 centerOfMass = physObj.MassCenter; + Vector3 relativePosition = position - centerOfMass; + Vector3 worldAngularImpulse = relativePosition.Cross( impulse ); + + // Convert angular impulse to local space (since we'll use it with LocalInertia) + Rotation bodyRotation = physObj.Transform.Rotation; + Vector3 localAngularImpulse = bodyRotation.Inverse * worldAngularImpulse; + + return (linearImpulse, localAngularImpulse); + } + +} diff --git a/Code/velox.csproj.user b/Code/velox.csproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/Code/velox.csproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Editor/ControllerWidget.cs b/Editor/ControllerWidget.cs new file mode 100644 index 0000000..11522ea --- /dev/null +++ b/Editor/ControllerWidget.cs @@ -0,0 +1,30 @@ +using Editor; +using Sandbox; +using VeloX.Audio; + +namespace VeloX; + +[CustomEditor( typeof( Controller ) )] +public class ControllerWidget : ControlObjectWidget +{ + public override bool SupportsMultiEdit => false; + public override bool IsWideMode => true; + + public ControllerWidget( SerializedProperty property ) : base( property, true ) + { + + Layout = Layout.Column(); + + SerializedObject.TryGetProperty( nameof( Controller.InputParameter ), out var inputType ); + Layout.Add( Create( inputType ) ); + SerializedObject.TryGetProperty( nameof( Controller.InputRange ), out var inputRange ); + Layout.Add( Create( inputRange ) ); + + SerializedObject.TryGetProperty( nameof( Controller.OutputParameter ), out var outputType ); + Layout.Add( Create( outputType ) ); + SerializedObject.TryGetProperty( nameof( Controller.OutputRange ), out var outputRange ); + Layout.Add( Create( outputRange ) ); + } + + protected override void OnPaint() { } +} diff --git a/Editor/ESEditor/EngineStreamEditor.cs b/Editor/ESEditor/EngineStreamEditor.cs new file mode 100644 index 0000000..8511ec7 --- /dev/null +++ b/Editor/ESEditor/EngineStreamEditor.cs @@ -0,0 +1,144 @@ +using Editor; +using Sandbox; +using static Editor.Inspectors.AssetInspector; +using static VeloX.EngineStream; + +namespace VeloX; +[Dock( "Editor", "Engine Stream Editor", "local_fire_department" )] +[CanEdit( "asset:engstr" )] +internal sealed class EngineStreamEditor : Widget, IAssetInspector +{ + + public EngineStreamEditor( Widget parent ) : base( parent, false ) + { + CreateEditor(); + } + public static EngineStreamEditor Instance { get; private set; } + private EngineStream ActiveStream; + private SerializedObject StreamSO; + private ScrollArea StreamTabs; + + //[EditorEvent.Hotload] + //internal void Reload() + //{ + // if ( Instance.IsValid() ) + // CreateEditor(); + //} + + //[Menu( "Editor", "VeloX/Engine Stream Editor", "time_to_leave" )] + internal static void Open() + { + Instance = new EngineStreamEditor(); + } + + internal EngineStreamEditor() + { + + Size = new Vector2( 1300, 700 ); + CreateEditor(); + Show(); + } + + private class LayerObject + { + public Layer Layer { get; set; } + public string Id { get; set; } + } + + + private void CreateEditor() + { + WindowTitle = "Engine Stream Editor"; + Layout = Layout.Column(); + //Canvas = new( null ) + //{ + // Layout = Layout, + //}; + + CreateHeader(); + + var b = Layout.AddRow(); + + CreateTabs( b ); + CreateRightMenu( b ); + } + private void CreateHeader() + { + + var a = Layout.AddRow(); + a.Margin = new Sandbox.UI.Margin( 8, 12, 8, 16 ); + a.Alignment = TextFlag.Left; + a.Spacing = 12; + + var fileButton = a.Add( new Button( "Open File" ), 0 ); + fileButton.Clicked = () => + { + var picker = AssetPicker.Create( this, + AssetType.FromType( typeof( EngineStream ) ), + new() { EnableCloud = true, SeparateCloudTab = false } + ); + + picker.OnAssetPicked += asset => CreateStreamEditor( asset[0].LoadResource() ); + picker.Show(); + }; + } + Layout RightLayout; + SimulatedEngineWidget SimulatedEngineWidget; + private void CreateRightMenu( Layout layout ) + { + RightLayout = layout.AddColumn( 1 ); + RightLayout.Alignment = TextFlag.Top; + RebuildRightMenu(); + } + + private void RebuildRightMenu() + { + + RightLayout.Clear( true ); + RightLayout.Spacing = 12; + var layoutTop = RightLayout.AddColumn(); + SimulatedEngineWidget = new SimulatedEngineWidget( null, ActiveStream ); + layoutTop.Add( SimulatedEngineWidget ); + if ( StreamSO is not null ) + layoutTop.Add( new ParametersWidget( StreamSO.GetProperty( nameof( EngineStream.Parameters ) ) ) ); + + } + + private void CreateTabs( Layout layout ) + { + + StreamTabs = layout.Add( new ScrollArea( this ), 5 ); + StreamTabs.Canvas = new Widget( StreamTabs ) + { + Layout = Layout.Column(), + VerticalSizeMode = (SizeMode)3, + }; + + StreamTabs.Canvas.Layout.Margin = new Sandbox.UI.Margin( 8, 8, 16, 8 ); + StreamTabs.Canvas.Layout.Alignment = TextFlag.Top; + + } + + public void RebuildLayers() + { + StreamTabs.Canvas.Layout.Clear( true ); + StreamSO = ActiveStream.GetSerialized(); + + if ( !StreamSO.TryGetProperty( nameof( EngineStream.Layers ), out var layers ) ) + return; + + StreamTabs.Canvas.Layout.Add( new LayersEditorWidget( layers, ActiveStream, SimulatedEngineWidget ) ); + } + + private void CreateStreamEditor( EngineStream stream ) + { + ActiveStream = stream; + StreamSO = ActiveStream.GetSerialized(); + RebuildRightMenu(); + RebuildLayers(); + } + public void SetAsset( Asset asset ) + { + CreateStreamEditor( asset.LoadResource() ); + } +} diff --git a/Editor/ESEditor/LayerObjectWidget.cs b/Editor/ESEditor/LayerObjectWidget.cs new file mode 100644 index 0000000..eb71a9d --- /dev/null +++ b/Editor/ESEditor/LayerObjectWidget.cs @@ -0,0 +1,154 @@ + +using Editor; +using Sandbox; +using System.Numerics; +using VeloX.Audio; +using static VeloX.EngineStream; + +namespace VeloX; +internal class LayerObjectWidget : ControlObjectWidget +{ + private SerializedProperty Index; + + private SerializedCollection Controllers; + private class ControllerWidget : ControlObjectWidget + { + public ControllerWidget( LayerObjectWidget parent, SerializedProperty property ) : base( property, true ) + { + PaintBackground = false; + Layout = Layout.Row(); + HorizontalSizeMode = (SizeMode)3; + //SetStyles( "background-color: #123; color: white; font-weight: 600;" ); + SerializedObject.TryGetProperty( nameof( Controller.InputParameter ), out var inputType ); + Layout.Add( EnumControlWidget.Create( inputType ), 1 ); + SerializedObject.TryGetProperty( nameof( Controller.InputRange ), out var inputRange ); + Layout.Add( ValueRangeWidget.Create( inputRange ), 12 ); + + SerializedObject.TryGetProperty( nameof( Controller.OutputParameter ), out var outputType ); + Layout.Add( EnumControlWidget.Create( outputType ), 1 ); + SerializedObject.TryGetProperty( nameof( Controller.OutputRange ), out var outputRange ); + Layout.Add( ValueRangeWidget.Create( outputRange ), 12 ); + var removeButton = new IconButton( "clear", () => parent.RemoveEntry( property ) ) { ToolTip = "Remove", Background = Theme.ControlBackground, FixedWidth = Theme.RowHeight, FixedHeight = Theme.RowHeight }; + Layout.Add( removeButton ); + } + + } + + void RemoveEntry( SerializedProperty index ) + { + Controllers.Remove( index ); + Rebuild(); + } + private SerializedCollection layers; + + SimulatedEngineWidget SimulatedEngine; + public LayerObjectWidget( SerializedProperty property, SerializedProperty index, SerializedCollection layers, SimulatedEngineWidget SimulatedEngine ) : base( property, true ) + { + + this.SimulatedEngine = SimulatedEngine; + SetStyles( "background-color: #;" ); + + this.layers = layers; + Index = index; + Layout = Layout.Column(); + Layout.SizeConstraint = SizeConstraint.SetMaximumSize; + Layout.Alignment = TextFlag.Top; + Layout.Margin = 4; + Layout.Spacing = 4; + + Rebuild(); + } + protected override void PaintUnder() + { + + base.PaintUnder(); + + if ( SimulatedEngine is null ) + return; + if ( SimulatedEngine.Player is null ) + return; + + var layer = SimulatedEngine.Player.Stream.Layers[Index.GetValue()]; + if ( SimulatedEngine.Player.EngineSounds.TryGetValue( layer, out var handle ) ) + { + + Paint.SetBrush( Color.Gray ); + Rect localRect = base.LocalRect; + var newVolume = localRect.Width * handle.Volume; + + Paint.DrawRect( new( 0, 0, newVolume, localRect.Height ) ); + + Update(); + } + } + + public void Rebuild() + { + using var _ = SuspendUpdates.For( this ); + Layout.Clear( true ); + VerticalSizeMode = SizeMode.CanShrink; + + var layout = Layout.AddRow(); + layout.Alignment = TextFlag.Top; + var id = layout.Add( + StringControlWidget.Create( Index ), 1 + ).HorizontalSizeMode = SizeMode.Flexible; + + layout.Add( + ResourceControlWidget.Create( SerializedObject.GetProperty( nameof( Layer.AudioPath ) ) ), 3 + ).HorizontalSizeMode = SizeMode.Flexible; + + var ctlrs = Layout.AddColumn(); + ctlrs.Spacing = 2; + ctlrs.Margin = 4; + + var hints = ctlrs.AddRow(); + hints.Add( new Label( "Input Type" ), 3 ); + hints.Add( new Label( "Input Min" ), 2 ); + hints.Add( new Label( "Input Max" ), 2 ); + hints.Add( new Label( "Output Type" ), 3 ); + hints.Add( new Label( "Output Min" ), 2 ); + hints.Add( new Label( "Output Max" ), 2 ); + + if ( !SerializedObject.TryGetProperty( nameof( Layer.Controllers ), out var constrollers ) ) + return; + + if ( !constrollers.TryGetAsObject( out var so ) || so is not SerializedCollection sc ) + return; + + Controllers = sc; + Controllers.OnEntryAdded = Rebuild; + Controllers.OnEntryRemoved = Rebuild; + foreach ( var controller in sc ) + { + ctlrs.Add( new ControllerWidget( this, controller ) ); + } + + var footer = ctlrs.AddRow(); + var controls = footer.AddRow(); + controls.Alignment = TextFlag.Left; + controls.Spacing = 4; + + var useRedline = SerializedObject.GetProperty( nameof( Layer.UseRedline ) ); + controls.Add( new BoolControlWidget( useRedline ) ); + controls.Add( new Label( useRedline.Description ) ); + + var isMuted = SerializedObject.GetProperty( nameof( Layer.IsMuted ) ); + controls.Add( new BoolControlWidget( isMuted ) ); + controls.Add( new Label( isMuted.DisplayName ) ); + + var cont = footer.AddRow(); + cont.Spacing = 4; + cont.Alignment = TextFlag.Right; + cont.Add( new Button( "Add Controller" ) ).Clicked = () => + { + Controllers.Add( null ); + }; + + cont.Add( new Button( "Remove Layer" ) ).Clicked = () => + { + layers.RemoveAt( Index.GetValue() ); + }; + + } +} diff --git a/Editor/ESEditor/LayersEditorWidget.cs b/Editor/ESEditor/LayersEditorWidget.cs new file mode 100644 index 0000000..cc36807 --- /dev/null +++ b/Editor/ESEditor/LayersEditorWidget.cs @@ -0,0 +1,49 @@ + +using Editor; +using Sandbox; +using System.IO; + +namespace VeloX; + + +internal sealed class LayersEditorWidget : ControlObjectWidget +{ + public EngineStream Stream; + SimulatedEngineWidget SimulatedEngineWidget; + public LayersEditorWidget( SerializedProperty property, EngineStream stream, SimulatedEngineWidget SimulatedEngine ) : base( property, true ) + { + SimulatedEngineWidget = SimulatedEngine; + PaintBackground = false; + Stream = stream; + Layout = Layout.Column(); + Layout.SizeConstraint = SizeConstraint.SetMaximumSize; + Layout.Spacing = 12; + + SetStyles( "background-color:rgba(0,0,0,0);" ); + + if ( SerializedObject is not SerializedCollection sc ) + return; + sc.OnEntryAdded += Rebuild; + sc.OnEntryRemoved += Rebuild; + + Rebuild(); + } + + private void Rebuild() + { + Layout.Clear( true ); + if ( SerializedObject is not SerializedCollection sc ) + return; + + foreach ( var t in sc ) + { + var id = t.GetKey(); + Layout.Add( new LayerObjectWidget( t, id, sc, SimulatedEngineWidget ) ); + } + Layout.Add( new Button( "Add Layer" ) ).Clicked = () => + { + sc.Add( "", null ); + }; + } + +} diff --git a/Editor/ESEditor/ParametersWidget.cs b/Editor/ESEditor/ParametersWidget.cs new file mode 100644 index 0000000..b68c349 --- /dev/null +++ b/Editor/ESEditor/ParametersWidget.cs @@ -0,0 +1,44 @@ +using Editor; +using Sandbox; + +namespace VeloX; + +internal sealed class ParametersWidget : ControlObjectWidget +{ + public ParametersWidget( SerializedProperty property ) : base( property, true ) + { + Layout = Layout.Column(); + Layout.Spacing = 6; + Layout.Margin = new Sandbox.UI.Margin( 8, 8, 16, 8 ); + Layout.Alignment = TextFlag.Top; + Layout.SizeConstraint = SizeConstraint.SetMinimumSize; + + var title = new Label( "Stream Parameters" ); + title.SetStyles( "background-color:rgba(29, 62, 81, 200);" ); + title.Alignment = TextFlag.CenterHorizontally; + title.Margin = 4; + Layout.Add( title ); + + if ( !property.TryGetAsObject( out var so ) ) + return; + + + var StreamTabs = Layout.Add( new ScrollArea( this ) ); + StreamTabs.Canvas = new Widget( StreamTabs ) + { + Layout = Layout.Column(), + VerticalSizeMode = (SizeMode)3, + }; + + StreamTabs.Canvas.Layout.Margin = new Sandbox.UI.Margin( 8, 8, 16, 8 ); + StreamTabs.Canvas.Layout.Alignment = TextFlag.Top; + + foreach ( var item in TypeLibrary.GetType().Properties ) + { + var row = StreamTabs.Canvas.Layout.AddColumn(); + var propetry = so.GetProperty( item.Name ); + row.Add( new Label( propetry.DisplayName ) ); + row.Add( Create( propetry ) ); + } + } +} diff --git a/Editor/ESEditor/SimulatedEngineWidget.cs b/Editor/ESEditor/SimulatedEngineWidget.cs new file mode 100644 index 0000000..003a5b3 --- /dev/null +++ b/Editor/ESEditor/SimulatedEngineWidget.cs @@ -0,0 +1,91 @@ +using Editor; +using Sandbox; + +namespace VeloX; + +internal sealed class SimulatedEngineWidget : Widget +{ + + public FloatSlider Throttle; + public FloatSlider RPM; + + public EngineStreamPlayer Player { get; private set; } + public bool IsPlaying { get; private set; } + public SimulatedEngineWidget( Widget parent, EngineStream stream ) : base( parent ) + { + Player = new EngineStreamPlayer( stream ); + Layout = Layout.Column(); + Layout.Alignment = TextFlag.Top; + Layout.Spacing = 6; + Layout.Margin = new Sandbox.UI.Margin( 8, 8, 16, 8 ); + + + var title = new Label( "Simulated Engine" ); + title.SetStyles( "background-color:rgba(29, 62, 81, 200);" ); + title.Alignment = TextFlag.CenterHorizontally; + title.Margin = 8; + var startButton = new Button( "Toggle Engine" ) + { + Clicked = () => + { + IsPlaying = !IsPlaying; + + } + }; + Layout.Add( title ); + Layout.Add( startButton ); + + var rpmTitle = new Label( "RPM" ); + Layout.Add( rpmTitle ); + + RPM = new FloatSlider( rpmTitle ) + { + Maximum = 1, + Value = 0.5f + }; + Layout.Add( RPM ); + + var throttleTitle = new Label( "Throttle" ); + Layout.Add( throttleTitle ); + + Throttle = new FloatSlider( throttleTitle ) + { + Maximum = 1, + Value = 0.5f + }; + Layout.Add( Throttle ); + } + + [EditorEvent.Hotload] + internal void Dispose() + { + Player.Dispose(); + } + protected override void OnClosed() + { + base.OnClosed(); + Dispose(); + } + public override void OnDestroyed() + { + base.OnDestroyed(); + Dispose(); + } + bool IsRedlining; + + [EditorEvent.Frame] + public void OnFrame() + { + if ( Player.Stream is null ) + return; + Player.EngineState = IsPlaying ? EngineState.Running : EngineState.Off; + + Player.Throttle = Throttle.Value; + Player.RPMPercent = RPM.Value; + IsRedlining = RPM.Value == 1 && !IsRedlining; + Player.IsRedlining = IsRedlining; + + Player.Update( Time.Delta, Vector3.Zero, true ); + + } +} diff --git a/Editor/EngineStreamInspector.cs b/Editor/EngineStreamInspector.cs new file mode 100644 index 0000000..036961b --- /dev/null +++ b/Editor/EngineStreamInspector.cs @@ -0,0 +1,55 @@ +//using Editor; +//using Editor.Assets; +//using Sandbox; +//using VeloX; +//using static Editor.Inspectors.AssetInspector; + +//[CanEdit( "asset:engstr" )] +//public class EngineStreamInspector : Widget, IAssetInspector +//{ +// EngineStream EngineStream; +// ControlSheet MainSheet; + +// public EngineStreamInspector( Widget parent ) : base( parent ) +// { +// Layout = Layout.Column(); +// Layout.Margin = 12; +// Layout.Spacing = 12; + +// MainSheet = new ControlSheet(); + +// Layout.Add( MainSheet, 1 ); + +// } + +// [EditorEvent.Hotload] +// void RebuildSheet() +// { +// if ( EngineStream is null || MainSheet is null ) +// return; + +// Layout.Clear( true ); +// var text = Layout.Add( new Editor.TextEdit() ); +// var but = Layout.Add( new Editor.Button( "Load JSON" ) ); +// but.Clicked += () => +// { +// EngineStream.LoadFromJson( text.PlainText ); +// }; + +// var so = EngineStream.GetSerialized(); + + +// so.OnPropertyChanged += _ => +// { +// EngineStream.StateHasChanged(); +// }; +// Layout.Add( ControlWidget.Create( so.GetProperty( nameof( EngineStream.Layers ) ) ) ); +// Layout.Add( ControlWidget.Create( so.GetProperty( nameof( EngineStream.Parameters ) ) ) ); +// } + +// public void SetAsset( Asset asset ) +// { +// EngineStream = asset.LoadResource(); +// RebuildSheet(); +// } +//} diff --git a/Editor/ValueRangeWidget.cs b/Editor/ValueRangeWidget.cs new file mode 100644 index 0000000..0abbbd8 --- /dev/null +++ b/Editor/ValueRangeWidget.cs @@ -0,0 +1,26 @@ +using Editor; +using Sandbox; +using VeloX.Audio; + +namespace VeloX; + +[CustomEditor( typeof( ValueRange ) )] +public class ValueRangeWidget : ControlObjectWidget +{ + public override bool SupportsMultiEdit => false; + public override bool IsWideMode => true; + + public ValueRangeWidget( SerializedProperty property ) : base( property, true ) + { + + Layout = Layout.Row(); + + SerializedObject.TryGetProperty( nameof( ValueRange.Min ), out var min ); + Layout.Add( Create( min ) ); + + SerializedObject.TryGetProperty( nameof( ValueRange.Max ), out var max ); + Layout.Add( Create( max ) ); + } + + protected override void OnPaint() { } +} diff --git a/UnitTests/LibraryTest.cs b/UnitTests/LibraryTest.cs new file mode 100644 index 0000000..c5cf360 --- /dev/null +++ b/UnitTests/LibraryTest.cs @@ -0,0 +1,18 @@ +using Sandbox; + +[TestClass] +public partial class LibraryTests +{ + [TestMethod] + public void SceneTest() + { + var scene = new Scene(); + using ( scene.Push() ) + { + var go = new GameObject(); + + Assert.AreEqual( 1, scene.Directory.GameObjectCount ); + } + } + +} diff --git a/UnitTests/UnitTest.cs b/UnitTests/UnitTest.cs new file mode 100644 index 0000000..1ae9250 --- /dev/null +++ b/UnitTests/UnitTest.cs @@ -0,0 +1,11 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; + +[TestClass] +public class TestInit +{ + [AssemblyInitialize] + public static void ClassInitialize( TestContext context ) + { + Sandbox.Application.InitUnitTest(); + } +} diff --git a/velox.sbproj b/velox.sbproj new file mode 100644 index 0000000..5aa39dd --- /dev/null +++ b/velox.sbproj @@ -0,0 +1,14 @@ +{ + "Title": "VeloX", + "Type": "library", + "Org": "meteorlab", + "Ident": "velox", + "Schema": 1, + "IncludeSourceFiles": false, + "Resources": null, + "PackageReferences": [], + "EditorReferences": null, + "Mounts": null, + "IsStandaloneOnly": false, + "Metadata": {} +} \ No newline at end of file