Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The Everything Update (UI Overhaul + Bug Fixes) #30

Open
wants to merge 80 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
0d16bca
begin moving scoresaber to leaderboardcore
speecil Oct 13, 2024
f59914c
More stuff i forgot, not working
speecil Oct 14, 2024
b4caac8
Closer, but bsml macros are laughing at me
speecil Oct 15, 2024
b5c2569
somewhere
speecil Oct 15, 2024
fbcc9d4
Fixed Page
speecil Oct 15, 2024
7722966
Merge remote-tracking branch 'upstream/main'
speecil Oct 16, 2024
1479e6b
Showing scores now, still buggy
speecil Oct 16, 2024
ef71707
VERY WORKING STATE
speecil Oct 16, 2024
0805e41
Cache leaderboardInfoMaps and fade cell text in
speecil Oct 17, 2024
9ad13ed
Handle ost
speecil Oct 17, 2024
82bf098
MapInfoModal, fix lb pfp on first launch with less than 10 cells
speecil Oct 17, 2024
2c4c5a2
fix panelview misalignment
speecil Oct 17, 2024
9e37d6a
begin desktop imber ui
speecil Oct 18, 2024
d53b9ae
Ensure seperator is not scaled
speecil Oct 18, 2024
7f41d33
Add graphic raycaster and scaler
speecil Oct 18, 2024
c25e39c
Display Imber on desktop, not clickable rn
speecil Oct 19, 2024
130bc29
Graphic raycaster on viewcontroller
speecil Oct 19, 2024
d92b5d2
Working imber ui + scrubber, cant move it around yet
speecil Oct 21, 2024
e78761b
Move desktop imber ui, add settings, bug fixes
speecil Oct 22, 2024
3a1fdf2
Add hiding replay watermark if it is the users replay
speecil Oct 22, 2024
60c3dde
Fix replay sync
speecil Oct 22, 2024
fc4d7ca
Make sure playback is paused while scrubbing
speecil Oct 22, 2024
2e7f7d6
Fix legacyreplayplayer throwing
speecil Oct 22, 2024
80242d5
Update README.md
speecil Oct 22, 2024
46e6902
C to unlock/lock cursor while in replay
speecil Oct 22, 2024
d8c058f
Merge branch 'main' of https://github.com/speecil/scoresaber-plugin
speecil Oct 22, 2024
5dc64aa
Make tab selector bigger in imber desktop
speecil Oct 22, 2024
90ed814
Fix energyplayer
speecil Oct 22, 2024
70a4d95
smooth profile picture fade in
speecil Oct 23, 2024
602d70e
Improve timebar
speecil Oct 23, 2024
a2cbeb8
Move tweening service, add gaymode and denyah mode back
speecil Oct 23, 2024
4b44641
Enable controller details in score modal
speecil Oct 23, 2024
3795421
forgot about the bsml
speecil Oct 23, 2024
50a2374
Increase score modal height
speecil Oct 23, 2024
9604a51
Ensure time is paused while scrubbing from bg
speecil Oct 23, 2024
ee1e2db
Actually remove LB Patches file :)
speecil Oct 23, 2024
0c15aec
remove LeaderboardMap unity engine include
speecil Nov 4, 2024
5244203
enable scrub keybinds
speecil Nov 4, 2024
4eca372
set if users replay earlier
speecil Nov 4, 2024
bd4806d
save fpfc variable to plugin
speecil Nov 4, 2024
6149138
align panelview prompt left
speecil Nov 4, 2024
890479e
make header text show map info instead
speecil Nov 4, 2024
dddb5a3
add desktop replay ui toggle hint
speecil Nov 4, 2024
32a94f6
make replay ui not italic
speecil Nov 4, 2024
1f4fedb
:)
speecil Nov 4, 2024
fdd30f9
Unskew buttons too
speecil Nov 4, 2024
0c6dd88
add CCT to team
speecil Nov 4, 2024
2e9a016
Promo Banner WIP
speecil Nov 10, 2024
c988bb7
Add arrow key bind to scrub +- 5 seconds
speecil Nov 16, 2024
2c9a812
fade desktop imber
speecil Nov 16, 2024
90b4e81
Rich presence init
speecil Dec 3, 2024
6b5dbe7
Add toggle for rich presence
speecil Dec 4, 2024
b98bc2f
bugfix richpresence
speecil Dec 4, 2024
367c845
Show current beatmap in rich presence, can download / go to beatmap
speecil Dec 5, 2024
cb18a0b
Add map presence UI to profile modal
speecil Dec 10, 2024
b0fad27
Qwasyx Review 1
speecil Dec 11, 2024
5c616cb
Qwasyx review 2
speecil Dec 11, 2024
0ae231b
Publicize DataModels
speecil Dec 11, 2024
4e25796
bugfix
speecil Dec 11, 2024
01f5b59
Qwasyx Review 3
speecil Dec 12, 2024
0c9bfdf
make rich p not on by default, popup modal
speecil Dec 12, 2024
14548c0
Qwasyx review 4
speecil Dec 12, 2024
dfc0c17
Umbra review 1
speecil Dec 14, 2024
16dcb3b
rich disclaimer bug fix
speecil Dec 14, 2024
875fd86
use menu button, umbra review 2
speecil Dec 14, 2024
cd62533
Make header use scoresaber diff colours
speecil Dec 14, 2024
40ae61a
Http client rewrite + small changes
speecil Dec 15, 2024
256182e
remove static http client, only injection
speecil Dec 15, 2024
56968cd
Allow OST score uploading
speecil Dec 15, 2024
8711994
Null check levelAuthorName
speecil Dec 15, 2024
3f7ab10
Update to 1.40, fix arc replay
speecil Dec 17, 2024
17bf7f4
replay consistency
speecil Dec 22, 2024
020100a
yellow menu button
speecil Dec 22, 2024
448f951
Replay updates / bugfixes
speecil Dec 26, 2024
61d0f05
Qwasyx Review 4?
speecil Dec 27, 2024
15dba32
fix timescale text folding itself
speecil Dec 28, 2024
1c21390
handle end of song correctly
speecil Jan 3, 2025
7a69e2b
fix replay softlock
speecil Jan 3, 2025
7624715
More replay consistency
speecil Jan 5, 2025
2198fa7
update gitignore
speecil Jan 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
# ScoreSaber-Plugin
Clone, and goodluck. The open source release of ScoreSaber was done hastily and quite a lot needs to be changed and plenty of "security through obscurity" practices covered by obfuscation can finally be removed, cleaning up the project.
ScoreSaber is Beat Saber's largest leaderboard system for custom songs, hosting 60 million scores across 170,000+ leaderboards, with more than 1 million users worldwide

