From b6abf380121bd1d84cb4dfc33d5361137a1b5fe4 Mon Sep 17 00:00:00 2001 From: Dippys Date: Tue, 10 Feb 2026 22:14:32 +0400 Subject: [PATCH 1/4] Respect Stuff Completed. --- Turbo.Catalog/Grains/LtdRaffleGrain.cs | 2 +- .../Entities/Players/PlayerEntity.cs | 20 + .../20260210173232_respects.Designer.cs | 2780 +++++++++++++++++ .../Migrations/20260210173232_respects.cs | 68 + .../Migrations/TurboDbContextModelSnapshot.cs | 28 + .../Handshake/InfoRetrieveMessageHandler.cs | 11 +- .../Users/ReplenishRespectMessageHandler.cs | 85 + .../Users/RespectUserMessageHandler.cs | 60 + Turbo.Players/Grains/PlayerGrain.cs | 125 +- Turbo.Players/Grains/PlayerLiveState.cs | 5 + .../Incoming/Room/Pets/RespectPetMessage.cs | 5 +- .../Incoming/Users/ReplenishRespectMessage.cs | 5 + .../Incoming/Users/RespectUserMessage.cs | 8 + .../Outgoing/Handshake/UserObjectMessage.cs | 1 + .../Pets/PetRespectFailedMessageComposer.cs | 6 +- .../RespectNotificationMessageComposer.cs | 6 +- .../Players/PlayerExtendedProfileSnapshot.cs | 3 + .../Players/PlayerSummarySnapshot.cs | 12 + .../Players/Grains/IPlayerGrain.cs | 30 + .../Room/Pets/RespectPetMessageParser.cs | 3 +- .../Users/ReplenishRespectMessageParser.cs | 10 + .../Parsers/Users/RespectUserMessageParser.cs | 11 + .../Revision20260112/Revision20260112.cs | 8 + .../Handshake/UserObjectMessageSerializer.cs | 10 +- ...tRespectFailedMessageComposerSerializer.cs | 3 +- ...ctNotificationMessageComposerSerializer.cs | 3 +- appsettings.json | 6 + 27 files changed, 3300 insertions(+), 14 deletions(-) create mode 100644 Turbo.Database/Migrations/20260210173232_respects.Designer.cs create mode 100644 Turbo.Database/Migrations/20260210173232_respects.cs create mode 100644 Turbo.PacketHandlers/Users/ReplenishRespectMessageHandler.cs create mode 100644 Turbo.PacketHandlers/Users/RespectUserMessageHandler.cs create mode 100644 Turbo.Primitives/Messages/Incoming/Users/ReplenishRespectMessage.cs create mode 100644 Turbo.Primitives/Messages/Incoming/Users/RespectUserMessage.cs create mode 100644 Turbo.Revisions/Revision20260112/Parsers/Users/ReplenishRespectMessageParser.cs create mode 100644 Turbo.Revisions/Revision20260112/Parsers/Users/RespectUserMessageParser.cs diff --git a/Turbo.Catalog/Grains/LtdRaffleGrain.cs b/Turbo.Catalog/Grains/LtdRaffleGrain.cs index 170dd826..2d86df60 100644 --- a/Turbo.Catalog/Grains/LtdRaffleGrain.cs +++ b/Turbo.Catalog/Grains/LtdRaffleGrain.cs @@ -451,7 +451,7 @@ private async Task CalculateWeightAsync(int playerId, CancellationToken if (cfg.RespectsReceived.Enabled) weight += Math.Min( - profile.StarGemCount * cfg.RespectsReceived.BonusPerUnit, + profile.RespectTotal * cfg.RespectsReceived.BonusPerUnit, cfg.RespectsReceived.MaxBonus ); diff --git a/Turbo.Database/Entities/Players/PlayerEntity.cs b/Turbo.Database/Entities/Players/PlayerEntity.cs index 79afc271..bb07ca9a 100644 --- a/Turbo.Database/Entities/Players/PlayerEntity.cs +++ b/Turbo.Database/Entities/Players/PlayerEntity.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; @@ -45,6 +46,25 @@ public class PlayerEntity : TurboEntity [Column("room_chat_style_id")] public int? RoomChatStyleId { get; set; } + [Column("respect_total")] + [DefaultValue(0)] + public int RespectTotal { get; set; } + + [Column("respect_left")] + [DefaultValue(0)] + public int RespectLeft { get; set; } + + [Column("pet_respect_left")] + [DefaultValue(0)] + public int PetRespectLeft { get; set; } + + [Column("respect_replenishes_left")] + [DefaultValue(1)] + public int RespectReplenishesLeft { get; set; } + + [Column("last_respect_reset")] + public DateTime? LastRespectReset { get; set; } + [InverseProperty("PlayerEntity")] public List? PlayerBadges { get; set; } diff --git a/Turbo.Database/Migrations/20260210173232_respects.Designer.cs b/Turbo.Database/Migrations/20260210173232_respects.Designer.cs new file mode 100644 index 00000000..cd61eccc --- /dev/null +++ b/Turbo.Database/Migrations/20260210173232_respects.Designer.cs @@ -0,0 +1,2780 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Turbo.Database.Context; + +#nullable disable + +namespace Turbo.Database.Migrations +{ + [DbContext(typeof(TurboDbContext))] + [Migration("20260210173232_respects")] + partial class respects + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.CatalogOfferEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CanBundle") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("can_bundle"); + + b.Property("CanGift") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("can_gift"); + + b.Property("CatalogPageEntityId") + .HasColumnType("int") + .HasColumnName("page_id"); + + b.Property("ClubLevel") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("club_level"); + + b.Property("CostCredits") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("cost_credits"); + + b.Property("CostCurrency") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("cost_currency"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("CurrencyTypeId") + .HasColumnType("int") + .HasColumnName("currency_type_id"); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("LocalizationId") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("localization_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.Property("Visible") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("visible"); + + b.HasKey("Id"); + + b.HasIndex("CatalogPageEntityId"); + + b.HasIndex("CurrencyTypeId"); + + b.ToTable("catalog_offers"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.CatalogPageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("Icon") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("icon"); + + b.PrimitiveCollection("ImageData") + .HasColumnType("longtext") + .HasColumnName("image_data"); + + b.Property("Layout") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasDefaultValue("default_3x3") + .HasColumnName("layout"); + + b.Property("Localization") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasColumnName("localization"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasColumnName("name"); + + b.Property("ParentEntityId") + .HasColumnType("int") + .HasColumnName("parent_id"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("sort_order"); + + b.PrimitiveCollection("TextData") + .HasColumnType("longtext") + .HasColumnName("text_data"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.Property("Visible") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("visible"); + + b.HasKey("Id"); + + b.HasIndex("ParentEntityId"); + + b.ToTable("catalog_pages"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.CatalogProductEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CatalogOfferEntityId") + .HasColumnType("int") + .HasColumnName("offer_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("ExtraParam") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("extra_param"); + + b.Property("FurnitureDefinitionEntityId") + .HasColumnType("int") + .HasColumnName("definition_id"); + + b.Property("ProductType") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("product_type"); + + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("quantity"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("CatalogOfferEntityId"); + + b.HasIndex("FurnitureDefinitionEntityId"); + + b.ToTable("catalog_products"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.CurrencyTypeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ActivityPointType") + .HasColumnType("int") + .HasColumnName("activity_point_type"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("CurrencyType") + .HasColumnType("int") + .HasColumnName("type"); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("enabled"); + + b.Property("Name") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("name"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.ToTable("currency_types"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.LtdRaffleEntryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BatchId") + .IsRequired() + .HasMaxLength(36) + .HasColumnType("varchar(36)") + .HasColumnName("batch_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("EnteredAt") + .HasColumnType("datetime(6)") + .HasColumnName("entered_at"); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("ProcessedAt") + .HasColumnType("datetime(6)") + .HasColumnName("processed_at"); + + b.Property("Result") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("pending") + .HasColumnName("result"); + + b.Property("SerialNumber") + .HasColumnType("int") + .HasColumnName("serial_number"); + + b.Property("SeriesEntityId") + .HasColumnType("int") + .HasColumnName("series_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId"); + + b.HasIndex("SeriesEntityId"); + + b.ToTable("ltd_raffle_entries"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.LtdSeriesEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CatalogProductEntityId") + .HasColumnType("int") + .HasColumnName("catalog_product_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("EndsAt") + .HasColumnType("datetime(6)") + .HasColumnName("ends_at"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("is_active"); + + b.Property("IsRaffleFinished") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("has_raffle_finished"); + + b.Property("RaffleWindowSeconds") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(30) + .HasColumnName("raffle_window_seconds"); + + b.Property("RemainingQuantity") + .HasColumnType("int") + .HasColumnName("remaining_quantity"); + + b.Property("StartsAt") + .HasColumnType("datetime(6)") + .HasColumnName("starts_at"); + + b.Property("TotalQuantity") + .HasColumnType("int") + .HasColumnName("total_quantity"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("CatalogProductEntityId"); + + b.ToTable("ltd_series"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Furniture.FurnitureDefinitionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CanGroup") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("can_group"); + + b.Property("CanLay") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("can_lay"); + + b.Property("CanRecycle") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("can_recycle"); + + b.Property("CanSell") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("can_sell"); + + b.Property("CanSit") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("can_sit"); + + b.Property("CanStack") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("can_stack"); + + b.Property("CanTrade") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("can_trade"); + + b.Property("CanWalk") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("can_walk"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("ExtraData") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("extra_data"); + + b.Property("FurniCategory") + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("category"); + + b.Property("Length") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("length"); + + b.Property("Logic") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasDefaultValue("none") + .HasColumnName("logic"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("name"); + + b.Property("ProductType") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("type"); + + b.Property("SpriteId") + .HasColumnType("int") + .HasColumnName("sprite_id"); + + b.Property("StackHeight") + .ValueGeneratedOnAdd() + .HasColumnType("double(10,3)") + .HasDefaultValue(0.0) + .HasColumnName("stack_height"); + + b.Property("TotalStates") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("total_states"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.Property("UsagePolicy") + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("usage_policy"); + + b.Property("Width") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("width"); + + b.HasKey("Id"); + + b.HasIndex("SpriteId", "ProductType", "FurniCategory") + .IsUnique(); + + b.ToTable("furniture_definitions"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Furniture.FurnitureEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("ExtraData") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("extra_data"); + + b.Property("FurnitureDefinitionEntityId") + .HasColumnType("int") + .HasColumnName("definition_id"); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RoomEntityId") + .HasColumnType("int") + .HasColumnName("room_id"); + + b.Property("Rotation") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("direction"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.Property("WallOffset") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("wall_offset"); + + b.Property("X") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("x"); + + b.Property("Y") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("y"); + + b.Property("Z") + .ValueGeneratedOnAdd() + .HasColumnType("double(10,3)") + .HasDefaultValue(0.0) + .HasColumnName("z"); + + b.HasKey("Id"); + + b.HasIndex("FurnitureDefinitionEntityId"); + + b.HasIndex("PlayerEntityId"); + + b.HasIndex("RoomEntityId"); + + b.ToTable("furniture"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Furniture.FurnitureTeleportLinkEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("FurnitureEntityOneId") + .HasColumnType("int") + .HasColumnName("furniture_one_id"); + + b.Property("FurnitureEntityTwoId") + .HasColumnType("int") + .HasColumnName("furniture_two_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("FurnitureEntityOneId") + .IsUnique(); + + b.HasIndex("FurnitureEntityTwoId") + .IsUnique(); + + b.ToTable("furniture_teleport_links"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerBlockedEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BlockedPlayerEntityId") + .HasColumnType("int") + .HasColumnName("blocked_player_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("BlockedPlayerEntityId"); + + b.HasIndex("PlayerEntityId", "BlockedPlayerEntityId") + .IsUnique(); + + b.ToTable("messenger_blocked"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerCategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("name"); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId", "Name") + .IsUnique(); + + b.ToTable("messenger_categories"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerFriendEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("FriendPlayerEntityId") + .HasColumnType("int") + .HasColumnName("requested_id"); + + b.Property("MessengerCategoryEntityId") + .HasColumnType("int") + .HasColumnName("category_id"); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RelationType") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("relation"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("FriendPlayerEntityId"); + + b.HasIndex("MessengerCategoryEntityId"); + + b.HasIndex("PlayerEntityId", "FriendPlayerEntityId") + .IsUnique(); + + b.ToTable("messenger_friends"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerIgnoredEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("IgnoredPlayerEntityId") + .HasColumnType("int") + .HasColumnName("ignored_player_id"); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("IgnoredPlayerEntityId"); + + b.HasIndex("PlayerEntityId", "IgnoredPlayerEntityId") + .IsUnique(); + + b.ToTable("messenger_ignored"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerMessageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("Delivered") + .HasColumnType("tinyint(1)") + .HasColumnName("delivered"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("message"); + + b.Property("ReceiverPlayerEntityId") + .HasColumnType("int") + .HasColumnName("receiver_id"); + + b.Property("SenderPlayerEntityId") + .HasColumnType("int") + .HasColumnName("sender_id"); + + b.Property("Timestamp") + .HasColumnType("datetime(6)") + .HasColumnName("timestamp"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("ReceiverPlayerEntityId", "SenderPlayerEntityId", "Timestamp"); + + b.HasIndex("SenderPlayerEntityId", "ReceiverPlayerEntityId", "Timestamp"); + + b.ToTable("messenger_messages"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RequestedPlayerEntityId") + .HasColumnType("int") + .HasColumnName("requested_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("RequestedPlayerEntityId"); + + b.HasIndex("PlayerEntityId", "RequestedPlayerEntityId") + .IsUnique(); + + b.ToTable("messenger_requests"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Navigator.NavigatorEventCategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("name"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.Property("Visible") + .HasColumnType("tinyint(1)") + .HasColumnName("visible"); + + b.HasKey("Id"); + + b.ToTable("navigator_eventcats"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Navigator.NavigatorFlatCategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Automatic") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("automatic"); + + b.Property("AutomaticCategory") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("automatic_category"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("GlobalCategory") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("global_category"); + + b.Property("MinRank") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("min_rank"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("name"); + + b.Property("OrderNum") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("order_num"); + + b.Property("StaffOnly") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("staff_only"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.Property("Visible") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("visible"); + + b.HasKey("Id"); + + b.ToTable("navigator_flatcats"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Navigator.NavigatorTopLevelContextEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("OrderNum") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("order_num"); + + b.Property("SearchCode") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("search_code"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.Property("Visible") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("visible"); + + b.HasKey("Id"); + + b.HasIndex("SearchCode") + .IsUnique(); + + b.ToTable("navigator_top_level_contexts"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerBadgeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BadgeCode") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("badge_code"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("SlotId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("slot_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId", "BadgeCode") + .IsUnique(); + + b.ToTable("player_badges"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerChatStyleEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientStyleId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("client_style_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("ClientStyleId") + .IsUnique(); + + b.ToTable("player_chat_styles"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerChatStyleOwnedEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatStyleId") + .HasColumnType("int") + .HasColumnName("chat_style_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("ChatStyleId"); + + b.HasIndex("PlayerEntityId", "ChatStyleId") + .IsUnique(); + + b.ToTable("player_chat_styles_owned"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerCurrencyEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("amount"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("CurrencyTypeEntityId") + .HasColumnType("int") + .HasColumnName("currency_type_id"); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("CurrencyTypeEntityId"); + + b.HasIndex("PlayerEntityId", "CurrencyTypeEntityId") + .IsUnique(); + + b.ToTable("player_currencies"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("Figure") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasDefaultValue("hr-115-42.hd-195-19.ch-3030-82.lg-275-1408.fa-1201.ca-1804-64") + .HasColumnName("figure"); + + b.Property("Gender") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("gender"); + + b.Property("LastRespectReset") + .HasColumnType("datetime(6)") + .HasColumnName("last_respect_reset"); + + b.Property("Motto") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("motto"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("name"); + + b.Property("PetRespectLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("pet_respect_left"); + + b.Property("PlayerPerks") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("perk_flags"); + + b.Property("PlayerStatus") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("status"); + + b.Property("RespectLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("respect_left"); + + b.Property("RespectReplenishesLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("respect_replenishes_left"); + + b.Property("RespectTotal") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("respect_total"); + + b.Property("RoomChatStyleId") + .HasColumnType("int") + .HasColumnName("room_chat_style_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("players"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerFavoriteRoomsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RoomEntityId") + .HasColumnType("int") + .HasColumnName("room_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("RoomEntityId"); + + b.HasIndex("PlayerEntityId", "RoomEntityId") + .IsUnique(); + + b.ToTable("player_favorite_rooms"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomBanEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DateExpires") + .HasColumnType("datetime(6)") + .HasColumnName("date_expires"); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RoomEntityId") + .HasColumnType("int") + .HasColumnName("room_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId"); + + b.HasIndex("RoomEntityId", "PlayerEntityId") + .IsUnique(); + + b.ToTable("room_bans"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomChatlogEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("Message") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("message"); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RoomEntityId") + .HasColumnType("int") + .HasColumnName("room_id"); + + b.Property("TargetPlayerEntityId") + .HasColumnType("int") + .HasColumnName("target_player_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId"); + + b.HasIndex("RoomEntityId"); + + b.HasIndex("TargetPlayerEntityId"); + + b.ToTable("room_chatlogs"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AllowBlocking") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("allow_blocking"); + + b.Property("AllowPets") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("allow_pets"); + + b.Property("AllowPetsEat") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("allow_pets_eat"); + + b.Property("BanType") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("ban_type"); + + b.Property("ChatBubbleType") + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("chat_bubble_type"); + + b.Property("ChatDistance") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(50) + .HasColumnName("chat_distance"); + + b.Property("ChatFloodType") + .HasColumnType("int") + .HasDefaultValue(2) + .HasColumnName("chat_flood_type"); + + b.Property("ChatModeType") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("chat_mode_type"); + + b.Property("ChatSpeedType") + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("chat_speed_type"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("description"); + + b.Property("DoorMode") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("door_mode"); + + b.Property("HideWalls") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("hide_walls"); + + b.Property("KickType") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("kick_type"); + + b.Property("LastActive") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("last_active"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LastActive")); + + b.Property("MuteType") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("mute_type"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("name"); + + b.Property("NavigatorCategoryEntityId") + .HasColumnType("int") + .HasColumnName("category_id"); + + b.Property("PaintFloor") + .ValueGeneratedOnAdd() + .HasColumnType("double") + .HasDefaultValue(0.0) + .HasColumnName("paint_floor"); + + b.Property("PaintLandscape") + .ValueGeneratedOnAdd() + .HasColumnType("double") + .HasDefaultValue(0.0) + .HasColumnName("paint_landscape"); + + b.Property("PaintWall") + .ValueGeneratedOnAdd() + .HasColumnType("double") + .HasDefaultValue(0.0) + .HasColumnName("paint_wall"); + + b.Property("Password") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("password"); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("PlayersMax") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(25) + .HasColumnName("players_max"); + + b.Property("RoomModelEntityId") + .HasColumnType("int") + .HasColumnName("model_id"); + + b.Property("ThicknessFloor") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("thickness_floor"); + + b.Property("ThicknessWall") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("thickness_wall"); + + b.Property("TradeType") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("trade_type"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.Property("UsersNow") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("users_now"); + + b.Property("WallHeight") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(-1) + .HasColumnName("wall_height"); + + b.HasKey("Id"); + + b.HasIndex("NavigatorCategoryEntityId"); + + b.HasIndex("PlayerEntityId"); + + b.HasIndex("RoomModelEntityId"); + + b.ToTable("rooms"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomEntryLogEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RoomEntityId") + .HasColumnType("int") + .HasColumnName("room_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId"); + + b.HasIndex("RoomEntityId"); + + b.ToTable("room_entry_logs"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomModelEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("Custom") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("custom"); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("DoorRotation") + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("door_rotation"); + + b.Property("DoorX") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("door_x"); + + b.Property("DoorY") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("door_y"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true) + .HasColumnName("enabled"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("longtext") + .HasColumnName("model"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("name"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("room_models"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomMuteEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DateExpires") + .HasColumnType("datetime(6)") + .HasColumnName("date_expires"); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RoomEntityId") + .HasColumnType("int") + .HasColumnName("room_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId"); + + b.HasIndex("RoomEntityId", "PlayerEntityId") + .IsUnique(); + + b.ToTable("room_mutes"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomRightEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RoomEntityId") + .HasColumnType("int") + .HasColumnName("room_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId"); + + b.HasIndex("RoomEntityId", "PlayerEntityId") + .IsUnique(); + + b.ToTable("room_rights"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Security.SecurityTicketEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("ip_address"); + + b.Property("IsLocked") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("is_locked"); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("Ticket") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("ticket"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId") + .IsUnique(); + + b.HasIndex("Ticket") + .IsUnique(); + + b.ToTable("security_tickets"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Tracking.PerformanceLogEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AverageFrameRate") + .HasColumnType("int") + .HasColumnName("average_frame_rate"); + + b.Property("Browser") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("browser"); + + b.Property("ElapsedTime") + .HasColumnType("int") + .HasColumnName("elapsed_time"); + + b.Property("FlashVersion") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("flash_version"); + + b.Property("GarbageCollections") + .HasColumnType("int") + .HasColumnName("garbage_collections"); + + b.Property("IPAddress") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("varchar(45)") + .HasColumnName("ip_address"); + + b.Property("IsDebugger") + .HasColumnType("tinyint(1)") + .HasColumnName("is_debugger"); + + b.Property("MemoryUsage") + .HasColumnType("int") + .HasColumnName("memory_usage"); + + b.Property("OS") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("os"); + + b.Property("UserAgent") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("user_agent"); + + b.HasKey("Id"); + + b.HasIndex("ElapsedTime"); + + b.HasIndex("IPAddress"); + + b.ToTable("performance_logs"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.CatalogOfferEntity", b => + { + b.HasOne("Turbo.Database.Entities.Catalog.CatalogPageEntity", "Page") + .WithMany("Offers") + .HasForeignKey("CatalogPageEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Catalog.CurrencyTypeEntity", "CurrencyTypeEntity") + .WithMany("CatalogOffers") + .HasForeignKey("CurrencyTypeId"); + + b.Navigation("CurrencyTypeEntity"); + + b.Navigation("Page"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.CatalogPageEntity", b => + { + b.HasOne("Turbo.Database.Entities.Catalog.CatalogPageEntity", "ParentEntity") + .WithMany("Children") + .HasForeignKey("ParentEntityId"); + + b.Navigation("ParentEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.CatalogProductEntity", b => + { + b.HasOne("Turbo.Database.Entities.Catalog.CatalogOfferEntity", "Offer") + .WithMany("Products") + .HasForeignKey("CatalogOfferEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Furniture.FurnitureDefinitionEntity", "FurnitureDefinition") + .WithMany() + .HasForeignKey("FurnitureDefinitionEntityId"); + + b.Navigation("FurnitureDefinition"); + + b.Navigation("Offer"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.LtdRaffleEntryEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "Player") + .WithMany() + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Catalog.LtdSeriesEntity", "Series") + .WithMany() + .HasForeignKey("SeriesEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.LtdSeriesEntity", b => + { + b.HasOne("Turbo.Database.Entities.Catalog.CatalogProductEntity", "CatalogProduct") + .WithMany() + .HasForeignKey("CatalogProductEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogProduct"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Furniture.FurnitureEntity", b => + { + b.HasOne("Turbo.Database.Entities.Furniture.FurnitureDefinitionEntity", "FurnitureDefinitionEntity") + .WithMany("Furnitures") + .HasForeignKey("FurnitureDefinitionEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("Furniture") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Room.RoomEntity", "RoomEntity") + .WithMany() + .HasForeignKey("RoomEntityId"); + + b.Navigation("FurnitureDefinitionEntity"); + + b.Navigation("PlayerEntity"); + + b.Navigation("RoomEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Furniture.FurnitureTeleportLinkEntity", b => + { + b.HasOne("Turbo.Database.Entities.Furniture.FurnitureEntity", "FurnitureEntityOne") + .WithMany() + .HasForeignKey("FurnitureEntityOneId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Furniture.FurnitureEntity", "FurnitureEntityTwo") + .WithMany() + .HasForeignKey("FurnitureEntityTwoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FurnitureEntityOne"); + + b.Navigation("FurnitureEntityTwo"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerBlockedEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "BlockedPlayerEntity") + .WithMany() + .HasForeignKey("BlockedPlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("MessengerBlocked") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlockedPlayerEntity"); + + b.Navigation("PlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerCategoryEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("MessengerCategories") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerFriendEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "FriendPlayerEntity") + .WithMany() + .HasForeignKey("FriendPlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Messenger.MessengerCategoryEntity", "MessengerCategoryEntity") + .WithMany() + .HasForeignKey("MessengerCategoryEntityId"); + + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("MessengerFriends") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FriendPlayerEntity"); + + b.Navigation("MessengerCategoryEntity"); + + b.Navigation("PlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerIgnoredEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "IgnoredPlayerEntity") + .WithMany() + .HasForeignKey("IgnoredPlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("MessengerIgnored") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IgnoredPlayerEntity"); + + b.Navigation("PlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerMessageEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "ReceiverPlayerEntity") + .WithMany("MessengerMessagesReceived") + .HasForeignKey("ReceiverPlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "SenderPlayerEntity") + .WithMany("MessengerMessagesSent") + .HasForeignKey("SenderPlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReceiverPlayerEntity"); + + b.Navigation("SenderPlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Messenger.MessengerRequestEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("MessengerRequestsSent") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "RequestedPlayerEntity") + .WithMany("MessengerRequests") + .HasForeignKey("RequestedPlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + + b.Navigation("RequestedPlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerBadgeEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("PlayerBadges") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerChatStyleOwnedEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerChatStyleEntity", "ChatStyle") + .WithMany("OwnedChatStyles") + .HasForeignKey("ChatStyleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("PlayerOwnedChatStyles") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatStyle"); + + b.Navigation("PlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerCurrencyEntity", b => + { + b.HasOne("Turbo.Database.Entities.Catalog.CurrencyTypeEntity", "CurrencyTypeEntity") + .WithMany("PlayerCurrencies") + .HasForeignKey("CurrencyTypeEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("PlayerCurrencies") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CurrencyTypeEntity"); + + b.Navigation("PlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerFavoriteRoomsEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany() + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Room.RoomEntity", "RoomEntity") + .WithMany() + .HasForeignKey("RoomEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + + b.Navigation("RoomEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomBanEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("RoomBans") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Room.RoomEntity", "RoomEntity") + .WithMany("RoomBans") + .HasForeignKey("RoomEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + + b.Navigation("RoomEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomChatlogEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("RoomChatlogs") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Room.RoomEntity", "RoomEntity") + .WithMany("RoomChats") + .HasForeignKey("RoomEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "TargetPlayerEntity") + .WithMany() + .HasForeignKey("TargetPlayerEntityId"); + + b.Navigation("PlayerEntity"); + + b.Navigation("RoomEntity"); + + b.Navigation("TargetPlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomEntity", b => + { + b.HasOne("Turbo.Database.Entities.Navigator.NavigatorFlatCategoryEntity", "NavigatorFlatCategoryEntity") + .WithMany() + .HasForeignKey("NavigatorCategoryEntityId"); + + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("Rooms") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Room.RoomModelEntity", "RoomModelEntity") + .WithMany() + .HasForeignKey("RoomModelEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NavigatorFlatCategoryEntity"); + + b.Navigation("PlayerEntity"); + + b.Navigation("RoomModelEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomEntryLogEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany() + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Room.RoomEntity", "RoomEntity") + .WithMany() + .HasForeignKey("RoomEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + + b.Navigation("RoomEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomMuteEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("RoomMutes") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Room.RoomEntity", "RoomEntity") + .WithMany("RoomMutes") + .HasForeignKey("RoomEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + + b.Navigation("RoomEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomRightEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("RoomRights") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turbo.Database.Entities.Room.RoomEntity", "RoomEntity") + .WithMany("RoomRights") + .HasForeignKey("RoomEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + + b.Navigation("RoomEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Security.SecurityTicketEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithMany("SecurityTickets") + .HasForeignKey("PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.CatalogOfferEntity", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.CatalogPageEntity", b => + { + b.Navigation("Children"); + + b.Navigation("Offers"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Catalog.CurrencyTypeEntity", b => + { + b.Navigation("CatalogOffers"); + + b.Navigation("PlayerCurrencies"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Furniture.FurnitureDefinitionEntity", b => + { + b.Navigation("Furnitures"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerChatStyleEntity", b => + { + b.Navigation("OwnedChatStyles"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerEntity", b => + { + b.Navigation("Furniture"); + + b.Navigation("MessengerBlocked"); + + b.Navigation("MessengerCategories"); + + b.Navigation("MessengerFriends"); + + b.Navigation("MessengerIgnored"); + + b.Navigation("MessengerMessagesReceived"); + + b.Navigation("MessengerMessagesSent"); + + b.Navigation("MessengerRequests"); + + b.Navigation("MessengerRequestsSent"); + + b.Navigation("PlayerBadges"); + + b.Navigation("PlayerCurrencies"); + + b.Navigation("PlayerOwnedChatStyles"); + + b.Navigation("RoomBans"); + + b.Navigation("RoomChatlogs"); + + b.Navigation("RoomMutes"); + + b.Navigation("RoomRights"); + + b.Navigation("Rooms"); + + b.Navigation("SecurityTickets"); + }); + + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomEntity", b => + { + b.Navigation("RoomBans"); + + b.Navigation("RoomChats"); + + b.Navigation("RoomMutes"); + + b.Navigation("RoomRights"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Turbo.Database/Migrations/20260210173232_respects.cs b/Turbo.Database/Migrations/20260210173232_respects.cs new file mode 100644 index 00000000..e1bf56c2 --- /dev/null +++ b/Turbo.Database/Migrations/20260210173232_respects.cs @@ -0,0 +1,68 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Turbo.Database.Migrations +{ + /// + public partial class respects : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "last_respect_reset", + table: "players", + type: "datetime(6)", + nullable: true + ); + + migrationBuilder.AddColumn( + name: "pet_respect_left", + table: "players", + type: "int", + nullable: false, + defaultValue: 0 + ); + + migrationBuilder.AddColumn( + name: "respect_left", + table: "players", + type: "int", + nullable: false, + defaultValue: 0 + ); + + migrationBuilder.AddColumn( + name: "respect_replenishes_left", + table: "players", + type: "int", + nullable: false, + defaultValue: 1 + ); + + migrationBuilder.AddColumn( + name: "respect_total", + table: "players", + type: "int", + nullable: false, + defaultValue: 0 + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "last_respect_reset", table: "players"); + + migrationBuilder.DropColumn(name: "pet_respect_left", table: "players"); + + migrationBuilder.DropColumn(name: "respect_left", table: "players"); + + migrationBuilder.DropColumn(name: "respect_replenishes_left", table: "players"); + + migrationBuilder.DropColumn(name: "respect_total", table: "players"); + } + } +} diff --git a/Turbo.Database/Migrations/TurboDbContextModelSnapshot.cs b/Turbo.Database/Migrations/TurboDbContextModelSnapshot.cs index 8a00bf1d..0e83906a 100644 --- a/Turbo.Database/Migrations/TurboDbContextModelSnapshot.cs +++ b/Turbo.Database/Migrations/TurboDbContextModelSnapshot.cs @@ -1478,6 +1478,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasDefaultValue(0) .HasColumnName("gender"); + b.Property("LastRespectReset") + .HasColumnType("datetime(6)") + .HasColumnName("last_respect_reset"); + b.Property("Motto") .HasMaxLength(512) .HasColumnType("varchar(512)") @@ -1489,6 +1493,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("varchar(512)") .HasColumnName("name"); + b.Property("PetRespectLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("pet_respect_left"); + b.Property("PlayerPerks") .HasColumnType("int") .HasDefaultValue(0) @@ -1499,6 +1509,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasDefaultValue(0) .HasColumnName("status"); + b.Property("RespectLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("respect_left"); + + b.Property("RespectReplenishesLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("respect_replenishes_left"); + + b.Property("RespectTotal") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("respect_total"); + b.Property("RoomChatStyleId") .HasColumnType("int") .HasColumnName("room_chat_style_id"); diff --git a/Turbo.PacketHandlers/Handshake/InfoRetrieveMessageHandler.cs b/Turbo.PacketHandlers/Handshake/InfoRetrieveMessageHandler.cs index d7e18a03..313b100e 100644 --- a/Turbo.PacketHandlers/Handshake/InfoRetrieveMessageHandler.cs +++ b/Turbo.PacketHandlers/Handshake/InfoRetrieveMessageHandler.cs @@ -1,5 +1,6 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; using Orleans; using Turbo.Messages.Registry; using Turbo.Primitives.Messages.Incoming.Handshake; @@ -8,10 +9,11 @@ namespace Turbo.PacketHandlers.Handshake; -public class InfoRetrieveMessageHandler(IGrainFactory grainFactory) +public class InfoRetrieveMessageHandler(IGrainFactory grainFactory, IConfiguration configuration) : IMessageHandler { private readonly IGrainFactory _grainFactory = grainFactory; + private readonly IConfiguration _configuration = configuration; public async ValueTask HandleAsync( InfoRetrieveMessage message, @@ -22,7 +24,12 @@ CancellationToken ct var player = _grainFactory.GetPlayerGrain(ctx.PlayerId); var snapshot = await player.GetSummaryAsync(ct).ConfigureAwait(false); - await ctx.SendComposerAsync(new UserObjectMessage { Player = snapshot }, ct) + var maxRespectPerDay = _configuration.GetValue("Turbo:Respect:DailyRespectAmount", 3); + + await ctx.SendComposerAsync( + new UserObjectMessage { Player = snapshot, MaxRespectPerDay = maxRespectPerDay }, + ct + ) .ConfigureAwait(false); } } diff --git a/Turbo.PacketHandlers/Users/ReplenishRespectMessageHandler.cs b/Turbo.PacketHandlers/Users/ReplenishRespectMessageHandler.cs new file mode 100644 index 00000000..3bba9e42 --- /dev/null +++ b/Turbo.PacketHandlers/Users/ReplenishRespectMessageHandler.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Orleans; +using Turbo.Messages.Registry; +using Turbo.Primitives.Messages.Incoming.Users; +using Turbo.Primitives.Messages.Outgoing.Handshake; +using Turbo.Primitives.Messages.Outgoing.Notifications; +using Turbo.Primitives.Orleans; +using Turbo.Primitives.Players.Enums.Wallet; +using Turbo.Primitives.Players.Wallet; + +namespace Turbo.PacketHandlers.Users; + +public class ReplenishRespectMessageHandler( + IGrainFactory grainFactory, + IConfiguration configuration +) : IMessageHandler +{ + private readonly IGrainFactory _grainFactory = grainFactory; + private readonly IConfiguration _configuration = configuration; + + public async ValueTask HandleAsync( + ReplenishRespectMessage message, + MessageContext ctx, + CancellationToken ct + ) + { + if (ctx.PlayerId <= 0) + return; + + var replenishCost = _configuration.GetValue("Turbo:Respect:ReplenishCostDuckets", 20); + var maxRespectPerDay = _configuration.GetValue("Turbo:Respect:DailyRespectAmount", 3); + + // Deduct duckets via the wallet grain + var walletGrain = _grainFactory.GetPlayerWalletGrain(ctx.PlayerId); + var debitResult = await walletGrain + .TryDebitAsync( + [ + new WalletDebitRequest + { + CurrencyKind = new CurrencyKind + { + CurrencyType = CurrencyType.ActivityPoints, + ActivityPointType = 0, + }, + Amount = replenishCost, + }, + ], + ct + ) + .ConfigureAwait(false); + + if (!debitResult.Succeeded) + return; + + // Replenish respect on the player grain + var playerGrain = _grainFactory.GetPlayerGrain(ctx.PlayerId); + var replenished = await playerGrain + .TryReplenishRespectAsync(maxRespectPerDay, ct) + .ConfigureAwait(false); + + if (!replenished) + return; + + // Send full activity points snapshot so the client purse reflects the currency deduction + var activityPoints = await walletGrain.GetActivityPointsAsync(ct).ConfigureAwait(false); + + await ctx.SendComposerAsync( + new ActivityPointsMessageComposer { PointsByCategoryId = activityPoints }, + ct + ) + .ConfigureAwait(false); + + // Send updated UserObject to refresh the client's respect data + var snapshot = await playerGrain.GetSummaryAsync(ct).ConfigureAwait(false); + + await ctx.SendComposerAsync( + new UserObjectMessage { Player = snapshot, MaxRespectPerDay = maxRespectPerDay }, + ct + ) + .ConfigureAwait(false); + } +} diff --git a/Turbo.PacketHandlers/Users/RespectUserMessageHandler.cs b/Turbo.PacketHandlers/Users/RespectUserMessageHandler.cs new file mode 100644 index 00000000..4335e4a7 --- /dev/null +++ b/Turbo.PacketHandlers/Users/RespectUserMessageHandler.cs @@ -0,0 +1,60 @@ +using System.Threading; +using System.Threading.Tasks; +using Orleans; +using Turbo.Messages.Registry; +using Turbo.Primitives.Messages.Incoming.Users; +using Turbo.Primitives.Messages.Outgoing.Users; +using Turbo.Primitives.Orleans; +using Turbo.Primitives.Rooms.Enums; + +namespace Turbo.PacketHandlers.Users; + +public class RespectUserMessageHandler(IGrainFactory grainFactory) + : IMessageHandler +{ + private readonly IGrainFactory _grainFactory = grainFactory; + + public async ValueTask HandleAsync( + RespectUserMessage message, + MessageContext ctx, + CancellationToken ct + ) + { + if (ctx.PlayerId <= 0 || ctx.RoomId <= 0 || message.UserId <= 0) + return; + + // Cannot respect yourself + if ((int)ctx.PlayerId == message.UserId) + return; + + // Check if giver has respect left and consume it + var giverGrain = _grainFactory.GetPlayerGrain(ctx.PlayerId); + var consumed = await giverGrain.TryConsumeRespectAsync(ct).ConfigureAwait(false); + + if (!consumed) + return; + + // Give respect to the target player + var targetGrain = _grainFactory.GetPlayerGrain(message.UserId); + var newTotal = await targetGrain + .ReceiveRespectAsync(ctx.PlayerId, ct) + .ConfigureAwait(false); + + // Play the thumbs-up expression on the giver + var roomGrain = _grainFactory.GetRoomGrain(ctx.RoomId); + await roomGrain + .SetAvatarExpressionAsync(ctx.AsActionContext(), AvatarExpressionType.Respect, ct) + .ConfigureAwait(false); + + // Broadcast the respect notification to the entire room + await roomGrain + .SendComposerToRoomAsync( + new RespectNotificationMessageComposer + { + UserId = message.UserId, + RespectTotal = newTotal, + } + ) + .ConfigureAwait(false); + } +} diff --git a/Turbo.Players/Grains/PlayerGrain.cs b/Turbo.Players/Grains/PlayerGrain.cs index 85e0168f..d3d32644 100644 --- a/Turbo.Players/Grains/PlayerGrain.cs +++ b/Turbo.Players/Grains/PlayerGrain.cs @@ -3,6 +3,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Orleans; using Turbo.Database.Context; using Turbo.Logging; @@ -19,13 +21,22 @@ internal sealed class PlayerGrain : Grain, IPlayerGrain { private readonly IDbContextFactory _dbCtxFactory; private readonly IGrainFactory _grainFactory; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; private readonly PlayerLiveState _state; - public PlayerGrain(IDbContextFactory dbCtxFactory, IGrainFactory grainFactory) + public PlayerGrain( + IDbContextFactory dbCtxFactory, + IGrainFactory grainFactory, + ILogger logger, + IConfiguration configuration + ) { _dbCtxFactory = dbCtxFactory; _grainFactory = grainFactory; + _logger = logger; + _configuration = configuration; _state = new() { PlayerId = PlayerId.Parse((int)this.GetPrimaryKeyLong()) }; } @@ -33,6 +44,7 @@ public PlayerGrain(IDbContextFactory dbCtxFactory, IGrainFactory public override async Task OnActivateAsync(CancellationToken ct) { await HydrateAsync(ct); + await TryResetDailyRespectIfNeededAsync(ct); } public override async Task OnDeactivateAsync(DeactivationReason reason, CancellationToken ct) @@ -84,6 +96,11 @@ await dbCtx _state.AchievementScore = 0; _state.CreatedAt = entity.CreatedAt; _state.LastUpdated = entity.UpdatedAt; + _state.RespectTotal = entity.RespectTotal; + _state.RespectLeft = entity.RespectLeft; + _state.PetRespectLeft = entity.PetRespectLeft; + _state.RespectReplenishesLeft = entity.RespectReplenishesLeft; + _state.LastRespectReset = entity.LastRespectReset ?? DateTime.MinValue; await _grainFactory .GetPlayerDirectoryGrain() @@ -103,7 +120,17 @@ await dbCtx up.SetProperty(p => p.Name, snapshot.Name) .SetProperty(p => p.Motto, snapshot.Motto) .SetProperty(p => p.Figure, snapshot.Figure) - .SetProperty(p => p.Gender, snapshot.Gender), + .SetProperty(p => p.Gender, snapshot.Gender) + .SetProperty(p => p.RespectTotal, _state.RespectTotal) + .SetProperty(p => p.RespectLeft, _state.RespectLeft) + .SetProperty(p => p.PetRespectLeft, _state.PetRespectLeft) + .SetProperty(p => p.RespectReplenishesLeft, _state.RespectReplenishesLeft) + .SetProperty( + p => p.LastRespectReset, + _state.LastRespectReset == DateTime.MinValue + ? null + : (DateTime?)_state.LastRespectReset + ), ct ); @@ -121,6 +148,10 @@ public Task GetSummaryAsync(CancellationToken ct) => Gender = _state.Gender, AchievementScore = _state.AchievementScore, CreatedAt = _state.CreatedAt, + RespectTotal = _state.RespectTotal, + RespectLeft = _state.RespectLeft, + PetRespectLeft = _state.PetRespectLeft, + RespectReplenishesLeft = _state.RespectReplenishesLeft, } ); @@ -146,9 +177,99 @@ public Task GetExtendedProfileSnapshotAsync(Cance AccountLevel = 1, IntegerField24 = 0, StarGemCount = 0, + RespectTotal = _state.RespectTotal, BooleanField26 = false, BooleanField27 = false, } ); } + + public async Task ReceiveRespectAsync(PlayerId giverId, CancellationToken ct) + { + _state.RespectTotal++; + await WriteToDatabaseAsync(ct); + + _logger.LogDebug( + "Player {PlayerId} received respect from {GiverId}. Total: {Total}", + _state.PlayerId, + giverId, + _state.RespectTotal + ); + + return _state.RespectTotal; + } + + public async Task TryConsumeRespectAsync(CancellationToken ct) + { + await TryResetDailyRespectIfNeededAsync(ct); + + if (_state.RespectLeft <= 0) + return false; + + _state.RespectLeft--; + await WriteToDatabaseAsync(ct); + return true; + } + + public async Task TryReplenishRespectAsync(int maxRespectPerDay, CancellationToken ct) + { + await TryResetDailyRespectIfNeededAsync(ct); + + if (_state.RespectReplenishesLeft <= 0) + return false; + + _state.RespectReplenishesLeft--; + _state.RespectLeft = maxRespectPerDay; + await WriteToDatabaseAsync(ct); + + _logger.LogDebug( + "Player {PlayerId} replenished respect. RespectLeft: {Left}, ReplenishesLeft: {Replenishes}", + _state.PlayerId, + _state.RespectLeft, + _state.RespectReplenishesLeft + ); + + return true; + } + + public async Task ResetDailyRespectAsync( + int dailyRespectAmount, + int dailyPetRespectAmount, + int dailyReplenishLimit, + CancellationToken ct + ) + { + _state.RespectLeft = dailyRespectAmount; + _state.PetRespectLeft = dailyPetRespectAmount; + _state.RespectReplenishesLeft = dailyReplenishLimit; + _state.LastRespectReset = DateTime.UtcNow; + await WriteToDatabaseAsync(ct); + } + + /// + /// Lazy daily reset: if the last reset was before today's midnight (UTC), + /// automatically replenish daily respect allowances from configuration. + /// This avoids the need for a global scheduled task to iterate all players. + /// + private async Task TryResetDailyRespectIfNeededAsync(CancellationToken ct) + { + var todayMidnight = DateTime.UtcNow.Date; + + if (_state.LastRespectReset >= todayMidnight) + return; + + var dailyRespect = _configuration.GetValue("Turbo:Respect:DailyRespectAmount", 3); + var dailyPetRespect = _configuration.GetValue("Turbo:Respect:DailyPetRespectAmount", 3); + var dailyReplenish = _configuration.GetValue("Turbo:Respect:DailyReplenishLimit", 1); + + await ResetDailyRespectAsync(dailyRespect, dailyPetRespect, dailyReplenish, ct); + + _logger.LogDebug( + "Player {PlayerId} daily respect reset. Respect: {Respect}, PetRespect: {PetRespect}, Replenishes: {Replenishes}", + _state.PlayerId, + dailyRespect, + dailyPetRespect, + dailyReplenish + ); + } } diff --git a/Turbo.Players/Grains/PlayerLiveState.cs b/Turbo.Players/Grains/PlayerLiveState.cs index e3e2a4f8..e58b9512 100644 --- a/Turbo.Players/Grains/PlayerLiveState.cs +++ b/Turbo.Players/Grains/PlayerLiveState.cs @@ -14,4 +14,9 @@ public sealed class PlayerLiveState public int AchievementScore { get; set; } = 0; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime LastUpdated { get; set; } = DateTime.UtcNow; + public int RespectTotal { get; set; } = 0; + public int RespectLeft { get; set; } = 0; + public int PetRespectLeft { get; set; } = 0; + public int RespectReplenishesLeft { get; set; } = 0; + public DateTime LastRespectReset { get; set; } = DateTime.MinValue; } diff --git a/Turbo.Primitives/Messages/Incoming/Room/Pets/RespectPetMessage.cs b/Turbo.Primitives/Messages/Incoming/Room/Pets/RespectPetMessage.cs index 551f3813..d8656bd0 100644 --- a/Turbo.Primitives/Messages/Incoming/Room/Pets/RespectPetMessage.cs +++ b/Turbo.Primitives/Messages/Incoming/Room/Pets/RespectPetMessage.cs @@ -2,4 +2,7 @@ namespace Turbo.Primitives.Messages.Incoming.Room.Pets; -public record RespectPetMessage : IMessageEvent { } +public record RespectPetMessage : IMessageEvent +{ + public required int PetId { get; init; } +} diff --git a/Turbo.Primitives/Messages/Incoming/Users/ReplenishRespectMessage.cs b/Turbo.Primitives/Messages/Incoming/Users/ReplenishRespectMessage.cs new file mode 100644 index 00000000..23d08138 --- /dev/null +++ b/Turbo.Primitives/Messages/Incoming/Users/ReplenishRespectMessage.cs @@ -0,0 +1,5 @@ +using Turbo.Primitives.Networking; + +namespace Turbo.Primitives.Messages.Incoming.Users; + +public record ReplenishRespectMessage : IMessageEvent { } diff --git a/Turbo.Primitives/Messages/Incoming/Users/RespectUserMessage.cs b/Turbo.Primitives/Messages/Incoming/Users/RespectUserMessage.cs new file mode 100644 index 00000000..4651afcc --- /dev/null +++ b/Turbo.Primitives/Messages/Incoming/Users/RespectUserMessage.cs @@ -0,0 +1,8 @@ +using Turbo.Primitives.Networking; + +namespace Turbo.Primitives.Messages.Incoming.Users; + +public record RespectUserMessage : IMessageEvent +{ + public required int UserId { get; init; } +} diff --git a/Turbo.Primitives/Messages/Outgoing/Handshake/UserObjectMessage.cs b/Turbo.Primitives/Messages/Outgoing/Handshake/UserObjectMessage.cs index fb392586..f71f934c 100644 --- a/Turbo.Primitives/Messages/Outgoing/Handshake/UserObjectMessage.cs +++ b/Turbo.Primitives/Messages/Outgoing/Handshake/UserObjectMessage.cs @@ -6,4 +6,5 @@ namespace Turbo.Primitives.Messages.Outgoing.Handshake; public sealed record UserObjectMessage : IComposer { public required PlayerSummarySnapshot Player { get; init; } + public required int MaxRespectPerDay { get; init; } } diff --git a/Turbo.Primitives/Messages/Outgoing/Room/Pets/PetRespectFailedMessageComposer.cs b/Turbo.Primitives/Messages/Outgoing/Room/Pets/PetRespectFailedMessageComposer.cs index 25059982..bf1f179c 100644 --- a/Turbo.Primitives/Messages/Outgoing/Room/Pets/PetRespectFailedMessageComposer.cs +++ b/Turbo.Primitives/Messages/Outgoing/Room/Pets/PetRespectFailedMessageComposer.cs @@ -6,5 +6,9 @@ namespace Turbo.Primitives.Messages.Outgoing.Room.Pets; [GenerateSerializer, Immutable] public sealed record PetRespectFailedMessageComposer : IComposer { - // TODO: add properties if/when identified + [Id(0)] + public required int RequiredDays { get; init; } + + [Id(1)] + public required int AvatarAgeInDays { get; init; } } diff --git a/Turbo.Primitives/Messages/Outgoing/Users/RespectNotificationMessageComposer.cs b/Turbo.Primitives/Messages/Outgoing/Users/RespectNotificationMessageComposer.cs index ec6cf61d..6af20fff 100644 --- a/Turbo.Primitives/Messages/Outgoing/Users/RespectNotificationMessageComposer.cs +++ b/Turbo.Primitives/Messages/Outgoing/Users/RespectNotificationMessageComposer.cs @@ -6,5 +6,9 @@ namespace Turbo.Primitives.Messages.Outgoing.Users; [GenerateSerializer, Immutable] public sealed record RespectNotificationMessageComposer : IComposer { - // TODO: add properties if/when identified + [Id(0)] + public required int UserId { get; init; } + + [Id(1)] + public required int RespectTotal { get; init; } } diff --git a/Turbo.Primitives/Orleans/Snapshots/Players/PlayerExtendedProfileSnapshot.cs b/Turbo.Primitives/Orleans/Snapshots/Players/PlayerExtendedProfileSnapshot.cs index 43aada73..58146da9 100644 --- a/Turbo.Primitives/Orleans/Snapshots/Players/PlayerExtendedProfileSnapshot.cs +++ b/Turbo.Primitives/Orleans/Snapshots/Players/PlayerExtendedProfileSnapshot.cs @@ -64,4 +64,7 @@ public sealed record PlayerExtendedProfileSnapshot [Id(18)] public required bool BooleanField27 { get; init; } + + [Id(19)] + public required int RespectTotal { get; init; } } diff --git a/Turbo.Primitives/Orleans/Snapshots/Players/PlayerSummarySnapshot.cs b/Turbo.Primitives/Orleans/Snapshots/Players/PlayerSummarySnapshot.cs index 2c22ca36..e67b2568 100644 --- a/Turbo.Primitives/Orleans/Snapshots/Players/PlayerSummarySnapshot.cs +++ b/Turbo.Primitives/Orleans/Snapshots/Players/PlayerSummarySnapshot.cs @@ -28,4 +28,16 @@ public sealed record PlayerSummarySnapshot [Id(6)] public required DateTime CreatedAt { get; init; } + + [Id(7)] + public required int RespectTotal { get; init; } + + [Id(8)] + public required int RespectLeft { get; init; } + + [Id(9)] + public required int PetRespectLeft { get; init; } + + [Id(10)] + public required int RespectReplenishesLeft { get; init; } } diff --git a/Turbo.Primitives/Players/Grains/IPlayerGrain.cs b/Turbo.Primitives/Players/Grains/IPlayerGrain.cs index d3248226..10b573da 100644 --- a/Turbo.Primitives/Players/Grains/IPlayerGrain.cs +++ b/Turbo.Primitives/Players/Grains/IPlayerGrain.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Orleans; using Turbo.Primitives.Orleans.Snapshots.Players; +using Turbo.Primitives.Players; using Turbo.Primitives.Rooms.Enums; namespace Turbo.Primitives.Grains.Players; @@ -15,4 +16,33 @@ public interface IPlayerGrain : IGrainWithIntegerKey public Task GetExtendedProfileSnapshotAsync( CancellationToken ct ); + + /// + /// Gives one respect point to this player from the giver. + /// Returns the updated total respect count of this player. + /// + public Task ReceiveRespectAsync(PlayerId giverId, CancellationToken ct); + + /// + /// Consumes one "respect left" from this player (the giver). + /// Returns false if no respects remain. + /// + public Task TryConsumeRespectAsync(CancellationToken ct); + + /// + /// Replenishes respect to max per day. Consumes one replenish use. + /// Returns false if no replenishes remain. + /// + public Task TryReplenishRespectAsync(int maxRespectPerDay, CancellationToken ct); + + /// + /// Resets daily respect allowances for this player. + /// Called at midnight server time. + /// + public Task ResetDailyRespectAsync( + int dailyRespectAmount, + int dailyPetRespectAmount, + int dailyReplenishLimit, + CancellationToken ct + ); } diff --git a/Turbo.Revisions/Revision20260112/Parsers/Room/Pets/RespectPetMessageParser.cs b/Turbo.Revisions/Revision20260112/Parsers/Room/Pets/RespectPetMessageParser.cs index e3170afb..a70db9b5 100644 --- a/Turbo.Revisions/Revision20260112/Parsers/Room/Pets/RespectPetMessageParser.cs +++ b/Turbo.Revisions/Revision20260112/Parsers/Room/Pets/RespectPetMessageParser.cs @@ -6,5 +6,6 @@ namespace Turbo.Revisions.Revision20260112.Parsers.Room.Pets; internal class RespectPetMessageParser : IParser { - public IMessageEvent Parse(IClientPacket packet) => new RespectPetMessage(); + public IMessageEvent Parse(IClientPacket packet) => + new RespectPetMessage { PetId = packet.PopInt() }; } diff --git a/Turbo.Revisions/Revision20260112/Parsers/Users/ReplenishRespectMessageParser.cs b/Turbo.Revisions/Revision20260112/Parsers/Users/ReplenishRespectMessageParser.cs new file mode 100644 index 00000000..b62e3e49 --- /dev/null +++ b/Turbo.Revisions/Revision20260112/Parsers/Users/ReplenishRespectMessageParser.cs @@ -0,0 +1,10 @@ +using Turbo.Primitives.Messages.Incoming.Users; +using Turbo.Primitives.Networking; +using Turbo.Primitives.Packets; + +namespace Turbo.Revisions.Revision20260112.Parsers.Users; + +internal class ReplenishRespectMessageParser : IParser +{ + public IMessageEvent Parse(IClientPacket packet) => new ReplenishRespectMessage(); +} diff --git a/Turbo.Revisions/Revision20260112/Parsers/Users/RespectUserMessageParser.cs b/Turbo.Revisions/Revision20260112/Parsers/Users/RespectUserMessageParser.cs new file mode 100644 index 00000000..51630189 --- /dev/null +++ b/Turbo.Revisions/Revision20260112/Parsers/Users/RespectUserMessageParser.cs @@ -0,0 +1,11 @@ +using Turbo.Primitives.Messages.Incoming.Users; +using Turbo.Primitives.Networking; +using Turbo.Primitives.Packets; + +namespace Turbo.Revisions.Revision20260112.Parsers.Users; + +internal class RespectUserMessageParser : IParser +{ + public IMessageEvent Parse(IClientPacket packet) => + new RespectUserMessage { UserId = packet.PopInt() }; +} diff --git a/Turbo.Revisions/Revision20260112/Revision20260112.cs b/Turbo.Revisions/Revision20260112/Revision20260112.cs index 2df2e12e..5926346f 100644 --- a/Turbo.Revisions/Revision20260112/Revision20260112.cs +++ b/Turbo.Revisions/Revision20260112/Revision20260112.cs @@ -1379,6 +1379,8 @@ public class Revision20260112 : IRevision { MessageEvent.UnblockGroupMemberMessageEvent, new UnblockGroupMemberMessageParser() }, { MessageEvent.UnblockUserMessageEvent, new UnblockUserMessageParser() }, { MessageEvent.UnignoreUserMessageEvent, new UnignoreUserMessageParser() }, + { MessageEvent.ReplenishRespectMessageEvent, new ReplenishRespectMessageParser() }, + { MessageEvent.RespectUserMessageEvent, new RespectUserMessageParser() }, { MessageEvent.UpdateGuildBadgeMessageEvent, new UpdateGuildBadgeMessageParser() }, { MessageEvent.UpdateGuildColorsMessageEvent, new UpdateGuildColorsMessageParser() }, { @@ -3215,6 +3217,12 @@ public class Revision20260112 : IRevision MessageComposer.RelationshipStatusInfoComposer ) }, + { + typeof(RespectNotificationMessageComposer), + new RespectNotificationMessageComposerSerializer( + MessageComposer.RespectNotificationMessageComposer + ) + }, { typeof(ScrSendUserInfoMessageComposer), new ScrSendUserInfoMessageSerializer(MessageComposer.ScrSendUserInfoComposer) diff --git a/Turbo.Revisions/Revision20260112/Serializers/Handshake/UserObjectMessageSerializer.cs b/Turbo.Revisions/Revision20260112/Serializers/Handshake/UserObjectMessageSerializer.cs index e6f31668..bfd6dcda 100644 --- a/Turbo.Revisions/Revision20260112/Serializers/Handshake/UserObjectMessageSerializer.cs +++ b/Turbo.Revisions/Revision20260112/Serializers/Handshake/UserObjectMessageSerializer.cs @@ -16,12 +16,16 @@ protected override void Serialize(IServerPacket packet, UserObjectMessage messag packet.WriteString(message.Player.Motto); packet.WriteString(message.Player.Name); // real name packet.WriteBoolean(false); // direct mail - packet.WriteInteger(0); // respect total - packet.WriteInteger(0); // respect left - packet.WriteInteger(0); // pet respect left + packet.WriteInteger(message.Player.RespectTotal); + packet.WriteInteger(message.Player.RespectLeft); + packet.WriteInteger(message.Player.PetRespectLeft); packet.WriteBoolean(false); // stream publishing enabled packet.WriteString(message.Player.CreatedAt.ToString()); // last online packet.WriteBoolean(false); // can name change packet.WriteBoolean(false); // account safety locked + packet.WriteBoolean(false); // account trade locked + packet.WriteString(string.Empty); // name color + packet.WriteInteger(message.Player.RespectReplenishesLeft); + packet.WriteInteger(message.MaxRespectPerDay); } } diff --git a/Turbo.Revisions/Revision20260112/Serializers/Room/Pets/PetRespectFailedMessageComposerSerializer.cs b/Turbo.Revisions/Revision20260112/Serializers/Room/Pets/PetRespectFailedMessageComposerSerializer.cs index 7f2cd526..39740fb7 100644 --- a/Turbo.Revisions/Revision20260112/Serializers/Room/Pets/PetRespectFailedMessageComposerSerializer.cs +++ b/Turbo.Revisions/Revision20260112/Serializers/Room/Pets/PetRespectFailedMessageComposerSerializer.cs @@ -8,6 +8,7 @@ internal class PetRespectFailedMessageComposerSerializer(int header) { protected override void Serialize(IServerPacket packet, PetRespectFailedMessageComposer message) { - // + packet.WriteInteger(message.RequiredDays); + packet.WriteInteger(message.AvatarAgeInDays); } } diff --git a/Turbo.Revisions/Revision20260112/Serializers/Users/RespectNotificationMessageComposerSerializer.cs b/Turbo.Revisions/Revision20260112/Serializers/Users/RespectNotificationMessageComposerSerializer.cs index baf90bf1..9f4deef6 100644 --- a/Turbo.Revisions/Revision20260112/Serializers/Users/RespectNotificationMessageComposerSerializer.cs +++ b/Turbo.Revisions/Revision20260112/Serializers/Users/RespectNotificationMessageComposerSerializer.cs @@ -11,6 +11,7 @@ protected override void Serialize( RespectNotificationMessageComposer message ) { - // + packet.WriteInteger(message.UserId); + packet.WriteInteger(message.RespectTotal); } } diff --git a/appsettings.json b/appsettings.json index 79315f84..b7388840 100644 --- a/appsettings.json +++ b/appsettings.json @@ -104,6 +104,12 @@ "NormalFriendLimit": 100, "ExtendedFriendLimit": 100 }, + "Respect": { + "DailyRespectAmount": 3, + "DailyPetRespectAmount": 3, + "DailyReplenishLimit": 1, + "ReplenishCostDuckets": 20 + }, "Crypto": { "KeySize": "3", "PublicKey": "86851DD364D5C5CECE3C883171CC6DDC5760779B992482BD1E20DD296888DF91B33B936A7B93F06D29E8870F703A216257DEC7C81DE0058FEA4CC5116F75E6EFC4E9113513E45357DC3FD43D4EFAB5963EF178B78BD61E81A14C603B24C8BCCE0A12230B320045498EDC29282FF0603BC7B7DAE8FC1B05B52B2F301A9DC783B7", From a162a2b698f042ddeb94273b0527c90d0eb43462 Mon Sep 17 00:00:00 2001 From: Dippys Date: Tue, 10 Feb 2026 22:20:02 +0400 Subject: [PATCH 2/4] remove logs --- Turbo.Players/Grains/PlayerGrain.cs | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/Turbo.Players/Grains/PlayerGrain.cs b/Turbo.Players/Grains/PlayerGrain.cs index d3d32644..98cfde77 100644 --- a/Turbo.Players/Grains/PlayerGrain.cs +++ b/Turbo.Players/Grains/PlayerGrain.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; using Orleans; using Turbo.Database.Context; using Turbo.Logging; @@ -21,7 +20,6 @@ internal sealed class PlayerGrain : Grain, IPlayerGrain { private readonly IDbContextFactory _dbCtxFactory; private readonly IGrainFactory _grainFactory; - private readonly ILogger _logger; private readonly IConfiguration _configuration; private readonly PlayerLiveState _state; @@ -29,13 +27,11 @@ internal sealed class PlayerGrain : Grain, IPlayerGrain public PlayerGrain( IDbContextFactory dbCtxFactory, IGrainFactory grainFactory, - ILogger logger, IConfiguration configuration ) { _dbCtxFactory = dbCtxFactory; _grainFactory = grainFactory; - _logger = logger; _configuration = configuration; _state = new() { PlayerId = PlayerId.Parse((int)this.GetPrimaryKeyLong()) }; @@ -189,13 +185,6 @@ public async Task ReceiveRespectAsync(PlayerId giverId, CancellationToken c _state.RespectTotal++; await WriteToDatabaseAsync(ct); - _logger.LogDebug( - "Player {PlayerId} received respect from {GiverId}. Total: {Total}", - _state.PlayerId, - giverId, - _state.RespectTotal - ); - return _state.RespectTotal; } @@ -222,13 +211,6 @@ public async Task TryReplenishRespectAsync(int maxRespectPerDay, Cancellat _state.RespectLeft = maxRespectPerDay; await WriteToDatabaseAsync(ct); - _logger.LogDebug( - "Player {PlayerId} replenished respect. RespectLeft: {Left}, ReplenishesLeft: {Replenishes}", - _state.PlayerId, - _state.RespectLeft, - _state.RespectReplenishesLeft - ); - return true; } @@ -263,13 +245,5 @@ private async Task TryResetDailyRespectIfNeededAsync(CancellationToken ct) var dailyReplenish = _configuration.GetValue("Turbo:Respect:DailyReplenishLimit", 1); await ResetDailyRespectAsync(dailyRespect, dailyPetRespect, dailyReplenish, ct); - - _logger.LogDebug( - "Player {PlayerId} daily respect reset. Respect: {Respect}, PetRespect: {PetRespect}, Replenishes: {Replenishes}", - _state.PlayerId, - dailyRespect, - dailyPetRespect, - dailyReplenish - ); } } From 04c8fae7750f83e40c36d4969ef9e88d3d78e48d Mon Sep 17 00:00:00 2001 From: Dippys Date: Tue, 10 Feb 2026 22:29:24 +0400 Subject: [PATCH 3/4] compliance --- .../Users/RespectUserMessageHandler.cs | 18 ++---------------- Turbo.Players/Grains/PlayerGrain.cs | 10 ++++++++++ .../Grains/PlayerPresenceGrain.Room.cs | 9 +++++++++ .../Grains/IPlayerPresenceGrain.Room.cs | 2 ++ 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Turbo.PacketHandlers/Users/RespectUserMessageHandler.cs b/Turbo.PacketHandlers/Users/RespectUserMessageHandler.cs index 4335e4a7..1f83a3b2 100644 --- a/Turbo.PacketHandlers/Users/RespectUserMessageHandler.cs +++ b/Turbo.PacketHandlers/Users/RespectUserMessageHandler.cs @@ -3,7 +3,6 @@ using Orleans; using Turbo.Messages.Registry; using Turbo.Primitives.Messages.Incoming.Users; -using Turbo.Primitives.Messages.Outgoing.Users; using Turbo.Primitives.Orleans; using Turbo.Primitives.Rooms.Enums; @@ -34,27 +33,14 @@ CancellationToken ct if (!consumed) return; - // Give respect to the target player + // Give respect to the target player (grain sends room notification) var targetGrain = _grainFactory.GetPlayerGrain(message.UserId); - var newTotal = await targetGrain - .ReceiveRespectAsync(ctx.PlayerId, ct) - .ConfigureAwait(false); + await targetGrain.ReceiveRespectAsync(ctx.PlayerId, ct).ConfigureAwait(false); // Play the thumbs-up expression on the giver var roomGrain = _grainFactory.GetRoomGrain(ctx.RoomId); await roomGrain .SetAvatarExpressionAsync(ctx.AsActionContext(), AvatarExpressionType.Respect, ct) .ConfigureAwait(false); - - // Broadcast the respect notification to the entire room - await roomGrain - .SendComposerToRoomAsync( - new RespectNotificationMessageComposer - { - UserId = message.UserId, - RespectTotal = newTotal, - } - ) - .ConfigureAwait(false); } } diff --git a/Turbo.Players/Grains/PlayerGrain.cs b/Turbo.Players/Grains/PlayerGrain.cs index 98cfde77..74200711 100644 --- a/Turbo.Players/Grains/PlayerGrain.cs +++ b/Turbo.Players/Grains/PlayerGrain.cs @@ -9,6 +9,7 @@ using Turbo.Logging; using Turbo.Primitives; using Turbo.Primitives.Grains.Players; +using Turbo.Primitives.Messages.Outgoing.Users; using Turbo.Primitives.Orleans; using Turbo.Primitives.Orleans.Snapshots.Players; using Turbo.Primitives.Players; @@ -185,6 +186,15 @@ public async Task ReceiveRespectAsync(PlayerId giverId, CancellationToken c _state.RespectTotal++; await WriteToDatabaseAsync(ct); + var presence = _grainFactory.GetPlayerPresenceGrain((int)this.GetPrimaryKeyLong()); + await presence.SendComposerToActiveRoomAsync( + new RespectNotificationMessageComposer + { + UserId = (int)_state.PlayerId, + RespectTotal = _state.RespectTotal, + } + ); + return _state.RespectTotal; } diff --git a/Turbo.Players/Grains/PlayerPresenceGrain.Room.cs b/Turbo.Players/Grains/PlayerPresenceGrain.Room.cs index 5b19b5ca..e5d4a9c1 100644 --- a/Turbo.Players/Grains/PlayerPresenceGrain.Room.cs +++ b/Turbo.Players/Grains/PlayerPresenceGrain.Room.cs @@ -115,4 +115,13 @@ public Task SetPendingRoomAsync(RoomId roomId, bool approved) return Task.CompletedTask; } + + public async Task SendComposerToActiveRoomAsync(IComposer composer) + { + if (_state.ActiveRoomId <= 0) + return; + + var roomGrain = _grainFactory.GetRoomGrain(_state.ActiveRoomId); + await roomGrain.SendComposerToRoomAsync(composer); + } } diff --git a/Turbo.Primitives/Players/Grains/IPlayerPresenceGrain.Room.cs b/Turbo.Primitives/Players/Grains/IPlayerPresenceGrain.Room.cs index c8573694..08590884 100644 --- a/Turbo.Primitives/Players/Grains/IPlayerPresenceGrain.Room.cs +++ b/Turbo.Primitives/Players/Grains/IPlayerPresenceGrain.Room.cs @@ -1,5 +1,6 @@ using System.Threading; using System.Threading.Tasks; +using Turbo.Primitives.Networking; using Turbo.Primitives.Orleans.Snapshots.Room; using Turbo.Primitives.Rooms; @@ -12,4 +13,5 @@ public partial interface IPlayerPresenceGrain public Task SetActiveRoomAsync(RoomId roomId, CancellationToken ct); public Task ClearActiveRoomAsync(CancellationToken ct); public Task SetPendingRoomAsync(RoomId roomId, bool approved); + public Task SendComposerToActiveRoomAsync(IComposer composer); } From 25cc00775496653c69b640014c4f91f00d53f701 Mon Sep 17 00:00:00 2001 From: Dippys Date: Tue, 10 Feb 2026 23:06:50 +0400 Subject: [PATCH 4/4] move respects to its own table. --- Turbo.Database/Context/ITurboDbContext.cs | 2 + Turbo.Database/Context/TurboDbContext.cs | 1 + .../Entities/Players/PlayerEntity.cs | 21 +--- .../Entities/Players/PlayerRespectEntity.cs | 36 ++++++ .../Migrations/20260210173232_respects.cs | 68 ----------- ...cs => 20260210190340_respects.Designer.cs} | 113 +++++++++++++----- .../Migrations/20260210190340_respects.cs | 98 +++++++++++++++ .../Migrations/TurboDbContextModelSnapshot.cs | 111 ++++++++++++----- Turbo.Players/Grains/PlayerGrain.cs | 84 ++++++++++--- 9 files changed, 374 insertions(+), 160 deletions(-) create mode 100644 Turbo.Database/Entities/Players/PlayerRespectEntity.cs delete mode 100644 Turbo.Database/Migrations/20260210173232_respects.cs rename Turbo.Database/Migrations/{20260210173232_respects.Designer.cs => 20260210190340_respects.Designer.cs} (97%) create mode 100644 Turbo.Database/Migrations/20260210190340_respects.cs diff --git a/Turbo.Database/Context/ITurboDbContext.cs b/Turbo.Database/Context/ITurboDbContext.cs index ad82502a..c7c6481d 100644 --- a/Turbo.Database/Context/ITurboDbContext.cs +++ b/Turbo.Database/Context/ITurboDbContext.cs @@ -32,6 +32,8 @@ public interface ITurboDbContext : IDisposable public DbSet? Players { get; set; } + public DbSet? PlayerRespects { get; set; } + public DbSet? RoomBans { get; set; } public DbSet? Chatlogs { get; set; } diff --git a/Turbo.Database/Context/TurboDbContext.cs b/Turbo.Database/Context/TurboDbContext.cs index 51c96d5e..392a7b07 100644 --- a/Turbo.Database/Context/TurboDbContext.cs +++ b/Turbo.Database/Context/TurboDbContext.cs @@ -30,6 +30,7 @@ public class TurboDbContext(DbContextOptions options) public DbSet PlayerCurrencies { get; init; } public DbSet Players { get; init; } + public DbSet PlayerRespects { get; init; } public DbSet RoomBans { get; init; } diff --git a/Turbo.Database/Entities/Players/PlayerEntity.cs b/Turbo.Database/Entities/Players/PlayerEntity.cs index bb07ca9a..a0e6ab36 100644 --- a/Turbo.Database/Entities/Players/PlayerEntity.cs +++ b/Turbo.Database/Entities/Players/PlayerEntity.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; @@ -46,24 +45,8 @@ public class PlayerEntity : TurboEntity [Column("room_chat_style_id")] public int? RoomChatStyleId { get; set; } - [Column("respect_total")] - [DefaultValue(0)] - public int RespectTotal { get; set; } - - [Column("respect_left")] - [DefaultValue(0)] - public int RespectLeft { get; set; } - - [Column("pet_respect_left")] - [DefaultValue(0)] - public int PetRespectLeft { get; set; } - - [Column("respect_replenishes_left")] - [DefaultValue(1)] - public int RespectReplenishesLeft { get; set; } - - [Column("last_respect_reset")] - public DateTime? LastRespectReset { get; set; } + [InverseProperty("PlayerEntity")] + public PlayerRespectEntity? PlayerRespect { get; set; } [InverseProperty("PlayerEntity")] public List? PlayerBadges { get; set; } diff --git a/Turbo.Database/Entities/Players/PlayerRespectEntity.cs b/Turbo.Database/Entities/Players/PlayerRespectEntity.cs new file mode 100644 index 00000000..d8fadfc2 --- /dev/null +++ b/Turbo.Database/Entities/Players/PlayerRespectEntity.cs @@ -0,0 +1,36 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Turbo.Database.Entities.Players; + +[Table("player_respects")] +[Index(nameof(PlayerEntityId), IsUnique = true)] +public class PlayerRespectEntity : TurboEntity +{ + [Column("player_id")] + public required int PlayerEntityId { get; set; } + + [Column("respect_total")] + [DefaultValue(0)] + public int RespectTotal { get; set; } + + [Column("respect_left")] + [DefaultValue(0)] + public int RespectLeft { get; set; } + + [Column("pet_respect_left")] + [DefaultValue(0)] + public int PetRespectLeft { get; set; } + + [Column("respect_replenishes_left")] + [DefaultValue(1)] + public int RespectReplenishesLeft { get; set; } + + [Column("last_respect_reset")] + public DateTime? LastRespectReset { get; set; } + + [ForeignKey(nameof(PlayerEntityId))] + public PlayerEntity? PlayerEntity { get; set; } +} diff --git a/Turbo.Database/Migrations/20260210173232_respects.cs b/Turbo.Database/Migrations/20260210173232_respects.cs deleted file mode 100644 index e1bf56c2..00000000 --- a/Turbo.Database/Migrations/20260210173232_respects.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Turbo.Database.Migrations -{ - /// - public partial class respects : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "last_respect_reset", - table: "players", - type: "datetime(6)", - nullable: true - ); - - migrationBuilder.AddColumn( - name: "pet_respect_left", - table: "players", - type: "int", - nullable: false, - defaultValue: 0 - ); - - migrationBuilder.AddColumn( - name: "respect_left", - table: "players", - type: "int", - nullable: false, - defaultValue: 0 - ); - - migrationBuilder.AddColumn( - name: "respect_replenishes_left", - table: "players", - type: "int", - nullable: false, - defaultValue: 1 - ); - - migrationBuilder.AddColumn( - name: "respect_total", - table: "players", - type: "int", - nullable: false, - defaultValue: 0 - ); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn(name: "last_respect_reset", table: "players"); - - migrationBuilder.DropColumn(name: "pet_respect_left", table: "players"); - - migrationBuilder.DropColumn(name: "respect_left", table: "players"); - - migrationBuilder.DropColumn(name: "respect_replenishes_left", table: "players"); - - migrationBuilder.DropColumn(name: "respect_total", table: "players"); - } - } -} diff --git a/Turbo.Database/Migrations/20260210173232_respects.Designer.cs b/Turbo.Database/Migrations/20260210190340_respects.Designer.cs similarity index 97% rename from Turbo.Database/Migrations/20260210173232_respects.Designer.cs rename to Turbo.Database/Migrations/20260210190340_respects.Designer.cs index cd61eccc..2ff35554 100644 --- a/Turbo.Database/Migrations/20260210173232_respects.Designer.cs +++ b/Turbo.Database/Migrations/20260210190340_respects.Designer.cs @@ -12,7 +12,7 @@ namespace Turbo.Database.Migrations { [DbContext(typeof(TurboDbContext))] - [Migration("20260210173232_respects")] + [Migration("20260210190340_respects")] partial class respects { /// @@ -1481,10 +1481,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasDefaultValue(0) .HasColumnName("gender"); - b.Property("LastRespectReset") - .HasColumnType("datetime(6)") - .HasColumnName("last_respect_reset"); - b.Property("Motto") .HasMaxLength(512) .HasColumnType("varchar(512)") @@ -1496,12 +1492,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("varchar(512)") .HasColumnName("name"); - b.Property("PetRespectLeft") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(0) - .HasColumnName("pet_respect_left"); - b.Property("PlayerPerks") .HasColumnType("int") .HasDefaultValue(0) @@ -1512,24 +1502,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasDefaultValue(0) .HasColumnName("status"); - b.Property("RespectLeft") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(0) - .HasColumnName("respect_left"); - - b.Property("RespectReplenishesLeft") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1) - .HasColumnName("respect_replenishes_left"); - - b.Property("RespectTotal") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(0) - .HasColumnName("respect_total"); - b.Property("RoomChatStyleId") .HasColumnType("int") .HasColumnName("room_chat_style_id"); @@ -1597,6 +1569,76 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("player_favorite_rooms"); }); + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerRespectEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("LastRespectReset") + .HasColumnType("datetime(6)") + .HasColumnName("last_respect_reset"); + + b.Property("PetRespectLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("pet_respect_left"); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RespectLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("respect_left"); + + b.Property("RespectReplenishesLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("respect_replenishes_left"); + + b.Property("RespectTotal") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("respect_total"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId") + .IsUnique(); + + b.ToTable("player_respects"); + }); + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomBanEntity", b => { b.Property("Id") @@ -2559,6 +2601,17 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("RoomEntity"); }); + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerRespectEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithOne("PlayerRespect") + .HasForeignKey("Turbo.Database.Entities.Players.PlayerRespectEntity", "PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + }); + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomBanEntity", b => { b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") @@ -2751,6 +2804,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("PlayerOwnedChatStyles"); + b.Navigation("PlayerRespect"); + b.Navigation("RoomBans"); b.Navigation("RoomChatlogs"); diff --git a/Turbo.Database/Migrations/20260210190340_respects.cs b/Turbo.Database/Migrations/20260210190340_respects.cs new file mode 100644 index 00000000..fafbe3ea --- /dev/null +++ b/Turbo.Database/Migrations/20260210190340_respects.cs @@ -0,0 +1,98 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Turbo.Database.Migrations +{ + /// + public partial class respects : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder + .CreateTable( + name: "player_respects", + columns: table => new + { + id = table + .Column(type: "int", nullable: false) + .Annotation( + "MySql:ValueGenerationStrategy", + MySqlValueGenerationStrategy.IdentityColumn + ), + player_id = table.Column(type: "int", nullable: false), + respect_total = table.Column( + type: "int", + nullable: false, + defaultValue: 0 + ), + respect_left = table.Column( + type: "int", + nullable: false, + defaultValue: 0 + ), + pet_respect_left = table.Column( + type: "int", + nullable: false, + defaultValue: 0 + ), + respect_replenishes_left = table.Column( + type: "int", + nullable: false, + defaultValue: 1 + ), + last_respect_reset = table.Column( + type: "datetime(6)", + nullable: true + ), + created_at = table + .Column(type: "datetime(6)", nullable: false) + .Annotation( + "MySql:ValueGenerationStrategy", + MySqlValueGenerationStrategy.IdentityColumn + ), + updated_at = table + .Column(type: "datetime(6)", nullable: false) + .Annotation( + "MySql:ValueGenerationStrategy", + MySqlValueGenerationStrategy.ComputedColumn + ), + deleted_at = table + .Column(type: "datetime(6)", nullable: true) + .Annotation( + "MySql:ValueGenerationStrategy", + MySqlValueGenerationStrategy.ComputedColumn + ), + }, + constraints: table => + { + table.PrimaryKey("PK_player_respects", x => x.id); + table.ForeignKey( + name: "FK_player_respects_players_player_id", + column: x => x.player_id, + principalTable: "players", + principalColumn: "id", + onDelete: ReferentialAction.Cascade + ); + } + ) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_player_respects_player_id", + table: "player_respects", + column: "player_id", + unique: true + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable(name: "player_respects"); + } + } +} diff --git a/Turbo.Database/Migrations/TurboDbContextModelSnapshot.cs b/Turbo.Database/Migrations/TurboDbContextModelSnapshot.cs index 0e83906a..9a3769b4 100644 --- a/Turbo.Database/Migrations/TurboDbContextModelSnapshot.cs +++ b/Turbo.Database/Migrations/TurboDbContextModelSnapshot.cs @@ -1478,10 +1478,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasDefaultValue(0) .HasColumnName("gender"); - b.Property("LastRespectReset") - .HasColumnType("datetime(6)") - .HasColumnName("last_respect_reset"); - b.Property("Motto") .HasMaxLength(512) .HasColumnType("varchar(512)") @@ -1493,12 +1489,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("varchar(512)") .HasColumnName("name"); - b.Property("PetRespectLeft") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(0) - .HasColumnName("pet_respect_left"); - b.Property("PlayerPerks") .HasColumnType("int") .HasDefaultValue(0) @@ -1509,24 +1499,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasDefaultValue(0) .HasColumnName("status"); - b.Property("RespectLeft") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(0) - .HasColumnName("respect_left"); - - b.Property("RespectReplenishesLeft") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1) - .HasColumnName("respect_replenishes_left"); - - b.Property("RespectTotal") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(0) - .HasColumnName("respect_total"); - b.Property("RoomChatStyleId") .HasColumnType("int") .HasColumnName("room_chat_style_id"); @@ -1594,6 +1566,76 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("player_favorite_rooms"); }); + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerRespectEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("DeletedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("deleted_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("DeletedAt")); + + b.Property("LastRespectReset") + .HasColumnType("datetime(6)") + .HasColumnName("last_respect_reset"); + + b.Property("PetRespectLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("pet_respect_left"); + + b.Property("PlayerEntityId") + .HasColumnType("int") + .HasColumnName("player_id"); + + b.Property("RespectLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("respect_left"); + + b.Property("RespectReplenishesLeft") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1) + .HasColumnName("respect_replenishes_left"); + + b.Property("RespectTotal") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0) + .HasColumnName("respect_total"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("PlayerEntityId") + .IsUnique(); + + b.ToTable("player_respects"); + }); + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomBanEntity", b => { b.Property("Id") @@ -2556,6 +2598,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("RoomEntity"); }); + modelBuilder.Entity("Turbo.Database.Entities.Players.PlayerRespectEntity", b => + { + b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") + .WithOne("PlayerRespect") + .HasForeignKey("Turbo.Database.Entities.Players.PlayerRespectEntity", "PlayerEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerEntity"); + }); + modelBuilder.Entity("Turbo.Database.Entities.Room.RoomBanEntity", b => { b.HasOne("Turbo.Database.Entities.Players.PlayerEntity", "PlayerEntity") @@ -2748,6 +2801,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("PlayerOwnedChatStyles"); + b.Navigation("PlayerRespect"); + b.Navigation("RoomBans"); b.Navigation("RoomChatlogs"); diff --git a/Turbo.Players/Grains/PlayerGrain.cs b/Turbo.Players/Grains/PlayerGrain.cs index 74200711..bd15dc51 100644 --- a/Turbo.Players/Grains/PlayerGrain.cs +++ b/Turbo.Players/Grains/PlayerGrain.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration; using Orleans; using Turbo.Database.Context; +using Turbo.Database.Entities.Players; using Turbo.Logging; using Turbo.Primitives; using Turbo.Primitives.Grains.Players; @@ -93,11 +94,19 @@ await dbCtx _state.AchievementScore = 0; _state.CreatedAt = entity.CreatedAt; _state.LastUpdated = entity.UpdatedAt; - _state.RespectTotal = entity.RespectTotal; - _state.RespectLeft = entity.RespectLeft; - _state.PetRespectLeft = entity.PetRespectLeft; - _state.RespectReplenishesLeft = entity.RespectReplenishesLeft; - _state.LastRespectReset = entity.LastRespectReset ?? DateTime.MinValue; + + var respectEntity = await dbCtx + .PlayerRespects.AsNoTracking() + .SingleOrDefaultAsync(x => x.PlayerEntityId == (int)_state.PlayerId, ct); + + if (respectEntity is not null) + { + _state.RespectTotal = respectEntity.RespectTotal; + _state.RespectLeft = respectEntity.RespectLeft; + _state.PetRespectLeft = respectEntity.PetRespectLeft; + _state.RespectReplenishesLeft = respectEntity.RespectReplenishesLeft; + _state.LastRespectReset = respectEntity.LastRespectReset ?? DateTime.MinValue; + } await _grainFactory .GetPlayerDirectoryGrain() @@ -117,20 +126,12 @@ await dbCtx up.SetProperty(p => p.Name, snapshot.Name) .SetProperty(p => p.Motto, snapshot.Motto) .SetProperty(p => p.Figure, snapshot.Figure) - .SetProperty(p => p.Gender, snapshot.Gender) - .SetProperty(p => p.RespectTotal, _state.RespectTotal) - .SetProperty(p => p.RespectLeft, _state.RespectLeft) - .SetProperty(p => p.PetRespectLeft, _state.PetRespectLeft) - .SetProperty(p => p.RespectReplenishesLeft, _state.RespectReplenishesLeft) - .SetProperty( - p => p.LastRespectReset, - _state.LastRespectReset == DateTime.MinValue - ? null - : (DateTime?)_state.LastRespectReset - ), + .SetProperty(p => p.Gender, snapshot.Gender), ct ); + await WriteRespectToDatabaseAsync(dbCtx, ct); + _state.LastUpdated = DateTime.Now; } @@ -256,4 +257,55 @@ private async Task TryResetDailyRespectIfNeededAsync(CancellationToken ct) await ResetDailyRespectAsync(dailyRespect, dailyPetRespect, dailyReplenish, ct); } + + private async Task WriteRespectToDatabaseAsync(TurboDbContext dbCtx, CancellationToken ct) + { + var playerId = (int)_state.PlayerId; + + var existing = await dbCtx + .PlayerRespects.Where(x => x.PlayerEntityId == playerId) + .CountAsync(ct); + + if (existing > 0) + { + await dbCtx + .PlayerRespects.Where(x => x.PlayerEntityId == playerId) + .ExecuteUpdateAsync( + up => + up.SetProperty(r => r.RespectTotal, _state.RespectTotal) + .SetProperty(r => r.RespectLeft, _state.RespectLeft) + .SetProperty(r => r.PetRespectLeft, _state.PetRespectLeft) + .SetProperty( + r => r.RespectReplenishesLeft, + _state.RespectReplenishesLeft + ) + .SetProperty( + r => r.LastRespectReset, + _state.LastRespectReset == DateTime.MinValue + ? null + : (DateTime?)_state.LastRespectReset + ), + ct + ); + } + else + { + dbCtx.PlayerRespects.Add( + new PlayerRespectEntity + { + PlayerEntityId = playerId, + RespectTotal = _state.RespectTotal, + RespectLeft = _state.RespectLeft, + PetRespectLeft = _state.PetRespectLeft, + RespectReplenishesLeft = _state.RespectReplenishesLeft, + LastRespectReset = + _state.LastRespectReset == DateTime.MinValue + ? null + : _state.LastRespectReset, + } + ); + + await dbCtx.SaveChangesAsync(ct); + } + } }