1
0
mirror of synced 2024-11-23 22:41:01 +01:00

README Overhaul, added DisplayUnplayedDans option

You can now hide all unplayed dan entries.
This commit is contained in:
Farewell_ 2024-11-12 17:16:04 +01:00
parent e4606c3d8b
commit ec8ceebf12
8 changed files with 659 additions and 646 deletions

285
README.md
View File

@ -1,279 +1,34 @@
# Taiko Local Server
This is a server for Taiko no Tatsujin Nijiiro ver CHN
This is a server for Taiko no Tatsujin Nijiiro ver 39.06
It is composed of two major components :
## Setup
- [TaikoLocalServer](./TaikoLocalServer/): The server handling the game's requests
- [TaikoWebUI](./TaikoWebUI/): The frontend handling user profiles.
## Installation
### Prerequisite
- A working game, with dongle and QR reader emulation. You can use [TaikoArcadeLoader](https://github.com/BroGamer4256/TaikoArcadeLoader) for these if you haven't.
- You need a working game install, with dongle and QR reader emulation.
You can use [TaikoArcadeLoader](https://github.com/esuo1198/TaikoArcadeLoader) to have these working (Teknoparrot will NOT work).
### Setup Steps
1. Extract the server release anywhere
1. Extract the Server's release anywhere
2. From the game files (Data/x64/datatable), copy `music_order.bin`, `musicinfo.bin`, `wordlist.bin`, `don_cos_reward.bin`, `shougou.bin`,`neiro.bin` to [taikolocalserver/wwwroot/data/datatable](./TaikoLocalServer/wwwroot/data/datatable/)
3. (Optional) In `Certificates` folder, import `root.pfx` to trusted root store and `cert.pfx` to personal store. All the other import options can be kept default
4. Visit [http://localhost](http://localhost). If the WebUI starts without errors, the config is fine
5. Start your game! (First boot with the server will take a good minute, be patient!)
2. From the game files, copy `music_order.bin`, `musicinfo.bin`, `wordlist.bin`, `don_cos_reward.bin`, `shougou.bin`,`neiro.bin` to `wwwroot/data/datatable` folder.
## Configuration
3. (Optional) In `Certificates` folder, import `root.pfx` to trusted root store, `cert.pfx` to personal store. All the other import options can be kept default.
### TaikoLocalServer configuration
4. Visit http://localhost:5000 if the web ui starts without errors, the config is fine.
5. Now start the game and it should be able to connect.
There are various json files under [taikolocalserver/wwwroot/data](./TaikoLocalServer/wwwroot/data/) that can be customized.
Please refer to the [taikolocalserver](./TaikoLocalServer/README.md) folder for documentation.
## Data config
### TaikoWebUI configuration
There are various data json files under wwwroot/data that can be customized.
- dan_data.json: This is used customize normal dans.
```
[
{
"danId":1, // The danId of the dan, has to be unique in all dans in dan_data.json
"verupNo":1, // Used to control whether the client should update to a new dan when offline cache files are still present
"title":"5kyuu", // Title of the dan, for example, "5kyuu" = 5級, "9dan" = 九段, "14dan" = 達人, etc.
"aryOdaiSong":[
{
"songNo":420, // The uniqueId of the first song
"level":2, // The level of the first song, 1 = easy, 4 = oni, 5 = ura, etc.
"isHiddenSongName":false // If set to true, the song name will be displayed as ??? in dani selection in-game
},
{
"songNo":881, // The uniqueId of the second song
"level":2,
"isHiddenSongName":false
},
{
"songNo":995, // The uniqueId of the third song
"level":2,
"isHiddenSongName":false
}
],
"aryOdaiBorder":[
{
"odaiType":1, // The odai type, 1 = soul gauge percentage, 2 = good count, 3 = ok count, 4 = bad count, 5 = combo count, 6 = renda count, 7 = score, 8 = hit count
"borderType":1, // Controls whether this odai requirement is shared, 1 means all 3 songs share this same odai requirement, 2 means 3 songs have separate odai requirements, to see how to set separate odai requirements, see the next dan example
"redBorderTotal":92, // The odai requirement to get a red pass for this dan
"goldBorderTotal":95 // The odai requirement to get a gold pass for this dan
},
{
"odaiType":8,
"borderType":1,
"redBorderTotal":884,
"goldBorderTotal":936
}
]
},
{
"danId":14,
"verupNo":1,
"title":"9dan",
"aryOdaiSong":[
{
"songNo":568,
"level":4,
"isHiddenSongName":false
},
{
"songNo":117,
"level":4,
"isHiddenSongName":false
},
{
"songNo":21,
"level":4,
"isHiddenSongName":false
}
],
"aryOdaiBorder":[
{
"odaiType":1,
"borderType":1,
"redBorderTotal":100,
"goldBorderTotal":100
},
{
"odaiType":2,
"borderType":1,
"redBorderTotal":2045,
"goldBorderTotal":2100
},
{
"odaiType":4,
"borderType":1,
"redBorderTotal":10,
"goldBorderTotal":5
},
{
"odaiType":6, // This is set to 6, which means this is the renda requirement odai
"borderType":2, // This is set to 2, which means the 3 songs have individual odai requirements
"redBorder_1":107, // This means to get a red pass, you have to get above or equal to 107 rendas in song 1
"goldBorder_1":114, // This means to get a gold pass, you have to get above or equal to 114 rendas in song 1
"redBorder_2":74, // This means to get a red pass, you have to get above or equal to 74 rendas in song 2
"goldBorder_2":79, // This means to get a gold pass, you have to get above or equal to 79 rendas in song 2
"redBorder_3":54, // This means to get a red pass, you have to get above or equal to 54 rendas in song 3
"goldBorder_3":59 // This means to get a gold pass, you have to get above or equal to 59 rendas in song 3
}
]
}
]
```
- event_folder_data.json: This is used to populate event folders/genres
```
[
{
"folderId": 1, // The folderId of the event folder, find corresponding folderId in wordlist.bin by searching keys called folder_eventX, where X is the folderId
"verupNo": 1, // Used to control whether the client should update to a new event folder when offline cache files are still present
"priority": 1,
"songNo": [] // The uniqueId of the songs to be added to this event folder, if left empty, this folder will not show up in-game
},
{
"folderId": 2,
"verupNo": 1,
"priority": 1,
"songNo": [
478, 153, 200, 482, 511, 672, 675, 646, 644, 645, 676, 671, 479,
707, 480, 481, 203, 204, 483, 205, 202, 241, 14, 387, 197, 281, 226,
484, 543, 512, 709, 35
] // A populated event folder example
}
]
```
- gaiden_data.json: This is used to customize gaiden dans.
```
[
{
"danId":20, // The danId of the gaiden dan, can be the same value as a dan in dan_data.json, but has to be unique in all gaidens in gaiden_data.json
"verupNo":1, // Used to control whether the client should update to a new dan when offline cache files are still present
"title":"[JPN]=復活!ブルー十段,[ENG]=Blue 10Dan", // The title of the gaiden dan, which will be displayed when scanning the QR code and in dani select interface. Use language code to specify each language's entry. [JPN], [CHS], [CHT], [KOR], [ENG] are supported. Use comma to separate each language's entry.
"aryOdaiSong":[ // Starting from here, it uses the same format as dan_data.json
{
"songNo":60,
"level":5,
"isHiddenSongName":false
},
{
"songNo":55,
"level":4,
"isHiddenSongName":false
},
{
"songNo":737,
"level":4,
"isHiddenSongName":false
}
],
"aryOdaiBorder":[
{
"odaiType":1,
"borderType":1,
"redBorderTotal":100,
"goldBorderTotal":100
},
{
"odaiType":3,
"borderType":1,
"redBorderTotal":90,
"goldBorderTotal":60
},
{
"odaiType":4,
"borderType":1,
"redBorderTotal":8,
"goldBorderTotal":5
}
]
}
]
```
- intro_data.json: This is used to customize the song intro displayed before entering the game
```
[
{
"setId":1, // The setId of the intro, has to be unique in all intros in intro_data.json
"verupNo":1, // Used to control whether the client should update to a new intro when offline cache files are still present
"mainSongNo":1115, // The uniqueId of the main song, which will be displayed at the top of the four other songs
"subSongNo":[1022,7,1089,1059] // The uniqueId of the four other songs, which will be displayed below the main song, there has to be 4 songs exactly
},
{
"setId":2,
"verupNo":1,
"mainSongNo":1102,
"subSongNo":[1065,966,1008,916]
}
]
```
- locked_songs_data.json: This is used to customize locked songs.
```
{
"songNo": [
// Fill in the uniqueId of songs you wish to lock
// Songs locked will not be visible in-game to players(including guest) without the corresponding entry in UnlockedSongIdList in UserData, but may still show up in the shop folder
100,
200,
300
],
"uraSongNo": [
// Fill in the uniqueId of songs whose ura chart you wish to lock
]
}
```
- movie_data.json: This is used to control which in-game movie is displayed before entering the game
```
[
{
"movie_id": 0, // The movie id, 8 = iM@S 15th anniversary collab, 9 = iM@S 15th anniversary collab (en), 10 = ONE PIECE collab, 12 = ONE PIECE collab 2 (special mode here), 14 = Touhou collab 2021,15 = Taiko no Tatsujin 20th anniversary Soshina collab, 16 = Taiko no Tatsujin 20th anniversary Soshina collab (en), 17 = Taiko no Tatsujin 20th anniversary Soshina collab (zh-tw), 18 = Taiko no Tatsujin 20th anniversary Soshina collab (ko)
"enable_days": 0 // Simply set to 999 for the movie to always be displayed
}
]
```
- qrcode_data.json: This is used to customize which qrcode's uniqueId is invoked when a qrcode request is received.
```
[
{
"serial": "gaiden_blue_10dan", // QR serial data sent by TAL
"id": 20 // The uniqueId of the qrcode the server sends back, corresponding to the uniqueId in qrcode_info.bin in the client's datatable
},
{
"serial": "gaiden_white_10dan",
"id": 21
}
]
```
- shop_folder_data.json: This is used to customize the in-game shop folder content.
```
[
{
"songNo": 100, // The uniqueId of the song
"type": 0, // 0 indicates the shop is selling the song itself
"price": 20 // How many don coins buying this song costs, the type of don coin is specified by token_data.json
},
{
"songNo": 200,
"type": 1, // And 1 indicates the shop is selling the song's ura chart
"price": 20
}
]
```
- token_data.json: This is used to customize in-game reward tokens.
```
{
"shopTokenId": -1, // The token id used in shop, a.k.a. don coin, can be from 1 to 11, 1=spring, 2=summer, 3=autumn, 4=winter, 5=spring(again), etc. By default, this is turned off by setting it to -1
"kaTokenId": -1, // The token id of ka coins, can be 1000 or 1001, corresponding to reward entrys in reward.bin in the client's datatable. By default, this is turned off by setting it to -1
"onePieceTokenId": 100100, // The token id representing onePiece collab mode's win count, should not be changed
"soshinaTokenId": 100200 // The token id representing soshina collab mode's win count, should not be changed
}
```
## TaikoWebUI appsettings.json config
This section is for configuring the TaikoWebUI appsettings.json file found under the ```wwwroot``` folder.
This file is used to configure the web UI.
```
{
"WebUiSettings": {
"LoginRequired": "false", // Whether a login is required to access personal profiles, default to false
"AdminUserName": "admin", // The username of the admin account, if LoginRequired is set to false, this is ignored, admin account can always access all personal profiles
"AdminPassword": "admin", // The password of the admin account, if LoginRequired is set to false, this is ignored
"OnlyAdmin": "false" // Whether only the admin account can access personal profiles, if set to true, register will be unavailable and only admins can login, default to false
}
}
```
The WebUI has a few settings you can change in [appsettings.json](./TaikoWebUI/wwwroot/appsettings.json)
Please refer to the [taikowebui](./TaikoWebUI/README.md) folder for documentation.

View File

@ -1,3 +1,3 @@
# Shared Project
This is the shared project for shared enums/models/utils etc. between server and web ui.
This is the shared project for shared enums/models/utils etc. between server and web ui.

View File

@ -1,135 +1,316 @@
# Server
# Taiko Local Server
This is the solution for server.
Server is implemented with ASP.NET Core 6. ORM is Entity Framework Core 6. Database is SQLite for easier setup.
As the game uses protobuf, `protobuf-net` is used for serializing and deserializing the data.
- [Taiko Local Server](#taiko-local-server)
- [Datatable documentation](#datatable-documentation)
- [dan\_data.json](#dan_datajson)
- [event\_folder\_data.json](#event_folder_datajson)
- [gaiden\_data.json](#gaiden_datajson)
- [intro\_data.json](#intro_datajson)
- [locked\_songs\_data.json](#locked_songs_datajson)
- [movie\_data.json](#movie_datajson)
- [qrcode\_data.json](#qrcode_datajson)
## Datatable documentation
The **verupNo** field is the version number of your list. You'll have to increment it each time you make an edit for the game tu pull the new version (otherwise it'll use an old cached copy !)
The server sends a variety of information to the game that you can edit.
You'll find a list of all the files bellow!
### event_folder_data
### dan_data.json
This is used to customize normal dans.
```json
[
{
"danId":1, // The danId of the dan, has to be unique in all dans in dan_data.json
"verupNo":1, // Used to control whether the client should update to a new dan when offline cache files are still present
"title":"5kyuu", // Title of the dan, for example, "5kyuu" = 5級, "9dan" = 九段, "14dan" = 達人, etc.
"aryOdaiSong":[
{
"songNo":420, // The uniqueId of the first song
"level":2, // The level of the first song, 1 = easy, 4 = oni, 5 = ura, etc.
"isHiddenSongName":false // If set to true, the song name will be displayed as ??? in dani selection in-game
},
{
"songNo":881, // The uniqueId of the second song
"level":2,
"isHiddenSongName":false
},
{
"songNo":995, // The uniqueId of the third song
"level":2,
"isHiddenSongName":false
}
],
"aryOdaiBorder":[
{
"odaiType":1, // The odai type, 1 = soul gauge percentage, 2 = good count, 3 = ok count, 4 = bad count, 5 = combo count, 6 = renda count, 7 = score, 8 = hit count
"borderType":1, // Controls whether this odai requirement is shared, 1 means all 3 songs share this same odai requirement, 2 means 3 songs have separate odai requirements, to see how to set separate odai requirements, see the next dan example
"redBorderTotal":92, // The odai requirement to get a red pass for this dan
"goldBorderTotal":95 // The odai requirement to get a gold pass for this dan
},
{
"odaiType":8,
"borderType":1,
"redBorderTotal":884,
"goldBorderTotal":936
}
]
},
{
"danId":14,
"verupNo":1,
"title":"9dan",
"aryOdaiSong":[
{
"songNo":568,
"level":4,
"isHiddenSongName":false
},
{
"songNo":117,
"level":4,
"isHiddenSongName":false
},
{
"songNo":21,
"level":4,
"isHiddenSongName":false
}
],
"aryOdaiBorder":[
{
"odaiType":1,
"borderType":1,
"redBorderTotal":100,
"goldBorderTotal":100
},
{
"odaiType":2,
"borderType":1,
"redBorderTotal":2045,
"goldBorderTotal":2100
},
{
"odaiType":4,
"borderType":1,
"redBorderTotal":10,
"goldBorderTotal":5
},
{
"odaiType":6, // This is set to 6, which means this is the renda requirement odai
"borderType":2, // This is set to 2, which means the 3 songs have individual odai requirements
"redBorder_1":107, // This means to get a red pass, you have to get above or equal to 107 rendas in song 1
"goldBorder_1":114, // This means to get a gold pass, you have to get above or equal to 114 rendas in song 1
"redBorder_2":74, // This means to get a red pass, you have to get above or equal to 74 rendas in song 2
"goldBorder_2":79, // This means to get a gold pass, you have to get above or equal to 79 rendas in song 2
"redBorder_3":54, // This means to get a red pass, you have to get above or equal to 54 rendas in song 3
"goldBorder_3":59 // This means to get a gold pass, you have to get above or equal to 59 rendas in song 3
}
]
}
]
```
### event_folder_data.json
This is used to populate event folders/genres
```json
[
{
// Touhou Project Special
// A collection of special songs!
"folderId": 1,
"verupNo": 1,
"priority": 1,
"songNo": []
"folderId": 1, // The folderId of the event folder, find corresponding folderId in the wordlist by searching keys called folder_eventX, where X is the folderId
//For 39.06, the list is the following:
//1: Touhou Project Special
//2: The Idolmaster Special
//3: Highly Recommended Songs
//4 -- UNUSED --
//5: Studio Ghibli Feature
//6: Yokai Watch Special
//7: UUUM Creator Feature
//8: Soshina's Playlist
//9: Soshina's Recommended Playlist
//10: Championship Songs
//11: [Bonus] 2023 Championship Songs
//12: #Compass Creator Feature
//13: Winter Seasonal Songs Pack
//14: World Popular Songs
//15: Taiko no Tatsujin 20th Anniversary Songs
"verupNo": 1, // Used to control whether the client should update to a new event folder when offline cache files are still present
"priority": 1,
"songNo": [] // The uniqueId of the songs to be added to this event folder, if left empty, this folder will not show up in-game
},
{
// The Idolmaster Special
// A collection of special songs!
"folderId": 2,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// Highly Recommended Songs
// Why dont you start with these popular songs?
"folderId": 3,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// ?
"folderId": 4,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// Studio Ghibli Feature
// A collection of special songs!
"folderId": 5,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// Yokai Watch Special
// A collection of special songs!
"folderId": 6,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// UUUM Creator Feature
// A collection of special songs!
"folderId": 7,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// Soshina's Playlist
// A collection of songs produced by Soshina!
"folderId": 8,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// Recommended Playlist
// Soshina's handpicked songs to battle to!
"folderId": 9,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// Championship Songs
// Assigned songs for the World Championship
"folderId": 10,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// [Bonus] 2023 Championship Songs
// We gathered up some songs from the online 2023 World's Online Championship Match [Bonus] !
"folderId": 11,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// #Compass Special
// Here is a collection of songs from #Compass!
"folderId": 12,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// Winter Seasonal Songs Pack
// A collection of songs to play in the winter!
"folderId": 13,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// World Popular Songs
// We've collected some of the world's most popular songs!
"folderId": 14,
"verupNo": 1,
"priority": 1,
"songNo": []
},
{
// Taiko no Tatsujin 20th Anniversary Songs
// A collection of “Taiko no Tatsujin” 20th Anniversary Songs!
"folderId": 14,
"verupNo": 1,
"priority": 1,
"songNo": []
"songNo": [
478, 153, 200, 482, 511, 672, 675, 646, 644, 645, 676, 671, 479,
707, 480, 481, 203, 204, 483, 205, 202, 241, 14, 387, 197, 281, 226,
484, 543, 512, 709, 35
] // A populated event folder example
}
]
```
### gaiden_data.json
This is used to customize gaiden dans.
```json
[
{
"danId":20, // The danId of the gaiden dan, can be the same value as a dan in dan_data.json, but has to be unique in all gaidens in gaiden_data.json
"verupNo":1, // Used to control whether the client should update to a new dan when offline cache files are still present
"title":"[JPN]=復活!ブルー十段,[ENG]=Blue 10Dan", // The title of the gaiden dan, which will be displayed when scanning the QR code and in dani select interface. Use language code to specify each language's entry. [JPN], [CHS], [CHT], [KOR], [ENG] are supported. Use comma to separate each language's entry.
"aryOdaiSong":[ // Starting from here, it uses the same format as dan_data.json
{
"songNo":60,
"level":5,
"isHiddenSongName":false
},
{
"songNo":55,
"level":4,
"isHiddenSongName":false
},
{
"songNo":737,
"level":4,
"isHiddenSongName":false
}
],
"aryOdaiBorder":[
{
"odaiType":1,
"borderType":1,
"redBorderTotal":100,
"goldBorderTotal":100
},
{
"odaiType":3,
"borderType":1,
"redBorderTotal":90,
"goldBorderTotal":60
},
{
"odaiType":4,
"borderType":1,
"redBorderTotal":8,
"goldBorderTotal":5
}
]
}
]
```
### intro_data.json
This is used to customize the song intro displayed before entering the game
```json
[
{
"setId":1, // The setId of the intro, has to be unique in all intros in intro_data.json
"verupNo":1, // Used to control whether the client should update to a new intro when offline cache files are still present
"mainSongNo":1115, // The uniqueId of the main song, which will be displayed at the top of the four other songs
"subSongNo":[1022,7,1089,1059] // The uniqueId of the four other songs, which will be displayed below the main song, there has to be 4 songs exactly
},
{
"setId":2,
"verupNo":1,
"mainSongNo":1102,
"subSongNo":[1065,966,1008,916]
}
]
```
### locked_songs_data.json
This is used to customize locked songs.
```json
{
"songNo": [
// Fill in the uniqueId of songs you wish to lock
// Songs locked will not be visible in-game to players(including guest) without the corresponding entry in UnlockedSongIdList in UserData, but may still show up in the shop folder
100,
200,
300
],
"uraSongNo": [
// Fill in the uniqueId of songs whose ura chart you wish to lock
]
}
```
### movie_data.json
This is used to control which in-game movie is displayed before entering the game
```json
[
{
"movie_id": 0, // The movie id can be the following:
//8 = iM@S 15th anniversary collab,
//9 = iM@S 15th anniversary collab (en),
//10 = ONE PIECE collab,
//12 = ONE PIECE collab 2 (special mode here),
//14 = Touhou collab 2021,
//15 = Taiko no Tatsujin 20th anniversary Soshina collab,
//16 = Taiko no Tatsujin 20th anniversary Soshina collab (en),
//17 = Taiko no Tatsujin 20th anniversary Soshina collab (zh-tw),
//18 = Taiko no Tatsujin 20th anniversary Soshina collab (ko)
"enable_days": 0 // Simply set to 999 for the movie to always be displayed
}
]
```
### qrcode_data.json
This is used to customize which qrcode's uniqueId is invoked when a qrcode request is received.
```json
[
{
"serial": "gaiden_blue_10dan", // QR serial data sent by TAL
"id": 20 // The uniqueId of the qrcode the server sends back, corresponding to the uniqueId in qrcode_info.bin in the client's datatable
},
{
"serial": "gaiden_white_10dan",
"id": 21
}
]
```
- shop_folder_data.json: This is used to customize the in-game shop folder content.
```json
[
{
"songNo": 100, // The uniqueId of the song
"type": 0, // 0 indicates the shop is selling the song itself
"price": 20 // How many don coins buying this song costs, the type of don coin is specified by token_data.json
},
{
"songNo": 200,
"type": 1, // And 1 indicates the shop is selling the song's ura chart
"price": 20
}
]
```
- token_data.json: This is used to customize in-game reward tokens.
```json
{
"shopTokenId": -1, // The token id used in shop, a.k.a. don coin, can be from 1 to 11, 1=spring, 2=summer, 3=autumn, 4=winter, 5=spring(again), etc. By default, this is turned off by setting it to -1
"kaTokenId": -1, // The token id of ka coins, can be 1000 or 1001, corresponding to reward entrys in reward.bin in the client's datatable. By default, this is turned off by setting it to -1
"onePieceTokenId": 100100, // The token id representing onePiece collab mode's win count, should not be changed
"soshinaTokenId": 100200 // The token id representing soshina collab mode's win count, should not be changed
}
```

View File

@ -23,17 +23,26 @@
}
else
{
<MudGrid Class="my-4 pb-10">
<MudItem xs="12">
<MudPaper Elevation="0" Outlined="true">
<MudTabs ActivePanelIndex="0" Rounded="true" Border="true" MinimumTabWidth="100px" PanelClass="pa-8">
@foreach (var danId in danMap.Keys)
@if (danMap.Count() == 0)
{
var danData = danMap[danId];
<MudItem xs="12">
<MudText Align="Align.Center" Class="my-8">
@Localizer["No Data"]
</MudText>
</MudItem>
}
else
{
@foreach (var danId in danMap.Keys)
{
var danData = danMap[danId];
<MudTabPanel Text="@GetDanTitle(danData.Title)" Icon="@GetDanResultIcon(danId)">
<MudText Typo="Typo.h5" Class="mb-4">@Localizer["Details"]</MudText>
<MudTabPanel Text="@GetDanTitle(danData.Title)" Icon="@GetDanResultIcon(danId)">
<MudText Typo="Typo.h5" Class="mb-4">@Localizer["Details"]</MudText>
<MudGrid Class="d-flex">
<MudItem xs="12" sm="12" md="4" Class="pb-1">
<MudCard Outlined="true" Style="height:100%;">
@ -45,11 +54,11 @@ else
<MudCardContent Class="d-flex py-10" Style="justify-content:center;">
<MudStack Justify="Justify.Center" AlignItems="AlignItems.Center" Spacing="6">
@{
var danResultState = GetDanResultState(danId);
var danClearStateString = GetDanClearStateString(danResultState);
}
<img src=@($"/images/dani_{danResultState.ToString()}.webp") style="max-width:150px; width:100%;" alt="danResultState.ToString()" />
<MudText Typo="Typo.body1">@danClearStateString</MudText>
var danResultState = GetDanResultState(danId);
var danClearStateString = GetDanClearStateString(danResultState);
}
<img src=@($"/images/dani_{danResultState.ToString()}.webp") style="max-width:150px; width:100%;" alt="danResultState.ToString()" />
<MudText Typo="Typo.body1">@danClearStateString</MudText>
</MudStack>
</MudCardContent>
</MudCard>
@ -65,11 +74,11 @@ else
</MudCardHeader>
<MudCardContent Class="d-flex py-10" Style="justify-content:center">
@{
var totalScore = GetTotalScore(danId);
}
var totalScore = GetTotalScore(danId);
}
<MudStack Row="true" Spacing="4" Justify="Justify.SpaceEvenly" Style="width:100%">
<MudText Typo="Typo.h4">@totalScore</MudText>
<MudStack Row="true" Spacing="4" Justify="Justify.SpaceEvenly" Style="width:100%">
<MudText Typo="Typo.h4">@totalScore</MudText>
</MudStack>
</MudCardContent>
</MudCard>
@ -81,17 +90,17 @@ else
</MudCardHeader>
<MudCardContent Class="d-flex py-10" Style="justify-content:center">
@{
var totalGoods = GetTotalGoodHits(danId);
var totalOks = GetTotalOkHits(danId);
var totalBads = GetTotalBadHits(danId);
var totalDrumroll = GetTotalDrumrollHits(danId);
var totalMaxCombo = GetTotalMaxCombo(danId);
var totalHits = GetTotalHits(danId);
}
var totalGoods = GetTotalGoodHits(danId);
var totalOks = GetTotalOkHits(danId);
var totalBads = GetTotalBadHits(danId);
var totalDrumroll = GetTotalDrumrollHits(danId);
var totalMaxCombo = GetTotalMaxCombo(danId);
var totalHits = GetTotalHits(danId);
}
<MudStack Row="true" Spacing="4" Justify="Justify.SpaceEvenly" Style="width:100%">
<MudCard Elevation="0">
<MudText Typo="Typo.caption">@Localizer["Good"]</MudText>
<MudStack Row="true" Spacing="4" Justify="Justify.SpaceEvenly" Style="width:100%">
<MudCard Elevation="0">
<MudText Typo="Typo.caption">@Localizer["Good"]</MudText>
<MudText Typo="Typo.body1" Style="font-weight: bold;">@totalGoods</MudText>
</MudCard>
<MudCard Elevation="0">
@ -126,26 +135,26 @@ else
<MudItem xs="12">
<MudGrid Class="d-block">
@for (uint j = 0; j <= 2; j++)
{
var index = (int)j;
var danDataOdaiSong = danData.OdaiSongList[index];
var stageNumber = j + 1;
var difficulty = (Difficulty)danDataOdaiSong.Level;
{
var index = (int)j;
var danDataOdaiSong = danData.OdaiSongList[index];
var stageNumber = j + 1;
var difficulty = (Difficulty)danDataOdaiSong.Level;
<MudItem xs="12" Class="pb-1">
<MudCard Outlined="true" Class="pa-4">
<MudGrid Style="display:flex; align-items: center; justify-content: flex-start;">
<MudItem xs="1" md="1" Style="display:flex;flex-direction:column;align-items:center;">
<h3>@stageNumber</h3>
</MudItem>
<MudItem xs="12" Class="pb-1">
<MudCard Outlined="true" Class="pa-4">
<MudGrid Style="display:flex; align-items: center; justify-content: flex-start;">
<MudItem xs="1" md="1" Style="display:flex;flex-direction:column;align-items:center;">
<h3>@stageNumber</h3>
</MudItem>
<MudItem xs="2" md="1" Style="display:flex;flex-direction:column;align-items:center;">
<MudTooltip Text="@difficulty.ToString()" Placement="Placement.Top" Arrow="true">
<img src=@($"/images/difficulty_{difficulty}.webp") style="width:40px;height:40px;margin-bottom:2px;" alt="@difficulty" />
</MudTooltip>
<MudStack Row="true" Spacing="1" Justify="Justify.Center" AlignItems="AlignItems.Center">
<MudIcon Icon="@Icons.Material.Filled.Star" Size="Size.Small" />
<MudText Typo="Typo.caption" Style="line-height:1;margin-top:2px;margin-right:2px;">@GameDataService.GetMusicStarLevel(musicDetailDictionary, danDataOdaiSong.SongNo, difficulty)</MudText>
<MudItem xs="2" md="1" Style="display:flex;flex-direction:column;align-items:center;">
<MudTooltip Text="@difficulty.ToString()" Placement="Placement.Top" Arrow="true">
<img src=@($"/images/difficulty_{difficulty}.webp") style="width:40px;height:40px;margin-bottom:2px;" alt="@difficulty" />
</MudTooltip>
<MudStack Row="true" Spacing="1" Justify="Justify.Center" AlignItems="AlignItems.Center">
<MudIcon Icon="@Icons.Material.Filled.Star" Size="Size.Small" />
<MudText Typo="Typo.caption" Style="line-height:1;margin-top:2px;margin-right:2px;">@GameDataService.GetMusicStarLevel(musicDetailDictionary, danDataOdaiSong.SongNo, difficulty)</MudText>
</MudStack>
</MudItem>
@ -158,15 +167,15 @@ else
@if (_bestDataMap.TryGetValue(danId, out var danBestData))
{
if (danBestData.DanBestStageDataList.Count > index)
{
var bestStage = danBestData.DanBestStageDataList[index];
if (danBestData.DanBestStageDataList.Count > index)
{
var bestStage = danBestData.DanBestStageDataList[index];
<MudItem xs="12" md="6" Style="display:flex;flex-direction:column;">
<MudStack Row="true" Spacing="4" Justify="Justify.SpaceEvenly">
<MudCard Elevation="0">
<MudText Typo="Typo.caption">@Localizer["Good"]</MudText>
<MudItem xs="12" md="6" Style="display:flex;flex-direction:column;">
<MudStack Row="true" Spacing="4" Justify="Justify.SpaceEvenly">
<MudCard Elevation="0">
<MudText Typo="Typo.caption">@Localizer["Good"]</MudText>
<MudText Typo="Typo.body1" Style="font-weight: bold;">@bestStage.GoodCount</MudText>
</MudCard>
<MudCard Elevation="0">
@ -191,17 +200,17 @@ else
</MudCard>
</MudStack>
</MudItem>
}
}
}
</MudGrid>
</MudCard>
</MudItem>
}
</MudGrid>
</MudItem>
</MudGrid>
</MudGrid>
</MudCard>
</MudItem>
}
</MudGrid>
</MudItem>
</MudGrid>
<MudText Typo="Typo.h5" Class="mt-10 mb-4">@Localizer["Conditions"]</MudText>
<MudText Typo="Typo.h5" Class="mt-10 mb-4">@Localizer["Conditions"]</MudText>
<MudGrid>
<MudItem xs="12" Class="dani-results">
<MudGrid>
@ -215,42 +224,42 @@ else
<MudCardContent>
<MudStack Spacing="8">
@{
var redRequirement = GetSoulGauge(danData, false);
var goldRequirement = GetSoulGauge(danData, true);
var barClass = "bar-default";
var resultText = Localizer["Not Passed"];
}
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
var redRequirement = GetSoulGauge(danData, false);
var goldRequirement = GetSoulGauge(danData, true);
var barClass = "bar-default";
var resultText = Localizer["Not Passed"];
}
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
@if (_bestDataMap.TryGetValue(danId, out var danBestData))
{
if (danBestData.SoulGaugeTotal >= redRequirement)
{
barClass = "bar-pass-red";
resultText = Localizer["Pass"];
}
if (danBestData.SoulGaugeTotal >= redRequirement)
{
barClass = "bar-pass-red";
resultText = Localizer["Pass"];
}
if (danBestData.SoulGaugeTotal >= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = Localizer["Gold"];
}
if (danBestData.SoulGaugeTotal >= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = Localizer["Gold"];
}
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="100" Value="@danBestData.SoulGaugeTotal">
<MudText Typo="Typo.caption">@danBestData.SoulGaugeTotal%</MudText>
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="100" Value="@danBestData.SoulGaugeTotal">
<MudText Typo="Typo.caption">@danBestData.SoulGaugeTotal%</MudText>
</MudProgressLinear>
<MudText Typo="Typo.caption" Style="text-align: right">@resultText</MudText>
}
else
{
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="100" Value="0">
<MudText Typo="Typo.caption">0%</MudText>
</MudProgressLinear>
<MudText Typo="Typo.caption" Style="text-align: right">N/A</MudText>
}
</MudStack>
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold">@Localizer["Conditions"]</MudText>
<MudText Typo="Typo.caption" Style="text-align: right">@resultText</MudText>
}
else
{
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="100" Value="0">
<MudText Typo="Typo.caption">0%</MudText>
</MudProgressLinear>
<MudText Typo="Typo.caption" Style="text-align: right">N/A</MudText>
}
</MudStack>
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold">@Localizer["Conditions"]</MudText>
<MudStack Row="true" Spacing="16">
<MudStack Spacing="0">
<MudText Typo="Typo.caption">@Localizer["Red"]</MudText>
@ -270,92 +279,92 @@ else
<MudItem xs="12" md="9">
<MudStack Spacing="4">
@for (var j = 1; j < danData.OdaiBorderList.Count; j++)
{
var border = danData.OdaiBorderList[j];
{
var border = danData.OdaiBorderList[j];
<MudCard Outlined="true" Class="pa-2">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">
@GetDanRequirementTitle(border)
</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
@{
var borderType = (DanBorderType)border.BorderType;
}
@if (borderType == DanBorderType.All)
{
<MudStack Spacing="8">
@{
var redRequirement = border.RedBorderTotal;
var goldRequirement = border.GoldBorderTotal;
var barClass = "bar-default";
var resultText = Localizer["Not Passed"];
}
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
<MudCard Outlined="true" Class="pa-2">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">
@GetDanRequirementTitle(border)
</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
@{
var borderType = (DanBorderType)border.BorderType;
}
@if (borderType == DanBorderType.All)
{
<MudStack Spacing="8">
@{
var redRequirement = border.RedBorderTotal;
var goldRequirement = border.GoldBorderTotal;
var barClass = "bar-default";
var resultText = Localizer["Not Passed"];
}
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
@if (_bestDataMap.TryGetValue(danId, out var danBestData))
{
var bestData = GetAllBestFromData((DanConditionType)border.OdaiType, danBestData);
if ((DanConditionType)border.OdaiType is DanConditionType.BadCount or DanConditionType.OkCount)
{
if (bestData <= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = Localizer["Gold"];
}
else if (bestData <= redRequirement)
{
barClass = "bar-pass-red";
resultText = Localizer["Pass"];
}
var bestData = GetAllBestFromData((DanConditionType)border.OdaiType, danBestData);
var resultValue = redRequirement - bestData;
if (resultValue < 0) resultValue = 0;
if ((DanConditionType)border.OdaiType is DanConditionType.BadCount or DanConditionType.OkCount)
{
if (bestData <= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = Localizer["Gold"];
}
else if (bestData <= redRequirement)
{
barClass = "bar-pass-red";
resultText = Localizer["Pass"];
}
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@redRequirement" Value="@resultValue">
<MudText Typo="Typo.caption">@resultValue</MudText>
var resultValue = redRequirement - bestData;
if (resultValue < 0) resultValue = 0;
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@redRequirement" Value="@resultValue">
<MudText Typo="Typo.caption">@resultValue</MudText>
</MudProgressLinear>
<MudText Typo="Typo.caption" Style="text-align: right">@resultText</MudText>
<MudText Typo="Typo.caption" Style="text-align: right">@resultText</MudText>
}
else
{
if (bestData >= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = Localizer["Gold"];
}
else if (bestData >= redRequirement)
{
barClass = "bar-pass-red";
resultText = Localizer["Pass"];
}
var resultValue = Math.Min(bestData, redRequirement);
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@redRequirement" Value="@resultValue">
<MudText Typo="Typo.caption">@bestData</MudText>
</MudProgressLinear>
<MudText Typo="Typo.caption" Style="text-align: right">@resultText</MudText>
}
}
else
{
if (bestData >= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = Localizer["Gold"];
}
else if (bestData >= redRequirement)
{
barClass = "bar-pass-red";
resultText = Localizer["Pass"];
}
var resultValue = Math.Min(bestData, redRequirement);
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@redRequirement" Value="@resultValue">
<MudText Typo="Typo.caption">@bestData</MudText>
</MudProgressLinear>
<MudText Typo="Typo.caption" Style="text-align: right">@resultText</MudText>
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="100" Value="0">
<MudText Typo="Typo.caption">0</MudText>
</MudProgressLinear>
<MudText Typo="Typo.caption" Style="text-align: right">N/A</MudText>
}
</MudStack>
@{
var conditionOperator = GetDanConditionOperator((DanConditionType)border.OdaiType);
}
else
{
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="100" Value="0">
<MudText Typo="Typo.caption">0</MudText>
</MudProgressLinear>
<MudText Typo="Typo.caption" Style="text-align: right">N/A</MudText>
}
</MudStack>
@{
var conditionOperator = GetDanConditionOperator((DanConditionType)border.OdaiType);
}
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold">@Localizer["Conditions"]</MudText>
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold">@Localizer["Conditions"]</MudText>
<MudStack Row="true" Spacing="16">
<MudStack Spacing="0">
<MudText Typo="Typo.caption">@Localizer["Red"]</MudText>
@ -368,94 +377,94 @@ else
</MudStack>
</MudStack>
</MudStack>
}
else
{
<MudGrid>
@for (var k = 0; k < 3; k++)
{
var songNumber = k;
var redRequirement = GetSongBorderCondition(border, songNumber, false);
var goldRequirement = GetSongBorderCondition(border, songNumber, true);
var barClass = "bar-default";
var resultText = Localizer["Not Cleared"];
}
else
{
<MudGrid>
@for (var k = 0; k < 3; k++)
{
var songNumber = k;
var redRequirement = GetSongBorderCondition(border, songNumber, false);
var goldRequirement = GetSongBorderCondition(border, songNumber, true);
var barClass = "bar-default";
var resultText = Localizer["Not Cleared"];
<MudItem xs="12" md="4">
<MudCard Outlined="true">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.body1" Style="font-weight:bold">@Localizer["Stage"] @(songNumber + 1)</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
<MudItem xs="12" md="4">
<MudCard Outlined="true">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.body1" Style="font-weight:bold">@Localizer["Stage"] @(songNumber + 1)</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
@if (_bestDataMap.TryGetValue(danId, out var danBestData) && (danBestData.DanBestStageDataList.Count > songNumber))
{
var bestData = GetSongBestFromData((DanConditionType)border.OdaiType, danBestData, songNumber);
if ((DanConditionType)border.OdaiType is DanConditionType.BadCount or DanConditionType.OkCount)
{
if (bestData <= redRequirement)
var bestData = GetSongBestFromData((DanConditionType)border.OdaiType, danBestData, songNumber);
if ((DanConditionType)border.OdaiType is DanConditionType.BadCount or DanConditionType.OkCount)
{
barClass = "bar-pass-red";
resultText = Localizer["Pass"];
}
if (bestData <= redRequirement)
{
barClass = "bar-pass-red";
resultText = Localizer["Pass"];
}
if (bestData <= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = Localizer["Gold"];
}
if (bestData <= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = Localizer["Gold"];
}
var resultValue = redRequirement - bestData;
if (bestData >= redRequirement) resultValue = 0;
var resultValue = redRequirement - bestData;
if (bestData >= redRequirement) resultValue = 0;
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@redRequirement" Value="@resultValue">
<MudText Typo="Typo.caption">@resultValue</MudText>
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@redRequirement" Value="@resultValue">
<MudText Typo="Typo.caption">@resultValue</MudText>
</MudProgressLinear>
<MudText Typo="Typo.caption" Style="text-align: right">@resultText</MudText>
<MudText Typo="Typo.caption" Style="text-align: right">@resultText</MudText>
}
else
{
if (bestData >= redRequirement)
{
barClass = "bar-pass-red";
resultText = Localizer["Pass"];
}
if (bestData >= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = Localizer["Gold"];
}
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@(goldRequirement > 0 ? goldRequirement : 1)" Value="@(goldRequirement > 0 ? bestData : 1)">
<MudText Typo="Typo.caption">@bestData</MudText>
</MudProgressLinear>
<MudStack Class="mt-1" AlignItems="AlignItems.End">
<MudText Typo="Typo.caption">@resultText</MudText>
</MudStack>
}
}
else
{
if (bestData >= redRequirement)
{
barClass = "bar-pass-red";
resultText = Localizer["Pass"];
}
if (bestData >= goldRequirement)
{
barClass = "bar-pass-gold";
resultText = Localizer["Gold"];
}
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@(goldRequirement > 0 ? goldRequirement : 1)" Value="@(goldRequirement > 0 ? bestData : 1)">
<MudText Typo="Typo.caption">@bestData</MudText>
</MudProgressLinear>
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="100" Value="0">
<MudText Typo="Typo.caption">0</MudText>
</MudProgressLinear>
<MudStack Class="mt-1" AlignItems="AlignItems.End">
<MudText Typo="Typo.caption">@resultText</MudText>
</MudStack>
<MudText Typo="Typo.caption">N/A</MudText>
</MudStack>
}
}
else
{
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="100" Value="0">
<MudText Typo="Typo.caption">0</MudText>
</MudProgressLinear>
<MudStack Class="mt-1" AlignItems="AlignItems.End">
<MudText Typo="Typo.caption">N/A</MudText>
</MudStack>
}
@{
var conditionOperator = GetDanConditionOperator((DanConditionType)border.OdaiType);
@{
var conditionOperator = GetDanConditionOperator((DanConditionType)border.OdaiType);
if (redRequirement == 0)
{
conditionOperator = "";
if (redRequirement == 0)
{
conditionOperator = "";
}
}
}
<MudStack Spacing="1" Class="mt-8">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold">@Localizer["Conditions"]</MudText>
<MudStack Spacing="1" Class="mt-8">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold">@Localizer["Conditions"]</MudText>
<MudStack Row="true" Spacing="16">
<MudStack Spacing="0">
<MudText Typo="Typo.caption">@Localizer["Red"]</MudText>
@ -470,18 +479,19 @@ else
</MudCardContent>
</MudCard>
</MudItem>
}
</MudGrid>
}
</MudCardContent>
</MudCard>
}
</MudStack>
</MudItem>
</MudGrid>
</MudItem>
</MudGrid>
</MudTabPanel>
}
</MudGrid>
}
</MudCardContent>
</MudCard>
}
</MudStack>
</MudItem>
</MudGrid>
</MudItem>
</MudGrid>
</MudTabPanel>
}
}
</MudTabs>
</MudPaper>

View File

@ -1,10 +1,14 @@
using System.Collections.Immutable;
using Microsoft.JSInterop;
using Microsoft.Extensions.Options;
using System.Collections.Immutable;
using TaikoWebUI.Settings;
namespace TaikoWebUI.Pages;
public partial class DaniDojo
{
[Inject]
IOptions<WebUiSettings> UiSettings { get; set; } = default!;
[Parameter]
public int Baid { get; set; }
@ -16,6 +20,7 @@ public partial class DaniDojo
private static Dictionary<uint, DanBestData> _bestDataMap = new();
private Dictionary<uint, MusicDetail> musicDetailDictionary = new();
private ImmutableDictionary<uint, DanData> danMap = ImmutableDictionary<uint, DanData>.Empty;
private Dictionary<uint, DanData> danMapTemp = new();
protected override async Task OnInitializedAsync()
{
@ -30,10 +35,21 @@ public partial class DaniDojo
response.ThrowIfNull();
response.DanBestDataList.ForEach(data => data.DanBestStageDataList
.Sort((stageData, otherStageData) => stageData.SongNumber.CompareTo(otherStageData.SongNumber)));
_bestDataMap = response.DanBestDataList.ToDictionary(data => data.DanId);
danMap = GameDataService.GetDanMap();
if (!UiSettings.Value.DisplayUnplayedDans)
{
foreach (var best in _bestDataMap)
{
var value = danMap.First(dan => dan.Key == best.Key);
danMapTemp.Add(value.Key, value.Value);
}
danMap = danMapTemp.ToImmutableDictionary();
}
SongNameLanguage = await LocalStorage.GetItemAsync<string>("songNameLanguage");
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
@ -171,26 +187,26 @@ public partial class DaniDojo
};
}
private string GetDanResultIcon(uint danId)
private static string GetDanResultIcon(uint danId)
{
string icon;
const string notClearIcon = "<image href='/images/dani_NotClear.webp' width='24' height='24' style='filter: contrast(0.65)'/>";
if (!_bestDataMap.ContainsKey(danId))
if (!_bestDataMap.TryGetValue(danId, out DanBestData? value))
{
return notClearIcon;
}
var state = _bestDataMap[danId].ClearState;
var state = value.ClearState;
icon = state is DanClearState.NotClear ? notClearIcon : $"<image href='/images/dani_{state}.webp' width='24' height='24' />";
return icon;
}
private DanClearState GetDanResultState(uint danId)
private static DanClearState GetDanResultState(uint danId)
{
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].ClearState : DanClearState.NotClear;
return _bestDataMap.TryGetValue(danId, out DanBestData? value) ? value.ClearState : DanClearState.NotClear;
}
private static uint GetSoulGauge(DanData data, bool isGold)
@ -217,36 +233,36 @@ public partial class DaniDojo
private static long GetTotalScore(uint danId)
{
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.HighScore) : 0;
return _bestDataMap.TryGetValue(danId, out DanBestData? value) ? value.DanBestStageDataList.Sum(stageData => stageData.HighScore) : 0;
}
private static long GetTotalGoodHits(uint danId)
{
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.GoodCount) : 0;
return _bestDataMap.TryGetValue(danId, out DanBestData? value) ? value.DanBestStageDataList.Sum(stageData => stageData.GoodCount) : 0;
}
private static long GetTotalOkHits(uint danId)
{
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.OkCount) : 0;
return _bestDataMap.TryGetValue(danId, out DanBestData? value) ? value.DanBestStageDataList.Sum(stageData => stageData.OkCount) : 0;
}
private static long GetTotalBadHits(uint danId)
{
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.BadCount) : 0;
return _bestDataMap.TryGetValue(danId, out DanBestData? value) ? value.DanBestStageDataList.Sum(stageData => stageData.BadCount) : 0;
}
private static long GetTotalDrumrollHits(uint danId)
{
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.DrumrollCount) : 0;
return _bestDataMap.TryGetValue(danId, out DanBestData? value) ? value.DanBestStageDataList.Sum(stageData => stageData.DrumrollCount) : 0;
}
private static long GetTotalMaxCombo(uint danId)
{
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.ComboCount) : 0;
return _bestDataMap.TryGetValue(danId, out DanBestData? value) ? value.DanBestStageDataList.Sum(stageData => stageData.ComboCount) : 0;
}
private static long GetTotalHits(uint danId)
{
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.TotalHitCount) : 0;
return _bestDataMap.TryGetValue(danId, out DanBestData? value) ? value.DanBestStageDataList.Sum(stageData => stageData.TotalHitCount) : 0;
}
}