Everything here is licensed under MIT and we'll be accepting contributors immediately, so if you're looking to help us out, that'd be greatly appreciated, we're going to need it.
![image](https://github.com/user-attachments/assets/f638f92b-d961-46e1-8277-d2628676128a)

Scores can also be viewed on [the website](https://scoresaber.com)

# Installation
## Method 1
- Install from your preferred mod manager
## Method 2
- Download from the [releases](https://github.com/ScoreSaber/scoresaber-plugin/releases/latest)
116 changes: 116 additions & 0 deletions ScoreSaber/Core/AffinityPatches/MenuPresencePatches.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using HarmonyLib;
using ScoreSaber.Core.Services;
using ScoreSaber.Core.Utils;
using SiraUtil.Affinity;
using SiraUtil.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Zenject;

namespace ScoreSaber.Core.AffinityPatches {
public class MenuPresencePatches : IAffinity {

[Inject] private readonly ScoreSaberRichPresenceService _richPresenceService = null;

[AffinityPatch(typeof(SinglePlayerLevelSelectionFlowCoordinator), "HandleStandardLevelDidFinish")]
[AffinityPostfix]
public void HandleStopStandardLevelPostfix() {
var jsonObject = new SceneChangeEvent() {
Timestamp = _richPresenceService.TimeRightNow,
Scene = Scene.menu
};

_richPresenceService.SendUserProfileChannel("scene_change", jsonObject);
}

[AffinityPatch(typeof(SinglePlayerLevelSelectionFlowCoordinator), nameof(SinglePlayerLevelSelectionFlowCoordinator.StartLevel))]
[AffinityPostfix]
public void HandleStartLevelPostfix(SinglePlayerLevelSelectionFlowCoordinator __instance, Action beforeSceneSwitchCallback, bool practice) {

string hash = string.Empty;

if (__instance.selectedBeatmapLevel.hasPrecalculatedData) {
hash = "#" + __instance.selectedBeatmapLevel.levelID;
} else {
hash = __instance.selectedBeatmapLevel.levelID.Split('_')[2];
}

bool isPractice = practice;
Services.GameMode gameMode = Services.GameMode.solo;

if (isPractice) {
gameMode = Services.GameMode.practice;

// this is to privatise the practice mode song, as it would be exposed in the rich presence, still not shown in UI though.
var songeventPrivate = new SongRichPresenceInfo(_richPresenceService.TimeRightNow, gameMode,
"PRACTICE",
string.Empty,
"PRACTICE AUTHOR",
"PRACTICE MAPPER",
"Standard",
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE",
(int)2000000000,
1,
0,
1);

_richPresenceService.SendUserProfileChannel("start_song", songeventPrivate);
return;
}

GameplayModifiers gameplayModifiers = __instance.gameplayModifiers;
int startTime = 0;
if (__instance.isInPracticeView) {
startTime = (int)__instance._practiceViewController._practiceSettings.startSongTime;
}

var songevent = CreateSongStartEvent(__instance.selectedBeatmapLevel, __instance.selectedBeatmapKey, gameplayModifiers, startTime, gameMode);

_richPresenceService.SendUserProfileChannel("start_song", songevent);
}

[AffinityPatch(typeof(MultiplayerLevelSelectionFlowCoordinator), nameof(MultiplayerLevelSelectionFlowCoordinator.HandleLobbyGameStateControllerGameStarted))]
[AffinityPostfix]
public void HandleMultiplayerGameStartPostfix(MultiplayerLevelSelectionFlowCoordinator __instance, ILevelGameplaySetupData levelGameplaySetupData) {

// i dont like this, but i have to do it, just in case the users selected level doesnt match what the game started with
BeatmapLevel beatmapLevel = SongCore.Loader.GetLevelByHash(levelGameplaySetupData.beatmapKey.levelId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not work for multiple reasons:

  • GetLevelByHash requires a hash, not levelid
  • It can only deal with custom maps.

What you really want to use is GetLevelById which fixes both problems

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


GameplayModifiers gameplayModifiers = levelGameplaySetupData.gameplayModifiers;
int startTime = 0;

var songevent = CreateSongStartEvent(beatmapLevel, levelGameplaySetupData.beatmapKey, gameplayModifiers, startTime, GameMode.multiplayer);

_richPresenceService.SendUserProfileChannel("start_song", songevent);
}

private SongRichPresenceInfo CreateSongStartEvent(BeatmapLevel beatmapLevel, BeatmapKey beatmapKey, GameplayModifiers gameplayModifiers, int startTime, GameMode gameMode) {

string hash = string.Empty;

if (!beatmapLevel.levelID.StartsWith("custom_level_")) {
hash = "#" + beatmapLevel.levelID;
} else {
hash = beatmapLevel.levelID.Split('_')[2];
}


var songevent = new SongRichPresenceInfo(_richPresenceService.TimeRightNow, gameMode,
beatmapLevel.songName,
beatmapLevel.songSubName,
BeatmapUtils.FriendlyLevelAuthorName(beatmapLevel.allMappers, beatmapLevel.allLighters),
beatmapLevel.songAuthorName,
beatmapKey.beatmapCharacteristic.SerializedName(),
hash,
(int)beatmapLevel.songDuration,
((int)beatmapKey.difficulty * 2) + 1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we still going with the (by the game long removed) numeric difficulty instead of string difficulty? (Sorry if this was already sufficiently resolved, but this big review is starting to get a bit out of hand and confusing.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numeric would be easier overall, but im fine with changing it if the backend changes it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what sense would numeric be easier? Don't you have to keep converting it everywhere?

startTime,
gameplayModifiers.songSpeedMul);

return songevent;
}
}
}
3 changes: 3 additions & 0 deletions ScoreSaber/Core/AppInstaller.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ScoreSaber.Core.Daemons;
using ScoreSaber.Core.Services;
using System.Reflection;
using Zenject;

Expand All @@ -7,8 +8,10 @@ internal class AppInstaller : Installer {

public override void InstallBindings() {
Plugin.Container = Container;
Container.Bind<PlayerService>().AsSingle();

Container.Bind<ReplayService>().AsSingle().NonLazy();
Container.BindInterfacesAndSelfTo<ScoreSaberRichPresenceService>().AsSingle();
}
}
}
2 changes: 1 addition & 1 deletion ScoreSaber/Core/Daemons/UploadDaemon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ public async Task Seven(ScoreSaberUploadData scoreSaberUploadData, string upload
UploadStatusChanged?.Invoke(UploadStatus.Packaging, "Checking leaderboard ranked status...");

bool ranked = true;
Leaderboard currentLeaderboard = await _leaderboardService.GetCurrentLeaderboard(beatmapKey);
Leaderboard currentLeaderboard = await _leaderboardService.GetCurrentLeaderboard(beatmapKey, beatmapLevel);

if (currentLeaderboard != null) {
ranked = currentLeaderboard.leaderboardInfo.ranked;
Expand Down
38 changes: 37 additions & 1 deletion ScoreSaber/Core/Data/Internal/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace ScoreSaber.Core.Data
{
internal class Settings
{
private static int _currentVersion => 8;
private static int _currentVersion => 9;

public bool hideReplayUI = false;

Expand Down Expand Up @@ -36,6 +36,12 @@ internal class Settings
public bool leftHandedReplayUI { get; set; }
public bool lockedReplayUIMode { get; set; }
public List<SpectatorPoseRoot> spectatorPositions { get; set; }
public Vec2 replayUIPosition { get; set; }
public float replayUISize { get; set; }
public bool startReplayUIHidden { get; set; }
public bool hideWatermarkIfUsersReplay { get; set; }
public bool enableRichPresence { get; set; }
public bool hasAcceptedRichPresenceDisclaimer { get; set; }

internal static string dataPath => "UserData";
internal static string configPath => dataPath + @"\ScoreSaber";
Expand Down Expand Up @@ -65,6 +71,12 @@ public void SetDefaults() {
hasOpenedReplayUI = false;
leftHandedReplayUI = false;
lockedReplayUIMode = false;
replayUIPosition = new Vec2(new Vector2(0.12f, 0.14f));
replayUISize = 1.25f;
startReplayUIHidden = false;
hideWatermarkIfUsersReplay = false;
enableRichPresence = false;
hasAcceptedRichPresenceDisclaimer = false;
SetDefaultSpectatorPositions();
}

Expand Down Expand Up @@ -113,6 +125,14 @@ internal static Settings LoadSettings() {
if(decoded.fileVersion < 8) {
decoded.replayCameraSmoothing = true;
}
if (decoded.fileVersion < 9) {
decoded.replayUIPosition = new Vec2(new Vector2(0.12f, 0.14f));
decoded.replayUISize = 1.25f;
decoded.startReplayUIHidden = false;
decoded.enableRichPresence = false;
decoded.hasAcceptedRichPresenceDisclaimer = false;
decoded.hideWatermarkIfUsersReplay = false;
}
SaveSettings(decoded);
}
return decoded;
Expand All @@ -136,6 +156,22 @@ internal static void SaveSettings(Settings settings) {
}
}

internal struct Vec2 {
[JsonProperty("x")]
internal float x { get; set; }
[JsonProperty("y")]
internal float y { get; set; }

internal Vec2(Vector2 position) {
x = position.x;
y = position.y;
}

internal Vector2 ToVector2() {
return new Vector2(x, y);
}
}

internal struct SpectatorPoseRoot {
[JsonProperty("name")]
internal string name { get; set; }
Expand Down
13 changes: 1 addition & 12 deletions ScoreSaber/Core/Data/Models/LeaderboardUploadData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ internal static ScoreSaberUploadData Create(BeatmapLevel beatmapLevel, BeatmapKe
data.songName = beatmapLevel.songName;
data.songSubName = beatmapLevel.songSubName;
data.songAuthorName = beatmapLevel.songAuthorName;
data.levelAuthorName = friendlyLevelAuthorName(beatmapLevel.allMappers, beatmapLevel.allLighters);
data.levelAuthorName = BeatmapUtils.FriendlyLevelAuthorName(beatmapLevel.allMappers, beatmapLevel.allLighters);
data.bpm = Convert.ToInt32(beatmapLevel.beatsPerMinute);

data.playerName = playerInfo.playerName;
Expand All @@ -83,16 +83,5 @@ internal static ScoreSaberUploadData Create(BeatmapLevel beatmapLevel, BeatmapKe
data.deviceControllerRightIdentifier = VRDevices.GetDeviceControllerRight();
return data;
}

static string friendlyLevelAuthorName(string[] mappers, string[] lighters) {
List<string> mappersAndLighters = new List<string>();
mappersAndLighters.AddRange(mappers);
mappersAndLighters.AddRange(lighters);

if(mappersAndLighters.Count <= 1) {
return mappersAndLighters.FirstOrDefault();
}
return $"{string.Join(", ", mappersAndLighters.Take(mappersAndLighters.Count - 1))} & {mappersAndLighters.Last()}";
}
}
}
92 changes: 73 additions & 19 deletions ScoreSaber/Core/Data/Models/ScoreSaberTeam.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
#pragma warning disable IDE1006 // Naming Styles
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;

namespace ScoreSaber.Core.Data.Models
{
internal class ScoreSaberTeam
{
namespace ScoreSaber.Core.Data.Models {
internal class ScoreSaberTeam {
[JsonProperty("TeamMembers")]
[JsonConverter(typeof(TeamMembersJsonConverter))]
public Dictionary<TeamType, List<TeamMember>> TeamMembers { get; set; }
}

internal class TeamMember
{
internal class TeamMember {
[JsonProperty("Name")]
internal string Name { get; set; }
[JsonProperty("ProfilePicture")]
Expand All @@ -29,17 +28,72 @@ internal class TeamMember
internal string YouTube { get; set; }
}

[JsonConverter(typeof(StringEnumConverter))]
internal enum TeamType
{
Backend,
Frontend,
Mod,
PPv3,
Admin,
RT,
NAT,
QAT,
CAT
internal class TeamType {
private readonly string _value;

private TeamType(string value) => _value = value;

public static readonly TeamType Backend = new TeamType("Backend");
public static readonly TeamType Frontend = new TeamType("Frontend");
public static readonly TeamType Mod = new TeamType("Mod");
public static readonly TeamType PPv3 = new TeamType("PPv3");
public static readonly TeamType Admin = new TeamType("Admin");
public static readonly TeamType RT = new TeamType("RT");
public static readonly TeamType NAT = new TeamType("NAT");
public static readonly TeamType QAT = new TeamType("QAT");
public static readonly TeamType CAT = new TeamType("CAT");
public static readonly TeamType CCT = new TeamType("CCT");


// holds the known team types as of building the plugin, can handle unknown team types
public static TeamType FromString(string value) => value switch {
"Backend" => Backend,
"Frontend" => Frontend,
"Mod" => Mod,
"PPv3" => PPv3,
"Admin" => Admin,
"RT" => RT,
"NAT" => NAT,
"QAT" => QAT,
"CAT" => CAT,
"CCT" => CCT,
_ => new TeamType(value)
};

public override string ToString() => _value;

public override bool Equals(object obj) => obj is TeamType other && _value == other._value;

public override int GetHashCode() => _value.GetHashCode();

public static implicit operator string(TeamType teamType) => teamType._value;
}

internal class TeamMembersJsonConverter : JsonConverter<Dictionary<TeamType, List<TeamMember>>> {
public override Dictionary<TeamType, List<TeamMember>> ReadJson(JsonReader reader, Type objectType, Dictionary<TeamType, List<TeamMember>> existingValue, bool hasExistingValue, JsonSerializer serializer) {
var dictionary = new Dictionary<TeamType, List<TeamMember>>();

var jsonObject = JObject.Load(reader);

foreach (var property in jsonObject.Properties()) {
var teamType = TeamType.FromString(property.Name);

var teamMembers = property.Value.ToObject<List<TeamMember>>(serializer);

dictionary[teamType] = teamMembers;
}

return dictionary;
}

public override void WriteJson(JsonWriter writer, Dictionary<TeamType, List<TeamMember>> value, JsonSerializer serializer) {
writer.WriteStartObject();
foreach (var kvp in value) {
writer.WritePropertyName(kvp.Key.ToString());
serializer.Serialize(writer, kvp.Value);
}
writer.WriteEndObject();
}
}

}
2 changes: 2 additions & 0 deletions ScoreSaber/Core/Data/Wrappers/LeaderboardMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ internal LeaderboardMap(Leaderboard leaderboard, BeatmapLevel beatmapLevel, Beat
}
return leaderboardTableScoreData;
}


}
}
Loading