├── .gitignore
├── .gitattributes
├── overlayButton.png
├── osu.Game.Rulesets.RurusettoAddon
├── Resources
│ └── Textures
│ │ ├── cover.jpg
│ │ ├── oh_no.png
│ │ ├── default_pfp.png
│ │ ├── rurusetto-logo.png
│ │ └── default_wiki_cover.jpg
├── osu.Game.Rulesets.RurusettoAddon.csproj.user
├── TextureNames.cs
├── UI
│ ├── Menus
│ │ └── LocalisableOsuMenuItem.cs
│ ├── Overlay
│ │ ├── RurusettoToolbarButton.cs
│ │ ├── RurusettoOverlayBackground.cs
│ │ ├── CategorisedTabControlOverlayHeader.cs
│ │ ├── RurusettoOverlay.cs
│ │ └── RurusettoOverlayHeader.cs
│ ├── Users
│ │ ├── UserTab.cs
│ │ ├── VerifiedIcon.cs
│ │ └── DrawableRurusettoUser.cs
│ ├── Wiki
│ │ ├── HomeButton.cs
│ │ ├── IssueButton.cs
│ │ ├── WikiPage.cs
│ │ ├── MarkdownPage.cs
│ │ ├── WikiSubpage.cs
│ │ └── RecommendedBeatmapsPage.cs
│ ├── RurusettoAddonConfigSubsection.cs
│ ├── TogglableScrollContainer.cs
│ ├── RulesetLogo.cs
│ ├── OverlayTab.cs
│ ├── Listing
│ │ ├── ListingTab.cs
│ │ └── DrawableListingEntry.cs
│ ├── DrawableTag.cs
│ ├── RulesetManagementContextMenu.cs
│ ├── RulesetDownloadButton.cs
│ └── RequestFailedDrawable.cs
├── API
│ ├── SubpageListingEntry.cs
│ ├── UserDetail.cs
│ ├── Status.cs
│ ├── Subpage.cs
│ ├── ListingEntry.cs
│ ├── APIUser.cs
│ ├── UserProfile.cs
│ ├── BeatmapRecommendation.cs
│ ├── RulesetDetail.cs
│ └── APIRuleset.cs
├── APIUserStore.cs
├── Configuration
│ └── RurusettoConfigManager.cs
├── osu.Game.Rulesets.RurusettoAddon.csproj
├── Extensions.cs
├── RurusettoAddonRuleset.cs
├── RurusettoIcon.cs
├── Localisation
│ ├── Strings.resx
│ ├── Strings.ru.resx
│ ├── Strings.pl.resx
│ ├── Strings.es.resx
│ └── Strings.cs
├── APIRulesetStore.cs
└── RulesetDownloader.cs
├── LocalizationGenerator
├── Source
│ ├── meta.jsonc
│ ├── en.jsonc
│ ├── pl.jsonc
│ ├── ru.jsonc
│ └── es.jsonc
├── LocalisationGenerator.csproj
└── Program.cs
├── osu.Game.Rulesets.RurusettoAddon.Tests
├── VisualTestRunner.cs
├── TestSceneOverlay.cs
├── TestSceneOsuGame.cs
└── osu.Game.Rulesets.RurusettoAddon.Tests.csproj
├── LICENSE
├── README.md
└── osu.Game.Rulesets.RurusettoAddon.sln
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs/
2 | .vscode/
3 | bin/
4 | obj/
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/overlayButton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutterish/rurusetto-addon/HEAD/overlayButton.png
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/Resources/Textures/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutterish/rurusetto-addon/HEAD/osu.Game.Rulesets.RurusettoAddon/Resources/Textures/cover.jpg
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/Resources/Textures/oh_no.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutterish/rurusetto-addon/HEAD/osu.Game.Rulesets.RurusettoAddon/Resources/Textures/oh_no.png
--------------------------------------------------------------------------------
/LocalizationGenerator/Source/meta.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "default": "en",
3 | "args": {
4 | "page-load-failed": [ "wikiLink" ],
5 | "load-error": [ "errorCode" ]
6 | }
7 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/Resources/Textures/default_pfp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutterish/rurusetto-addon/HEAD/osu.Game.Rulesets.RurusettoAddon/Resources/Textures/default_pfp.png
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/Resources/Textures/rurusetto-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutterish/rurusetto-addon/HEAD/osu.Game.Rulesets.RurusettoAddon/Resources/Textures/rurusetto-logo.png
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/Resources/Textures/default_wiki_cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Flutterish/rurusetto-addon/HEAD/osu.Game.Rulesets.RurusettoAddon/Resources/Textures/default_wiki_cover.jpg
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon.Tests/VisualTestRunner.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework;
2 | using osu.Framework.Platform;
3 | using osu.Game.Tests;
4 |
5 | using DesktopGameHost host = Host.GetSuitableDesktopHost( @"osu", new() { BindIPC = true } );
6 | host.Run( new OsuTestBrowser() );
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/osu.Game.Rulesets.RurusettoAddon.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 |
6 |
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/TextureNames.cs:
--------------------------------------------------------------------------------
1 | namespace osu.Game.Rulesets.RurusettoAddon;
2 |
3 | public static class TextureNames {
4 | public const string HeaderBackground = "Textures/cover.jpg";
5 | public const string DefaultAvatar = "Textures/default_pfp.png";
6 | public const string DefaultCover = "Textures/default_wiki_cover.jpg";
7 | public const string ErrorCover = "Textures/oh_no.png";
8 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon.Tests/TestSceneOverlay.cs:
--------------------------------------------------------------------------------
1 | using osu.Game.Rulesets.RurusettoAddon;
2 | using osu.Game.Rulesets.RurusettoAddon.UI.Overlay;
3 | using osu.Game.Tests.Visual;
4 |
5 | public class TestSceneOverlay : OsuTestScene {
6 | RurusettoOverlay overlay;
7 | public TestSceneOverlay () {
8 | Add( overlay = new RurusettoOverlay( new RurusettoAddonRuleset() ) );
9 | overlay.Show();
10 | }
11 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Menus/LocalisableOsuMenuItem.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Localisation;
2 | using osu.Game.Graphics.UserInterface;
3 |
4 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Menus;
5 |
6 | public class LocalisableOsuMenuItem : OsuMenuItem {
7 | public LocalisableOsuMenuItem ( LocalisableString text, MenuItemType type, Action action ) : base( "???", type, action ) {
8 | Text.Value = text;
9 | }
10 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/API/SubpageListingEntry.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using osu.Framework.Localisation;
3 |
4 | #nullable disable
5 | namespace osu.Game.Rulesets.RurusettoAddon.API;
6 |
7 | public record SubpageListingEntry {
8 | /// Title of the subpage
9 | [JsonProperty( "title" )]
10 | public LocalisableString Title { get; init; }
11 |
12 | /// Slug of the subpage. Use in subpage URL path.
13 | [JsonProperty( "slug" )]
14 | public string Slug { get; init; }
15 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Overlay/RurusettoToolbarButton.cs:
--------------------------------------------------------------------------------
1 | using osu.Game.Overlays.Toolbar;
2 |
3 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Overlay;
4 |
5 | public class RurusettoToolbarButton : ToolbarOverlayToggleButton {
6 | protected override Anchor TooltipAnchor => Anchor.TopRight;
7 |
8 | public RurusettoToolbarButton () {
9 | //Hotkey = GlobalAction.ToggleChat;
10 | }
11 |
12 | [BackgroundDependencyLoader( true )]
13 | private void load ( RurusettoOverlay overlay ) {
14 | StateContainer = overlay;
15 | }
16 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/APIUserStore.cs:
--------------------------------------------------------------------------------
1 | namespace osu.Game.Rulesets.RurusettoAddon;
2 |
3 | public class APIUserStore {
4 | RurusettoAPI API;
5 | APIUser unknownUser;
6 | Dictionary users = new();
7 | public APIUserStore ( RurusettoAPI API ) {
8 | this.API = API;
9 | unknownUser = APIUser.Local( API );
10 | }
11 |
12 | public APIUser GetUser ( int id )
13 | => users.GetOrAdd( id, () => APIUser.FromID( API, id ) );
14 |
15 | public APIUser GetUser ( UserDetail? detail )
16 | => detail?.ID is int id ? GetUser( id ) : unknownUser;
17 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon.Tests/TestSceneOsuGame.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics;
2 | using osu.Framework.Graphics.Shapes;
3 | using osu.Game;
4 | using osu.Game.Tests.Visual;
5 | using osuTK.Graphics;
6 |
7 | public class TestSceneOsuGame : OsuTestScene {
8 | protected override void LoadComplete () {
9 | base.LoadComplete();
10 |
11 | Children = new Drawable[] {
12 | new Box {
13 | RelativeSizeAxes = Axes.Both,
14 | Colour = Color4.Black,
15 | }
16 | };
17 | AddGame( new OsuGame() );
18 | }
19 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Users/UserTab.cs:
--------------------------------------------------------------------------------
1 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Users;
2 |
3 | public class UserTab : OverlayTab {
4 | public readonly APIUser User;
5 | public UserTab ( APIUser user ) {
6 | User = user;
7 | }
8 |
9 | protected override void LoadContent () {
10 | Add( new DrawableRurusettoUser( User, false ) { Height = 80 } );
11 |
12 | User.RequestDetail( profile => {
13 | OnContentLoaded();
14 | }, failure: e => {
15 | API.LogFailure( $"Could not retrieve profile for {User}", e );
16 | OnContentLoaded();
17 | } );
18 | }
19 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/Configuration/RurusettoConfigManager.cs:
--------------------------------------------------------------------------------
1 | using osu.Game.Configuration;
2 | using osu.Game.Rulesets.Configuration;
3 |
4 | namespace osu.Game.Rulesets.RurusettoAddon.Configuration;
5 |
6 | public class RurusettoConfigManager : RulesetConfigManager {
7 | public RurusettoConfigManager ( SettingsStore store, RulesetInfo ruleset, int? variant = null ) : base( store, ruleset, variant ) {
8 | AddBindable( RurusettoSetting.APIAddress, new Bindable( RurusettoAPI.DefaultAPIAddress ) );
9 | }
10 | }
11 |
12 | public enum RurusettoSetting {
13 | APIAddress
14 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Wiki/HomeButton.cs:
--------------------------------------------------------------------------------
1 | using osu.Game.Graphics.UserInterface;
2 |
3 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Wiki;
4 |
5 | public class HomeButton : GrayButton {
6 | RulesetDetail entry;
7 | public HomeButton ( RulesetDetail entry ) : base( FontAwesome.Solid.Home ) {
8 | this.entry = entry;
9 | TooltipText = Localisation.Strings.HomePage;
10 | }
11 |
12 | [BackgroundDependencyLoader( permitNulls: true )]
13 | private void load ( OsuGame game, OsuColour colours ) {
14 | Background.Colour = colours.Blue3;
15 | Icon.Colour = Colour4.White;
16 | Icon.Scale = new osuTK.Vector2( 1.5f );
17 |
18 | if ( !string.IsNullOrWhiteSpace( entry.Source ) ) {
19 | Action = () => {
20 | game?.OpenUrlExternally( entry.Source );
21 | };
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/RurusettoAddonConfigSubsection.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Localisation;
2 | using osu.Game.Overlays.Settings;
3 | using osu.Game.Rulesets.RurusettoAddon.Configuration;
4 |
5 | namespace osu.Game.Rulesets.RurusettoAddon.UI;
6 |
7 | public class RurusettoAddonConfigSubsection : RulesetSettingsSubsection {
8 | public RurusettoAddonConfigSubsection ( Ruleset ruleset ) : base( ruleset ) { }
9 |
10 | protected override LocalisableString Header => Localisation.Strings.SettingsHeader;
11 |
12 | protected override void LoadComplete () {
13 | base.LoadComplete();
14 | var config = (RurusettoConfigManager)Config;
15 |
16 | Add( new SettingsTextBox { LabelText = Localisation.Strings.SettingsApiAddress, Current = config.GetBindable( RurusettoSetting.APIAddress ) } );
17 | }
18 | }
--------------------------------------------------------------------------------
/LocalizationGenerator/LocalisationGenerator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | PreserveNewest
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Users/VerifiedIcon.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Cursor;
2 | using osu.Framework.Input.Events;
3 | using osu.Framework.Localisation;
4 | using osu.Game.Overlays;
5 | using osuTK.Graphics;
6 |
7 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Users;
8 |
9 | public class VerifiedIcon : CompositeDrawable, IHasTooltip {
10 | SpriteIcon icon = null!;
11 |
12 | [BackgroundDependencyLoader]
13 | private void load ( OverlayColourProvider colours ) {
14 | AddInternal( icon = new SpriteIcon {
15 | Icon = FontAwesome.Solid.Certificate,
16 | Colour = colours.Colour1,
17 | RelativeSizeAxes = Axes.Both
18 | } );
19 | }
20 |
21 | protected override bool OnHover ( HoverEvent e ) {
22 | icon.FlashColour( Color4.White, 600 );
23 |
24 | return true;
25 | }
26 |
27 | public LocalisableString TooltipText => Localisation.Strings.CreatorVerified;
28 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Wiki/IssueButton.cs:
--------------------------------------------------------------------------------
1 | using osu.Game.Graphics.UserInterface;
2 |
3 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Wiki;
4 |
5 | public class IssueButton : GrayButton {
6 | RulesetDetail entry;
7 | public IssueButton ( RulesetDetail entry ) : base( FontAwesome.Solid.Exclamation ) {
8 | this.entry = entry;
9 | TooltipText = Localisation.Strings.ReportIssue;
10 | }
11 |
12 | [BackgroundDependencyLoader( permitNulls: true )]
13 | private void load ( OsuGame game ) {
14 | Background.Colour = Colour4.FromHex( "#FF6060" );
15 | Icon.Colour = Colour4.White;
16 | Icon.Scale = new osuTK.Vector2( 1.2f );
17 |
18 | if ( !string.IsNullOrWhiteSpace( entry.Source ) && entry.Source.StartsWith( "https://github.com/" ) ) {
19 | Action = () => {
20 | game?.OpenUrlExternally( entry.Source.TrimEnd( '/' ) + "/issues/new" );
21 | };
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Wiki/WikiPage.cs:
--------------------------------------------------------------------------------
1 | using osu.Game.Overlays;
2 |
3 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Wiki;
4 |
5 | public abstract class WikiPage : CompositeDrawable {
6 | [Resolved]
7 | protected WikiTab Tab { get; private set; } = null!;
8 | [Resolved]
9 | protected RurusettoOverlay Overlay { get; private set; } = null!;
10 | [Resolved]
11 | protected RurusettoAPI API { get; private set; } = null!;
12 | [Resolved]
13 | protected OverlayColourProvider ColourProvider { get; private set; } = null!;
14 | [Resolved]
15 | protected APIUserStore Users { get; private set; } = null!;
16 |
17 | protected readonly APIRuleset Ruleset;
18 |
19 | public WikiPage ( APIRuleset ruleset ) {
20 | RelativeSizeAxes = Axes.X;
21 | AutoSizeAxes = Axes.Y;
22 | Ruleset = ruleset;
23 | }
24 |
25 | public abstract bool Refresh ();
26 |
27 | protected override void LoadComplete () {
28 | base.LoadComplete();
29 | Refresh();
30 | }
31 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/osu.Game.Rulesets.RurusettoAddon.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | osu.Game.Rulesets.Sample
5 | Library
6 | AnyCPU
7 | osu.Game.Rulesets.RurusettoAddon
8 | enable
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/TogglableScrollContainer.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Input.Events;
2 |
3 | namespace osu.Game.Rulesets.RurusettoAddon.UI;
4 |
5 | public class TogglableScrollContainer : OsuScrollContainer {
6 | public TogglableScrollContainer ( Direction direction = Direction.Vertical ) : base( direction ) { }
7 |
8 | protected bool CanScroll => ScrollDirection switch {
9 | Direction.Vertical => DrawHeight < ScrollContent.DrawHeight,
10 | _ => DrawWidth < ScrollContent.DrawWidth
11 | };
12 |
13 | protected override bool OnMouseDown ( MouseDownEvent e )
14 | => CanScroll && base.OnMouseDown( e );
15 |
16 | protected override bool OnDragStart ( DragStartEvent e )
17 | => CanScroll && base.OnDragStart( e );
18 |
19 | public override bool DragBlocksClick => CanScroll && base.DragBlocksClick;
20 |
21 | protected override bool OnHover ( HoverEvent e )
22 | => CanScroll && base.OnHover( e );
23 |
24 | protected override bool OnScroll ( ScrollEvent e )
25 | => CanScroll && base.OnScroll( e );
26 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/API/UserDetail.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #nullable disable
4 | namespace osu.Game.Rulesets.RurusettoAddon.API;
5 |
6 | public record UserDetail {
7 | /// The ID of the user in Rūrusetto database.
8 | [JsonProperty( "id" )]
9 | public int? ID { get; init; }
10 |
11 | [JsonProperty( "user" )]
12 | private UserInfo info { get; init; }
13 |
14 | ///
15 | public string Username => info.Username;
16 |
17 | ///
18 | public string Email => info.Email;
19 |
20 | /// The URL of the user's profile image.
21 | [JsonProperty( "image" )]
22 | public string ProfilePicture { get; init; }
23 | }
24 |
25 | public record UserInfo {
26 | /// Username of request user.
27 | [JsonProperty( "username" )]
28 | public string Username { get; init; }
29 |
30 | /// Email of request user. (Can be blank)
31 | [JsonProperty( "email" )]
32 | public string Email { get; init; }
33 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Wiki/MarkdownPage.cs:
--------------------------------------------------------------------------------
1 | using osu.Game.Graphics.Containers.Markdown;
2 |
3 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Wiki;
4 |
5 | public class MarkdownPage : WikiPage {
6 | ContentMarkdown? content;
7 | public MarkdownPage ( APIRuleset ruleset ) : base( ruleset ) { }
8 |
9 | string text = "";
10 | public string Text {
11 | get => text;
12 | set {
13 | text = value;
14 | if ( content != null )
15 | content.Text = value;
16 | }
17 | }
18 |
19 | public override bool Refresh () {
20 | ClearInternal();
21 |
22 | var address = API.GetEndpoint( Ruleset.Slug is null ? "/rulesets" : $"/rulesets/{Ruleset.Slug}" ).AbsoluteUri;
23 | AddInternal( content = new ContentMarkdown( address ) {
24 | RelativeSizeAxes = Axes.X,
25 | AutoSizeAxes = Axes.Y,
26 | Text = text
27 | } );
28 |
29 | return true;
30 | }
31 |
32 | private class ContentMarkdown : OsuMarkdownContainer {
33 | public ContentMarkdown ( string address ) {
34 | DocumentUrl = address;
35 | var uri = new Uri( address );
36 | RootUrl = $"{uri.Scheme}://{uri.Host}";
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Flutterish
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon.Tests/osu.Game.Rulesets.RurusettoAddon.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | false
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | WinExe
21 | net6.0
22 | osu.Game.Rulesets.RurusettoAddon.Tests
23 |
24 |
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Wiki/WikiSubpage.cs:
--------------------------------------------------------------------------------
1 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Wiki;
2 |
3 | public class WikiSubpage : WikiPage {
4 | string slug;
5 | public WikiSubpage ( APIRuleset ruleset, string slug ) : base( ruleset ) {
6 | this.slug = slug;
7 | }
8 |
9 | public override bool Refresh () {
10 | ClearInternal();
11 |
12 | Ruleset.FlushSubpage( slug );
13 | var content = new FillFlowContainer {
14 | RelativeSizeAxes = Axes.X,
15 | AutoSizeAxes = Axes.Y,
16 | Direction = FillDirection.Vertical
17 | };
18 | AddInternal( content );
19 |
20 | Overlay.StartLoading( Tab );
21 | Ruleset.RequestSubpage( slug, subpage => {
22 | var markdown = new MarkdownPage( Ruleset ) { Text = subpage.Content ?? "" };
23 | content.Child = markdown;
24 | Overlay.FinishLoadiong( Tab );
25 |
26 | }, failure: e => {
27 | content.Add( new Container {
28 | Padding = new MarginPadding { Horizontal = -32 },
29 | AutoSizeAxes = Axes.Y,
30 | RelativeSizeAxes = Axes.X,
31 | Child = new RequestFailedDrawable {
32 | ContentText = Localisation.Strings.PageFetchError,
33 | ButtonClicked = () => Refresh()
34 | }
35 | } );
36 |
37 | Overlay.FinishLoadiong( Tab );
38 | } );
39 |
40 | return true;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/API/Status.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #nullable disable
4 | namespace osu.Game.Rulesets.RurusettoAddon.API;
5 |
6 | public record Status {
7 | /// The latest version name of the ruleset.
8 | [JsonProperty( "latest_version" )]
9 | public string LatestVersion { get; init; }
10 | /// The time on ruleset's latest update in JSON time format.
11 | [JsonProperty( "latest_update" )]
12 | public DateTime? LatestUpdate { get; init; }
13 |
14 | /// True if the ruleset is marked as pre-release in GitHub Release.
15 | [JsonProperty( "pre_realase" )]
16 | public bool IsPrerelease { get; init; }
17 | /// The latest changelog of the ruleset in markdown format.
18 | [JsonProperty( "changelog" )]
19 | public string Changelog { get; init; }
20 | /// The size of the latest release file in bytes.
21 | [JsonProperty( "file_size" )]
22 | public int FileSize { get; init; }
23 | /// The status about the playable of the ruleset. Has 3 choices (yes, no, unknown)
24 | [JsonProperty( "playable" )]
25 | public string PlayableStatus { get; init; }
26 |
27 | public bool IsPlayable => PlayableStatus == "yes";
28 | public bool IsBorked => PlayableStatus == "no";
29 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/RulesetLogo.cs:
--------------------------------------------------------------------------------
1 | using osu.Game.Overlays;
2 | using TagLib.IFD;
3 |
4 | namespace osu.Game.Rulesets.RurusettoAddon.UI;
5 |
6 | public class RulesetLogo : CompositeDrawable {
7 | [Resolved]
8 | protected RurusettoAPI API { get; private set; } = null!;
9 |
10 | APIRuleset ruleset;
11 | public bool UseDarkerBackground { get; init; }
12 | public RulesetLogo ( APIRuleset ruleset ) {
13 | this.ruleset = ruleset;
14 | }
15 |
16 | [BackgroundDependencyLoader]
17 | private void load ( OverlayColourProvider colours ) {
18 | var color = UseDarkerBackground ? colours.Background4 : colours.Background3;
19 |
20 | InternalChildren = new Drawable[] {
21 | new Circle {
22 | RelativeSizeAxes = Axes.Both,
23 | Colour = color
24 | }
25 | };
26 |
27 | ruleset.RequestDarkLogo( logo => {
28 | try {
29 | AddInternal( logo );
30 | }
31 | catch {
32 | RemoveInternal( logo, false );
33 | ruleset.RequestDarkLogo( AddInternal, AddInternal, useLocalIcon: false );
34 | }
35 | }, fallback => AddInternal( fallback ) );
36 | }
37 |
38 | bool subtreeWorks = true;
39 | public override bool UpdateSubTree () {
40 | if ( !subtreeWorks )
41 | return false;
42 |
43 | try {
44 | return base.UpdateSubTree();
45 | }
46 | catch {
47 | subtreeWorks = false;
48 | return false;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/API/Subpage.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #nullable disable
4 | namespace osu.Game.Rulesets.RurusettoAddon.API;
5 |
6 | public record Subpage {
7 | /// Details of ruleset.
8 | [JsonProperty( "ruleset_detail" )]
9 | public RulesetDetail RulesetDetail { get; init; }
10 | /// Title of the subpage
11 | [JsonProperty( "title" )]
12 | public string Title { get; init; }
13 | /// Slug of the subpage. Use in subpage URL path.
14 | [JsonProperty( "slug" )]
15 | public string Slug { get; init; }
16 | /// Content of the subpage in markdown format.
17 | [JsonProperty( "content" )]
18 | public string Content { get; init; }
19 | /// user_detail of user who create this page.
20 | [JsonProperty( "creator_detail" )]
21 | public UserDetail Creator { get; init; }
22 | /// user_detail of user who last edited this subpage.
23 | [JsonProperty( "last_edited_by_detail" )]
24 | public UserDetail LastEditedBy { get; init; }
25 | /// The UTC time of the latest wiki edit in JSON time format.
26 | [JsonProperty( "last_edited_at" )]
27 | public DateTime? LastEditedAt { get; init; }
28 | /// The UTC time that the wiki page has create in JSON time format.
29 | [JsonProperty( "created_at" )]
30 | public DateTime? CreatedAt { get; init; }
31 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Overlay/RurusettoOverlayBackground.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Textures;
2 | using osu.Framework.Platform;
3 |
4 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Overlay;
5 |
6 | public class RurusettoOverlayBackground : CompositeDrawable {
7 | public RurusettoOverlayBackground () {
8 | Height = 80;
9 | RelativeSizeAxes = Axes.X;
10 |
11 | Masking = true;
12 | }
13 |
14 | private Dictionary covers = new();
15 | Sprite? currentCover;
16 | Texture defaultCover = null!;
17 |
18 | [BackgroundDependencyLoader]
19 | private void load ( GameHost host, TextureStore textures, RurusettoAddonRuleset ruleset ) {
20 | SetCover( defaultCover = ruleset.GetTexture( host, textures, TextureNames.HeaderBackground ), expanded: true );
21 | }
22 |
23 | public void SetCover ( Texture? cover, bool expanded ) {
24 | cover ??= defaultCover;
25 |
26 | if ( !covers.TryGetValue( cover, out var sprite ) ) {
27 | AddInternal( sprite = new Sprite {
28 | RelativeSizeAxes = Axes.Both,
29 | Texture = cover,
30 | FillMode = FillMode.Fill,
31 | Anchor = Anchor.Centre,
32 | Origin = Anchor.Centre
33 | } );
34 |
35 | covers.Add( cover, sprite );
36 | }
37 |
38 | currentCover?.FadeOut( 400 );
39 | ChangeInternalChildDepth( sprite, (float)Clock.CurrentTime );
40 | sprite.FadeIn();
41 |
42 | currentCover = sprite;
43 |
44 | if ( expanded ) {
45 | this.FadeIn().ResizeHeightTo( 80, 400, Easing.Out );
46 | }
47 | else {
48 | this.ResizeHeightTo( 0, 400, Easing.Out ).Then().FadeOut();
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/OverlayTab.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Input.Events;
2 | using osuTK.Input;
3 |
4 | namespace osu.Game.Rulesets.RurusettoAddon.UI;
5 |
6 | public abstract class OverlayTab : VisibilityContainer {
7 | [Resolved]
8 | protected RurusettoOverlay Overlay { get; private set; } = null!;
9 | [Resolved]
10 | protected RurusettoAPI API { get; private set; } = null!;
11 | [Resolved]
12 | protected RulesetDownloader Downloader { get; private set; } = null!;
13 | [Resolved]
14 | protected APIRulesetStore Rulesets { get; private set; } = null!;
15 | [Resolved]
16 | protected APIUserStore Users { get; private set; } = null!;
17 |
18 | public OverlayTab () {
19 | RelativeSizeAxes = Axes.X;
20 | AutoSizeAxes = Axes.Y;
21 |
22 | Origin = Anchor.TopCentre;
23 | Anchor = Anchor.TopCentre;
24 | }
25 |
26 | protected override void LoadComplete () {
27 | base.LoadComplete();
28 |
29 | LoadContent();
30 | }
31 |
32 | protected override void PopIn () {
33 | this.FadeIn( 200 ).ScaleTo( 1, 300, Easing.Out );
34 | }
35 |
36 | protected override void PopOut () {
37 | this.FadeOut( 200 ).ScaleTo( 0.8f, 300, Easing.Out );
38 | }
39 |
40 | protected override bool StartHidden => true;
41 |
42 | protected abstract void LoadContent ();
43 | protected virtual void OnContentLoaded () { }
44 |
45 | public override bool AcceptsFocus => true;
46 | public override bool RequestsFocus => true;
47 | protected override bool OnKeyDown ( KeyDownEvent e ) {
48 | if ( e.Key is Key.F5 ) { // NOTE o!f doenst seem to have a 'refresh' action
49 | return Refresh();
50 | }
51 |
52 | return false;
53 | }
54 |
55 | public virtual bool Refresh () {
56 | return false;
57 | }
58 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/Extensions.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using System.Linq;
3 | global using System.Collections.Generic;
4 | global using osuTK;
5 | global using osu.Framework.Bindables;
6 | global using osu.Framework.Graphics;
7 | global using osu.Framework.Allocation;
8 | global using osu.Framework.Graphics.Containers;
9 | global using osu.Framework.Graphics.Shapes;
10 | global using osu.Framework.Graphics.Sprites;
11 | global using osu.Game.Graphics;
12 | global using osu.Game.Graphics.Containers;
13 | global using osu.Game.Rulesets.RurusettoAddon.API;
14 | global using osu.Game.Rulesets.RurusettoAddon.UI.Overlay;
15 | global using osu.Framework.Extensions.Color4Extensions;
16 | global using osu.Framework.Graphics.Colour;
17 | global using osu.Framework.Graphics.Primitives;
18 | global using Image = SixLabors.ImageSharp.Image;
19 | using System.Reflection;
20 |
21 | namespace osu.Game.Rulesets.RurusettoAddon;
22 |
23 | public static class Extensions {
24 | public static T GetOrAdd ( this IDictionary self, Tkey key, Func @default ) {
25 | if ( !self.TryGetValue( key, out var value ) )
26 | self.Add( key, value = @default() );
27 |
28 | return value;
29 | }
30 |
31 | public static T GetOrAdd ( this IDictionary self, Tkey key, Func @default, Action after ) {
32 | if ( !self.TryGetValue( key, out var value ) ) {
33 | self.Add( key, value = @default() );
34 | after( value );
35 | }
36 |
37 | return value;
38 | }
39 |
40 | public static T? GetField ( this (Type type, object instance) self, string name ) {
41 | return (T?)self.type.GetField( name, BindingFlags.NonPublic | BindingFlags.Instance )?.GetValue( self.instance );
42 | }
43 |
44 | public static MethodInfo? GetMethod ( this (Type type, object instance) self, string name ) {
45 | return self.type.GetMethod( name, BindingFlags.NonPublic | BindingFlags.Instance )?.MakeGenericMethod( typeof( T1 ) );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/API/ListingEntry.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #nullable disable
4 | namespace osu.Game.Rulesets.RurusettoAddon.API;
5 |
6 | public record ShortListingEntry {
7 | /// The ID of the ruleset in Rūrusetto database.
8 | [JsonProperty( "id" )]
9 | public int ID { get; init; }
10 |
11 | /// The name of the ruleset.
12 | [JsonProperty( "name" )]
13 | public string Name { get; init; }
14 |
15 | /// The slug of the ruleset. Use in the URL of the ruleset's wiki page.
16 | [JsonProperty( "slug" )]
17 | public string Slug { get; init; }
18 |
19 | /// The short description of the rulesets.
20 | [JsonProperty( "description" )]
21 | public string Description { get; init; }
22 |
23 | /// The URL of the ruleset icon that use in website's default theme (dark theme).
24 | [JsonProperty( "icon" )]
25 | public string DarkIcon { get; init; }
26 |
27 | /// The URL of the ruleset icon that use in website's light theme.
28 | [JsonProperty( "light_icon" )]
29 | public string LightIcon { get; init; }
30 |
31 | /// True if the wiki maintainer has verified that the the owner is the real owner of this ruleset.
32 | [JsonProperty( "verified" )]
33 | public bool IsVerified { get; init; }
34 |
35 | /// True if the rulesets is stop update or archived by rulesets creator.
36 | [JsonProperty( "archive" )]
37 | public bool IsArchived { get; init; }
38 |
39 | /// URL for download the latest release of ruleset from GitHub
40 | [JsonProperty( "direct_download_link" )]
41 | public string Download { get; init; }
42 |
43 | ///
44 | /// True if website can render the direct download link from the source and github_download_filename
45 | /// so user can download directly from direct_download_link.
46 | ///
47 | [JsonProperty( "can_download" )]
48 | public bool CanDownload { get; init; }
49 |
50 | /// The status of the ruleset.
51 | [JsonProperty( "status" )]
52 | public Status Status { get; init; }
53 | }
54 |
55 | public record ListingEntry : ShortListingEntry {
56 | /// The user_detail of the ruleset's current owner.
57 | [JsonProperty( "owner_detail" )]
58 | public UserDetail Owner { get; init; }
59 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/API/APIUser.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Textures;
2 |
3 | namespace osu.Game.Rulesets.RurusettoAddon.API;
4 |
5 | public class APIUser {
6 | private APIUser () { }
7 | public static APIUser FromID ( RurusettoAPI API, int ID ) {
8 | return new() {
9 | Source = Source.Web,
10 | API = API,
11 | ID = ID,
12 | HasProfile = true
13 | };
14 | }
15 | public static APIUser Local ( RurusettoAPI API ) {
16 | return new() {
17 | Source = Source.Local,
18 | API = API
19 | };
20 | }
21 |
22 | public Source Source { get; private set; }
23 | private int ID;
24 | private RurusettoAPI? API;
25 |
26 | public bool HasProfile { get; private set; }
27 |
28 | public void RequestDetail ( Action success, Action? failure = null ) {
29 | if ( Source == Source.Web && API != null ) {
30 | API.RequestUserProfile( ID, success, failure );
31 | }
32 | else {
33 | success( new() );
34 | }
35 | }
36 |
37 | public void RequestProfilePicture ( Action success, Action? failure = null ) {
38 | void requestDefault ( Exception? e = null ) {
39 | if ( API != null ) {
40 | API.RequestImage( StaticAPIResource.DefaultProfileImage, success, failure: e => {
41 | failure?.Invoke( e );
42 | } );
43 | }
44 | }
45 |
46 | if ( Source == Source.Web && API != null ) {
47 | RequestDetail( detail => {
48 | if ( !string.IsNullOrWhiteSpace( detail.ProfilePicture ) ) {
49 | API.RequestImage( detail.ProfilePicture, success, requestDefault );
50 | }
51 | else {
52 | requestDefault();
53 | }
54 | }, failure: requestDefault );
55 | }
56 | else {
57 | requestDefault();
58 | }
59 | }
60 |
61 | public void RequestDarkCover ( Action success, Action? failure = null ) {
62 | void requestDefault ( Exception? e = null ) {
63 | if ( API != null ) {
64 | API.RequestImage( StaticAPIResource.DefaultCover, success, failure: e => {
65 | failure?.Invoke( e );
66 | } );
67 | }
68 | }
69 |
70 | if ( Source == Source.Web && API != null ) {
71 | RequestDetail( detail => {
72 | if ( !string.IsNullOrWhiteSpace( detail.DarkCover ) ) {
73 | API.RequestImage( detail.DarkCover, success, requestDefault );
74 | }
75 | else {
76 | requestDefault();
77 | }
78 | }, failure: requestDefault );
79 | }
80 | else {
81 | requestDefault();
82 | }
83 | }
84 |
85 | public override string ToString ()
86 | => Source is Source.Web ? $"User with ID = {ID}" : $"Local user";
87 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/API/UserProfile.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #nullable disable
4 | namespace osu.Game.Rulesets.RurusettoAddon.API;
5 |
6 | public record UserProfile {
7 | /// The ID of the user. Use in URL path to target user's profile page.
8 | [JsonProperty( "id" )]
9 | public int? ID { get; init; }
10 |
11 | [JsonProperty( "user" )]
12 | private UserInfo info { get; init; }
13 |
14 | /// URL of the user's profile picture.
15 | [JsonProperty( "image" )]
16 | public string ProfilePicture { get; init; }
17 |
18 | /// URL of the user's cover picture in website's default theme (Dark theme).
19 | [JsonProperty( "cover" )]
20 | public string DarkCover { get; init; }
21 |
22 | /// URL of the user's cover picture in website's light theme.
23 | [JsonProperty( "cover_light" )]
24 | public string LightCover { get; init; }
25 |
26 | /// User's introduction text on profile page.
27 | [JsonProperty( "about_me" )]
28 | public string Bio { get; init; }
29 |
30 | /// osu! account username of target user (Can be blank)
31 | [JsonProperty( "osu_username" )]
32 | public string OsuUsername { get; init; }
33 |
34 | ///
35 | public string Username => info?.Username;
36 |
37 | ///
38 | public string Email => info?.Email;
39 |
40 | /// List of tag that user has. Will be [] if no tags found in this user.
41 | [JsonProperty( "tags" )]
42 | public List Tags { get; init; }
43 |
44 | /// List of ruleset that user created. Will be [] if no created rulesets found from this user.
45 | [JsonProperty( "created_rulesets" )]
46 | public List CreatedRulesets { get; init; }
47 | }
48 |
49 | public record UserTag {
50 | /// The name of the tag.
51 | [JsonProperty( "name" )]
52 | public string Text { get; init; }
53 |
54 | /// The background color of the tag pills that show in profile. Will return in hex color (e.g. #FFFFFF).
55 | [JsonProperty( "pills_color" )]
56 | public string BackgroundColor { get; init; }
57 |
58 | /// The font color of the tag pills that show in profile. Will return in hex color (e.g. #FFFFFF).
59 | [JsonProperty( "font_color" )]
60 | public string ForegroundColor { get; init; }
61 |
62 | /// The description of the tag.
63 | [JsonProperty( "description" )]
64 | public string Description { get; init; }
65 | }
--------------------------------------------------------------------------------
/LocalizationGenerator/Source/en.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "locale": "en",
3 |
4 | "local-ruleset-description": "Local ruleset, not listed on the wiki.",
5 | // {0} = link to the wiki page
6 | "page-load-failed": "Failed to fetch the ruleset wiki page. Sorry!\nYou can still try visiting it at [rurusetto]({0}).",
7 | "listing-tab": "listing",
8 | "users-tab": "users",
9 | "collections-tab": "collections",
10 | "rurusetto-description": "browse and manage rulesets",
11 | "user-unknown": "Unknown",
12 | "tag-archived": "ARCHIVED",
13 | "tag-archived-tooltip": "This ruleset is no longer maintained",
14 | "tag-local": "UNLISTED",
15 | "tag-local-tooltip": "This ruleset is installed locally, but is not listed on the wiki",
16 | "tag-hardcoded": "HARD CODED",
17 | "tag-hardcoded-tooltip": "This ruleset is hard coded into the game and cannot be modified",
18 | "tag-failed-import": "FAILED IMPORT",
19 | "tag-failed-import-tooltip": "This ruleset is downloaded, but failed to import",
20 | "tag-borked": "BORKED",
21 | "tag-borked-tooltip": "This ruleset does not work",
22 | "tag-playable": "PLAYABLE",
23 | "tag-playable-tooltip": "This ruleset works",
24 | "tag-prerelease": "PRE-RELEASE",
25 | "tag-prerelease-tooltip": "The current version is a pre-release",
26 | "home-page": "Home Page",
27 | "report-issue": "Report Issue",
28 | "download-checking": "Checking...",
29 | "unavailable-online": "Unavailable Online",
30 | "installed-unavailable-online": "Installed, not available online",
31 | "download": "Download",
32 | "redownload": "Re-download",
33 | "downloading": "Downloading...",
34 | "update": "Update",
35 | "remove": "Remove",
36 | "cancel-download": "Cancel Download",
37 | "cancel-remove": "Cancel Removal",
38 | "cancel-update": "Cancel Update",
39 | "refresh": "Refresh",
40 | "to-be-removed": "Will be removed on restart!",
41 | "to-be-installed": "Will be installed on restart!",
42 | "to-be-updated": "Will be updated on restart!",
43 | "installed": "Installed",
44 | "outdated": "Outdated",
45 | "creator-verified": "Verified Ruleset Creator",
46 | // {0} = error code
47 | "load-error": "Could not load rurusetto-addon: Please report this to the rurusetto-addon repository NOT the osu!lazer repository: Code {0}",
48 | "main-page": "Main",
49 | "changelog-page": "Changelog",
50 | "recommended-beatmaps-page": "Recommended Beatmaps",
51 | "unknown-version": "Unknown Version",
52 | "settings-header": "Rurusetto Addon",
53 | "settings-api-address": "API Address",
54 | "untitled-ruleset": "Untitled Ruleset",
55 | "error-header": "Oh no!",
56 | "error-footer": "Please make sure you have an internet connection and the API address in settings is correct",
57 | "retry": "Retry",
58 | "listing-fetch-error": "Could not retrieve the ruleset listing",
59 | "page-fetch-error": "Could not load the page",
60 | "subpages-fetch-error": "Could not retrieve subpages",
61 | "error-message-generic": "Something went wrong, but I don't know what!",
62 | "notification-work-incomplete": "It seems rurusetto addon couldn't finish some work. Please make sure all your changes were applied correctly"
63 | }
--------------------------------------------------------------------------------
/LocalizationGenerator/Source/pl.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "locale": "pl",
3 |
4 | "local-ruleset-description": "Lokalny tryb gry, nie wymieniony na wiki.",
5 | // {0} = link to the wiki page
6 | "page-load-failed": "Nie udało się pobrać strony o tym trybie gry.\nJeśli chcesz, ciągle możesz spróbować na oficjalnej stronie [rurusetto]({0}).",
7 | "listing-tab": "katalog",
8 | "rurusetto-description": "przeglądaj i zarządzaj trybami gry",
9 | "user-unknown": "Nieznany Użytkownik",
10 | "tag-archived": "ARCHIWUM",
11 | "tag-archived-tooltip": "Ten tryb gry nie jest już utrzymywany",
12 | "tag-local": "LOKALNY",
13 | "tag-local-tooltip": "Ten tryb gry jest zainstalowany lokalnie, ale nie ma go na wiki",
14 | "tag-hardcoded": "WBUDOWANY",
15 | "tag-hardcoded-tooltip": "Ten tryb gry jest wbudowany w grę i nie da się go modyfikować",
16 | "tag-failed-import": "BŁĄD IMPORTU",
17 | "tag-failed-import-tooltip": "Ten tryb gry jest pobrany, ale nie udało się go zaimportować",
18 | "tag-borked": "ZDZBANIONY",
19 | "tag-borked-tooltip": "Ten tryb gry nie działa",
20 | "tag-playable": "GRYWALNY",
21 | "tag-playable-tooltip": "Ten tryb gry działa",
22 | "tag-prerelease": "PRE-RELEASE",
23 | "tag-prerelease-tooltip": "Obecna wersja tego trybu gry jest niedokończona",
24 | "home-page": "Strona domowa",
25 | "report-issue": "Zgłoś problem",
26 | "download-checking": "Sprawdzanie...",
27 | "unavailable-online": "Niedostępny Online",
28 | "installed-unavailable-online": "Zainstalowany, ale niedostępny online",
29 | "download": "Pobierz",
30 | "redownload": "Pobierz ponownie",
31 | "downloading": "Pobieranie...",
32 | "update": "Uaktualnij",
33 | "remove": "Usuń",
34 | "cancel-download": "Anuluj Pobieranie",
35 | "cancel-remove": "Anuluj Usuwanie",
36 | "cancel-update": "Anujuj Aktualizacje",
37 | "refresh": "Odśwież",
38 | "to-be-removed": "Zostanie usunięty przy następnym uruchomieniu!",
39 | "to-be-installed": "Zostanie zainstalowany przy następnym uruchomieniu!",
40 | "to-be-updated": "Zostanie uaktualniony przy następnym uruchomieniu!",
41 | "installed": "Zainstalowany",
42 | "outdated": "Przestarzały",
43 | "creator-verified": "Zweryfikowani Twórcy Trybu",
44 | // {0} = error code
45 | "load-error": "Nie udało się uruchomić rurusetto-addon: Proszę, zgłoś to do repozytorium rurusetto-addon i NIE do repozytorium osu!lazer: Kod {0}",
46 | "main-page": "Główna",
47 | "changelog-page": "Zmiany",
48 | "recommended-beatmaps-page": "Polecane Mapy",
49 | "unknown-version": "Nieznana Wersja",
50 | "settings-header": "Zintegrowane Rurusetto",
51 | "settings-api-address": "Adres API",
52 | "untitled-ruleset": "Nienazwany Tryb Gry",
53 | "error-header": "Ups!",
54 | "error-footer": "Upewnij się, że masz połączenie z internetem, a adres API w ustawieniach jest poprawny",
55 | "retry": "Odśwież",
56 | "listing-fetch-error": "Nie udało się pobrać katalogu",
57 | "page-fetch-error": "Nie udało się załadować strony",
58 | "subpages-fetch-error": "Nie udało się pobrać listy podstron",
59 | "error-message-generic": "Coś poszło nie tak, ale nie wiem co!",
60 | "notification-work-incomplete": "Wygląda na to, że wtyczka rurusetto nie dokończyła pracy. Sprawdź, czy wszystkie twoje zmiany zostały pomyślnie zaaplikowane"
61 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/API/BeatmapRecommendation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #nullable disable
4 | namespace osu.Game.Rulesets.RurusettoAddon.API;
5 |
6 | public record BeatmapRecommendation {
7 | /// user_detail of user who recommend this beatmap.
8 | [JsonProperty( "user_detail" )]
9 | public UserDetail Recommender { get; init; }
10 |
11 | /// ID of this beatmap in osu!.
12 | [JsonProperty( "beatmap_id" )]
13 | public int BeatmapID { get; init; }
14 |
15 | /// ID of set of this beatmap in osu!.
16 | [JsonProperty( "beatmapset_id" )]
17 | public int BeatmapSetID { get; init; }
18 |
19 | /// Beatmap's song name.
20 | [JsonProperty( "title" )]
21 | public string Title { get; init; }
22 |
23 | /// Song's artist of this beatmap.
24 | [JsonProperty( "artist" )]
25 | public string Artist { get; init; }
26 |
27 | /// Song's source of this beatmap.
28 | [JsonProperty( "source" )]
29 | public string Source { get; init; }
30 |
31 | /// Name of user in osu! who create this beatmap (mapper).
32 | [JsonProperty( "creator" )]
33 | public string Creator { get; init; }
34 |
35 | /// Approval state of this beatmap (4 = loved, 3 = qualified, 2 = approved, 1 = ranked, 0 = pending, -1 = WIP, -2 = graveyard)
36 | [JsonProperty( "approved" )]
37 | public BeatmapStatus Status { get; init; }
38 |
39 | /// Star rating of this beatmap in osu! mode.
40 | [JsonProperty( "difficultyrating" )]
41 | public float StarDifficulty { get; init; }
42 |
43 | /// BPM of the song in this beatmap.
44 | [JsonProperty( "bpm" )]
45 | public float BPM { get; init; }
46 |
47 | /// Difficulty name of this beatmap in beatmap's beatmapset.
48 | [JsonProperty( "version" )]
49 | public string Version { get; init; }
50 |
51 | /// URL to go to this beatmap in osu! website.
52 | [JsonProperty( "url" )]
53 | public string Url { get; init; }
54 |
55 | /// URL of beatmap's cover image that use as the background in beatmap page.
56 | [JsonProperty( "beatmap_cover" )]
57 | public string CoverImage { get; init; }
58 |
59 | /// URL of beatmap's thumbnail image that use in old osu! site and in osu! stable.
60 | [JsonProperty( "beatmap_thumbnail" )]
61 | public string ThumbnailImage { get; init; }
62 |
63 | /// URL of beatmap's card image that use in new osu! new beatmap card design.
64 | [JsonProperty( "beatmap_card" )]
65 | public string CardImage { get; init; }
66 |
67 | /// URL of beatmap's list image that use in new osu! new beatmap card design.
68 | [JsonProperty( "beatmap_list" )]
69 | public string ListImage { get; init; }
70 |
71 | /// Comment from user who recommend this beatmap.
72 | [JsonProperty( "comment" )]
73 | public string Comment { get; init; }
74 |
75 | /// The time on this recommend beatmap added to the site in JSON time format.
76 | [JsonProperty( "created_at" )]
77 | public DateTime? CreatedAt { get; init; }
78 | }
79 |
80 | public enum BeatmapStatus {
81 | Pending = 0,
82 | Ranked = 1,
83 | Approved = 2,
84 | Qualified = 3,
85 | Loved = 4,
86 | WIP = -1,
87 | Graveyard = -2
88 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/Listing/ListingTab.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace osu.Game.Rulesets.RurusettoAddon.UI.Listing;
4 |
5 | public class ListingTab : OverlayTab {
6 | FillFlowContainer info;
7 | ListingEntryContainer content;
8 | Bindable apiAddress = new( RurusettoAPI.DefaultAPIAddress );
9 | public ListingTab () {
10 | AddInternal( new FillFlowContainer() {
11 | Direction = FillDirection.Vertical,
12 | RelativeSizeAxes = Axes.X,
13 | AutoSizeAxes = Axes.Y,
14 |
15 | Children = new Drawable[] {
16 | info = new() {
17 | Direction = FillDirection.Full,
18 | RelativeSizeAxes = Axes.X,
19 | AutoSizeAxes = Axes.Y
20 | },
21 | content = new() {
22 | Direction = FillDirection.Full,
23 | RelativeSizeAxes = Axes.X,
24 | AutoSizeAxes = Axes.Y,
25 | Padding = new MarginPadding { Horizontal = 32, Top = 8 }
26 | }
27 | }
28 | } );
29 | }
30 |
31 | Task? refreshTask = null;
32 | public void ReloadListing () {
33 | Schedule( () => {
34 | Overlay.StartLoading( this );
35 | content.Clear();
36 | info.Clear();
37 | } );
38 |
39 | Task? task = null;
40 | task = refreshTask = Rulesets.RequestIdentities().ContinueWith( t => {
41 | Schedule( () => {
42 | Overlay.FinishLoadiong( this );
43 | if ( task != refreshTask )
44 | return;
45 |
46 | if ( !t.Result.ContainsWebListing ) {
47 | info.Add( new RequestFailedDrawable {
48 | ContentText = Localisation.Strings.ListingFetchError,
49 | ButtonClicked = () => Refresh()
50 | } );
51 | }
52 |
53 | foreach ( var i in t.Result ) {
54 | content.Add( new DrawableListingEntry( i ) {
55 | Anchor = Anchor.TopCentre,
56 | Origin = Anchor.TopCentre
57 | } );
58 | }
59 | } );
60 | } );
61 | }
62 |
63 | [BackgroundDependencyLoader]
64 | private void load () {
65 | apiAddress.BindTo( API.Address );
66 | apiAddress.BindValueChanged( _ => Refresh() );
67 | }
68 |
69 | public override bool Refresh () {
70 | API.FlushRulesetListingCache();
71 | Rulesets.Refresh();
72 | ReloadListing();
73 |
74 | return true;
75 | }
76 |
77 | protected override void LoadContent () {
78 | ReloadListing();
79 | }
80 |
81 | private class ListingEntryContainer : FillFlowContainer {
82 | Dictionary rulesets = new();
83 | public override IEnumerable FlowingChildren => base.FlowingChildren.OrderBy( x =>
84 | ( rulesets[x].Source == Source.Local ) ? 2 : 1
85 | ).ThenBy( x =>
86 | ( rulesets[x].ListingEntry?.CanDownload == true ) ? 1 : 2
87 | ).ThenBy( x =>
88 | ( rulesets[x].ListingEntry?.Status?.IsPlayable == true ) ? 1 : 2
89 | ).ThenBy( x =>
90 | ( rulesets[x].ListingEntry?.Status?.IsBorked == true ) ? 3 : 2
91 | ).ThenByDescending( x =>
92 | rulesets[x].ListingEntry?.Status?.LatestUpdate
93 | );
94 |
95 | public override void Add ( DrawableListingEntry drawable ) {
96 | base.Add( drawable );
97 |
98 | rulesets.Add( drawable, drawable.Ruleset );
99 | }
100 |
101 | public override bool Remove ( DrawableListingEntry drawable, bool disposeImmediately ) {
102 | rulesets.Remove( drawable );
103 | return base.Remove( drawable, disposeImmediately );
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/LocalizationGenerator/Source/ru.jsonc:
--------------------------------------------------------------------------------
1 | /// Author: https://github.com/Loreos7
2 |
3 | {
4 | "locale": "ru",
5 |
6 | "local-ruleset-description": "Локальный режим, которого нет на вики",
7 | // {0} = link to the wiki page
8 | "page-load-failed": "Не удалось загрузить страницу режима на вики. Извините!\nПопробуйте открыть ее на сайте [rurusetto]({1}).",
9 | "listing-tab": "список",
10 | "rurusetto-description": "открыть список доступных режимов",
11 | "user-unknown": "Аноним",
12 | "tag-archived": "В АРХИВЕ",
13 | "tag-archived-tooltip": "Этот режим больше не поддерживается",
14 | "tag-local": "ЛОКАЛЬНЫЙ",
15 | "tag-local-tooltip": "Этот режим установлен только у вас, его нет на вики",
16 | "tag-hardcoded": "ВСТРОЕННЫЙ",
17 | "tag-hardcoded-tooltip": "Этот режим встроен в игру и не может быть изменён",
18 | "tag-failed-import": "ОШИБКА ИМПОРТА",
19 | "tag-failed-import-tooltip": "Режим скачан, но его не удалось импортировать",
20 | "tag-borked": "НЕРАБОЧИЙ",
21 | "tag-borked-tooltip": "Этот режим не работает",
22 | "tag-playable": "РАБОЧИЙ",
23 | "tag-playable-tooltip": "Этот режим работает",
24 | "tag-prerelease": "БЕТА",
25 | "tag-prerelease-tooltip": "Текущая версия находится в стадии активной разработки",
26 | "home-page": "Домашняя страница",
27 | "report-issue": "Сообщить о проблеме",
28 | "download-checking": "Проверка...",
29 | "unavailable-online": "Недоступно для прямого скачивания",
30 | "installed-unavailable-online": "Установлен, не доступен онлайн",
31 | "download": "Скачать",
32 | "redownload": "Скачать заново",
33 | "downloading": "Загрузка...",
34 | "update": "Скачать обновление",
35 | "remove": "Удалить",
36 | "cancel-download": "Отменить загрузку",
37 | "cancel-remove": "Отменить удаление",
38 | "cancel-update": "Отменить обновление",
39 | "refresh": "Обновить статус",
40 | "to-be-removed": "Будет удалён при следующем входе в игру!",
41 | "to-be-installed": "Будет установлен при следующем входе в игру!",
42 | "to-be-updated": "Будет обновлён при следующем входе в игру!",
43 | "installed": "Установлен",
44 | "outdated": "Устарел",
45 | "creator-verified": "Подтверждённый создатель режима",
46 | // {0} = error code
47 | "load-error": "Не удалось загрузить rurusetto-addon: Пожалуйста, сообщите об этом в репозиторий rurusetto-addon, НЕ в репозиторий osu!lazer: Code {1}",
48 | "main-page": "Главная",
49 | "changelog-page": "Список изменений",
50 | "unknown-version": "Версия неизвестна",
51 | "settings-header": "Rurusetto Addon",
52 | "settings-api-address": "Адрес API",
53 | "untitled-ruleset": "Безымянный режим",
54 | "error-header": "О нет!",
55 | "error-footer": "Пожалуйста, убедитесь, что у вас есть подключение к интернету и адрес API в настройках не содержит ошибок",
56 | "retry": "Обновить",
57 | "listing-fetch-error": "Не удалось загрузить список режимов",
58 | "page-fetch-error": "Не удалось загрузить страницу",
59 | "subpages-fetch-error": "Не удалось загрузить подстраницы",
60 | "error-message-generic": "Что-то пошло не так, и я не знаю, что именно!",
61 | "notification-work-incomplete": "Кажется, rurusetto addon не смог завершить предыдущую рабочую сессию. Пожалуйста, убедитесь, что ваши действия не могли вызвать ошибок",
62 | "recommended-beatmaps-page": "Рекомендуемые карты"
63 | }
64 |
--------------------------------------------------------------------------------
/LocalizationGenerator/Source/es.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "locale": "es",
3 |
4 | "local-ruleset-description": "Ruleset local, no listado en la wiki.",
5 | // {0} = link to the wiki page
6 | "page-load-failed": "No se pudo obtener la página de la wiki del ruleset. ¡Lo siento!\nTodavía puedes intentar visitarla en [rurusetto]({0}).",
7 | "listing-tab": "listado",
8 | "users-tab": "usuarios",
9 | "collections-tab": "colecciones",
10 | "rurusetto-description": "explorar y administrar rulesets",
11 | "user-unknown": "Desconocido",
12 | "tag-archived": "ARCHIVADO",
13 | "tag-archived-tooltip": "Este ruleset ya no se mantiene",
14 | "tag-local": "NO LISTADO",
15 | "tag-local-tooltip": "Este ruleset está instalado localmente, pero no está listado en la wiki",
16 | "tag-hardcoded": "CODIFICADO",
17 | "tag-hardcoded-tooltip": "Este ruleset está codificado en el juego y no se puede modificar.",
18 | "tag-failed-import": "IMPORTACIÓN FALLIDA",
19 | "tag-failed-import-tooltip": "Este ruleset se descargó, pero no se pudo importar",
20 | "tag-borked": "BORRADO",
21 | "tag-borked-tooltip": "Este ruleset no funciona.",
22 | "tag-playable": "JUGABLE",
23 | "tag-playable-tooltip": "Este ruleset funciona",
24 | "tag-prerelease": "PRE-LANZAMIENTO",
25 | "tag-prerelease-tooltip": "La versión actual es un pre-lanzamiento.",
26 | "home-page": "Página de inicio",
27 | "report-issue": "Reportar problema",
28 | "download-checking": "Comprobando...",
29 | "unavailable-online": "No disponible en línea",
30 | "installed-unavailable-online": "Instalado, no disponible en línea",
31 | "download": "Descargar",
32 | "redownload": "Re-descargar",
33 | "downloading": "Descargando...",
34 | "update": "Actualizar",
35 | "remove": "Eliminar",
36 | "cancel-download": "Cancelar descarga",
37 | "cancel-remove": "Cancelar eliminación",
38 | "cancel-update": "Cancelar actualización",
39 | "refresh": "Actualizar",
40 | "to-be-removed": "Se eliminará al reiniciar!",
41 | "to-be-installed": "Se instalará al reiniciar!",
42 | "to-be-updated": "Se actualizará al restart!",
43 | "installed": "Instalado",
44 | "outdated": "Desactualizado",
45 | "creator-verified": "Creador de rulesets verificado",
46 | // {0} = error code
47 | "load-error": "No se pudo cargar rurusetto-addon: Informe esto al repositorio de rurusetto-addon NO al repositorio de osu!lazer: Código {0}",
48 | "main-page": "Principal",
49 | "changelog-page": "Registro de cambios",
50 | "recommended-beatmaps-page": "Beatmaps recomendados",
51 | "unknown-version": "Versión desconocida",
52 | "settings-header": "Rurusetto Addon",
53 | "settings-api-address": "Dirección de la API",
54 | "untitled-ruleset": "Ruleset sin título",
55 | "error-header": "Oh no!",
56 | "error-footer": "Asegúrese de tener una conexión a Internet y que la dirección de la API en la configuración sea correcta.",
57 | "retry": "Reintentar",
58 | "listing-fetch-error": "No se pudo recuperar el listado de rulesets",
59 | "page-fetch-error": "No se pudo cargar la página",
60 | "subpages-fetch-error": "No se pudieron recuperar las subpáginas",
61 | "error-message-generic": "Algo salió mal, pero no sé qué!",
62 | "notification-work-incomplete": "Parece que rurusetto addon no pudo terminar un trabajo. Por favor, asegúrese de que todos sus cambios se aplicaron correctamente"
63 | }
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rūruestto Addon
2 | An addon for osu!lazer which allows you to browse the [rūrusetto](https://rulesets.info) wiki in-game and manage your rulesets.
3 |
4 | ## Special thanks
5 | To [Nao](https://github.com/naoei) for graphical design for the addon and [Yulianna](https://github.com/HelloYeew) for making Rurūsetto.
6 | To [Loreos](https://github.com/Loreos7) for the russian locale.
7 |
8 | ## Installing
9 | * Open osu!lazer
10 | * Go to settings
11 | * Click `Open osu! foler` at the bottom of `General` settings
12 | * Open the `rulesets` folder
13 | * Copy the path
14 | * Go to [Releases](/releases) of this repository
15 | * Click the topmost one
16 | * Download the `osu.Game.Rulesets.RurusettoAddon.dll` file. Save to the copied path.
17 | * Restart osu!
18 | * Done! Click the  button to browse the wiki
19 |
20 | You can also install and update locale (translation) files by following the same steps for the `.zip` files included with each release.
21 |
22 | https://user-images.githubusercontent.com/40297338/149041138-003f4a3e-3144-4139-8558-34d13da8d40f.mp4
23 |
24 | ## Contributing
25 | If you can code, you can contribute the standard [github way](https://github.com/firstcontributions/first-contributions):
26 | * Fork this repository
27 | * Apply code changes
28 | * Open a PR (Pull Request) to submit your changes for review
29 |
30 | If you can't code, you can still contribute localisation (translation files). You still need to follow the steps outlined in [here](https://github.com/firstcontributions/first-contributions):
31 | * Fork this repository
32 | * Open the [Localisation Generator Source folder](./LocalizationGenerator/Source)
33 | * The files in this folder are named after the locale it uses. For example english uses `en.jsonc`. For a list of ISO language codes, see [this website](http://www.lingoes.net/en/translator/langcode.htm) or if you can't find your locale there, wikipedia has a list (although it it a bit harder to read than this site)
34 | * If your locale is not there, create a file following the naming convention outlined above. Initialize the file with `{ "locale": "ISO language code" }`
35 | * Open the file. It can be opened in a regular text editor, although we recommend [VS Code](https://code.visualstudio.com)
36 | * You probably also want to open `en.jsonc` or any other translation file as a reference
37 | * If you can build, you can run `LocalisationGenerator.csproj`
38 | * The program will perform some checks to see what resources are missing across all localisation files
39 | * You can exit the program. You don't need to confirm to generate files just yet
40 | * Add translations or typecheck your file
41 | * Since the format is `.jsonc` and not `.json`, you can add comments to the file if you want by typing `// this is my comment!` anywhere. Even though github credits commit authors, feel free to credit yourself at the top of the file with a triple slash comment
42 | * Some strings contain a `{0}` or `{1}` etc. These are going to be replaced by something such as a number, a link or an error code. All entries which contain them must be commented by explaining what they are
43 | * If you can build, run `LocalisationGenerator.csproj`. This time confirm and generate resource files. If you do not know how to build, a collaborator will do this step for you when you submit your PR
44 | * Open a PR (Pull Request) to submit your changes for review
45 |
46 | You can also contribute to the [rūrusetto wiki](https://rulesets.info) or any of the rulesets you discover! Make sure to show some ❤️ to the awesome people who develop and maintain them.
47 |
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/DrawableTag.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Cursor;
2 | using osu.Framework.Localisation;
3 | using osu.Game.Graphics.Sprites;
4 |
5 | namespace osu.Game.Rulesets.RurusettoAddon.UI;
6 |
7 | public class DrawableTag : CompositeDrawable, IHasTooltip {
8 | public DrawableTag ( LocalisableString tag, Colour4 colour, bool solid, float height = 18 ) {
9 | AutoSizeAxes = Axes.X;
10 | Height = height;
11 |
12 | if ( solid ) {
13 | AddInternal( new Box {
14 | RelativeSizeAxes = Axes.Both,
15 | Colour = colour
16 | } );
17 |
18 | AddInternal( new Container {
19 | Padding = new MarginPadding { Horizontal = 4 },
20 | AutoSizeAxes = Axes.Both,
21 | Child = new OsuSpriteText {
22 | Colour = Colour4.FromHex( "#191C17" ),
23 | UseFullGlyphHeight = false,
24 | Font = OsuFont.GetFont( Typeface.Torus, size: height - 2, weight: FontWeight.Bold ),
25 | Text = tag,
26 | Anchor = Anchor.CentreLeft,
27 | Origin = Anchor.CentreLeft
28 | },
29 | Anchor = Anchor.CentreLeft,
30 | Origin = Anchor.CentreLeft
31 | } );
32 |
33 | Masking = true;
34 | CornerRadius = 4;
35 | }
36 | else {
37 | Masking = true;
38 | CornerRadius = 4;
39 | BorderColour = colour;
40 | BorderThickness = 3;
41 |
42 | AddInternal( new Box {
43 | RelativeSizeAxes = Axes.Both,
44 | Colour = Colour4.Transparent
45 | } );
46 |
47 | AddInternal( new Container {
48 | Padding = new MarginPadding { Horizontal = 4 },
49 | AutoSizeAxes = Axes.Both,
50 | Child = new OsuSpriteText {
51 | Colour = colour,
52 | UseFullGlyphHeight = false,
53 | Font = OsuFont.GetFont( Typeface.Torus, size: height - 2, weight: FontWeight.Bold ),
54 | Text = tag,
55 | Anchor = Anchor.CentreLeft,
56 | Origin = Anchor.CentreLeft
57 | },
58 | Anchor = Anchor.CentreLeft,
59 | Origin = Anchor.CentreLeft
60 | } );
61 | }
62 | }
63 |
64 | public static DrawableTag CreateArchived ( bool large = false ) => new( Localisation.Strings.TagArchived, Colour4.FromHex( "#FFE766" ), solid: false, height: large ? 26 : 18 ) {
65 | TooltipText = Localisation.Strings.TagArchivedTooltip
66 | };
67 | public static DrawableTag CreateLocal ( bool large = false ) => new( Localisation.Strings.TagLocal, Colour4.FromHex( "#FFE766" ), solid: true, height: large ? 26 : 18 ) {
68 | TooltipText = Localisation.Strings.TagLocalTooltip
69 | };
70 | public static DrawableTag CreateHardCoded ( bool large = false ) => new( Localisation.Strings.TagHardcoded, Colour4.FromHex( "#FF6060" ), solid: true, height: large ? 26 : 18 ) {
71 | TooltipText = Localisation.Strings.TagHardcodedTooltip
72 | };
73 | public static DrawableTag CreateFailledImport ( bool large = false ) => new( Localisation.Strings.TagFailedImport, Colour4.FromHex( "#FF6060" ), solid: true, height: large ? 26 : 18 ) {
74 | TooltipText = Localisation.Strings.TagFailedImportTooltip
75 | };
76 |
77 | public static DrawableTag CreateBorked ( bool large = false ) => new( Localisation.Strings.TagBorked, Colour4.FromHex( "#FF6060" ), solid: false, height: large ? 26 : 18 ) {
78 | TooltipText = Localisation.Strings.TagBorkedTooltip
79 | };
80 | public static DrawableTag CreatePlayable ( bool large = false ) => new( Localisation.Strings.TagPlayable, Colour4.FromHex( "#6CB946" ), solid: false, height: large ? 26 : 18 ) {
81 | TooltipText = Localisation.Strings.TagPlayableTooltip
82 | };
83 | public static DrawableTag CreatePrerelease ( bool large = false ) => new( Localisation.Strings.TagPrerelease, Colour4.FromHex( "#FFE766" ), solid: false, height: large ? 26 : 18 ) {
84 | TooltipText = Localisation.Strings.TagPrereleaseTooltip
85 | };
86 |
87 |
88 | public LocalisableString TooltipText { get; set; }
89 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/API/RulesetDetail.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Serialization;
3 | using osu.Framework.Localisation;
4 |
5 | #nullable disable
6 | namespace osu.Game.Rulesets.RurusettoAddon.API;
7 |
8 | public record RulesetDetail {
9 | ///
10 | /// Whether the request has succeded
11 | ///
12 | public bool Success => string.IsNullOrWhiteSpace( Detail );
13 |
14 | /// The ID of the ruleset in Rūrusetto database.
15 | [JsonProperty( "id" )]
16 | public int ID { get; init; }
17 |
18 | /// The name of the ruleset.
19 | [JsonProperty( "name" )]
20 | public LocalisableString Name { get; init; }
21 | /// The slug of the ruleset. Use in the URL of the ruleset's wiki page.
22 | [JsonProperty( "slug" )]
23 | public string Slug { get; init; }
24 | /// The short description of the rulesets.
25 | [JsonProperty( "description" )]
26 | public LocalisableString Description { get; init; }
27 |
28 | /// The URL of the ruleset icon that use in website's default theme (dark theme).
29 | [JsonProperty( "icon" )]
30 | public string DarkIcon { get; init; }
31 | /// The URL of the ruleset icon that use in website's light theme.
32 | [JsonProperty( "light_icon" )]
33 | public string LightIcon { get; init; }
34 | /// The URL of the ruleset logo that use in the infobox.
35 | [JsonProperty( "logo" )]
36 | public string Logo { get; init; }
37 | /// The URL of the cover image in ruleset's wiki page in website's default theme (dark theme).
38 | [JsonProperty( "cover_image" )]
39 | public string CoverDark { get; init; }
40 | /// The URL of the cover image in ruleset's wiki page in website's light theme.
41 | [JsonProperty( "cover_image_light" )]
42 | public string CoverLight { get; init; }
43 | /// The URL of the image that use in the opengraph part of the wiki URL.
44 | [JsonProperty( "opengraph_image" )]
45 | public string OpengraphImage { get; init; }
46 |
47 | /// The URL of the CSS file that's override the website's default styling.
48 | [JsonProperty( "custom_css" )]
49 | public string CustomCSS { get; init; }
50 | /// Wiki main content in markdown format.
51 | [JsonProperty( "content" )]
52 | public LocalisableString Content { get; init; }
53 |
54 | /// The URL source of the rulesets.
55 | [JsonProperty( "source" )]
56 | public string Source { get; init; }
57 | /// Filename that use in rendering the direct download link with the source link.
58 | [JsonProperty( "github_download_filename" )]
59 | public string GithubFilename { get; init; }
60 | /// URL for download the latest release of ruleset from GitHub
61 | [JsonProperty( "direct_download_link" )]
62 | public string Download { get; init; }
63 | ///
64 | /// True if website can render the direct download link from the source and github_download_filename
65 | /// so user can download directly from direct_download_link.
66 | ///
67 | [JsonProperty( "can_download" )]
68 | public bool CanDownload { get; init; }
69 |
70 | /// The user_detail of the ruleset's current owner
71 | [JsonProperty( "owner_detail" )]
72 | public UserDetail Owner { get; init; }
73 |
74 | /// The user_detail of the user who create this wiki page, not the owner.
75 | [JsonProperty( "creator_detail" )]
76 | public UserDetail Creator { get; init; }
77 | /// The UTC time that the wiki page has create in JSON time format.
78 | [JsonProperty( "created_at" )]
79 | public DateTime? CreatedAt { get; init; }
80 |
81 | /// The user_detail of the user who edit the wiki page last time.
82 | [JsonProperty( "last_edited_by_detail" )]
83 | public UserDetail LastEditedBy { get; init; }
84 | /// The UTC time of the latest wiki edit.
85 | [JsonProperty( "last_edited_at" )]
86 | public DateTime? LastEditedAt { get; init; }
87 |
88 | /// True if the wiki maintainer has verified that the the owner is the real owner of this ruleset.
89 | [JsonProperty( "verified" )]
90 | public bool IsVerified { get; init; }
91 | /// True if this ruleset is stop update or archived by rulesets creator.
92 | [JsonProperty( "archive" )]
93 | public bool IsArchived { get; init; }
94 |
95 | [JsonProperty( "detail" )]
96 | private string Detail { get; init; }
97 | }
--------------------------------------------------------------------------------
/osu.Game.Rulesets.RurusettoAddon/UI/RulesetManagementContextMenu.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Extensions.EnumExtensions;
2 | using osu.Framework.Graphics.Cursor;
3 | using osu.Framework.Graphics.UserInterface;
4 | using osu.Game.Graphics.UserInterface;
5 | using osu.Game.Rulesets.RurusettoAddon.UI.Menus;
6 |
7 | namespace osu.Game.Rulesets.RurusettoAddon.UI;
8 |
9 | public class RulesetManagementContextMenu : CompositeDrawable, IHasContextMenu {
10 | [Resolved]
11 | public RulesetDownloader Downloader { get; private set; } = null!;
12 |
13 | public readonly Bindable State = new( DownloadState.NotDownloading );
14 | public readonly Bindable Avail = new( Availability.Unknown );
15 |
16 | APIRuleset ruleset;
17 | LocalisableOsuMenuItem download;
18 | LocalisableOsuMenuItem update;
19 | LocalisableOsuMenuItem redownload;
20 | LocalisableOsuMenuItem remove;
21 | LocalisableOsuMenuItem cancelDownload;
22 | LocalisableOsuMenuItem cancelUpdate;
23 | LocalisableOsuMenuItem cancelRemoval;
24 | LocalisableOsuMenuItem refresh;
25 |
26 | public RulesetManagementContextMenu ( APIRuleset ruleset ) {
27 | this.ruleset = ruleset;
28 |
29 | RelativeSizeAxes = Axes.Both;
30 |
31 | download = new( Localisation.Strings.Download, MenuItemType.Standard, () => Downloader.DownloadRuleset( ruleset ) );
32 | update = new( Localisation.Strings.Update, MenuItemType.Standard, () => Downloader.UpdateRuleset( ruleset ) );
33 | redownload = new( Localisation.Strings.Redownload, MenuItemType.Standard, () => Downloader.UpdateRuleset( ruleset ) );
34 | remove = new( Localisation.Strings.Remove, MenuItemType.Destructive, () => Downloader.RemoveRuleset( ruleset ) );
35 | cancelDownload = new( Localisation.Strings.CancelDownload, MenuItemType.Standard, () => Downloader.CancelRulesetDownload( ruleset ) );
36 | cancelUpdate = new( Localisation.Strings.CancelUpdate, MenuItemType.Standard, () => Downloader.CancelRulesetDownload( ruleset ) );
37 | cancelRemoval = new( Localisation.Strings.CancelRemove, MenuItemType.Standard, () => Downloader.CancelRulesetRemoval( ruleset ) );
38 | refresh = new( Localisation.Strings.Refresh, MenuItemType.Standard, () => Downloader.CheckAvailability( ruleset ) );
39 | }
40 |
41 | protected override void LoadComplete () {
42 | base.LoadComplete();
43 |
44 | Downloader.BindWith( ruleset, State );
45 | Downloader.BindWith( ruleset, Avail );
46 |
47 | State.ValueChanged += _ => Schedule( updateContextMenu );
48 | Avail.ValueChanged += _ => Schedule( updateContextMenu );
49 |
50 | updateContextMenu();
51 | }
52 |
53 | private void updateContextMenu () {
54 | if ( Avail.Value == Availability.Unknown ) {
55 | ContextMenuItems = Array.Empty