View File

@ -1,5 +1,52 @@
# Taiko Web UI
This is the solution for the front end part.
This is the solution for the front end part.
It is implemented with Blazor Webassembly (also in C#).
The front end is implemented with Blazor Webassembly (also in C#).
## TaikoWebUI appsettings.json config
This section is for configuring the TaikoWebUI [appsettings.json](./wwwroot/appsettings.json) file.
This file is used to configure the web UI.
```json
{
"WebUiSettings": {
"Title": "TaikoWebUI",
"LoginRequired": "false", //Setting this to true will change the UI to allow users to register / login.
"OnlyAdmin": "false",
"BoundAccessCodeUpperLimit": "3",
"RegisterWithLastPlayTime": "false",
"AllowUserDelete": "true",
"AllowFreeProfileEditing": "true", //Enabling this allows user to set all their profile settings freely
//Bypassing the need to unlock titles, costumes, etc.
"DisplayUnplayedDans": "false", //Display all Dans, even ones that haven't been played yet.
"MaxWidth": "3", //0:Large, 1:Medium, 2:Small, 3:ExtraLarge, 4:ExtraExtraLarge
"SongLeaderboardSettings": {
"DisablePagination": "false",
"PageSize": "10"
},
"SupportedLanguages": [
{
"CultureCode": "en-US",
"DisplayName": "English"
},
{
"CultureCode": "fr-FR",
"DisplayName": "Français"
},
{
"CultureCode": "zh-Hans",
"DisplayName": "简体中文"
},
{
"CultureCode": "zh-Hant",
"DisplayName": "繁體中文"
},
{
"CultureCode": "ja",
"DisplayName": "日本語"
}
]
}
}
```

View File

@ -10,6 +10,8 @@ public class WebUiSettings
public bool AllowUserDelete { get; set; }
public bool AllowFreeProfileEditing { get; set; }
public bool DisplayUnplayedDans { get; set; }
public MaxWidth MaxWidth { get; set; }
public SongLeaderboardSettings SongLeaderboardSettings { get; set; } = new SongLeaderboardSettings();

View File

@ -7,6 +7,8 @@
"RegisterWithLastPlayTime": "false",
"AllowUserDelete": "true",
"AllowFreeProfileEditing": "true",
"DisplayUnplayedDans": "true",
"MaxWidth": "3",
"SongLeaderboardSettings": {
"DisablePagination": "false",
"PageSize": "10"