From 78fe2d59131c9f4eeb11b30dfb41d9998b9b4573 Mon Sep 17 00:00:00 2001 From: Eveldee Date: Mon, 27 Mar 2023 00:22:43 +0200 Subject: [PATCH] [Activity] Add organized activities --- ...6160040_AddOrganizedActivities.Designer.cs | 686 ++++++++++++++++++ .../20230326160040_AddOrganizedActivities.cs | 29 + .../CocotteDbContextModelSnapshot.cs | 10 + .../Modules/Activities/ActivityFormatter.cs | 65 +- Cocotte/Modules/Activities/ActivityHelper.cs | 17 +- Cocotte/Modules/Activities/ActivityModule.cs | 165 ++++- .../Modules/Activities/ActivityModuleDebug.cs | 4 +- Cocotte/Modules/Activities/Models/Activity.cs | 6 + .../Activities/Models/ActivityPlayer.cs | 1 + .../Activities/Models/OrganizedActivity.cs | 8 + Cocotte/Modules/Activities/PlayerRoles.cs | 11 +- Cocotte/Options/ActivityOptions.cs | 3 + Cocotte/Program.cs | 2 +- Cocotte/Services/CocotteDbContext.cs | 1 + Cocotte/Utils/CocotteDayOfWeek.cs | 21 + Cocotte/Utils/DateTimeUtils.cs | 9 + 16 files changed, 981 insertions(+), 57 deletions(-) create mode 100644 Cocotte/Migrations/20230326160040_AddOrganizedActivities.Designer.cs create mode 100644 Cocotte/Migrations/20230326160040_AddOrganizedActivities.cs create mode 100644 Cocotte/Modules/Activities/Models/OrganizedActivity.cs create mode 100644 Cocotte/Utils/CocotteDayOfWeek.cs diff --git a/Cocotte/Migrations/20230326160040_AddOrganizedActivities.Designer.cs b/Cocotte/Migrations/20230326160040_AddOrganizedActivities.Designer.cs new file mode 100644 index 0000000..8c9563f --- /dev/null +++ b/Cocotte/Migrations/20230326160040_AddOrganizedActivities.Designer.cs @@ -0,0 +1,686 @@ +// +using System; +using Cocotte.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Cocotte.Migrations +{ + [DbContext(typeof(CocotteDbContext))] + [Migration("20230326160040_AddOrganizedActivities")] + partial class AddOrganizedActivities + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.4"); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("TRIGGER_NAME"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("TRIGGER_GROUP"); + + b.Property("BlobData") + .HasColumnType("bytea") + .HasColumnName("BLOB_DATA"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("QRTZ_BLOB_TRIGGERS", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCalendar", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("CALENDAR_NAME"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("CALENDAR"); + + b.HasKey("SchedulerName", "CalendarName"); + + b.ToTable("QRTZ_CALENDARS", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("TRIGGER_NAME"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("TRIGGER_GROUP"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("text") + .HasColumnName("CRON_EXPRESSION"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("TIME_ZONE_ID"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("QRTZ_CRON_TRIGGERS", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzFiredTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("EntryId") + .HasColumnType("text") + .HasColumnName("ENTRY_ID"); + + b.Property("FiredTime") + .HasColumnType("bigint") + .HasColumnName("FIRED_TIME"); + + b.Property("InstanceName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("INSTANCE_NAME"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("IS_NONCONCURRENT"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("JOB_GROUP"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("JOB_NAME"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("PRIORITY"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("REQUESTS_RECOVERY"); + + b.Property("ScheduledTime") + .HasColumnType("bigint") + .HasColumnName("SCHED_TIME"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("STATE"); + + b.Property("TriggerGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("TRIGGER_GROUP"); + + b.Property("TriggerName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("TRIGGER_NAME"); + + b.HasKey("SchedulerName", "EntryId"); + + b.HasIndex("InstanceName") + .HasDatabaseName("IDX_QRTZ_FT_TRIG_INST_NAME"); + + b.HasIndex("JobGroup") + .HasDatabaseName("IDX_QRTZ_FT_JOB_GROUP"); + + b.HasIndex("JobName") + .HasDatabaseName("IDX_QRTZ_FT_JOB_NAME"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("IDX_QRTZ_FT_JOB_REQ_RECOVERY"); + + b.HasIndex("TriggerGroup") + .HasDatabaseName("IDX_QRTZ_FT_TRIG_GROUP"); + + b.HasIndex("TriggerName") + .HasDatabaseName("IDX_QRTZ_FT_TRIG_NAME"); + + b.HasIndex("SchedulerName", "TriggerName", "TriggerGroup") + .HasDatabaseName("IDX_QRTZ_FT_TRIG_NM_GP"); + + b.ToTable("QRTZ_FIRED_TRIGGERS", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("JOB_NAME"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("JOB_GROUP"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("DESCRIPTION"); + + b.Property("IsDurable") + .HasColumnType("bool") + .HasColumnName("IS_DURABLE"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("IS_NONCONCURRENT"); + + b.Property("IsUpdateData") + .HasColumnType("bool") + .HasColumnName("IS_UPDATE_DATA"); + + b.Property("JobClassName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("JOB_CLASS_NAME"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("JOB_DATA"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("REQUESTS_RECOVERY"); + + b.HasKey("SchedulerName", "JobName", "JobGroup"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("IDX_QRTZ_J_REQ_RECOVERY"); + + b.ToTable("QRTZ_JOB_DETAILS", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzLock", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("LockName") + .HasColumnType("text") + .HasColumnName("LOCK_NAME"); + + b.HasKey("SchedulerName", "LockName"); + + b.ToTable("QRTZ_LOCKS", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzPausedTriggerGroup", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("TRIGGER_GROUP"); + + b.HasKey("SchedulerName", "TriggerGroup"); + + b.ToTable("QRTZ_PAUSED_TRIGGER_GRPS", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSchedulerState", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("InstanceName") + .HasColumnType("text") + .HasColumnName("INSTANCE_NAME"); + + b.Property("CheckInInterval") + .HasColumnType("bigint") + .HasColumnName("CHECKIN_INTERVAL"); + + b.Property("LastCheckInTime") + .HasColumnType("bigint") + .HasColumnName("LAST_CHECKIN_TIME"); + + b.HasKey("SchedulerName", "InstanceName"); + + b.ToTable("QRTZ_SCHEDULER_STATE", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("TRIGGER_NAME"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("TRIGGER_GROUP"); + + b.Property("BooleanProperty1") + .HasColumnType("bool") + .HasColumnName("BOOL_PROP_1"); + + b.Property("BooleanProperty2") + .HasColumnType("bool") + .HasColumnName("BOOL_PROP_2"); + + b.Property("DecimalProperty1") + .HasColumnType("numeric") + .HasColumnName("DEC_PROP_1"); + + b.Property("DecimalProperty2") + .HasColumnType("numeric") + .HasColumnName("DEC_PROP_2"); + + b.Property("IntegerProperty1") + .HasColumnType("integer") + .HasColumnName("INT_PROP_1"); + + b.Property("IntegerProperty2") + .HasColumnType("integer") + .HasColumnName("INT_PROP_2"); + + b.Property("LongProperty1") + .HasColumnType("bigint") + .HasColumnName("LONG_PROP_1"); + + b.Property("LongProperty2") + .HasColumnType("bigint") + .HasColumnName("LONG_PROP_2"); + + b.Property("StringProperty1") + .HasColumnType("text") + .HasColumnName("STR_PROP_1"); + + b.Property("StringProperty2") + .HasColumnType("text") + .HasColumnName("STR_PROP_2"); + + b.Property("StringProperty3") + .HasColumnType("text") + .HasColumnName("STR_PROP_3"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("TIME_ZONE_ID"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("QRTZ_SIMPROP_TRIGGERS", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("TRIGGER_NAME"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("TRIGGER_GROUP"); + + b.Property("RepeatCount") + .HasColumnType("bigint") + .HasColumnName("REPEAT_COUNT"); + + b.Property("RepeatInterval") + .HasColumnType("bigint") + .HasColumnName("REPEAT_INTERVAL"); + + b.Property("TimesTriggered") + .HasColumnType("bigint") + .HasColumnName("TIMES_TRIGGERED"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("QRTZ_SIMPLE_TRIGGERS", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("SCHED_NAME"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("TRIGGER_NAME"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("TRIGGER_GROUP"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("CALENDAR_NAME"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("DESCRIPTION"); + + b.Property("EndTime") + .HasColumnType("bigint") + .HasColumnName("END_TIME"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("JOB_DATA"); + + b.Property("JobGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("JOB_GROUP"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("JOB_NAME"); + + b.Property("MisfireInstruction") + .HasColumnType("smallint") + .HasColumnName("MISFIRE_INSTR"); + + b.Property("NextFireTime") + .HasColumnType("bigint") + .HasColumnName("NEXT_FIRE_TIME"); + + b.Property("PreviousFireTime") + .HasColumnType("bigint") + .HasColumnName("PREV_FIRE_TIME"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("PRIORITY"); + + b.Property("StartTime") + .HasColumnType("bigint") + .HasColumnName("START_TIME"); + + b.Property("TriggerState") + .IsRequired() + .HasColumnType("text") + .HasColumnName("TRIGGER_STATE"); + + b.Property("TriggerType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("TRIGGER_TYPE"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.HasIndex("NextFireTime") + .HasDatabaseName("IDX_QRTZ_T_NEXT_FIRE_TIME"); + + b.HasIndex("TriggerState") + .HasDatabaseName("IDX_QRTZ_T_STATE"); + + b.HasIndex("NextFireTime", "TriggerState") + .HasDatabaseName("IDX_QRTZ_T_NFT_ST"); + + b.HasIndex("SchedulerName", "JobName", "JobGroup"); + + b.ToTable("QRTZ_TRIGGERS", (string)null); + }); + + modelBuilder.Entity("Cocotte.Modules.Activities.Models.Activity", b => + { + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("MessageId") + .HasColumnType("INTEGER"); + + b.Property("AreRolesEnabled") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CreatorDisplayName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatorUserId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DueDateTime") + .HasColumnType("TEXT"); + + b.Property("IsClosed") + .HasColumnType("INTEGER"); + + b.Property("MaxPlayers") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("INTEGER"); + + b.Property("ThreadId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("GuildId", "ChannelId", "MessageId"); + + b.HasIndex("ThreadId"); + + b.ToTable("Activities"); + + b.HasDiscriminator("Discriminator").HasValue("Activity"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Cocotte.Modules.Activities.Models.ActivityPlayer", b => + { + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("MessageId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsOrganizer") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("GuildId", "ChannelId", "MessageId", "UserId"); + + b.ToTable("ActivityPlayers"); + + b.HasDiscriminator("Discriminator").HasValue("ActivityPlayer"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Cocotte.Modules.Activities.Models.InterstellarActivity", b => + { + b.HasBaseType("Cocotte.Modules.Activities.Models.Activity"); + + b.Property("Color") + .HasColumnType("INTEGER"); + + b.HasDiscriminator().HasValue("InterstellarActivity"); + }); + + modelBuilder.Entity("Cocotte.Modules.Activities.Models.OrganizedActivity", b => + { + b.HasBaseType("Cocotte.Modules.Activities.Models.Activity"); + + b.HasDiscriminator().HasValue("OrganizedActivity"); + }); + + modelBuilder.Entity("Cocotte.Modules.Activities.Models.StagedActivity", b => + { + b.HasBaseType("Cocotte.Modules.Activities.Models.Activity"); + + b.Property("Stage") + .HasColumnType("INTEGER"); + + b.HasDiscriminator().HasValue("StagedActivity"); + }); + + modelBuilder.Entity("Cocotte.Modules.Activities.Models.ActivityRolePlayer", b => + { + b.HasBaseType("Cocotte.Modules.Activities.Models.ActivityPlayer"); + + b.Property("Roles") + .HasColumnType("INTEGER"); + + b.HasDiscriminator().HasValue("ActivityRolePlayer"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("BlobTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("CronTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimplePropertyTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimpleTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", "JobDetail") + .WithMany("Triggers") + .HasForeignKey("SchedulerName", "JobName", "JobGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDetail"); + }); + + modelBuilder.Entity("Cocotte.Modules.Activities.Models.ActivityPlayer", b => + { + b.HasOne("Cocotte.Modules.Activities.Models.Activity", "Activity") + .WithMany("ActivityPlayers") + .HasForeignKey("GuildId", "ChannelId", "MessageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Activity"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Navigation("Triggers"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Navigation("BlobTriggers"); + + b.Navigation("CronTriggers"); + + b.Navigation("SimplePropertyTriggers"); + + b.Navigation("SimpleTriggers"); + }); + + modelBuilder.Entity("Cocotte.Modules.Activities.Models.Activity", b => + { + b.Navigation("ActivityPlayers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Cocotte/Migrations/20230326160040_AddOrganizedActivities.cs b/Cocotte/Migrations/20230326160040_AddOrganizedActivities.cs new file mode 100644 index 0000000..f29eba2 --- /dev/null +++ b/Cocotte/Migrations/20230326160040_AddOrganizedActivities.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Cocotte.Migrations +{ + /// + public partial class AddOrganizedActivities : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsOrganizer", + table: "ActivityPlayers", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsOrganizer", + table: "ActivityPlayers"); + } + } +} diff --git a/Cocotte/Migrations/CocotteDbContextModelSnapshot.cs b/Cocotte/Migrations/CocotteDbContextModelSnapshot.cs index 76c8bb0..5c04b66 100644 --- a/Cocotte/Migrations/CocotteDbContextModelSnapshot.cs +++ b/Cocotte/Migrations/CocotteDbContextModelSnapshot.cs @@ -538,6 +538,9 @@ namespace Cocotte.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("IsOrganizer") + .HasColumnType("INTEGER"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -561,6 +564,13 @@ namespace Cocotte.Migrations b.HasDiscriminator().HasValue("InterstellarActivity"); }); + modelBuilder.Entity("Cocotte.Modules.Activities.Models.OrganizedActivity", b => + { + b.HasBaseType("Cocotte.Modules.Activities.Models.Activity"); + + b.HasDiscriminator().HasValue("OrganizedActivity"); + }); + modelBuilder.Entity("Cocotte.Modules.Activities.Models.StagedActivity", b => { b.HasBaseType("Cocotte.Modules.Activities.Models.Activity"); diff --git a/Cocotte/Modules/Activities/ActivityFormatter.cs b/Cocotte/Modules/Activities/ActivityFormatter.cs index 98de424..5f5c4d4 100644 --- a/Cocotte/Modules/Activities/ActivityFormatter.cs +++ b/Cocotte/Modules/Activities/ActivityFormatter.cs @@ -4,6 +4,7 @@ using Cocotte.Options; using Cocotte.Utils; using Discord; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; namespace Cocotte.Modules.Activities; @@ -31,7 +32,7 @@ public class ActivityFormatter ActivityName.InterstellarExploration => "Porte interstellaire", ActivityName.BreakFromDestiny => "Échapper au destin (3v3)", ActivityName.CriticalAbyss => "Abîme critique (8v8)", - ActivityName.Event => "Event", + ActivityName.Event => "Évènement", ActivityName.Fishing => "Pêche", ActivityName.MirroriaRace => "Course Mirroria", _ => throw new ArgumentOutOfRangeException(nameof(activityName), activityName, null) @@ -45,25 +46,48 @@ public class ActivityFormatter public EmbedBuilder ActivityEmbed(Activity activity, IReadOnlyCollection players) { + // Load activity players and organizers + var participants = activity.Participants.ToArray(); + var organizers = activity.Organizers.ToArray(); + // Activity full - bool activityFull = players.Count >= activity.MaxPlayers; + bool activityFull = participants.Length >= activity.MaxPlayers; + + // Players and organizers fields + var fields = new List(); + + // Add organizers if it's an organized activity + if (activity is OrganizedActivity) + { + var organizersPadding = organizers.Length > 0 ? organizers.Max(p => p.Name.Length) : 0; + + var organizersField = new EmbedFieldBuilder() + .WithName("Organisateurs") + .WithValue($"{(!organizers.Any() ? "*Aucun organisateur inscrit*" : string.Join("\n", organizers.Select(p => FormatActivityPlayer(p, organizersPadding, ActivityHelper.IsEventActivity(activity.Type)))))}"); + + fields.Add(organizersField); + } // Compute padding using player with longest name - var namePadding = players.Count > 0 ? players.Max(p => p.Name.Length) : 0; + var participantsPadding = participants.Length > 0 ? participants.Max(p => p.Name.Length) : 0; // Players field var playersField = new EmbedFieldBuilder() .WithName("Joueurs inscrits") - .WithValue($"{(!players.Any() ? "*Aucun joueur inscrit*" : string.Join("\n", players.Select(p => FormatActivityPlayer(p, namePadding))))}"); + .WithValue($"{(!participants.Any() ? "*Aucun joueur inscrit*" : string.Join("\n", participants.Select(p => FormatActivityPlayer(p, participantsPadding))))}"); + + fields.Add(playersField); var title = activity switch { StagedActivity stagedActivity => - $"{FormatActivityName(activity.Name)} ({players.Count}/{activity.MaxPlayers}) - Étage {stagedActivity.Stage}", + $"{FormatActivityName(activity.Name)} ({participants.Length}/{activity.MaxPlayers}) - Étage {stagedActivity.Stage}", InterstellarActivity interstellar => $"{FormatActivityName(activity.Name)} {_interstellarFormatter.FormatInterstellarColor(interstellar.Color)} ({players.Count}/{activity.MaxPlayers})", + OrganizedActivity => + $"{(ActivityHelper.IsEventActivity(activity.Type) ? "Organisation d'évènement" : "Proposition d'aide")} ({participants.Length}/{activity.MaxPlayers})", _ => - $"{FormatActivityName(activity.Name)} ({players.Count}/{activity.MaxPlayers})" + $"{FormatActivityName(activity.Name)} ({participants.Length}/{activity.MaxPlayers})" }; // Build description @@ -99,7 +123,7 @@ public class ActivityFormatter .WithTitle(title) .WithDescription(descriptionBuilder.ToString()) .WithImageUrl(bannerUrl) - .WithFields(playersField); + .WithFields(fields); // Add material for interstellar exploration if (activity is InterstellarActivity interstellarActivity) @@ -127,9 +151,10 @@ public class ActivityFormatter _ => "NA" }; - public string FormatActivityPlayer(ActivityPlayer player, int namePadding) => player switch + public string FormatActivityPlayer(ActivityPlayer player, int namePadding, bool isEvent = false) => player switch { - ActivityRolePlayer rolePlayer => $"` {player.Name.PadRight(namePadding)} ` **―** {RolesToEmotes(rolePlayer.Roles)}", + ActivityRolePlayer rolePlayer => $"` {player.Name.PadRight(namePadding)} ` **╿** {RolesToEmotes(rolePlayer.Roles)}", + _ when isEvent && player.IsOrganizer => $"` {player.Name.PadRight(namePadding)} ` **╿** {_options.OrganizerEmote} ", _ => $"` {player.Name} `" }; @@ -139,17 +164,7 @@ public class ActivityFormatter if (rolePlayerRoles.HasFlag(PlayerRoles.Helper)) { - emotesBuilder.Append($" {_options.HelperEmote} "); - } - - if (rolePlayerRoles.HasFlag(PlayerRoles.Dps)) - { - emotesBuilder.Append($" {_options.DpsEmote} "); - } - - if (rolePlayerRoles.HasFlag(PlayerRoles.Tank)) - { - emotesBuilder.Append($" {_options.TankEmote} "); + emotesBuilder.Append($" {_options.HelperEmote} **╿**"); } if (rolePlayerRoles.HasFlag(PlayerRoles.Support)) @@ -157,6 +172,16 @@ public class ActivityFormatter emotesBuilder.Append($" {_options.SupportEmote} "); } + if (rolePlayerRoles.HasFlag(PlayerRoles.Tank)) + { + emotesBuilder.Append($" {_options.TankEmote} "); + } + + if (rolePlayerRoles.HasFlag(PlayerRoles.Dps)) + { + emotesBuilder.Append($" {_options.DpsEmote} "); + } + return emotesBuilder.ToString(); } } \ No newline at end of file diff --git a/Cocotte/Modules/Activities/ActivityHelper.cs b/Cocotte/Modules/Activities/ActivityHelper.cs index 7f52ea5..5e56260 100644 --- a/Cocotte/Modules/Activities/ActivityHelper.cs +++ b/Cocotte/Modules/Activities/ActivityHelper.cs @@ -34,6 +34,7 @@ public class ActivityHelper var role when role == _options.DpsRoleId => PlayerRoles.Dps, var role when role == _options.TankRoleId => PlayerRoles.Tank, var role when role == _options.SupportRoleId => PlayerRoles.Support, + var role when role == _options.OrganizerRoleId => PlayerRoles.Organizer, _ => PlayerRoles.None }; } @@ -80,6 +81,20 @@ public class ActivityHelper _ => 0 }; + public static bool IsEventActivity(ActivityType activityType) => + activityType is ActivityType.Event8Players or ActivityType.Event4Players or ActivityType.Other; + + public bool IsOrganizer(OrganizedActivity organizedActivity, SocketGuildUser user) + { + // If it is an event, check if the user has organizer role, otherwise helper + if (IsEventActivity(organizedActivity.Type)) + { + return user.Roles.Any(r => r.Id == _options.OrganizerRoleId); + } + + return user.Roles.Any(r => r.Id == _options.HelperRoleId); + } + public async Task UpdateActivityEmbed(IMessageChannel channel, Activity activity, ActivityUpdateReason updateReason) { // Fetch players @@ -93,7 +108,7 @@ public class ActivityHelper var isActivityFull = players.Count >= activity.MaxPlayers; properties.Components = updateReason switch { - ActivityUpdateReason.PlayerJoin when isActivityFull => ActivityComponents(activity.MessageId, disabled: true).Build(), + ActivityUpdateReason.PlayerJoin when isActivityFull && activity is not OrganizedActivity => ActivityComponents(activity.MessageId, disabled: true).Build(), ActivityUpdateReason.PlayerLeave when !isActivityFull => ActivityComponents(activity.MessageId, disabled: false).Build(), _ => Optional.Unspecified }; diff --git a/Cocotte/Modules/Activities/ActivityModule.cs b/Cocotte/Modules/Activities/ActivityModule.cs index 9330fcc..adb33f5 100644 --- a/Cocotte/Modules/Activities/ActivityModule.cs +++ b/Cocotte/Modules/Activities/ActivityModule.cs @@ -54,7 +54,7 @@ public partial class ActivityModule : InteractionModuleBase= activity.MaxPlayers) - { - await RespondAsync( - ephemeral: true, - embed: EmbedUtils.ErrorEmbed("L'activité est **complète**").Build() - ); + // Check if activity is full + if (await _activitiesRepository.ActivityPlayerCount(activity) >= activity.MaxPlayers) + { + await RespondAsync( + ephemeral: true, + embed: EmbedUtils.ErrorEmbed("L'activité est **complète**").Build() + ); - return false; + return false; + } } _logger.LogTrace("Player {Player} joined activity {Id}", user.DisplayName, activity.MessageId); - var activityPlayer = CreateActivityPlayer(activity, user, activity.AreRolesEnabled); + var activityPlayer = CreateActivityPlayer(activity, user, activity.AreRolesEnabled, isOrganizer: isOrganizer); // Add player to activity activity.ActivityPlayers.Add(activityPlayer); diff --git a/Cocotte/Modules/Activities/ActivityModuleDebug.cs b/Cocotte/Modules/Activities/ActivityModuleDebug.cs index c80648c..3e50ca9 100644 --- a/Cocotte/Modules/Activities/ActivityModuleDebug.cs +++ b/Cocotte/Modules/Activities/ActivityModuleDebug.cs @@ -21,8 +21,8 @@ public partial class ActivityModule Activity = activity, Name = $"Player{Random.Shared.Next(1, 100)}", UserId = (ulong) Random.Shared.NextInt64(), - Roles = (PlayerRoles) Random.Shared.Next((int) (PlayerRoles.Dps | PlayerRoles.Helper | - PlayerRoles.Support | PlayerRoles.Tank) + 1) + Roles = (PlayerRoles) Random.Shared.Next((int)PlayerRoles.Helper, (int) (PlayerRoles.Dps | PlayerRoles.Helper | + PlayerRoles.Support | PlayerRoles.Tank) + 1 - (int)PlayerRoles.Helper) }; // Add the player to the activity diff --git a/Cocotte/Modules/Activities/Models/Activity.cs b/Cocotte/Modules/Activities/Models/Activity.cs index 1783280..826bf1b 100644 --- a/Cocotte/Modules/Activities/Models/Activity.cs +++ b/Cocotte/Modules/Activities/Models/Activity.cs @@ -29,6 +29,12 @@ public class Activity public List ActivityPlayers { get; init; } = new(); + [NotMapped] + public IEnumerable Participants => ActivityPlayers.Where(p => !p.IsOrganizer); + [NotMapped] + public IEnumerable Organizers => ActivityPlayers.Where(p => p.IsOrganizer); + + [NotMapped] public string JobKey => $"{GuildId}/{ChannelId}/{MessageId}"; public override string ToString() diff --git a/Cocotte/Modules/Activities/Models/ActivityPlayer.cs b/Cocotte/Modules/Activities/Models/ActivityPlayer.cs index 15afedf..2a1baae 100644 --- a/Cocotte/Modules/Activities/Models/ActivityPlayer.cs +++ b/Cocotte/Modules/Activities/Models/ActivityPlayer.cs @@ -10,6 +10,7 @@ public class ActivityPlayer public required ulong UserId { get; init; } public required string Name { get; init; } + public bool IsOrganizer { get; init; } public ulong GuildId { get; set; } public ulong ChannelId { get; set; } diff --git a/Cocotte/Modules/Activities/Models/OrganizedActivity.cs b/Cocotte/Modules/Activities/Models/OrganizedActivity.cs new file mode 100644 index 0000000..78d1342 --- /dev/null +++ b/Cocotte/Modules/Activities/Models/OrganizedActivity.cs @@ -0,0 +1,8 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Cocotte.Modules.Activities.Models; + +public class OrganizedActivity : Activity +{ + +} \ No newline at end of file diff --git a/Cocotte/Modules/Activities/PlayerRoles.cs b/Cocotte/Modules/Activities/PlayerRoles.cs index a20b0ee..3b0bd7d 100644 --- a/Cocotte/Modules/Activities/PlayerRoles.cs +++ b/Cocotte/Modules/Activities/PlayerRoles.cs @@ -3,9 +3,10 @@ [Flags] public enum PlayerRoles : byte { - None = 0b0000, - Helper = 0b0001, - Dps = 0b0010, - Tank = 0b0100, - Support = 0b1000 + None = 0b0000_0000, + Helper = 0b0000_0001, + Dps = 0b0000_0010, + Tank = 0b0000_0100, + Support = 0b0000_1000, + Organizer = 0b0001_0000 } \ No newline at end of file diff --git a/Cocotte/Options/ActivityOptions.cs b/Cocotte/Options/ActivityOptions.cs index 558922c..b359d23 100644 --- a/Cocotte/Options/ActivityOptions.cs +++ b/Cocotte/Options/ActivityOptions.cs @@ -4,6 +4,9 @@ public class ActivityOptions { public const string SectionName = "ActivityOptions"; + public ulong OrganizerRoleId { get; init; } + public required string OrganizerEmote { get; init; } + public ulong HelperRoleId { get; init; } public required string HelperEmote { get; init; } diff --git a/Cocotte/Program.cs b/Cocotte/Program.cs index c33fd66..7bdabe1 100644 --- a/Cocotte/Program.cs +++ b/Cocotte/Program.cs @@ -88,7 +88,7 @@ await using(var scope = host.Services.CreateAsyncScope()) var dbContext = scope.ServiceProvider.GetRequiredService(); if (hostEnvironment.IsDevelopment()) { - // await dbContext.Database.EnsureDeletedAsync(); + await dbContext.Database.EnsureDeletedAsync(); await dbContext.Database.EnsureCreatedAsync(); } else diff --git a/Cocotte/Services/CocotteDbContext.cs b/Cocotte/Services/CocotteDbContext.cs index 89f05e7..356f905 100644 --- a/Cocotte/Services/CocotteDbContext.cs +++ b/Cocotte/Services/CocotteDbContext.cs @@ -10,6 +10,7 @@ public class CocotteDbContext : DbContext public DbSet Activities => Set(); public DbSet StagedActivities => Set(); public DbSet InterstellarActivities => Set(); + public DbSet OrganizedActivities => Set(); public DbSet ActivityPlayers => Set(); public DbSet ActivityRolePlayers => Set(); diff --git a/Cocotte/Utils/CocotteDayOfWeek.cs b/Cocotte/Utils/CocotteDayOfWeek.cs new file mode 100644 index 0000000..300d215 --- /dev/null +++ b/Cocotte/Utils/CocotteDayOfWeek.cs @@ -0,0 +1,21 @@ +using Discord.Interactions; + +namespace Cocotte.Utils; + +public enum CocotteDayOfWeek +{ + [ChoiceDisplay("Lundi")] + Monday = DayOfWeek.Monday, + [ChoiceDisplay("Mardi")] + Tuesday = DayOfWeek.Tuesday, + [ChoiceDisplay("Mercredi")] + Wednesday = DayOfWeek.Wednesday, + [ChoiceDisplay("Jeudi")] + Thursday = DayOfWeek.Thursday, + [ChoiceDisplay("Vendredi")] + Friday = DayOfWeek.Friday, + [ChoiceDisplay("Samedi")] + Saturday = DayOfWeek.Saturday, + [ChoiceDisplay("Dimanche")] + Sunday = DayOfWeek.Sunday +} \ No newline at end of file diff --git a/Cocotte/Utils/DateTimeUtils.cs b/Cocotte/Utils/DateTimeUtils.cs index 98a84ea..2a6db57 100644 --- a/Cocotte/Utils/DateTimeUtils.cs +++ b/Cocotte/Utils/DateTimeUtils.cs @@ -14,6 +14,15 @@ public static class DateTimeUtils return offset; } + public static DateTime NextDateWithDayAndTime(DayOfWeek desired, TimeOnly time) + { + var date = DateTime.Today; + date = date.AddDays(CalculateDayOfWeekOffset(date.DayOfWeek, desired)).WithTimeOnly(time); + + // If it's previous than current date, add 7 days + return date < DateTime.Now ? date.AddDays(7) : date; + } + public static DateTime WithTimeOnly(this DateTime dateTime, TimeOnly timeOnly) { return new DateTime(