From baebe8bab860074b136d47226ba33ec843014975 Mon Sep 17 00:00:00 2001
From: Jay Malhotra <5047192+SapiensAnatis@users.noreply.github.com>
Date: Sat, 1 Jun 2024 14:56:20 +0100
Subject: [PATCH] Implement summoning pity rates (#849)
First half of #698
- Refactor summoning odds calculation into static pure functions to make
them easier to test
- Add new pity counter column to DbPlayerBannerData
This does not activate the pity rate calculation for actual results;
everyone will have a counter of 0 which will not accumulate.
The algorithm for pity rates is somewhat empirical based on a random
screenshot and may not distribute the .5% increases in the exact way
that the game originally did.
---
.config/dotnet-tools.json | 11 +-
.../Entities/DbPlayerBannerData.cs | 6 +-
...240601133017_summon_pity_rates.Designer.cs | 2633 +++++++++++++++++
.../20240601133017_summon_pity_rates.cs | 40 +
.../Migrations/ApiContextModelSnapshot.cs | 9 +-
...sServiceTest.cs => SummonOddsLogicTest.cs} | 1042 ++++---
.../Summoning/RedoableSummonController.cs | 15 +-
.../Features/Summoning/SummonBannerOptions.cs | 21 +
.../Features/Summoning/SummonController.cs | 21 +-
.../Features/Summoning/SummonOddsLogic.cs | 586 ++++
.../Features/Summoning/SummonOddsService.cs | 543 +---
.../Features/Summoning/SummonService.cs | 18 +-
12 files changed, 4010 insertions(+), 935 deletions(-)
create mode 100644 DragaliaAPI/DragaliaAPI.Database/Migrations/20240601133017_summon_pity_rates.Designer.cs
create mode 100644 DragaliaAPI/DragaliaAPI.Database/Migrations/20240601133017_summon_pity_rates.cs
rename DragaliaAPI/DragaliaAPI.Test/Features/Summon/{SummonOddsServiceTest.cs => SummonOddsLogicTest.cs} (53%)
create mode 100644 DragaliaAPI/DragaliaAPI/Features/Summoning/SummonOddsLogic.cs
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index c7e935435..c23f8cced 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -6,19 +6,22 @@
"version": "0.6.4",
"commands": [
"husky"
- ]
+ ],
+ "rollForward": false
},
"csharpier": {
"version": "0.28.2",
"commands": [
"dotnet-csharpier"
- ]
+ ],
+ "rollForward": false
},
"dotnet-ef": {
- "version": "8.0.4",
+ "version": "8.0.6",
"commands": [
"dotnet-ef"
- ]
+ ],
+ "rollForward": false
}
}
}
\ No newline at end of file
diff --git a/DragaliaAPI/DragaliaAPI.Database/Entities/DbPlayerBannerData.cs b/DragaliaAPI/DragaliaAPI.Database/Entities/DbPlayerBannerData.cs
index 4d940e346..94d7e8a3a 100644
--- a/DragaliaAPI/DragaliaAPI.Database/Entities/DbPlayerBannerData.cs
+++ b/DragaliaAPI/DragaliaAPI.Database/Entities/DbPlayerBannerData.cs
@@ -14,14 +14,12 @@ public class DbPlayerBannerData : DbPlayerData
[Required]
public int SummonBannerId { get; set; }
- [Column("Pity")]
- [Required]
- public byte PityRate { get; set; }
-
[Column("SummonCount")]
[Required]
public int SummonCount { get; set; }
+ public int SummonCountSinceLastFiveStar { get; set; }
+
[Column("DailyLimitedSummons")]
[Required]
public int DailyLimitedSummonCount { get; set; }
diff --git a/DragaliaAPI/DragaliaAPI.Database/Migrations/20240601133017_summon_pity_rates.Designer.cs b/DragaliaAPI/DragaliaAPI.Database/Migrations/20240601133017_summon_pity_rates.Designer.cs
new file mode 100644
index 000000000..b250f4f4c
--- /dev/null
+++ b/DragaliaAPI/DragaliaAPI.Database/Migrations/20240601133017_summon_pity_rates.Designer.cs
@@ -0,0 +1,2633 @@
+//
+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("20240601133017_summon_pity_rates")]
+ partial class summon_pity_rates
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.5")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbAbilityCrest", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId", "AbilityCrestId");
+
+ b.ToTable("PlayerAbilityCrests");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbAbilityCrestSet", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("AbilityCrestSetNo")
+ .HasColumnType("integer");
+
+ b.Property("AbilityCrestSetName")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ 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("ViewerId", "AbilityCrestSetNo");
+
+ b.ToTable("PlayerAbilityCrestSets");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbCompletedDailyMission", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("Id")
+ .HasColumnType("integer");
+
+ b.Property("Date")
+ .HasColumnType("date");
+
+ b.Property("EndDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Progress")
+ .HasColumnType("integer");
+
+ b.Property("StartDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("ViewerId", "Id", "Date");
+
+ b.ToTable("CompletedDailyMissions");
+ });
+
+ 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("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId", "EmblemId");
+
+ b.ToTable("Emblems");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbEquippedStamp", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("Slot")
+ .HasColumnType("integer");
+
+ b.Property("StampId")
+ .HasColumnType("integer");
+
+ b.HasKey("ViewerId", "Slot");
+
+ 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("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.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.HasKey("BuildId");
+
+ b.HasIndex("ViewerId");
+
+ b.ToTable("PlayerFortBuilds");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbFortDetail", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("CarpenterNum")
+ .HasColumnType("integer")
+ .HasColumnName("CarpenterNum");
+
+ b.HasKey("ViewerId");
+
+ b.ToTable("PlayerFortDetail");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbLoginBonus", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("Id")
+ .HasColumnType("integer");
+
+ b.Property("CurrentDay")
+ .HasColumnType("integer");
+
+ b.Property("IsComplete")
+ .HasColumnType("boolean");
+
+ b.HasKey("ViewerId", "Id");
+
+ b.ToTable("LoginBonuses");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbNewsItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)");
+
+ b.Property("Headline")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("Time")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.ToTable("NewsItems");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbParty", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("PartyNo")
+ .HasColumnType("integer");
+
+ b.Property("PartyName")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)");
+
+ b.HasKey("ViewerId", "PartyNo");
+
+ b.ToTable("PartyData");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPartyPower", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("MaxPartyPower")
+ .HasColumnType("integer")
+ .HasColumnName("MaxPartyPower");
+
+ b.HasKey("ViewerId");
+
+ b.ToTable("PartyPowers");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPartyUnit", b =>
+ {
+ b.Property("Id")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ 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("PartyNo")
+ .HasColumnType("integer");
+
+ b.Property("UnitNo")
+ .HasColumnType("integer");
+
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ViewerId", "PartyNo");
+
+ b.ToTable("PlayerPartyUnits");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayer", b =>
+ {
+ b.Property("ViewerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ViewerId"));
+
+ b.Property("AccountId")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("character varying(16)");
+
+ b.Property("SavefileVersion")
+ .HasColumnType("integer");
+
+ b.HasKey("ViewerId");
+
+ b.HasIndex("AccountId");
+
+ b.ToTable("Players");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerBannerData", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("SummonCount")
+ .HasColumnType("integer")
+ .HasColumnName("SummonCount");
+
+ b.Property("SummonCountSinceLastFiveStar")
+ .HasColumnType("integer");
+
+ b.Property("SummonPoints")
+ .HasColumnType("integer")
+ .HasColumnName("SummonPoints");
+
+ b.HasKey("ViewerId", "SummonBannerId");
+
+ b.ToTable("PlayerBannerData");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerCharaData", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId", "CharaId");
+
+ b.ToTable("PlayerCharaData");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeChara", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId", "CharaId");
+
+ b.ToTable("PlayerDmodeCharas");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeDungeon", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId");
+
+ b.ToTable("PlayerDmodeDungeons");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeExpedition", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId");
+
+ b.ToTable("PlayerDmodeExpeditions");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeInfo", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId");
+
+ b.ToTable("PlayerDmodeInfos");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDmodeServitorPassive", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("PassiveId")
+ .HasColumnType("integer")
+ .HasColumnName("PassiveId");
+
+ b.Property("Level")
+ .HasColumnType("integer")
+ .HasColumnName("Level");
+
+ b.HasKey("ViewerId", "PassiveId");
+
+ 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("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.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.HasKey("DragonKeyId");
+
+ b.HasIndex("ViewerId");
+
+ b.ToTable("PlayerDragonData");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDragonGift", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("DragonGiftId")
+ .HasColumnType("integer")
+ .HasColumnName("DragonGiftId");
+
+ b.Property("Quantity")
+ .HasColumnType("integer")
+ .HasColumnName("Quantity");
+
+ b.HasKey("ViewerId", "DragonGiftId");
+
+ b.ToTable("PlayerDragonGift");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerDragonReliability", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId", "DragonId");
+
+ b.ToTable("PlayerDragonReliability");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventData", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("EventId")
+ .HasColumnType("integer")
+ .HasColumnName("EventId");
+
+ b.Property("CustomEventFlag")
+ .HasColumnType("boolean")
+ .HasColumnName("CustomEventFlag");
+
+ b.HasKey("ViewerId", "EventId");
+
+ b.ToTable("PlayerEventData");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventItem", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId", "Id");
+
+ b.HasIndex("ViewerId", "EventId");
+
+ b.ToTable("PlayerEventItems");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventPassive", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("EventId")
+ .HasColumnType("integer")
+ .HasColumnName("EventId");
+
+ b.Property("PassiveId")
+ .HasColumnType("integer")
+ .HasColumnName("PassiveId");
+
+ b.Property("Progress")
+ .HasColumnType("integer")
+ .HasColumnName("Progress");
+
+ b.HasKey("ViewerId", "EventId", "PassiveId");
+
+ b.ToTable("PlayerEventPassives");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerEventReward", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("EventId")
+ .HasColumnType("integer")
+ .HasColumnName("EventId");
+
+ b.Property("RewardId")
+ .HasColumnType("integer")
+ .HasColumnName("RewardId");
+
+ b.HasKey("ViewerId", "EventId", "RewardId");
+
+ b.ToTable("PlayerEventRewards");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerMaterial", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("MaterialId")
+ .HasColumnType("integer")
+ .HasColumnName("MaterialId");
+
+ b.Property("Quantity")
+ .HasColumnType("integer")
+ .HasColumnName("Quantity");
+
+ b.HasKey("ViewerId", "MaterialId");
+
+ b.ToTable("PlayerMaterial");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerMission", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId", "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("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.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.HasKey("PresentId");
+
+ b.HasIndex("ViewerId");
+
+ 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("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.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ViewerId");
+
+ b.ToTable("PlayerPresentHistory");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerQuestWall", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("WallId")
+ .HasColumnType("integer");
+
+ b.Property("IsStartNextLevel")
+ .HasColumnType("boolean");
+
+ b.Property("WallLevel")
+ .HasColumnType("integer");
+
+ b.HasKey("ViewerId", "WallId");
+
+ b.ToTable("PlayerQuestWalls");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerShopInfo", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property("DailySummonCount")
+ .HasColumnType("integer");
+
+ b.Property("LastSummonTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("ViewerId");
+
+ b.ToTable("PlayerShopInfos");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerShopPurchase", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ 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("ViewerId", "GoodsId");
+
+ b.ToTable("PlayerPurchases");
+ });
+
+ modelBuilder.Entity("DragaliaAPI.Database.Entities.DbPlayerStoryState", b =>
+ {
+ b.Property("ViewerId")
+ .HasColumnType("bigint");
+
+ b.Property