1
0
mirror of synced 2025-02-26 14:51:38 +01:00

Add Compete Dialog

This commit is contained in:
ptmaster 2024-09-15 22:56:28 +08:00
parent d755d6edd5
commit 9bb363e1b8
36 changed files with 5831 additions and 3717 deletions

View File

@ -0,0 +1,37 @@
using SharedProject.Enums;
using System.Text.Json.Serialization;
namespace SharedProject.Models;
public class ChallengeCompeteCreateInfo
{
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
[JsonPropertyName("desc")]
public string Desc { get; set; } = string.Empty;
[JsonPropertyName("competeMode")]
public CompeteModeType CompeteMode { get; set; }
[JsonPropertyName("maxParticipant")]
public uint MaxParticipant { get; set; }
[JsonPropertyName("onlyPlayOnce")]
public bool OnlyPlayOnce { get; set; }
[JsonPropertyName("lastFor")]
public uint LastFor { get; set; }
[JsonPropertyName("requiredTitle")]
public uint RequiredTitle { get; set; } = 0;
[JsonPropertyName("shareType")]
public ShareType ShareType { get; set; } = ShareType.EveryOne;
[JsonPropertyName("competeTargetType")]
public CompeteTargetType CompeteTargetType { get; set; } = CompeteTargetType.EveryOne;
[JsonPropertyName("challengeCompeteSongs")]
public List<ChallengeCompeteCreateSongInfo> challengeCompeteSongs { get; set; } = new();
}

View File

@ -0,0 +1,24 @@
using SharedProject.Enums;
using System.Text.Json.Serialization;
namespace SharedProject.Models;
public class ChallengeCompeteCreateSongInfo
{
[JsonPropertyName("songId")]
public uint SongId { get; set; }
[JsonPropertyName("difficulty")]
public Difficulty Difficulty { get; set; }
[JsonPropertyName("speed")]
public int Speed { get; set; } = -1;
[JsonPropertyName("isVanishOn")]
public int IsVanishOn { get; set; } = -1;
[JsonPropertyName("isInverseOn")]
public int IsInverseOn { get; set; } = -1;
[JsonPropertyName("randomType")]
public int RandomType { get; set; } = -1;
}

View File

@ -0,0 +1,22 @@
using SharedProject.Enums;
namespace SharedProject.Models;
public class ChallengeCompetition
{
public uint CompId { get; set; }
public CompeteModeType CompeteMode { get; set; } = CompeteModeType.None;
public CompeteState State { get; set; } = CompeteState.Normal;
public uint Baid { get; set; }
public string CompeteName { get; set; } = String.Empty;
public string CompeteDescribe { get; set; } = String.Empty;
public uint MaxParticipant { get; set; } = 2;
public DateTime CreateTime { get; set; }
public DateTime ExpireTime { get; set; }
public uint RequireTitle { get; set; } = 0;
public bool OnlyPlayOnce { get; set; } = false;
public ShareType Share { get; set; } = ShareType.EveryOne;
public CompeteTargetType CompeteTarget { get; set; } = CompeteTargetType.EveryOne;
public List<ChallengeCompetitionSong> Songs { get; set; } = new();
public List<ChallengeCompetitionParticipant> Participants { get; set; } = new();
}

View File

