[Activity] Add organized activities

This commit is contained in:
2023-03-27 00:22:43 +02:00
parent a6e8ad0401
commit 78fe2d5913
16 changed files with 981 additions and 57 deletions

View File

@@ -0,0 +1,686 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("TriggerName")
.HasColumnType("text")
.HasColumnName("TRIGGER_NAME");
b.Property<string>("TriggerGroup")
.HasColumnType("text")
.HasColumnName("TRIGGER_GROUP");
b.Property<byte[]>("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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("CalendarName")
.HasColumnType("text")
.HasColumnName("CALENDAR_NAME");
b.Property<byte[]>("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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("TriggerName")
.HasColumnType("text")
.HasColumnName("TRIGGER_NAME");
b.Property<string>("TriggerGroup")
.HasColumnType("text")
.HasColumnName("TRIGGER_GROUP");
b.Property<string>("CronExpression")
.IsRequired()
.HasColumnType("text")
.HasColumnName("CRON_EXPRESSION");
b.Property<string>("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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("EntryId")
.HasColumnType("text")
.HasColumnName("ENTRY_ID");
b.Property<long>("FiredTime")
.HasColumnType("bigint")
.HasColumnName("FIRED_TIME");
b.Property<string>("InstanceName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("INSTANCE_NAME");
b.Property<bool>("IsNonConcurrent")
.HasColumnType("bool")
.HasColumnName("IS_NONCONCURRENT");
b.Property<string>("JobGroup")
.HasColumnType("text")
.HasColumnName("JOB_GROUP");
b.Property<string>("JobName")
.HasColumnType("text")
.HasColumnName("JOB_NAME");
b.Property<int>("Priority")
.HasColumnType("integer")
.HasColumnName("PRIORITY");
b.Property<bool?>("RequestsRecovery")
.HasColumnType("bool")
.HasColumnName("REQUESTS_RECOVERY");
b.Property<long>("ScheduledTime")
.HasColumnType("bigint")
.HasColumnName("SCHED_TIME");
b.Property<string>("State")
.IsRequired()
.HasColumnType("text")
.HasColumnName("STATE");
b.Property<string>("TriggerGroup")
.IsRequired()
.HasColumnType("text")
.HasColumnName("TRIGGER_GROUP");
b.Property<string>("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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("JobName")
.HasColumnType("text")
.HasColumnName("JOB_NAME");
b.Property<string>("JobGroup")
.HasColumnType("text")
.HasColumnName("JOB_GROUP");
b.Property<string>("Description")
.HasColumnType("text")
.HasColumnName("DESCRIPTION");
b.Property<bool>("IsDurable")
.HasColumnType("bool")
.HasColumnName("IS_DURABLE");
b.Property<bool>("IsNonConcurrent")
.HasColumnType("bool")
.HasColumnName("IS_NONCONCURRENT");
b.Property<bool>("IsUpdateData")
.HasColumnType("bool")
.HasColumnName("IS_UPDATE_DATA");
b.Property<string>("JobClassName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("JOB_CLASS_NAME");
b.Property<byte[]>("JobData")
.HasColumnType("bytea")
.HasColumnName("JOB_DATA");
b.Property<bool>("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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("InstanceName")
.HasColumnType("text")
.HasColumnName("INSTANCE_NAME");
b.Property<long>("CheckInInterval")
.HasColumnType("bigint")
.HasColumnName("CHECKIN_INTERVAL");
b.Property<long>("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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("TriggerName")
.HasColumnType("text")
.HasColumnName("TRIGGER_NAME");
b.Property<string>("TriggerGroup")
.HasColumnType("text")
.HasColumnName("TRIGGER_GROUP");
b.Property<bool?>("BooleanProperty1")
.HasColumnType("bool")
.HasColumnName("BOOL_PROP_1");
b.Property<bool?>("BooleanProperty2")
.HasColumnType("bool")
.HasColumnName("BOOL_PROP_2");
b.Property<decimal?>("DecimalProperty1")
.HasColumnType("numeric")
.HasColumnName("DEC_PROP_1");
b.Property<decimal?>("DecimalProperty2")
.HasColumnType("numeric")
.HasColumnName("DEC_PROP_2");
b.Property<int?>("IntegerProperty1")
.HasColumnType("integer")
.HasColumnName("INT_PROP_1");
b.Property<int?>("IntegerProperty2")
.HasColumnType("integer")
.HasColumnName("INT_PROP_2");
b.Property<long?>("LongProperty1")
.HasColumnType("bigint")
.HasColumnName("LONG_PROP_1");
b.Property<long?>("LongProperty2")
.HasColumnType("bigint")
.HasColumnName("LONG_PROP_2");
b.Property<string>("StringProperty1")
.HasColumnType("text")
.HasColumnName("STR_PROP_1");
b.Property<string>("StringProperty2")
.HasColumnType("text")
.HasColumnName("STR_PROP_2");
b.Property<string>("StringProperty3")
.HasColumnType("text")
.HasColumnName("STR_PROP_3");
b.Property<string>("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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("TriggerName")
.HasColumnType("text")
.HasColumnName("TRIGGER_NAME");
b.Property<string>("TriggerGroup")
.HasColumnType("text")
.HasColumnName("TRIGGER_GROUP");
b.Property<long>("RepeatCount")
.HasColumnType("bigint")
.HasColumnName("REPEAT_COUNT");
b.Property<long>("RepeatInterval")
.HasColumnType("bigint")
.HasColumnName("REPEAT_INTERVAL");
b.Property<long>("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<string>("SchedulerName")
.HasColumnType("text")
.HasColumnName("SCHED_NAME");
b.Property<string>("TriggerName")
.HasColumnType("text")
.HasColumnName("TRIGGER_NAME");
b.Property<string>("TriggerGroup")
.HasColumnType("text")
.HasColumnName("TRIGGER_GROUP");
b.Property<string>("CalendarName")
.HasColumnType("text")
.HasColumnName("CALENDAR_NAME");
b.Property<string>("Description")
.HasColumnType("text")
.HasColumnName("DESCRIPTION");
b.Property<long?>("EndTime")
.HasColumnType("bigint")
.HasColumnName("END_TIME");
b.Property<byte[]>("JobData")
.HasColumnType("bytea")
.HasColumnName("JOB_DATA");
b.Property<string>("JobGroup")
.IsRequired()
.HasColumnType("text")
.HasColumnName("JOB_GROUP");
b.Property<string>("JobName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("JOB_NAME");
b.Property<short?>("MisfireInstruction")
.HasColumnType("smallint")
.HasColumnName("MISFIRE_INSTR");
b.Property<long?>("NextFireTime")
.HasColumnType("bigint")
.HasColumnName("NEXT_FIRE_TIME");
b.Property<long?>("PreviousFireTime")
.HasColumnType("bigint")
.HasColumnName("PREV_FIRE_TIME");
b.Property<int?>("Priority")
.HasColumnType("integer")
.HasColumnName("PRIORITY");
b.Property<long>("StartTime")
.HasColumnType("bigint")
.HasColumnName("START_TIME");
b.Property<string>("TriggerState")
.IsRequired()
.HasColumnType("text")
.HasColumnName("TRIGGER_STATE");
b.Property<string>("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<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<ulong>("ChannelId")
.HasColumnType("INTEGER");
b.Property<ulong>("MessageId")
.HasColumnType("INTEGER");
b.Property<bool>("AreRolesEnabled")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreationDate")
.HasColumnType("TEXT");
b.Property<string>("CreatorDisplayName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<ulong>("CreatorUserId")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime?>("DueDateTime")
.HasColumnType("TEXT");
b.Property<bool>("IsClosed")
.HasColumnType("INTEGER");
b.Property<uint>("MaxPlayers")
.HasColumnType("INTEGER");
b.Property<int>("Name")
.HasColumnType("INTEGER");
b.Property<ulong>("ThreadId")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("GuildId", "ChannelId", "MessageId");
b.HasIndex("ThreadId");
b.ToTable("Activities");
b.HasDiscriminator<string>("Discriminator").HasValue("Activity");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("Cocotte.Modules.Activities.Models.ActivityPlayer", b =>
{
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<ulong>("ChannelId")
.HasColumnType("INTEGER");
b.Property<ulong>("MessageId")
.HasColumnType("INTEGER");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsOrganizer")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("GuildId", "ChannelId", "MessageId", "UserId");
b.ToTable("ActivityPlayers");
b.HasDiscriminator<string>("Discriminator").HasValue("ActivityPlayer");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("Cocotte.Modules.Activities.Models.InterstellarActivity", b =>
{
b.HasBaseType("Cocotte.Modules.Activities.Models.Activity");
b.Property<int>("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<uint>("Stage")
.HasColumnType("INTEGER");
b.HasDiscriminator().HasValue("StagedActivity");
});
modelBuilder.Entity("Cocotte.Modules.Activities.Models.ActivityRolePlayer", b =>
{
b.HasBaseType("Cocotte.Modules.Activities.Models.ActivityPlayer");
b.Property<byte>("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
}
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Cocotte.Migrations
{
/// <inheritdoc />
public partial class AddOrganizedActivities : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsOrganizer",
table: "ActivityPlayers",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsOrganizer",
table: "ActivityPlayers");
}
}
}

View File

@@ -538,6 +538,9 @@ namespace Cocotte.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsOrganizer")
.HasColumnType("INTEGER");
b.Property<string>("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");

View File

@@ -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<ActivityPlayer> 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<EmbedFieldBuilder>();
// 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();
}
}

View File

@@ -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<MessageComponent>.Unspecified
};

View File

@@ -54,7 +54,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
}
#endif
#region activities
#region Activities
[SlashCommand("abime-néant", "Créer un groupe pour l'Abîme du Néant")]
[Alias("abime", "abyss")]
@@ -171,12 +171,94 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
#endregion
#region Organization
[SlashCommand("organiser-abime", "Organiser un groupe d'aide pour l'abîme du néant")]
public async Task OrganizeVoidAbyss(
[Summary("jour", "Jour auquel l'activité est prévu")] CocotteDayOfWeek day,
[Summary("heure", "Heure à laquelle l'activité est prévue")] string timeInput,
[Summary("joueurs", "Nombre de joueurs maximum qui peuvent s'inscrire")] uint maxPlayers,
string description = "")
{
if (!TimeOnly.TryParse(timeInput, out var time))
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils
.ErrorEmbed(
"**Heure invalide**, essayez avec le **format** `heure:minutes`\nPar exemple: `15:30`")
.Build()
);
return;
}
// Calculate date
var date = DateTimeUtils.NextDateWithDayAndTime((DayOfWeek) day, time);
await CreateActivity(ActivityName.Abyss, null, description, true, maxPlayers, date: date);
}
[SlashCommand("organiser-origine", "Organiser un groupe d'aide pour l'origine de la guerre")]
public async Task OrganizeOrigins(
[Summary("jour", "Jour auquel l'activité est prévu")] CocotteDayOfWeek day,
[Summary("heure", "Heure à laquelle l'activité est prévue")] string timeInput,
[Summary("joueurs", "Nombre de joueurs maximum qui peuvent s'inscrire")] uint maxPlayers,
string description = "")
{
if (!TimeOnly.TryParse(timeInput, out var time))
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils
.ErrorEmbed(
"**Heure invalide**, essayez avec le **format** `heure:minutes`\nPar exemple: `15:30`")
.Build()
);
return;
}
// Calculate date
var date = DateTimeUtils.NextDateWithDayAndTime((DayOfWeek) day, time);
await CreateActivity(ActivityName.OriginsOfWar, null, description, true, maxPlayers, date: date);
}
[SlashCommand("organiser-evenement", "Organiser un groupe d'aide pour l'abîme du néant")]
public async Task OrganizeEvent(
[Summary("jour", "Jour auquel l'activité est prévu")] CocotteDayOfWeek day,
[Summary("heure", "Heure à laquelle l'activité est prévue")] string timeInput,
[Summary("joueurs", "Nombre de joueurs maximum qui peuvent s'inscrire")] uint maxPlayers,
string description = "")
{
if (!TimeOnly.TryParse(timeInput, out var time))
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils
.ErrorEmbed(
"**Heure invalide**, essayez avec le **format** `heure:minutes`\nPar exemple: `15:30`")
.Build()
);
return;
}
// Calculate date
var date = DateTimeUtils.NextDateWithDayAndTime((DayOfWeek) day, time);
await CreateActivity(ActivityName.Event, null, description, false, maxPlayers, date: date);
}
#endregion
private async Task CreateActivity(ActivityName activityName, string? timeInput, string description,
bool areRolesEnabled = true, uint? maxPlayers = null, uint? stage = null,
InterstellarColor? interstellarColor = null)
InterstellarColor? interstellarColor = null, DateTime? date = null)
{
// Check time if it's specified
DateTime? dueDate = null;
var dueDate = date;
if (timeInput is not null)
{
if (!TimeOnly.TryParse(timeInput, out var parsedTime))
@@ -209,7 +291,26 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
maxPlayers ??= ActivityHelper.ActivityTypeToMaxPlayers(activityType);
Activity activity;
if (stage is not null)
// Create organized activity if date is not null
if (date is not null)
{
activity = new OrganizedActivity
{
MessageId = response.Id,
ChannelId = Context.Channel.Id,
GuildId = Context.Guild.Id,
ThreadId = threadId,
CreatorUserId = Context.User.Id,
CreatorDisplayName = ((SocketGuildUser) Context.User).DisplayName,
Description = description,
DueDateTime = dueDate,
Type = activityType,
Name = activityName,
AreRolesEnabled = areRolesEnabled,
MaxPlayers = (uint) maxPlayers
};
}
else if (stage is not null)
{
activity = new StagedActivity
{
@@ -269,10 +370,10 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
// Add activity to db
await _activitiesRepository.AddActivity(activity);
// Add creator to activity
var rolePlayer = CreateActivityPlayer(activity, User, areRolesEnabled);
// Add creator to activity, make it an helper
var activityPlayer = CreateActivityPlayer(activity, User, areRolesEnabled, activity is OrganizedActivity);
activity.ActivityPlayers.Add(rolePlayer);
activity.ActivityPlayers.Add(activityPlayer);
await _activitiesRepository.SaveChanges();
@@ -283,7 +384,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
{
m.Content = "";
m.Components = components.Build();
m.Embed = _activityFormatter.ActivityEmbed(activity, Enumerable.Repeat(rolePlayer, 1).ToImmutableList())
m.Embed = _activityFormatter.ActivityEmbed(activity, Enumerable.Repeat(activityPlayer, 1).ToImmutableList())
.Build();
});
@@ -310,7 +411,7 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
}
}
private ActivityPlayer CreateActivityPlayer(Activity activity, SocketGuildUser user, bool areRolesEnabled)
private ActivityPlayer CreateActivityPlayer(Activity activity, SocketGuildUser user, bool areRolesEnabled, bool isOrganizer = false)
{
return areRolesEnabled
? new ActivityRolePlayer
@@ -318,13 +419,15 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
Activity = activity,
UserId = user.Id,
Name = user.DisplayName,
Roles = _activityHelper.GetPlayerRoles(user.Roles)
Roles = _activityHelper.GetPlayerRoles(user.Roles),
IsOrganizer = isOrganizer
}
: new ActivityPlayer
{
Activity = activity,
UserId = user.Id,
Name = user.DisplayName
Name = user.DisplayName,
IsOrganizer = isOrganizer
};
}
@@ -435,31 +538,37 @@ public partial class ActivityModule : InteractionModuleBase<SocketInteractionCon
return false;
}
// Check if activity is closed
if (activity.IsClosed)
// If activity is an organized activity and user is an organizer, bypass full and closed checks
var isOrganizer = activity is OrganizedActivity organizedActivity &&
_activityHelper.IsOrganizer(organizedActivity, user);
if (!isOrganizer)
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Cette activité est fermée").Build()
);
// Check if activity is closed
if (activity.IsClosed)
{
await RespondAsync(
ephemeral: true,
embed: EmbedUtils.ErrorEmbed("Cette activité est fermée").Build()
);
return false;
}
return false;
}
// 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()
);
// 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);

