diff --git a/.editorconfig b/.editorconfig index 61eee91c..0fcb49e4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -121,7 +121,7 @@ csharp_style_var_elsewhere = true:silent #Style - modifiers dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning -csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning +csharp_preferred_modifier_order = public,private,protected,internal,abstract,virtual,sealed,override,static,new,readonly,extern,unsafe,volatile,async:warning #Style - parentheses # Skipped because roslyn cannot separate +-*/ with << >> diff --git a/.gitignore b/.gitignore index 6696ada6..85bad547 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ mono_crash.*.json # Other nuget.config *.lscache +addons/gde_gozen +.tmp diff --git a/README.md b/README.md index 0b2274a8..f7118a6e 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,9 @@ We ❤️ developers! Contributions are welcome — whether it's bug fixes, feat > [!WARNING] > **Nightly is no longer actively maintained.** This guide is for contributing to the **Rewrite** client. The contribution process is the same, but you'll be working with the [Rewrite repository](https://github.com/Rhythia/Client). +> [!WARNING] +> **The first build will fail** because it downloads the addon, just restart godot and rerun the build and it should work. + ### Required Software | Software | Notes | diff --git a/Rhythia.csproj b/Rhythia.csproj index 6905bd64..90648de3 100644 --- a/Rhythia.csproj +++ b/Rhythia.csproj @@ -18,4 +18,27 @@ + + 0.1.0 + https://github.com/Rhythia/gde_gozen/releases/download/v$(AddonBuildVersion)/GDE_GoZen_v9.7_csharp.zip + GdeGozen_v9.7.zip + + + + + + + + + + + + + + + + diff --git a/prefabs/map_button_square.tscn b/prefabs/map_button_square.tscn index 9146a83f..edb3b482 100644 --- a/prefabs/map_button_square.tscn +++ b/prefabs/map_button_square.tscn @@ -5,6 +5,7 @@ [ext_resource type="Texture2D" uid="uid://cacq7g5ijo7sy" path="res://user/skins/default/ui/play/favorite.png" id="2_ywqiq"] [ext_resource type="Texture2D" uid="uid://bp468b5qqj4l1" path="res://user/skins/default/ui/play/grid_cover_background.png" id="3_ii2dr"] [ext_resource type="Theme" uid="uid://dhuhu3mmaew1a" path="res://themes/theme.tres" id="3_xysv8"] +[ext_resource type="Texture2D" uid="uid://degvlg4p0if70" path="res://user/skins/default/ui/buttons/add_video.png" id="6_ii2dr"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ywqiq"] bg_color = Color(1, 1, 1, 1) @@ -115,23 +116,6 @@ grow_horizontal = 2 grow_vertical = 2 expand_mode = 1 -[node name="Favorited" type="TextureRect" parent="Holder" unique_id=2076074980] -visible = false -texture_filter = 6 -custom_minimum_size = Vector2(16, 16) -layout_mode = 1 -anchors_preset = -1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -18.0 -offset_top = 2.0 -offset_right = -2.0 -offset_bottom = 18.0 -grow_horizontal = 0 -pivot_offset = Vector2(8, 8) -texture = ExtResource("2_ywqiq") -expand_mode = 1 - [node name="Separator2" type="ColorRect" parent="Holder" unique_id=972899073] layout_mode = 1 anchors_preset = -1 @@ -189,16 +173,15 @@ anchors_preset = -1 anchor_top = 0.657 anchor_right = 0.721 anchor_bottom = 0.88900006 -offset_left = 39.0 +offset_left = 15.0 offset_top = 3.0199966 -offset_right = 0.05999756 +offset_right = -23.940002 offset_bottom = -8.460007 grow_horizontal = 2 grow_vertical = 0 theme = ExtResource("3_xysv8") text = "0:00" label_settings = SubResource("LabelSettings_hc3kw") -horizontal_alignment = 1 vertical_alignment = 1 autowrap_mode = 3 clip_text = true @@ -249,3 +232,41 @@ offset_right = 15.703194 offset_bottom = -6.6200027 rotation = 1.5707964 color = Color(1, 1, 1, 0.23529412) + +[node name="IconContainer" type="HBoxContainer" parent="Holder" unique_id=2048061899] +layout_mode = 1 +anchors_preset = -1 +anchor_left = 0.93600005 +anchor_top = 0.693 +anchor_right = 0.93600005 +anchor_bottom = 0.693 +offset_left = -34.04001 +offset_top = -0.020004272 +offset_right = -0.040008545 +offset_bottom = 15.979996 +grow_horizontal = 0 +theme_override_constants/separation = 2 +alignment = 2 + +[node name="Video" type="TextureRect" parent="Holder/IconContainer" unique_id=542011967] +visible = false +texture_filter = 6 +custom_minimum_size = Vector2(16, 16) +layout_mode = 2 +texture = ExtResource("6_ii2dr") +expand_mode = 1 + +[node name="FavoriteParent" type="Control" parent="Holder/IconContainer" unique_id=1380362991] +visible = false +custom_minimum_size = Vector2(16, 16) +layout_mode = 2 + +[node name="Favorited" type="TextureRect" parent="Holder/IconContainer/FavoriteParent" unique_id=2116415263] +texture_filter = 6 +custom_minimum_size = Vector2(16, 16) +layout_mode = 0 +offset_right = 16.0 +offset_bottom = 16.0 +pivot_offset_ratio = Vector2(0.5, 0.5) +texture = ExtResource("2_ywqiq") +expand_mode = 1 diff --git a/prefabs/map_button_wide.tscn b/prefabs/map_button_wide.tscn index f5a57e60..0eb70e29 100644 --- a/prefabs/map_button_wide.tscn +++ b/prefabs/map_button_wide.tscn @@ -5,6 +5,7 @@ [ext_resource type="Shader" uid="uid://b38n4irr1f443" path="res://user/skins/default/ui/play/map_button_cover.gdshader" id="3_d10q8"] [ext_resource type="Texture2D" uid="uid://cacq7g5ijo7sy" path="res://user/skins/default/ui/play/favorite.png" id="4_ew23d"] [ext_resource type="Theme" uid="uid://dhuhu3mmaew1a" path="res://themes/theme.tres" id="5_mpifo"] +[ext_resource type="Texture2D" uid="uid://degvlg4p0if70" path="res://user/skins/default/ui/buttons/add_video.png" id="6_84wsl"] [ext_resource type="Shader" uid="uid://d0g8fej0676c" path="res://scripts/shaders/linear_alpha_gradient.gdshader" id="6_frxlp"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4823j"] @@ -49,8 +50,8 @@ shader = ExtResource("6_frxlp") [node name="MapButtonWide" type="Control" unique_id=807495564] self_modulate = Color(0, 0, 0, 0.8784314) -clip_contents = true custom_minimum_size = Vector2(0, 90) +clip_contents = true layout_mode = 3 anchors_preset = 10 anchor_right = 1.0 @@ -99,23 +100,6 @@ grow_vertical = 2 expand_mode = 2 stretch_mode = 6 -[node name="Favorited" type="TextureRect" parent="Holder" unique_id=922682128] -visible = false -texture_filter = 6 -custom_minimum_size = Vector2(24, 24) -layout_mode = 1 -anchors_preset = -1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -32.0 -offset_top = 8.0 -offset_right = -8.0 -offset_bottom = 32.0 -grow_horizontal = 0 -pivot_offset = Vector2(12, 12) -texture = ExtResource("4_ew23d") -expand_mode = 1 - [node name="Title" type="Label" parent="Holder" unique_id=704763863] layout_mode = 1 anchors_preset = -1 @@ -177,3 +161,38 @@ focus_mode = 0 mouse_filter = 1 mouse_default_cursor_shape = 2 flat = true + +[node name="IconContainer" type="HBoxContainer" parent="Holder" unique_id=1116304380] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -32.0 +offset_top = 8.0 +offset_right = -8.0 +offset_bottom = 32.0 +grow_horizontal = 0 +theme_override_constants/separation = 8 + +[node name="Video" type="TextureRect" parent="Holder/IconContainer" unique_id=1926918097] +visible = false +texture_filter = 6 +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +texture = ExtResource("6_84wsl") +expand_mode = 1 + +[node name="FavoriteParent" type="Control" parent="Holder/IconContainer" unique_id=1211224476] +visible = false +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 + +[node name="Favorited" type="TextureRect" parent="Holder/IconContainer/FavoriteParent" unique_id=922682128] +texture_filter = 6 +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_right = 24.0 +offset_bottom = 24.0 +pivot_offset_ratio = Vector2(0.5, 0.5) +texture = ExtResource("4_ew23d") +expand_mode = 1 diff --git a/prefabs/map_info_container.tscn b/prefabs/map_info_container.tscn index db3771d1..2eda6792 100644 --- a/prefabs/map_info_container.tscn +++ b/prefabs/map_info_container.tscn @@ -382,15 +382,13 @@ icon = ExtResource("5_1m5pv") icon_alignment = 1 [node name="Video" type="Button" parent="Info/ScrollContainer/HBoxContainer/Subholder/Buttons" unique_id=1985048325] -visible = false layout_mode = 2 -tooltip_text = "Add Video" +tooltip_text = "Disable Video Background" focus_mode = 0 mouse_default_cursor_shape = 2 theme = ExtResource("4_4d08m") theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_constants/icon_max_width = 24 -disabled = true icon = ExtResource("6_g02bu") icon_alignment = 1 diff --git a/prefabs/spaces/grid.tscn b/prefabs/spaces/grid.tscn index 3e8f3c95..bfe658d4 100644 --- a/prefabs/spaces/grid.tscn +++ b/prefabs/spaces/grid.tscn @@ -1,7 +1,7 @@ [gd_scene format=3 uid="uid://dtkff13bgd1u4"] [ext_resource type="Script" uid="uid://bxx72dwqlrh2o" path="res://scripts/spaces/Grid.cs" id="1_wac2s"] -[ext_resource type="Texture2D" uid="uid://cnqebcj2iorfr" path="res://textures/spaces/grid/grid_tile.png" id="2_83vuq"] +[ext_resource type="Texture2D" uid="uid://cnqebcj2iorfr" path="res://textures/spaces/grid/grid_tile.png" id="2_wac2s"] [sub_resource type="Environment" id="Environment_ge866"] ambient_light_source = 1 @@ -16,7 +16,7 @@ fog_depth_end = 60.0 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_n0yhb"] shading_mode = 0 -albedo_texture = ExtResource("2_83vuq") +albedo_texture = ExtResource("2_wac2s") uv1_scale = Vector3(34, 34, 34) texture_filter = 5 billboard_keep_scale = true diff --git a/project.godot b/project.godot index b22af139..cf4f7cca 100644 --- a/project.godot +++ b/project.godot @@ -59,7 +59,7 @@ project/assembly_name="Rhythia" [editor_plugins] -enabled=PackedStringArray() +enabled=PackedStringArray("res://addons/gde_gozen/plugin.cfg") [file_customization] diff --git a/scenes/game.tscn b/scenes/game.tscn index 1d706810..8aa9ce7b 100644 --- a/scenes/game.tscn +++ b/scenes/game.tscn @@ -220,7 +220,6 @@ texture_filter = 1 texture_repeat = false [sub_resource type="QuadMesh" id="QuadMesh_3p7x2"] -size = Vector2(258.3, 210) [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_d5aou"] @@ -708,8 +707,7 @@ fov = 70.0 size = 5.0 [node name="Video" type="MeshInstance3D" parent="Runner" unique_id=1036410191] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.479675, -0.459572, -99.9929) -visible = false +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -100) material_override = SubResource("StandardMaterial3D_rj3ky") transparency = 1.0 cast_shadow = 0 diff --git a/scenes/main_menu.tscn b/scenes/main_menu.tscn index cf30d5cd..52e98788 100644 --- a/scenes/main_menu.tscn +++ b/scenes/main_menu.tscn @@ -56,6 +56,7 @@ [ext_resource type="Script" uid="uid://dqi64p5jl23cv" path="res://scripts/ui/menu/VersionLabel.cs" id="45_mb34c"] [ext_resource type="Texture2D" uid="uid://8du82a5dxjdk" path="res://user/skins/default/ui/jukebox_play.png" id="48_q1l3m"] [ext_resource type="Texture2D" uid="uid://bpsvcunhigs6c" path="res://user/skins/default/ui/jukebox_skip.png" id="49_7u5pb"] +[ext_resource type="Script" uid="uid://crvc85t37oayi" path="res://scripts/ui/menu/play/ContextMenu.cs" id="57_sgh51"] [sub_resource type="SystemFont" id="SystemFont_lsskb"] font_names = PackedStringArray("Ubuntu") @@ -2105,11 +2106,13 @@ grow_vertical = 0 mouse_filter = 2 alignment = 2 -[node name="ContextMenu" type="Panel" parent="." unique_id=836062335] +[node name="ContextMenu" type="Panel" parent="." unique_id=836062335 node_paths=PackedStringArray("videoDialog")] visible = false offset_right = 150.0 offset_bottom = 120.0 theme_override_styles/panel = SubResource("StyleBoxEmpty_t0m3h") +script = ExtResource("57_sgh51") +videoDialog = NodePath("../VideoDialog") [node name="Background" type="Panel" parent="ContextMenu" unique_id=1072032974] layout_mode = 1 @@ -2130,45 +2133,16 @@ grow_horizontal = 2 grow_vertical = 2 theme_override_constants/separation = 0 -[node name="Favorite" type="Button" parent="ContextMenu/Container" unique_id=1524374689] -custom_minimum_size = Vector2(0, 30) -layout_mode = 2 -size_flags_vertical = 3 -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme = ExtResource("6_engjn") -theme_override_colors/font_color = Color(1, 1, 0.501961, 1) -theme_override_colors/font_hover_color = Color(1, 1, 0.768627, 1) -theme_override_fonts/font = ExtResource("19_c6nra") -text = "Favorite" -flat = true -alignment = 0 - -[node name="Delete" type="Button" parent="ContextMenu/Container" unique_id=559400908] -custom_minimum_size = Vector2(0, 30) -layout_mode = 2 -size_flags_vertical = 3 -focus_mode = 0 -mouse_default_cursor_shape = 2 -theme = ExtResource("6_engjn") -theme_override_colors/font_color = Color(1, 0.501961, 0.501961, 1) -theme_override_colors/font_hover_color = Color(1, 0.768627, 0.768627, 1) -theme_override_fonts/font = ExtResource("19_c6nra") -text = "Delete" -flat = true -alignment = 0 - -[node name="Video" type="Button" parent="ContextMenu/Container" unique_id=16155190] +[node name="TemplateButton" type="Button" parent="ContextMenu/Container" unique_id=704183841] +visible = false custom_minimum_size = Vector2(0, 30) layout_mode = 2 size_flags_vertical = 3 focus_mode = 0 mouse_default_cursor_shape = 2 theme = ExtResource("6_engjn") -theme_override_colors/font_color = Color(0.501961, 0.501961, 1, 1) -theme_override_colors/font_hover_color = Color(0.768627, 0.768627, 1, 1) theme_override_fonts/font = ExtResource("19_c6nra") -text = "Add Video" +text = "Button" flat = true alignment = 0 diff --git a/scripts/Constants.cs b/scripts/Constants.cs index d94a0154..192ba0a8 100644 --- a/scripts/Constants.cs +++ b/scripts/Constants.cs @@ -50,4 +50,6 @@ public partial class Constants : Node public static readonly Color RANKED_COLOR = new(0x00f23cff); public static readonly Color DOWNLOADED_COLOR = new Color(0x4ebf50ff); + + public static readonly string[] SUPPORTED_VIDEO_EXTENSIONS = ["mp4", "mkv", "webm"]; // in order of preference } diff --git a/scripts/SceneManager.cs b/scripts/SceneManager.cs index b945e033..2a72e2bd 100644 --- a/scripts/SceneManager.cs +++ b/scripts/SceneManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Godot; +using Spaces; public partial class SceneManager : Node { @@ -47,7 +48,7 @@ public static void ReloadCurrentScene() Load(activeScenePath, true); } - public static void Load(string path, bool skipTransition = false, Action callback = null) + public static void Load(string path, bool skipTransition = false, bool forceVoidBg = false, Action callback = null) { bool isSceneLoaded = Scenes.TryGetValue(path, out BaseScene loadedScene); var newScene = isSceneLoaded ? loadedScene : (BaseScene)ResourceLoader.Load(path).Instantiate(); @@ -73,7 +74,7 @@ public static void Load(string path, bool skipTransition = false, Action callbac activeScenePath = path; Scene = newScene; - addScene(newScene); + addScene(newScene, forceVoidBg: forceVoidBg); newScene.Transition.SelfModulate = Color.FromHtml("ffffffff"); Instance @@ -86,7 +87,7 @@ public static void Load(string path, bool skipTransition = false, Action callbac ); } - private static void addScene(BaseScene scene, bool updateSpace = true) + private static void addScene(BaseScene scene, bool updateSpace = true, bool forceVoidBg = false) { if (scene == null || scene.GetParent() == Instance) { @@ -95,7 +96,15 @@ private static void addScene(BaseScene scene, bool updateSpace = true) if (updateSpace) { - addSpace(scene.GetSpace(), scene.AddSpaceAsChild); + if (forceVoidBg) + { + BaseSpace voidSpace = GD.Load("res://prefabs/spaces/void.tscn").Instantiate() as BaseSpace; + addSpace(voidSpace, scene.AddSpaceAsChild); + } + else + { + addSpace(scene.GetSpace(), scene.AddSpaceAsChild); + } } Instance.AddChild(scene); diff --git a/scripts/database/settings/SettingsProfile.cs b/scripts/database/settings/SettingsProfile.cs index d4a5eeb0..44e15ab1 100644 --- a/scripts/database/settings/SettingsProfile.cs +++ b/scripts/database/settings/SettingsProfile.cs @@ -115,6 +115,12 @@ public partial class SettingsProfile [Order] public SettingsItem GameSpace { get; private set; } + /// + /// Use void background when video background is visible + /// + [Order] + public SettingsItem VideoBgVoidSpace { get; private set; } + /// /// Toggles note hit effects for the game space /// @@ -208,14 +214,20 @@ public partial class SettingsProfile /// /// Adjusts the video background dim /// - //[Order] - //public SettingsItem VideoDim { get; private set; } + [Order] + public SettingsItem VideoDim { get; private set; } /// /// Adjusts the scale of the video background /// - //[Order] - //public SettingsItem VideoRenderScale { get; private set; } + // [Order] + // public SettingsItem VideoRenderScale { get; private set; } + + /// + /// Disables video bg for all maps + /// + [Order] + public SettingsItem DisableVideoGlobal { get; private set; } /// /// Toggles Grid Guides @@ -718,6 +730,14 @@ public SettingsProfile() }, }; + VideoBgVoidSpace = new(false) + { + Id = "VideoBgVoidSpace", + Title = "Use Void Space For Video Backgrounds", + Description = "Use the void game space when video background is visible", + Section = SettingsSection.Visual, + }; + SpaceHitEffects = new(true) { Id = "SpaceHitEffects", @@ -916,37 +936,45 @@ public SettingsProfile() }, }; - //VideoDim = new(80) - //{ - //Id = "VideoDim", - //Title = "Video BG Dim", - //Description = "Adjusts the video background dim", - //Section = SettingsSection.Visual, - //Slider = new() - //{ - //Step = 1, - //MinValue = 0, - //MaxValue = 100 - //} - //}; - #endregion #region Video - //VideoRenderScale = new(100) - //{ - //Id = "VideoRenderScale", - //Title = "Video BG Render Scale", - //Description = "Adjusts the scale of the video background", - //Section = SettingsSection.Visual, - //Slider = new() - //{ - //Step = 1, - //MinValue = 0, - //MaxValue = 100 - //} - //}; + VideoDim = new(80) + { + Id = "VideoDim", + Title = "Video BG Dim", + Description = "Adjusts the video background dim", + Section = SettingsSection.Visual, + Slider = new() + { + Step = 1, + MinValue = 0, + MaxValue = 100, + }, + }; + + // VideoRenderScale = new(100) + // { + // Id = "VideoRenderScale", + // Title = "Video BG Render Scale", + // Description = "Adjusts the scale of the video background", + // Section = SettingsSection.Visual, + // Slider = new() + // { + // Step = 1, + // MinValue = 0, + // MaxValue = 100 + // } + // }; + + DisableVideoGlobal = new(false) + { + Id = "DisableVideoGlobal", + Title = "Disable All Video Backgrounds", + Description = "Don't show video backgrounds for any map", + Section = SettingsSection.Visual, + }; SimpleHUD = new(false) { diff --git a/scripts/game/Attempt.cs b/scripts/game/Attempt.cs index 7aa32df0..3f96a05d 100644 --- a/scripts/game/Attempt.cs +++ b/scripts/game/Attempt.cs @@ -27,6 +27,7 @@ public partial class Attempt : RefCounted public double Speed; public double StartFrom; public double Length; + public bool DisableVideo; public double Accuracy = 100; public double Health = 100; @@ -70,7 +71,8 @@ public Attempt( CameraMode cameraMode, List mods, string[] players = null, - Replay[] replays = null + Replay[] replays = null, + bool disableVideo = false ) { ID = $"{map.Name}_{OS.GetUniqueId()}_{Time.GetDatetimeStringFromUnixTime((long)Time.GetUnixTimeFromSystem())}".Replace(":", "_"); @@ -89,6 +91,7 @@ public Attempt( HasHealthModifier = Modifiers.Any(mod => mod is IHealthModifier); Objects[typeof(Note)] = [.. map.Notes]; HitsInfo = IsReplay ? Replays[0].Notes : new float[Map.Notes.Length]; + DisableVideo = disableVideo; if (IsReplay) { diff --git a/scripts/game/Runner.cs b/scripts/game/Runner.cs index 58eb0a8f..ad8a158b 100644 --- a/scripts/game/Runner.cs +++ b/scripts/game/Runner.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Security.Cryptography; +using System.Threading.Tasks; using Godot; public partial class Runner : Node3D @@ -55,9 +56,15 @@ public partial class Runner : Node3D [Export] public MeshInstance3D Cursor; - // [Export] public VideoStreamPlayer VideoStreamPlayer; + [Export] + public MeshInstance3D VideoMesh; + + [Export] + public SubViewport VideoViewport; + + public VideoPlayback VideoPlayer; - public override void _Ready() + public override async void _Ready() { base._Ready(); @@ -66,7 +73,8 @@ public override void _Ready() Renderers ??= GetNode>("Renderers"); Grid ??= HudManager.GetNode("Grid"); Cursor ??= GetNode("Cursor"); - // VideoStreamPlayer ??= GetNode("Video/VideoViewport/VideoStreamPlayer"); + VideoMesh ??= GetNode("Video"); + VideoViewport ??= GetNode("Video/VideoViewport"); } public override void _Process(double delta) @@ -125,7 +133,7 @@ public override void _Process(double delta) } } - // Song state check + // Song and video state check if (Attempt.Map.AudioBuffer != null) { @@ -142,6 +150,26 @@ public override void _Process(double delta) } } + if (!Attempt.DisableVideo && Attempt.Map.VideoBuffer != null && settings.VideoDim < 100 && VideoPlayer != null && VideoPlayer.IsOpen()) + { + double offsetProgress = Attempt.Progress - settings.LocalOffset; + double songEnd = SoundManager.Song.Stream.GetLength() * 1000; + + if (VideoPlayer.CurrentFrame >= VideoPlayer.GetVideoFrameCount()) + { + VideoPlayer.Close(); + } + else if (!VideoPlayer.IsPlaying && offsetProgress >= 0 && offsetProgress < songEnd) + { + VideoPlayer.SeekFrame((int)((float)Attempt.Progress / 1000f * VideoPlayer.GetVideoFramerate())); + VideoPlayer.Play(); + } + else if (VideoPlayer.IsPlaying && offsetProgress >= songEnd) + { + VideoPlayer.Close(); + } + } + // Skip check int passedNotes = ObjectIndicesStart[typeof(Note)]; @@ -353,6 +381,32 @@ public void Play() SoundManager.Song.PitchScale = (float)Speed; } + if (!Attempt.DisableVideo && Attempt.Map.VideoBuffer != null && settings.VideoDim < 100) + { + spawnVideoPlayer(); + + double parallax = (double)settings.CameraParallax; + + double meshHeight = 2 * Math.Abs(Camera.Position.Z - VideoMesh.Position.Z) * Math.Tan(settings.FoV * Math.PI / 180 / 2); + double meshWidth = (double)VideoViewport.Size.X / VideoViewport.Size.Y * meshHeight; + + QuadMesh videoQuad = (QuadMesh)VideoMesh.Mesh; + videoQuad.Size = new Vector2((float)meshWidth + 2 * Constants.BOUNDS.X, (float)meshHeight + 2 * Constants.BOUNDS.Y); + + VideoPlayer.Path = MapUtil.GetMapVideoPath(Map); + VideoPlayer.PlaybackSpeed = (float)Speed; + + VideoMesh.Transparency = 1; + VideoMesh.Visible = true; + + Tween fadeIn = VideoMesh.CreateTween(); + fadeIn + .TweenProperty(VideoMesh, "transparency", (float)(double)settings.VideoDim / 100f, Math.Abs(Attempt.Progress) / 1000) + .SetTrans(Tween.TransitionType.Quad) + .SetEase(Tween.EaseType.In); // assuming Attempt.Progress < 0 + fadeIn.Play(); + } + if (Attempt.IsReplay) { for (int i = 0; i < Attempt.Replays.Length; i++) @@ -373,9 +427,22 @@ public void Pause(bool? pause = null) SoundManager.Song.PitchScale = (float)Speed; SoundManager.Song.StreamPaused = !Playing; + if (!Attempt.DisableVideo && Attempt.Map.VideoBuffer != null && Attempt.Progress >= 0 && VideoPlayer != null && VideoPlayer.IsOpen()) + { + if (Playing) + { + VideoPlayer.Play(); + } + else if (!Playing) + { + VideoPlayer.Pause(); + } + } + if (Playing) { syncSongPosition(); + syncVideoPosition(); } } @@ -413,6 +480,7 @@ public void Seek(double ms) RenderObjects(0); syncSongPosition(); + syncVideoPosition(); // Discord.Client.UpdateEndTime(DateTime.UtcNow.AddSeconds((Time.GetUnixTimeFromSystem() + (Attempt.Map.Length - Attempt.Progress) / 1000 / Speed))); } @@ -460,6 +528,10 @@ public void Stop(bool results = true) return; } + VideoPlayer?.SetCurrentFrame(VideoPlayer.GetVideoFrameCount()); + VideoPlayer?.Close(); + _ = destroyVideoPlayer(); + // give objects a last chance ProcessObjects(); @@ -691,7 +763,51 @@ private void syncSongPosition() } SoundManager.Song.Seek((float)(Attempt.Progress - Attempt.Settings.LocalOffset) / 1000); - // VideoStreamPlayer.StreamPosition = (float)Attempt.Progress / 1000; } } + + private void syncVideoPosition() + { + if (!Attempt.Map.DisableVideo && Attempt.Map.VideoBuffer != null && VideoPlayer != null && VideoPlayer.IsOpen()) + { + if (!VideoPlayer.IsPlaying && Playing) + { + VideoPlayer.Play(); + } + + VideoPlayer.SeekFrame((int)((float)Attempt.Progress / 1000f * VideoPlayer.GetVideoFramerate())); + } + } + + private void spawnVideoPlayer() + { + if (VideoPlayer != null) + { + Logger.Log("VideoPlayer is already populated. Skipping creation."); + return; + } + + VideoPlayback player = new VideoPlayback { Name = "VideoPlayer", EnableAudio = false }; + + player.SetAnchorsPreset(Control.LayoutPreset.FullRect); + + VideoPlayer = player; + + VideoViewport.AddChild(VideoPlayer); + } + + private async Task destroyVideoPlayer() + { + if (VideoPlayer == null) + { + Logger.Log("VideoPlayer is already empty. Skipping."); + return; + } + + VideoPlayer.QueueFree(); + + await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); + + VideoPlayer = null; + } } diff --git a/scripts/game/ui/PauseMenu.cs b/scripts/game/ui/PauseMenu.cs index 2c932e1c..d4ef94d9 100644 --- a/scripts/game/ui/PauseMenu.cs +++ b/scripts/game/ui/PauseMenu.cs @@ -48,6 +48,24 @@ public void ShowMenu(bool show = true, bool instant = false) SoundManager.Song.PitchScale = (float)attempt.Speed; SoundManager.Song.StreamPaused = !runner.Playing; + if ( + !attempt.DisableVideo + && attempt.Map.VideoBuffer != null + && attempt.Progress >= 0 + && runner.VideoPlayer != null + && runner.VideoPlayer.IsOpen() + ) + { + if (runner.Playing) + { + runner.VideoPlayer.Play(); + } + else if (!runner.Playing) + { + runner.VideoPlayer.Pause(); + } + } + MenuCursor.Instance.UpdateVisible(Shown && SettingsManager.Instance.Settings.UseCursorInMenus.Value); if (Shown) diff --git a/scripts/map/Map.cs b/scripts/map/Map.cs index 66c9a60d..286b00de 100644 --- a/scripts/map/Map.cs +++ b/scripts/map/Map.cs @@ -90,6 +90,12 @@ public Texture2D Cover [Ignore] public byte[] VideoBuffer { get; set; } = []; + + public bool DisableVideo { get; set; } + + [Ignore] + public string VideoExt { get; set; } = string.Empty; + private Note[] notes; [Ignore] @@ -186,6 +192,8 @@ public Map( public string EncodeMeta() { string path = $"{MapUtil.MapsFolder}/{Name}"; + string? videoPath = MapUtil.GetMapVideoPath(path); + string videoExt = videoPath != null ? Path.GetExtension(videoPath).TrimStart('.') : VideoExt; return Json.Stringify( new Godot.Collections.Dictionary() { @@ -201,8 +209,9 @@ public string EncodeMeta() ["Length"] = Length, ["HasAudio"] = AudioBuffer != null && File.Exists($"{path}/audio.{AudioExt}"), ["HasCover"] = CoverBuffer != null && File.Exists($"{path}/cover.png"), - ["HasVideo"] = VideoBuffer != null && File.Exists($"{path}/video.mp4"), + ["HasVideo"] = VideoBuffer != null && videoPath != null && File.Exists(videoPath), ["AudioExt"] = AudioExt, + ["VideoExt"] = videoExt, }, "\t" ); diff --git a/scripts/map/MapManager.cs b/scripts/map/MapManager.cs index 7cdbe2d5..90ffbfb4 100644 --- a/scripts/map/MapManager.cs +++ b/scripts/map/MapManager.cs @@ -55,9 +55,11 @@ public static void InsertVideo(Map map, string path) { Godot.FileAccess file = Godot.FileAccess.Open(path, Godot.FileAccess.ModeFlags.Read); byte[] videoBuffer = file.GetBuffer((long)file.GetLength()); + string ext = Path.GetExtension(path).TrimStart('.'); file.Close(); map.VideoBuffer = videoBuffer; + map.VideoExt = ext; var oldmap = MapParser.Decode(map.FolderPath); @@ -65,11 +67,13 @@ public static void InsertVideo(Map map, string path) map.AudioBuffer = oldmap.AudioBuffer; map.CoverBuffer = oldmap.CoverBuffer; - Directory.Delete(map.FolderPath); + Directory.Delete(map.FolderPath, true); + MapCache.RemoveMap(map); MapParser.Encode(map); - map.FolderPath = MapCache.GetMd5Checksum(map.FolderPath); + map.MetadataObjectHash = MapCache.GetMd5Checksum(map.FolderPath); Update(map); + MapInfo.Instance.InfoContainer.Refresh(true, true); } public static void RemoveVideo(Map map) @@ -77,6 +81,7 @@ public static void RemoveVideo(Map map) _ = Godot.FileAccess.Open(map.FolderPath, Godot.FileAccess.ModeFlags.Read); map.VideoBuffer = null; + map.VideoExt = string.Empty; var oldmap = MapParser.Decode(map.FolderPath); @@ -84,11 +89,13 @@ public static void RemoveVideo(Map map) map.AudioBuffer = oldmap.AudioBuffer; map.CoverBuffer = oldmap.CoverBuffer; - File.Delete(map.FolderPath); + Directory.Delete(map.FolderPath, true); + MapCache.RemoveMap(map); MapParser.Encode(map); map.MetadataObjectHash = MapCache.GetMd5Checksum(map.FolderPath); Update(map); + MapInfo.Instance.InfoContainer.Refresh(true, true); } public static void Delete(Map map, bool quiet = false) diff --git a/scripts/map/MapParser.cs b/scripts/map/MapParser.cs index 839ec842..69c5be9d 100644 --- a/scripts/map/MapParser.cs +++ b/scripts/map/MapParser.cs @@ -152,7 +152,7 @@ void addAsset(string name, byte[] buffer) if (decodedMap.CoverBuffer != null) addAsset($"cover.png", decodedMap.CoverBuffer); if (decodedMap.VideoBuffer != null) - addAsset($"video.mp4", decodedMap.VideoBuffer); + addAsset($"video{Path.GetExtension(MapUtil.GetMapVideoPath(decodedMap))}", decodedMap.VideoBuffer); } File.WriteAllBytes(exportFilePath, ms.ToArray()); @@ -229,7 +229,7 @@ void addAsset(string name, byte[] buffer) if (map.CoverBuffer != null) addAsset($"cover.png", map.CoverBuffer); if (map.VideoBuffer != null) - addAsset($"video.mp4", map.VideoBuffer); + addAsset($"video{Path.GetExtension(MapUtil.GetMapVideoPath(map))}", map.VideoBuffer); File.WriteAllText(Path.Combine(mapFolderPath, "metadata.json"), map.EncodeMeta()); @@ -708,7 +708,10 @@ public static Map PHXMFolder(string path) if ((bool)metadata["HasVideo"]) { - videoBuffer = File.ReadAllBytes($"{path}/video.mp4"); + string videoPath = File.Exists($"{path}/video.{metadata["VideoExt"]}") + ? $"{path}/video.{metadata["VideoExt"]}" + : MapUtil.GetMapVideoPath(path); + videoBuffer = File.ReadAllBytes(videoPath); } var notes = DecodePHXMO(objectsBuffer); diff --git a/scripts/map/MapUtil.cs b/scripts/map/MapUtil.cs index 2b5d831d..451fa5a7 100644 --- a/scripts/map/MapUtil.cs +++ b/scripts/map/MapUtil.cs @@ -1,6 +1,51 @@ -internal static class MapUtil +using System.Collections.Generic; +using System.IO; +using System.Linq; + +internal static class MapUtil { public static string MapsFolder => $"{Constants.USER_FOLDER}/maps"; public static string MapsCacheFolder => $"{Constants.USER_FOLDER}/cache/maps"; + + public static string? GetMapVideoPath(string mapPath) + { + DirectoryInfo dirInfo = new DirectoryInfo(mapPath); + + FileInfo[] filesInfo = dirInfo.GetFiles(); + Dictionary extPath = []; + + foreach (FileInfo file in filesInfo) + { + if ( + Path.GetFileNameWithoutExtension(file.FullName) == "video" + && Constants.SUPPORTED_VIDEO_EXTENSIONS.Contains(file.Extension.TrimStart('.')) + ) + { + extPath.TryAdd(file.Extension.TrimStart('.'), file.FullName); + } + } + + for (int i = 0; i < Constants.SUPPORTED_VIDEO_EXTENSIONS.Length; i++) + { + if (extPath.TryGetValue(Constants.SUPPORTED_VIDEO_EXTENSIONS[i], out string fullPath)) + { + return fullPath; + } + } + + return null; + } + + public static string? GetMapVideoPath(Map map, bool skipMetadata = false) + { + string mapPath = map.FolderPath; + + if (!string.IsNullOrEmpty(map.VideoExt) && !skipMetadata) + { + return Path.Join(map.FolderPath, $"video.{map.VideoExt}"); + } + + return GetMapVideoPath(mapPath); + } } diff --git a/scripts/scenes/Game.cs b/scripts/scenes/Game.cs index c8765e0b..92a6bdc5 100644 --- a/scripts/scenes/Game.cs +++ b/scripts/scenes/Game.cs @@ -1,4 +1,8 @@ +using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; using Godot; public partial class Game : BaseScene @@ -224,9 +228,22 @@ public static void Play( StartQueued = true; var parsedMap = MapParser.Decode(map.FolderPath, Rhythia.AudioFilePath); - Attempt = new(parsedMap, speed, startFrom, cameraMode, mods, players, replays); + parsedMap.Id = map.Id; - SceneManager.Load("res://scenes/game.tscn"); + string hash = Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(Path.Combine(map.FolderPath, "objects.phxmo")))); + Map mapRow = DatabaseService.Connection.Table().FirstOrDefault(row => row.ObjectHash == hash); + + bool mapDisableVideo = mapRow != null && mapRow.DisableVideo; + bool globalVideoDisable = (bool)SettingsManager.Instance.Settings.DisableVideoGlobal; + bool disableVideo = mapDisableVideo || globalVideoDisable; + + Attempt = new(parsedMap, speed, startFrom, cameraMode, mods, players, replays, disableVideo: disableVideo); + + var metadata = (Godot.Collections.Dictionary)Json.ParseString(File.ReadAllText($"{map.FolderPath}/metadata.json")); + bool hasVideo = (bool)metadata["HasVideo"]; + bool useVoidSpace = (bool)SettingsManager.Instance.Settings.VideoBgVoidSpace && !disableVideo && hasVideo; + + SceneManager.Load("res://scenes/game.tscn", forceVoidBg: useVoidSpace); } public void Restart() @@ -236,6 +253,14 @@ public void Restart() var oldAttempt = Attempt; var map = MapParser.Decode(oldAttempt.Map.FolderPath, Rhythia.AudioFilePath); + + // string hash = Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(Path.Combine(map.FolderPath, "objects.phxmo")))); + // Map mapRow = DatabaseService.Connection.Table().FirstOrDefault(row => row.ObjectHash == hash); + + // bool mapDisableVideo = mapRow != null && mapRow.DisableVideo; + // bool globalVideoDisable = (bool)SettingsManager.Instance.Settings.DisableVideoGlobal; + // bool disableVideo = mapDisableVideo || globalVideoDisable; + Attempt = new( map, oldAttempt.Speed, @@ -243,9 +268,13 @@ public void Restart() oldAttempt.CameraMode, oldAttempt.Modifiers, oldAttempt.Players, - oldAttempt.Replays + oldAttempt.Replays, + oldAttempt.DisableVideo ); - SceneManager.ReloadCurrentScene(); + bool useVoidSpace = + (bool)SettingsManager.Instance.Settings.VideoBgVoidSpace && !oldAttempt.DisableVideo && File.Exists(MapUtil.GetMapVideoPath(map)); + + SceneManager.Load("res://scenes/game.tscn", skipTransition: true, forceVoidBg: useVoidSpace); } } diff --git a/scripts/scenes/Results.cs b/scripts/scenes/Results.cs index eec5d68f..46a6cc14 100644 --- a/scripts/scenes/Results.cs +++ b/scripts/scenes/Results.cs @@ -112,9 +112,12 @@ public override void _Ready() if (FileAccess.FileExists(path)) { Replay replay = new(path); + Map replayMap = MapParser.Decode(replay.MapFilePath); + replayMap.Id = Game.Attempt.Map.Id; + replayMap.DisableVideo = Game.Attempt.Map.DisableVideo; SoundManager.Song.Stop(); - Game.Play(MapParser.Decode(replay.MapFilePath), replay.Speed, replay.StartFrom, replay.CameraMode, replay.Modifiers, null, [replay]); + Game.Play(replayMap, replay.Speed, replay.StartFrom, replay.CameraMode, replay.Modifiers, null, [replay]); } }; } @@ -177,6 +180,8 @@ public static void Replay() Map map = MapParser.Decode(attempt.Map.FolderPath); map.Ephemeral = attempt.Map.Ephemeral; + map.Id = attempt.Map.Id; + map.DisableVideo = attempt.Map.DisableVideo; SoundManager.Song.Stop(); Game.Play(map, attempt.Speed, attempt.StartFrom, attempt.CameraMode, attempt.Modifiers); diff --git a/scripts/skinning/SkinManager.cs b/scripts/skinning/SkinManager.cs index 7a538962..1c4b8753 100644 --- a/scripts/skinning/SkinManager.cs +++ b/scripts/skinning/SkinManager.cs @@ -94,8 +94,8 @@ public static void Load() skin.UnfavoriteButtonImage = loadTexture("ui/buttons/unfavorite.png"); skin.CopyButtonImage = loadTexture("ui/buttons/copy.png"); skin.DeleteButtonImage = loadTexture("ui/buttons/delete.png"); - skin.AddVideoButtonImage = loadTexture("ui/buttons/add_video.png"); - skin.RemoveVideoButtonImage = loadTexture("ui/buttons/remove_video.png"); + skin.VideoEnabledButtonImage = loadTexture("ui/buttons/add_video.png"); + skin.VideoDisabledButtonImage = loadTexture("ui/buttons/remove_video.png"); skin.GrabberNormalImage = loadTexture("ui/buttons/grabber_normal.png"); skin.GrabberPressedImage = loadTexture("ui/buttons/grabber_pressed.png"); diff --git a/scripts/skinning/SkinProfile.cs b/scripts/skinning/SkinProfile.cs index 751f9cfb..d00fff81 100644 --- a/scripts/skinning/SkinProfile.cs +++ b/scripts/skinning/SkinProfile.cs @@ -73,9 +73,9 @@ public partial class SkinProfile : RefCounted public ImageTexture DeleteButtonImage { get; set; } = new(); - public ImageTexture AddVideoButtonImage { get; set; } = new(); + public ImageTexture VideoEnabledButtonImage { get; set; } = new(); - public ImageTexture RemoveVideoButtonImage { get; set; } = new(); + public ImageTexture VideoDisabledButtonImage { get; set; } = new(); public ImageTexture GrabberNormalImage { get; set; } = new(); diff --git a/scripts/ui/menu/play/ContextMenu.cs b/scripts/ui/menu/play/ContextMenu.cs new file mode 100644 index 00000000..8d9ca41f --- /dev/null +++ b/scripts/ui/menu/play/ContextMenu.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; + +public partial class ContextMenu : Panel +{ + public static ContextMenu Instance; + + public static bool Shown { get; private set; } + + public static Map Map { get; private set; } + + private static Dictionary subscriptions = []; + + private static bool dialogHasAction = false; + + private Button favoriteButton; + + private Button deleteButton; + + private Button videoButton; + + [Export] + private VBoxContainer container; + + [Export] + private Button templateButton; + + [Export] + private FileDialog videoDialog; + + public override void _Ready() + { + Instance = this; + + container ??= GetNode("Container"); + templateButton ??= GetNode public List Maps = []; + private Panel contextMenu; + private TextureRect mask; private TextureRect selectionCursor; private Panel scrollBar; @@ -107,6 +110,7 @@ public override void _Ready() { Instance = this; + contextMenu = Owner.GetNode("ContextMenu"); mask = GetNode("Mask"); selectionCursor = GetNode("SelectionCursor"); scrollBar = GetNode("ScrollBar"); @@ -124,7 +128,7 @@ public override void _Ready() MouseExited += () => { - toggleSelectionCursor(false); + ToggleSelectionCursor(false); }; Resized += clear; SkinManager.Instance.Loaded += UpdateSkin; @@ -562,10 +566,13 @@ private MapButton setupButton(MapButton button) { if (hovered) { - hoveredButton = button; + if (!SelectionCursorLockDisplay) + { + hoveredButton = button; + } if (Layout == ListLayout.List) { - toggleSelectionCursor(true); + ToggleSelectionCursor(true); } } @@ -574,21 +581,39 @@ private MapButton setupButton(MapButton button) button.UpdateOutline(hovered ? 0.5f : 0); } }; - button.Pressed += () => + + button.GuiInput += action => { - if (dragDistance < 500 && Select(button.Map)) + if (action is InputEventMouseButton mouseButton && mouseButton.Pressed) { - button.Select(); - button.UpdateOutline(1.0f); + if (mouseButton.ButtonIndex == MouseButton.Left) + { + if (dragDistance < 500 && Select(button.Map)) + { + button.Select(); + button.UpdateOutline(1.0f); + } + } + if (mouseButton.ButtonIndex == MouseButton.Right) + { + ContextMenu.Show(mouseButton.GlobalPosition, button.Map); + ToggleSelectionCursor(true, true); + GetViewport().SetInputAsHandled(); + } } }; return button; } - private void toggleSelectionCursor(bool display) + public void ToggleSelectionCursor(bool display, bool? lockDisplay = null) { - if (DisplaySelectionCursor == display) + if (lockDisplay != null) + { + SelectionCursorLockDisplay = lockDisplay.Value; + } + + if (SelectionCursorLockDisplay || DisplaySelectionCursor == display) { return; } diff --git a/scripts/ui/menu/play/ScorePanel.cs b/scripts/ui/menu/play/ScorePanel.cs index e32b88c4..cde5295d 100644 --- a/scripts/ui/menu/play/ScorePanel.cs +++ b/scripts/ui/menu/play/ScorePanel.cs @@ -1,10 +1,14 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Security.Cryptography; using Godot; public partial class ScorePanel : Panel { + public Map Map; + public Leaderboard.Score Score; public Button Button; @@ -90,6 +94,11 @@ public void Replay() Replay replay = new(replayPath); var map = MapParser.Decode(replay.MapFilePath); + string hash = Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(Path.Combine(map.FolderPath, "objects.phxmo")))); + Map mapRow = DatabaseService.Connection.Table().FirstOrDefault(row => row.ObjectHash == hash); + + map.DisableVideo = mapRow != null && mapRow.DisableVideo; + Game.Play(map, replay.Speed, replay.StartFrom, replay.CameraMode, replay.Modifiers, null, [replay]); } } diff --git a/textures/throbber.png.import b/textures/throbber.png.import index f34e29e8..e5eff5ab 100644 --- a/textures/throbber.png.import +++ b/textures/throbber.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://c8l35lu3xsl07" -path="res://.godot/imported/throbber.png-a38f9a9185bc57fb76987fda3b847e30.ctex" +path.s3tc="res://.godot/imported/throbber.png-a38f9a9185bc57fb76987fda3b847e30.s3tc.ctex" +path.etc2="res://.godot/imported/throbber.png-a38f9a9185bc57fb76987fda3b847e30.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://textures/throbber.png" -dest_files=["res://.godot/imported/throbber.png-a38f9a9185bc57fb76987fda3b847e30.ctex"] +dest_files=["res://.godot/imported/throbber.png-a38f9a9185bc57fb76987fda3b847e30.s3tc.ctex", "res://.godot/imported/throbber.png-a38f9a9185bc57fb76987fda3b847e30.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -23,7 +25,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" @@ -37,4 +39,4 @@ process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 -detect_3d/compress_to=1 +detect_3d/compress_to=0