@ -0,0 +1,23 @@
using SharedProject.Enums;
namespace SharedProject.Models;
public class ChallengeCompetitionBestScore
{
public uint CompId { get; set; }
public uint Baid { get; set; }
public uint SongId { get; set; }
public UserAppearance? UserAppearance { get; set; } = null;
public Difficulty Difficulty { get; set; }
public CrownType Crown { get; set; }
public uint Score { get; set; }
public uint ScoreRate { get; set; }
public ScoreRank ScoreRank { get; set; }
public uint GoodCount { get; set; }
public uint OkCount { get; set; }
public uint MissCount { get; set; }
public uint ComboCount { get; set; }
public uint HitCount { get; set; }
public uint DrumrollCount { get; set; }
public bool Skipped { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace SharedProject.Models;
public class ChallengeCompetitionParticipant
{
public uint CompId { get; set; }
public uint Baid { get; set; }
public bool IsActive { get; set; }
}

View File

@ -0,0 +1,16 @@
using SharedProject.Enums;
namespace SharedProject.Models;
public class ChallengeCompetitionSong
{
public uint CompId { get; set; }
public uint SongId { get; set; }
public MusicDetail? MusicDetail { get; set; } = null;
public Difficulty Difficulty { get; set; }
public uint? Speed { get; set; } = null;
public bool? IsVanishOn { get; set; } = null;
public bool? IsInverseOn { get; set; } = null;
public RandomType? RandomType { get; set; } = null;
public List<ChallengeCompetitionBestScore> BestScores { get; set; } = new();
}

View File

@ -0,0 +1,8 @@
namespace SharedProject.Models.Requests;
public class AnswerChallengeRequest
{
public uint CompId { get; set; }
public uint Baid { get; set; }
public bool Accept { get; set; }
}

View File

@ -0,0 +1,10 @@
namespace SharedProject.Models.Responses;
public class ChallengeCompetitionResponse
{
public List<ChallengeCompetition> List { get; set; } = new();
public int Page { get; set; } = 1;
public int TotalPages { get; set; } = 0;
public int Total { get; set; } = 0;
}

View File

@ -0,0 +1,11 @@
using SharedProject.Enums;
namespace SharedProject.Models;
public class SongInfo
{
public MusicDetail MusicDetail { get; set; } = new();
public Difficulty Difficulty { get; set; } = new();
}

View File

@ -0,0 +1,18 @@
namespace SharedProject.Models;
public class UserAppearance
{
public uint Baid { get; set; }
public string MyDonName { get; set; } = string.Empty;
public uint MyDonNameLanguage { get; set; }
public string Title { get; set; } = string.Empty;
public uint TitlePlateId { get; set; }
public uint Kigurumi { get; set; }
public uint Head { get; set; }
public uint Body { get; set; }
public uint Face { get; set; }
public uint Puchi { get; set; }
public uint FaceColor { get; set; }
public uint BodyColor { get; set; }
public uint LimbColor { get; set; }
}

View File

@ -1,4 +1,7 @@
using TaikoLocalServer.Filters;
using MediatR;
using SharedProject.Models;
using SharedProject.Models.Responses;
using TaikoLocalServer.Filters;
namespace TaikoLocalServer.Controllers.Api;
@ -8,33 +11,49 @@ public class ChallengeCompeteManageController(IChallengeCompeteService challenge
{
[HttpGet]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public ActionResult<List<ChallengeCompeteDatum>> GetAllChallengeCompete()
public async Task<ActionResult<List<ChallengeCompetition>>> GetAllChallengeCompete()
{
List<ChallengeCompeteDatum> datum = challengeCompeteService.GetAllChallengeCompete();
List<ChallengeCompeteDatum> datum = await challengeCompeteService.GetAllChallengeCompete();
List<ChallengeCompetition> converted = new();
foreach (var data in datum)
{
foreach (var participant in data.Participants)
{
participant.ChallengeCompeteData = null;
}
foreach (var song in data.Songs)
{
song.ChallengeCompeteData = null;
foreach (var bestScore in song.BestScores)
{
bestScore.ChallengeCompeteSongData = null;
}
}
var challengeCompetition = Mappers.ChallengeCompeMappers.MapData(data);
challengeCompetition = await challengeCompeteService.FillData(challengeCompetition);
converted.Add(challengeCompetition);
}
return Ok(datum);
return Ok(converted);
}
[HttpGet("queryPage")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<ActionResult<ChallengeCompetitionResponse>> GetChallengeCompePage([FromQuery] uint mode = 0, [FromQuery] uint baid = 0, [FromQuery] int inProgress = 0, [FromQuery] int page = 1, [FromQuery] int limit = 10, [FromQuery] string? searchTerm = null)
{
if (page < 1)
{
return BadRequest(new { Message = "Page number cannot be less than 1." });
}
if (limit > 200)
{
return BadRequest(new { Message = "Limit cannot be greater than 200." });
}
if (mode == 0)
{
return BadRequest(new { Message = "Invalid mode." });
}
ChallengeCompetitionResponse response = await challengeCompeteService.GetChallengeCompetePage((CompeteModeType)mode, baid, inProgress != 0, page, limit, searchTerm);
return Ok(response);
}
[HttpGet("test/{mode}")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public ActionResult<List<ChallengeCompeteDatum>> testCreateCompete(uint mode)
{
ChallengeCompeteInfo info = new()
ChallengeCompeteCreateInfo info = new()
{
Name = "测试数据",
Desc = "测试数据描述",
@ -48,7 +67,7 @@ public class ChallengeCompeteManageController(IChallengeCompeteService challenge
new() {
SongId = 1,
Difficulty = Difficulty.Oni,
RandomType = RandomType.Messy
RandomType = (int)RandomType.Messy
},
new() {
SongId = 2,
@ -65,10 +84,22 @@ public class ChallengeCompeteManageController(IChallengeCompeteService challenge
return NoContent();
}
[HttpPost("createOfficialCompete")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> CreateCompete(ChallengeCompeteCreateInfo challengeCompeteInfo)
{
Logger.LogInformation("CreateOfficialCompete : {Request}", JsonFormatter.JsonSerialize(challengeCompeteInfo));
await challengeCompeteService.CreateCompete(0, challengeCompeteInfo);
return NoContent();
}
[HttpPost("{baid}/createCompete")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> CreateCompete(uint baid, ChallengeCompeteInfo challengeCompeteInfo)
public async Task<IActionResult> CreateCompete(uint baid, ChallengeCompeteCreateInfo challengeCompeteInfo)
{
Logger.LogInformation("CreateCompete : {Request}", JsonFormatter.JsonSerialize(challengeCompeteInfo));
await challengeCompeteService.CreateCompete(baid, challengeCompeteInfo);
return NoContent();
@ -76,8 +107,9 @@ public class ChallengeCompeteManageController(IChallengeCompeteService challenge
[HttpPost("{baid}/createChallenge/{targetBaid}")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> CreateChallenge(uint baid, uint targetBaid, ChallengeCompeteInfo challengeCompeteInfo)
public async Task<IActionResult> CreateChallenge(uint baid, uint targetBaid, ChallengeCompeteCreateInfo challengeCompeteInfo)
{
Logger.LogInformation("CreateChallenge : {Request}", JsonFormatter.JsonSerialize(challengeCompeteInfo));
await challengeCompeteService.CreateChallenge(baid, targetBaid, challengeCompeteInfo);
return NoContent();

View File

@ -10,22 +10,21 @@ public class ChallengeCompetitionController : BaseController<ChallengeCompetitio
{
Logger.LogInformation("ChallengeCompe request : {Request}", request.Stringify());
var response = await Mediator.Send(new ChallengeCompeteQuery(request.Baid));
CommonChallengeCompeResponse response = await Mediator.Send(new ChallengeCompeteQuery(request.Baid));
var response_3906 = Mappers.ChallengeCompeMappers.MapTo3906(response);
return Ok(response);
return Ok(response_3906);
}
[HttpPost("/v12r00_cn/chassis/challengecompe.php")]
[Produces("application/protobuf")]
public IActionResult HandleChallenge3209([FromBody] Models.v3209.ChallengeCompeRequest request)
public async Task<IActionResult> HandleChallenge3209([FromBody] Models.v3209.ChallengeCompeRequest request)
{
Logger.LogInformation("ChallengeCompe request : {Request}", request.Stringify());
var response = new Models.v3209.ChallengeCompeResponse
{
Result = 1
};
CommonChallengeCompeResponse response = await Mediator.Send(new ChallengeCompeteQuery((uint)request.Baid));
var response_3209 = Mappers.ChallengeCompeMappers.MapTo3209(response);
return Ok(response);
return Ok(response_3209);
}
}

View File

@ -4,23 +4,23 @@ using System.Buffers.Binary;
namespace TaikoLocalServer.Handlers;
public record ChallengeCompeteQuery(uint Baid) : IRequest<ChallengeCompeResponse>;
public record ChallengeCompeteQuery(uint Baid) : IRequest<CommonChallengeCompeResponse>;
public class ChallengeCompeteQueryHandler(TaikoDbContext context, IChallengeCompeteService challengeCompeteService, ILogger<UserDataQueryHandler> logger)
: IRequestHandler<ChallengeCompeteQuery, ChallengeCompeResponse>
public class ChallengeCompeteQueryHandler(IChallengeCompeteService challengeCompeteService)
: IRequestHandler<ChallengeCompeteQuery, CommonChallengeCompeResponse>
{
public async Task<ChallengeCompeResponse> Handle(ChallengeCompeteQuery request, CancellationToken cancellationToken)
public async Task<CommonChallengeCompeResponse> Handle(ChallengeCompeteQuery request, CancellationToken cancellationToken)
{
List<ChallengeCompeteDatum> competes = challengeCompeteService.GetInProgressChallengeCompete(request.Baid);
ChallengeCompeResponse response = new()
List<ChallengeCompeteDatum> competes = await challengeCompeteService.GetInProgressChallengeCompete(request.Baid);
CommonChallengeCompeResponse response = new()
{
Result = 1
};
foreach (var compete in competes)
{
ChallengeCompeResponse.CompeData compeData = new()
CommonCompeData compeData = new()
{
CompeId = compete.CompId
};
@ -45,7 +45,7 @@ public class ChallengeCompeteQueryHandler(TaikoDbContext context, IChallengeComp
}
}
ChallengeCompeResponse.CompeData.TracksData tracksData = new()
CommonTracksData tracksData = new()
{
SongNo = song.SongId,
Level = (uint) song.Difficulty,

View File

@ -73,7 +73,7 @@ public class UserDataQueryHandler(TaikoDbContext context, IGameDataService gameD
}
}
bool hasChallengeCompe = challengeCompeteService.HasChallengeCompete(request.Baid);
bool hasChallengeCompe = await challengeCompeteService.HasChallengeCompete(request.Baid);
var response = new CommonUserDataResponse
{

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using SharedProject.Models;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class ChallengeCompeMappers
{
public static partial ChallengeCompeResponse MapTo3906(CommonChallengeCompeResponse response);
public static partial Models.v3209.ChallengeCompeResponse MapTo3209(CommonChallengeCompeResponse response);
public static partial ChallengeCompetition MapData(ChallengeCompeteDatum data);
}

View File

@ -0,0 +1,8 @@
namespace TaikoLocalServer.Models.Application;
public class CommonChallengeCompeRequest
{
public uint Baid { get; set; }
public string ChassisId { get; set; } = string.Empty;
public string ShopId { get; set; } = string.Empty;
}

View File

@ -0,0 +1,23 @@
namespace TaikoLocalServer.Models.Application;
public class CommonChallengeCompeResponse
{
public uint Result { get; set; }
public List<CommonCompeData> AryChallengeStats { get; set; } = [];
public List<CommonCompeData> AryUserCompeStats { get; set; } = [];
public List<CommonCompeData> AryBngCompeStats { get; set; } = [];
}
public class CommonCompeData
{
public uint CompeId { get; set; }
public List<CommonTracksData> AryTrackStats { get; set; } = [];
}
public class CommonTracksData
{
public uint SongNo { get; set; }
public uint Level { get; set; }
public byte[] OptionFlg { get; set; } = [];
public uint HighScore { get; set; }
}

View File

@ -1,5 +1,4 @@
using SharedProject.Models;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace TaikoLocalServer.Models;

View File

@ -1,7 +1,9 @@

using GameDatabase.Context;
using SharedProject.Models;
using SharedProject.Models.Responses;
using SharedProject.Utils;
using TaikoLocalServer.Services.Interfaces;
using Throw;
namespace TaikoLocalServer.Services;
@ -9,18 +11,26 @@ namespace TaikoLocalServer.Services;
public class ChallengeCompeteService : IChallengeCompeteService
{
private readonly TaikoDbContext context;
public ChallengeCompeteService(TaikoDbContext context)
private readonly IGameDataService gameDataService;
private readonly IUserDatumService userDatumService;
private Dictionary<uint, MusicDetail> musicDetailDict;
public ChallengeCompeteService(TaikoDbContext context, IGameDataService gameDataService, IUserDatumService userDatumService)
{
this.context = context;
this.gameDataService = gameDataService;
this.userDatumService = userDatumService;
this.musicDetailDict = gameDataService.GetMusicDetailDictionary();
}
public bool HasChallengeCompete(uint baid)
public async Task<bool> HasChallengeCompete(uint baid)
{
return context.ChallengeCompeteData
return await context.ChallengeCompeteData
.Include(c => c.Participants)
.Include(c => c.Songs)
.ThenInclude(s => s.BestScores)
.Any(data =>
.AnyAsync(data =>
data.State == CompeteState.Normal &&
data.CreateTime < DateTime.Now &&
data.ExpireTime > DateTime.Now &&
@ -32,9 +42,9 @@ public class ChallengeCompeteService : IChallengeCompeteService
);
}
public List<ChallengeCompeteDatum> GetInProgressChallengeCompete(uint baid)
public async Task<List<ChallengeCompeteDatum>> GetInProgressChallengeCompete(uint baid)
{
return context.ChallengeCompeteData
return await context.ChallengeCompeteData
.Include(c => c.Participants)
.Include(c => c.Songs)
.ThenInclude(s => s.BestScores)
@ -47,19 +57,76 @@ public class ChallengeCompeteService : IChallengeCompeteService
// Only Play Once need there is no Score for current Compete
!data.OnlyPlayOnce || data.Songs.Any(song => !song.BestScores.Any(s => s.Baid == baid))
)
).ToList();
).ToListAsync();
}
public List<ChallengeCompeteDatum> GetAllChallengeCompete()
public async Task<List<ChallengeCompeteDatum>> GetAllChallengeCompete()
{
return context.ChallengeCompeteData
return await context.ChallengeCompeteData
.Include(c => c.Participants)
.Include(c => c.Songs)
.ThenInclude(s => s.BestScores)
.Where(data => true).ToList();
.Where(data => true).ToListAsync();
}
public async Task CreateCompete(uint baid, ChallengeCompeteInfo challengeCompeteInfo)
public async Task<ChallengeCompetitionResponse> GetChallengeCompetePage(CompeteModeType mode, uint baid, bool inProgress, int page, int limit, string? search)
{
IQueryable<ChallengeCompeteDatum>? query = null;
string? lowSearch = search != null ? search.ToLower() : null;
if (mode == CompeteModeType.Chanllenge)
{
query = context.ChallengeCompeteData
.Include(e => e.Songs).ThenInclude(e => e.BestScores).Include(e => e.Participants)
.Where(e => e.CompeteMode == CompeteModeType.Chanllenge)
.Where(e => inProgress == false || (e.CreateTime < DateTime.Now && DateTime.Now < e.ExpireTime))
.Where(e => baid == 0 || (e.Baid == baid || e.Participants.Any(p => p.Baid == baid)))
.Where(e => lowSearch == null || (e.CompId.ToString() == lowSearch || e.CompeteName.ToLower().Contains(lowSearch)));
}
else if (mode == CompeteModeType.Compete)
{
query = context.ChallengeCompeteData
.Include(e => e.Songs).ThenInclude(e => e.BestScores).Include(e => e.Participants)
.Where(e => e.CompeteMode == CompeteModeType.Compete)
.Where(e => inProgress == false || (e.CreateTime < DateTime.Now && DateTime.Now < e.ExpireTime))
.Where(e => baid == 0 || (e.Baid == baid || e.Participants.Any(p => p.Baid == baid) || e.Share == ShareType.EveryOne))
.Where(e => lowSearch == null || (e.CompId.ToString() == lowSearch || e.CompeteName.ToLower().Contains(lowSearch)));
}
else if (mode == CompeteModeType.OfficialCompete)
{
query = context.ChallengeCompeteData
.Include(e => e.Songs).ThenInclude(e => e.BestScores).Include(e => e.Participants)
.Where(e => e.CompeteMode == CompeteModeType.OfficialCompete)
.Where(e => inProgress == false || (e.CreateTime < DateTime.Now && DateTime.Now < e.ExpireTime))
.Where(e => lowSearch == null || (e.CompId.ToString() == lowSearch || e.CompeteName.ToLower().Contains(lowSearch)));
}
if (query == null) return new ChallengeCompetitionResponse();
var total = await query.CountAsync();
var totalPage = total / limit;
if (total % limit > 0) totalPage += 1;
var challengeCompeteDatum= await query
.OrderBy(e => e.CompId).Skip((page - 1) * limit).Take(limit)
.ToListAsync();
List<ChallengeCompetition> converted = new();
foreach (var data in challengeCompeteDatum)
{
var challengeCompetition = Mappers.ChallengeCompeMappers.MapData(data);
challengeCompetition = await FillData(challengeCompetition);
converted.Add(challengeCompetition);
}
return new ChallengeCompetitionResponse
{
List = converted,
Page = page,
TotalPages = totalPage,
Total = total
};
}
public async Task CreateCompete(uint baid, ChallengeCompeteCreateInfo challengeCompeteInfo)
{
ChallengeCompeteDatum challengeCompeteData = new()
{
@ -85,20 +152,23 @@ public class ChallengeCompeteService : IChallengeCompeteService
CompId = challengeCompeteData.CompId,
SongId = song.SongId,
Difficulty = song.Difficulty,
Speed = song.Speed,
IsInverseOn = song.IsInverseOn,
IsVanishOn = song.IsVanishOn,
RandomType = song.RandomType
Speed = song.Speed == -1 ? null : (uint)song.Speed,
IsInverseOn = song.IsInverseOn == -1 ? null : (song.IsInverseOn != 0),
IsVanishOn = song.IsVanishOn == -1 ? null : (song.IsVanishOn != 0),
RandomType = song.RandomType == -1 ? null : (RandomType)song.RandomType,
};
await context.AddAsync(challengeCompeteSongData);
}
ChallengeCompeteParticipantDatum participantDatum = new()
if (baid != 0)
{
CompId = challengeCompeteData.CompId,
Baid = baid,
IsActive = true
};
await context.AddAsync(participantDatum);
ChallengeCompeteParticipantDatum participantDatum = new()
{
CompId = challengeCompeteData.CompId,
Baid = baid,
IsActive = true
};
await context.AddAsync(participantDatum);
}
await context.SaveChangesAsync();
}
@ -125,7 +195,7 @@ public class ChallengeCompeteService : IChallengeCompeteService
return true;
}
public async Task CreateChallenge(uint baid, uint targetBaid, ChallengeCompeteInfo challengeCompeteInfo)
public async Task CreateChallenge(uint baid, uint targetBaid, ChallengeCompeteCreateInfo challengeCompeteInfo)
{
ChallengeCompeteDatum challengeCompeteData = new()
{
@ -151,10 +221,10 @@ public class ChallengeCompeteService : IChallengeCompeteService
CompId = challengeCompeteData.CompId,
SongId = song.SongId,
Difficulty = song.Difficulty,
Speed = song.Speed,
IsInverseOn = song.IsInverseOn,
IsVanishOn = song.IsVanishOn,
RandomType = song.RandomType
Speed = song.Speed == -1 ? null : (uint)song.Speed,
IsInverseOn = song.IsInverseOn == -1 ? null : (song.IsInverseOn != 0),
IsVanishOn = song.IsVanishOn == -1 ? null : (song.IsVanishOn != 0),
RandomType = song.RandomType == -1 ? null : (RandomType)song.RandomType,
};
await context.AddAsync(challengeCompeteSongData);
}
@ -268,4 +338,36 @@ public class ChallengeCompeteService : IChallengeCompeteService
}
await context.AddRangeAsync();
}
public async Task<ChallengeCompetition> FillData(ChallengeCompetition challenge)
{
foreach (var song in challenge.Songs)
{
if (song == null) continue;
song.MusicDetail = musicDetailDict.GetValueOrDefault(song.SongId);
foreach (var score in song.BestScores)
{
UserDatum? user = await userDatumService.GetFirstUserDatumOrNull(score.Baid);
if (user == null) continue;
score.UserAppearance = new UserAppearance
{
Baid = score.Baid,
MyDonName = user.MyDonName,
MyDonNameLanguage = user.MyDonNameLanguage,
Title = user.Title,
TitlePlateId = user.TitlePlateId,
Kigurumi = user.CurrentKigurumi,
Head = user.CurrentHead,
Body = user.CurrentBody,
Face = user.CurrentFace,
Puchi = user.CurrentPuchi,
FaceColor = user.ColorFace,
BodyColor = user.ColorBody,
LimbColor = user.ColorLimb,
};
}
}
return challenge;
}
}

View File

@ -1,20 +1,27 @@
namespace TaikoLocalServer.Services.Interfaces;
using SharedProject.Models;
using SharedProject.Models.Responses;
namespace TaikoLocalServer.Services.Interfaces;
public interface IChallengeCompeteService
{
public bool HasChallengeCompete(uint baid);
public Task<bool> HasChallengeCompete(uint baid);
public List<ChallengeCompeteDatum> GetInProgressChallengeCompete(uint baid);
public Task<List<ChallengeCompeteDatum>> GetInProgressChallengeCompete(uint baid);
public List<ChallengeCompeteDatum> GetAllChallengeCompete();
public Task<List<ChallengeCompeteDatum>> GetAllChallengeCompete();
public Task CreateCompete(uint baid, ChallengeCompeteInfo challengeCompeteInfo);
public Task<ChallengeCompetitionResponse> GetChallengeCompetePage(CompeteModeType mode, uint baid, bool inProgress, int page, int limit, string? search);
public Task CreateCompete(uint baid, ChallengeCompeteCreateInfo challengeCompeteInfo);
public Task<bool> ParticipateCompete(uint compId, uint baid);
public Task CreateChallenge(uint baid, uint targetBaid, ChallengeCompeteInfo challengeCompeteInfo);
public Task CreateChallenge(uint baid, uint targetBaid, ChallengeCompeteCreateInfo challengeCompeteInfo);
public Task<bool> AnswerChallenge(uint compId, uint baid, bool accept);
public Task UpdateBestScore(uint baid, SongPlayDatum playData, short option);
public Task<ChallengeCompetition> FillData(ChallengeCompetition challenge);
}

View File

@ -28,34 +28,36 @@
</ItemGroup>
<ItemGroup>
<Compile Remove="Templates\TemplateController.cs"/>
<Compile Remove="Models\ChallengeCompeteInfo.cs" />
<Compile Remove="Models\ChallengeCompeteSongInfo.cs" />
<Compile Remove="Templates\TemplateController.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DotNetZip" Version="1.16.0"/>
<PackageReference Include="MediatR" Version="12.2.0"/>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4"/>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.4"/>
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4"/>
<PackageReference Include="DotNetZip" Version="1.16.0" />
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Otp.NET" Version="1.4.0"/>
<PackageReference Include="protobuf-net" Version="3.2.30"/>
<PackageReference Include="protobuf-net.AspNetCore" Version="3.2.12"/>
<PackageReference Include="Riok.Mapperly" Version="3.5.1"/>
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2-dev-00334"/>
<PackageReference Include="Serilog.Expressions" Version="4.0.0"/>
<PackageReference Include="Serilog.Sinks.File.Header" Version="1.0.2"/>
<PackageReference Include="SharpZipLib" Version="1.4.2"/>
<PackageReference Include="Swan.Core" Version="7.0.0-beta.2"/>
<PackageReference Include="Swan.Logging" Version="6.0.2-beta.96"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0"/>
<PackageReference Include="Throw" Version="1.4.0"/>
<PackageReference Include="Yoh.Text.Json.NamingPolicies" Version="1.1.2"/>
<PackageReference Include="Otp.NET" Version="1.4.0" />
<PackageReference Include="protobuf-net" Version="3.2.30" />
<PackageReference Include="protobuf-net.AspNetCore" Version="3.2.12" />
<PackageReference Include="Riok.Mapperly" Version="3.5.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2-dev-00334" />
<PackageReference Include="Serilog.Expressions" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.File.Header" Version="1.0.2" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="Swan.Core" Version="7.0.0-beta.2" />
<PackageReference Include="Swan.Logging" Version="6.0.2-beta.96" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Throw" Version="1.4.0" />
<PackageReference Include="Yoh.Text.Json.NamingPolicies" Version="1.1.2" />
</ItemGroup>
<ItemGroup>
@ -65,26 +67,26 @@
<None Update="Certificates\root.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Remove="Configurations\ServerSettings.json"/>
<Content Remove="Configurations\ServerSettings.json" />
<None Include="Configurations\AuthSettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Configurations\ServerSettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Remove="Configurations\Database.json"/>
<Content Remove="Configurations\Database.json" />
<None Include="Configurations\Database.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Remove="Configurations\DataSettings.json"/>
<Content Remove="Configurations\DataSettings.json" />
<None Include="Configurations\DataSettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Remove="Configurations\Kestrel.json"/>
<Content Remove="Configurations\Kestrel.json" />
<None Include="Configurations\Kestrel.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Remove="Configurations\Logging.json"/>
<Content Remove="Configurations\Logging.json" />
<None Include="Configurations\Logging.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@ -199,8 +201,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GameDatabase\GameDatabase.csproj"/>
<ProjectReference Include="..\SharedProject\SharedProject.csproj"/>
<ProjectReference Include="..\TaikoWebUI\TaikoWebUI.csproj"/>
<ProjectReference Include="..\GameDatabase\GameDatabase.csproj" />
<ProjectReference Include="..\SharedProject\SharedProject.csproj" />
<ProjectReference Include="..\TaikoWebUI\TaikoWebUI.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,100 @@
@inject IDialogService DialogService;
@inject HttpClient Client
@if (ChallengeCompetition != null)
{
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6" Style="font-weight:bold;word-break:break-all">@ChallengeCompetition.CompeteName</MudText>
<MudText Typo="Typo.caption" Style="display: block">@Localizer["Comp ID"]:@ChallengeCompetition.CompId</MudText>
<MudText Typo="Typo.caption" Style="display: block">@Localizer["Describe"]:@ChallengeCompetition.CompeteDescribe</MudText>
</CardHeaderContent>
<CardHeaderActions>
@if (false && SelfHoldedChallengeCompetiton())
{
<MudIconButton Icon="@Icons.Material.Filled.Settings" Color="Color.Default" />
}
</CardHeaderActions>
</MudCardHeader>
<MudCardContent>
@{
foreach (var song in ChallengeCompetition.Songs)
{
<div>
<MudButton Variant="Variant.Text" StartIcon=@Icons.Material.Filled.Audiotrack>@song.MusicDetail?.SongName</MudButton>
</div>
}
}
</MudCardContent>
<MudCardActions>
<MudStack Row="true" Style="width:100%" Spacing="4" Justify="Justify.FlexEnd">
<MudButton Disabled=@CanParticipateChallengeCompetition()
OnClick=@(_ => AnswerChallenge(true))>@Localizer["Information"]</MudButton>
@if (ChallengeCompetition.CompeteMode == CompeteModeType.Chanllenge)
{
<MudButtonGroup Color="Color.Primary" Variant="Variant.Filled">
<MudButton Disabled=@ChallengeNeedAnswer()
OnClick=@(_ => AnswerChallenge(true))>@Localizer["Accept"]</MudButton>
<MudButton Disabled=@ChallengeNeedAnswer()
OnClick=@(_ => AnswerChallenge(false))>@Localizer["Reject"]</MudButton>
</MudButtonGroup>
}
else
{
<MudButton Disabled=@CanParticipateChallengeCompetition()
OnClick=@(_ => AnswerChallenge(true))>@Localizer["Participate"]</MudButton>
}
</MudStack>
</MudCardActions>
</MudCard>
}
@code {
[Parameter] public ChallengeCompetition? ChallengeCompetition { get; set; }
[Parameter] public uint Baid { get; set; }
private bool SelfHoldedChallengeCompetiton()
{
return ChallengeCompetition?.Baid == Baid || Baid == 0;
}
private bool ChallengeNeedAnswer()
{
return ChallengeCompetition?.State == CompeteState.Waiting && !SelfHoldedChallengeCompetiton();
}
private bool ParticipatedChallengeCompetition()
{
return ChallengeCompetition?.Participants?.Find(p => p.Baid == Baid) != null;
}
private bool CanParticipateChallengeCompetition()
{
return ChallengeCompetition?.CreateTime < DateTime.Now && DateTime.Now < ChallengeCompetition?.ExpireTime && !ParticipatedChallengeCompetition();
}
private async Task AnswerChallenge(bool accept)
{
if (ChallengeCompetition == null || ChallengeCompetition.State != CompeteState.Waiting) return;
var request = new AnswerChallengeRequest
{
CompId = ChallengeCompetition.CompId,
Baid = Baid,
Accept = accept
};
var response = await Client.PostAsJsonAsync("api/ChallengeCompetitionManage/AnswerChallenge", request);
if (!response.IsSuccessStatusCode)
{
await DialogService.ShowMessageBox(
Localizer["Error"],
Localizer["Unknown Error"],
Localizer["Dialog OK"], null, null, new DialogOptions { DisableBackdropClick = true });
return;
}
ChallengeCompetition.State = accept ? CompeteState.Normal : CompeteState.Rejected;
}
}

View File

@ -0,0 +1,194 @@
@inject HttpClient Client
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@inject BreadcrumbsStateContainer BreadcrumbsStateContainer
@inject IDialogService DialogService;
@using TaikoWebUI.Components;
@using TaikoWebUI.Pages.Dialogs
<MudGrid Class="my-8">
@if (!AuthService.LoginRequired || (AuthService.LoginRequired && AuthService.IsAdmin))
{
<MudItem xs="12">
<div class="d-flex justify-content-end gap-2">
<MudStack StretchItems="StretchItems.Start" Row="true" Style="width: 100%">
<MudInput @bind-Value="searchTerm"
TextChanged="(async term => { isLoading = true; await OnSearch(term); })"
Immediate="true"
FullWidth="true"
Clearable="true"
Margin="Margin.Dense"
Label="@Localizer["Search"]"
Placeholder="@Localizer["Search by Name, Comp ID"]"
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search" Style="width: 100%"/>
@if (Mode == 1) {
<MudButton Variant="Variant.Filled" Size="Size.Large" StartIcon="@Icons.Material.Filled.Add" Color="Color.Tertiary" Disabled="@(Baid==0)" OnClick=@(_ => OpenDialogAsync(1, 1)) Style="width: 80px">@Localizer["Add"]</MudButton>
}
else if (Mode == 2)
{
<MudButton Variant="Variant.Filled" Size="Size.Large" StartIcon="@Icons.Material.Filled.Add" Color="Color.Tertiary" Disabled="@(Baid==0)" OnClick=@(_ => OpenDialogAsync(2, 3)) Style="width: 80px">@Localizer["Add"]</MudButton>
}
else if (Mode == 3)
{
<MudButton Variant="Variant.Filled" Size="Size.Large" StartIcon="@Icons.Material.Filled.Add" Color="Color.Tertiary" Disabled="@(Baid!=0)" OnClick=@(_ => OpenDialogAsync(3, 3)) Style="width: 80px">@Localizer["Add"]</MudButton>
}
</MudStack>
</div>
</MudItem>
@if (isLoading || response == null)
{
// Loading...
for (uint i = 0; i < pageSize; i++)
{
<MudItem xs="12" md="6" lg="4" xl="3">
<MudCard Outlined="true">
<MudCardContent>
<MudSkeleton Width="30%" Height="42px;" Class="mb-5" />
<MudSkeleton Width="80%" />
<MudSkeleton Width="100%" />
</MudCardContent>
<MudCardActions>
<MudStack Row="true" Style="width:100%" Spacing="4" Justify="Justify.FlexEnd">
<MudSkeleton Width="64px" Height="40px" />
<MudSkeleton Width="64px" Height="40px" />
</MudStack>
</MudCardActions>
</MudCard>
</MudItem>
}
}
else if (response.List.Count > 0)
{
foreach (var challengeCompetition in response.List)
{
<MudItem xs="12" md="6" lg="4" xl="3">
<ChallengeCompe ChallengeCompetition="challengeCompetition" Baid="(uint)Baid" />
</MudItem>
}
}
else
{ // No users in the database
<MudItem xs="12">
<MudText Align="Align.Center" Class="my-8">
@Localizer["No Data"]
</MudText>
</MudItem>
}
}
else if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
// Not logged in, redirect
NavigationManager.NavigateTo("/Login");
}
else
{
NavigationManager.NavigateTo("/");
}
@if (response != null && TotalPages > 1)
{
<MudItem xs="12">
<div class="d-flex flex-column align-center">
<MudPagination Class="pa-4" Rectangular="true" DisableElevation="true" Count="@TotalPages" Selected="currentPage" SelectedChanged="(page) => OnPageChange(page)" BoundaryCount="1" MiddleCount="3" />
</div>
</MudItem>
}
</MudGrid>
@code {
[Parameter]
public int Baid { get; set; }
[Parameter]
public int Mode { get; set; }
private ChallengeCompetitionResponse? response = new();
private CancellationTokenSource? cts;
private int TotalPages { get; set; } = 0;
private bool isLoading = true;
private int currentPage = 1;
private readonly int pageSize = 12;
private string? searchTerm = null;
private bool inProgress = false;
private async Task GetUsersData()
{
isLoading = true;
response = await Client.GetFromJsonAsync<ChallengeCompetitionResponse>($"api/ChallengeCompeteManage/queryPage?mode={(uint)Mode}&baid={Baid}&inProgress={(inProgress ? 1 : 0)}&page={currentPage}&limit={pageSize}&searchTerm={searchTerm}");
response.ThrowIfNull();
TotalPages = response.TotalPages;
isLoading = false;
}
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await AuthService.LoginWithAuthToken();
}
if (AuthService.IsAdmin || !AuthService.LoginRequired)
{
await GetUsersData();
}
BreadcrumbsStateContainer.breadcrumbs.Clear();
BreadcrumbsStateContainer.breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
BreadcrumbsStateContainer.NotifyStateChanged();
}
private async Task OnPageChange(int page)
{
currentPage = page;
await GetUsersData();
}
private async Task Debounce(Func<Task> action, int delayInMilliseconds)
{
// Cancel the previous task
cts?.Cancel();
// Create a new CancellationTokenSource
cts = new CancellationTokenSource();
try
{
// Wait for the delay
await Task.Delay(delayInMilliseconds, cts.Token);
// Execute the action
await action();
}
catch (TaskCanceledException)
{
// Ignore the exception
}
}
private async Task OnSearch(string search)
{
searchTerm = search;
currentPage = 1;
// Debounce the GetUsersData method
await Debounce(GetUsersData, 500); // 500 milliseconds delay
}
private Task OpenDialogAsync(int mode, int maxSongs)
{
var options = new DialogOptions { CloseOnEscapeKey = true };
var parameters = new DialogParameters();
parameters.Add("Mode", mode);
parameters.Add("MaxSongs", maxSongs);
return DialogService.ShowAsync<AddChallengeCompetitionDialog>(Localizer["Create"], parameters, options);
}
}

View File

@ -43,6 +43,14 @@
<MudNavLink Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Logout" IconColor="Color.Error" OnClick="Logout">@Localizer["Log Out"]</MudNavLink>
}
}
@{
<MudNavGroup Title=@Localizer["Challenge Competition Data"] Expanded="true" Icon="@Icons.Material.Filled.EmojiEvents">
<MudNavLink Href="@($"ChallengeCompe/{baid}/Challenge")" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.People">@Localizer["Challenge"]</MudNavLink>
<MudNavLink Href="@($"ChallengeCompe/{baid}/Competition")" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.WorkspacePremium">@Localizer["Competition"]</MudNavLink>
<MudNavLink Href="@($"ChallengeCompe/{baid}/OfficialCompetition")" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.EmojiEvents">@Localizer["Official Competition"]</MudNavLink>
</MudNavGroup>
}
</MudNavMenu>
@code {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -117,264 +117,6 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Dashboard" xml:space="preserve">
<value>Tableau de bord</value>
</data>
<data name="Users" xml:space="preserve">
<value>Utilisateurs</value>
</data>
<data name="Edit Profile" xml:space="preserve">
<value>Profil</value>
</data>
<data name="User" xml:space="preserve">
<value>Utilisateur</value>
</data>
<data name="View Play Data" xml:space="preserve">
<value>Données de jeu</value>
</data>
<data name="High Scores" xml:space="preserve">
<value>Meilleurs Scores</value>
</data>
<data name="Show QR Code" xml:space="preserve">
<value>Afficher le Code QR</value>
</data>
<data name="Access Codes" xml:space="preserve">
<value>Codes d'accès</value>
</data>
<data name="Change Password" xml:space="preserve">
<value>Changer le mot de passe</value>
</data>
<data name="Reset Password" xml:space="preserve">
<value>Réinitialiser le mot de passe</value>
</data>
<data name="Delete User" xml:space="preserve">
<value>Supprimer l'utilisateur</value>
</data>
<data name="Welcome to TaikoWebUI!" xml:space="preserve">
<value>Bienvenue sur TaikoWebUI !</value>
</data>
<data name="Song Name" xml:space="preserve">
<value>Musique</value>
</data>
<data name="Level" xml:space="preserve">
<value>Niveau</value>
</data>
<data name="Genre" xml:space="preserve">
<value>Genre</value>
</data>
<data name="Best Score" xml:space="preserve">
<value>Meilleur score</value>
</data>
<data name="Best Crown" xml:space="preserve">
<value>Meilleure couronne</value>
</data>
<data name="Best Rank" xml:space="preserve">
<value>Meilleur rank</value>
</data>
<data name="Good" xml:space="preserve">
<value>Good</value>
</data>
<data name="OK" xml:space="preserve">
<value>OK</value>
</data>
<data name="Bad" xml:space="preserve">
<value>Bad</value>
</data>
<data name="Drumroll" xml:space="preserve">
<value>Drumroll</value>
</data>
<data name="MAX Combo" xml:space="preserve">
<value>Combo MAX</value>
</data>
<data name="AI Battle Data" xml:space="preserve">
<value>Données du mode IA Battle</value>
</data>
<data name="Last Played" xml:space="preserve">
<value>Joué la dernière fois</value>
</data>
<data name="Total Credits Played" xml:space="preserve">
<value>Nombre total de parties</value>
</data>
<data name="Total Clears" xml:space="preserve">
<value>Total Clears</value>
</data>
<data name="Total Full Combos" xml:space="preserve">
<value>Nombre total de Full Combos</value>
</data>
<data name="Total Donderful Combos" xml:space="preserve">
<value>Nombre total de Combos Donderful</value>
</data>
<data name="Song List" xml:space="preserve">
<value>Musiques</value>
</data>
<data name="Hide" xml:space="preserve">
<value>Cacher</value>
</data>
<data name="Show" xml:space="preserve">
<value>Afficher</value>
</data>
<data name="Section No." xml:space="preserve">
<value>Numéro de section</value>
</data>
<data name="Result" xml:space="preserve">
<value>Résultats</value>
</data>
<data name="Score" xml:space="preserve">
<value>Score</value>
</data>
<data name="Crown" xml:space="preserve">
<value>Couronnes</value>
</data>
<data name="No Data" xml:space="preserve">
<value>Pas de données</value>
</data>
<data name="Log In First" xml:space="preserve">
<value>Veuillez vous connecter en cliquant d'abord sur l'onglet “Utilisateurs”.</value>
</data>
<data name="Total Hits" xml:space="preserve">
<value>Total Hits</value>
</data>
<data name="Soul Gauge" xml:space="preserve">
<value>Jauge d'âme</value>
</data>
<data name="Course Songs" xml:space="preserve">
<value>Musiques</value>
</data>
<data name="Conditions" xml:space="preserve">
<value>Conditions</value>
</data>
<data name="Red" xml:space="preserve">
<value>Red Clear</value>
</data>
<data name="Gold" xml:space="preserve">
<value>Gold</value>
</data>
<data name="Not Cleared" xml:space="preserve">
<value>Pas Clear</value>
</data>
<data name="Pass" xml:space="preserve">
<value>Pass</value>
</data>
<data name="Totals" xml:space="preserve">
<value>Totaux</value>
</data>
<data name="Details" xml:space="preserve">
<value>Details</value>
</data>
<data name="Stage" xml:space="preserve">
<value>Stage</value>
</data>
<data name="Dani Dojo" xml:space="preserve">
<value>Dani Dojo</value>
</data>
<data name="Profile" xml:space="preserve">
<value>Profil</value>
</data>
<data name="Profile Options" xml:space="preserve">
<value>Options de profil</value>
</data>
<data name="Costume" xml:space="preserve">
<value>Costume</value>
</data>
<data name="Costume Options" xml:space="preserve">
<value>Options de costume</value>
</data>
<data name="Song Options" xml:space="preserve">
<value>Options de jeu</value>
</data>
<data name="Player" xml:space="preserve">
<value>Joueur</value>
</data>
<data name="Achievement Panel" xml:space="preserve">
<value>Tableau de reussite</value>
</data>
<data name="Save" xml:space="preserve">
<value>Sauvegarder</value>
</data>
<data name="Language" xml:space="preserve">
<value>Langue</value>
</data>
<data name="Name" xml:space="preserve">
<value>Nom</value>
</data>
<data name="Title" xml:space="preserve">
<value>Titre</value>
</data>
<data name="Title Plate" xml:space="preserve">
<value>Décor de Plaque</value>
</data>
<data name="Achievement Panel Difficulty" xml:space="preserve">
<value>Difficulté du tableau de réussite</value>
</data>
<data name="Display Dan Rank on Name Plate" xml:space="preserve">
<value>Afficher le Dan sur la plaque</value>
</data>
<data name="Display Achievement Panel" xml:space="preserve">
<value>Afficher le Tableau de Réussite</value>
</data>
<data name="Difficulty Setting Course" xml:space="preserve">
<value>Mode recherche : Difficulté</value>
</data>
<data name="Difficulty Setting Star" xml:space="preserve">
<value>Mode recherche : Etoiles</value>
</data>
<data name="Difficulty Setting Sort" xml:space="preserve">
<value>Mode recherche : Tri</value>
</data>
<data name="Select a Title" xml:space="preserve">
<value>Sélectionner un titre</value>
</data>
<data name="Head" xml:space="preserve">
<value>Tête</value>
</data>
<data name="Body" xml:space="preserve">
<value>Corps</value>
</data>
<data name="Face" xml:space="preserve">
<value>Visage</value>
</data>
<data name="Kigurumi" xml:space="preserve">
<value>Kigurumi</value>
</data>
<data name="Puchi" xml:space="preserve">
<value>Puchi</value>
</data>
<data name="Body Color" xml:space="preserve">
<value>Couleur du Corps</value>
</data>
<data name="Face Color" xml:space="preserve">
<value>Couleur du Visage</value>
</data>
<data name="Limb Color" xml:space="preserve">
<value>Couleur des Membres</value>
</data>
<data name="Vanish" xml:space="preserve">
<value>Disparition</value>
</data>
<data name="Inverse" xml:space="preserve">
<value>Inverser</value>
</data>
<data name="Voice" xml:space="preserve">
<value>Voix</value>
</data>
<data name="Speed" xml:space="preserve">
<value>Vitesse</value>
</data>
<data name="Random" xml:space="preserve">
<value>Aléatoire</value>
</data>
<data name="Tone" xml:space="preserve">
<value>Son de tambour</value>
</data>
<data name="Notes Position" xml:space="preserve">
<value>Position des notes</value>
</data>
<data name="Reset Password Confirm 1" xml:space="preserve">
<value>Êtes-vous sûr de vouloir réinitialiser le mot de passe de cet utilisateur ?</value>
</data>
<data name="Reset Password Confirm 2" xml:space="preserve">
<value>Le mot de passe actuel de l'utilisateur sera alors supprimé et l'utilisateur devra s'inscrire à nouveau.</value>
</data>
<data name="DateFormat" xml:space="preserve">
<value>dd/MM/yyyy h:mm:ss tt</value>
</data>
@ -387,12 +129,36 @@
<data name="Log In" xml:space="preserve">
<value>Connexion</value>
</data>
<data name="Achievement Panel" xml:space="preserve">
<value>Tableau de reussite</value>
</data>
<data name="Achievement Panel Difficulty" xml:space="preserve">
<value>Difficulté du tableau de réussite</value>
</data>
<data name="AI Battle Data" xml:space="preserve">
<value>Données du mode IA Battle</value>
</data>
<data name="Bad" xml:space="preserve">
<value>Bad</value>
</data>
<data name="Best Crown" xml:space="preserve">
<value>Meilleure couronne</value>
</data>
<data name="Best Rank" xml:space="preserve">
<value>Meilleur rank</value>
</data>
<data name="Log Out" xml:space="preserve">
<value>Déconnexion</value>
</data>
<data name="Play Data" xml:space="preserve">
<value>Données de jeu</value>
</data>
<data name="High Scores" xml:space="preserve">
<value>Meilleurs Scores</value>
</data>
<data name="Access Codes" xml:space="preserve">
<value>Codes d'accès</value>
</data>
<data name="Add Access Code" xml:space="preserve">
<value>Ajouter un code d'accès</value>
</data>
@ -420,32 +186,50 @@
<data name="QR Code" xml:space="preserve">
<value>Code QR</value>
</data>
<data name="Chojin" xml:space="preserve">
<value>Chojin</value>
</data>
<data name="Eighth Dan" xml:space="preserve">
<value>8ème Dan</value>
</data>
<data name="Fifth Dan" xml:space="preserve">
<value>5ème Dan</value>
</data>
<data name="Fifth Kyuu" xml:space="preserve">
<value>5ème Kyuu</value>
</data>
<data name="First Dan" xml:space="preserve">
<value>1er Dan</value>
</data>
<data name="First Kyuu" xml:space="preserve">
<value>1er Kyuu</value>
</data>
<data name="Fourth Dan" xml:space="preserve">
<value>4ème Dan</value>
</data>
<data name="Fourth Kyuu" xml:space="preserve">
<value>4ème Kyuu</value>
</data>
<data name="Gaiden" xml:space="preserve">
<value>Gaiden</value>
<data name="Third Kyuu" xml:space="preserve">
<value>3ème Kyuu</value>
</data>
<data name="Second Kyuu" xml:space="preserve">
<value>2ème Kyuu</value>
</data>
<data name="First Kyuu" xml:space="preserve">
<value>1er Kyuu</value>
</data>
<data name="First Dan" xml:space="preserve">
<value>1er Dan</value>
</data>
<data name="Second Dan" xml:space="preserve">
<value>2ème Dan</value>
</data>
<data name="Third Dan" xml:space="preserve">
<value>3ème Dan</value>
</data>
<data name="Fourth Dan" xml:space="preserve">
<value>4ème Dan</value>
</data>
<data name="Fifth Dan" xml:space="preserve">
<value>5ème Dan</value>
</data>
<data name="Sixth Dan" xml:space="preserve">
<value>6ème Dan</value>
</data>
<data name="Seventh Dan" xml:space="preserve">
<value>7ème Dan</value>
</data>
<data name="Eighth Dan" xml:space="preserve">
<value>8ème Dan</value>
</data>
<data name="Ninth Dan" xml:space="preserve">
<value>9ème Dan</value>
</data>
<data name="Tenth Dan" xml:space="preserve">
<value>10ème Dan</value>
</data>
<data name="Kuroto" xml:space="preserve">
<value>Kuroto</value>
@ -453,45 +237,42 @@
<data name="Meijin" xml:space="preserve">
<value>Meijin</value>
</data>
<data name="Ninth Dan" xml:space="preserve">
<value>9ème Dan</value>
</data>
<data name="Second Dan" xml:space="preserve">
<value>2ème Dan</value>
</data>
<data name="Second Kyuu" xml:space="preserve">
<value>2ème Kyuu</value>
</data>
<data name="Seventh Dan" xml:space="preserve">
<value>7ème Dan</value>
</data>
<data name="Sixth Dan" xml:space="preserve">
<value>6ème Dan</value>
<data name="Chojin" xml:space="preserve">
<value>Chojin</value>
</data>
<data name="Tatsujin" xml:space="preserve">
<value>Tatsujin</value>
</data>
<data name="Tenth Dan" xml:space="preserve">
<value>10ème Dan</value>
</data>
<data name="Third Dan" xml:space="preserve">
<value>3ème Dan</value>
</data>
<data name="Third Kyuu" xml:space="preserve">
<value>3ème Kyuu</value>
<data name="Gaiden" xml:space="preserve">
<value>Gaiden</value>
</data>
<data name="Gold Full Combo" xml:space="preserve">
<value>Gold Full Combo</value>
</data>
<data name="Red Donderful Combo" xml:space="preserve">
<value>Red Donderful Combo</value>
</data>
<data name="Red Full Combo" xml:space="preserve">
<value>Red Full Combo</value>
</data>
<data name="Red Donderful Combo" xml:space="preserve">
<value>Red Donderful Combo</value>
</data>
<data name="Gold Donderful Combo" xml:space="preserve">
<value>Gold Donderful Combo</value>
</data>
<data name="Song List" xml:space="preserve">
<value>Musiques</value>
</data>
<data name="Log In First" xml:space="preserve">
<value>Veuillez vous connecter en cliquant d'abord sur l'onglet “Utilisateurs”.</value>
</data>
<data name="Dani Dojo" xml:space="preserve">
<value>Dani Dojo</value>
</data>
<data name="Course Songs" xml:space="preserve">
<value>Musiques</value>
</data>
<data name="Song Name" xml:space="preserve">
<value>Musique</value>
</data>
<data name="Song Title / Artist" xml:space="preserve">
<value>Titre / Artiste de la musique</value>
</data>
@ -528,6 +309,12 @@
<data name="Search by Title, Artist or Date" xml:space="preserve">
<value>Recherche par titre, artiste ou date</value>
</data>
<data name="Users" xml:space="preserve">
<value>Utilisateurs</value>
</data>
<data name="Dashboard" xml:space="preserve">
<value>Tableau de bord</value>
</data>
<data name="Unregister" xml:space="preserve">
<value>Désinscription</value>
</data>
@ -546,6 +333,15 @@
<data name="Error" xml:space="preserve">
<value>Erreur</value>
</data>
<data name="View Play Data" xml:space="preserve">
<value>Données de jeu</value>
</data>
<data name="Not Passed" xml:space="preserve">
<value>Non validé</value>
</data>
<data name="User" xml:space="preserve">
<value>Utilisateur</value>
</data>
<data name="Access Code is Required" xml:space="preserve">
<value>Le code d'accès est requis</value>
</data>
@ -591,6 +387,9 @@
<data name="Rows Per Page:" xml:space="preserve">
<value>Rangées par page :</value>
</data>
<data name="Total Credits Played" xml:space="preserve">
<value>Nombre total de parties</value>
</data>
<data name="UI" xml:space="preserve">
<value>UI</value>
</data>
@ -663,6 +462,9 @@
<data name="ID" xml:space="preserve">
<value>ID</value>
</data>
<data name="Edit Profile" xml:space="preserve">
<value>Profil</value>
</data>
<data name="Access Code Delete Confirm" xml:space="preserve">
<value>Êtes-vous sûr de vouloir supprimer ce code d'accès ?</value>
</data>
@ -672,12 +474,123 @@
<data name="Reset" xml:space="preserve">
<value>Réinitialiser</value>
</data>
<data name="Reset Password Confirm 1" xml:space="preserve">
<value>Êtes-vous sûr de vouloir réinitialiser le mot de passe de cet utilisateur ?</value>
</data>
<data name="Reset Password Confirm 2" xml:space="preserve">
<value>Le mot de passe actuel de l'utilisateur sera alors supprimé et l'utilisateur devra s'inscrire à nouveau.</value>
</data>
<data name="Delete User Confirm" xml:space="preserve">
<value>Voulez-vous vraiment supprimer les données de cet utilisateur ? &lt;br /&gt;Toutes les données associées seront supprimées et ne pourront pas être récupérées !</value>
</data>
<data name="Delete User Success" xml:space="preserve">
<value>Utilisateur supprimé.</value>
</data>
<data name="Skip Song" xml:space="preserve">
<value>Passer la chanson</value>
</data>
<data name="None" xml:space="preserve">
<value>Désactivé</value>
</data>
<data name="Whimsical" xml:space="preserve">
<value>Fantaisiste</value>
</data>
<data name="Messy" xml:space="preserve">
<value>Désordonné</value>
</data>
<data name="Taiko" xml:space="preserve">
<value>Taiko</value>
</data>
<data name="Matsuri" xml:space="preserve">
<value>Festival</value>
</data>
<data name="Inuneko" xml:space="preserve">
<value>Chiens &amp; Chats</value>
</data>
<data name="Wonderfultaiko" xml:space="preserve">
<value>Taiko Deluxe</value>
</data>
<data name="Drum" xml:space="preserve">
<value>Tambour</value>
</data>
<data name="Tambourine" xml:space="preserve">
<value>Tambourin</value>
</data>
<data name="Wadadon" xml:space="preserve">
<value>Wadadon</value>
</data>
<data name="Clapping" xml:space="preserve">
<value>Applaudissements</value>
</data>
<data name="Conga" xml:space="preserve">
<value>Conga</value>
</data>
<data name="8bittaiko" xml:space="preserve">
<value>Taiko 8 bits</value>
</data>
<data name="Soya" xml:space="preserve">
<value>Oh hisse</value>
</data>
<data name="Mekadon" xml:space="preserve">
<value>Don Mecha</value>
</data>
<data name="Funassyi" xml:space="preserve">
<value>Funassyi</value>
</data>
<data name="Wrap" xml:space="preserve">
<value>Rap</value>
</data>
<data name="Isogai" xml:space="preserve">
<value>Hosogai</value>
</data>
<data name="Akemi" xml:space="preserve">
<value>Akemi</value>
</data>
<data name="Synthdrum" xml:space="preserve">
<value>Tambour Synthétique</value>
</data>
<data name="Shuriken" xml:space="preserve">
<value>Shuriken</value>
</data>
<data name="Puchipuchi" xml:space="preserve">
<value>Bubble Pop</value>
</data>
<data name="Electric Guitar" xml:space="preserve">
<value>Guitare électrique</value>
</data>
<data name="UraOni" xml:space="preserve">
<value>Ura</value>
</data>
<data name="Set Up Each Time" xml:space="preserve">
<value>Configurer à chaque fois</value>
</data>
<data name="Default" xml:space="preserve">
<value>Défaut</value>
</data>
<data name="Not Cleared" xml:space="preserve">
<value>Pas Clear</value>
</data>
<data name="Not Full Combo" xml:space="preserve">
<value>Pas Full Combo</value>
</data>
<data name="Not Donderful Combo" xml:space="preserve">
<value>Pas Donderful Combo</value>
</data>
<data name="Japanese" xml:space="preserve">
<value>Japonais</value>
</data>
<data name="English" xml:space="preserve">
<value>Anglais</value>
</data>
<data name="Chinese Traditional" xml:space="preserve">
<value>Chinois traditionnel</value>
</data>
<data name="Korean" xml:space="preserve">
<value>Coréen</value>
</data>
<data name="Chinese Simplified" xml:space="preserve">
<value>Chinois simplifié</value>
</data>
<data name="1 Star" xml:space="preserve">
<value>★ 1</value>
</data>
@ -708,127 +621,247 @@
<data name="10 Star" xml:space="preserve">
<value>★ 10</value>
</data>
<data name="Not Passed" xml:space="preserve">
<value>Non validé</value>
</data>
<data name="User ID" xml:space="preserve">
<value>ID de l'utilisateur</value>
<data name="Log Out Confirm" xml:space="preserve">
<value>Êtes-vous sûr de vouloir vous déconnecter ?</value>
</data>
<data name="Leaderboard" xml:space="preserve">
<value>Classement</value>
</data>
<data name="Log Out Confirm" xml:space="preserve">
<value>Êtes-vous sûr de vouloir vous déconnecter ?</value>
<data name="No Data" xml:space="preserve">
<value>Pas de données</value>
</data>
<data name="8bittaiko" xml:space="preserve">
<value>Taiko 8 bits</value>
</data>
<data name="Akemi" xml:space="preserve">
<value>Akemi</value>
</data>
<data name="Chinese Simplified" xml:space="preserve">
<value>Chinois simplifié</value>
</data>
<data name="Chinese Traditional" xml:space="preserve">
<value>Chinois traditionnel</value>
</data>
<data name="Clapping" xml:space="preserve">
<value>Applaudissements</value>
</data>
<data name="Conga" xml:space="preserve">
<value>Conga</value>
</data>
<data name="Default" xml:space="preserve">
<value>Défaut</value>
</data>
<data name="Drum" xml:space="preserve">
<value>Tambour</value>
</data>
<data name="None" xml:space="preserve">
<value>Désactivé</value>
</data>
<data name="Whimsical" xml:space="preserve">
<value>Fantaisiste</value>
</data>
<data name="Messy" xml:space="preserve">
<value>Désordonné</value>
</data>
<data name="Taiko" xml:space="preserve">
<value>Taiko</value>
</data>
<data name="Matsuri" xml:space="preserve">
<value>Festival</value>
</data>
<data name="Inuneko" xml:space="preserve">
<value>Chiens &amp; Chats</value>
</data>
<data name="Wonderfultaiko" xml:space="preserve">
<value>Taiko Deluxe</value>
</data>
<data name="Tambourine" xml:space="preserve">
<value>Tambourin</value>
</data>
<data name="Wadadon" xml:space="preserve">
<value>Wadadon</value>
</data>
<data name="Soya" xml:space="preserve">
<value>Oh hisse</value>
</data>
<data name="Mekadon" xml:space="preserve">
<value>Don Mecha</value>
</data>
<data name="Funassyi" xml:space="preserve">
<value>Funassyi</value>
</data>
<data name="Wrap" xml:space="preserve">
<value>Rap</value>
</data>
<data name="Isogai" xml:space="preserve">
<value>Hosogai</value>
</data>
<data name="Synthdrum" xml:space="preserve">
<value>Tambour Synthétique</value>
</data>
<data name="Shuriken" xml:space="preserve">
<value>Shuriken</value>
</data>
<data name="Puchipuchi" xml:space="preserve">
<value>Bubble Pop</value>
</data>
<data name="Electric Guitar" xml:space="preserve">
<value>Guitare électrique</value>
</data>
<data name="UraOni" xml:space="preserve">
<value>Ura</value>
</data>
<data name="Set Up Each Time" xml:space="preserve">
<value>Configurer à chaque fois</value>
</data>
<data name="Not Full Combo" xml:space="preserve">
<value>Pas Full Combo</value>
</data>
<data name="Not Donderful Combo" xml:space="preserve">
<value>Pas Donderful Combo</value>
</data>
<data name="Japanese" xml:space="preserve">
<value>Japonais</value>
</data>
<data name="English" xml:space="preserve">
<value>Anglais</value>
</data>
<data name="Korean" xml:space="preserve">
<value>Coréen</value>
</data>
<data name="Skip Song" xml:space="preserve">
<value>Passer la chanson</value>
</data>
<data name="Search" xml:space="preserve">
<value>Rechercher</value>
</data>
<data name="Player Titles" xml:space="preserve">
<value>Titres du joueur</value>
<data name="User ID" xml:space="preserve">
<value>ID de l'utilisateur</value>
</data>
<data name="Search by Name, ID, or Access Code" xml:space="preserve">
<value>Recherche par nom, identifiant ou code d'accès</value>
</data>
<data name="Player Titles" xml:space="preserve">
<value>Titres du joueur</value>
</data>
<data name="Search" xml:space="preserve">
<value>Rechercher</value>
</data>
<data name="Show QR Code" xml:space="preserve">
<value>Afficher le Code QR</value>
</data>
<data name="Change Password" xml:space="preserve">
<value>Changer le mot de passe</value>
</data>
<data name="Reset Password" xml:space="preserve">
<value>Réinitialiser le mot de passe</value>
</data>
<data name="Delete User" xml:space="preserve">
<value>Supprimer l'utilisateur</value>
</data>
<data name="Welcome to TaikoWebUI!" xml:space="preserve">
<value>Bienvenue sur TaikoWebUI !</value>
</data>
<data name="Level" xml:space="preserve">
<value>Niveau</value>
</data>
<data name="Genre" xml:space="preserve">
<value>Genre</value>
</data>
<data name="Best Score" xml:space="preserve">
<value>Meilleur score</value>
</data>
<data name="Good" xml:space="preserve">
<value>Good</value>
</data>
<data name="OK" xml:space="preserve">
<value>OK</value>
</data>
<data name="Drumroll" xml:space="preserve">
<value>Drumroll</value>
</data>
<data name="MAX Combo" xml:space="preserve">
<value>Combo MAX</value>
</data>
<data name="Last Played" xml:space="preserve">
<value>Joué la dernière fois</value>
</data>
<data name="Total Clears" xml:space="preserve">
<value>Total Clears</value>
</data>
<data name="Total Full Combos" xml:space="preserve">
<value>Nombre total de Full Combos</value>
</data>
<data name="Total Donderful Combos" xml:space="preserve">
<value>Nombre total de Combos Donderful</value>
</data>
<data name="Hide" xml:space="preserve">
<value>Cacher</value>
</data>
<data name="Show" xml:space="preserve">
<value>Afficher</value>
</data>
<data name="Section No." xml:space="preserve">
<value>Numéro de section</value>
</data>
<data name="Result" xml:space="preserve">
<value>Résultats</value>
</data>
<data name="Score" xml:space="preserve">
<value>Score</value>
</data>
<data name="Crown" xml:space="preserve">
<value>Couronnes</value>
</data>
<data name="Total Hits" xml:space="preserve">
<value>Total Hits</value>
</data>
<data name="Soul Gauge" xml:space="preserve">
<value>Jauge d'âme</value>
</data>
<data name="Conditions" xml:space="preserve">
<value>Conditions</value>
</data>
<data name="Red" xml:space="preserve">
<value>Red Clear</value>
</data>
<data name="Gold" xml:space="preserve">
<value>Gold</value>
</data>
<data name="Pass" xml:space="preserve">
<value>Pass</value>
</data>
<data name="Totals" xml:space="preserve">
<value>Totaux</value>
</data>
<data name="Details" xml:space="preserve">
<value>Details</value>
</data>
<data name="Stage" xml:space="preserve">
<value>Stage</value>
</data>
<data name="Profile" xml:space="preserve">
<value>Profil</value>
</data>
<data name="Profile Options" xml:space="preserve">
<value>Options de profil</value>
</data>
<data name="Costume" xml:space="preserve">
<value>Costume</value>
</data>
<data name="Costume Options" xml:space="preserve">
<value>Options de costume</value>
</data>
<data name="Song Options" xml:space="preserve">
<value>Options de jeu</value>
</data>
<data name="Player" xml:space="preserve">
<value>Joueur</value>
</data>
<data name="Save" xml:space="preserve">
<value>Sauvegarder</value>
</data>
<data name="Language" xml:space="preserve">
<value>Langue</value>
</data>
<data name="Name" xml:space="preserve">
<value>Nom</value>
</data>
<data name="Title" xml:space="preserve">
<value>Titre</value>
</data>
<data name="Title Plate" xml:space="preserve">
<value>Décor de Plaque</value>
</data>
<data name="Display Dan Rank on Name Plate" xml:space="preserve">
<value>Afficher le Dan sur la plaque</value>
</data>
<data name="Display Achievement Panel" xml:space="preserve">
<value>Afficher le Tableau de Réussite</value>
</data>
<data name="Difficulty Setting Course" xml:space="preserve">
<value>Mode recherche : Difficulté</value>
</data>
<data name="Difficulty Setting Star" xml:space="preserve">
<value>Mode recherche : Etoiles</value>
</data>
<data name="Difficulty Setting Sort" xml:space="preserve">
<value>Mode recherche : Tri</value>
</data>
<data name="Select a Title" xml:space="preserve">
<value>Sélectionner un titre</value>
</data>
<data name="Head" xml:space="preserve">
<value>Tête</value>
</data>
<data name="Body" xml:space="preserve">
<value>Corps</value>
</data>
<data name="Face" xml:space="preserve">
<value>Visage</value>
</data>
<data name="Kigurumi" xml:space="preserve">
<value>Kigurumi</value>
</data>
<data name="Puchi" xml:space="preserve">
<value>Puchi</value>
</data>
<data name="Body Color" xml:space="preserve">
<value>Couleur du Corps</value>
</data>
<data name="Face Color" xml:space="preserve">
<value>Couleur du Visage</value>
</data>
<data name="Limb Color" xml:space="preserve">
<value>Couleur des Membres</value>
</data>
<data name="Vanish" xml:space="preserve">
<value>Disparition</value>
</data>
<data name="Inverse" xml:space="preserve">
<value>Inverser</value>
</data>
<data name="Voice" xml:space="preserve">
<value>Voix</value>
</data>
<data name="Speed" xml:space="preserve">
<value>Vitesse</value>
</data>
<data name="Random" xml:space="preserve">
<value>Aléatoire</value>
</data>
<data name="Tone" xml:space="preserve">
<value>Son de tambour</value>
</data>
<data name="Notes Position" xml:space="preserve">
<value>Position des notes</value>
</data>
<data name="Challenge" xml:space="preserve">
<value>Défi</value>
</data>
<data name="Competition" xml:space="preserve">
<value>Compétition</value>
</data>
<data name="Official Competition" xml:space="preserve">
<value>Compétition Officielle</value>
</data>
<data name="Comp ID" xml:space="preserve">
<value>ID de compé</value>
</data>
<data name="Search by Name, Comp ID" xml:space="preserve">
<value>Recherche par nom, ID de compé</value>
</data>
<data name="Describe" xml:space="preserve">
<value>Commentaire</value>
</data>
<data name="Participate" xml:space="preserve">
<value>Rejoindre</value>
</data>
<data name="Accept" xml:space="preserve">
<value>Accepter</value>
</data>
<data name="Reject" xml:space="preserve">
<value>Rejeter</value>
</data>
<data name="Information" xml:space="preserve">
<value>Information</value>
</data>
<data name="Challenge Competition Data" xml:space="preserve">
<value>Données de compétition de défi</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
@using TaikoWebUI.Components;
@page "/ChallengeCompe/{baid:int}/Challenge"
<ChallengeCompeteGrid Baid="Baid" Mode="1"></ChallengeCompeteGrid>
@code {
[Parameter]
public int Baid { get; set; }
}

View File

@ -0,0 +1,10 @@
@using TaikoWebUI.Components;
@page "/ChallengeCompe/{baid:int}/Competition"
<ChallengeCompeteGrid Baid="Baid" Mode="2"></ChallengeCompeteGrid>
@code {
[Parameter]
public int Baid { get; set; }
}

View File

@ -0,0 +1,255 @@
@using System.Net
@using TaikoWebUI.Utilities;
@inject HttpClient Client
@inject ISnackbar Snackbar
@inject IDialogService DialogService;
<MudDialog Style="width: 700px">
<TitleContent>
@if (Mode == 1)
{
<MudText Typo="Typo.h6">
<MudIcon Icon="@Icons.Material.Filled.Flag" Class="mr-3 mb-n1" />
@Localizer["Create Challenge"]
</MudText>
}
else if (Mode == 2)
{
<MudText Typo="Typo.h6">
<MudIcon Icon="@Icons.Material.Filled.Flag" Class="mr-3 mb-n1" />
@Localizer["Create Competition"]
</MudText>
}
else if (Mode == 3)
{
<MudText Typo="Typo.h6">
<MudIcon Icon="@Icons.Material.Filled.Flag" Class="mr-3 mb-n1" />
@Localizer["Create Official Competition"]
</MudText>
}
</TitleContent>
<DialogContent>
<MudStack>
@if (Mode == 2 || Mode == 3)
{
<MudTextField Value="@Name" Label="@Localizer["Name"]"/>
}
<MudTextField Value="@Desc" Label="@Localizer["Describe"]" />
@for (int i = 0; i < Info.challengeCompeteSongs.Count; i ++)
{
var song_idx = i;
var song_label = Localizer["Song"] + $" {song_idx + 1}";
var song = Info.challengeCompeteSongs[song_idx];
<MudStack Spacing="2">
<MudStack StretchItems="StretchItems.Start" Row="true">
<MudField Value="@song.SongId" Label="@song_label" Variant="Variant.Outlined" InnerPadding="false">
<MudIconButton Icon="@Icons.Material.Filled.Audiotrack" OnClick="@(_ => SelSong(song_idx))" Color="Color.Primary" />
@if (difficulties[song_idx] != Difficulty.None)
{
Difficulty difficulty = difficulties[song_idx];
<MudImage Src="@ScoreUtils.GetDifficultyIcon(difficulty)" Alt="@ScoreUtils.GetDifficultyTitle(difficulty)" Width="24" Height="48" Style="padding: 12px 0px 12px 0px; margin-right: 8px; vertical-align: middle;" />
}
<span style="vertical-align: middle; font-family: Nijiiro, sans-serif;">@(musicDetails[song_idx].SongId == 0 ? Localizer["No Select"] : musicDetails[song_idx].SongName)</span>
</MudField>
<MudIconButton Icon="@Icons.Material.Filled.Settings" Variant="Variant.Text" Color="Color.Primary" OnClick="(_ => OnExpandCollapseClick(song_idx))" />
@if (Info.challengeCompeteSongs.Count > 1)
{
<MudIconButton Icon="@Icons.Material.Filled.Delete" Variant="Variant.Text" Color="Color.Error" OnClick="(_ => DelSong(song_idx))" />
}
</MudStack>
<MudCollapse Expanded="_expandeds[song_idx]">
<MudStack Row="true">
<MudSelect @bind-Value="@song.IsVanishOn" Label=@Localizer["Vanish"] AnchorOrigin="Origin.BottomCenter">
<MudSelectItem Value="@(-1)">@Localizer["Any"]</MudSelectItem>
<MudSelectItem Value="@(1)">@Localizer["On"]</MudSelectItem>
<MudSelectItem Value="@(0)">@Localizer["Off"]</MudSelectItem>
</MudSelect>
<MudSelect @bind-Value="@song.IsInverseOn" Label=@Localizer["Inverse"] AnchorOrigin="Origin.BottomCenter">
<MudSelectItem Value="@(-1)">@Localizer["Any"]</MudSelectItem>
<MudSelectItem Value="@(1)">@Localizer["On"]</MudSelectItem>
<MudSelectItem Value="@(0)">@Localizer["Off"]</MudSelectItem>
</MudSelect>
<MudSelect @bind-Value="@song.Speed" Label=@Localizer["Speed"] AnchorOrigin="Origin.BottomCenter">
<MudSelectItem Value="@(-1)">@Localizer["Any"]</MudSelectItem>
@for (uint idx = 0; idx < SpeedStrings.Length; idx++)
{
var speed_idx = idx;
<MudSelectItem Value="@((int)speed_idx)">@SpeedStrings[speed_idx]</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@song.RandomType" Label=@Localizer["Random"] AnchorOrigin="Origin.BottomCenter">
<MudSelectItem Value="@(-1)">@Localizer["Any"]</MudSelectItem>
@foreach (var item in Enum.GetValues<RandomType>())
{
<MudSelectItem Value="@((int)item)">@Localizer[item.ToString()]</MudSelectItem>
}
</MudSelect>
</MudStack>
</MudCollapse>
</MudStack>
}
@if (Info.challengeCompeteSongs.Count < MaxSongs)
{
<MudIconButton Icon="@Icons.Material.Filled.Add" Variant="Variant.Filled" Color="Color.Primary" OnClick="AddSong" Size="Size.Medium" />
}
<MudNumericField HideSpinButtons="true" @bind-Value="@ParticipantCount" Label="@Localizer["Max Participant"]" Variant="Variant.Text" Min="1" Max="MaxParticipant" />
<MudNumericField HideSpinButtons="true" @bind-Value="@LastFor" Label="@Localizer["Last For (Days)"]" Variant="Variant.Text" Min="1" Max="MaxDays" />
<MudSwitch @bind-Value="@OnlyPlayOnce" Color="Color.Primary">@Localizer["Only Play Once"]</MudSwitch>
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">@Localizer["Cancel"]</MudButton>
<MudButton Color="Color.Surface" OnClick="CreateChallengeCompetition">@Localizer["Create"]</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter]
private MudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public int Mode { get; set; } = 0;
[Parameter]
public int MaxSongs { get; set; } = 1;
[Parameter]
public int MaxDays { get; set; } = 30;
[Parameter]
public int MaxParticipant { get; set; } = 20;
[Parameter]
public ChallengeCompeteCreateInfo Info { get; set; } = new();
private static readonly string[] SpeedStrings =
{
"1.0", "1.1", "1.2", "1.3", "1.4",
"1.5", "1.6", "1.7", "1.8", "1.9",
"2.0", "2.5", "3.0", "3.5", "4.0"
};
private string Name = string.Empty;
private string Desc = string.Empty;
private bool[] _expandeds = new bool[0];
private MusicDetail[] musicDetails = new MusicDetail[0];
private Difficulty[] difficulties = new Difficulty[0];
private int LastFor = 1;
private int ParticipantCount = 1;
private bool OnlyPlayOnce = false;
protected override void OnInitialized()
{
_expandeds = new bool[MaxSongs];
musicDetails = new MusicDetail[MaxSongs];
difficulties = new Difficulty[MaxSongs];
for (int i = 0; i < MaxSongs; i++)
{
musicDetails[i] = new();
difficulties[i] = Difficulty.None;
}
Info.challengeCompeteSongs.Add(new());
}
private void AddSong()
{
Info.challengeCompeteSongs.Add(new());
}
private async Task SelSong(int i)
{
var options = new DialogOptions { CloseOnEscapeKey = true };
var parameters = new DialogParameters();
parameters.Add("SelectedSong", musicDetails[i]);
var reference = await DialogService.ShowAsync<ChooseSongDialog>(Localizer["Create"], parameters, options);
var songInfo = await reference.GetReturnValueAsync<SongInfo>();
if (songInfo != null)
{
musicDetails[i] = songInfo.MusicDetail;
difficulties[i] = songInfo.Difficulty;
}
}
private void DelSong(int i)
{
Info.challengeCompeteSongs.RemoveAt(i);
for (int ex = i; ex < MaxSongs; ex ++)
{
if (ex + 1 < MaxSongs)
{
_expandeds[ex] = _expandeds[ex + 1];
musicDetails[ex] = musicDetails[ex + 1];
difficulties[ex] = difficulties[ex + 1];
}
else
{
_expandeds[ex] = false;
musicDetails[ex] = new();
difficulties[ex] = Difficulty.None;
}
}
}
private void Cancel() => MudDialog.Cancel();
private async Task CreateChallengeCompetition()
{
var options = new DialogOptions { CloseOnEscapeKey = true };
Info.Name = Name;
Info.Desc = Desc;
for (int i = 0; i < Info.challengeCompeteSongs.Count; i ++)
{
if (difficulties[i] == Difficulty.None)
{
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
(string)Localizer["Must Select Song Error"],
Localizer["Dialog OK"], null, null, options);
return;
}
Info.challengeCompeteSongs[i].SongId = musicDetails[i].SongId;
Info.challengeCompeteSongs[i].Difficulty = difficulties[i];
}
Info.MaxParticipant = (uint) ParticipantCount;
Info.LastFor = (uint) LastFor;
Info.CompeteMode = (CompeteModeType)Mode;
Info.OnlyPlayOnce = OnlyPlayOnce;
var resp = await Client.PostAsJsonAsync($"api/ChallengeCompeteManage/createOfficialCompete", Info);
if (resp.StatusCode != HttpStatusCode.OK)
{
await DialogService.ShowMessageBox(
Localizer["Error"],
(MarkupString)
(string)Localizer["Create Compete Error"],
Localizer["Dialog OK"], null, null, options);
return;
}
MudDialog.Close(DialogResult.Ok(1));
}
private void OnExpandCollapseClick(int i)
{
_expandeds[i] = !_expandeds[i];
}
}

View File

@ -0,0 +1,138 @@
@using Blazored.LocalStorage
@using TaikoWebUI.Utilities;
@inject IJSRuntime Js
@inject ILocalStorageService LocalStorage
@inject IGameDataService GameDataService
<MudDialog Style="width: 1200px">
<DialogContent>
<MudTable Items="musicDetailDictionary.Values" Filter="@FilterSongs" @bind-SelectedItem="@SelectedSong" Height="40vh" Hover="true">
<ColGroup>
<col style="width: 50px;" />
<col />
</ColGroup>
<ToolBarContent>
<MudTextField @bind-Value="Search"
Placeholder="@Localizer["Search by Title"]"
Variant="Variant.Outlined"
Clearable="true"
Immediate="true"
Margin="Margin.Dense"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
IconSize="Size.Medium" />
</ToolBarContent>
<HeaderContent>
<MudTh>
<MudTableSortLabel T="MusicDetail" SortBy="context => GameDataService.GetMusicNameBySongId(musicDetailDictionary, context.SongId, SongNameLanguage)">
@Localizer["Song Title"]
</MudTableSortLabel>
</MudTh>
@foreach (var difficulty in Enum.GetValues<Difficulty>())
{
@if (difficulty is not Difficulty.None)
{
<MudTh>
<MudTableSortLabel T="MusicDetail" SortBy="context => GameDataService.GetMusicStarLevel(musicDetailDictionary, context.SongId, difficulty)">
<img src="@ScoreUtils.GetDifficultyIcon(difficulty)" alt="@ScoreUtils.GetDifficultyTitle(difficulty)" style="@Constants.ICON_STYLE" />
</MudTableSortLabel>
</MudTh>
}
}
</HeaderContent>
<RowTemplate>
<MudTd Style="width:400px">
<MudText Typo="Typo.body2" Style="font-weight:bold">
@GameDataService.GetMusicNameBySongId(musicDetailDictionary, context.SongId, SongNameLanguage)
</MudText>
</MudTd>
@foreach (var difficulty in Enum.GetValues<Difficulty>())
{
@if (difficulty is not Difficulty.None)
{
var starLevel = GameDataService.GetMusicStarLevel(musicDetailDictionary, context.SongId, difficulty);
<MudTd>
@if (starLevel > 0)
{
<MudButton StartIcon="@Icons.Material.Filled.Star" OnClick="@(_ => Select(context, difficulty))">@starLevel</MudButton>
}
else
{
<MudText Typo="Typo.body2" Style="font-weight:bold">
-
</MudText>
}
</MudTd>
}
}
</RowTemplate>
<PagerContent>
<MudTablePager RowsPerPageString=@Localizer["Rows Per Page:"] />
</PagerContent>
</MudTable>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">
@Localizer["Cancel"]
</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; } = null!;
[Parameter] public MusicDetail SelectedSong { get; set; } = new();
private Dictionary<uint, MusicDetail> musicDetailDictionary = new();
private string? SongNameLanguage;
private string Search { get; set; } = string.Empty;
private string GenreFilter { get; set; } = string.Empty;
private string searchString = string.Empty;
protected override async Task OnInitializedAsync()
{
base.OnInitialized();
SongNameLanguage = await LocalStorage.GetItemAsync<string>("songNameLanguage");
musicDetailDictionary = await GameDataService.GetMusicDetailDictionary();
}
private bool FilterSongs(MusicDetail musicDetail)
{
var stringsToCheck = new List<string>
{
musicDetail.SongName,
musicDetail.SongNameEN,
musicDetail.SongNameCN,
musicDetail.SongNameKO,
};
if (!string.IsNullOrEmpty(Search) && !stringsToCheck.Any(s => s.Contains(Search, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
return true;
}
private void Select(MusicDetail musicDetail, Difficulty difficulty)
{
MudDialog.Close(DialogResult.Ok(new SongInfo()
{
MusicDetail = musicDetail,
Difficulty = difficulty
}));
}
private void Cancel()
{
MudDialog.Cancel();
}
}

View File

@ -0,0 +1,10 @@
@using TaikoWebUI.Components;
@page "/ChallengeCompe/{baid:int}/OfficialCompetition"
<ChallengeCompeteGrid Baid="Baid" Mode="3"></ChallengeCompeteGrid>
@code {
[Parameter]
public int Baid { get; set; }
}