diff --git a/DragaliaAPI.Database/ApiContext.cs b/DragaliaAPI.Database/ApiContext.cs index 734464f7b..731a5041e 100644 --- a/DragaliaAPI.Database/ApiContext.cs +++ b/DragaliaAPI.Database/ApiContext.cs @@ -184,5 +184,7 @@ but EF Core doesn't like this and the client probably stops you anyway? public DbSet Emblems { get; set; } + public DbSet QuestEvents { get; set; } + public DbSet PartyPowers { get; set; } } diff --git a/DragaliaAPI.Database/Entities/DbPlayer.cs b/DragaliaAPI.Database/Entities/DbPlayer.cs index f84671e3a..29a28e752 100644 --- a/DragaliaAPI.Database/Entities/DbPlayer.cs +++ b/DragaliaAPI.Database/Entities/DbPlayer.cs @@ -91,5 +91,7 @@ public class DbPlayer public virtual DbPlayerShopInfo? ShopInfo { get; set; } + public virtual ICollection QuestEvents { get; set; } = new List(); + public virtual DbPartyPower? PartyPower { get; set; } } diff --git a/DragaliaAPI.Database/Entities/DbQuest.cs b/DragaliaAPI.Database/Entities/DbQuest.cs index 77faeffd4..8f8f86649 100644 --- a/DragaliaAPI.Database/Entities/DbQuest.cs +++ b/DragaliaAPI.Database/Entities/DbQuest.cs @@ -33,9 +33,9 @@ public class DbQuest : IDbHasAccountId public int WeeklyPlayCount { get; set; } = 0; - public int LastDailyResetTime { get; set; } = 0; + public DateTimeOffset LastDailyResetTime { get; set; } = DateTimeOffset.UnixEpoch; - public int LastWeeklyResetTime { get; set; } = 0; + public DateTimeOffset LastWeeklyResetTime { get; set; } = DateTimeOffset.UnixEpoch; public bool IsAppear { get; set; } = true; diff --git a/DragaliaAPI.Database/Entities/DbQuestEvent.cs b/DragaliaAPI.Database/Entities/DbQuestEvent.cs new file mode 100644 index 000000000..7924c601b --- /dev/null +++ b/DragaliaAPI.Database/Entities/DbQuestEvent.cs @@ -0,0 +1,46 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace DragaliaAPI.Database.Entities; + +[Index(nameof(DeviceAccountId))] +[PrimaryKey(nameof(DeviceAccountId), nameof(QuestEventId))] +public class DbQuestEvent : IDbHasAccountId +{ + /// + public virtual DbPlayer? Owner { get; set; } + + /// + [ForeignKey(nameof(Owner))] + public required string DeviceAccountId { get; set; } + + [Column("QuestEventId")] + public required int QuestEventId { get; set; } + + [Column("DailyPlayCount")] + public int DailyPlayCount { get; set; } + + [Column("LastDailyResetTime")] + public DateTimeOffset LastDailyResetTime { get; set; } = DateTimeOffset.UnixEpoch; + + [Column("WeeklyPlayCount")] + public int WeeklyPlayCount { get; set; } + + [Column("LastWeeklyResetTime")] + public DateTimeOffset LastWeeklyResetTime { get; set; } = DateTimeOffset.UnixEpoch; + + [Column("QuestBonusReceiveCount")] + public int QuestBonusReceiveCount { get; set; } + + [Column("QuestBonusStackCount")] + public int QuestBonusStackCount { get; set; } + + [Column("QuestBonusStackTime")] + public DateTimeOffset QuestBonusStackTime { get; set; } = DateTimeOffset.UnixEpoch; + + [Column("QuestBonusReserveCount")] + public int QuestBonusReserveCount { get; set; } + + [Column("QuestBonusReserveTime")] + public DateTimeOffset QuestBonusReserveTime { get; set; } = DateTimeOffset.UnixEpoch; +} diff --git a/DragaliaAPI.Database/Migrations/20230808101930_quest-event-1.Designer.cs b/DragaliaAPI.Database/Migrations/20230808101930_quest-event-1.Designer.cs new file mode 100644 index 000000000..ac3ab6f99 --- /dev/null +++ b/DragaliaAPI.Database/Migrations/20230808101930_quest-event-1.Designer.cs @@ -0,0 +1,2348 @@ +// +using System; +using DragaliaAPI.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DragaliaAPI.Database.Migrations +{ + [DbContext(typeof(ApiContext))] + [Migration("20230808101930_quest-event-1")] + partial class questevent1 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbAbilityCrest", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("AbilityCrestId") + .HasColumnType("integer"); + + b.Property("AttackPlusCount") + .HasColumnType("integer"); + + b.Property("BuildupCount") + .HasColumnType("integer"); + + b.Property("EquipableCount") + .HasColumnType("integer"); + + b.Property("GetTime") + .HasColumnType("timestamp with time zone"); + + b.Property("HpPlusCount") + .HasColumnType("integer"); + + b.Property("IsFavorite") + .HasColumnType("boolean"); + + b.Property("IsNew") + .HasColumnType("boolean"); + + b.Property("LimitBreakCount") + .HasColumnType("integer"); + + b.HasKey("DeviceAccountId", "AbilityCrestId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerAbilityCrests"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbAbilityCrestSet", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("AbilityCrestSetNo") + .HasColumnType("integer"); + + b.Property("AbilityCrestSetName") + .IsRequired() + .HasColumnType("text"); + + b.Property("CrestSlotType1CrestId1") + .HasColumnType("integer"); + + b.Property("CrestSlotType1CrestId2") + .HasColumnType("integer"); + + b.Property("CrestSlotType1CrestId3") + .HasColumnType("integer"); + + b.Property("CrestSlotType2CrestId1") + .HasColumnType("integer"); + + b.Property("CrestSlotType2CrestId2") + .HasColumnType("integer"); + + b.Property("CrestSlotType3CrestId1") + .HasColumnType("integer"); + + b.Property("CrestSlotType3CrestId2") + .HasColumnType("integer"); + + b.Property("TalismanKeyId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("DeviceAccountId", "AbilityCrestSetNo"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerAbilityCrestSets"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbDeviceAccount", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("HashedPassword") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DeviceAccounts"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbEmblem", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("EmblemId") + .HasColumnType("integer") + .HasColumnName("EmblemId"); + + b.Property("GetTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("GetTime"); + + b.Property("IsNew") + .HasColumnType("boolean") + .HasColumnName("IsNew"); + + b.HasKey("DeviceAccountId", "EmblemId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("Emblems"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbEquippedStamp", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("Slot") + .HasColumnType("integer"); + + b.Property("StampId") + .HasColumnType("integer"); + + b.HasKey("DeviceAccountId", "Slot"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("EquippedStamps"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbFortBuild", b => + { + b.Property("BuildId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BuildId")); + + b.Property("BuildEndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("BuildStartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceAccountId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsNew") + .HasColumnType("boolean"); + + b.Property("LastIncomeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("PlantId") + .HasColumnType("integer"); + + b.Property("PositionX") + .HasColumnType("integer"); + + b.Property("PositionZ") + .HasColumnType("integer"); + + b.HasKey("BuildId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerFortBuilds"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbFortDetail", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("CarpenterNum") + .HasColumnType("integer") + .HasColumnName("CarpenterNum"); + + b.HasKey("DeviceAccountId"); + + b.HasIndex("DeviceAccountId") + .IsUnique(); + + b.ToTable("PlayerFortDetail"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbLoginBonus", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("Id") + .HasColumnType("integer"); + + b.Property("CurrentDay") + .HasColumnType("integer"); + + b.Property("IsComplete") + .HasColumnType("boolean"); + + b.HasKey("DeviceAccountId", "Id"); + + b.ToTable("LoginBonuses"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbParty", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("PartyNo") + .HasColumnType("integer"); + + b.Property("PartyName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("DeviceAccountId", "PartyNo"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PartyData"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPartyUnit", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CharaId") + .HasColumnType("integer"); + + b.Property("DeviceAccountId") + .IsRequired() + .HasColumnType("text"); + + b.Property("EditSkill1CharaId") + .HasColumnType("integer"); + + b.Property("EditSkill2CharaId") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType1CrestId1") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType1CrestId2") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType1CrestId3") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType2CrestId1") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType2CrestId2") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType3CrestId1") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType3CrestId2") + .HasColumnType("integer"); + + b.Property("EquipDragonKeyId") + .HasColumnType("bigint"); + + b.Property("EquipTalismanKeyId") + .HasColumnType("bigint"); + + b.Property("EquipWeaponBodyId") + .HasColumnType("integer"); + + b.Property("EquipWeaponSkinId") + .HasColumnType("integer"); + + b.Property("PartyNo") + .HasColumnType("integer"); + + b.Property("UnitNo") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeviceAccountId"); + + b.HasIndex("DeviceAccountId", "PartyNo"); + + b.ToTable("PlayerPartyUnits"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayer", b => + { + b.Property("AccountId") + .HasColumnType("text"); + + b.Property("SavefileVersion") + .HasColumnType("integer"); + + b.HasKey("AccountId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerBannerData", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("SummonBannerId") + .HasColumnType("integer") + .HasColumnName("SummonBannerId"); + + b.Property("ConsecutionSummonPoints") + .HasColumnType("integer") + .HasColumnName("CsSummonPoints"); + + b.Property("ConsecutionSummonPointsMaxDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("CsSummonPointsMaxDate"); + + b.Property("ConsecutionSummonPointsMinDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("CsSummonPointsMinDate"); + + b.Property("DailyLimitedSummonCount") + .HasColumnType("integer") + .HasColumnName("DailyLimitedSummons"); + + b.Property("IsBeginnerFreeSummonAvailable") + .HasColumnType("integer") + .HasColumnName("BeginnerSummonAvailable"); + + b.Property("IsConsecutionFreeSummonAvailable") + .HasColumnType("integer") + .HasColumnName("CsSummonAvailable"); + + b.Property("IsFreeSummonAvailable") + .HasColumnType("integer") + .HasColumnName("FreeSummonAvailable"); + + b.Property("PityRate") + .HasColumnType("smallint") + .HasColumnName("Pity"); + + b.Property("SummonCount") + .HasColumnType("integer") + .HasColumnName("SummonCount"); + + b.Property("SummonPoints") + .HasColumnType("integer") + .HasColumnName("SummonPoints"); + + b.HasKey("DeviceAccountId", "SummonBannerId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerBannerData"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerCharaData", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("CharaId") + .HasColumnType("integer") + .HasColumnName("CharaId"); + + b.Property("Ability1Level") + .HasColumnType("smallint") + .HasColumnName("Abil1Lvl"); + + b.Property("Ability2Level") + .HasColumnType("smallint") + .HasColumnName("Abil2Lvl"); + + b.Property("Ability3Level") + .HasColumnType("smallint") + .HasColumnName("Abil3Lvl"); + + b.Property("AttackBase") + .HasColumnType("integer") + .HasColumnName("AtkBase"); + + b.Property("AttackNode") + .HasColumnType("integer") + .HasColumnName("AtkNode"); + + b.Property("AttackPlusCount") + .HasColumnType("smallint") + .HasColumnName("AtkPlusCount"); + + b.Property("BurstAttackLevel") + .HasColumnType("smallint") + .HasColumnName("BurstAtkLvl"); + + b.Property("ComboBuildupCount") + .HasColumnType("integer") + .HasColumnName("ComboBuildupCount"); + + b.Property("ExAbility2Level") + .HasColumnType("smallint") + .HasColumnName("ExAbility2Lvl"); + + b.Property("ExAbilityLevel") + .HasColumnType("smallint") + .HasColumnName("ExAbility1Lvl"); + + b.Property("Exp") + .HasColumnType("integer") + .HasColumnName("Exp"); + + b.Property("GetTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("GetTime"); + + b.Property("HpBase") + .HasColumnType("integer") + .HasColumnName("HpBase"); + + b.Property("HpNode") + .HasColumnType("integer") + .HasColumnName("HpNode"); + + b.Property("HpPlusCount") + .HasColumnType("smallint") + .HasColumnName("HpPlusCount"); + + b.Property("IsNew") + .HasColumnType("boolean") + .HasColumnName("IsNew"); + + b.Property("IsTemporary") + .HasColumnType("boolean") + .HasColumnName("IsTemp"); + + b.Property("IsUnlockEditSkill") + .HasColumnType("boolean") + .HasColumnName("IsUnlockEditSkill"); + + b.Property("Level") + .HasColumnType("smallint") + .HasColumnName("Level"); + + b.Property("ListViewFlag") + .HasColumnType("boolean") + .HasColumnName("ListViewFlag"); + + b.Property("ManaNodeUnlockCount") + .HasColumnType("integer") + .HasColumnName("ManaNodeUnlockCount"); + + b.Property("Rarity") + .HasColumnType("smallint") + .HasColumnName("Rarity"); + + b.Property("Skill1Level") + .HasColumnType("smallint") + .HasColumnName("Skill1Lvl"); + + b.Property("Skill2Level") + .HasColumnType("smallint") + .HasColumnName("Skill2Lvl"); + + b.HasKey("DeviceAccountId", "CharaId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerCharaData"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerCurrency", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("CurrencyType") + .HasColumnType("integer") + .HasColumnName("CurrencyType"); + + b.Property("Quantity") + .HasColumnType("bigint") + .HasColumnName("Quantity"); + + b.HasKey("DeviceAccountId", "CurrencyType"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerCurrency"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeChara", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("CharaId") + .HasColumnType("integer") + .HasColumnName("CharaId"); + + b.Property("MaxFloor") + .HasColumnType("integer") + .HasColumnName("MaxFloor"); + + b.Property("MaxScore") + .HasColumnType("integer") + .HasColumnName("MaxScore"); + + b.Property("SelectEditSkillCharaId1") + .HasColumnType("integer") + .HasColumnName("SelectEditSkillCharaId1"); + + b.Property("SelectEditSkillCharaId2") + .HasColumnType("integer") + .HasColumnName("SelectEditSkillCharaId2"); + + b.Property("SelectEditSkillCharaId3") + .HasColumnType("integer") + .HasColumnName("SelectEditSkillCharaId3"); + + b.Property("SelectedServitorId") + .HasColumnType("integer") + .HasColumnName("SelectedServitorId"); + + b.HasKey("DeviceAccountId", "CharaId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerDmodeCharas"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeDungeon", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("CharaId") + .HasColumnType("integer") + .HasColumnName("CharaId"); + + b.Property("DungeonScore") + .HasColumnType("integer") + .HasColumnName("DungeonScore"); + + b.Property("Floor") + .HasColumnType("integer") + .HasColumnName("Floor"); + + b.Property("IsPlayEnd") + .HasColumnType("boolean") + .HasColumnName("IsPlayEnd"); + + b.Property("QuestTime") + .HasColumnType("integer") + .HasColumnName("QuestTime"); + + b.Property("State") + .HasColumnType("integer") + .HasColumnName("State"); + + b.HasKey("DeviceAccountId"); + + b.ToTable("PlayerDmodeDungeons"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeExpedition", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("CharaId1") + .HasColumnType("integer") + .HasColumnName("CharaId1"); + + b.Property("CharaId2") + .HasColumnType("integer") + .HasColumnName("CharaId2"); + + b.Property("CharaId3") + .HasColumnType("integer") + .HasColumnName("CharaId3"); + + b.Property("CharaId4") + .HasColumnType("integer") + .HasColumnName("CharaId4"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("StartTime"); + + b.Property("State") + .HasColumnType("integer") + .HasColumnName("State"); + + b.Property("TargetFloor") + .HasColumnType("integer") + .HasColumnName("TargetFloor"); + + b.HasKey("DeviceAccountId"); + + b.ToTable("PlayerDmodeExpeditions"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeInfo", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("FloorSkipCount") + .HasColumnType("integer") + .HasColumnName("FloorSkipCount"); + + b.Property("FloorSkipTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("FloorSkipTime"); + + b.Property("Point1Quantity") + .HasColumnType("integer") + .HasColumnName("Point1Quantity"); + + b.Property("Point2Quantity") + .HasColumnType("integer") + .HasColumnName("Point2Quantity"); + + b.Property("RecoveryCount") + .HasColumnType("integer") + .HasColumnName("RecoveryCount"); + + b.Property("RecoveryTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("RecoveryTime"); + + b.HasKey("DeviceAccountId"); + + b.ToTable("PlayerDmodeInfos"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeServitorPassive", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("PassiveId") + .HasColumnType("integer") + .HasColumnName("PassiveId"); + + b.Property("Level") + .HasColumnType("integer") + .HasColumnName("Level"); + + b.HasKey("DeviceAccountId", "PassiveId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerDmodeServitorPassives"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDragonData", b => + { + b.Property("DragonKeyId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("DragonKeyId"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("DragonKeyId")); + + b.Property("Ability1Level") + .HasColumnType("smallint") + .HasColumnName("Abil1Level"); + + b.Property("Ability2Level") + .HasColumnType("smallint") + .HasColumnName("Abil2Level"); + + b.Property("AttackPlusCount") + .HasColumnType("smallint") + .HasColumnName("AttackPlusCount"); + + b.Property("DeviceAccountId") + .IsRequired() + .HasColumnType("text"); + + b.Property("DragonId") + .HasColumnType("integer") + .HasColumnName("DragonId"); + + b.Property("Exp") + .HasColumnType("integer") + .HasColumnName("Exp"); + + b.Property("GetTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("GetTime"); + + b.Property("HpPlusCount") + .HasColumnType("smallint") + .HasColumnName("HpPlusCount"); + + b.Property("IsLock") + .HasColumnType("boolean") + .HasColumnName("IsLocked"); + + b.Property("IsNew") + .HasColumnType("boolean") + .HasColumnName("IsNew"); + + b.Property("Level") + .HasColumnType("smallint") + .HasColumnName("Level"); + + b.Property("LimitBreakCount") + .HasColumnType("smallint") + .HasColumnName("LimitBreakCount"); + + b.Property("Skill1Level") + .HasColumnType("smallint") + .HasColumnName("Skill1Level"); + + b.HasKey("DragonKeyId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerDragonData"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDragonGift", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text") + .HasColumnName("DeviceAccountId"); + + b.Property("DragonGiftId") + .HasColumnType("integer") + .HasColumnName("DragonGiftId"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("Quantity"); + + b.HasKey("DeviceAccountId", "DragonGiftId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerDragonGift"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDragonReliability", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("DragonId") + .HasColumnType("integer") + .HasColumnName("DragonId"); + + b.Property("Exp") + .HasColumnType("integer") + .HasColumnName("TotalExp"); + + b.Property("GetTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LastContactTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastContactTime"); + + b.Property("Level") + .HasColumnType("smallint") + .HasColumnName("Level"); + + b.HasKey("DeviceAccountId", "DragonId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerDragonReliability"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventData", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("EventId") + .HasColumnType("integer") + .HasColumnName("EventId"); + + b.Property("CustomEventFlag") + .HasColumnType("boolean") + .HasColumnName("CustomEventFlag"); + + b.HasKey("DeviceAccountId", "EventId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerEventData"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventItem", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("Id"); + + b.Property("EventId") + .HasColumnType("integer") + .HasColumnName("EventId"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("Quantity"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("Type"); + + b.HasKey("DeviceAccountId", "Id"); + + b.HasIndex("DeviceAccountId", "EventId"); + + b.ToTable("PlayerEventItems"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventPassive", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("EventId") + .HasColumnType("integer") + .HasColumnName("EventId"); + + b.Property("PassiveId") + .HasColumnType("integer") + .HasColumnName("PassiveId"); + + b.Property("Progress") + .HasColumnType("integer") + .HasColumnName("Progress"); + + b.HasKey("DeviceAccountId", "EventId", "PassiveId"); + + b.HasIndex("DeviceAccountId", "EventId"); + + b.ToTable("PlayerEventPassives"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventReward", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("EventId") + .HasColumnType("integer") + .HasColumnName("EventId"); + + b.Property("RewardId") + .HasColumnType("integer") + .HasColumnName("RewardId"); + + b.HasKey("DeviceAccountId", "EventId", "RewardId"); + + b.HasIndex("DeviceAccountId", "EventId"); + + b.ToTable("PlayerEventRewards"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerMaterial", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("MaterialId") + .HasColumnType("integer") + .HasColumnName("MaterialId"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("Quantity"); + + b.HasKey("DeviceAccountId", "MaterialId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerMaterial"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerMission", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("MissionId"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("Type"); + + b.Property("End") + .HasColumnType("timestamp with time zone") + .HasColumnName("EndDate"); + + b.Property("GroupId") + .HasColumnType("integer") + .HasColumnName("GroupId"); + + b.Property("Pickup") + .HasColumnType("boolean") + .HasColumnName("Pickup"); + + b.Property("Progress") + .HasColumnType("integer") + .HasColumnName("Progress"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("StartDate"); + + b.Property("State") + .HasColumnType("integer") + .HasColumnName("State"); + + b.HasKey("DeviceAccountId", "Id", "Type"); + + b.ToTable("PlayerMissions"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerPresent", b => + { + b.Property("PresentId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("PresentId"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PresentId")); + + b.Property("CreateTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreateTime"); + + b.Property("DeviceAccountId") + .IsRequired() + .HasColumnType("text"); + + b.Property("EntityId") + .HasColumnType("integer") + .HasColumnName("EntityId"); + + b.Property("EntityLevel") + .HasColumnType("integer") + .HasColumnName("EntityLevel"); + + b.Property("EntityLimitBreakCount") + .HasColumnType("integer") + .HasColumnName("EntityLimitBreakCount"); + + b.Property("EntityQuantity") + .HasColumnType("integer") + .HasColumnName("EntityQuantity"); + + b.Property("EntityStatusPlusCount") + .HasColumnType("integer") + .HasColumnName("EntityStatusPlusCount"); + + b.Property("EntityType") + .HasColumnType("integer") + .HasColumnName("EntityType"); + + b.Property("MasterId") + .HasColumnType("bigint") + .HasColumnName("MasterId"); + + b.Property("MessageId") + .HasColumnType("integer") + .HasColumnName("MessageId"); + + b.Property("MessageParamValue1") + .HasColumnType("integer") + .HasColumnName("MessageParamValue1"); + + b.Property("MessageParamValue2") + .HasColumnType("integer") + .HasColumnName("MessageParamValue2"); + + b.Property("MessageParamValue3") + .HasColumnType("integer") + .HasColumnName("MessageParamValue3"); + + b.Property("MessageParamValue4") + .HasColumnType("integer") + .HasColumnName("MessageParamValue4"); + + b.Property("ReceiveLimitTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ReceiveLimitTime"); + + b.Property("State") + .HasColumnType("bigint") + .HasColumnName("State"); + + b.HasKey("PresentId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerPresent"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerPresentHistory", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("CreateTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("CreateTime"); + + b.Property("DeviceAccountId") + .IsRequired() + .HasColumnType("text"); + + b.Property("EntityId") + .HasColumnType("integer") + .HasColumnName("EntityId"); + + b.Property("EntityLevel") + .HasColumnType("integer") + .HasColumnName("EntityLevel"); + + b.Property("EntityLimitBreakCount") + .HasColumnType("integer") + .HasColumnName("EntityLimitBreakCount"); + + b.Property("EntityQuantity") + .HasColumnType("integer") + .HasColumnName("EntityQuantity"); + + b.Property("EntityStatusPlusCount") + .HasColumnType("integer") + .HasColumnName("EntityStatusPlusCount"); + + b.Property("EntityType") + .HasColumnType("integer") + .HasColumnName("EntityType"); + + b.Property("MessageId") + .HasColumnType("integer") + .HasColumnName("MessageId"); + + b.Property("MessageParamValue1") + .HasColumnType("integer") + .HasColumnName("MessageParamValue1"); + + b.Property("MessageParamValue2") + .HasColumnType("integer") + .HasColumnName("MessageParamValue2"); + + b.Property("MessageParamValue3") + .HasColumnType("integer") + .HasColumnName("MessageParamValue3"); + + b.Property("MessageParamValue4") + .HasColumnType("integer") + .HasColumnName("MessageParamValue4"); + + b.HasKey("Id"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerPresentHistory"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerShopInfo", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("DailySummonCount") + .HasColumnType("integer"); + + b.Property("LastSummonTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("DeviceAccountId"); + + b.ToTable("PlayerShopInfos"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerShopPurchase", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("GoodsId") + .HasColumnType("integer"); + + b.Property("BuyCount") + .HasColumnType("integer"); + + b.Property("EffectEndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectStartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LastBuyTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ShopType") + .HasColumnType("integer"); + + b.HasKey("DeviceAccountId", "GoodsId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerPurchases"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerStoryState", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("StoryType") + .HasColumnType("integer") + .HasColumnName("StoryType"); + + b.Property("StoryId") + .HasColumnType("integer") + .HasColumnName("StoryId"); + + b.Property("State") + .HasColumnType("integer") + .HasColumnName("State"); + + b.HasKey("DeviceAccountId", "StoryType", "StoryId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerStoryState"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerSummonHistory", b => + { + b.Property("KeyId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("KeyId")); + + b.Property("DeviceAccountId") + .IsRequired() + .HasColumnType("text"); + + b.Property("EntityAttackPlusCount") + .HasColumnType("integer") + .HasColumnName("AtkPlusCount"); + + b.Property("EntityHpPlusCount") + .HasColumnType("integer") + .HasColumnName("HpPlusCount"); + + b.Property("EntityId") + .HasColumnType("integer") + .HasColumnName("EntityId"); + + b.Property("EntityLevel") + .HasColumnType("smallint") + .HasColumnName("Level"); + + b.Property("EntityLimitBreakCount") + .HasColumnType("smallint") + .HasColumnName("LimitBreakCount"); + + b.Property("EntityQuantity") + .HasColumnType("integer") + .HasColumnName("Quantity"); + + b.Property("EntityRarity") + .HasColumnType("smallint") + .HasColumnName("Rarity"); + + b.Property("EntityType") + .HasColumnType("integer") + .HasColumnName("EntityType"); + + b.Property("ExecDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("SummonDate"); + + b.Property("GetDewPointQuantity") + .HasColumnType("integer") + .HasColumnName("DewPointGet"); + + b.Property("PaymentType") + .HasColumnType("integer") + .HasColumnName("PaymentType"); + + b.Property("SummonExecType") + .HasColumnType("smallint") + .HasColumnName("SummonExecType"); + + b.Property("SummonId") + .HasColumnType("integer") + .HasColumnName("BannerId"); + + b.Property("SummonPoint") + .HasColumnType("integer") + .HasColumnName("SummonPointGet"); + + b.Property("SummonPrizeRank") + .HasColumnType("integer") + .HasColumnName("SummonPrizeRank"); + + b.HasKey("KeyId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerSummonHistory"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerTrade", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("TradeId"); + + b.Property("Count") + .HasColumnType("integer") + .HasColumnName("TradeCount"); + + b.Property("LastTradeTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastTrade"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("TradeType"); + + b.HasKey("DeviceAccountId", "Id"); + + b.HasIndex("DeviceAccountId"); + + b.HasIndex("DeviceAccountId", "Type"); + + b.ToTable("PlayerTrades"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerUseItem", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("ItemId") + .HasColumnType("integer") + .HasColumnName("ItemId"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("Quantity"); + + b.HasKey("DeviceAccountId", "ItemId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerUseItems"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerUserData", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("ActiveMemoryEventId") + .HasColumnType("integer"); + + b.Property("BuildTimePoint") + .HasColumnType("integer"); + + b.Property("Coin") + .HasColumnType("bigint"); + + b.Property("CreateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Crystal") + .HasColumnType("integer"); + + b.Property("DewPoint") + .HasColumnType("integer"); + + b.Property("EmblemId") + .HasColumnType("integer"); + + b.Property("Exp") + .HasColumnType("integer"); + + b.Property("FortOpenTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LastLoginTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSaveImportTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStaminaMultiUpdateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStaminaSingleUpdateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("MainPartyNo") + .HasColumnType("integer"); + + b.Property("ManaPoint") + .HasColumnType("integer"); + + b.Property("MaxDragonQuantity") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("QuestSkipPoint") + .HasColumnType("integer"); + + b.Property("StaminaMulti") + .HasColumnType("integer"); + + b.Property("StaminaMultiSurplusSecond") + .HasColumnType("integer"); + + b.Property("StaminaSingle") + .HasColumnType("integer"); + + b.Property("StaminaSingleSurplusSecond") + .HasColumnType("integer"); + + b.Property("TutorialFlag") + .HasColumnType("integer"); + + b.Property("TutorialStatus") + .HasColumnType("integer"); + + b.Property("ViewerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ViewerId")); + + b.HasKey("DeviceAccountId"); + + b.HasIndex("DeviceAccountId") + .IsUnique(); + + b.ToTable("PlayerUserData"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbQuest", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("QuestId") + .HasColumnType("integer"); + + b.Property("BestClearTime") + .HasColumnType("real"); + + b.Property("DailyPlayCount") + .HasColumnType("integer"); + + b.Property("IsAppear") + .HasColumnType("boolean"); + + b.Property("IsMissionClear1") + .HasColumnType("boolean"); + + b.Property("IsMissionClear2") + .HasColumnType("boolean"); + + b.Property("IsMissionClear3") + .HasColumnType("boolean"); + + b.Property("LastDailyResetTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LastWeeklyResetTime") + .HasColumnType("timestamp with time zone"); + + b.Property("PlayCount") + .HasColumnType("integer"); + + b.Property("State") + .HasColumnType("smallint"); + + b.Property("WeeklyPlayCount") + .HasColumnType("integer"); + + b.HasKey("DeviceAccountId", "QuestId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerQuests"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbQuestClearPartyUnit", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("QuestId") + .HasColumnType("integer"); + + b.Property("IsMulti") + .HasColumnType("boolean"); + + b.Property("UnitNo") + .HasColumnType("integer"); + + b.Property("CharaId") + .HasColumnType("integer"); + + b.Property("EditSkill1CharaId") + .HasColumnType("integer"); + + b.Property("EditSkill2CharaId") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType1CrestId1") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType1CrestId2") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType1CrestId3") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType2CrestId1") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType2CrestId2") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType3CrestId1") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType3CrestId2") + .HasColumnType("integer"); + + b.Property("EquipDragonKeyId") + .HasColumnType("bigint"); + + b.Property("EquipTalismanKeyId") + .HasColumnType("bigint"); + + b.Property("EquipWeaponBodyId") + .HasColumnType("integer"); + + b.Property("EquipWeaponSkinId") + .HasColumnType("integer"); + + b.Property("EquippedDragonEntityId") + .HasColumnType("integer"); + + b.Property("EquippedTalismanEntityId") + .HasColumnType("integer"); + + b.HasKey("DeviceAccountId", "QuestId", "IsMulti", "UnitNo"); + + b.ToTable("QuestClearPartyUnits"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbQuestEvent", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("QuestEventId") + .HasColumnType("integer") + .HasColumnName("QuestEventId"); + + b.Property("DailyPlayCount") + .HasColumnType("integer") + .HasColumnName("DailyPlayCount"); + + b.Property("LastDailyResetTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastDailyResetTime"); + + b.Property("LastWeeklyResetTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastWeeklyResetTime"); + + b.Property("QuestBonusReceiveCount") + .HasColumnType("integer") + .HasColumnName("QuestBonusReceiveCount"); + + b.Property("QuestBonusReserveCount") + .HasColumnType("integer") + .HasColumnName("QuestBonusReserveCount"); + + b.Property("QuestBonusReserveTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("QuestBonusReserveTime"); + + b.Property("QuestBonusStackCount") + .HasColumnType("integer") + .HasColumnName("QuestBonusStackCount"); + + b.Property("QuestBonusStackTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("QuestBonusStackTime"); + + b.Property("WeeklyPlayCount") + .HasColumnType("integer") + .HasColumnName("WeeklyPlayCount"); + + b.HasKey("DeviceAccountId", "QuestEventId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("QuestEvents"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbSetUnit", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("CharaId") + .HasColumnType("integer"); + + b.Property("UnitSetNo") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType1CrestId1") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType1CrestId2") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType1CrestId3") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType2CrestId1") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType2CrestId2") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType3CrestId1") + .HasColumnType("integer"); + + b.Property("EquipCrestSlotType3CrestId2") + .HasColumnType("integer"); + + b.Property("EquipDragonKeyId") + .HasColumnType("bigint"); + + b.Property("EquipTalismanKeyId") + .HasColumnType("bigint"); + + b.Property("EquipWeaponBodyId") + .HasColumnType("integer"); + + b.Property("UnitSetName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("DeviceAccountId", "CharaId", "UnitSetNo"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerSetUnit"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbSummonTicket", b => + { + b.Property("TicketKeyId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TicketKeyId")); + + b.Property("DeviceAccountId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ExpirationTime"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("Quantity"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("Type"); + + b.HasKey("TicketKeyId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerSummonTickets"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbTalisman", b => + { + b.Property("TalismanKeyId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TalismanKeyId")); + + b.Property("AdditionalAttack") + .HasColumnType("integer"); + + b.Property("AdditionalHp") + .HasColumnType("integer"); + + b.Property("DeviceAccountId") + .IsRequired() + .HasColumnType("text"); + + b.Property("GetTime") + .HasColumnType("timestamp with time zone"); + + b.Property("IsLock") + .HasColumnType("boolean"); + + b.Property("IsNew") + .HasColumnType("boolean"); + + b.Property("TalismanAbilityId1") + .HasColumnType("integer"); + + b.Property("TalismanAbilityId2") + .HasColumnType("integer"); + + b.Property("TalismanAbilityId3") + .HasColumnType("integer"); + + b.Property("TalismanId") + .HasColumnType("integer"); + + b.HasKey("TalismanKeyId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerTalismans"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbWeaponBody", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("WeaponBodyId") + .HasColumnType("integer"); + + b.Property("AdditionalCrestSlotType1Count") + .HasColumnType("integer"); + + b.Property("AdditionalCrestSlotType2Count") + .HasColumnType("integer"); + + b.Property("AdditionalCrestSlotType3Count") + .HasColumnType("integer"); + + b.Property("BuildupCount") + .HasColumnType("integer"); + + b.Property("EquipableCount") + .HasColumnType("integer"); + + b.Property("FortPassiveCharaWeaponBuildupCount") + .HasColumnType("integer"); + + b.Property("GetTime") + .HasColumnType("timestamp with time zone"); + + b.Property("IsNew") + .HasColumnType("boolean"); + + b.Property("LimitBreakCount") + .HasColumnType("integer"); + + b.Property("LimitOverCount") + .HasColumnType("integer"); + + b.Property("UnlockWeaponPassiveAbilityNoString") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("DeviceAccountId", "WeaponBodyId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerWeapons"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbWeaponPassiveAbility", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("WeaponPassiveAbilityId") + .HasColumnType("integer"); + + b.HasKey("DeviceAccountId", "WeaponPassiveAbilityId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerPassiveAbilities"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbWeaponSkin", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("WeaponSkinId") + .HasColumnType("integer"); + + b.Property("GetTime") + .HasColumnType("timestamp with time zone"); + + b.Property("IsNew") + .HasColumnType("boolean"); + + b.HasKey("DeviceAccountId", "WeaponSkinId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("PlayerWeaponSkins"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbAbilityCrest", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("AbilityCrestList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbAbilityCrestSet", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("AbilityCrestSetList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbEmblem", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbEquippedStamp", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("EquippedStampList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbFortBuild", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("BuildList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbFortDetail", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithOne("FortDetail") + .HasForeignKey("DragaliaAPI.Database.Entities.DbFortDetail", "DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbLoginBonus", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbParty", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("PartyList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPartyUnit", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbParty", "Party") + .WithMany("Units") + .HasForeignKey("DeviceAccountId", "PartyNo") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Party"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerBannerData", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("UserSummonList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerCharaData", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("CharaList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerCurrency", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("Currencies") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeChara", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("DmodeCharas") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeDungeon", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithOne("DmodeDungeon") + .HasForeignKey("DragaliaAPI.Database.Entities.DbPlayerDmodeDungeon", "DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeExpedition", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithOne("DmodeExpedition") + .HasForeignKey("DragaliaAPI.Database.Entities.DbPlayerDmodeExpedition", "DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeInfo", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithOne("DmodeInfo") + .HasForeignKey("DragaliaAPI.Database.Entities.DbPlayerDmodeInfo", "DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeServitorPassive", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("DmodeServitorPassives") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDragonData", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("DragonList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDragonGift", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("DragonGiftList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDragonReliability", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("DragonReliabilityList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventData", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventItem", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventPassive", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventReward", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerMaterial", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("MaterialList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerMission", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerPresent", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("Presents") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerPresentHistory", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("PresentHistory") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerShopInfo", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithOne("ShopInfo") + .HasForeignKey("DragaliaAPI.Database.Entities.DbPlayerShopInfo", "DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerShopPurchase", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerStoryState", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("StoryStates") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerSummonHistory", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("SummonHistory") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerTrade", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerUseItem", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerUserData", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithOne("UserData") + .HasForeignKey("DragaliaAPI.Database.Entities.DbPlayerUserData", "DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbQuest", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("QuestList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbQuestClearPartyUnit", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbQuestEvent", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("QuestEvents") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbSetUnit", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("UnitSets") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbSummonTicket", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany() + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbTalisman", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("TalismanList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbWeaponBody", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("WeaponBodyList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbWeaponPassiveAbility", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("WeaponPassiveAbilityList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbWeaponSkin", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("WeaponSkinList") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbParty", b => + { + b.Navigation("Units"); + }); + + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayer", b => + { + b.Navigation("AbilityCrestList"); + + b.Navigation("AbilityCrestSetList"); + + b.Navigation("BuildList"); + + b.Navigation("CharaList"); + + b.Navigation("Currencies"); + + b.Navigation("DmodeCharas"); + + b.Navigation("DmodeDungeon"); + + b.Navigation("DmodeExpedition"); + + b.Navigation("DmodeInfo"); + + b.Navigation("DmodeServitorPassives"); + + b.Navigation("DragonGiftList"); + + b.Navigation("DragonList"); + + b.Navigation("DragonReliabilityList"); + + b.Navigation("EquippedStampList"); + + b.Navigation("FortDetail"); + + b.Navigation("MaterialList"); + + b.Navigation("PartyList"); + + b.Navigation("PresentHistory"); + + b.Navigation("Presents"); + + b.Navigation("QuestEvents"); + + b.Navigation("QuestList"); + + b.Navigation("ShopInfo"); + + b.Navigation("StoryStates"); + + b.Navigation("SummonHistory"); + + b.Navigation("TalismanList"); + + b.Navigation("UnitSets"); + + b.Navigation("UserData"); + + b.Navigation("UserSummonList"); + + b.Navigation("WeaponBodyList"); + + b.Navigation("WeaponPassiveAbilityList"); + + b.Navigation("WeaponSkinList"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DragaliaAPI.Database/Migrations/20230808101930_quest-event-1.cs b/DragaliaAPI.Database/Migrations/20230808101930_quest-event-1.cs new file mode 100644 index 000000000..cd2e83c2f --- /dev/null +++ b/DragaliaAPI.Database/Migrations/20230808101930_quest-event-1.cs @@ -0,0 +1,94 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DragaliaAPI.Database.Migrations +{ + /// + public partial class questevent1 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "LastWeeklyResetTime", table: "PlayerQuests"); + + migrationBuilder.AddColumn( + name: "LastWeeklyResetTime", + table: "PlayerQuests", + type: "timestamp with time zone", + nullable: false, + defaultValue: DateTimeOffset.UnixEpoch + ); + + migrationBuilder.DropColumn(name: "LastDailyResetTime", table: "PlayerQuests"); + + migrationBuilder.AddColumn( + name: "LastDailyResetTime", + table: "PlayerQuests", + type: "timestamp with time zone", + nullable: false, + defaultValue: DateTimeOffset.UnixEpoch + ); + + migrationBuilder.CreateTable( + name: "QuestEvents", + columns: table => new + { + DeviceAccountId = table.Column(type: "text", nullable: false), + QuestEventId = table.Column(type: "integer", nullable: false), + DailyPlayCount = table.Column(type: "integer", nullable: false), + LastDailyResetTime = table.Column(type: "timestamp with time zone", nullable: false), + WeeklyPlayCount = table.Column(type: "integer", nullable: false), + LastWeeklyResetTime = table.Column(type: "timestamp with time zone", nullable: false), + QuestBonusReceiveCount = table.Column(type: "integer", nullable: false), + QuestBonusStackCount = table.Column(type: "integer", nullable: false), + QuestBonusStackTime = table.Column(type: "timestamp with time zone", nullable: false), + QuestBonusReserveCount = table.Column(type: "integer", nullable: false), + QuestBonusReserveTime = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_QuestEvents", x => new { x.DeviceAccountId, x.QuestEventId }); + table.ForeignKey( + name: "FK_QuestEvents_Players_DeviceAccountId", + column: x => x.DeviceAccountId, + principalTable: "Players", + principalColumn: "AccountId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_QuestEvents_DeviceAccountId", + table: "QuestEvents", + column: "DeviceAccountId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "QuestEvents"); + + migrationBuilder.DropColumn(name: "LastWeeklyResetTime", table: "PlayerQuests"); + + migrationBuilder.AddColumn( + name: "LastWeeklyResetTime", + table: "PlayerQuests", + type: "integer", + nullable: false, + defaultValue: 0 + ); + + migrationBuilder.DropColumn(name: "LastDailyResetTime", table: "PlayerQuests"); + + migrationBuilder.AddColumn( + name: "LastDailyResetTime", + table: "PlayerQuests", + type: "integer", + nullable: false, + defaultValue: 0 + ); + } + } +} diff --git a/DragaliaAPI.Database/Migrations/ApiContextModelSnapshot.cs b/DragaliaAPI.Database/Migrations/ApiContextModelSnapshot.cs index f68835d4d..51ca14985 100644 --- a/DragaliaAPI.Database/Migrations/ApiContextModelSnapshot.cs +++ b/DragaliaAPI.Database/Migrations/ApiContextModelSnapshot.cs @@ -1445,11 +1445,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsMissionClear3") .HasColumnType("boolean"); - b.Property("LastDailyResetTime") - .HasColumnType("integer"); + b.Property("LastDailyResetTime") + .HasColumnType("timestamp with time zone"); - b.Property("LastWeeklyResetTime") - .HasColumnType("integer"); + b.Property("LastWeeklyResetTime") + .HasColumnType("timestamp with time zone"); b.Property("PlayCount") .HasColumnType("integer"); @@ -1534,6 +1534,58 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("QuestClearPartyUnits"); }); + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbQuestEvent", b => + { + b.Property("DeviceAccountId") + .HasColumnType("text"); + + b.Property("QuestEventId") + .HasColumnType("integer") + .HasColumnName("QuestEventId"); + + b.Property("DailyPlayCount") + .HasColumnType("integer") + .HasColumnName("DailyPlayCount"); + + b.Property("LastDailyResetTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastDailyResetTime"); + + b.Property("LastWeeklyResetTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("LastWeeklyResetTime"); + + b.Property("QuestBonusReceiveCount") + .HasColumnType("integer") + .HasColumnName("QuestBonusReceiveCount"); + + b.Property("QuestBonusReserveCount") + .HasColumnType("integer") + .HasColumnName("QuestBonusReserveCount"); + + b.Property("QuestBonusReserveTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("QuestBonusReserveTime"); + + b.Property("QuestBonusStackCount") + .HasColumnType("integer") + .HasColumnName("QuestBonusStackCount"); + + b.Property("QuestBonusStackTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("QuestBonusStackTime"); + + b.Property("WeeklyPlayCount") + .HasColumnType("integer") + .HasColumnName("WeeklyPlayCount"); + + b.HasKey("DeviceAccountId", "QuestEventId"); + + b.HasIndex("DeviceAccountId"); + + b.ToTable("QuestEvents"); + }); + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbSetUnit", b => { b.Property("DeviceAccountId") @@ -2166,6 +2218,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Owner"); }); + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbQuestEvent", b => + { + b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") + .WithMany("QuestEvents") + .HasForeignKey("DeviceAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + modelBuilder.Entity("DragaliaAPI.Database.Entities.DbSetUnit", b => { b.HasOne("DragaliaAPI.Database.Entities.DbPlayer", "Owner") @@ -2277,6 +2340,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Presents"); + b.Navigation("QuestEvents"); + b.Navigation("QuestList"); b.Navigation("ShopInfo"); diff --git a/DragaliaAPI.Database/Repositories/IQuestRepository.cs b/DragaliaAPI.Database/Repositories/IQuestRepository.cs index c21304cb7..350dda03c 100644 --- a/DragaliaAPI.Database/Repositories/IQuestRepository.cs +++ b/DragaliaAPI.Database/Repositories/IQuestRepository.cs @@ -1,11 +1,12 @@ using DragaliaAPI.Database.Entities; -using DragaliaAPI.Shared.Definitions.Enums; namespace DragaliaAPI.Database.Repositories; public interface IQuestRepository { IQueryable Quests { get; } + IQueryable QuestEvents { get; } Task GetQuestDataAsync(int questId); + Task GetQuestEventAsync(int questEventId); } diff --git a/DragaliaAPI.Database/Repositories/QuestRepository.cs b/DragaliaAPI.Database/Repositories/QuestRepository.cs index ec30e8c72..9870283b6 100644 --- a/DragaliaAPI.Database/Repositories/QuestRepository.cs +++ b/DragaliaAPI.Database/Repositories/QuestRepository.cs @@ -22,30 +22,19 @@ public QuestRepository(ApiContext apiContext, IPlayerIdentityService playerIdent x => x.DeviceAccountId == this.playerIdentityService.AccountId ); - public async Task UpdateQuestState(int questId, int state) - { - DbQuest? questData = await apiContext.PlayerQuests.FindAsync( - this.playerIdentityService.AccountId, - questId + public IQueryable QuestEvents => + this.apiContext.QuestEvents.Where( + x => x.DeviceAccountId == this.playerIdentityService.AccountId ); - if (questData is null) - { - questData = new() - { - DeviceAccountId = this.playerIdentityService.AccountId, - QuestId = questId, - State = (byte)state - }; - apiContext.PlayerQuests.Add(questData); - } - - questData.State = (byte)state; + private async Task FindQuestAsync(int questId) + { + return await apiContext.PlayerQuests.FindAsync(playerIdentityService.AccountId, questId); } public async Task GetQuestDataAsync(int questId) { - DbQuest? questData = await this.Quests.SingleOrDefaultAsync(x => x.QuestId == questId); + DbQuest? questData = await FindQuestAsync(questId); questData ??= this.apiContext.PlayerQuests .Add( new DbQuest() @@ -58,34 +47,25 @@ public async Task GetQuestDataAsync(int questId) return questData; } - public async Task CompleteQuest(int questId, float clearTime) + private async Task FindQuestEventAsync(int questEventId) { - DbQuest? questData = await apiContext.PlayerQuests.SingleOrDefaultAsync( - x => x.DeviceAccountId == this.playerIdentityService.AccountId && x.QuestId == questId + return await apiContext.QuestEvents.FindAsync( + playerIdentityService.AccountId, + questEventId ); + } - if (questData is null) - { - questData = new() - { - DeviceAccountId = this.playerIdentityService.AccountId, - QuestId = questId, - }; - apiContext.PlayerQuests.Add(questData); - } - - questData.State = 3; - - questData.PlayCount++; - questData.DailyPlayCount++; - questData.WeeklyPlayCount++; - questData.IsAppear = true; - - if (clearTime < questData.BestClearTime || questData.BestClearTime == -1.0f) - { - questData.BestClearTime = clearTime; - } - - return questData; + public async Task GetQuestEventAsync(int questEventId) + { + return await FindQuestEventAsync(questEventId) + ?? apiContext.QuestEvents + .Add( + new DbQuestEvent + { + DeviceAccountId = playerIdentityService.AccountId, + QuestEventId = questEventId + } + ) + .Entity; } } diff --git a/DragaliaAPI.Integration.Test/Dragalia/DungeonRecordTest.cs b/DragaliaAPI.Integration.Test/Dragalia/DungeonRecordTest.cs index 7c7b365be..bf837cf40 100644 --- a/DragaliaAPI.Integration.Test/Dragalia/DungeonRecordTest.cs +++ b/DragaliaAPI.Integration.Test/Dragalia/DungeonRecordTest.cs @@ -15,7 +15,10 @@ public DungeonRecordTest( CustomWebApplicationFactory factory, ITestOutputHelper outputHelper ) - : base(factory, outputHelper) { } + : base(factory, outputHelper) + { + CommonAssertionOptions.ApplyTimeOptions(2); + } [Fact] public async Task Record_ReturnsExpectedResponse() @@ -143,7 +146,9 @@ await this.Client.PostMsgpack( response.ingame_result_data.grow_record.take_player_exp.Should().NotBe(0); - response.update_data_list.user_data.coin.Should().Be(oldUserData.Coin + 10); + response.update_data_list.user_data.coin + .Should() + .BeInRange(oldUserData.Coin + 10, oldUserData.Coin + 10 + 1000); // +1000 because of temp. quest event group reward response.update_data_list.user_data.mana_point.Should().Be(oldUserData.ManaPoint + 10); response.update_data_list.material_list @@ -156,11 +161,16 @@ await this.Client.PostMsgpack( { quest_id = questId, state = 3, + daily_play_count = 1, + play_count = 1, + weekly_play_count = 1, is_appear = 1, is_mission_clear_1 = 1, is_mission_clear_2 = 1, is_mission_clear_3 = 1, - best_clear_time = 10 + best_clear_time = 10, + last_daily_reset_time = DateTimeOffset.UtcNow, + last_weekly_reset_time = DateTimeOffset.UtcNow } ); } diff --git a/DragaliaAPI.Shared/Definitions/Enums/QuestBonusReceiveType.cs b/DragaliaAPI.Shared/Definitions/Enums/QuestBonusReceiveType.cs new file mode 100644 index 000000000..39b61bbad --- /dev/null +++ b/DragaliaAPI.Shared/Definitions/Enums/QuestBonusReceiveType.cs @@ -0,0 +1,10 @@ +namespace DragaliaAPI.Shared.Definitions.Enums; + +public enum QuestBonusReceiveType +{ + AutoReceive, + SelectReceive, + StackReceive, + StackSelectReceive, + Max +} diff --git a/DragaliaAPI.Shared/Definitions/Enums/QuestResetIntervalType.cs b/DragaliaAPI.Shared/Definitions/Enums/QuestResetIntervalType.cs new file mode 100644 index 000000000..3e8a04059 --- /dev/null +++ b/DragaliaAPI.Shared/Definitions/Enums/QuestResetIntervalType.cs @@ -0,0 +1,9 @@ +namespace DragaliaAPI.Shared.Definitions.Enums; + +public enum QuestResetIntervalType +{ + None, + Daily, + Weekly, + Total +} diff --git a/DragaliaAPI.Shared/MasterAsset/Models/QuestEvent.cs b/DragaliaAPI.Shared/MasterAsset/Models/QuestEvent.cs index ba92624e2..940b8a656 100644 --- a/DragaliaAPI.Shared/MasterAsset/Models/QuestEvent.cs +++ b/DragaliaAPI.Shared/MasterAsset/Models/QuestEvent.cs @@ -2,4 +2,11 @@ namespace DragaliaAPI.Shared.MasterAsset.Models; -public record QuestEvent(int Id, QuestEventType QuestEventType); +public record QuestEvent( + int Id, + QuestEventType QuestEventType, + QuestBonusReceiveType QuestBonusReceiveType, + QuestResetIntervalType QuestBonusType, + int QuestBonusCount, + int QuestBonusStackCountMax +); diff --git a/DragaliaAPI.Test/Controllers/DungeonControllerTest.cs b/DragaliaAPI.Test/Controllers/DungeonControllerTest.cs index 2a45fe971..355fd9ae8 100644 --- a/DragaliaAPI.Test/Controllers/DungeonControllerTest.cs +++ b/DragaliaAPI.Test/Controllers/DungeonControllerTest.cs @@ -1,7 +1,10 @@ using DragaliaAPI.Features.Dungeon; -using DragaliaAPI.Features.Dungeon.Record; +using DragaliaAPI.Features.Quest; +using DragaliaAPI.Features.Reward; using DragaliaAPI.Models; using DragaliaAPI.Models.Generated; +using DragaliaAPI.Services; +using DragaliaAPI.Features.Dungeon.Record; using DragaliaAPI.Services.Photon; using DragaliaAPI.Shared.Definitions.Enums; using DragaliaAPI.Shared.MasterAsset; @@ -12,6 +15,9 @@ public class DungeonControllerTest { private readonly Mock mockDungeonService; private readonly Mock mockOddsInfoService; + private readonly Mock mockQuestService; + private readonly Mock mockUpdateDataService; + private readonly Mock mockRewardService; private readonly Mock mockMatchingService; private readonly Mock mockDungeonRecordHelperService; @@ -21,12 +27,18 @@ public DungeonControllerTest() { this.mockDungeonService = new(MockBehavior.Strict); this.mockOddsInfoService = new(MockBehavior.Strict); + this.mockQuestService = new(MockBehavior.Strict); + this.mockUpdateDataService = new(MockBehavior.Strict); + this.mockRewardService = new(MockBehavior.Strict); this.mockMatchingService = new(MockBehavior.Strict); this.mockDungeonRecordHelperService = new(MockBehavior.Strict); this.dungeonController = new( this.mockDungeonService.Object, this.mockOddsInfoService.Object, + this.mockQuestService.Object, + this.mockUpdateDataService.Object, + this.mockRewardService.Object, this.mockMatchingService.Object, this.mockDungeonRecordHelperService.Object ); diff --git a/DragaliaAPI.Test/Features/Dungeon/Record/DungeonRecordServiceTest.cs b/DragaliaAPI.Test/Features/Dungeon/Record/DungeonRecordServiceTest.cs index 7516ceeb5..eb6bdf094 100644 --- a/DragaliaAPI.Test/Features/Dungeon/Record/DungeonRecordServiceTest.cs +++ b/DragaliaAPI.Test/Features/Dungeon/Record/DungeonRecordServiceTest.cs @@ -5,6 +5,7 @@ using DragaliaAPI.Features.Dungeon.Record; using DragaliaAPI.Features.Missions; using DragaliaAPI.Features.Player; +using DragaliaAPI.Features.Quest; using DragaliaAPI.Models; using DragaliaAPI.Models.Generated; using DragaliaAPI.Services; @@ -20,8 +21,7 @@ namespace DragaliaAPI.Test.Features.Dungeon.Record; public class DungeonRecordServiceTest { private readonly Mock mockDungeonRewardService; - private readonly Mock mockQuestRepository; - private readonly Mock mockMissionProgressionService; + private readonly Mock mockQuestService; private readonly Mock mockUserService; private readonly Mock mockTutorialService; private readonly Mock> mockLogger; @@ -31,16 +31,14 @@ public class DungeonRecordServiceTest public DungeonRecordServiceTest() { this.mockDungeonRewardService = new(MockBehavior.Strict); - this.mockQuestRepository = new(MockBehavior.Strict); - this.mockMissionProgressionService = new(MockBehavior.Strict); + this.mockQuestService = new(MockBehavior.Strict); this.mockUserService = new(MockBehavior.Strict); this.mockTutorialService = new(MockBehavior.Strict); this.mockLogger = new(MockBehavior.Loose); this.dungeonRecordService = new DungeonRecordService( this.mockDungeonRewardService.Object, - this.mockQuestRepository.Object, - this.mockMissionProgressionService.Object, + this.mockQuestService.Object, this.mockUserService.Object, this.mockTutorialService.Object, this.mockLogger.Object @@ -152,11 +150,9 @@ public async Task GenerateIngameResultData_CallsExpectedMethods() int takeAccumulatePoint = 30; int takeBoostAccumulatePoint = 40; - this.mockQuestRepository - .Setup(x => x.GetQuestDataAsync(lSurtrSoloId)) - .ReturnsAsync(mockQuest); - - this.mockMissionProgressionService.Setup(x => x.OnQuestCleared(lSurtrSoloId)); + this.mockQuestService + .Setup(x => x.ProcessQuestCompletion(lSurtrSoloId, 10)) + .ReturnsAsync((mockQuest, true, new List())); this.mockUserService .Setup(x => x.RemoveStamina(StaminaType.Single, 40)) @@ -234,11 +230,8 @@ await this.dungeonRecordService.GenerateIngameResultData( } ); - mockQuest.State.Should().Be(3); - this.mockDungeonRewardService.VerifyAll(); - this.mockQuestRepository.VerifyAll(); - this.mockMissionProgressionService.VerifyAll(); + this.mockQuestService.VerifyAll(); this.mockUserService.VerifyAll(); this.mockTutorialService.VerifyAll(); this.mockLogger.VerifyAll(); diff --git a/DragaliaAPI/AutoMapper/Profiles/QuestMapProfile.cs b/DragaliaAPI/AutoMapper/Profiles/QuestMapProfile.cs index f10441e6a..718a0a45d 100644 --- a/DragaliaAPI/AutoMapper/Profiles/QuestMapProfile.cs +++ b/DragaliaAPI/AutoMapper/Profiles/QuestMapProfile.cs @@ -11,6 +11,8 @@ public QuestMapProfile() { this.CreateMap().ReverseMap(); + this.CreateMap().ReverseMap(); + this.CreateMap() .ForMember(x => x.quest_story_id, o => o.MapFrom(nameof(DbPlayerStoryState.StoryId))); diff --git a/DragaliaAPI/Features/Dungeon/DungeonController.cs b/DragaliaAPI/Features/Dungeon/DungeonController.cs index f0566d4db..33f1698f1 100644 --- a/DragaliaAPI/Features/Dungeon/DungeonController.cs +++ b/DragaliaAPI/Features/Dungeon/DungeonController.cs @@ -1,8 +1,10 @@ using DragaliaAPI.Controllers; -using DragaliaAPI.Features.Dungeon.Record; +using DragaliaAPI.Features.Quest; +using DragaliaAPI.Features.Reward; using DragaliaAPI.Models; using DragaliaAPI.Models.Generated; using DragaliaAPI.Services; +using DragaliaAPI.Features.Dungeon.Record; using DragaliaAPI.Services.Exceptions; using DragaliaAPI.Services.Photon; using DragaliaAPI.Shared.Definitions; @@ -14,6 +16,9 @@ namespace DragaliaAPI.Features.Dungeon; public class DungeonController( IDungeonService dungeonService, IOddsInfoService oddsInfoService, + IQuestService questService, + IUpdateDataService updateDataService, + IRewardService rewardService, IMatchingService matchingService, IDungeonRecordHelperService dungeonRecordHelperService ) : DragaliaControllerBase @@ -67,4 +72,21 @@ public async Task Fail(DungeonFailRequest request) return this.Ok(response); } + + [HttpPost("receive_quest_bonus")] + public async Task ReceiveQuestBonus(DungeonReceiveQuestBonusRequest request) + { + DungeonReceiveQuestBonusData resp = new(); + + resp.receive_quest_bonus = await questService.ReceiveQuestBonus( + request.quest_event_id, + request.is_receive, + request.receive_bonus_count + ); + + resp.update_data_list = await updateDataService.SaveChangesAsync(); + resp.entity_result = rewardService.GetEntityResult(); + + return Ok(resp); + } } diff --git a/DragaliaAPI/Features/Dungeon/Record/DungeonRecordService.cs b/DragaliaAPI/Features/Dungeon/Record/DungeonRecordService.cs index 3ad09cd59..2b0e875ff 100644 --- a/DragaliaAPI/Features/Dungeon/Record/DungeonRecordService.cs +++ b/DragaliaAPI/Features/Dungeon/Record/DungeonRecordService.cs @@ -1,23 +1,16 @@ using DragaliaAPI.Database.Entities; -using DragaliaAPI.Database.Repositories; -using DragaliaAPI.Extensions; -using DragaliaAPI.Features.Missions; using DragaliaAPI.Features.Player; -using DragaliaAPI.Features.Reward; +using DragaliaAPI.Features.Quest; using DragaliaAPI.Models; using DragaliaAPI.Models.Generated; using DragaliaAPI.Services; -using DragaliaAPI.Services.Photon; using DragaliaAPI.Shared.Definitions.Enums; -using DragaliaAPI.Shared.MasterAsset.Models; -using PhotonPlayer = DragaliaAPI.Photon.Shared.Models.Player; namespace DragaliaAPI.Features.Dungeon.Record; public class DungeonRecordService( IDungeonRecordRewardService dungeonRecordRewardService, - IQuestRepository questRepository, - IMissionProgressionService missionProgressionService, + IQuestService questService, IUserService userService, ITutorialService tutorialService, ILogger logger @@ -53,11 +46,13 @@ DungeonSession session is_clear = true, }; - DbQuest questData = await questRepository.GetQuestDataAsync(session.QuestData.Id); - questData.State = 3; + ( + DbQuest questData, + ingameResultData.is_best_clear_time, + ingameResultData.reward_record.quest_bonus_list + ) = await questService.ProcessQuestCompletion(session.QuestData.Id, playRecord.time); this.ProcessClearTime(ingameResultData, playRecord.time, questData); - this.ProcessMissionProgression(session); await this.ProcessGrowth(ingameResultData.grow_record, session); await this.ProcessStaminaConsumption(session); await this.ProcessPlayerLevel( @@ -129,14 +124,6 @@ private Task ProcessGrowth(GrowRecord growRecord, DungeonSession session) return Task.CompletedTask; } - private void ProcessMissionProgression(DungeonSession session) - { - if (session.QuestData.IsPartOfVoidBattleGroups) - missionProgressionService.OnVoidBattleCleared(); - - missionProgressionService.OnQuestCleared(session.QuestData.Id); - } - private async Task ProcessStaminaConsumption(DungeonSession session) { StaminaType type = StaminaType.None; diff --git a/DragaliaAPI/Features/GraphQL/Schema.cs b/DragaliaAPI/Features/GraphQL/Schema.cs index bdf473d0d..5139c634d 100644 --- a/DragaliaAPI/Features/GraphQL/Schema.cs +++ b/DragaliaAPI/Features/GraphQL/Schema.cs @@ -43,6 +43,7 @@ public static IServiceCollection ConfigureGraphQlSchema(this IServiceCollection .Include(x => x.DmodeExpedition) .Include(x => x.DmodeInfo) .Include(x => x.DmodeServitorPassives) + .Include(x => x.QuestEvents) .Include(x => x.PartyPower) .AsSplitQuery() .First( diff --git a/DragaliaAPI/Features/Quest/IQuestCacheService.cs b/DragaliaAPI/Features/Quest/IQuestCacheService.cs new file mode 100644 index 000000000..6e058d3da --- /dev/null +++ b/DragaliaAPI/Features/Quest/IQuestCacheService.cs @@ -0,0 +1,8 @@ +namespace DragaliaAPI.Features.Quest; + +public interface IQuestCacheService +{ + Task SetQuestGroupQuestIdAsync(int questGroupId, int questId); + Task GetQuestGroupQuestIdAsync(int questGroupId); + Task RemoveQuestGroupQuestIdAsync(int questGroupId); +} diff --git a/DragaliaAPI/Features/Quest/IQuestService.cs b/DragaliaAPI/Features/Quest/IQuestService.cs new file mode 100644 index 000000000..0c018978c --- /dev/null +++ b/DragaliaAPI/Features/Quest/IQuestService.cs @@ -0,0 +1,15 @@ +using DragaliaAPI.Database.Entities; +using DragaliaAPI.Models.Generated; + +namespace DragaliaAPI.Features.Quest; + +public interface IQuestService +{ + Task<( + DbQuest Quest, + bool BestClearTime, + IEnumerable Bonus + )> ProcessQuestCompletion(int questId, float clearTime); + + Task ReceiveQuestBonus(int eventGroupId, bool isReceive, int count); +} diff --git a/DragaliaAPI/Features/Quest/QuestCacheService.cs b/DragaliaAPI/Features/Quest/QuestCacheService.cs new file mode 100644 index 000000000..6de311efe --- /dev/null +++ b/DragaliaAPI/Features/Quest/QuestCacheService.cs @@ -0,0 +1,55 @@ +using DragaliaAPI.Shared.PlayerDetails; +using Microsoft.Extensions.Caching.Distributed; + +namespace DragaliaAPI.Features.Quest; + +public class QuestCacheService( + IDistributedCache distributedCache, + IPlayerIdentityService playerIdentityService, + ILogger logger +) : IQuestCacheService +{ + private static readonly DistributedCacheEntryOptions QuestEntryCacheOptions = + new() { AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1) }; + + public async Task SetQuestGroupQuestIdAsync(int questGroupId, int questId) + { + await distributedCache.SetStringAsync( + Schema.QuestEventId(playerIdentityService.AccountId, questGroupId), + questId.ToString(), + QuestEntryCacheOptions + ); + } + + public async Task GetQuestGroupQuestIdAsync(int questGroupId) + { + string? questIdString = await distributedCache.GetStringAsync( + Schema.QuestEventId(playerIdentityService.AccountId, questGroupId) + ); + + if (int.TryParse(questIdString, out int questId)) + { + return questId; + } + + logger.LogError( + "Tried to parse cached quest id string {questIdString} but it was not an int", + questIdString + ); + + return null; + } + + public async Task RemoveQuestGroupQuestIdAsync(int questGroupId) + { + await distributedCache.RemoveAsync( + Schema.QuestEventId(playerIdentityService.AccountId, questGroupId) + ); + } +} + +file static class Schema +{ + public static string QuestEventId(string playerId, int questEventId) => + $":{playerId}:lastcompletedquest:{questEventId}"; +} diff --git a/DragaliaAPI/Features/Quest/QuestService.cs b/DragaliaAPI/Features/Quest/QuestService.cs new file mode 100644 index 000000000..e01da1f6d --- /dev/null +++ b/DragaliaAPI/Features/Quest/QuestService.cs @@ -0,0 +1,242 @@ +using System.Diagnostics; +using DragaliaAPI.Database.Entities; +using DragaliaAPI.Database.Repositories; +using DragaliaAPI.Features.Missions; +using DragaliaAPI.Features.Reward; +using DragaliaAPI.Helpers; +using DragaliaAPI.Models; +using DragaliaAPI.Models.Generated; +using DragaliaAPI.Services.Exceptions; +using DragaliaAPI.Shared.Definitions.Enums; +using DragaliaAPI.Shared.MasterAsset; +using DragaliaAPI.Shared.MasterAsset.Models; + +namespace DragaliaAPI.Features.Quest; + +public class QuestService( + ILogger logger, + IQuestRepository questRepository, + IDateTimeProvider dateTimeProvider, + IResetHelper resetHelper, + IQuestCacheService questCacheService, + IRewardService rewardService, + IMissionProgressionService missionProgressionService +) : IQuestService +{ + public async Task<( + DbQuest Quest, + bool BestClearTime, + IEnumerable Bonus + )> ProcessQuestCompletion(int questId, float clearTime) + { + DbQuest quest = await questRepository.GetQuestDataAsync(questId); + quest.State = 3; + + missionProgressionService.OnQuestCleared(questId); + + bool isBestClearTime = false; + + if (0 > quest.BestClearTime || quest.BestClearTime > clearTime) + { + quest.BestClearTime = clearTime; + isBestClearTime = true; + } + + quest.PlayCount++; + + if (resetHelper.LastDailyReset > quest.LastDailyResetTime) + { + logger.LogTrace("Resetting daily play count for quest {questId}", questId); + + quest.DailyPlayCount = 0; + quest.LastDailyResetTime = dateTimeProvider.UtcNow; + } + + quest.DailyPlayCount++; + + if (resetHelper.LastWeeklyReset > quest.LastWeeklyResetTime) + { + logger.LogTrace("Resetting weekly play count for quest {questId}", questId); + + quest.WeeklyPlayCount = 0; + quest.LastWeeklyResetTime = dateTimeProvider.UtcNow; + } + + quest.WeeklyPlayCount++; + + IEnumerable questEventRewards = Enumerable.Empty(); + + QuestData questData = MasterAsset.QuestData[questId]; + if (questData.Gid > 20000) // TODO: replace with IsEventQuest when mission pr is merged + { + int baseGroupId = MasterAsset.QuestEventGroup[questData.Gid].BaseQuestGroupId; + + await questCacheService.SetQuestGroupQuestIdAsync(baseGroupId, questId); + + questEventRewards = await ProcessQuestEventCompletion(baseGroupId, questId); + } + + return (quest, isBestClearTime, questEventRewards); + } + + private async Task> ProcessQuestEventCompletion( + int eventGroupId, + int questId + ) + { + logger.LogTrace("Completing quest for quest group {eventGroupId}", eventGroupId); + + // TODO: Remove when missions pr is merged + if (eventGroupId == 30000) + missionProgressionService.OnVoidBattleCleared(); + + DbQuestEvent questEvent = await questRepository.GetQuestEventAsync(eventGroupId); + QuestEvent questEventData = MasterAsset.QuestEvent[eventGroupId]; + + if (resetHelper.LastDailyReset > questEvent.LastDailyResetTime) + { + if (questEventData.QuestBonusType == QuestResetIntervalType.Daily) + { + ResetQuestEventBonus(questEvent, questEventData); + } + + questEvent.DailyPlayCount = 0; + questEvent.LastDailyResetTime = dateTimeProvider.UtcNow; + } + + questEvent.DailyPlayCount++; + + if (resetHelper.LastWeeklyReset > questEvent.LastWeeklyResetTime) + { + if (questEventData.QuestBonusType == QuestResetIntervalType.Weekly) + { + ResetQuestEventBonus(questEvent, questEventData); + } + + questEvent.WeeklyPlayCount = 0; + questEvent.LastWeeklyResetTime = dateTimeProvider.UtcNow; + } + + questEvent.WeeklyPlayCount++; + + int totalBonusCount = questEvent.QuestBonusReserveCount + questEvent.QuestBonusReceiveCount; + if (questEventData.QuestBonusCount > totalBonusCount) + { + return Enumerable.Empty(); + } + + if (questEventData.QuestBonusReceiveType != QuestBonusReceiveType.AutoReceive) + { + questEvent.QuestBonusReserveCount++; + questEvent.QuestBonusReserveTime = dateTimeProvider.UtcNow; + + return Enumerable.Empty(); + } + + questEvent.QuestBonusReceiveCount++; + + return (await GenerateBonusDrops(questId, 1)).Select(x => x.ToFirstClearSet()); + } + + public async Task> GenerateBonusDrops(int questId, int count) + { + // TODO: Drop gen + + List drops = new(); + + for (int i = 0; i < count; i++) + { + Entity entity = new(EntityTypes.Rupies, 0, 1000); + + await rewardService.GrantReward(entity); + + drops.Add(entity); + } + + return drops; + } + + public async Task ReceiveQuestBonus( + int eventGroupId, + bool isReceive, + int count + ) + { + if (!isReceive) + { + await questCacheService.RemoveQuestGroupQuestIdAsync(eventGroupId); + + return new AtgenReceiveQuestBonus(); + } + + DbQuestEvent questEvent = await questRepository.GetQuestEventAsync(eventGroupId); + + int questId = + await questCacheService.GetQuestGroupQuestIdAsync(eventGroupId) + ?? throw new DragaliaException( + ResultCode.CommonDbError, + $"Could not find latest quest clear id for group {eventGroupId} in cache." + ); + + if (count > questEvent.QuestBonusReserveCount + questEvent.QuestBonusStackCount) + { + throw new DragaliaException( + ResultCode.QuestSelectBonusReceivableLimit, + "Tried to receive too many drops" + ); + } + + if (count > questEvent.QuestBonusReserveCount) + { + questEvent.QuestBonusStackCount -= count - questEvent.QuestBonusReserveCount; + questEvent.QuestBonusStackTime = + questEvent.QuestBonusStackCount == 0 + ? DateTimeOffset.UnixEpoch + : dateTimeProvider.UtcNow; + + count = questEvent.QuestBonusReserveCount; + } + + questEvent.QuestBonusReserveCount -= count; + questEvent.QuestBonusReserveTime = + questEvent.QuestBonusReserveCount == 0 + ? DateTimeOffset.UnixEpoch + : dateTimeProvider.UtcNow; + + questEvent.QuestBonusReceiveCount += count; + + // TODO: bonus factor? + IEnumerable bonusRewards = ( + await GenerateBonusDrops(questId, count) + ).Select(x => x.ToBuildEventRewardEntityList()); + + // Remove at the end so it doesn't get messed up when erroring + await questCacheService.RemoveQuestGroupQuestIdAsync(eventGroupId); + + return new AtgenReceiveQuestBonus(questId, count, 1, bonusRewards); + } + + private void ResetQuestEventBonus(DbQuestEvent questEvent, QuestEvent questEventData) + { + questEvent.QuestBonusReceiveCount = 0; + + if (questEventData.QuestBonusReceiveType != QuestBonusReceiveType.AutoReceive) + { + if (questEventData.QuestBonusStackCountMax > 0) + { + int newStackCount = + questEvent.QuestBonusReserveCount + questEvent.QuestBonusStackCount; + + questEvent.QuestBonusStackCount = Math.Min( + questEventData.QuestBonusStackCountMax, + newStackCount + ); + + questEvent.QuestBonusStackTime = dateTimeProvider.UtcNow; + } + + questEvent.QuestBonusReserveCount = 0; + questEvent.QuestBonusReserveTime = dateTimeProvider.UtcNow; + } + } +} diff --git a/DragaliaAPI/Models/Generated/Components.cs b/DragaliaAPI/Models/Generated/Components.cs index 856b72d80..18aa91871 100644 --- a/DragaliaAPI/Models/Generated/Components.cs +++ b/DragaliaAPI/Models/Generated/Components.cs @@ -7782,11 +7782,11 @@ public class QuestEventList public int weekly_play_count { get; set; } public int quest_bonus_receive_count { get; set; } public int quest_bonus_stack_count { get; set; } - public int quest_bonus_stack_time { get; set; } + public DateTimeOffset quest_bonus_stack_time { get; set; } public int quest_bonus_reserve_count { get; set; } - public int quest_bonus_reserve_time { get; set; } - public int last_daily_reset_time { get; set; } - public int last_weekly_reset_time { get; set; } + public DateTimeOffset quest_bonus_reserve_time { get; set; } + public DateTimeOffset last_daily_reset_time { get; set; } + public DateTimeOffset last_weekly_reset_time { get; set; } public QuestEventList( int quest_event_id, @@ -7794,11 +7794,11 @@ public QuestEventList( int weekly_play_count, int quest_bonus_receive_count, int quest_bonus_stack_count, - int quest_bonus_stack_time, + DateTimeOffset quest_bonus_stack_time, int quest_bonus_reserve_count, - int quest_bonus_reserve_time, - int last_daily_reset_time, - int last_weekly_reset_time + DateTimeOffset quest_bonus_reserve_time, + DateTimeOffset last_daily_reset_time, + DateTimeOffset last_weekly_reset_time ) { this.quest_event_id = quest_event_id; @@ -7872,8 +7872,8 @@ public class QuestList public int daily_play_count { get; set; } public int weekly_play_count { get; set; } public int play_count { get; set; } - public int last_daily_reset_time { get; set; } - public int last_weekly_reset_time { get; set; } + public DateTimeOffset last_daily_reset_time { get; set; } + public DateTimeOffset last_weekly_reset_time { get; set; } public int is_appear { get; set; } public float best_clear_time { get; set; } @@ -7886,8 +7886,8 @@ public QuestList( int daily_play_count, int weekly_play_count, int play_count, - int last_daily_reset_time, - int last_weekly_reset_time, + DateTimeOffset last_daily_reset_time, + DateTimeOffset last_weekly_reset_time, int is_appear, float best_clear_time ) diff --git a/DragaliaAPI/Models/Generated/Requests.cs b/DragaliaAPI/Models/Generated/Requests.cs index 7d569969a..640bbd972 100644 --- a/DragaliaAPI/Models/Generated/Requests.cs +++ b/DragaliaAPI/Models/Generated/Requests.cs @@ -1142,12 +1142,14 @@ public DungeonGetAreaOddsRequest() { } public class DungeonReceiveQuestBonusRequest { public int quest_event_id { get; set; } - public int is_receive { get; set; } + + [MessagePackFormatter(typeof(BoolToIntFormatter))] + public bool is_receive { get; set; } public int receive_bonus_count { get; set; } public DungeonReceiveQuestBonusRequest( int quest_event_id, - int is_receive, + bool is_receive, int receive_bonus_count ) { diff --git a/DragaliaAPI/Program.cs b/DragaliaAPI/Program.cs index 101c38841..9be051d48 100644 --- a/DragaliaAPI/Program.cs +++ b/DragaliaAPI/Program.cs @@ -36,6 +36,7 @@ using DragaliaAPI.Features.Item; using DragaliaAPI.Features.PartyPower; using DragaliaAPI.Features.Player; +using DragaliaAPI.Features.Quest; using DragaliaAPI.Features.Talisman; using DragaliaAPI.Features.Tickets; @@ -201,6 +202,9 @@ .AddScoped() // Emblem feature .AddScoped() + // Quest feature + .AddScoped() + .AddScoped() // Party power feature .AddScoped() .AddScoped(); diff --git a/DragaliaAPI/Services/Game/LoadService.cs b/DragaliaAPI/Services/Game/LoadService.cs index 1878786f9..8d630124f 100644 --- a/DragaliaAPI/Services/Game/LoadService.cs +++ b/DragaliaAPI/Services/Game/LoadService.cs @@ -73,6 +73,7 @@ public async Task BuildIndexData() .Where(x => x.StoryType == StoryTypes.Castle) .Select(mapper.Map), quest_list = savefile.QuestList.Select(mapper.Map), + quest_event_list = savefile.QuestEvents.Select(mapper.Map), material_list = savefile.MaterialList.Select(mapper.Map), weapon_skin_list = savefile.WeaponSkinList.Select(mapper.Map), weapon_passive_ability_list = savefile.WeaponPassiveAbilityList.Select( @@ -105,7 +106,8 @@ public async Task BuildIndexData() shop_notice = new ShopNotice(await shopRepository.GetDailySummonCountAsync() == 0), summon_ticket_list = (await ticketRepository.GetTicketsAsync()).Select( mapper.Map - ) + ), + quest_bonus_stack_base_time = 1617775200 // 7. April 2017 }; logger.LogInformation("{time} ms: Mapping complete", stopwatch.ElapsedMilliseconds); diff --git a/DragaliaAPI/Services/Game/SavefileService.cs b/DragaliaAPI/Services/Game/SavefileService.cs index 6395347f3..8ea194501 100644 --- a/DragaliaAPI/Services/Game/SavefileService.cs +++ b/DragaliaAPI/Services/Game/SavefileService.cs @@ -483,6 +483,18 @@ out DbTalisman? talisman stopwatch.Elapsed.TotalMilliseconds ); + this.apiContext.QuestEvents.AddRange( + savefile.quest_event_list.MapWithDeviceAccount( + mapper, + deviceAccountId + ) + ); + + this.logger.LogDebug( + "Mapping DbQuestEvent step done after {t} ms", + stopwatch.Elapsed.TotalMilliseconds + ); + this.logger.LogInformation( "Mapping completed after {t} ms", stopwatch.Elapsed.TotalMilliseconds @@ -612,6 +624,9 @@ private void Delete() this.apiContext.Emblems.RemoveRange( this.apiContext.Emblems.Where(x => x.DeviceAccountId == deviceAccountId) ); + this.apiContext.QuestEvents.RemoveRange( + this.apiContext.QuestEvents.Where(x => x.DeviceAccountId == deviceAccountId) + ); this.apiContext.PartyPowers.RemoveRange( this.apiContext.PartyPowers.Where(x => x.DeviceAccountId == deviceAccountId) ); @@ -645,6 +660,7 @@ public IQueryable Load() .Include(x => x.WeaponSkinList) .Include(x => x.WeaponPassiveAbilityList) .Include(x => x.EquippedStampList) + .Include(x => x.QuestEvents) .Include(x => x.PartyPower) .AsSplitQuery(); } diff --git a/DragaliaAPI/Services/Game/UpdateDataService.cs b/DragaliaAPI/Services/Game/UpdateDataService.cs index a5dde0205..2a1045679 100644 --- a/DragaliaAPI/Services/Game/UpdateDataService.cs +++ b/DragaliaAPI/Services/Game/UpdateDataService.cs @@ -96,6 +96,7 @@ private async Task MapUpdateDataList(List entit item_list = ConvertEntities(entities), talisman_list = ConvertEntities(entities), summon_ticket_list = ConvertEntities(entities), + quest_event_list = ConvertEntities(entities), party_power_data = ConvertEntities(entities)?.Single() };