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(