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
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