diff --git a/GameDatabase/Context/TaikoDbContextPartial.cs b/GameDatabase/Context/TaikoDbContextPartial.cs index 9d55aa2..cb583fa 100644 --- a/GameDatabase/Context/TaikoDbContextPartial.cs +++ b/GameDatabase/Context/TaikoDbContextPartial.cs @@ -10,6 +10,7 @@ public partial class TaikoDbContext public virtual DbSet DanStageScoreData { get; set; } = null!; public virtual DbSet AiScoreData { get; set; } = null!; public virtual DbSet AiSectionScoreData { get; set; } = null!; + public virtual DbSet Tokens { get; set; } = null!; partial void OnModelCreatingPartial(ModelBuilder modelBuilder) { @@ -60,5 +61,16 @@ public partial class TaikoDbContext .HasForeignKey(d => new { d.Baid, d.SongId, d.Difficulty }) .OnDelete(DeleteBehavior.Cascade); }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.Baid, e.Id }); + + entity.HasOne(d => d.Datum) + .WithMany(p => p.Tokens) + .HasPrincipalKey(p => p.Baid) + .HasForeignKey(d => d.Baid) + .OnDelete(DeleteBehavior.Cascade); + }); } } \ No newline at end of file diff --git a/GameDatabase/Entities/Token.cs b/GameDatabase/Entities/Token.cs new file mode 100644 index 0000000..61f3d40 --- /dev/null +++ b/GameDatabase/Entities/Token.cs @@ -0,0 +1,12 @@ +namespace GameDatabase.Entities; + +public class Token +{ + public uint Baid { get; set; } + + public int Id { get; set; } + + public int Count { get; set; } + + public virtual UserDatum? Datum { get; set; } +} \ No newline at end of file diff --git a/GameDatabase/Entities/UserDatum.cs b/GameDatabase/Entities/UserDatum.cs index 68212cf..04d1b9b 100644 --- a/GameDatabase/Entities/UserDatum.cs +++ b/GameDatabase/Entities/UserDatum.cs @@ -31,7 +31,7 @@ namespace GameDatabase.Entities public bool DisplayAchievement { get; set; } public Difficulty AchievementDisplayDifficulty { get; set; } public int AiWinCount { get; set; } - public string TokenCountDict { get; set; } = "{}"; + public List Tokens { get; set; } = new(); public string UnlockedSongIdList { get; set; } = "[]"; public bool IsAdmin { get; set; } } diff --git a/GameDatabase/Migrations/20240309102758_SeparateTokens.Designer.cs b/GameDatabase/Migrations/20240309102758_SeparateTokens.Designer.cs new file mode 100644 index 0000000..0a4d5c1 --- /dev/null +++ b/GameDatabase/Migrations/20240309102758_SeparateTokens.Designer.cs @@ -0,0 +1,519 @@ +// +using System; +using GameDatabase.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace GameDatabase.Migrations +{ + [DbContext(typeof(TaikoDbContext))] + [Migration("20240309102758_SeparateTokens")] + partial class SeparateTokens + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0-rc.2.23480.1"); + + modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("Difficulty") + .HasColumnType("INTEGER"); + + b.Property("IsWin") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "SongId", "Difficulty"); + + b.ToTable("AiScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("Difficulty") + .HasColumnType("INTEGER"); + + b.Property("SectionIndex") + .HasColumnType("INTEGER"); + + b.Property("Crown") + .HasColumnType("INTEGER"); + + b.Property("DrumrollCount") + .HasColumnType("INTEGER"); + + b.Property("GoodCount") + .HasColumnType("INTEGER"); + + b.Property("IsWin") + .HasColumnType("INTEGER"); + + b.Property("MissCount") + .HasColumnType("INTEGER"); + + b.Property("OkCount") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "SongId", "Difficulty", "SectionIndex"); + + b.ToTable("AiSectionScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.Card", b => + { + b.Property("AccessCode") + .HasColumnType("TEXT"); + + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.HasKey("AccessCode"); + + b.HasIndex("Baid"); + + b.ToTable("Card", (string)null); + }); + + modelBuilder.Entity("GameDatabase.Entities.Credential", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Baid"); + + b.ToTable("Credential", (string)null); + }); + + modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("DanId") + .HasColumnType("INTEGER"); + + b.Property("DanType") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("ArrivalSongCount") + .HasColumnType("INTEGER"); + + b.Property("ClearState") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0u); + + b.Property("ComboCountTotal") + .HasColumnType("INTEGER"); + + b.Property("SoulGaugeTotal") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "DanId", "DanType"); + + b.ToTable("DanScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("DanId") + .HasColumnType("INTEGER"); + + b.Property("DanType") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("SongNumber") + .HasColumnType("INTEGER"); + + b.Property("BadCount") + .HasColumnType("INTEGER"); + + b.Property("ComboCount") + .HasColumnType("INTEGER"); + + b.Property("DrumrollCount") + .HasColumnType("INTEGER"); + + b.Property("GoodCount") + .HasColumnType("INTEGER"); + + b.Property("HighScore") + .HasColumnType("INTEGER"); + + b.Property("OkCount") + .HasColumnType("INTEGER"); + + b.Property("PlayScore") + .HasColumnType("INTEGER"); + + b.Property("TotalHitCount") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "DanId", "DanType", "SongNumber"); + + b.ToTable("DanStageScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("Difficulty") + .HasColumnType("INTEGER"); + + b.Property("BestCrown") + .HasColumnType("INTEGER"); + + b.Property("BestRate") + .HasColumnType("INTEGER"); + + b.Property("BestScore") + .HasColumnType("INTEGER"); + + b.Property("BestScoreRank") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "SongId", "Difficulty"); + + b.ToTable("SongBestData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("ComboCount") + .HasColumnType("INTEGER"); + + b.Property("Crown") + .HasColumnType("INTEGER"); + + b.Property("Difficulty") + .HasColumnType("INTEGER"); + + b.Property("DrumrollCount") + .HasColumnType("INTEGER"); + + b.Property("GoodCount") + .HasColumnType("INTEGER"); + + b.Property("HitCount") + .HasColumnType("INTEGER"); + + b.Property("MissCount") + .HasColumnType("INTEGER"); + + b.Property("OkCount") + .HasColumnType("INTEGER"); + + b.Property("PlayTime") + .HasColumnType("datetime"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("ScoreRank") + .HasColumnType("INTEGER"); + + b.Property("ScoreRate") + .HasColumnType("INTEGER"); + + b.Property("Skipped") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("SongNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Baid"); + + b.ToTable("SongPlayData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.Token", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "Id"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("GameDatabase.Entities.UserDatum", b => + { + b.Property("Baid") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AchievementDisplayDifficulty") + .HasColumnType("INTEGER"); + + b.Property("AiWinCount") + .HasColumnType("INTEGER"); + + b.Property("ColorBody") + .HasColumnType("INTEGER"); + + b.Property("ColorFace") + .HasColumnType("INTEGER"); + + b.Property("ColorLimb") + .HasColumnType("INTEGER"); + + b.Property("CostumeData") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CostumeFlgArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DifficultyPlayedArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DifficultySettingArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DisplayAchievement") + .HasColumnType("INTEGER"); + + b.Property("DisplayDan") + .HasColumnType("INTEGER"); + + b.Property("FavoriteSongsArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GenericInfoFlgArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("IsSkipOn") + .HasColumnType("INTEGER"); + + b.Property("IsVoiceOn") + .HasColumnType("INTEGER"); + + b.Property("LastPlayDatetime") + .HasColumnType("datetime"); + + b.Property("LastPlayMode") + .HasColumnType("INTEGER"); + + b.Property("MyDonName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MyDonNameLanguage") + .HasColumnType("INTEGER"); + + b.Property("NotesPosition") + .HasColumnType("INTEGER"); + + b.Property("OptionSetting") + .HasColumnType("INTEGER"); + + b.Property("SelectedToneId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitleFlgArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitlePlateId") + .HasColumnType("INTEGER"); + + b.Property("ToneFlgArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UnlockedSongIdList") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Baid"); + + b.ToTable("UserData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b => + { + b.HasOne("GameDatabase.Entities.AiScoreDatum", "Parent") + .WithMany("AiSectionScoreData") + .HasForeignKey("Baid", "SongId", "Difficulty") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("GameDatabase.Entities.Card", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.Credential", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b => + { + b.HasOne("GameDatabase.Entities.DanScoreDatum", "Parent") + .WithMany("DanStageScoreData") + .HasForeignKey("Baid", "DanId", "DanType") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.Token", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Datum") + .WithMany("Tokens") + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Datum"); + }); + + modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b => + { + b.Navigation("AiSectionScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b => + { + b.Navigation("DanStageScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.UserDatum", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/GameDatabase/Migrations/20240309102758_SeparateTokens.cs b/GameDatabase/Migrations/20240309102758_SeparateTokens.cs new file mode 100644 index 0000000..8b7e718 --- /dev/null +++ b/GameDatabase/Migrations/20240309102758_SeparateTokens.cs @@ -0,0 +1,71 @@ +using System.Text.Json; +using GameDatabase.Context; +using GameDatabase.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace GameDatabase.Migrations +{ + internal record Pair(uint Baid, string TokenCountDict); + /// + public partial class SeparateTokens : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + using var context = new TaikoDbContext(); + var tokenJsons = context.Database + .SqlQuery($"SELECT Baid, TokenCountDict FROM UserData") + .ToList(); + + migrationBuilder.CreateTable( + name: "Tokens", + columns: table => new + { + Baid = table.Column(type: "INTEGER", nullable: false), + Id = table.Column(type: "INTEGER", nullable: false), + Count = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tokens", x => new { x.Baid, x.Id }); + table.ForeignKey( + name: "FK_Tokens_UserData_Baid", + column: x => x.Baid, + principalTable: "UserData", + principalColumn: "Baid", + onDelete: ReferentialAction.Cascade); + }); + foreach (var (baid, tokenCountDict) in tokenJsons) + { + var tokenDict = JsonSerializer.Deserialize>(tokenCountDict); + foreach (var (key, value) in tokenDict) + { + migrationBuilder.InsertData( + table: "Tokens", + columns: new[] { "Baid", "Id", "Count" }, + values: new object[] { baid, key, value }); + } + } + migrationBuilder.DropColumn( + name: "TokenCountDict", + table: "UserData"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Tokens"); + + migrationBuilder.AddColumn( + name: "TokenCountDict", + table: "UserData", + type: "TEXT", + nullable: false, + defaultValue: ""); + } + } +} diff --git a/GameDatabase/Migrations/TaikoDbContextModelSnapshot.cs b/GameDatabase/Migrations/TaikoDbContextModelSnapshot.cs index f0e4507..2b23b5a 100644 --- a/GameDatabase/Migrations/TaikoDbContextModelSnapshot.cs +++ b/GameDatabase/Migrations/TaikoDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace TaikoLocalServer.Migrations modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b => { - b.Property("Baid") + b.Property("Baid") .HasColumnType("INTEGER"); b.Property("SongId") @@ -38,7 +38,7 @@ namespace TaikoLocalServer.Migrations modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b => { - b.Property("Baid") + b.Property("Baid") .HasColumnType("INTEGER"); b.Property("SongId") @@ -81,7 +81,7 @@ namespace TaikoLocalServer.Migrations b.Property("AccessCode") .HasColumnType("TEXT"); - b.Property("Baid") + b.Property("Baid") .HasColumnType("INTEGER"); b.HasKey("AccessCode"); @@ -93,7 +93,7 @@ namespace TaikoLocalServer.Migrations modelBuilder.Entity("GameDatabase.Entities.Credential", b => { - b.Property("Baid") + b.Property("Baid") .HasColumnType("INTEGER"); b.Property("Password") @@ -111,7 +111,7 @@ namespace TaikoLocalServer.Migrations modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b => { - b.Property("Baid") + b.Property("Baid") .HasColumnType("INTEGER"); b.Property("DanId") @@ -143,7 +143,7 @@ namespace TaikoLocalServer.Migrations modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b => { - b.Property("Baid") + b.Property("Baid") .HasColumnType("INTEGER"); b.Property("DanId") @@ -188,7 +188,7 @@ namespace TaikoLocalServer.Migrations modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b => { - b.Property("Baid") + b.Property("Baid") .HasColumnType("INTEGER"); b.Property("SongId") @@ -220,7 +220,7 @@ namespace TaikoLocalServer.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Baid") + b.Property("Baid") .HasColumnType("INTEGER"); b.Property("ComboCount") @@ -275,9 +275,25 @@ namespace TaikoLocalServer.Migrations b.ToTable("SongPlayData"); }); + modelBuilder.Entity("GameDatabase.Entities.Token", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "Id"); + + b.ToTable("Tokens"); + }); + modelBuilder.Entity("GameDatabase.Entities.UserDatum", b => { - b.Property("Baid") + b.Property("Baid") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); @@ -368,10 +384,6 @@ namespace TaikoLocalServer.Migrations b.Property("TitlePlateId") .HasColumnType("INTEGER"); - b.Property("TokenCountDict") - .IsRequired() - .HasColumnType("TEXT"); - b.Property("ToneFlgArray") .IsRequired() .HasColumnType("TEXT"); @@ -473,6 +485,17 @@ namespace TaikoLocalServer.Migrations b.Navigation("Ba"); }); + modelBuilder.Entity("GameDatabase.Entities.Token", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Datum") + .WithMany("Tokens") + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Datum"); + }); + modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b => { b.Navigation("AiSectionScoreData"); @@ -482,6 +505,11 @@ namespace TaikoLocalServer.Migrations { b.Navigation("DanStageScoreData"); }); + + modelBuilder.Entity("GameDatabase.Entities.UserDatum", b => + { + b.Navigation("Tokens"); + }); #pragma warning restore 612, 618 } } diff --git a/TaikoLocalServer/Controllers/Game/AddTokenCountController.cs b/TaikoLocalServer/Controllers/Game/AddTokenCountController.cs index 7b97521..75b5f8a 100644 --- a/TaikoLocalServer/Controllers/Game/AddTokenCountController.cs +++ b/TaikoLocalServer/Controllers/Game/AddTokenCountController.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using GameDatabase.Entities; using Throw; namespace TaikoLocalServer.Controllers.Game; @@ -23,32 +24,25 @@ public class AddTokenCountController : BaseController var user = await userDatumService.GetFirstUserDatumOrNull(request.Baid); user.ThrowIfNull($"User with baid {request.Baid} does not exist!"); - var tokenCountDict = new Dictionary(); - try - { - tokenCountDict = !string.IsNullOrEmpty(user.TokenCountDict) - ? JsonSerializer.Deserialize>(user.TokenCountDict) - : new Dictionary(); - } - catch (JsonException e) - { - Logger.LogError(e, "Parsing TokenCountDict data for user with baid {Request} failed!", request.Baid); - } - - tokenCountDict.ThrowIfNull("TokenCountDict should never be null"); - foreach (var addTokenCountData in request.AryAddTokenCountDatas) { var tokenId = addTokenCountData.TokenId; var addTokenCount = addTokenCountData.AddTokenCount; - - if (tokenCountDict.ContainsKey(tokenId)) - tokenCountDict[tokenId] += addTokenCount; + var token = user.Tokens.FirstOrDefault(t => t.Id == tokenId); + if (token != null) + { + token.Count += addTokenCount; + } else - tokenCountDict.Add(tokenId, addTokenCount); + { + user.Tokens.Add(new Token + { + Id = (int)tokenId, + Count = addTokenCount + }); + } } - user.TokenCountDict = JsonSerializer.Serialize(tokenCountDict); await userDatumService.UpdateUserDatum(user); var response = new AddTokenCountResponse diff --git a/TaikoLocalServer/Controllers/Game/GetTokenCountController.cs b/TaikoLocalServer/Controllers/Game/GetTokenCountController.cs index 7e2f8bf..1009bd8 100644 --- a/TaikoLocalServer/Controllers/Game/GetTokenCountController.cs +++ b/TaikoLocalServer/Controllers/Game/GetTokenCountController.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using GameDatabase.Entities; using Throw; namespace TaikoLocalServer.Controllers.Game; @@ -60,20 +61,6 @@ public class GetTokenCountController : BaseController machina4TokenId }; - var tokenCountDict = new Dictionary(); - try - { - tokenCountDict = !string.IsNullOrEmpty(user.TokenCountDict) - ? JsonSerializer.Deserialize>(user.TokenCountDict) - : new Dictionary(); - } - catch (JsonException e) - { - Logger.LogError(e, "Parsing TokenCountDict data for user with baid {Request} failed!", request.Baid); - } - - tokenCountDict.ThrowIfNull("TokenCountDict should never be null"); - var response = new GetTokenCountResponse { Result = 1 @@ -83,15 +70,22 @@ public class GetTokenCountController : BaseController { if (tokenDataId <= 0) continue; var castedTokenDataId = (uint)tokenDataId; - tokenCountDict.TryAdd(castedTokenDataId, 0); + if (user.Tokens.All(token => token.Id != castedTokenDataId)) + { + user.Tokens.Add(new Token + { + Id = (int)castedTokenDataId, + Count = 0 + }); + } + var tokenCount = user.Tokens.First(token => token.Id == castedTokenDataId).Count; response.AryTokenCountDatas.Add(new GetTokenCountResponse.TokenCountData { - TokenCount = tokenCountDict[castedTokenDataId], + TokenCount = tokenCount, TokenId = castedTokenDataId }); } - - user.TokenCountDict = JsonSerializer.Serialize(tokenCountDict); + await userDatumService.UpdateUserDatum(user); return Ok(response); diff --git a/TaikoLocalServer/Controllers/Game/MyDonEntryController.cs b/TaikoLocalServer/Controllers/Game/MyDonEntryController.cs index 80b1b7a..521a30d 100644 --- a/TaikoLocalServer/Controllers/Game/MyDonEntryController.cs +++ b/TaikoLocalServer/Controllers/Game/MyDonEntryController.cs @@ -43,7 +43,6 @@ public class MyDonEntryController : BaseController TitleFlgArray = "[]", CostumeFlgArray = "[[0],[0],[0],[0],[0]]", GenericInfoFlgArray = "[]", - TokenCountDict = "{}", UnlockedSongIdList = "[]" }; await userDatumService.InsertUserDatum(newUser); diff --git a/TaikoLocalServer/Controllers/Game/SongPurchaseController.cs b/TaikoLocalServer/Controllers/Game/SongPurchaseController.cs index e2511d5..5aca77f 100644 --- a/TaikoLocalServer/Controllers/Game/SongPurchaseController.cs +++ b/TaikoLocalServer/Controllers/Game/SongPurchaseController.cs @@ -23,7 +23,7 @@ public class SongPurchaseController : BaseController var user = await userDatumService.GetFirstUserDatumOrNull(request.Baid); user.ThrowIfNull($"User with baid {request.Baid} does not exist!"); - var tokenCountDict = new Dictionary(); + /*var tokenCountDict = new Dictionary(); try { tokenCountDict = !string.IsNullOrEmpty(user.TokenCountDict) @@ -35,7 +35,7 @@ public class SongPurchaseController : BaseController Logger.LogError(e, "Parsing TokenCountDict data for user with baid {Request} failed!", request.Baid); } - tokenCountDict.ThrowIfNull("TokenCountDict should never be null"); + tokenCountDict.ThrowIfNull("TokenCountDict should never be null");*/ Logger.LogInformation("Original UnlockedSongIdList: {UnlockedSongIdList}", user.UnlockedSongIdList); @@ -53,11 +53,21 @@ public class SongPurchaseController : BaseController unlockedSongIdList.ThrowIfNull("UnlockedSongIdList should never be null"); - if (tokenCountDict.ContainsKey(request.TokenId)) tokenCountDict[request.TokenId] -= (int)request.Price; + //if (tokenCountDict.ContainsKey(request.TokenId)) tokenCountDict[request.TokenId] -= (int)request.Price; + var token = user.Tokens.FirstOrDefault(t => t.Id == request.TokenId); + if (token is not null && token.Count >= request.Price) + { + token.Count -= (int)request.Price; + } + else + { + Logger.LogError("User with baid {Baid} does not have enough tokens to purchase song with id {SongNo}!", request.Baid, request.SongNo); + return Ok(new SongPurchaseResponse { Result = 0 }); + } + if (!unlockedSongIdList.Contains(request.SongNo)) unlockedSongIdList.Add(request.SongNo); - user.TokenCountDict = JsonSerializer.Serialize(tokenCountDict); user.UnlockedSongIdList = JsonSerializer.Serialize(unlockedSongIdList); Logger.LogInformation("Updated UnlockedSongIdList: {UnlockedSongIdList}", user.UnlockedSongIdList); @@ -67,7 +77,7 @@ public class SongPurchaseController : BaseController var response = new SongPurchaseResponse { Result = 1, - TokenCount = tokenCountDict[request.TokenId] + TokenCount = token.Count }; return Ok(response); diff --git a/TaikoLocalServer/Services/UserDatumService.cs b/TaikoLocalServer/Services/UserDatumService.cs index d452b8a..c43b12a 100644 --- a/TaikoLocalServer/Services/UserDatumService.cs +++ b/TaikoLocalServer/Services/UserDatumService.cs @@ -19,12 +19,16 @@ public class UserDatumService : IUserDatumService public async Task GetFirstUserDatumOrNull(uint baid) { - return await context.UserData.FindAsync(baid); + return await context.UserData + .Include(d => d.Tokens) + .FirstOrDefaultAsync(d => d.Baid == baid); } public async Task GetFirstUserDatumOrDefault(uint baid) { - return await context.UserData.FindAsync(baid) ?? new UserDatum(); + return await context.UserData + .Include(d => d.Tokens) + .FirstOrDefaultAsync(d => d.Baid == baid) ?? new UserDatum(); } public async Task> GetAllUserData()