1
0
mirror of synced 2024-11-28 00:20:53 +01:00

Merge branch 'Refactor' into new-songs-ui

This commit is contained in:
shiibe 2024-03-16 10:55:38 -04:00
commit 88e6c70e13
123 changed files with 6776 additions and 2034 deletions

View File

@ -4,34 +4,42 @@ namespace GameDatabase.Entities;
public partial class UserDatum
{
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 string FavoriteSongsArray { get; set; } = "[]";
public string ToneFlgArray { get; set; } = "[]";
public string TitleFlgArray { get; set; } = "[]";
public string CostumeFlgArray { get; set; } = "[[],[],[],[],[]]";
public string GenericInfoFlgArray { get; set; } = "[]";
public short OptionSetting { get; set; }
public int NotesPosition { get; set; }
public bool IsVoiceOn { get; set; }
public bool IsSkipOn { get; set; }
public string DifficultyPlayedArray { get; set; } = "[]";
public string DifficultySettingArray { get; set; } = "[]";
public uint SelectedToneId { get; set; }
public DateTime LastPlayDatetime { get; set; }
public uint LastPlayMode { get; set; }
public uint ColorBody { get; set; }
public uint ColorFace { get; set; }
public uint ColorLimb { get; set; }
public string CostumeData { get; set; } = "[]";
public bool DisplayDan { get; set; }
public bool DisplayAchievement { get; set; }
public Difficulty AchievementDisplayDifficulty { get; set; }
public int AiWinCount { get; set; }
public List<Token> Tokens { get; set; } = new();
public string UnlockedSongIdList { get; set; } = "[]";
public bool IsAdmin { get; set; }
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 List<uint> FavoriteSongsArray { get; set; } = [];
public List<uint> ToneFlgArray { get; set; } = [0];
public List<uint> TitleFlgArray { get; set; } = [];
public string CostumeFlgArray { get; set; } = "[[],[],[],[],[]]";
public List<uint> UnlockedKigurumi { get; set; } = [0];
public List<uint> UnlockedHead { get; set; } = [0];
public List<uint> UnlockedBody { get; set; }= [0];
public List<uint> UnlockedFace { get; set; }= [0];
public List<uint> UnlockedPuchi{ get; set; }= [0];
public uint[] GenericInfoFlgArray { get; set; } = Array.Empty<uint>();
public short OptionSetting { get; set; }
public int NotesPosition { get; set; }
public bool IsVoiceOn { get; set; }
public bool IsSkipOn { get; set; }
// TODO: Split into separate fields
public string DifficultyPlayedArray { get; set; } = "[]";
// TODO: Split into separate fields
public string DifficultySettingArray { get; set; } = "[]";
public uint SelectedToneId { get; set; }
public DateTime LastPlayDatetime { get; set; }
public uint LastPlayMode { get; set; }
public uint ColorBody { get; set; }
public uint ColorFace { get; set; }
public uint ColorLimb { get; set; }
// TODO: Split into separate fields
public string CostumeData { get; set; } = "[]";
public bool DisplayDan { get; set; }
public bool DisplayAchievement { get; set; }
public Difficulty AchievementDisplayDifficulty { get; set; }
public int AiWinCount { get; set; }
public List<Token> Tokens { get; set; } = new();
public List<uint> UnlockedSongIdList { get; set; } = [];
public bool IsAdmin { get; set; }
}

View File

@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>11</LangVersion>
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>

View File

@ -0,0 +1,539 @@
// <auto-generated />
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("20240316064531_SplitCostumeUnlocks")]
partial class SplitCostumeUnlocks
{
/// <inheritdoc />
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<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<bool>("IsWin")
.HasColumnType("INTEGER");
b.HasKey("Baid", "SongId", "Difficulty");
b.ToTable("AiScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<int>("SectionIndex")
.HasColumnType("INTEGER");
b.Property<int>("Crown")
.HasColumnType("INTEGER");
b.Property<uint>("DrumrollCount")
.HasColumnType("INTEGER");
b.Property<uint>("GoodCount")
.HasColumnType("INTEGER");
b.Property<bool>("IsWin")
.HasColumnType("INTEGER");
b.Property<uint>("MissCount")
.HasColumnType("INTEGER");
b.Property<uint>("OkCount")
.HasColumnType("INTEGER");
b.Property<uint>("Score")
.HasColumnType("INTEGER");
b.HasKey("Baid", "SongId", "Difficulty", "SectionIndex");
b.ToTable("AiSectionScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.Card", b =>
{
b.Property<string>("AccessCode")
.HasColumnType("TEXT");
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.HasKey("AccessCode");
b.HasIndex("Baid");
b.ToTable("Card", (string)null);
});
modelBuilder.Entity("GameDatabase.Entities.Credential", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Baid");
b.ToTable("Credential", (string)null);
});
modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("DanId")
.HasColumnType("INTEGER");
b.Property<int>("DanType")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.Property<uint>("ArrivalSongCount")
.HasColumnType("INTEGER");
b.Property<uint>("ClearState")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0u);
b.Property<uint>("ComboCountTotal")
.HasColumnType("INTEGER");
b.Property<uint>("SoulGaugeTotal")
.HasColumnType("INTEGER");
b.HasKey("Baid", "DanId", "DanType");
b.ToTable("DanScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("DanId")
.HasColumnType("INTEGER");
b.Property<int>("DanType")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.Property<uint>("SongNumber")
.HasColumnType("INTEGER");
b.Property<uint>("BadCount")
.HasColumnType("INTEGER");
b.Property<uint>("ComboCount")
.HasColumnType("INTEGER");
b.Property<uint>("DrumrollCount")
.HasColumnType("INTEGER");
b.Property<uint>("GoodCount")
.HasColumnType("INTEGER");
b.Property<uint>("HighScore")
.HasColumnType("INTEGER");
b.Property<uint>("OkCount")
.HasColumnType("INTEGER");
b.Property<uint>("PlayScore")
.HasColumnType("INTEGER");
b.Property<uint>("TotalHitCount")
.HasColumnType("INTEGER");
b.HasKey("Baid", "DanId", "DanType", "SongNumber");
b.ToTable("DanStageScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<uint>("BestCrown")
.HasColumnType("INTEGER");
b.Property<uint>("BestRate")
.HasColumnType("INTEGER");
b.Property<uint>("BestScore")
.HasColumnType("INTEGER");
b.Property<uint>("BestScoreRank")
.HasColumnType("INTEGER");
b.HasKey("Baid", "SongId", "Difficulty");
b.ToTable("SongBestData");
});
modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("ComboCount")
.HasColumnType("INTEGER");
b.Property<uint>("Crown")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<uint>("DrumrollCount")
.HasColumnType("INTEGER");
b.Property<uint>("GoodCount")
.HasColumnType("INTEGER");
b.Property<uint>("HitCount")
.HasColumnType("INTEGER");
b.Property<uint>("MissCount")
.HasColumnType("INTEGER");
b.Property<uint>("OkCount")
.HasColumnType("INTEGER");
b.Property<DateTime>("PlayTime")
.HasColumnType("datetime");
b.Property<uint>("Score")
.HasColumnType("INTEGER");
b.Property<uint>("ScoreRank")
.HasColumnType("INTEGER");
b.Property<uint>("ScoreRate")
.HasColumnType("INTEGER");
b.Property<bool>("Skipped")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("SongNumber")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("Baid");
b.ToTable("SongPlayData");
});
modelBuilder.Entity("GameDatabase.Entities.Token", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<int>("Id")
.HasColumnType("INTEGER");
b.Property<int>("Count")
.HasColumnType("INTEGER");
b.HasKey("Baid", "Id");
b.ToTable("Tokens");
});
modelBuilder.Entity("GameDatabase.Entities.UserDatum", b =>
{
b.Property<uint>("Baid")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<uint>("AchievementDisplayDifficulty")
.HasColumnType("INTEGER");
b.Property<int>("AiWinCount")
.HasColumnType("INTEGER");
b.Property<uint>("ColorBody")
.HasColumnType("INTEGER");
b.Property<uint>("ColorFace")
.HasColumnType("INTEGER");
b.Property<uint>("ColorLimb")
.HasColumnType("INTEGER");
b.Property<string>("CostumeData")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CostumeFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DifficultyPlayedArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DifficultySettingArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("DisplayAchievement")
.HasColumnType("INTEGER");
b.Property<bool>("DisplayDan")
.HasColumnType("INTEGER");
b.Property<string>("FavoriteSongsArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("GenericInfoFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
b.Property<bool>("IsSkipOn")
.HasColumnType("INTEGER");
b.Property<bool>("IsVoiceOn")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastPlayDatetime")
.HasColumnType("datetime");
b.Property<uint>("LastPlayMode")
.HasColumnType("INTEGER");
b.Property<string>("MyDonName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint>("MyDonNameLanguage")
.HasColumnType("INTEGER");
b.Property<int>("NotesPosition")
.HasColumnType("INTEGER");
b.Property<short>("OptionSetting")
.HasColumnType("INTEGER");
b.Property<uint>("SelectedToneId")
.HasColumnType("INTEGER");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TitleFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint>("TitlePlateId")
.HasColumnType("INTEGER");
b.Property<string>("ToneFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedBody")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedFace")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedHead")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedKigurumi")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedPuchi")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("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
}
}
}

View File

@ -0,0 +1,138 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace GameDatabase.Migrations
{
/// <inheritdoc />
public partial class SplitCostumeUnlocks : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "UnlockedBody",
table: "UserData",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "UnlockedFace",
table: "UserData",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "UnlockedHead",
table: "UserData",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "UnlockedKigurumi",
table: "UserData",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "UnlockedPuchi",
table: "UserData",
type: "TEXT",
nullable: false,
defaultValue: "");
// Split the costumeflgarray into separate fields
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedKigurumi = json_extract(CostumeFlgArray, '$[0]'),
UnlockedHead = json_extract(CostumeFlgArray, '$[1]'),
UnlockedBody = json_extract(CostumeFlgArray, '$[2]'),
UnlockedFace = json_extract(CostumeFlgArray, '$[3]'),
UnlockedPuchi = json_extract(CostumeFlgArray, '$[4]')");
// Deduplicate values
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedKigurumi = (
SELECT json_group_array(DISTINCT value)
FROM (
SELECT value
FROM UserData AS ud
CROSS JOIN json_each(ud.UnlockedKigurumi)
WHERE ud.Baid = UserData.Baid
)
)");
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedHead = (
SELECT json_group_array(DISTINCT value)
FROM (
SELECT value
FROM UserData AS ud
CROSS JOIN json_each(ud.UnlockedHead)
WHERE ud.Baid = UserData.Baid
)
)");
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedBody = (
SELECT json_group_array(DISTINCT value)
FROM (
SELECT value
FROM UserData AS ud
CROSS JOIN json_each(ud.UnlockedBody)
WHERE ud.Baid = UserData.Baid
)
)");
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedFace = (
SELECT json_group_array(DISTINCT value)
FROM (
SELECT value
FROM UserData AS ud
CROSS JOIN json_each(ud.UnlockedFace)
WHERE ud.Baid = UserData.Baid
)
)");
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedPuchi = (
SELECT json_group_array(DISTINCT value)
FROM (
SELECT value
FROM UserData AS ud
CROSS JOIN json_each(ud.UnlockedPuchi)
WHERE ud.Baid = UserData.Baid
)
)");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "UnlockedBody",
table: "UserData");
migrationBuilder.DropColumn(
name: "UnlockedFace",
table: "UserData");
migrationBuilder.DropColumn(
name: "UnlockedHead",
table: "UserData");
migrationBuilder.DropColumn(
name: "UnlockedKigurumi",
table: "UserData");
migrationBuilder.DropColumn(
name: "UnlockedPuchi",
table: "UserData");
}
}
}

View File

@ -388,6 +388,26 @@ namespace TaikoLocalServer.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedBody")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedFace")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedHead")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedKigurumi")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedPuchi")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedSongIdList")
.IsRequired()
.HasColumnType("TEXT");

View File

@ -0,0 +1,10 @@
namespace SharedProject.Enums;
public enum NameLanguage : uint
{
Japanese = 0,
English = 1,
Korean = 2,
ChineseTraditional = 3,
ChineseSimplified = 4,
}

View File

@ -2,7 +2,7 @@
namespace SharedProject.Models;
public class DanData
public class DanData : IVerupNo
{
[JsonPropertyName("danId")]
public uint DanId { get; set; }

View File

@ -2,7 +2,7 @@
namespace SharedProject.Models;
public class EventFolderData
public class EventFolderData : IVerupNo
{
[JsonPropertyName("folderId")]
public uint FolderId { get; set; }
@ -14,7 +14,7 @@ public class EventFolderData
public uint Priority { get; set; }
[JsonPropertyName("songNo")]
public uint[]? SongNo { get; set; }
public uint[]? SongNoes { get; set; }
[JsonPropertyName("parentFolderId")]
public uint ParentFolderId { get; set; }

View File

@ -0,0 +1,6 @@
namespace SharedProject.Models;
public interface IVerupNo
{
public uint VerupNo { get; set; }
}

View File

@ -6,5 +6,6 @@ public class ShopFolderData
{
[JsonPropertyName("songNo")] public uint SongNo { get; set; }
public uint Type { get; set; }
[JsonPropertyName("price")] public uint Price { get; set; }
}

View File

@ -2,7 +2,7 @@
namespace SharedProject.Models;
public class SongIntroductionData
public class SongIntroductionData : IVerupNo
{
[JsonPropertyName("setId")]
public uint SetId { get; set; }
@ -14,5 +14,5 @@ public class SongIntroductionData
public uint MainSongNo { get; set; }
[JsonPropertyName("subSongNo")]
public uint[]? SubSongNo { get; set; }
public uint[]? SubSongNoes { get; set; }
}

View File

@ -1,6 +1,4 @@
using System.Buffers.Binary;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Specialized;
using SharedProject.Enums;
using SharedProject.Models;
using Throw;

View File

@ -16,4 +16,13 @@ public static class Constants
public const string DON_COS_REWARD_BASE_NAME = "don_cos_reward";
public const string SHOUGOU_BASE_NAME = "shougou";
public const string NEIRO_BASE_NAME = "neiro";
public const uint DAN_VERUP_MASTER_TYPE = 101;
public const uint GAIDEN_VERUP_MASTER_TYPE = 102;
public const uint FOLDER_VERUP_MASTER_TYPE = 103;
public const uint INTRO_VERUP_MASTER_TYPE = 105;
public const uint FUNCTION_ID_DANI_FOLDER_AVAILABLE = 1;
public const uint FUNCTION_ID_DANI_AVAILABLE = 2;
public const uint FUNCTION_ID_AI_BATTLE_AVAILABLE = 3;
}

View File

@ -1,5 +1,6 @@
using GameDatabase.Entities;
using SharedProject.Utils;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Common.Utils;
@ -21,4 +22,21 @@ public static class Extensions
datum.MissCount = ValueHelpers.Min(sectionData.NgCnt, datum.MissCount);
datum.DrumrollCount = ValueHelpers.Max(sectionData.PoundCnt, datum.DrumrollCount);
}
public static void UpdateBest(this AiSectionScoreDatum datum,CommonPlayResultData.AiStageSectionData sectionData)
{
var crown = (CrownType)sectionData.Crown;
if (crown == CrownType.Gold && sectionData.OkCnt == 0)
{
crown = CrownType.Dondaful;
}
datum.IsWin = sectionData.IsWin ? sectionData.IsWin : datum.IsWin;
datum.Crown = ValueHelpers.Max(crown, datum.Crown);
datum.Score = ValueHelpers.Max(sectionData.Score, datum.Score);
datum.GoodCount = ValueHelpers.Max(sectionData.GoodCnt, datum.GoodCount);
datum.OkCount = ValueHelpers.Min(sectionData.OkCnt, datum.OkCount);
datum.MissCount = ValueHelpers.Min(sectionData.NgCnt, datum.MissCount);
datum.DrumrollCount = ValueHelpers.Max(sectionData.PoundCnt, datum.DrumrollCount);
}
}

View File

@ -1,5 +1,4 @@
using System.Collections;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using GameDatabase.Entities;

View File

@ -51,27 +51,4 @@ public static class JsonHelper
return costumeData;
}
public static List<List<uint>> GetCostumeUnlockDataFromUserData(UserDatum userData, ILogger logger)
{
var costumeUnlockData = new List<List<uint>> { new(), new(), new(), new(), new() };
try
{
costumeUnlockData = JsonSerializer.Deserialize<List<List<uint>>>(userData.CostumeFlgArray);
}
catch (JsonException e)
{
logger.LogError(e, "Parsing costume json data failed");
}
if (costumeUnlockData != null && costumeUnlockData.Count >= 5)
{
return costumeUnlockData;
}
logger.LogWarning("Costume unlock data is null or count less than 5!");
costumeUnlockData = new List<List<uint>> { new(), new(), new(), new(), new() };
return costumeUnlockData;
}
}

View File

@ -16,6 +16,9 @@
"GameServer2": {
"Url": "https://0.0.0.0:54431"
},
"GameServerCN": {
"Url": "https://0.0.0.0:57402"
},
"GarmcServer": {
"Url": "https://0.0.0.0:443"
}

View File

@ -1,7 +1,6 @@
using SharedProject.Models;
using SharedProject.Utils;
using System.Text.Json;
using Throw;
namespace TaikoLocalServer.Controllers.Api;
@ -31,9 +30,10 @@ public class UserSettingsController : BaseController<UserSettingsController>
var costumeData = JsonHelper.GetCostumeDataFromUserData(user, Logger);
var costumeUnlockData = JsonHelper.GetCostumeUnlockDataFromUserData(user, Logger);
List<List<uint>> costumeUnlockData =
[user.UnlockedKigurumi, user.UnlockedHead, user.UnlockedBody, user.UnlockedFace, user.UnlockedPuchi];
var unlockedTitle = JsonHelper.GetUIntArrayFromJson(user.TitleFlgArray, 0, Logger, nameof(user.TitleFlgArray))
var unlockedTitle = user.TitleFlgArray
.ToList();
for (var i = 0; i < 5; i++)
@ -125,19 +125,10 @@ public class UserSettingsController : BaseController<UserSettingsController>
user.CostumeData = JsonSerializer.Serialize(costumes);
// If a locked tone is selected, unlock it
uint[] toneFlg = { 0u };
try
{
toneFlg = JsonSerializer.Deserialize<uint[]>(user.ToneFlgArray)!;
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing tone flg json data failed");
}
toneFlg.ThrowIfNull("Tone flg should never be null!");
toneFlg = toneFlg.Append(0u).Append(userSetting.ToneId).Distinct().ToArray();
var toneFlg = user.ToneFlgArray;
toneFlg = toneFlg.Append(0u).Append(userSetting.ToneId).Distinct().ToList();
user.ToneFlgArray = JsonSerializer.Serialize(toneFlg);
user.ToneFlgArray = toneFlg;
await userDatumService.UpdateUserDatum(user);

View File

@ -1,6 +1,4 @@
using SharedProject.Models.Requests;
namespace TaikoLocalServer.Controllers.Api;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]

View File

@ -3,6 +3,10 @@
public abstract class BaseController<T> : ControllerBase where T : BaseController<T>
{
private ILogger<T>? logger;
private ISender? mediator;
protected ISender Mediator => (mediator ??= HttpContext.RequestServices.GetService<ISender>()) ?? throw new InvalidOperationException();
protected ILogger<T> Logger => (logger ??= HttpContext.RequestServices.GetService<ILogger<T>>()) ?? throw new InvalidOperationException();
}

View File