View File

@@ -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

View File

@@ -29,6 +29,12 @@ public class Activity
public List<ActivityPlayer> ActivityPlayers { get; init; } = new();
[NotMapped]
public IEnumerable<ActivityPlayer> Participants => ActivityPlayers.Where(p => !p.IsOrganizer);
[NotMapped]
public IEnumerable<ActivityPlayer> Organizers => ActivityPlayers.Where(p => p.IsOrganizer);
[NotMapped]
public string JobKey => $"{GuildId}/{ChannelId}/{MessageId}";
public override string ToString()

View File

@@ -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; }

View File

@@ -0,0 +1,8 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Cocotte.Modules.Activities.Models;
public class OrganizedActivity : Activity
{
}

View File

@@ -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
}

View File

@@ -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; }

View File

@@ -88,7 +88,7 @@ await using(var scope = host.Services.CreateAsyncScope())
var dbContext = scope.ServiceProvider.GetRequiredService<CocotteDbContext>();
if (hostEnvironment.IsDevelopment())
{
// await dbContext.Database.EnsureDeletedAsync();
await dbContext.Database.EnsureDeletedAsync();
await dbContext.Database.EnsureCreatedAsync();
}
else

View File

@@ -10,6 +10,7 @@ public class CocotteDbContext : DbContext
public DbSet<Activity> Activities => Set<Activity>();
public DbSet<StagedActivity> StagedActivities => Set<StagedActivity>();
public DbSet<InterstellarActivity> InterstellarActivities => Set<InterstellarActivity>();
public DbSet<OrganizedActivity> OrganizedActivities => Set<OrganizedActivity>();
public DbSet<ActivityPlayer> ActivityPlayers => Set<ActivityPlayer>();
public DbSet<ActivityRolePlayer> ActivityRolePlayers => Set<ActivityRolePlayer>();

View File

@@ -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
}

View File

@@ -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(