@ -1,49 +1,36 @@
using System.Text.Json;
using GameDatabase.Entities;
using Throw;
using TaikoLocalServer.Handlers;
using AddTokenCountRequestMapper = TaikoLocalServer.Mappers.AddTokenCountRequestMapper;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/addtokencount_7547j3o4.php")]
[ApiController]
public class AddTokenCountController : BaseController<AddTokenCountController>
{
private readonly IUserDatumService userDatumService;
public AddTokenCountController(IUserDatumService userDatumService)
{
this.userDatumService = userDatumService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/addtokencount_7547j3o4.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> AddTokenCount([FromBody] AddTokenCountRequest request)
{
Logger.LogInformation("AddTokenCount request : {Request}", request.Stringify());
Logger.LogInformation("[3906] AddTokenCount request : {Request}", request.Stringify());
var user = await userDatumService.GetFirstUserDatumOrNull(request.Baid);
user.ThrowIfNull($"User with baid {request.Baid} does not exist!");
var command = new AddTokenCountCommand(AddTokenCountRequestMapper.Map(request));
await Mediator.Send(command);
foreach (var addTokenCountData in request.AryAddTokenCountDatas)
var response = new AddTokenCountResponse
{
var tokenId = addTokenCountData.TokenId;
var addTokenCount = addTokenCountData.AddTokenCount;
var token = user.Tokens.FirstOrDefault(t => t.Id == tokenId);
if (token != null)
{
token.Count += addTokenCount;
}
else
{
user.Tokens.Add(new Token
{
Id = (int)tokenId,
Count = addTokenCount
});
}
}
Result = 1
};
await userDatumService.UpdateUserDatum(user);
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/addtokencount.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> AddTokenCount3209([FromBody] Models.v3209.AddTokenCountRequest request)
{
Logger.LogInformation("[3209] AddTokenCount request : {Request}", request.Stringify());
var command = new AddTokenCountCommand(AddTokenCountRequestMapper.Map(request));
await Mediator.Send(command);
var response = new AddTokenCountResponse
{

View File

@ -1,200 +1,66 @@
using GameDatabase.Entities;
using System.Text.Json;
using Throw;
using TaikoLocalServer.Handlers;
namespace TaikoLocalServer.Controllers.Game;
[ApiController]
[Route("/v12r08_ww/chassis/baidcheck_dcfxit1u.php")]
public class BaidController : BaseController<BaidController>
{
private readonly IUserDatumService userDatumService;
private readonly ICardService cardService;
private readonly ISongBestDatumService songBestDatumService;
private readonly IDanScoreDatumService danScoreDatumService;
private readonly IAiDatumService aiDatumService;
private readonly IGameDataService gameDataService;
public BaidController(IUserDatumService userDatumService, ICardService cardService,
ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService, IAiDatumService aiDatumService,
IGameDataService gameDataService)
{
this.userDatumService = userDatumService;
this.cardService = cardService;
this.songBestDatumService = songBestDatumService;
this.danScoreDatumService = danScoreDatumService;
this.aiDatumService = aiDatumService;
this.gameDataService = gameDataService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/baidcheck_dcfxit1u.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetBaid([FromBody] BAIDRequest request)
{
Logger.LogInformation("Baid request: {Request}", request.Stringify());
var commonResponse = await Mediator.Send(new BaidQuery(request.AccessCode));
BAIDResponse response;
var card = await cardService.GetCardByAccessCode(request.AccessCode);
if (card is null)
if (commonResponse.IsNewUser)
{
Logger.LogInformation("New user with access code {AccessCode}", request.AccessCode);
var newId = cardService.GetNextBaid();
response = new BAIDResponse
{
Result = 1,
PlayerType = 1,
Baid = newId,
Baid = commonResponse.Baid,
};
return Ok(response);
}
var baid = card.Baid;
var userData = await userDatumService.GetFirstUserDatumOrDefault(baid);
var songBestData = await songBestDatumService.GetAllSongBestData(baid);
var achievementDisplayDifficulty = userData.AchievementDisplayDifficulty;
if (userData.AchievementDisplayDifficulty == Difficulty.None)
{
achievementDisplayDifficulty = songBestData.Any(datum => datum.BestCrown >= CrownType.Clear) ?
songBestData.Where(datum => datum.BestCrown >= CrownType.Clear).Max(datum => datum.Difficulty) :
Difficulty.Easy;
}
var songCountData = songBestData.Where(datum => achievementDisplayDifficulty != Difficulty.UraOni ?
datum.Difficulty == achievementDisplayDifficulty :
datum.Difficulty is Difficulty.Oni or Difficulty.UraOni).ToList();
var crownCount = CalculateCrownCount(songCountData);
var scoreRankCount = CalculateScoreRankCount(songCountData);
var costumeData = JsonHelper.GetCostumeDataFromUserData(userData, Logger);
var costumeArrays = JsonHelper.GetCostumeUnlockDataFromUserData(userData, Logger);
var costumeFlagArrays = gameDataService.GetCostumeFlagArraySizes()
.Select((size, index) => FlagCalculator.GetBitArrayFromIds(costumeArrays[index], size, Logger))
.ToList();
var danData = await danScoreDatumService.GetDanScoreDataList(baid, DanType.Normal);
var maxDan = danData.Where(datum => datum.ClearState != DanClearState.NotClear)
.Select(datum => datum.DanId)
.DefaultIfEmpty()
.Max();
var danDataDictionary = gameDataService.GetDanDataDictionary();
var danIdList = danDataDictionary.Keys.ToList();
var gotDanFlagArray = FlagCalculator.ComputeGotDanFlags(danData, danIdList);
var gaidenData = await danScoreDatumService.GetDanScoreDataList(baid, DanType.Gaiden);
var gaidenDataDictionary = gameDataService.GetGaidenDataDictionary();
var gaidenIdList = gaidenDataDictionary.Keys.ToList();
var gotGaidenFlagArray = FlagCalculator.ComputeGotDanFlags(gaidenData, gaidenIdList);
var genericInfoFlg = Array.Empty<uint>();
try
{
genericInfoFlg = JsonSerializer.Deserialize<uint[]>(userData.GenericInfoFlgArray);
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing genericinfo flg json data failed");
}
// The only way to get a null is provide string "null" as input,
// which means database content need to be fixed, so better throw
genericInfoFlg.ThrowIfNull("Genericinfo flg should never be null!");
var genericInfoFlgLength = genericInfoFlg.Any() ? genericInfoFlg.Max() + 1 : 0;
var genericInfoFlgArray = FlagCalculator.GetBitArrayFromIds(genericInfoFlg, (int)genericInfoFlgLength, Logger);
var aiRank = (uint)(userData.AiWinCount / 10);
if (aiRank > 11)
{
aiRank = 11;
}
response = new BAIDResponse
{
Result = 1,
PlayerType = 0,
Baid = baid,
MydonName = userData.MyDonName,
MydonNameLanguage = userData.MyDonNameLanguage,
Title = userData.Title,
TitleplateId = userData.TitlePlateId,
ColorFace = userData.ColorFace,
ColorBody = userData.ColorBody,
ColorLimb = userData.ColorLimb,
AryCostumedata = new BAIDResponse.CostumeData
{
Costume1 = costumeData[0],
Costume2 = costumeData[1],
Costume3 = costumeData[2],
Costume4 = costumeData[3],
Costume5 = costumeData[4]
},
CostumeFlg1 = costumeFlagArrays[0],
CostumeFlg2 = costumeFlagArrays[1],
CostumeFlg3 = costumeFlagArrays[2],
CostumeFlg4 = costumeFlagArrays[3],
CostumeFlg5 = costumeFlagArrays[4],
LastPlayDatetime = userData.LastPlayDatetime.ToString(Constants.DATE_TIME_FORMAT),
IsDispDanOn = userData.DisplayDan,
GotDanMax = maxDan,
GotDanFlg = gotDanFlagArray,
GotDanextraFlg = gotGaidenFlagArray,
DefaultToneSetting = userData.SelectedToneId,
GenericInfoFlg = genericInfoFlgArray,
AryCrownCounts = crownCount,
AryScoreRankCounts = scoreRankCount,
IsDispAchievementOn = userData.DisplayAchievement,
DispAchievementType = (uint)achievementDisplayDifficulty,
IsDispAchievementTypeSet = true,
LastPlayMode = userData.LastPlayMode,
IsDispSouuchiOn = true
};
response = Mappers.BaidResponseMapper.Map3906WithPostProcess(commonResponse);
response.PlayerType = 0;
response.IsDispAchievementTypeSet = true;
response.IsDispSouuchiOn = true;
return Ok(response);
}
private static uint[] CalculateScoreRankCount(IReadOnlyCollection<SongBestDatum> songCountData)
[HttpPost("/v12r00_cn/chassis/baidcheck.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetBaid3209([FromBody] Models.v3209.BAIDRequest request)
{
var scoreRankCount = new uint[7];
foreach (var scoreRankType in Enum.GetValues<ScoreRank>())
Logger.LogInformation("Baid request: {Request}", request.Stringify());
var commonResponse = await Mediator.Send(new BaidQuery(request.WechatQrStr));
Models.v3209.BAIDResponse response;
if (commonResponse.IsNewUser)
{
if (scoreRankType != ScoreRank.None)
Logger.LogInformation("New user with access code {AccessCode}", request.WechatQrStr);
response = new Models.v3209.BAIDResponse
{
scoreRankCount[(int)scoreRankType - 2] =
(uint)songCountData.Count(datum => datum.BestScoreRank == scoreRankType);
}
Result = 1,
PlayerType = 1,
Baid = commonResponse.Baid,
};
return Ok(response);
}
return scoreRankCount;
}
private static uint[] CalculateCrownCount(IReadOnlyCollection<SongBestDatum> songCountData)
{
var crownCount = new uint[3];
foreach (var crownType in Enum.GetValues<CrownType>())
{
if (crownType != CrownType.None)
{
crownCount[(int)crownType - 1] = (uint)songCountData.Count(datum => datum.BestCrown == crownType);
}
}
return crownCount;
response = Mappers.BaidResponseMapper.Map3209WithPostProcess(commonResponse);
response.PlayerType = 0;
response.IsDispAchievementTypeSet = true;
response.IsDispSouuchiOn = true;
return Ok(response);
}
}

View File

@ -1,14 +1,13 @@
namespace TaikoLocalServer.Controllers.Game;
[ApiController]
[Route("/v12r08_ww/chassis/bookkeeping_s4esi5un.php")]
public class BookkeepingController : BaseController<BookkeepingController>
{
[HttpPost]
[HttpPost("/v12r08_ww/chassis/bookkeeping_s4esi5un.php")]
[Produces("application/protobuf")]
public IActionResult StartupAuth([FromBody] BookKeepingRequest request)
{
Logger.LogInformation("Bookkeeping request: {Request}", request.Stringify());
Logger.LogInformation("[3906] Bookkeeping request: {Request}", request.Stringify());
var response = new BookKeepingResponse
{
Result = 1
@ -18,4 +17,15 @@ public class BookkeepingController : BaseController<BookkeepingController>
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/bookkeeping.php")]
[Produces("application/protobuf")]
public IActionResult StartupAuth3209([FromBody] Models.v3209.BookKeepingRequest request)
{
Logger.LogInformation("[3209] Bookkeeping request: {Request}", request.Stringify());
var response = new BookKeepingResponse
{
Result = 1
};
return Ok(response);
}
}

View File

@ -1,10 +1,9 @@
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/challengecompe.php")]
[ApiController]
public class ChallengeCompetitionController : BaseController<ChallengeCompetitionController>
{
[HttpPost]
[HttpPost("/v12r08_ww/chassis/challengecompe.php")]
[Produces("application/protobuf")]
public IActionResult HandleChallenge([FromBody] ChallengeCompeRequest request)
{
@ -16,6 +15,19 @@ public class ChallengeCompetitionController : BaseController<ChallengeCompetitio
};
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/challengecompe.php")]
[Produces("application/protobuf")]
public IActionResult HandleChallenge3209([FromBody] Models.v3209.ChallengeCompeRequest request)
{
Logger.LogInformation("ChallengeCompe request : {Request}", request.Stringify());
var response = new Models.v3209.ChallengeCompeResponse
{
Result = 1
};
return Ok(response);
}
}

View File

@ -3,7 +3,6 @@ using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/crownsdata_oqgqy90s.php")]
[ApiController]
public class CrownsDataController : BaseController<CrownsDataController>
{
@ -17,16 +16,50 @@ public class CrownsDataController : BaseController<CrownsDataController>
this.settings = settings.Value;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/crownsdata_oqgqy90s.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> CrownsData([FromBody] CrownsDataRequest request)
{
Logger.LogInformation("CrownsData request : {Request}", request.Stringify());
var songBestData = await songBestDatumService.GetAllSongBestData(request.Baid);
var crownData = await Handle(request.Baid);
var response = new CrownsDataResponse
{
Result = 1,
CrownFlg = crownData.CrownFlg,
DondafulCrownFlg = crownData.DondafulCrownFlg
};
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/crownsdata.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> CrownsData3209([FromBody] Models.v3209.CrownsDataRequest request)
{
Logger.LogInformation("CrownsData request : {Request}", request.Stringify());
var crownData = await Handle((uint)request.Baid);
var response = new Models.v3209.CrownsDataResponse
{
Result = 1,
CrownFlg = crownData.CrownFlg,
DondafulCrownFlg = crownData.DondafulCrownFlg
};
return Ok(response);
}
public record CrownData(byte[] CrownFlg, byte[] DondafulCrownFlg);
private async Task<CrownData> Handle(uint baid)
{
var songBestData = await songBestDatumService.GetAllSongBestData(baid);
var songIdMax = settings.EnableMoreSongs ? Constants.MUSIC_ID_MAX_EXPANDED : Constants.MUSIC_ID_MAX;
var crown = new ushort[songIdMax + 1];
var crown = new ushort[songIdMax + 1];
var dondafulCrown = new byte[songIdMax + 1];
for (var songId = 0; songId < songIdMax; songId++)
@ -34,7 +67,7 @@ public class CrownsDataController : BaseController<CrownsDataController>
var id = songId;
dondafulCrown[songId] = songBestData
// Select song of this song id with dondaful crown
.Where(datum => datum.SongId == id &&
.Where(datum => datum.SongId == id &&
datum.BestCrown == CrownType.Dondaful)
// Calculate flag according to difficulty
.Aggregate((byte)0, (flag, datum) => FlagCalculator.ComputeDondafulCrownFlag(flag, datum.Difficulty));
@ -46,14 +79,7 @@ public class CrownsDataController : BaseController<CrownsDataController>
// Calculate flag according to difficulty
.Aggregate((ushort)0, (flag, datum) => FlagCalculator.ComputeCrownFlag(flag, datum.BestCrown, datum.Difficulty));
}
var response = new CrownsDataResponse
{
Result = 1,
CrownFlg = GZipBytesUtil.GetGZipBytes(crown),
DondafulCrownFlg = GZipBytesUtil.GetGZipBytes(dondafulCrown)
};
return Ok(response);
return new CrownData(GZipBytesUtil.GetGZipBytes(crown), GZipBytesUtil.GetGZipBytes(dondafulCrown));
}
}

View File

@ -1,10 +1,9 @@
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/executeqrcode_rgowsr5m.php")]
[ApiController]
public class ExecuteQrCodeController : BaseController<ExecuteQrCodeController>
{
[HttpPost]
[HttpPost("/v12r08_ww/chassis/executeqrcode_rgowsr5m.php")]
[Produces("application/protobuf")]
public IActionResult ExecuteQrCode([FromBody] ExecuteQrcodeRequest request)
{
@ -18,4 +17,19 @@ public class ExecuteQrCodeController : BaseController<ExecuteQrCodeController>
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/executeqrcode.php")]
[Produces("application/protobuf")]
public IActionResult ExecuteQrCode3209([FromBody] Models.v3209.ExecuteQrcodeRequest request)
{
Logger.LogInformation("ExecuteQrcode request : {Request}", request.Stringify());
var response = new Models.v3209.ExecuteQrcodeResponse
{
QrcodeId = 1,
Result = 1
};
return Ok(response);
}
}

View File

@ -1,34 +1,29 @@
using Throw;
using TaikoLocalServer.Handlers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/getaidata_6x30b9nr.php")]
[ApiController]
public class GetAiDataController : BaseController<GetAiDataController>
{
private readonly IUserDatumService userDatumService;
public GetAiDataController(IUserDatumService userDatumService)
{
this.userDatumService = userDatumService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/getaidata_6x30b9nr.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetAiData([FromBody] GetAiDataRequest request)
{
Logger.LogInformation("GetAiData request : {Request}", request.Stringify());
var user = await userDatumService.GetFirstUserDatumOrNull(request.Baid);
user.ThrowIfNull($"User with baid {request.Baid} does not exist!");
var response = new GetAiDataResponse
{
Result = 1,
TotalWinnings = (uint)user.AiWinCount,
InputMedian = "1000",
InputVariance = "2000"
};
var commonResponse = await Mediator.Send(new GetAiDataQuery(request.Baid));
var response = Mappers.AiDataResponseMapper.MapTo3906(commonResponse);
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/getaidata.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetAiData3209([FromBody] Models.v3209.GetAiDataRequest request)
{
Logger.LogInformation("GetAiData request : {Request}", request.Stringify());
var commonResponse = await Mediator.Send(new GetAiDataQuery((uint)request.Baid));
var response = Mappers.AiDataResponseMapper.MapTo3209(commonResponse);
return Ok(response);
}
}

View File

@ -1,105 +1,32 @@
using Throw;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/getaiscore_lp38po4w.php")]
[ApiController]
public class GetAiScoreController : BaseController<GetAiScoreController>
{
private readonly IAiDatumService aiDatumService;
public GetAiScoreController(IAiDatumService aiDatumService)
{
this.aiDatumService = aiDatumService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/getaiscore_lp38po4w.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetAiScore([FromBody] GetAiScoreRequest request)
{
Logger.LogInformation("GetAiScore request : {Request}", request.Stringify());
var response = new GetAiScoreResponse
{
Result = 1
};
var commonResponse = await Mediator.Send(new GetAiScoreQuery(request.Baid, request.SongNo, request.Level));
var response = AiScoreMappers.MapTo3906(commonResponse);
var difficulty = (Difficulty)request.Level;
difficulty.Throw().IfOutOfRange();
return Ok(response);
}
var aiData = await aiDatumService.GetSongAiScore(request.Baid, request.SongNo, difficulty);
if (aiData is null)
{
return Ok(response);
}
[HttpPost("v12r00_cn/chassis/getaiscore.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetAiScore3209([FromBody] Models.v3209.GetAiScoreRequest request)
{
Logger.LogInformation("GetAiScore request : {Request}", request.Stringify());
for (var index = 0; index < aiData.AiSectionScoreData.Count; index++)
{
var sectionScoreDatum = aiData.AiSectionScoreData[index];
response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData
{
Crown = (uint)sectionScoreDatum.Crown,
GoodCnt = sectionScoreDatum.GoodCount,
OkCnt = sectionScoreDatum.OkCount,
NgCnt = sectionScoreDatum.MissCount,
PoundCnt = sectionScoreDatum.DrumrollCount,
Score = sectionScoreDatum.Score,
SectionNo = (uint)index
});
}
// There's either 3 or 5 total sections
// SectionNo doesn't seem to actually affect which section is being assigned to, only the List order matters
/*response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData()
{
SectionNo = 1,
Crown = (uint)CrownType.Clear,
Score = 100000,
GoodCnt = 100,
OkCnt = 50,
NgCnt = 25,
PoundCnt = 12
});
response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData()
{
SectionNo = 2,
Crown = (uint)CrownType.Gold,
Score = 100001,
GoodCnt = 101,
OkCnt = 50,
NgCnt = 25,
PoundCnt = 12
});
response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData()
{
SectionNo = 3,
Crown = (uint)CrownType.Dondaful,
Score = 100002,
GoodCnt = 102,
OkCnt = 50,
NgCnt = 25,
PoundCnt = 12
});
response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData()
{
SectionNo = 4,
Crown = (uint)CrownType.Gold,
Score = 100003,
GoodCnt = 103,
OkCnt = 50,
NgCnt = 25,
PoundCnt = 12
});
response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData()
{
SectionNo = 5,
Crown = (uint)CrownType.Clear,
Score = 100004,
GoodCnt = 104,
OkCnt = 50,
NgCnt = 25,
PoundCnt = 12
});*/
var commonResponse =
await Mediator.Send(new GetAiScoreQuery((uint)request.Baid, request.SongNo, request.Level));
var response = AiScoreMappers.MapTo3209(commonResponse);
return Ok(response);
}

View File

@ -1,12 +1,10 @@
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/getapplicationurl.php")]
[ApiController]
public class GetApplicationUrlController : BaseController<GetApplicationUrlController>
{
private const string APPLICATION_URL = "vsapi.taiko-p.jp";
[HttpPost]
[HttpPost("/v12r08_ww/chassis/getapplicationurl.php")]
[Produces("application/protobuf")]
public IActionResult GetApplicationUrl([FromBody] GetApplicationUrlRequest request)
{
@ -15,9 +13,25 @@ public class GetApplicationUrlController : BaseController<GetApplicationUrlContr
var response = new GetApplicationUrlResponse
{
Result = 1,
ApplicationUrl = APPLICATION_URL
ApplicationUrl = $"{HttpContext.Request.Host.Value}/app"
};
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/getapplicationurl.php")]
[Produces("application/protobuf")]
public IActionResult GetApplicationUrl3209([FromBody] Models.v3209.GetApplicationUrlRequest request)
{
Logger.LogInformation("GetApplicationUrl request : {Request}", request.Stringify());
var response = new Models.v3209.GetApplicationUrlResponse
{
Result = 1,
ApplicationUrl = $"{HttpContext.Request.Host.Value}/app"
};
return Ok(response);
}
}

View File

@ -1,19 +1,14 @@
namespace TaikoLocalServer.Controllers.Game;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/getdanodai_ela9zu1a.php")]
[ApiController]
public class GetDanOdaiController : BaseController<GetDanOdaiController>
{
private readonly IGameDataService gameDataService;
public GetDanOdaiController(IGameDataService gameDataService)
{
this.gameDataService = gameDataService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/getdanodai_ela9zu1a.php")]
[Produces("application/protobuf")]
public IActionResult GetDanOdai([FromBody] GetDanOdaiRequest request)
public async Task<IActionResult> GetDanOdai([FromBody] GetDanOdaiRequest request)
{
Logger.LogInformation("GetDanOdai request : {Request}", request.Stringify());
@ -22,34 +17,25 @@ public class GetDanOdaiController : BaseController<GetDanOdaiController>
Result = 1
};
if (request.Type == 1)
{
foreach (var danId in request.DanIds)
{
gameDataService.GetDanDataDictionary().TryGetValue(danId, out var odaiData);
if (odaiData is null)
{
Logger.LogWarning("Requested dan id {Id} does not exist!", danId);
continue;
}
var odaiDataList = await Mediator.Send(new GetDanOdaiQuery(request.DanIds, request.Type));
response.AryOdaiDatas.AddRange(odaiDataList.Select(DanDataMappers.To3906OdaiData));
response.AryOdaiDatas.Add(odaiData);
}
}
else if (request.Type == 2)
{
foreach (var danId in request.DanIds)
{
gameDataService.GetGaidenDataDictionary().TryGetValue(danId, out var odaiData);
if (odaiData is null)
{
Logger.LogWarning("Requested dan id {Id} does not exist!", danId);
continue;
}
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/getdanodai.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetDanOdai3209([FromBody] Models.v3209.GetDanOdaiRequest request)
{
Logger.LogInformation("GetDanOdai request : {Request}", request.Stringify());
response.AryOdaiDatas.Add(odaiData);
}
}
var response = new Models.v3209.GetDanOdaiResponse
{
Result = 1
};
var odaiDataList = await Mediator.Send(new GetDanOdaiQuery(request.DanIds, request.Type));
response.AryOdaiDatas.AddRange(odaiDataList.Select(DanDataMappers.To3209OdaiData));
return Ok(response);
}

View File

@ -1,52 +1,31 @@
using GameDatabase.Entities;
using Throw;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/getdanscore_frqhg7q6.php")]
[ApiController]
public class GetDanScoreController : BaseController<GetDanScoreController>
{
private readonly IDanScoreDatumService danScoreDatumService;
public GetDanScoreController(IDanScoreDatumService danScoreDatumService)
{
this.danScoreDatumService = danScoreDatumService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/getdanscore_frqhg7q6.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetDanScore([FromBody] GetDanScoreRequest request)
{
Logger.LogInformation("GetDanScore request : {Request}", request.Stringify());
var response = new GetDanScoreResponse
{
Result = 1
};
var commonResponse = await Mediator.Send(new GetDanScoreQuery(request.Baid, request.Type, request.DanIds));
var response = DanScoreMappers.MapTo3906(commonResponse);
var danType = (DanType)request.Type;
danType.Throw().IfOutOfRange();
var danScoreData = await danScoreDatumService.GetDanScoreDataList(request.Baid, danType);
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/getdanscore.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetDanScore3209([FromBody] Models.v3209.GetDanScoreRequest request)
{
Logger.LogInformation("GetDanScore3209 request : {Request}", request.Stringify());
foreach (var danId in request.DanIds)
{
var datum = danScoreData.FirstOrDefault(scoreDatum => scoreDatum.DanId == danId, new DanScoreDatum());
var responseData = new GetDanScoreResponse.DanScoreData
{
DanId = danId,
ArrivalSongCnt = datum.ArrivalSongCount,
ComboCntTotal = datum.ComboCountTotal,
SoulGaugeTotal = datum.SoulGaugeTotal
};
foreach (var stageScoreDatum in datum.DanStageScoreData)
{
responseData.AryDanScoreDataStages.Add(ObjectMappers.DanStageDbToResponseMap.Apply(stageScoreDatum));
}
response.AryDanScoreDatas.Add(responseData);
}
var commonResponse = await Mediator.Send(new GetDanScoreQuery((uint)request.Baid, request.Type, request.DanIds));
var response = DanScoreMappers.MapTo3209(commonResponse);
return Ok(response);
}

View File

@ -1,38 +1,28 @@
namespace TaikoLocalServer.Controllers.Game;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/getfolder_rffj346i.php")]
[ApiController]
public class GetFolderController : BaseController<GetFolderController>
{
private readonly IGameDataService gameDataService;
public GetFolderController(IGameDataService gameDataService)
{
this.gameDataService = gameDataService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/getfolder_rffj346i.php")]
[Produces("application/protobuf")]
public IActionResult GetFolder([FromBody] GetfolderRequest request)
public async Task<IActionResult> GetFolder([FromBody] GetfolderRequest request)
{
Logger.LogInformation("GetFolder request : {Request}", request.Stringify());
var response = new GetfolderResponse
{
Result = 1
};
foreach (var folderId in request.FolderIds)
{
gameDataService.GetFolderDictionary().TryGetValue(folderId, out var folderData);
if (folderData is null)
{
Logger.LogWarning("Requested folder id {Id} does not exist!", folderId);
continue;
}
response.AryEventfolderDatas.Add(folderData);
}
var commonResponse = await Mediator.Send(new GetFolderQuery(request.FolderIds));
var response = FolderDataMappers.MapTo3906(commonResponse);
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/getfolder.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetFolder([FromBody] Models.v3209.GetfolderRequest request)
{
Logger.LogInformation("GetFolder3209 request : {Request}", request.Stringify());
var commonResponse = await Mediator.Send(new GetFolderQuery(request.FolderIds));
var response = FolderDataMappers.MapTo3209(commonResponse);
return Ok(response);
}
}

View File

@ -1,10 +1,9 @@
namespace TaikoLocalServer.Controllers.Game;
[ApiController]
[Route("/v12r08_ww/chassis/getgenericmaster_ts8om3qd.php")]
public class GetGenericMasterController : BaseController<GetGenericMasterController>
{
[HttpPost]
[HttpPost("/v12r08_ww/chassis/getgenericmaster_ts8om3qd.php")]
[Produces("application/protobuf")]
public IActionResult GetGenericMaster([FromBody] GetGenericMasterRequest request)
{
@ -17,6 +16,21 @@ public class GetGenericMasterController : BaseController<GetGenericMasterControl
EnableIdBit = FlagCalculator.GetBitArrayTrue(5000)
};
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/getgenericmaster.php")]
[Produces("application/protobuf")]
public IActionResult GetGenericMaster([FromBody] Models.v3209.GetGenericMasterRequest request)
{
Logger.LogInformation("GetGenericMaster3209Request: {Request}", request.Stringify());
var response = new Models.v3209.GetGenericMasterResponse
{
Result = 1,
VerupNo = 2,
EnableIdBit = FlagCalculator.GetBitArrayTrue(5000)
};
return Ok(response);
}

View File

@ -3,36 +3,63 @@ using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/getscorerank_1c8l7y61.php")]
[ApiController]
public class GetScoreRankController : BaseController<GetScoreRankController>
public class GetScoreRankController(ISongBestDatumService songBestDatumService, IOptions<ServerSettings> settings)
: BaseController<GetScoreRankController>
{
private readonly ISongBestDatumService songBestDatumService;
private readonly ServerSettings settings = settings.Value;
private readonly ServerSettings settings;
public GetScoreRankController(ISongBestDatumService songBestDatumService, IOptions<ServerSettings> settings)
{
this.songBestDatumService = songBestDatumService;
this.settings = settings.Value;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/getscorerank_1c8l7y61.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetScoreRank([FromBody] GetScoreRankRequest request)
{
Logger.LogInformation("GetScoreRank request : {Request}", request.Stringify());
var scoreRankData = await Handle(request.Baid);
var response = new GetScoreRankResponse
{
Result = 1,
IkiScoreRankFlg = scoreRankData.IkiScoreRankFlg,
KiwamiScoreRankFlg = scoreRankData.KiwamiScoreRankFlg,
MiyabiScoreRankFlg = scoreRankData.MiyabiScoreRankFlg
};
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/getscorerank.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetScoreRank3209([FromBody] Models.v3209.GetScoreRankRequest request)
{
Logger.LogInformation("GetScoreRank request : {Request}", request.Stringify());
var scoreRankData = await Handle((uint)request.Baid);
var response = new Models.v3209.GetScoreRankResponse
{
Result = 1,
IkiScoreRankFlg = scoreRankData.IkiScoreRankFlg,
KiwamiScoreRankFlg = scoreRankData.KiwamiScoreRankFlg,
MiyabiScoreRankFlg = scoreRankData.MiyabiScoreRankFlg
};
return Ok(response);
}
public record ScoreRankData(byte[] IkiScoreRankFlg, byte[] KiwamiScoreRankFlg, byte[] MiyabiScoreRankFlg);
private async Task<ScoreRankData> Handle(uint baid)
{
var songIdMax = settings.EnableMoreSongs ? Constants.MUSIC_ID_MAX_EXPANDED : Constants.MUSIC_ID_MAX;
var kiwamiScores = new byte[songIdMax + 1];
var kiwamiScores = new byte[songIdMax + 1];
var miyabiScores = new ushort[songIdMax + 1];
var ikiScores = new ushort[songIdMax + 1];
var songBestData = await songBestDatumService.GetAllSongBestData(request.Baid);
var ikiScores = new ushort[songIdMax + 1];
var songBestData = await songBestDatumService.GetAllSongBestData(baid);
for (var songId = 0; songId < songIdMax; songId++)
{
var id = songId;
kiwamiScores[songId] = songBestData
.Where(datum => datum.SongId == id &&
.Where(datum => datum.SongId == id &&
datum.BestScoreRank == ScoreRank.Dondaful)
.Aggregate((byte)0, (flag, datum) => FlagCalculator.ComputeKiwamiScoreRankFlag(flag, datum.Difficulty));
@ -46,14 +73,11 @@ public class GetScoreRankController : BaseController<GetScoreRankController>
datum.BestScoreRank is ScoreRank.Gold or ScoreRank.Purple or ScoreRank.Sakura)
.Aggregate((ushort)0, (flag, datum) => FlagCalculator.ComputeMiyabiOrIkiScoreRank(flag, datum.BestScoreRank, datum.Difficulty));
}
var response = new GetScoreRankResponse
{
Result = 1,
IkiScoreRankFlg = GZipBytesUtil.GetGZipBytes(ikiScores),
KiwamiScoreRankFlg = GZipBytesUtil.GetGZipBytes(kiwamiScores),
MiyabiScoreRankFlg = GZipBytesUtil.GetGZipBytes(miyabiScores)
};
return Ok(response);
return new ScoreRankData(
GZipBytesUtil.GetGZipBytes(ikiScores),
GZipBytesUtil.GetGZipBytes(kiwamiScores),
GZipBytesUtil.GetGZipBytes(miyabiScores)
);
}
}

View File

@ -1,34 +1,31 @@
namespace TaikoLocalServer.Controllers.Game;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/getshopfolder_w4xik0uw.php")]
[ApiController]
public class GetShopFolderController : BaseController<GetShopFolderController>
{
private readonly IGameDataService gameDataService;
public GetShopFolderController(IGameDataService gameDataService)
{
this.gameDataService = gameDataService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/getshopfolder_w4xik0uw.php")]
[Produces("application/protobuf")]
public IActionResult GetShopFolder([FromBody] GetShopFolderRequest request)
public async Task<IActionResult> GetShopFolder([FromBody] GetShopFolderRequest request)
{
Logger.LogInformation("GetShopFolder request : {Request}", request.Stringify());
gameDataService.GetTokenDataDictionary().TryGetValue("shopTokenId", out var shopTokenId);
var commonResponse = await Mediator.Send(new GetShopFolderQuery());
var response = ShopFolderDataMappers.MapTo3906(commonResponse);
var shopFolderList = gameDataService.GetShopFolderList();
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/getshopfolder.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetShopFolder3209([FromBody] Models.v3209.GetShopFolderRequest request)
{
Logger.LogInformation("GetShopFolder request : {Request}", request.Stringify());
var response = new GetShopFolderResponse
{
Result = 1,
TokenId = shopTokenId > 0 ? (uint)shopTokenId : 1,
VerupNo = 2
};
response.AryShopFolderDatas.AddRange(shopFolderList);
var commonResponse = await Mediator.Send(new GetShopFolderQuery());
var response = ShopFolderDataMappers.MapTo3209(commonResponse);
return Ok(response);
}

View File

@ -1,6 +1,8 @@
namespace TaikoLocalServer.Controllers.Game;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/getsongintroduction_66blw6is.php")]
[ApiController]
public class GetSongIntroductionController : BaseController<GetSongIntroductionController>
{
@ -11,29 +13,27 @@ public class GetSongIntroductionController : BaseController<GetSongIntroductionC
this.gameDataService = gameDataService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/getsongintroduction_66blw6is.php")]
[Produces("application/protobuf")]
public IActionResult GetSongIntroduction([FromBody] GetSongIntroductionRequest request)
public async Task<IActionResult> GetSongIntroduction([FromBody] GetSongIntroductionRequest request)
{
Logger.LogInformation("GetSongIntroduction request : {Request}", request.Stringify());
var response = new GetSongIntroductionResponse
{
Result = 1
};
foreach (var setId in request.SetIds)
{
gameDataService.GetSongIntroDictionary().TryGetValue(setId, out var introData);
if (introData is null)
{
Logger.LogWarning("Requested set id {Id} does not exist!", setId);
continue;
}
response.ArySongIntroductionDatas.Add(introData);
}
var commonResponse = await Mediator.Send(new GetSongIntroductionQuery(request.SetIds));
var response = SongIntroductionDataMappers.MapTo3906(commonResponse);
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/getsongintroduction.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetSongIntroduction3209([FromBody] Models.v3209.GetSongIntroductionRequest request)
{
Logger.LogInformation("GetSongIntroduction request : {Request}", request.Stringify());
var commonResponse = await Mediator.Send(new GetSongIntroductionQuery(request.SetIds));
var response = SongIntroductionDataMappers.MapTo3209(commonResponse);
return Ok(response);
}
}

View File

@ -1,10 +1,9 @@
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/gettelop_o0cb2z3e.php")]
[ApiController]
public class GetTelopController : BaseController<GetTelopController>
{
[HttpPost]
[HttpPost("/v12r08_ww/chassis/gettelop_o0cb2z3e.php")]
[Produces("application/protobuf")]
public IActionResult GetTelop([FromBody] GettelopRequest request)
{
@ -18,7 +17,28 @@ public class GetTelopController : BaseController<GetTelopController>
Result = 1,
StartDatetime = startDateTime.ToString(Constants.DATE_TIME_FORMAT),
EndDatetime = endDateTime.ToString(Constants.DATE_TIME_FORMAT),
Telop = "Hello world",
Telop = "Hello 3906",
VerupNo = 1
};
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/gettelop.php")]
[Produces("application/protobuf")]
public IActionResult GetTelop3209([FromBody] Models.v3209.GettelopRequest request)
{
Logger.LogInformation("GetTelop request : {Request}", request.Stringify());
var startDateTime = DateTime.Now - TimeSpan.FromDays(999.0);
var endDateTime = DateTime.Now + TimeSpan.FromDays(999.0);
var response = new Models.v3209.GettelopResponse
{
Result = 1,
StartDatetime = startDateTime.ToString(Constants.DATE_TIME_FORMAT),
EndDatetime = endDateTime.ToString(Constants.DATE_TIME_FORMAT),
Telop = "Hello 3209",
VerupNo = 1
};

View File

@ -1,92 +1,31 @@
using System.Text.Json;
using GameDatabase.Entities;
using Throw;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/gettokencount_iut9g23g.php")]
[ApiController]
public class GetTokenCountController : BaseController<GetTokenCountController>
{
private readonly IGameDataService gameDataService;
private readonly IUserDatumService userDatumService;
public GetTokenCountController(IUserDatumService userDatumService, IGameDataService gameDataService)
{
this.userDatumService = userDatumService;
this.gameDataService = gameDataService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/gettokencount_iut9g23g.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetTokenCount([FromBody] GetTokenCountRequest request)
{
Logger.LogInformation("GetTokenCount request : {Request}", request.Stringify());
var user = await userDatumService.GetFirstUserDatumOrNull(request.Baid);
user.ThrowIfNull($"User with baid {request.Baid} does not exist!");
var commonResponse = await Mediator.Send(new GetTokenCountQuery(request.Baid));
var response = TokenCountDataMappers.MapTo3906(commonResponse);
var tokenDataDictionary = gameDataService.GetTokenDataDictionary();
tokenDataDictionary.TryGetValue("shopTokenId", out var shopTokenId);
tokenDataDictionary.TryGetValue("kaTokenId", out var kaTokenId);
tokenDataDictionary.TryGetValue("onePieceTokenId", out var onePieceTokenId);
tokenDataDictionary.TryGetValue("soshinaTokenId", out var soshinaTokenId);
tokenDataDictionary.TryGetValue("Yatsushika1TokenId", out var yatsushika1TokenId);
tokenDataDictionary.TryGetValue("Yatsushika2TokenId", out var yatsushika2TokenId);
tokenDataDictionary.TryGetValue("Yatsushika3TokenId", out var yatsushika3TokenId);
tokenDataDictionary.TryGetValue("Yatsushika4TokenId", out var yatsushika4TokenId);
tokenDataDictionary.TryGetValue("MaskedKid1TokenId", out var maskedKid1TokenId);
tokenDataDictionary.TryGetValue("MaskedKid2TokenId", out var maskedKid2TokenId);
tokenDataDictionary.TryGetValue("MaskedKid3TokenId", out var maskedKid3TokenId);
tokenDataDictionary.TryGetValue("MaskedKid4TokenId", out var maskedKid4TokenId);
tokenDataDictionary.TryGetValue("Kiyoshi1TokenId", out var kiyoshi1TokenId);
tokenDataDictionary.TryGetValue("Kiyoshi2TokenId", out var kiyoshi2TokenId);
tokenDataDictionary.TryGetValue("Kiyoshi3TokenId", out var kiyoshi3TokenId);
tokenDataDictionary.TryGetValue("Kiyoshi4TokenId", out var kiyoshi4TokenId);
tokenDataDictionary.TryGetValue("Amitie1TokenId", out var amitie1TokenId);
tokenDataDictionary.TryGetValue("Amitie2TokenId", out var amitie2TokenId);
tokenDataDictionary.TryGetValue("Amitie3TokenId", out var amitie3TokenId);
tokenDataDictionary.TryGetValue("Amitie4TokenId", out var amitie4TokenId);
tokenDataDictionary.TryGetValue("Machina1TokenId", out var machina1TokenId);
tokenDataDictionary.TryGetValue("Machina2TokenId", out var machina2TokenId);
tokenDataDictionary.TryGetValue("Machina3TokenId", out var machina3TokenId);
tokenDataDictionary.TryGetValue("Machina4TokenId", out var machina4TokenId);
return Ok(response);
}
[HttpPost("v12r00_cn/chassis/gettokencount.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetTokenCount3209([FromBody] Models.v3209.GetTokenCountRequest request)
{
Logger.LogInformation("GetTokenCount request : {Request}", request.Stringify());
int[] tokenDataIdArray =
{
shopTokenId, kaTokenId, onePieceTokenId, soshinaTokenId, yatsushika1TokenId, yatsushika2TokenId,
yatsushika3TokenId, yatsushika4TokenId, maskedKid1TokenId, maskedKid2TokenId, maskedKid3TokenId,
maskedKid4TokenId, kiyoshi1TokenId, kiyoshi2TokenId, kiyoshi3TokenId, kiyoshi4TokenId, amitie1TokenId,
amitie2TokenId, amitie3TokenId, amitie4TokenId, machina1TokenId, machina2TokenId, machina3TokenId,
machina4TokenId
};
var response = new GetTokenCountResponse
{
Result = 1
};
foreach (var tokenDataId in tokenDataIdArray)
{
if (tokenDataId <= 0) continue;
var castedTokenDataId = (uint)tokenDataId;
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 = tokenCount,
TokenId = castedTokenDataId
});
}
await userDatumService.UpdateUserDatum(user);
var commonResponse = await Mediator.Send(new GetTokenCountQuery((uint)request.Baid));
var response = TokenCountDataMappers.MapTo3209(commonResponse);
return Ok(response);
}

View File

@ -0,0 +1,19 @@
using TaikoLocalServer.Models.v3209;
namespace TaikoLocalServer.Controllers.Game;
[ApiController]
public class HeadClerk2Controller : BaseController<HeadClerk2Controller>
{
[HttpPost("/v12r00_cn/chassis/headclerk2.php")]
[Produces("application/protobuf")]
public IActionResult UploadHeadClert2([FromBody] HeadClerk2Request request)
{
Logger.LogInformation("HeadClerk2 request : {Request}", request.Stringify());
var response = new HeadClerk2Response
{
Result = 1
};
return Ok(response);
}
}

View File

@ -1,10 +1,9 @@
namespace TaikoLocalServer.Controllers.Game;
[ApiController]
[Route("/v12r08_ww/chassis/heartbeat_hcv5akgr.php")]
public class HeartbeatController : BaseController<HeartbeatController>
{
[HttpPost]
[HttpPost("/v12r08_ww/chassis/heartbeat_hcv5akgr.php")]
[Produces("application/protobuf")]
public IActionResult HeartBeat([FromBody] HeartBeatRequest request)
{
@ -18,4 +17,18 @@ public class HeartbeatController : BaseController<HeartbeatController>
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/heartbeat.php")]
[Produces("application/protobuf")]
public IActionResult HeartBeat3209([FromBody] Models.v3209.HeartBeatRequest request)
{
Logger.LogInformation("Heartbeat request: {Request}", request.Stringify());
var response = new Models.v3209.HeartBeatResponse
{
Result = 1,
GameSvrStat = 1
};
return Ok(response);
}
}

View File

@ -1,139 +1,31 @@
using Microsoft.Extensions.Options;
using TaikoLocalServer.Settings;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[ApiController]
[Route("/v12r08_ww/chassis/initialdatacheck_vaosv643.php")]
public class InitialDataCheckController : BaseController<InitialDataCheckController>
{
private readonly IGameDataService gameDataService;
private readonly ServerSettings settings;
public InitialDataCheckController(IGameDataService gameDataService, IOptions<ServerSettings> settings)
{
this.gameDataService = gameDataService;
this.settings = settings.Value;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/initialdatacheck_vaosv643.php")]
[Produces("application/protobuf")]
public IActionResult InitialDataCheck([FromBody] InitialdatacheckRequest request)
public async Task<IActionResult> InitialDataCheck([FromBody] InitialdatacheckRequest request)
{
Logger.LogInformation("Initial data check request: {Request}", request.Stringify());
var songIdMax = settings.EnableMoreSongs ? Constants.MUSIC_ID_MAX_EXPANDED : Constants.MUSIC_ID_MAX;
var commonResponse = await Mediator.Send(new GetInitialDataQuery());
var response = InitialDataMappers.MapTo3906(commonResponse);
var musicList = gameDataService.GetMusicList();
var lockedSongsList = gameDataService.GetLockedSongsList();
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/initialdatacheck.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> InitialDataCheckCN([FromBody] Models.v3209.InitialdatacheckRequest request)
{
Logger.LogInformation("Initial data check request: {Request}", request.Stringify());
var enabledArray =
FlagCalculator.GetBitArrayFromIds(musicList, songIdMax, Logger);
var defaultSongList = musicList.Except(lockedSongsList);
var defaultSongFlg =
FlagCalculator.GetBitArrayFromIds(defaultSongList, songIdMax, Logger);
var defaultSongWithUraList = gameDataService.GetMusicWithUraList();
var uraReleaseBit =
FlagCalculator.GetBitArrayFromIds(defaultSongWithUraList, songIdMax, Logger);
var response = new InitialdatacheckResponse
{
Result = 1,
DefaultSongFlg = defaultSongFlg,
AchievementSongBit = enabledArray,
UraReleaseBit = uraReleaseBit,
SongIntroductionEndDatetime = DateTime.Now.AddYears(10).ToString(Constants.DATE_TIME_FORMAT),
};
var movieDataDictionary = gameDataService.GetMovieDataDictionary();
foreach (var movieData in movieDataDictionary) response.AryMovieInfoes.Add(movieData.Value);
var verupNo1 = new uint[] { 2, 3, 4, 5, 6, 7, 8, 13, 15, 24, 25, 26, 27, 28, 29, 30, 31, 104 };
var aryVerUp = verupNo1.Select(i => new InitialdatacheckResponse.VerupNoData1
{
MasterType = i,
VerupNo = 1
})
.ToList();
response.AryVerupNoData1s.AddRange(aryVerUp);
var danData = new List<InitialdatacheckResponse.VerupNoData2.InformationData>();
var danDataDictionary = gameDataService.GetDanDataDictionary();
foreach (var danId in danDataDictionary.Keys)
{
gameDataService.GetDanDataDictionary().TryGetValue(danId, out var odaiData);
danData.Add(new InitialdatacheckResponse.VerupNoData2.InformationData
{
InfoId = danId,
VerupNo = odaiData?.VerupNo ?? 1
});
}
var verUp2Type101 = new InitialdatacheckResponse.VerupNoData2
{
MasterType = 101,
};
verUp2Type101.AryInformationDatas.AddRange(danData);
response.AryVerupNoData2s.Add(verUp2Type101);
var gaidenData = new List<InitialdatacheckResponse.VerupNoData2.InformationData>();
var gaidenDataDictionary = gameDataService.GetGaidenDataDictionary();
foreach (var gaidenId in gaidenDataDictionary.Keys)
{
gaidenDataDictionary.TryGetValue(gaidenId, out var odaiData);
gaidenData.Add(new InitialdatacheckResponse.VerupNoData2.InformationData
{
InfoId = gaidenId,
VerupNo = odaiData?.VerupNo ?? 1
});
}
var verUp2Type102 = new InitialdatacheckResponse.VerupNoData2
{
MasterType = 102,
};
verUp2Type102.AryInformationDatas.AddRange(gaidenData);
response.AryVerupNoData2s.Add(verUp2Type102);
var eventFolderData = new List<InitialdatacheckResponse.VerupNoData2.InformationData>();
var eventFolderDictionary = gameDataService.GetFolderDictionary();
foreach (var folderId in eventFolderDictionary.Keys)
{
eventFolderDictionary.TryGetValue(folderId, out var folderData);
eventFolderData.Add(new InitialdatacheckResponse.VerupNoData2.InformationData
{
InfoId = folderId,
VerupNo = folderData?.VerupNo ?? 1
});
}
var verUp2Type103 = new InitialdatacheckResponse.VerupNoData2
{
MasterType = 103,
};
verUp2Type103.AryInformationDatas.AddRange(eventFolderData);
response.AryVerupNoData2s.Add(verUp2Type103);
var songIntroData = new List<InitialdatacheckResponse.VerupNoData2.InformationData>();
var songIntroDictionary = gameDataService.GetSongIntroDictionary();
foreach (var setId in songIntroDictionary.Select(item => item.Value.SetId))
{
songIntroDictionary.TryGetValue(setId, out var introData);
songIntroData.Add(new InitialdatacheckResponse.VerupNoData2.InformationData
{
InfoId = setId,
VerupNo = introData?.VerupNo ?? 1
});
}
var verUp2Type105 = new InitialdatacheckResponse.VerupNoData2
{
MasterType = 105,
};
verUp2Type105.AryInformationDatas.AddRange(songIntroData);
response.AryVerupNoData2s.Add(verUp2Type105);
response.AryChassisFunctionIds = new uint[] { 1, 2, 3 };
var commonResponse = await Mediator.Send(new GetInitialDataQuery());
var response = InitialDataMappers.MapTo3209(commonResponse);
return Ok(response);
}

View File

@ -1,73 +1,30 @@
using GameDatabase.Entities;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/mydonentry_3nrd7kwk.php")]
[ApiController]
public class MyDonEntryController : BaseController<MyDonEntryController>
{
private readonly IUserDatumService userDatumService;
private readonly ICardService cardService;
private readonly ICredentialService credentialService;
public MyDonEntryController(IUserDatumService userDatumService, ICardService cardService, ICredentialService credentialService)
{
this.userDatumService = userDatumService;
this.cardService = cardService;
this.credentialService = credentialService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/mydonentry_3nrd7kwk.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetMyDonEntry([FromBody] MydonEntryRequest request)
{
Logger.LogInformation("MyDonEntry request : {Request}", request.Stringify());
var newId = cardService.GetNextBaid();
var newUser = new UserDatum
{
Baid = newId,
MyDonName = request.MydonName,
MyDonNameLanguage = 0,
DisplayDan = true,
DisplayAchievement = true,
AchievementDisplayDifficulty = Difficulty.None,
ColorFace = 0,
ColorBody = 1,
ColorLimb = 3,
FavoriteSongsArray = "[]",
ToneFlgArray = "[0]",
TitleFlgArray = "[]",
CostumeFlgArray = "[[0],[0],[0],[0],[0]]",
GenericInfoFlgArray = "[]",
UnlockedSongIdList = "[]"
};
await userDatumService.InsertUserDatum(newUser);
await cardService.AddCard(new Card
{
AccessCode = request.AccessCode,
Baid = newId
});
await credentialService.AddCredential(new Credential
{
Baid = newId,
Password = "",
Salt = ""
});
var response = new MydonEntryResponse
{
Result = 1,
Baid = newId,
MydonName = request.MydonName,
MydonNameLanguage = 0
};
var commonResponse = await Mediator.Send(new AddMyDonEntryCommand(request.AccessCode, request.MydonName, request.MydonNameLanguage));
var response = MyDonEntryMappers.MapTo3906(commonResponse);
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/mydonentry.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetMyDonEntry3209([FromBody] Models.v3209.MydonEntryRequest request)
{
Logger.LogInformation("MyDonEntry request : {Request}", request.Stringify());
var commonResponse = await Mediator.Send(new AddMyDonEntryCommand(request.WechatQrStr, request.MydonName, request.MydonNameLanguage));
var response = MyDonEntryMappers.MapTo3209(commonResponse);
return Ok(response);
}
}

View File

@ -1,410 +1,47 @@
using GameDatabase.Entities;
using System.Globalization;
using System.Text.Json;
using Throw;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
using StageData = PlayResultDataRequest.StageData;
[Route("/v12r08_ww/chassis/playresult_r3ky4a4z.php")]
[ApiController]
public class PlayResultController : BaseController<PlayResultController>
{
private readonly IUserDatumService userDatumService;
private readonly ISongPlayDatumService songPlayDatumService;
private readonly ISongBestDatumService songBestDatumService;
private readonly IDanScoreDatumService danScoreDatumService;
private readonly IAiDatumService aiDatumService;
public PlayResultController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService,
ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService, IAiDatumService aiDatumService)
{
this.userDatumService = userDatumService;
this.songPlayDatumService = songPlayDatumService;
this.songBestDatumService = songBestDatumService;
this.danScoreDatumService = danScoreDatumService;
this.aiDatumService = aiDatumService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/playresult_r3ky4a4z.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> UploadPlayResult([FromBody] PlayResultRequest request)
{
Logger.LogInformation("PlayResult request : {Request}", request.Stringify());
var truncated = request.PlayresultData.Skip(32).ToArray();
var decompressed = GZipBytesUtil.DecompressGZipBytes(truncated);
var playResultData = Serializer.Deserialize<PlayResultDataRequest>(new ReadOnlySpan<byte>(decompressed));
Logger.LogInformation("Play result data {Data}", playResultData.Stringify());
var commonRequest = PlayResultMappers.Map(playResultData);
var commonResponse = await Mediator.Send(new UpdatePlayResultCommand(request.BaidConf, commonRequest));
var response = new PlayResultResponse
{
Result = 1
Result = commonResponse
};
// Fix issue caused by guest play, god knows why they send guest play data
if (request.BaidConf == 0)
{
return Ok(response);
}
if (await userDatumService.GetFirstUserDatumOrNull(request.BaidConf) is null)
{
Logger.LogWarning("Game uploading a non exisiting user with baid {Baid}", request.BaidConf);
return Ok(response);
}
var lastPlayDatetime = DateTime.ParseExact(playResultData.PlayDatetime, Constants.DATE_TIME_FORMAT,
CultureInfo.InvariantCulture);
await UpdateUserData(request, playResultData, lastPlayDatetime);
var playMode = (PlayMode)playResultData.PlayMode;
if (playMode is PlayMode.DanMode or PlayMode.GaidenMode)
{
var danType = playMode == PlayMode.DanMode ? DanType.Normal : DanType.Gaiden;
await UpdateDanPlayData(request, playResultData, danType);
return Ok(response);
}
for (var songNumber = 0; songNumber < playResultData.AryStageInfoes.Count; songNumber++)
{
var stageData = playResultData.AryStageInfoes[songNumber];
if (stageData.IsSkipUse)
{
await UpdatePlayData(request, songNumber, stageData, lastPlayDatetime);
continue;
}
if (playMode == PlayMode.AiBattle)
{
await UpdateAiBattleData(request, stageData);
}
await UpdateBestData(request, stageData);
await UpdatePlayData(request, songNumber, stageData, lastPlayDatetime);
}
return Ok(response);
}
private async Task UpdateDanPlayData(PlayResultRequest request, PlayResultDataRequest playResultDataRequest, DanType danType)
[HttpPost("/v12r00_cn/chassis/playresult.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> UploadPlayResult3209([FromBody] Models.v3209.PlayResultRequest request)
{
if (playResultDataRequest.IsNotRecordedDan)
Logger.LogInformation("PlayResult3209 request : {Request}", request.Stringify());
var decompressed = GZipBytesUtil.DecompressGZipBytes(request.PlayresultData);
var playResultData =
Serializer.Deserialize<Models.v3209.PlayResultDataRequest>(new ReadOnlySpan<byte>(decompressed));
Logger.LogInformation("Play result data 3209 {Data}", playResultData.Stringify());
var commonRequest = PlayResultMappers.Map(playResultData);
var commonResponse = await Mediator.Send(new UpdatePlayResultCommand((uint) request.BaidConf, commonRequest));
var response = new Models.v3209.PlayResultResponse
{
Logger.LogInformation("Dan score will not be saved!");
return;
}
var danScoreData =
await danScoreDatumService.GetSingleDanScoreDatum(request.BaidConf, playResultDataRequest.DanId, danType) ??
new DanScoreDatum
{
Baid = request.BaidConf,
DanId = playResultDataRequest.DanId,
DanType = danType
};
danScoreData.ClearState =
(DanClearState)Math.Max(playResultDataRequest.DanResult, (uint)danScoreData.ClearState);
danScoreData.ArrivalSongCount =
Math.Max((uint)playResultDataRequest.AryStageInfoes.Count, danScoreData.ArrivalSongCount);
danScoreData.ComboCountTotal = Math.Max(playResultDataRequest.ComboCntTotal, danScoreData.ComboCountTotal);
danScoreData.SoulGaugeTotal = Math.Max(playResultDataRequest.SoulGaugeTotal, danScoreData.SoulGaugeTotal);
UpdateDanStageData(playResultDataRequest, danScoreData);
await danScoreDatumService.InsertOrUpdateDanScoreDatum(danScoreData);
}
private void UpdateDanStageData(PlayResultDataRequest playResultDataRequest, DanScoreDatum danScoreData)
{
for (var i = 0; i < playResultDataRequest.AryStageInfoes.Count; i++)
{
var stageData = playResultDataRequest.AryStageInfoes[i];
var songNumber = i;
var danStageData = danScoreData.DanStageScoreData.FirstOrDefault(datum => datum.SongNumber == songNumber,
new DanStageScoreDatum
{
Baid = danScoreData.Baid,
DanId = danScoreData.DanId,
DanType = danScoreData.DanType,
SongNumber = (uint)songNumber,
OkCount = stageData.OkCnt,
BadCount = stageData.NgCnt
});
danStageData.HighScore = Math.Max(danStageData.HighScore, stageData.PlayScore);
danStageData.ComboCount = Math.Max(danStageData.ComboCount, stageData.ComboCnt);
danStageData.DrumrollCount = Math.Max(danStageData.DrumrollCount, stageData.PoundCnt);
danStageData.GoodCount = Math.Max(danStageData.GoodCount, stageData.GoodCnt);
danStageData.TotalHitCount = Math.Max(danStageData.TotalHitCount, stageData.HitCnt);
danStageData.OkCount = Math.Min(danStageData.OkCount, stageData.OkCnt);
danStageData.BadCount = Math.Min(danStageData.BadCount, stageData.NgCnt);
var index = danScoreData.DanStageScoreData.IndexOf(danStageData);
if (index == -1)
{
danScoreDatumService.TrackDanStageData(danStageData);
}
}
}
private async Task UpdatePlayData(PlayResultRequest request, int songNumber, StageData stageData,
DateTime lastPlayDatetime)
{
var songPlayDatum = new SongPlayDatum
{
Baid = request.BaidConf,
SongNumber = (uint)songNumber,
GoodCount = stageData.GoodCnt,
OkCount = stageData.OkCnt,
MissCount = stageData.NgCnt,
ComboCount = stageData.ComboCnt,
HitCount = stageData.HitCnt,
DrumrollCount = stageData.PoundCnt,
Crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt),
Score = stageData.PlayScore,
ScoreRate = stageData.ScoreRate,
ScoreRank = (ScoreRank)stageData.ScoreRank,
Skipped = stageData.IsSkipUse,
SongId = stageData.SongNo,
PlayTime = lastPlayDatetime,
Difficulty = (Difficulty)stageData.Level
Result = commonResponse
};
await songPlayDatumService.AddSongPlayDatum(songPlayDatum);
}
private async Task UpdateUserData(PlayResultRequest request, PlayResultDataRequest playResultData,
DateTime lastPlayDatetime)
{
var userdata = await userDatumService.GetFirstUserDatumOrNull(request.BaidConf);
userdata.ThrowIfNull($"User data is null! Baid: {request.BaidConf}");
var playMode = (PlayMode)playResultData.PlayMode;
userdata.Title = playResultData.Title;
userdata.TitlePlateId = playResultData.TitleplateId;
var costumeData = new List<uint>
{
playResultData.AryCurrentCostume.Costume1,
playResultData.AryCurrentCostume.Costume2,
playResultData.AryCurrentCostume.Costume3,
playResultData.AryCurrentCostume.Costume4,
playResultData.AryCurrentCostume.Costume5
};
userdata.CostumeData = JsonSerializer.Serialize(costumeData);
// Skip user setting altogether following official logic
// Skip user setting saving when in dan mode as dan mode uses its own default setting
// if (playMode != PlayMode.DanMode)
// {
// var lastStage = playResultData.AryStageInfoes.Last();
// var option = BinaryPrimitives.ReadInt16LittleEndian(lastStage.OptionFlg);
// userdata.OptionSetting = option;
// userdata.IsSkipOn = lastStage.IsSkipOn;
// userdata.IsVoiceOn = !lastStage.IsVoiceOn;
// userdata.NotesPosition = lastStage.NotesPosition;
// }
userdata.LastPlayDatetime = lastPlayDatetime;
userdata.LastPlayMode = playResultData.PlayMode;
userdata.ToneFlgArray =
UpdateJsonUintFlagArray(userdata.ToneFlgArray, playResultData.GetToneNoes, nameof(userdata.ToneFlgArray));
userdata.TitleFlgArray =
UpdateJsonUintFlagArray(userdata.TitleFlgArray, playResultData.GetTitleNoes, nameof(userdata.TitleFlgArray));
userdata.CostumeFlgArray = UpdateJsonCostumeFlagArray(userdata.CostumeFlgArray,
new[]
{
playResultData.GetCostumeNo1s,
playResultData.GetCostumeNo2s,
playResultData.GetCostumeNo3s,
playResultData.GetCostumeNo4s,
playResultData.GetCostumeNo5s
});
userdata.GenericInfoFlgArray =
UpdateJsonUintFlagArray(userdata.GenericInfoFlgArray, playResultData.GetGenericInfoNoes, nameof(userdata.GenericInfoFlgArray));
var difficultyPlayedArray = new List<uint>
{
playResultData.DifficultyPlayedCourse,
playResultData.DifficultyPlayedStar,
playResultData.DifficultyPlayedSort
};
userdata.DifficultyPlayedArray = JsonSerializer.Serialize(difficultyPlayedArray);
userdata.AiWinCount += playResultData.AryStageInfoes.Count(data => data.IsWin);
await userDatumService.UpdateUserDatum(userdata);
}
private string UpdateJsonUintFlagArray(string originalValue, IReadOnlyCollection<uint>? newValue, string fieldName)
{
var flgData = new List<uint>();
try
{
flgData = JsonSerializer.Deserialize<List<uint>>(originalValue);
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing {FieldName} json data failed", fieldName);
}
flgData?.AddRange(newValue ?? Array.Empty<uint>());
var flgArray = flgData ?? new List<uint>();
return JsonSerializer.Serialize(flgArray);
}
private string UpdateJsonCostumeFlagArray(string originalValue, IReadOnlyList<IReadOnlyCollection<uint>?>? newValue)
{
var flgData = new List<List<uint>>();
try
{
flgData = JsonSerializer.Deserialize<List<List<uint>>>(originalValue);
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing Costume flag json data failed");
}
if (flgData is null)
{
flgData = new List<List<uint>>();
}
for (var index = 0; index < flgData.Count; index++)
{
var subFlgData = flgData[index];
subFlgData.AddRange(newValue?[index] ?? Array.Empty<uint>());
}
if (flgData.Count >= 5)
{
return JsonSerializer.Serialize(flgData);
}
Logger.LogWarning("Costume flag array count less than 5!");
flgData = new List<List<uint>>
{
new(), new(), new(), new(), new()
};
return JsonSerializer.Serialize(flgData);
}
private async Task UpdateBestData(PlayResultRequest request, StageData stageData)
{
var difficulty = (Difficulty)stageData.Level;
difficulty.Throw().IfOutOfRange();
var existing = await songBestDatumService.GetSongBestData(request.BaidConf, stageData.SongNo, difficulty);
// Determine whether it is dondaful crown as this is not reflected by play result
var crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt);
if (existing is null)
{
var songBestDatum = new SongBestDatum
{
Baid = request.BaidConf,
SongId = stageData.SongNo,
Difficulty = difficulty,
BestScore = stageData.PlayScore,
BestRate = stageData.ScoreRate,
BestCrown = crown,
BestScoreRank = (ScoreRank)stageData.ScoreRank
};
await songBestDatumService.InsertSongBestData(songBestDatum);
return;
}
existing.UpdateBestData(crown, stageData.ScoreRank, stageData.PlayScore, stageData.ScoreRate);
await songBestDatumService.UpdateSongBestData(existing);
}
private async Task UpdateAiBattleData(PlayResultRequest request, StageData stageData)
{
var difficulty = (Difficulty)stageData.Level;
difficulty.Throw().IfOutOfRange();
var existing = await aiDatumService.GetSongAiScore(request.BaidConf,
stageData.SongNo, difficulty);
if (existing is null)
{
var aiScoreDatum = new AiScoreDatum
{
Baid = request.BaidConf,
SongId = stageData.SongNo,
Difficulty = difficulty,
IsWin = stageData.IsWin
};
for (var index = 0; index < stageData.ArySectionDatas.Count; index++)
{
AddNewAiSectionScore(request, stageData, index, difficulty, aiScoreDatum);
}
await aiDatumService.InsertSongAiScore(aiScoreDatum);
return;
}
for (var index = 0; index < stageData.ArySectionDatas.Count; index++)
{
var sectionData = stageData.ArySectionDatas[index];
if (index < existing.AiSectionScoreData.Count)
{
existing.AiSectionScoreData[index].UpdateBest(sectionData);
}
else
{
AddNewAiSectionScore(request, stageData, index, difficulty, existing);
}
}
await aiDatumService.UpdateSongAiScore(existing);
}
private static void AddNewAiSectionScore(PlayResultRequest request, StageData stageData, int index, Difficulty difficulty,
AiScoreDatum aiScoreDatum)
{
var sectionData = stageData.ArySectionDatas[index];
var aiSectionScoreDatum = new AiSectionScoreDatum
{
Baid = request.BaidConf,
SongId = stageData.SongNo,
Difficulty = difficulty,
SectionIndex = index,
OkCount = sectionData.OkCnt,
MissCount = sectionData.NgCnt
};
aiSectionScoreDatum.UpdateBest(sectionData);
aiScoreDatum.AiSectionScoreData.Add(aiSectionScoreDatum);
}
private static CrownType PlayResultToCrown(uint playResult, uint okCount)
{
var crown = (CrownType)playResult;
if (crown == CrownType.Gold && okCount == 0)
{
crown = CrownType.Dondaful;
}
return crown;
return Ok(response);
}
}

View File

@ -1,10 +1,9 @@
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/rewarditem.php")]
[ApiController]
public class RewardItemController : BaseController<RewardItemController>
{
[HttpPost]
[HttpPost("/v12r08_ww/chassis/rewarditem.php")]
[Produces("application/protobuf")]
public IActionResult RewardItem([FromBody] RewardItemRequest request)
{
@ -17,4 +16,18 @@ public class RewardItemController : BaseController<RewardItemController>
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/rewarditem.php")]
[Produces("application/protobuf")]
public IActionResult RewardItem3209([FromBody] Models.v3209.RewardItemRequest request)
{
Logger.LogInformation("RewardItem request : {Request}", request.Stringify());
var response = new Models.v3209.RewardItemResponse
{
Result = 1
};
return Ok(response);
}
}

View File

@ -1,81 +1,34 @@
using GameDatabase.Entities;
using Throw;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/selfbest_5nz47auu.php")]
[ApiController]
public class SelfBestController : BaseController<SelfBestController>
{
private readonly ISongBestDatumService songBestDatumService;
private readonly IGameDataService gameDataService;
public SelfBestController(ISongBestDatumService songBestDatumService, IGameDataService gameDataService)
{
this.songBestDatumService = songBestDatumService;
this.gameDataService = gameDataService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/selfbest_5nz47auu.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> SelfBest([FromBody] SelfBestRequest request)
{
Logger.LogInformation("SelfBest request : {Request}", request.Stringify());
var response = new SelfBestResponse
{
Result = 1,
Level = request.Level
};
var requestDifficulty = (Difficulty)request.Level;
requestDifficulty.Throw().IfOutOfRange();
var playerBestData = await songBestDatumService.GetAllSongBestData(request.Baid);
playerBestData = playerBestData
.Where(datum => datum.Difficulty == requestDifficulty ||
(datum.Difficulty == Difficulty.UraOni && requestDifficulty == Difficulty.Oni))
.ToList();
foreach (var songNo in request.ArySongNoes)
{
if (!gameDataService.GetMusicList().Contains(songNo))
{
Logger.LogWarning("Music no {No} is missing!", songNo);
continue;
}
var selfBestData = GetSongSelfBestData(playerBestData, songNo);
response.ArySelfbestScores.Add(selfBestData);
}
response.ArySelfbestScores.Sort((data, otherData) => data.SongNo.CompareTo(otherData.SongNo));
var commonResponse =
await Mediator.Send(new GetSelfBestQuery(request.Baid, request.Level, request.ArySongNoes));
var response = SelfBestMappers.MapTo3906(commonResponse);
return Ok(response);
}
private static SelfBestResponse.SelfBestData GetSongSelfBestData(IEnumerable<SongBestDatum> playerBestData, uint songNo)
[HttpPost("/v12r00_cn/chassis/selfbest.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> SelfBest3209([FromBody] Models.v3209.SelfBestRequest request)
{
var songBestDatum = playerBestData.Where(datum => datum.SongId == songNo);
Logger.LogInformation("SelfBest3209 request : {Request}", request.Stringify());
var selfBestData = new SelfBestResponse.SelfBestData
{
SongNo = songNo,
};
var commonResponse =
await Mediator.Send(new GetSelfBestQuery((uint)request.Baid, request.Level, request.ArySongNoes));
var response = SelfBestMappers.MapTo3209(commonResponse);
foreach (var datum in songBestDatum)
{
if (datum.Difficulty == Difficulty.UraOni)
{
selfBestData.UraBestScore = datum.BestScore;
selfBestData.UraBestScoreRate = datum.BestRate;
continue;
}
selfBestData.SelfBestScore = datum.BestScore;
selfBestData.SelfBestScoreRate = datum.BestRate;
}
return selfBestData;
return Ok(response);
}
}

View File

@ -1,10 +1,9 @@
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/setanystring_mssxf3bo.php")]
[ApiController]
public class SetAnyStringController : BaseController<SetAnyStringController>
{
[HttpPost]
[HttpPost("/v12r08_ww/chassis/setanystring_mssxf3bo.php")]
[Produces("application/protobuf")]
public IActionResult SetAnyString([FromBody] SetAnyStringRequest request)
{
@ -17,4 +16,18 @@ public class SetAnyStringController : BaseController<SetAnyStringController>
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/setanystring.php")]
[Produces("application/protobuf")]
public IActionResult SetAnyString3209([FromBody] Models.v3209.SetAnyStringRequest request)
{
Logger.LogInformation("SetAnyString request : {Request}", request.Stringify());
var response = new Models.v3209.SetAnyStringResponse
{
Result = 1,
};
return Ok(response);
}
}

View File

@ -1,85 +1,30 @@
using System.Text.Json;
using Throw;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/songpurchase_wm2fh5bl.php")]
[ApiController]
public class SongPurchaseController : BaseController<SongPurchaseController>
{
private readonly IUserDatumService userDatumService;
public SongPurchaseController(IUserDatumService userDatumService)
{
this.userDatumService = userDatumService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/songpurchase_wm2fh5bl.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> SongPurchase([FromBody] SongPurchaseRequest request)
{
Logger.LogInformation("SongPurchase request : {Request}", request.Stringify());
var user = await userDatumService.GetFirstUserDatumOrNull(request.Baid);
user.ThrowIfNull($"User with baid {request.Baid} does not exist!");
/*var tokenCountDict = new Dictionary<uint, int>();
try
{
tokenCountDict = !string.IsNullOrEmpty(user.TokenCountDict)
? JsonSerializer.Deserialize<Dictionary<uint, int>>(user.TokenCountDict)
: new Dictionary<uint, int>();
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing TokenCountDict data for user with baid {Request} failed!", request.Baid);
}
tokenCountDict.ThrowIfNull("TokenCountDict should never be null");*/
Logger.LogInformation("Original UnlockedSongIdList: {UnlockedSongIdList}", user.UnlockedSongIdList);
var unlockedSongIdList = new List<uint>();
try
{
unlockedSongIdList = !string.IsNullOrEmpty(user.UnlockedSongIdList)
? JsonSerializer.Deserialize<List<uint>>(user.UnlockedSongIdList)
: new List<uint>();
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing UnlockedSongIdList data for user with baid {Request} failed!", request.Baid);
}
unlockedSongIdList.ThrowIfNull("UnlockedSongIdList should never be null");
//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.UnlockedSongIdList = JsonSerializer.Serialize(unlockedSongIdList);
Logger.LogInformation("Updated UnlockedSongIdList: {UnlockedSongIdList}", user.UnlockedSongIdList);
await userDatumService.UpdateUserDatum(user);
var response = new SongPurchaseResponse
{
Result = 1,
TokenCount = token.Count
};
var commonResponse = await Mediator.Send(SongPurchaseMappers.MapToCommand(request));
var response = SongPurchaseMappers.MapTo3906(commonResponse);
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/songpurchase.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> SongPurchase3209([FromBody] Models.v3209.SongPurchaseRequest request)
{
Logger.LogInformation("SongPurchase request : {Request}", request.Stringify());
var commonResponse = await Mediator.Send(SongPurchaseMappers.MapToCommand(request));
var response = SongPurchaseMappers.MapTo3209(commonResponse);
return Ok(response);
}
}

View File

@ -1,10 +1,9 @@
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/tournamentcheck.php")]
[ApiController]
public class TournamentCheckController : BaseController<TournamentCheckController>
{
[HttpPost]
[HttpPost("/v12r08_ww/chassis/tournamentcheck.php")]
[Produces("application/protobuf")]
public IActionResult TournamentCheck([FromBody] TournamentcheckRequest request)
{
@ -17,4 +16,18 @@ public class TournamentCheckController : BaseController<TournamentCheckControlle
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/tournamentcheck.php")]
[Produces("application/protobuf")]
public IActionResult TournamentCheck3209([FromBody] Models.v3209.TournamentcheckRequest request)
{
Logger.LogInformation("TournamentCheck request : {Request}", request.Stringify());
var response = new Models.v3209.TournamentcheckResponse
{
Result = 1,
};
return Ok(response);
}
}

View File

@ -1,178 +1,31 @@
using Microsoft.Extensions.Options;
using System.Buffers.Binary;
using System.Text.Json;
using TaikoLocalServer.Settings;
using Throw;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/userdata_gc6x17o8.php")]
[ApiController]
public class UserDataController : BaseController<UserDataController>
{
private readonly IUserDatumService userDatumService;
private readonly ISongPlayDatumService songPlayDatumService;
private readonly IGameDataService gameDataService;
private readonly ServerSettings settings;
public UserDataController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService,
IGameDataService gameDataService, IOptions<ServerSettings> settings)
{
this.userDatumService = userDatumService;
this.songPlayDatumService = songPlayDatumService;
this.gameDataService = gameDataService;
this.settings = settings.Value;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/userdata_gc6x17o8.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetUserData([FromBody] UserDataRequest request)
{
Logger.LogInformation("UserData request : {Request}", request.Stringify());
var songIdMax = settings.EnableMoreSongs ? Constants.MUSIC_ID_MAX_EXPANDED : Constants.MUSIC_ID_MAX;
var commonResponse = await Mediator.Send(new UserDataQuery(request.Baid));
var response = UserDataMappers.MapTo3906(commonResponse);
var userData = await userDatumService.GetFirstUserDatumOrDefault(request.Baid);
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/userdata.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> GetUserData3209([FromBody] Models.v3209.UserDataRequest request)
{
Logger.LogInformation("UserData request : {Request}", request.Stringify());
var unlockedSongIdList = new List<uint>();
try
{
unlockedSongIdList = !string.IsNullOrEmpty(userData.UnlockedSongIdList)
? JsonSerializer.Deserialize<List<uint>>(userData.UnlockedSongIdList)
: new List<uint>();
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing UnlockedSongIdList data for user with baid {Request} failed!", request.Baid);
}
unlockedSongIdList.ThrowIfNull("UnlockedSongIdList should never be null");
var musicList = gameDataService.GetMusicList();
var lockedSongsList = gameDataService.GetLockedSongsList();
lockedSongsList = lockedSongsList.Except(unlockedSongIdList).ToList();
var enabledMusicList = musicList.Except(lockedSongsList);
var releaseSongArray =
FlagCalculator.GetBitArrayFromIds(enabledMusicList, songIdMax, Logger);
var defaultSongWithUraList = gameDataService.GetMusicWithUraList();
var enabledUraMusicList = defaultSongWithUraList.Except(lockedSongsList);
var uraSongArray =
FlagCalculator.GetBitArrayFromIds(enabledUraMusicList, songIdMax, Logger);
var toneFlg = Array.Empty<uint>();
try
{
toneFlg = JsonSerializer.Deserialize<uint[]>(userData.ToneFlgArray);
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing tone flg json data failed");
}
// The only way to get a null is provide string "null" as input,
// which means database content need to be fixed, so better throw
toneFlg.ThrowIfNull("Tone flg should never be null!");
// If toneFlg is empty, add 0 to it
if (toneFlg.Length == 0)
{
toneFlg = new uint[] { 0 };
userData.ToneFlgArray = JsonSerializer.Serialize(toneFlg);
await userDatumService.UpdateUserDatum(userData);
}
var toneArray = FlagCalculator.GetBitArrayFromIds(toneFlg, gameDataService.GetToneFlagArraySize(), Logger);
var titleFlg = Array.Empty<uint>();
try
{
titleFlg = JsonSerializer.Deserialize<uint[]>(userData.TitleFlgArray);
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing title flg json data failed");
}
// The only way to get a null is provide string "null" as input,
// which means database content need to be fixed, so better throw
titleFlg.ThrowIfNull("Title flg should never be null!");
var titleArray = FlagCalculator.GetBitArrayFromIds(titleFlg, gameDataService.GetTitleFlagArraySize(), Logger);
var recentSongs = (await songPlayDatumService.GetSongPlayDatumByBaid(request.Baid))
.AsEnumerable()
.OrderByDescending(datum => datum.PlayTime)
.ThenByDescending(datum => datum.SongNumber)
.Select(datum => datum.SongId)
.ToArray();
// Use custom implementation as distinctby cannot guarantee preserved element
var recentSet = new OrderedSet<uint>();
foreach (var id in recentSongs)
{
recentSet.Add(id);
if (recentSet.Count == 10)
{
break;
}
}
recentSongs = recentSet.ToArray();
var favoriteSongs = Array.Empty<uint>();
try
{
favoriteSongs = JsonSerializer.Deserialize<uint[]>(userData.FavoriteSongsArray);
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing favorite songs json data failed");
}
// The only way to get a null is provide string "null" as input,
// which means database content need to be fixed, so better throw
favoriteSongs.ThrowIfNull("Favorite song should never be null!");
var defaultOptions = new byte[2];
BinaryPrimitives.WriteInt16LittleEndian(defaultOptions, userData.OptionSetting);
var difficultySettingArray = JsonHelper.GetUIntArrayFromJson(userData.DifficultySettingArray, 3, Logger, nameof(userData.DifficultySettingArray));
for (int i = 0; i < 3; i++)
{
if (difficultySettingArray[i] >= 2)
{
difficultySettingArray[i] -= 1;
}
}
var difficultyPlayedArray = JsonHelper.GetUIntArrayFromJson(userData.DifficultyPlayedArray, 3, Logger, nameof(userData.DifficultyPlayedArray));
var response = new UserDataResponse
{
Result = 1,
ToneFlg = toneArray,
TitleFlg = titleArray,
ReleaseSongFlg = releaseSongArray,
UraReleaseSongFlg = uraSongArray,
AryFavoriteSongNoes = favoriteSongs,
AryRecentSongNoes = recentSongs,
DefaultOptionSetting = defaultOptions,
NotesPosition = userData.NotesPosition,
IsVoiceOn = userData.IsVoiceOn,
IsSkipOn = userData.IsSkipOn,
DifficultySettingCourse = difficultySettingArray[0],
DifficultySettingStar = difficultySettingArray[1],
DifficultySettingSort = difficultySettingArray[2],
DifficultyPlayedCourse = difficultyPlayedArray[0],
DifficultyPlayedStar = difficultyPlayedArray[1],
DifficultyPlayedSort = difficultyPlayedArray[2],
IsChallengecompe = false,
SongRecentCnt = (uint)recentSongs.Length
};
var commonResponse = await Mediator.Send(new UserDataQuery((uint)request.Baid));
var response = UserDataMappers.MapTo3209(commonResponse);
return Ok(response);
}

View File

@ -1,6 +1,5 @@
namespace TaikoLocalServer.Controllers.Game;
[Route("/v12r08_ww/chassis/verifyqrcode_ku5ra5q7.php")]
[ApiController]
public class VerifyQrCodeController : BaseController<VerifyQrCodeController>
{
@ -11,27 +10,60 @@ public class VerifyQrCodeController : BaseController<VerifyQrCodeController>
this.gameDataService = gameDataService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/verifyqrcode_ku5ra5q7.php")]
[Produces("application/protobuf")]
public IActionResult VerifyQrCode([FromBody] VerifyQrcodeRequest request)
{
Logger.LogInformation("VerifyQrCode request : {Request}", request.Stringify());
var qrCodeDataDictionary = gameDataService.GetQRCodeDataDictionary();
qrCodeDataDictionary.TryGetValue(request.QrcodeSerial, out var qrCodeId);
if (qrCodeId == 0)
{
Logger.LogWarning("Requested QR code serial {Serial} does not exist!", request.QrcodeSerial);
}
var qrCodeId = VerifyQr(request.QrcodeSerial);
var response = new VerifyQrcodeResponse
{
Result = 1,
QrcodeId = qrCodeId
QrcodeId = (uint)qrCodeId
};
if (qrCodeId == -1)
{
response.Result = 51;
}
return Ok(response);
}
[HttpPost("/v12r00_cn/chassis/verifyqrcode.php")]
[Produces("application/protobuf")]
public IActionResult VerifyQrCode3209([FromBody] Models.v3209.VerifyQrcodeRequest request)
{
Logger.LogInformation("VerifyQrCode request : {Request}", request.Stringify());
var qrCodeId = VerifyQr(request.QrcodeSerial);
var response = new Models.v3209.VerifyQrcodeResponse
{
Result = 1,
QrcodeId = (uint)qrCodeId
};
if (qrCodeId == -1)
{
response.Result = 51;
}
return Ok(response);
}
private int VerifyQr(string serial)
{
var qrCodeDataDictionary = gameDataService.GetQRCodeDataDictionary();
qrCodeDataDictionary.TryGetValue(serial, out var qrCodeId);
if (qrCodeId == 0)
{
Logger.LogWarning("Requested QR code serial {Serial} does not exist!", serial);
return -1;
}
return (int)qrCodeId;
}
}

View File

@ -1,8 +1,4 @@
using System.Buffers;
using System.Security.Cryptography;
using Microsoft.Extensions.Options;
using TaikoLocalServer.Settings;
using System.Text.Json;
using System.Text;
using System.Text.Json.Serialization;

View File

@ -1,7 +1,4 @@
using System.Buffers;
using Microsoft.Extensions.Options;
using TaikoLocalServer.Settings;
using System.Text.Json;
using System.Text;
using System.Text.Json.Serialization;

View File

@ -2,9 +2,10 @@
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.EntityFrameworkCore;
global using ProtoBuf;
global using MediatR;
global using Swan.Formatters;
global using SharedProject.Enums;
global using taiko.game;
global using TaikoLocalServer.Models.v3906;
global using TaikoLocalServer.Common;
global using TaikoLocalServer.Common.Utils;
global using TaikoLocalServer.Models;

View File

@ -0,0 +1,68 @@
using GameDatabase.Context;
using GameDatabase.Entities;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Handlers;
public record AddMyDonEntryCommand(string AccessCode, string Name, uint Language) : IRequest<CommonMyDonEntryResponse>;
public class AddMyDonEntryCommandHandler(TaikoDbContext context, ILogger<AddMyDonEntryCommandHandler> logger)
: IRequestHandler<AddMyDonEntryCommand, CommonMyDonEntryResponse>
{
public async Task<CommonMyDonEntryResponse> Handle(AddMyDonEntryCommand request, CancellationToken cancellationToken)
{
var nextBaid = await context.Cards.Select(card => card.Baid)
.DefaultIfEmpty()
.MaxAsync(cancellationToken) + 1;
var newUser = new UserDatum
{
Baid = nextBaid,
MyDonName = request.Name,
MyDonNameLanguage = request.Language,
DisplayDan = true,
DisplayAchievement = true,
AchievementDisplayDifficulty = Difficulty.None,
ColorFace = 0,
ColorBody = 1,
ColorLimb = 3,
FavoriteSongsArray = [],
ToneFlgArray = [0],
TitleFlgArray = [],
UnlockedKigurumi = [0],
UnlockedBody = [0],
UnlockedFace = [0],
UnlockedHead = [0],
UnlockedPuchi = [0],
GenericInfoFlgArray = [],
UnlockedSongIdList = []
};
context.UserData.Add(newUser);
var newCard = new Card
{
AccessCode = request.AccessCode,
Baid = nextBaid
};
context.Cards.Add(newCard);
var newCredential = new Credential
{
Baid = nextBaid,
Password = "",
Salt = ""
};
context.Credentials.Add(newCredential);
await context.SaveChangesAsync(cancellationToken);
var response = new CommonMyDonEntryResponse
{
Result = 1,
Baid = nextBaid,
MydonName = request.Name,
MydonNameLanguage = request.Language,
ComSvrResult = 1,
AccessCode = request.AccessCode
};
return response;
}
}

View File

@ -0,0 +1,52 @@
using GameDatabase.Context;
using GameDatabase.Entities;
using TaikoLocalServer.Models.Application;
using Throw;
namespace TaikoLocalServer.Handlers;
public record AddTokenCountCommand(CommonAddTokenCountRequest Request) : IRequest;
public class AddTokenCountCommandHandler : IRequestHandler<AddTokenCountCommand>
{
private readonly TaikoDbContext context;
private readonly ILogger<AddTokenCountCommandHandler> logger;
public AddTokenCountCommandHandler(TaikoDbContext context, ILogger<AddTokenCountCommandHandler> logger)
{
this.context = context;
this.logger = logger;
}
public async Task Handle(AddTokenCountCommand command, CancellationToken cancellationToken)
{
var request = command.Request;
var user = await context.UserData
.Include(userDatum => userDatum.Tokens)
.FirstOrDefaultAsync(datum => datum.Baid == request.Baid, cancellationToken);
user.ThrowIfNull($"User with baid {request.Baid} does not exist!");
foreach (var addTokenCountData in request.AryAddTokenCountDatas)
{
var tokenId = addTokenCountData.TokenId;
var addTokenCount = addTokenCountData.AddTokenCount;
var token = user.Tokens.FirstOrDefault(t => t.Id == tokenId);
if (token is not null)
{
token.Count += addTokenCount;
}
else
{
user.Tokens.Add(new Token
{
Id = (int)tokenId,
Count = addTokenCount
});
}
}
context.Update(user);
await context.SaveChangesAsync(cancellationToken);
}
}

View File

@ -0,0 +1,138 @@
using GameDatabase.Context;
using TaikoLocalServer.Models.Application;
using Throw;
namespace TaikoLocalServer.Handlers;
public record BaidQuery(string AccessCode) : IRequest<CommonBaidResponse>;
public class BaidQueryHandler(
TaikoDbContext context,
ILogger<BaidQueryHandler> logger,
IGameDataService gameDataService)
: IRequestHandler<BaidQuery, CommonBaidResponse>
{
public async Task<CommonBaidResponse> Handle(BaidQuery request, CancellationToken cancellationToken)
{
var card = await context.Cards.FindAsync(request.AccessCode);
if (card is null)
{
logger.LogInformation("New user with access code {AccessCode}", request.AccessCode);
return new CommonBaidResponse
{
Result = 1,
IsNewUser = true,
Baid = context.Cards.Any() ? context.Cards.AsEnumerable().Max(c => c.Baid) + 1 : 1
};
}
var baid = card.Baid;
var userData = await context.UserData.FindAsync(baid, cancellationToken);
userData.ThrowIfNull($"User not found for card with Baid {baid}!");
var songBestData = context.SongBestData.Where(datum => datum.Baid == baid).ToList();
var achievementDisplayDifficulty = userData.AchievementDisplayDifficulty;
if (achievementDisplayDifficulty == Difficulty.None)
{
achievementDisplayDifficulty = songBestData
.Where(datum => datum.BestCrown >= CrownType.Clear)
.Select(datum => datum.Difficulty)
.DefaultIfEmpty(Difficulty.Easy)
.Max();
}
// For each crown type, calculate how many songs have that crown type
var crownCountData = songBestData
.Where(datum => datum.BestCrown >= CrownType.Clear)
.GroupBy(datum => datum.BestCrown)
.ToDictionary(datums => datums.Key, datums => (uint)datums.Count());
var crownCount = new uint[3];
foreach (var crownType in Enum.GetValues<CrownType>())
{
if (crownType != CrownType.None)
{
crownCount[(int)crownType - 1] = crownCountData.GetValueOrDefault(crownType, (uint)0);
}
}
var scoreRankData = songBestData
.Where(datum => datum.BestCrown >= CrownType.Clear)
.GroupBy(datum => datum.BestScoreRank)
.ToDictionary(datums => datums.Key, datums => (uint)datums.Count());
var scoreRankCount = new uint[7];
foreach (var scoreRank in Enum.GetValues<ScoreRank>())
{
if (scoreRank != ScoreRank.None)
{
scoreRankCount[(int)scoreRank - 2] = scoreRankData.GetValueOrDefault(scoreRank, (uint)0);
}
}
var costumeData = JsonHelper.GetCostumeDataFromUserData(userData, logger);
List<List<uint>> costumeArrays =
[userData.UnlockedKigurumi, userData.UnlockedHead, userData.UnlockedBody, userData.UnlockedFace, userData.UnlockedPuchi];
var costumeFlagArrays = gameDataService.GetCostumeFlagArraySizes()
.Select((size, index) => FlagCalculator.GetBitArrayFromIds(costumeArrays[index], size, logger))
.ToList();
var danData = await context.DanScoreData
.Where(datum => datum.Baid == baid && datum.DanType == DanType.Normal)
.Include(datum => datum.DanStageScoreData).ToListAsync(cancellationToken);
var gaidenData = await context.DanScoreData
.Where(datum => datum.Baid == baid && datum.DanType == DanType.Gaiden)
.Include(datum => datum.DanStageScoreData).ToListAsync(cancellationToken);
var maxDan = danData.Where(datum => datum.ClearState != DanClearState.NotClear)
.Select(datum => datum.DanId)
.DefaultIfEmpty()
.Max();
var danDataDictionary = gameDataService.GetCommonDanDataDictionary();
var danIdList = danDataDictionary.Keys.ToList();
var gotDanFlagArray = FlagCalculator.ComputeGotDanFlags(danData, danIdList);
var gaidenDataDictionary = gameDataService.GetCommonGaidenDataDictionary();
var gaidenIdList = gaidenDataDictionary.Keys.ToList();
var gotGaidenFlagArray = FlagCalculator.ComputeGotDanFlags(gaidenData, gaidenIdList);
var genericInfoFlg = userData.GenericInfoFlgArray;
var genericInfoFlgLength = genericInfoFlg.Any() ? genericInfoFlg.Max() + 1 : 0;
var genericInfoFlgArray = FlagCalculator.GetBitArrayFromIds(genericInfoFlg, (int)genericInfoFlgLength, logger);
var aiRank = (uint)(userData.AiWinCount / 10);
if (aiRank > 11)
{
aiRank = 11;
}
return new CommonBaidResponse
{
Result = 1,
IsNewUser = false,
Baid = baid,
MyDonName = userData.MyDonName,
MyDonNameLanguage = userData.MyDonNameLanguage,
AryCrownCounts = crownCount,
AryScoreRankCounts = scoreRankCount,
ColorBody = userData.ColorBody,
ColorFace = userData.ColorFace,
ColorLimb = userData.ColorLimb,
CostumeData = costumeData,
CostumeFlagArrays = costumeFlagArrays,
DisplayDan = userData.DisplayDan,
DispAchievementType = (uint)achievementDisplayDifficulty,
GenericInfoFlg = genericInfoFlgArray,
GotDanFlg = gotDanFlagArray,
GotDanMax = maxDan,
GotGaidenFlg = gotGaidenFlagArray,
IsDispAchievementOn = userData.DisplayAchievement,
LastPlayDatetime = userData.LastPlayDatetime.ToString(Constants.DATE_TIME_FORMAT),
LastPlayMode = userData.LastPlayMode,
SelectedToneId = userData.SelectedToneId,
Title = userData.Title,
TitlePlateId = userData.TitlePlateId
};
}
}

View File

@ -0,0 +1,35 @@
using GameDatabase.Context;
using TaikoLocalServer.Models.Application;
using Throw;
namespace TaikoLocalServer.Handlers;
public record GetAiDataQuery(uint Baid) : IRequest<CommonAiDataResponse>;
public class GetAiDataQueryHandler : IRequestHandler<GetAiDataQuery, CommonAiDataResponse>
{
private readonly TaikoDbContext context;
private readonly ILogger<GetAiDataQueryHandler> logger;
public GetAiDataQueryHandler(TaikoDbContext context, ILogger<GetAiDataQueryHandler> logger)
{
this.context = context;
this.logger = logger;
}
public async Task<CommonAiDataResponse> Handle(GetAiDataQuery request, CancellationToken cancellationToken)
{
var user = await context.UserData.FirstOrDefaultAsync(datum => datum.Baid == request.Baid);
user.ThrowIfNull($"User with baid {request.Baid} does not exist!");
var response = new CommonAiDataResponse
{
Result = 1,
TotalWinnings = (uint)user.AiWinCount,
InputMedian = "1",
InputVariance = "0"
};
return response;
}
}

View File

@ -0,0 +1,33 @@
using GameDatabase.Context;
using TaikoLocalServer.Mappers;
using TaikoLocalServer.Models.Application;
using Throw;
namespace TaikoLocalServer.Handlers;
public record GetAiScoreQuery(uint Baid, uint SongId, uint Level) : IRequest<CommonAiScoreResponse>;
public class GetAiScoreQueryHandler(TaikoDbContext context, ILogger<GetAiScoreQueryHandler> logger)
: IRequestHandler<GetAiScoreQuery, CommonAiScoreResponse>
{
public async Task<CommonAiScoreResponse> Handle(GetAiScoreQuery request, CancellationToken cancellationToken)
{
var difficulty = (Difficulty)request.Level;
difficulty.Throw().IfOutOfRange();
var aiData = await context.AiScoreData.Where(datum => datum.Baid == request.Baid &&
datum.SongId == request.SongId &&
datum.Difficulty == difficulty)
.Include(datum => datum.AiSectionScoreData)
.FirstOrDefaultAsync(cancellationToken);
if (aiData is null)
{
return new CommonAiScoreResponse
{
Result = 1
};
}
aiData.AiSectionScoreData.Sort((a, b) => a.SectionIndex.CompareTo(b.SectionIndex));
return AiScoreMappers.MapAsSuccess(aiData);
}
}

View File

@ -0,0 +1,52 @@
using SharedProject.Models;
using Throw;
namespace TaikoLocalServer.Handlers;
public record GetDanOdaiQuery(uint[] DanIds, uint Type) : IRequest<List<DanData>>;
public class GetDanOdaiQueryHandler : IRequestHandler<GetDanOdaiQuery, List<DanData>>
{
private readonly IGameDataService gameDataService;
public GetDanOdaiQueryHandler(IGameDataService gameDataService)
{
this.gameDataService = gameDataService;
}
public Task<List<DanData>> Handle(GetDanOdaiQuery request, CancellationToken cancellationToken)
{
var type = (DanType)request.Type;
type.Throw().IfOutOfRange();
var danDataList = new List<DanData>();
switch (type)
{
case DanType.Normal:
var danDataDictionary = gameDataService.GetCommonDanDataDictionary();
foreach (var danId in request.DanIds)
{
if (danDataDictionary.TryGetValue(danId, out var danData))
{
danDataList.Add(danData);
}
}
break;
case DanType.Gaiden:
var gaidenDataDictionary = gameDataService.GetCommonGaidenDataDictionary();
foreach (var danId in request.DanIds)
{
if (gaidenDataDictionary.TryGetValue(danId, out var danData))
{
danDataList.Add(danData);
}
}
break;
default:
throw new ApplicationException("Impossible");
}
return Task.FromResult(danDataList);
}
}

View File

@ -0,0 +1,73 @@
using GameDatabase.Context;
using GameDatabase.Entities;
using TaikoLocalServer.Models.Application;
using Throw;
namespace TaikoLocalServer.Handlers;
public record GetDanScoreQuery(uint Baid, uint Type, uint[] DanIds) : IRequest<CommonDanScoreDataResponse>;
public class GetDanScoreQueryHandler : IRequestHandler<GetDanScoreQuery, CommonDanScoreDataResponse>
{
private readonly ILogger<GetDanScoreQueryHandler> logger;
private readonly TaikoDbContext context;
public GetDanScoreQueryHandler(ILogger<GetDanScoreQueryHandler> logger, TaikoDbContext context)
{
this.logger = logger;
this.context = context;
}
public async Task<CommonDanScoreDataResponse> Handle(GetDanScoreQuery request, CancellationToken cancellationToken)
{
var danType = (DanType)request.Type;
danType.Throw().IfOutOfRange();
var idList = request.DanIds.ToList();
// Select the dan score data from the database where baid and type matches and danid is in the list of danids
var danScoreData = await context.DanScoreData
.Where(d => d.Baid == request.Baid && d.DanType == danType &&idList.Contains(d.DanId))
.Include(d => d.DanStageScoreData)
.ToListAsync(cancellationToken);
var response = new CommonDanScoreDataResponse
{
Result = 1
};
foreach (var danScoreDatum in danScoreData)
{
var responseData = new CommonDanScoreDataResponse.DanScoreData
{
DanId = danScoreDatum.DanId,
ArrivalSongCnt = danScoreDatum.ArrivalSongCount,
ComboCntTotal = danScoreDatum.ComboCountTotal,
SoulGaugeTotal = danScoreDatum.SoulGaugeTotal
};
for (int i = 0; i < danScoreDatum.ArrivalSongCount; i++)
{
var songNumber = i;
var stageScoreDatum = danScoreDatum.DanStageScoreData.FirstOrDefault(d => d.SongNumber == songNumber);
if (stageScoreDatum is null)
{
logger.LogWarning("Stage score data for dan {DanId} song number {SongNumber} not found", danScoreDatum.DanId, songNumber);
stageScoreDatum = new DanStageScoreDatum();
}
responseData.AryDanScoreDataStages.Add(new CommonDanScoreDataResponse.DanScoreDataStage
{
PlayScore = stageScoreDatum.PlayScore,
GoodCnt = stageScoreDatum.GoodCount,
OkCnt = stageScoreDatum.OkCount,
NgCnt = stageScoreDatum.BadCount,
PoundCnt = stageScoreDatum.DrumrollCount,
HitCnt = stageScoreDatum.TotalHitCount,
ComboCnt = stageScoreDatum.ComboCount,
HighScore = stageScoreDatum.HighScore
});
}
response.AryDanScoreDatas.Add(responseData);
}
return response;
}
}

View File

@ -0,0 +1,29 @@
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Handlers;
public record GetFolderQuery(uint[] FolderIds) : IRequest<CommonGetFolderResponse>;
public class GetFolderQueryHandler(ILogger<GetFolderQueryHandler> logger, IGameDataService gameDataService)
: IRequestHandler<GetFolderQuery, CommonGetFolderResponse>
{
public Task<CommonGetFolderResponse> Handle(GetFolderQuery request, CancellationToken cancellationToken)
{
var response = new CommonGetFolderResponse
{
Result = 1
};
var eventFolders = gameDataService.GetEventFolderDictionary();
foreach (var folderId in request.FolderIds)
{
eventFolders.TryGetValue(folderId, out var folderData);
if (folderData is null)
{
logger.LogWarning("Folder data for folder {FolderId} not found", folderId);
continue;
}
response.AryEventfolderDatas.Add(folderData);
}
return Task.FromResult(response);
}
}

View File

@ -0,0 +1,102 @@
using System.Collections.Immutable;
using Microsoft.Extensions.Options;
using SharedProject.Models;
using TaikoLocalServer.Models.Application;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Handlers;
public record GetInitialDataQuery : IRequest<CommonInitialDataCheckResponse>;
public class GetInitialDataQueryHandler(IGameDataService gameDataService,
ILogger<GetInitialDataQueryHandler> logger,
IOptions<ServerSettings> settings)
: IRequestHandler<GetInitialDataQuery, CommonInitialDataCheckResponse>
{
private readonly ServerSettings settings = settings.Value;
public Task<CommonInitialDataCheckResponse> Handle(GetInitialDataQuery request, CancellationToken cancellationToken)
{
var songIdMax = settings.EnableMoreSongs ? Constants.MUSIC_ID_MAX_EXPANDED : Constants.MUSIC_ID_MAX;
var musicList = gameDataService.GetMusicList();
var lockedSongsList = gameDataService.GetLockedSongsList();
var enabledArray =
FlagCalculator.GetBitArrayFromIds(musicList, songIdMax, logger);
var defaultSongList = musicList.Except(lockedSongsList);
var defaultSongFlg =
FlagCalculator.GetBitArrayFromIds(defaultSongList, songIdMax, logger);
var defaultSongWithUraList = gameDataService.GetMusicWithUraList();
var uraReleaseBit =
FlagCalculator.GetBitArrayFromIds(defaultSongWithUraList, songIdMax, logger);
var response = new CommonInitialDataCheckResponse
{
Result = 1,
DefaultSongFlg = defaultSongFlg,
AchievementSongBit = enabledArray,
UraReleaseBit = uraReleaseBit,
SongIntroductionEndDatetime = DateTime.Now.AddYears(10).ToString(Constants.DATE_TIME_FORMAT),
ServerCurrentDatetime = (ulong)DateTimeOffset.Now.ToUnixTimeSeconds()
};
var movieDataDictionary = gameDataService.GetMovieDataDictionary();
foreach (var movieData in movieDataDictionary)
{
response.AryMovieInfoes.Add(movieData.Value);
}
// TODO: Figure out what they are individually
var verupNo1 = new uint[] { 2, 3, 4, 5, 6, 7, 8, 13, 15, 24, 25, 26, 27, 28, 29, 30, 31, 104 };
var aryVerUp = verupNo1.Select(i => new CommonInitialDataCheckResponse.VerupNoData1
{
MasterType = i,
VerupNo = 1
}).ToList();
response.AryVerupNoData1s.AddRange(aryVerUp);
var commonDanDataDictionary = gameDataService.GetCommonDanDataDictionary();
var commonGaidenDataDictionary = gameDataService.GetCommonGaidenDataDictionary();
var eventFolderDictionary = gameDataService.GetEventFolderDictionary();
var songIntroDictionary = gameDataService.GetSongIntroductionDictionary();
CommonInitialDataCheckResponse.VerupNoData2[] verupNo2List =
[
GetVerupNoData2(Constants.DAN_VERUP_MASTER_TYPE, commonDanDataDictionary),
GetVerupNoData2(Constants.GAIDEN_VERUP_MASTER_TYPE, commonGaidenDataDictionary),
GetVerupNoData2(Constants.FOLDER_VERUP_MASTER_TYPE, eventFolderDictionary),
GetVerupNoData2(Constants.INTRO_VERUP_MASTER_TYPE, songIntroDictionary)
];
response.AryVerupNoData2s.AddRange(verupNo2List);
response.AryChassisFunctionIds =
[
Constants.FUNCTION_ID_DANI_AVAILABLE,
Constants.FUNCTION_ID_DANI_FOLDER_AVAILABLE,
Constants.FUNCTION_ID_AI_BATTLE_AVAILABLE
];
return Task.FromResult(response);
}
private CommonInitialDataCheckResponse.VerupNoData2 GetVerupNoData2<T>(uint masterType, ImmutableDictionary<uint, T> dictionary)
where T:IVerupNo
{
var infoData = dictionary.Select(pair => new CommonInitialDataCheckResponse.VerupNoData2.InformationData
{
InfoId = pair.Key,
VerupNo = pair.Value.VerupNo
}).ToList();
return new CommonInitialDataCheckResponse.VerupNoData2
{
MasterType = masterType,
AryInformationDatas = infoData
};
}
}

View File

@ -0,0 +1,57 @@
using GameDatabase.Context;
using TaikoLocalServer.Models.Application;
using Throw;
namespace TaikoLocalServer.Handlers;
public record GetSelfBestQuery(uint Baid, uint Difficulty, uint[] SongIdList) : IRequest<CommonSelfBestResponse>;
public class GetSelfBestQueryHandler(IGameDataService gameDataService, TaikoDbContext context, ILogger<GetSelfBestQueryHandler> logger)
: IRequestHandler<GetSelfBestQuery, CommonSelfBestResponse>
{
public async Task<CommonSelfBestResponse> Handle(GetSelfBestQuery request, CancellationToken cancellationToken)
{
var requestDifficulty = (Difficulty)request.Difficulty;
requestDifficulty.Throw().IfOutOfRange();
var allSongSet = gameDataService.GetMusicList().ToHashSet();
var requestSet = request.SongIdList.ToHashSet();
if (!requestSet.IsSubsetOf(allSongSet))
{
var invalidSongIds = requestSet.Except(allSongSet);
logger.LogWarning("Invalid song IDs: {InvalidSongIds}", invalidSongIds.Stringify());
requestSet.ExceptWith(invalidSongIds);
}
var selfbestScores = await context.SongBestData
.Where(datum => datum.Baid == request.Baid &&
requestSet.Contains(datum.SongId) &&
(datum.Difficulty == requestDifficulty ||
(datum.Difficulty == Difficulty.UraOni && requestDifficulty == Difficulty.Oni)))
.OrderBy(datum => datum.SongId)
.ToListAsync(cancellationToken);
var selfBestList = selfbestScores.ConvertAll(datum => new CommonSelfBestResponse.SelfBestData
{
SongNo = datum.SongId,
SelfBestScore = datum.BestScore,
UraBestScore = datum.Difficulty == Difficulty.UraOni ? datum.BestScore : 0,
SelfBestScoreRate = datum.BestRate,
UraBestScoreRate = datum.Difficulty == Difficulty.UraOni ? datum.BestRate : 0
});
// For songs that don't have a score, add them to the response with 0s
var missingSongs = requestSet.Except(selfBestList.Select(datum => datum.SongNo));
selfBestList.AddRange(missingSongs.Select(songNo => new CommonSelfBestResponse.SelfBestData
{
SongNo = songNo
}));
var response = new CommonSelfBestResponse
{
Result = 1,
Level = request.Difficulty,
ArySelfbestScores = selfBestList
};
return response;
}
}

View File

@ -0,0 +1,26 @@
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Handlers;
public record GetShopFolderQuery : IRequest<CommonGetShopFolderResponse>;
public class GetShopFolderHandler(IGameDataService gameDataService)
: IRequestHandler<GetShopFolderQuery, CommonGetShopFolderResponse>
{
public Task<CommonGetShopFolderResponse> Handle(GetShopFolderQuery request, CancellationToken cancellationToken)
{
gameDataService.GetTokenDataDictionary().TryGetValue("shopTokenId", out var shopTokenId);
var shopFolderList = gameDataService.GetShopFolderList();
var response = new CommonGetShopFolderResponse
{
Result = 1,
TokenId = shopTokenId > 0 ? (uint)shopTokenId : 1,
VerupNo = 2,
AryShopFolderDatas = shopFolderList
};
return Task.FromResult(response);
}
}

View File

@ -0,0 +1,32 @@
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Handlers;
public record GetSongIntroductionQuery(uint[] SetIds) : IRequest<CommonGetSongIntroductionResponse>;
public class GetSongIntroductionQueryHandler(IGameDataService gameDataService, ILogger<GetSongIntroductionQueryHandler> logger)
: IRequestHandler<GetSongIntroductionQuery, CommonGetSongIntroductionResponse>
{
public Task<CommonGetSongIntroductionResponse> Handle(GetSongIntroductionQuery request, CancellationToken cancellationToken)
{
var response = new CommonGetSongIntroductionResponse
{
Result = 1
};
foreach (var setId in request.SetIds)
{
gameDataService.GetSongIntroductionDictionary().TryGetValue(setId, out var introData);
if (introData is null)
{
logger.LogWarning("Requested set id {Id} does not exist!", setId);
continue;
}
response.ArySongIntroductionDatas.Add(introData);
}
return Task.FromResult(response);
}
}

View File

@ -0,0 +1,55 @@
using GameDatabase.Context;
using GameDatabase.Entities;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Handlers;
public record GetTokenCountQuery(uint Baid) : IRequest<CommonGetTokenCountResponse>;
public class GetTokenCountQueryHandler(IGameDataService gameDataService,
TaikoDbContext context,
ILogger<GetTokenCountQueryHandler> logger)
: IRequestHandler<GetTokenCountQuery, CommonGetTokenCountResponse>
{
public async Task<CommonGetTokenCountResponse> Handle(GetTokenCountQuery request, CancellationToken cancellationToken)
{
var response = new CommonGetTokenCountResponse
{
Result = 1
};
string[] tokenNames = ["shopTokenId", "kaTokenId", "onePieceTokenId", "soshinaTokenId", "Yatsushika1TokenId", "Yatsushika2TokenId",
"Yatsushika3TokenId", "Yatsushika4TokenId", "MaskedKid1TokenId", "MaskedKid2TokenId", "MaskedKid3TokenId", "MaskedKid4TokenId",
"Kiyoshi1TokenId", "Kiyoshi2TokenId", "Kiyoshi3TokenId", "Kiyoshi4TokenId", "Amitie1TokenId", "Amitie2TokenId", "Amitie3TokenId",
"Amitie4TokenId", "Machina1TokenId", "Machina2TokenId", "Machina3TokenId", "Machina4TokenId"];
var tokenDataDictionary = gameDataService.GetTokenDataDictionary();
foreach (var tokenName in tokenNames)
{
tokenDataDictionary.TryGetValue(tokenName, out var tokenId);
if (tokenId <= 0) continue;
var token = await context.Tokens.FirstOrDefaultAsync(t => t.Baid == request.Baid &&
t.Id == tokenId,
cancellationToken);
if (token is null)
{
token = new Token
{
Baid = request.Baid,
Id = tokenId,
Count = 0,
};
context.Tokens.Add(token);
}
response.AryTokenCountDatas.Add(new CommonTokenCountData
{
TokenId = (uint)token.Id,
TokenCount = token.Count
});
}
await context.SaveChangesAsync(cancellationToken);
return response;
}
}

View File

@ -0,0 +1,41 @@
using GameDatabase.Context;
using TaikoLocalServer.Models.Application;
using Throw;
namespace TaikoLocalServer.Handlers;
public record PurchaseSongCommand(uint Baid, uint SongNo, uint TokenId, uint Price) : IRequest<CommonSongPurchaseResponse>;
public class PurchaseSongCommandHandler(TaikoDbContext context, ILogger<PurchaseSongCommandHandler> logger)
: IRequestHandler<PurchaseSongCommand, CommonSongPurchaseResponse>
{
public async Task<CommonSongPurchaseResponse> Handle(PurchaseSongCommand request, CancellationToken cancellationToken)
{
var user = await context.UserData
.Include(u => u.Tokens)
.FirstOrDefaultAsync(u => u.Baid == request.Baid, cancellationToken);
user.ThrowIfNull($"User with baid {request.Baid} does not exist!");
if (user.UnlockedSongIdList.Contains(request.SongNo))
{
logger.LogWarning("User with baid {Baid} already has song with id {SongNo} unlocked!", request.Baid, request.SongNo);
return new CommonSongPurchaseResponse { Result = 0 };
}
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 new CommonSongPurchaseResponse { Result = 0 };
}
user.UnlockedSongIdList.Add(request.SongNo);
context.UserData.Update(user);
await context.SaveChangesAsync(cancellationToken);
return new CommonSongPurchaseResponse { Result = 1, TokenCount = token.Count };
}
}

View File

@ -0,0 +1,285 @@
using System.Text.Json;
using GameDatabase.Context;
using GameDatabase.Entities;
using TaikoLocalServer.Models.Application;
using Throw;
namespace TaikoLocalServer.Handlers;
public record UpdatePlayResultCommand(uint Baid, CommonPlayResultData PlayResultData) : IRequest<uint>;
public class UpdatePlayResultCommandHandler(TaikoDbContext context, ILogger<UpdatePlayResultCommandHandler> logger)
: IRequestHandler<UpdatePlayResultCommand, uint>
{
public async Task<uint> Handle(UpdatePlayResultCommand request, CancellationToken cancellationToken)
{
if (request.Baid == 0)
{
return 1;
}
var user = await context.UserData.FindAsync(request.Baid, cancellationToken);
if (user is null)
{
logger.LogWarning("Game uploading a non exisiting user with baid {Baid}", request.Baid);
return 1;
}
var lastPlayDateTime = DateTime.Now;
var playResultData = request.PlayResultData;
UpdateUserData(user, playResultData, lastPlayDateTime);
var playMode = (PlayMode)playResultData.PlayMode;
if (playMode is PlayMode.DanMode or PlayMode.GaidenMode)
{
var danType = playMode == PlayMode.DanMode ? DanType.Normal : DanType.Gaiden;
var danPlayData = await context.DanScoreData
.Include(datum => datum.DanStageScoreData)
.FirstOrDefaultAsync(datum => datum.Baid == request.Baid &&
datum.DanId == playResultData.DanId &&
datum.DanType == danType, cancellationToken);
if (danPlayData is null)
{
danPlayData = new DanScoreDatum
{
Baid = request.Baid,
DanId = playResultData.DanId,
DanType = danType
};
UpdateDanPlayData(danPlayData, playResultData);
context.DanScoreData.Add(danPlayData);
}
else
{
UpdateDanPlayData(danPlayData, playResultData);
context.DanScoreData.Update(danPlayData);
}
await context.SaveChangesAsync(cancellationToken);
return 1;
}
for (var songNumber = 0; songNumber < playResultData.AryStageInfoes.Count; songNumber++)
{
var stageData = playResultData.AryStageInfoes[songNumber];
if (playMode == PlayMode.AiBattle)
{
await UpdateAiBattleData(playResultData, stageData);
}
await UpdateBestData(playResultData, stageData);
var songPlayDatum = new SongPlayDatum
{
Baid = request.Baid,
SongNumber = (uint)songNumber,
GoodCount = stageData.GoodCnt,
OkCount = stageData.OkCnt,
MissCount = stageData.NgCnt,
ComboCount = stageData.ComboCnt,
HitCount = stageData.HitCnt,
DrumrollCount = stageData.PoundCnt,
Crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt),
Score = stageData.PlayScore,
ScoreRate = stageData.ScoreRate,
ScoreRank = (ScoreRank)stageData.ScoreRank,
Skipped = stageData.IsSkipUse,
SongId = stageData.SongNo,
PlayTime = lastPlayDateTime,
Difficulty = (Difficulty)stageData.Level
};
context.SongPlayData.Add(songPlayDatum);
}
await context.SaveChangesAsync(cancellationToken);
return 1;
}
private async Task UpdateBestData(CommonPlayResultData playResultData, CommonPlayResultData.StageData stageData)
{
var difficulty = (Difficulty)stageData.Level;
difficulty.Throw().IfOutOfRange();
var existing = await context.SongBestData.FindAsync(playResultData.Baid, stageData.SongNo, difficulty);
// Determine whether it is dondaful crown as this is not reflected by play result
var crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt);
if (existing is null)
{
existing = new SongBestDatum
{
Baid = playResultData.Baid,
SongId = stageData.SongNo,
Difficulty = difficulty,
BestScore = stageData.PlayScore,
BestRate = stageData.ScoreRate,
BestCrown = crown,
BestScoreRank = (ScoreRank)stageData.ScoreRank
};
context.SongBestData.Add(existing);
return;
}
existing.UpdateBestData(crown, stageData.ScoreRank, stageData.PlayScore, stageData.ScoreRate);
context.SongBestData.Update(existing);
}
private async Task UpdateAiBattleData(CommonPlayResultData playResultData, CommonPlayResultData.StageData stageData)
{
var difficulty = (Difficulty)stageData.Level;
difficulty.Throw().IfOutOfRange();
var existing = await context.AiScoreData
.Include(datum => datum.AiSectionScoreData)
.FirstOrDefaultAsync(datum => datum.Baid == playResultData.Baid &&
datum.SongId == stageData.SongNo &&
datum.Difficulty == difficulty);
if (existing is null)
{
existing = new AiScoreDatum
{
Baid = playResultData.Baid,
SongId = stageData.SongNo,
Difficulty = difficulty,
IsWin = stageData.IsWin
};
var aiSections = stageData.ArySectionDatas.Select((data, i) =>
{
var section = new AiSectionScoreDatum
{
Baid = playResultData.Baid,
SongId = stageData.SongNo,
Difficulty = difficulty,
SectionIndex = i,
OkCount = data.OkCnt,
MissCount = data.NgCnt
};
section.UpdateBest(data);
return section;
}
);
existing.AiSectionScoreData.AddRange(aiSections);
context.AiScoreData.Add(existing);
return;
}
for (var index = 0; index < stageData.ArySectionDatas.Count; index++)
{
var sectionData = stageData.ArySectionDatas[index];
if (index < existing.AiSectionScoreData.Count)
{
existing.AiSectionScoreData[index].UpdateBest(sectionData);
}
else
{
var aiSectionScoreDatum = new AiSectionScoreDatum
{
Baid = playResultData.Baid,
SongId = stageData.SongNo,
Difficulty = difficulty,
SectionIndex = index,
OkCount = sectionData.OkCnt,
MissCount = sectionData.NgCnt
};
aiSectionScoreDatum.UpdateBest(sectionData);
existing.AiSectionScoreData.Add(aiSectionScoreDatum);
context.AiScoreData.Update(existing);
}
}
}
private void UpdateDanPlayData(DanScoreDatum danPlayData, CommonPlayResultData playResultData)
{
danPlayData.ClearState =
(DanClearState)Math.Max(playResultData.DanResult, (uint)danPlayData.ClearState);
danPlayData.ArrivalSongCount =
Math.Max((uint)playResultData.AryStageInfoes.Count, danPlayData.ArrivalSongCount);
danPlayData.ComboCountTotal = Math.Max(playResultData.ComboCntTotal, danPlayData.ComboCountTotal);
danPlayData.SoulGaugeTotal = Math.Max(playResultData.SoulGaugeTotal, danPlayData.SoulGaugeTotal);
for (var i = 0; i < playResultData.AryStageInfoes.Count; i++)
{
var stageData = playResultData.AryStageInfoes[i];
var songNumber = i;
var danStageData = danPlayData.DanStageScoreData.FirstOrDefault(datum => datum.SongNumber == songNumber,
new DanStageScoreDatum
{
Baid = danPlayData.Baid,
DanId = danPlayData.DanId,
DanType = danPlayData.DanType,
SongNumber = (uint)songNumber,
OkCount = stageData.OkCnt,
BadCount = stageData.NgCnt
});
danStageData.HighScore = Math.Max(danStageData.HighScore, stageData.PlayScore);
danStageData.ComboCount = Math.Max(danStageData.ComboCount, stageData.ComboCnt);
danStageData.DrumrollCount = Math.Max(danStageData.DrumrollCount, stageData.PoundCnt);
danStageData.GoodCount = Math.Max(danStageData.GoodCount, stageData.GoodCnt);
danStageData.TotalHitCount = Math.Max(danStageData.TotalHitCount, stageData.HitCnt);
danStageData.OkCount = Math.Min(danStageData.OkCount, stageData.OkCnt);
danStageData.BadCount = Math.Min(danStageData.BadCount, stageData.NgCnt);
var index = danPlayData.DanStageScoreData.IndexOf(danStageData);
if (index == -1)
{
context.DanStageScoreData.Add(danStageData);
}
}
}
private void UpdateUserData(UserDatum user, CommonPlayResultData playResultData, DateTime lastPlayDateTime)
{
user.Title = playResultData.Title;
user.TitlePlateId = playResultData.TitleplateId;
var costumeData = new List<uint>
{
playResultData.AryCurrentCostume.Costume1,
playResultData.AryCurrentCostume.Costume2,
playResultData.AryCurrentCostume.Costume3,
playResultData.AryCurrentCostume.Costume4,
playResultData.AryCurrentCostume.Costume5
};
user.CostumeData = JsonSerializer.Serialize(costumeData);
user.LastPlayDatetime = lastPlayDateTime;
user.LastPlayMode = playResultData.PlayMode;
user.ToneFlgArray.AddRange(playResultData.GetToneNoes);
user.TitleFlgArray.AddRange(playResultData.GetTitleNoes);
user.UnlockedKigurumi.AddRange(playResultData.GetCostumeNo1s);
user.UnlockedHead.AddRange(playResultData.GetCostumeNo2s);
user.UnlockedBody.AddRange(playResultData.GetCostumeNo3s);
user.UnlockedFace.AddRange(playResultData.GetCostumeNo4s);
user.UnlockedPuchi.AddRange(playResultData.GetCostumeNo5s);
var genericInfo = user.GenericInfoFlgArray.ToList();
genericInfo.AddRange(playResultData.GetGenericInfoNoes);
user.GenericInfoFlgArray = genericInfo.ToArray();
var difficultyPlayedArray = new List<uint>
{
playResultData.DifficultyPlayedCourse,
playResultData.DifficultyPlayedStar,
playResultData.DifficultyPlayedSort
};
user.DifficultyPlayedArray = JsonSerializer.Serialize(difficultyPlayedArray);
user.AiWinCount += playResultData.AryStageInfoes.Count(data => data.IsWin);
context.UserData.Update(user);
}
private static CrownType PlayResultToCrown(uint playResult, uint okCount)
{
var crown = (CrownType)playResult;
if (crown == CrownType.Gold && okCount == 0)
{
crown = CrownType.Dondaful;
}
return crown;
}
}

View File

@ -0,0 +1,104 @@
using System.Buffers.Binary;
using GameDatabase.Context;
using TaikoLocalServer.Models.Application;
using Throw;
namespace TaikoLocalServer.Handlers;
public record UserDataQuery(uint Baid) : IRequest<CommonUserDataResponse>;
public class UserDataQueryHandler(TaikoDbContext context, IGameDataService gameDataService, ILogger<UserDataQueryHandler> logger)
: IRequestHandler<UserDataQuery, CommonUserDataResponse>
{
public async Task<CommonUserDataResponse> Handle(UserDataQuery request, CancellationToken cancellationToken)
{
var userData = await context.UserData.FindAsync(request.Baid, cancellationToken);
userData.ThrowIfNull($"User not found for Baid {request.Baid}!");
var unlockedSongIdList = userData.UnlockedSongIdList;
var musicList = gameDataService.GetMusicList();
var lockedSongsList = gameDataService.GetLockedSongsList();
lockedSongsList = lockedSongsList.Except(unlockedSongIdList).ToList();
var enabledMusicList = musicList.Except(lockedSongsList);
var releaseSongArray =
FlagCalculator.GetBitArrayFromIds(enabledMusicList, Constants.MUSIC_ID_MAX, logger);
var defaultSongWithUraList = gameDataService.GetMusicWithUraList();
var enabledUraMusicList = defaultSongWithUraList.Except(lockedSongsList);
var uraSongArray =
FlagCalculator.GetBitArrayFromIds(enabledUraMusicList, Constants.MUSIC_ID_MAX, logger);
if (userData.ToneFlgArray.Count == 0)
{
userData.ToneFlgArray = [0];
context.UserData.Update(userData);
await context.SaveChangesAsync(cancellationToken);
}
var toneArray = FlagCalculator.GetBitArrayFromIds(userData.ToneFlgArray, gameDataService.GetToneFlagArraySize(), logger);
var titleArray = FlagCalculator.GetBitArrayFromIds(userData.TitleFlgArray, gameDataService.GetTitleFlagArraySize(), logger);
var recentSongs = await context.SongPlayData
.Where(datum => datum.Baid == request.Baid)
.OrderByDescending(datum => datum.PlayTime)
.ThenByDescending(datum => datum.SongNumber)
.Select(datum => datum.SongId)
.ToArrayAsync(cancellationToken);
// Use custom implementation as distinctby cannot guarantee preserved element
var recentSet = new OrderedSet<uint>();
foreach (var id in recentSongs)
{
recentSet.Add(id);
if (recentSet.Count == 10)
{
break;
}
}
recentSongs = recentSet.ToArray();
var defaultOptions = new byte[2];
BinaryPrimitives.WriteInt16LittleEndian(defaultOptions, userData.OptionSetting);
var difficultySettingArray = JsonHelper.GetUIntArrayFromJson(userData.DifficultySettingArray, 3, logger, nameof(userData.DifficultySettingArray));
for (int i = 0; i < 3; i++)
{
if (difficultySettingArray[i] >= 2)
{
difficultySettingArray[i] -= 1;
}
}
var difficultyPlayedArray = JsonHelper.GetUIntArrayFromJson(userData.DifficultyPlayedArray, 3, logger, nameof(userData.DifficultyPlayedArray));
var response = new CommonUserDataResponse
{
Result = 1,
ToneFlg = toneArray,
TitleFlg = titleArray,
ReleaseSongFlg = releaseSongArray,
UraReleaseSongFlg = uraSongArray,
AryFavoriteSongNoes = userData.FavoriteSongsArray.ToArray(),
AryRecentSongNoes = recentSongs,
DefaultOptionSetting = defaultOptions,
NotesPosition = userData.NotesPosition,
IsVoiceOn = userData.IsVoiceOn,
IsSkipOn = userData.IsSkipOn,
DifficultySettingCourse = difficultySettingArray[0],
DifficultySettingStar = difficultySettingArray[1],
DifficultySettingSort = difficultySettingArray[2],
DifficultyPlayedCourse = difficultyPlayedArray[0],
DifficultyPlayedStar = difficultyPlayedArray[1],
DifficultyPlayedSort = difficultyPlayedArray[2],
SongRecentCnt = (uint)recentSongs.Length,
IsChallengecompe = false,
// TODO: Other fields
};
return response;
}
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class AddTokenCountRequestMapper
{
public static partial CommonAddTokenCountRequest Map(AddTokenCountRequest request);
public static partial CommonAddTokenCountRequest Map(Models.v3209.AddTokenCountRequest request);
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class AiDataResponseMapper
{
public static partial GetAiDataResponse MapTo3906(CommonAiDataResponse response);
public static partial Models.v3209.GetAiDataResponse MapTo3209(CommonAiDataResponse response);
}

View File

@ -0,0 +1,23 @@
using GameDatabase.Entities;
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class AiScoreMappers
{
[MapProperty(nameof(AiScoreDatum.AiSectionScoreData), nameof(CommonAiScoreResponse.AryBestSectionDatas))]
public static partial CommonAiScoreResponse MapToCommonAiScoreResponse(AiScoreDatum datum);
public static CommonAiScoreResponse MapAsSuccess(AiScoreDatum datum)
{
var response= MapToCommonAiScoreResponse(datum);
response.Result = 1;
return response;
}
public static partial GetAiScoreResponse MapTo3906(CommonAiScoreResponse response);
public static partial Models.v3209.GetAiScoreResponse MapTo3209(CommonAiScoreResponse response);
}

View File

@ -0,0 +1,50 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class BaidResponseMapper
{
public static partial BAIDResponse MapTo3906(CommonBaidResponse commonBaidResponse);
public static BAIDResponse Map3906WithPostProcess(CommonBaidResponse commonBaidResponse)
{
var response = MapTo3906(commonBaidResponse);
response.AryCostumedata = new BAIDResponse.CostumeData
{
Costume1 = commonBaidResponse.CostumeData[0],
Costume2 = commonBaidResponse.CostumeData[1],
Costume3 = commonBaidResponse.CostumeData[2],
Costume4 = commonBaidResponse.CostumeData[3],
Costume5 = commonBaidResponse.CostumeData[4]
};
response.CostumeFlg1 = commonBaidResponse.CostumeFlagArrays[0];
response.CostumeFlg2 = commonBaidResponse.CostumeFlagArrays[1];
response.CostumeFlg3 = commonBaidResponse.CostumeFlagArrays[2];
response.CostumeFlg4 = commonBaidResponse.CostumeFlagArrays[3];
response.CostumeFlg5 = commonBaidResponse.CostumeFlagArrays[4];
return response;
}
public static partial Models.v3209.BAIDResponse MapTo3209(CommonBaidResponse commonBaidResponse);
public static Models.v3209.BAIDResponse Map3209WithPostProcess(CommonBaidResponse commonBaidResponse)
{
var response = MapTo3209(commonBaidResponse);
response.AryCostumedata = new Models.v3209.BAIDResponse.CostumeData
{
Costume1 = commonBaidResponse.CostumeData[0],
Costume2 = commonBaidResponse.CostumeData[1],
Costume3 = commonBaidResponse.CostumeData[2],
Costume4 = commonBaidResponse.CostumeData[3],
Costume5 = commonBaidResponse.CostumeData[4]
};
response.CostumeFlg1 = commonBaidResponse.CostumeFlagArrays[0];
response.CostumeFlg2 = commonBaidResponse.CostumeFlagArrays[1];
response.CostumeFlg3 = commonBaidResponse.CostumeFlagArrays[2];
response.CostumeFlg4 = commonBaidResponse.CostumeFlagArrays[3];
response.CostumeFlg5 = commonBaidResponse.CostumeFlagArrays[4];
return response;
}
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using SharedProject.Models;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class DanDataMappers
{
public static partial GetDanOdaiResponse.OdaiData To3906OdaiData(DanData data);
public static partial Models.v3209.GetDanOdaiResponse.OdaiData To3209OdaiData(DanData data);
}

View File

@ -0,0 +1,11 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class DanScoreMappers
{
public static partial GetDanScoreResponse MapTo3906(CommonDanScoreDataResponse response);
public static partial Models.v3209.GetDanScoreResponse MapTo3209(CommonDanScoreDataResponse response);
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class FolderDataMappers
{
public static partial GetfolderResponse MapTo3906(CommonGetFolderResponse response);
public static partial Models.v3209.GetfolderResponse MapTo3209(CommonGetFolderResponse response);
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class InitialDataMappers
{
public static partial InitialdatacheckResponse MapTo3906(CommonInitialDataCheckResponse response);
public static partial Models.v3209.InitialdatacheckResponse MapTo3209(CommonInitialDataCheckResponse response);
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class MyDonEntryMappers
{
public static partial MydonEntryResponse MapTo3906(CommonMyDonEntryResponse response);
public static partial Models.v3209.MydonEntryResponse MapTo3209(CommonMyDonEntryResponse response);
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class PlayResultMappers
{
public static partial CommonPlayResultData Map(PlayResultDataRequest request);
public static partial CommonPlayResultData Map(Models.v3209.PlayResultDataRequest request);
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class SelfBestMappers
{
public static partial SelfBestResponse MapTo3906(CommonSelfBestResponse response);
public static partial Models.v3209.SelfBestResponse MapTo3209(CommonSelfBestResponse response);
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class ShopFolderDataMappers
{
public static partial GetShopFolderResponse MapTo3906(CommonGetShopFolderResponse response);
public static partial Models.v3209.GetShopFolderResponse MapTo3209(CommonGetShopFolderResponse response);
}

View File

@ -0,0 +1,13 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class SongIntroductionDataMappers
{
public static partial GetSongIntroductionResponse MapTo3906(CommonGetSongIntroductionResponse response);
public static partial Models.v3209.GetSongIntroductionResponse
MapTo3209(CommonGetSongIntroductionResponse response);
}

View File

@ -0,0 +1,17 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class SongPurchaseMappers
{
public static partial SongPurchaseResponse MapTo3906(CommonSongPurchaseResponse response);
public static partial Models.v3209.SongPurchaseResponse MapTo3209(CommonSongPurchaseResponse response);
public static partial PurchaseSongCommand MapToCommand(SongPurchaseRequest request);
public static partial PurchaseSongCommand MapToCommand(Models.v3209.SongPurchaseRequest request);
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class TokenCountDataMappers
{
public static partial GetTokenCountResponse MapTo3906(CommonGetTokenCountResponse response);
public static partial Models.v3209.GetTokenCountResponse MapTo3209(CommonGetTokenCountResponse response);
}

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class UserDataMappers
{
public static partial UserDataResponse MapTo3906(CommonUserDataResponse response);
public static partial Models.v3209.UserDataResponse MapTo3209(CommonUserDataResponse response);
}

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
#region Designer generated code
#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192
namespace taiko.game
namespace TaikoLocalServer.Models.v3906
{
[global::ProtoBuf.ProtoContract()]
public partial class HeartBeatRequest : global::ProtoBuf.IExtensible
@ -657,10 +657,10 @@ namespace taiko.game
public string Title { get; set; }
[global::ProtoBuf.ProtoMember(4, Name = @"ary_odai_song")]
public global::System.Collections.Generic.List<OdaiSong> AryOdaiSongs { get; } = new global::System.Collections.Generic.List<OdaiSong>();
public global::System.Collections.Generic.List<OdaiSong> OdaiSongList { get; } = new global::System.Collections.Generic.List<OdaiSong>();
[global::ProtoBuf.ProtoMember(5, Name = @"ary_odai_border")]
public global::System.Collections.Generic.List<OdaiBorder> AryOdaiBorders { get; } = new global::System.Collections.Generic.List<OdaiBorder>();
public global::System.Collections.Generic.List<OdaiBorder> OdaiBorderList { get; } = new global::System.Collections.Generic.List<OdaiBorder>();
[global::ProtoBuf.ProtoContract()]
public partial class OdaiSong : global::ProtoBuf.IExtensible
@ -1260,7 +1260,7 @@ namespace taiko.game
[global::ProtoBuf.ProtoMember(12, Name = @"mydon_name")]
[global::System.ComponentModel.DefaultValue("")]
public string MydonName
public string MyDonName
{
get => __pbn__MydonName ?? "";
set => __pbn__MydonName = value;
@ -1270,7 +1270,7 @@ namespace taiko.game
private string __pbn__MydonName;
[global::ProtoBuf.ProtoMember(13, Name = @"mydon_name_language")]
public uint MydonNameLanguage
public uint MyDonNameLanguage
{
get => __pbn__MydonNameLanguage.GetValueOrDefault();
set => __pbn__MydonNameLanguage = value;
@ -1291,7 +1291,7 @@ namespace taiko.game
private string __pbn__Title;
[global::ProtoBuf.ProtoMember(15, Name = @"titleplate_id")]
public uint TitleplateId
public uint TitlePlateId
{
get => __pbn__TitleplateId.GetValueOrDefault();
set => __pbn__TitleplateId = value;
@ -1395,7 +1395,7 @@ namespace taiko.game
private string __pbn__LastPlayDatetime;
[global::ProtoBuf.ProtoMember(26, Name = @"is_disp_dan_on")]
public bool IsDispDanOn
public bool DisplayDan
{
get => __pbn__IsDispDanOn.GetValueOrDefault();
set => __pbn__IsDispDanOn = value;
@ -1425,17 +1425,17 @@ namespace taiko.game
private byte[] __pbn__GotDanFlg;
[global::ProtoBuf.ProtoMember(29, Name = @"got_danextra_flg")]
public byte[] GotDanextraFlg
public byte[] GotGaidenFlg
{
get => __pbn__GotDanextraFlg;
set => __pbn__GotDanextraFlg = value;
get => pbnGotGaidenFlg;
set => pbnGotGaidenFlg = value;
}
public bool ShouldSerializeGotDanextraFlg() => __pbn__GotDanextraFlg != null;
public void ResetGotDanextraFlg() => __pbn__GotDanextraFlg = null;
private byte[] __pbn__GotDanextraFlg;
public bool ShouldSerializeGotDanextraFlg() => pbnGotGaidenFlg != null;
public void ResetGotDanextraFlg() => pbnGotGaidenFlg = null;
private byte[] pbnGotGaidenFlg;
[global::ProtoBuf.ProtoMember(30, Name = @"default_tone_setting")]
public uint DefaultToneSetting
public uint SelectedToneId
{
get => __pbn__DefaultToneSetting.GetValueOrDefault();
set => __pbn__DefaultToneSetting = value;
@ -2867,7 +2867,7 @@ namespace taiko.game
=> global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing);
[global::ProtoBuf.ProtoMember(1, Name = @"section_no", IsRequired = true)]
public uint SectionNo { get; set; }
public uint SectionIndex { get; set; }
[global::ProtoBuf.ProtoMember(2, Name = @"crown")]
public uint Crown
@ -2890,16 +2890,16 @@ namespace taiko.game
private uint? __pbn__Score;
[global::ProtoBuf.ProtoMember(4, Name = @"good_cnt", IsRequired = true)]
public uint GoodCnt { get; set; }
public uint GoodCount { get; set; }
[global::ProtoBuf.ProtoMember(5, Name = @"ok_cnt", IsRequired = true)]
public uint OkCnt { get; set; }
public uint OkCount { get; set; }
[global::ProtoBuf.ProtoMember(6, Name = @"ng_cnt", IsRequired = true)]
public uint NgCnt { get; set; }
public uint MissCount { get; set; }
[global::ProtoBuf.ProtoMember(7, Name = @"pound_cnt", IsRequired = true)]
public uint PoundCnt { get; set; }
public uint DrumrollCount { get; set; }
}

View File

@ -0,0 +1,13 @@
namespace TaikoLocalServer.Models.Application;
public class CommonAddTokenCountRequest
{
public uint Baid { get; set; }
public List<AddTokenCountData> AryAddTokenCountDatas { get; set; } = new();
}
public class AddTokenCountData
{
public uint TokenId { get; set; }
public int AddTokenCount { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace TaikoLocalServer.Models.Application;
public class CommonAiDataResponse
{
public uint Result { get; set; }
public uint TotalWinnings { get; set; }
public string InputMedian { get; set; } = "1";
public string InputVariance { get; set; } = "0";
}

View File

@ -0,0 +1,18 @@
namespace TaikoLocalServer.Models.Application;
public class CommonAiScoreResponse
{
public uint Result { get; set; }
public List<CommonAiBestSectionData> AryBestSectionDatas { get; set; } = new();
}
public class CommonAiBestSectionData
{
public uint SectionIndex { get; set; }
public uint Crown { get; set; }
public uint Score { get; set; }
public uint GoodCount { get; set; }
public uint OkCount { get; set; }
public uint MissCount { get; set; }
public uint DrumrollCount { get; set; }
}

View File

@ -0,0 +1,31 @@
namespace TaikoLocalServer.Models.Application;
public class CommonBaidResponse
{
public uint Result { get; set; }
public bool IsNewUser { get; set; }
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 ColorFace { get; set; }
public uint ColorBody { get; set; }
public uint ColorLimb { get; set; }
public List<uint> CostumeData { get; set; } = new() { 0, 0, 0, 0, 0 };
public List<byte[]> CostumeFlagArrays { get; set; }
= new() { Array.Empty<byte>(), Array.Empty<byte>(), Array.Empty<byte>(), Array.Empty<byte>(), Array.Empty<byte>() };
public string LastPlayDatetime { get; set; } = DateTime.Now.ToString(Constants.DATE_TIME_FORMAT);
public bool DisplayDan { get; set; }
public uint GotDanMax { get; set; }
public byte[] GotDanFlg { get; set; } = Array.Empty<byte>();
public byte[] GotGaidenFlg { get; set; } = Array.Empty<byte>();
public uint SelectedToneId { get; set; }
public byte[] GenericInfoFlg { get; set; } = Array.Empty<byte>();
public uint[] AryCrownCounts { get; set; } = Array.Empty<uint>();
public uint[] AryScoreRankCounts { get; set; } = Array.Empty<uint>();
public bool IsDispAchievementOn { get; set; }
public uint DispAchievementType { get; set; }
public uint LastPlayMode { get; set; }
}

Some files were not shown because too many files have changed in this diff Show More