[Raid] Add FC and role prompt when joining a raid
This commit is contained in:
9
Cocotte/Modules/Raids/IPlayerInfosRepository.cs
Normal file
9
Cocotte/Modules/Raids/IPlayerInfosRepository.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Cocotte.Modules.Raids;
|
||||
|
||||
public interface IPlayerInfosRepository
|
||||
{
|
||||
bool TryGetPlayerInfo(ulong playerId, [MaybeNullWhen(false)] out PlayerInfo playerInfo);
|
||||
void UpdatePlayerInfo(PlayerInfo playerInfo);
|
||||
}
|
||||
23
Cocotte/Modules/Raids/MemoryPlayerInfosRepository.cs
Normal file
23
Cocotte/Modules/Raids/MemoryPlayerInfosRepository.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Cocotte.Modules.Raids;
|
||||
|
||||
public class MemoryPlayerInfosRepository : IPlayerInfosRepository
|
||||
{
|
||||
private readonly IDictionary<ulong, PlayerInfo> _playerInfos;
|
||||
|
||||
public MemoryPlayerInfosRepository()
|
||||
{
|
||||
_playerInfos = new Dictionary<ulong, PlayerInfo>();
|
||||
}
|
||||
|
||||
public bool TryGetPlayerInfo(ulong playerId, [MaybeNullWhen(false)] out PlayerInfo playerInfo)
|
||||
{
|
||||
return _playerInfos.TryGetValue(playerId, out playerInfo);
|
||||
}
|
||||
|
||||
public void UpdatePlayerInfo(PlayerInfo playerInfo)
|
||||
{
|
||||
_playerInfos[playerInfo.Id] = playerInfo;
|
||||
}
|
||||
}
|
||||
31
Cocotte/Modules/Raids/PlayerInfo.cs
Normal file
31
Cocotte/Modules/Raids/PlayerInfo.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace Cocotte.Modules.Raids;
|
||||
|
||||
public class PlayerInfo
|
||||
{
|
||||
public static TimeSpan FcUpdateInterval { get; } = TimeSpan.FromDays(3);
|
||||
|
||||
public ulong Id { get; }
|
||||
|
||||
public uint Fc
|
||||
{
|
||||
get => _fc;
|
||||
set
|
||||
{
|
||||
_fc = value;
|
||||
_lastFcUpdate = DateTime.Today;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFcUpdateRequired => DateTime.Today - _lastFcUpdate > FcUpdateInterval;
|
||||
|
||||
private uint _fc;
|
||||
private DateTime _lastFcUpdate;
|
||||
|
||||
public PlayerInfo(ulong id, uint fc)
|
||||
{
|
||||
Id = id;
|
||||
Fc = fc;
|
||||
|
||||
_lastFcUpdate = DateTime.Today;
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,14 @@ public class Raid
|
||||
#endif
|
||||
}
|
||||
|
||||
public bool AddPlayer(ulong id, string name, PlayerRole role, int fc, bool substitute = false)
|
||||
public bool AddPlayer(RosterPlayer player)
|
||||
{
|
||||
return _rosterManager.AddPlayer(new RosterPlayer(id, name, role, fc, substitute));
|
||||
return _rosterManager.AddPlayer(player);
|
||||
}
|
||||
|
||||
public bool UpdatePlayer(RosterPlayer rosterPlayer)
|
||||
{
|
||||
return _rosterManager.UpdatePlayer(rosterPlayer);
|
||||
}
|
||||
|
||||
public RosterPlayer GetPlayer(ulong id)
|
||||
@@ -31,6 +36,11 @@ public class Raid
|
||||
return _rosterManager.GetPlayer(id);
|
||||
}
|
||||
|
||||
public bool ContainsPlayer(ulong userId)
|
||||
{
|
||||
return _rosterManager.ContainsPlayer(userId);
|
||||
}
|
||||
|
||||
public bool RemovePlayer(ulong id)
|
||||
{
|
||||
return _rosterManager.RemovePlayer(id);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class RaidFormatter
|
||||
_rolesOptions = rolesOptions;
|
||||
}
|
||||
|
||||
private string RoleToEmote(PlayerRole role) => role switch
|
||||
public string RoleToEmote(PlayerRole role) => role switch
|
||||
{
|
||||
PlayerRole.Dps => _rolesOptions.DpsEmote,
|
||||
PlayerRole.Tank => _rolesOptions.TankEmote,
|
||||
@@ -21,7 +21,7 @@ public class RaidFormatter
|
||||
_ => ":question:"
|
||||
};
|
||||
|
||||
public static string FcFormat(int fc) => fc switch
|
||||
public static string FcFormat(uint fc) => fc switch
|
||||
{
|
||||
< 1_000 => $"{fc}",
|
||||
_ => $"{fc/1000}k"
|
||||
|
||||
@@ -13,14 +13,19 @@ namespace Cocotte.Modules.Raids;
|
||||
public class RaidModule : InteractionModuleBase<SocketInteractionContext>
|
||||
{
|
||||
private readonly ILogger<RaidModule> _logger;
|
||||
private readonly IRaidsRepository _raidsRepository;
|
||||
private readonly IRaidsRepository _raids;
|
||||
private readonly IPlayerInfosRepository _playerInfos;
|
||||
private readonly RaidFormatter _raidFormatter;
|
||||
private readonly RaidRegisterManager _registerManager;
|
||||
|
||||
public RaidModule(ILogger<RaidModule> logger, IRaidsRepository raidsRepository, RaidFormatter raidFormatter)
|
||||
public RaidModule(ILogger<RaidModule> logger, IRaidsRepository raids, IPlayerInfosRepository playerInfos,
|
||||
RaidFormatter raidFormatter, RaidRegisterManager registerManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_raidsRepository = raidsRepository;
|
||||
_raids = raids;
|
||||
_playerInfos = playerInfos;
|
||||
_raidFormatter = raidFormatter;
|
||||
_registerManager = registerManager;
|
||||
}
|
||||
|
||||
[EnabledInDm(false)]
|
||||
@@ -47,12 +52,12 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
|
||||
_logger.LogInformation("Created new raid with id {RaidId}", raidId);
|
||||
|
||||
// Calculate date
|
||||
var date = DateTime.Now.Date;
|
||||
var date = DateTime.Today;
|
||||
date = date.AddDays(DateTimeUtils.CalculateDayOfWeekOffset(date.DayOfWeek, day))
|
||||
.Add(timeOnly.ToTimeSpan());
|
||||
|
||||
// New raid instance
|
||||
if (!_raidsRepository.AddNewRaid(raidId, date))
|
||||
if (!_raids.AddNewRaid(raidId, date))
|
||||
{
|
||||
// A raid with this message id already exists, how??
|
||||
_logger.LogWarning("Tried to create a new raid with already existing id: {RaidId}", raidId);
|
||||
@@ -67,7 +72,7 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
|
||||
}
|
||||
|
||||
// Build the raid message
|
||||
var raid = _raidsRepository[raidId];
|
||||
var raid = _raids[raidId];
|
||||
var components = RaidComponents(raidId);
|
||||
var embed = _raidFormatter.RaidEmbed(raid);
|
||||
|
||||
@@ -80,9 +85,9 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
|
||||
}
|
||||
|
||||
[ComponentInteraction("raid raid_join:*", true)]
|
||||
public async Task Join(ulong raidId)
|
||||
public async Task RaidJoin(ulong raidId)
|
||||
{
|
||||
if (!_raidsRepository.TryGetRaid(raidId, out var raid))
|
||||
if (!_raids.TryGetRaid(raidId, out var raid))
|
||||
{
|
||||
await RespondAsync(
|
||||
ephemeral: true,
|
||||
@@ -92,9 +97,10 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
|
||||
return;
|
||||
}
|
||||
|
||||
// Todo: Ask role, FC, substitute
|
||||
var user = (IGuildUser) Context.User;
|
||||
if (!raid.AddPlayer(user.Id, user.DisplayName, PlayerRole.Dps, 10000))
|
||||
|
||||
// Check if player is already registered early
|
||||
if (raid.ContainsPlayer(user.Id))
|
||||
{
|
||||
await RespondAsync(
|
||||
ephemeral: true,
|
||||
@@ -103,19 +109,154 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("User {User} joined raid {Raid}", Context.User.Username, raid);
|
||||
// Ask player info
|
||||
_registerManager.RegisteringPlayers[(raidId, user.Id)] = new RosterPlayer(user.Id, user.DisplayName);
|
||||
|
||||
await UpdateRaidRosterEmbed(raid);
|
||||
await RespondAsync(
|
||||
ephemeral: true,
|
||||
embed: EmbedUtils.SuccessEmbed($"Successfully joined the raid as {raid.GetPlayer(user.Id).Role}").Build()
|
||||
);
|
||||
_logger.LogDebug("User {User} is registering for raid {Raid}", user.Username, raid);
|
||||
|
||||
// Add name
|
||||
await RespondAsync($"Please select a role for raid", components: PlayerRoleComponent(raid, user).Build(), ephemeral: true);
|
||||
}
|
||||
|
||||
[ComponentInteraction("raid player_select_role:*:*", ignoreGroupNames: true)]
|
||||
public async Task PlayerSelectRole(ulong raidId, ulong playerId, string selectedRoleInput)
|
||||
{
|
||||
var selectedRole = Enum.Parse<PlayerRole>(selectedRoleInput);
|
||||
|
||||
if (_registerManager.RegisteringPlayers.TryGetValue((raidId, playerId), out var rosterPlayer))
|
||||
{
|
||||
_registerManager.RegisteringPlayers[(raidId, playerId)] = rosterPlayer with {Role = selectedRole};
|
||||
|
||||
await RespondAsync();
|
||||
}
|
||||
// The user is not currently registering, wonder how he got here then
|
||||
else
|
||||
{
|
||||
await RespondAsync(ephemeral: true,
|
||||
embed: EmbedUtils.ErrorEmbed("You are not registering for this raid :thinking:").Build());
|
||||
}
|
||||
}
|
||||
|
||||
[ComponentInteraction("raid player_join:*:*", ignoreGroupNames: true)]
|
||||
public async Task PlayerJoinNonSubstitute(ulong raidId, ulong playerId)
|
||||
{
|
||||
await PlayerJoin(raidId, playerId, false);
|
||||
}
|
||||
|
||||
[ComponentInteraction("raid player_join_substitute:*:*", ignoreGroupNames: true)]
|
||||
public async Task PlayerJoinSubstitute(ulong raidId, ulong playerId)
|
||||
{
|
||||
await PlayerJoin(raidId, playerId, true);
|
||||
}
|
||||
|
||||
private async Task PlayerJoin(ulong raidId, ulong playerId, bool substitute)
|
||||
{
|
||||
// Check if player is registering
|
||||
if (!_registerManager.RegisteringPlayers.TryGetValue((raidId, playerId), out var rosterPlayer))
|
||||
{
|
||||
await RespondAsync(ephemeral: true,
|
||||
embed: EmbedUtils.ErrorEmbed("You are not registering for this raid :thinking:").Build());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we need to ask FC
|
||||
if (!_playerInfos.TryGetPlayerInfo(playerId, out var playerInfo))
|
||||
{
|
||||
await Context.Interaction.RespondWithModalAsync<FcModal>($"raid modal_fc:{raidId}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Player already has FC registered but it's outdated
|
||||
if (playerInfo.IsFcUpdateRequired)
|
||||
{
|
||||
await Context.Interaction.RespondWithModalAsync<FcModal>($"raid modal_fc:{raidId}",
|
||||
modifyModal: m =>
|
||||
m.UpdateTextInput("fc", component => component.Value = playerInfo.Fc.FormatSpaced())
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Register user for raid
|
||||
await RegisterPlayer(raidId, rosterPlayer with { Fc = rosterPlayer.Fc, Substitute = substitute});
|
||||
}
|
||||
|
||||
[ModalInteraction("raid modal_fc:*", ignoreGroupNames: true)]
|
||||
public async Task ModalFcSubmit(ulong raidId, FcModal modal)
|
||||
{
|
||||
var playerId = Context.User.Id;
|
||||
_logger.LogTrace("Received modal FC modal from {User} with value: {Fc}", Context.User.Username, modal.Fc);
|
||||
|
||||
// Check if player is registering
|
||||
if (!_registerManager.RegisteringPlayers.TryGetValue((raidId, playerId), out var rosterPlayer))
|
||||
{
|
||||
await RespondAsync(ephemeral: true,
|
||||
embed: EmbedUtils.ErrorEmbed("You are not registering for this raid :thinking:").Build());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var fcInput = modal.Fc.Replace(" ", "");
|
||||
|
||||
if (!uint.TryParse(fcInput, out var fc))
|
||||
{
|
||||
await RespondAsync(ephemeral: true,
|
||||
embed: EmbedUtils.ErrorEmbed("Invalid fc, try registering again").Build());
|
||||
|
||||
_registerManager.RegisteringPlayers.Remove((raidId, playerId));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_playerInfos.UpdatePlayerInfo(new PlayerInfo(playerId, fc));
|
||||
|
||||
await RegisterPlayer(raidId, rosterPlayer with { Fc = fc });
|
||||
}
|
||||
|
||||
private async Task RegisterPlayer(ulong raidId, RosterPlayer rosterPlayer)
|
||||
{
|
||||
// Check if raid exists
|
||||
if (!_raids.TryGetRaid(raidId, out var raid))
|
||||
{
|
||||
await RespondAsync(
|
||||
ephemeral: true,
|
||||
embed: EmbedUtils.InfoEmbed("This raid does not exist").Build());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_registerManager.RegisteringPlayers[(raidId, Context.User.Id)] = rosterPlayer;
|
||||
|
||||
// Player is already registered, update info
|
||||
if (!raid.AddPlayer(rosterPlayer))
|
||||
{
|
||||
raid.UpdatePlayer(rosterPlayer);
|
||||
|
||||
await UpdateRaidRosterEmbed(raid);
|
||||
// await RespondAsync(
|
||||
// ephemeral: true,
|
||||
// embed: EmbedUtils.SuccessEmbed($"Successfully update you're role to: {rosterPlayer.Role}").Build());
|
||||
await RespondAsync();
|
||||
}
|
||||
// It's a new player
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("User {User} joined raid {Raid}", Context.User.Username, raid);
|
||||
|
||||
await UpdateRaidRosterEmbed(raid);
|
||||
await RespondAsync(
|
||||
ephemeral: true,
|
||||
embed: EmbedUtils.SuccessEmbed($"Successfully joined the raid as {raid.GetPlayer(rosterPlayer.Id).Role}").Build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[ComponentInteraction("raid raid_leave:*", ignoreGroupNames: true)]
|
||||
public async Task Leave(ulong raidId)
|
||||
{
|
||||
if (!_raidsRepository.TryGetRaid(raidId, out var raid))
|
||||
if (!_raids.TryGetRaid(raidId, out var raid))
|
||||
{
|
||||
await RespondAsync(
|
||||
ephemeral: true,
|
||||
@@ -155,7 +296,7 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
|
||||
}
|
||||
}
|
||||
|
||||
private ComponentBuilder RaidComponents(ulong raidId)
|
||||
private static ComponentBuilder RaidComponents(ulong raidId)
|
||||
{
|
||||
return new ComponentBuilder()
|
||||
.AddRow(new ActionRowBuilder()
|
||||
@@ -171,4 +312,38 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private ComponentBuilder PlayerRoleComponent(Raid raid, IGuildUser user)
|
||||
{
|
||||
var select = new SelectMenuBuilder()
|
||||
.WithPlaceholder(PlayerRole.Dps.ToString())
|
||||
.WithCustomId($"raid player_select_role:{raid.Id}:{user.Id}")
|
||||
.WithMinValues(1)
|
||||
.WithMaxValues(1);
|
||||
|
||||
foreach (var role in Enum.GetValues<PlayerRole>())
|
||||
{
|
||||
// TODO add emote
|
||||
select.AddOption(role.ToString(), role.ToString());
|
||||
}
|
||||
|
||||
return new ComponentBuilder()
|
||||
.AddRow(new ActionRowBuilder()
|
||||
.WithSelectMenu(select)
|
||||
)
|
||||
.AddRow(new ActionRowBuilder()
|
||||
.WithButton("Join", $"raid player_join:{raid.Id}:{user.Id}")
|
||||
.WithButton("Join substitute", $"raid player_join_substitute:{raid.Id}:{user.Id}")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class FcModal : IModal
|
||||
{
|
||||
public string Title => "Please enter your FC";
|
||||
|
||||
[NotNull]
|
||||
[InputLabel("FC")]
|
||||
[ModalTextInput("fc", placeholder: "30 000", minLength: 1, maxLength: 7)]
|
||||
public string? Fc { get; set; }
|
||||
}
|
||||
7
Cocotte/Modules/Raids/RaidRegisterManager.cs
Normal file
7
Cocotte/Modules/Raids/RaidRegisterManager.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Cocotte.Modules.Raids;
|
||||
|
||||
public class RaidRegisterManager
|
||||
{
|
||||
public IDictionary<(ulong raidId, ulong playerId), RosterPlayer> RegisteringPlayers =
|
||||
new Dictionary<(ulong raidId, ulong playerId), RosterPlayer>();
|
||||
}
|
||||
@@ -14,6 +14,18 @@ public class RosterManager
|
||||
return _players.TryAdd(rosterPlayer.Id, rosterPlayer);
|
||||
}
|
||||
|
||||
public bool UpdatePlayer(RosterPlayer rosterPlayer)
|
||||
{
|
||||
if (!_players.ContainsKey(rosterPlayer.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_players[rosterPlayer.Id] = rosterPlayer;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemovePlayer(ulong id)
|
||||
{
|
||||
return _players.Remove(id);
|
||||
@@ -23,4 +35,9 @@ public class RosterManager
|
||||
{
|
||||
return _players[id];
|
||||
}
|
||||
|
||||
public bool ContainsPlayer(ulong userId)
|
||||
{
|
||||
return _players.ContainsKey(userId);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Cocotte.Modules.Raids;
|
||||
|
||||
public record RosterPlayer(ulong Id, string Name, PlayerRole Role, int Fc, bool Substitute = false)
|
||||
public record RosterPlayer(ulong Id, string Name, PlayerRole Role = PlayerRole.Dps, uint Fc = 0, bool Substitute = false)
|
||||
{
|
||||
public int RosterNumber { get; set; }
|
||||
|
||||
|
||||
@@ -34,10 +34,12 @@ IHost host = Host.CreateDefaultBuilder(args)
|
||||
|
||||
// Data
|
||||
services.AddSingleton<IRaidsRepository, MemoryRaidRepository>();
|
||||
services.AddSingleton<IPlayerInfosRepository, MemoryPlayerInfosRepository>();
|
||||
services.AddSingleton<RolesOptions>();
|
||||
|
||||
// Raids
|
||||
services.AddSingleton<RaidFormatter>();
|
||||
services.AddSingleton<RaidRegisterManager>();
|
||||
|
||||
// Custom
|
||||
services.AddSingleton<SharedCounter>();
|
||||
|
||||
34
Cocotte/Utils/ModalExtensions.cs
Normal file
34
Cocotte/Utils/ModalExtensions.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Discord;
|
||||
|
||||
namespace Cocotte.Utils;
|
||||
|
||||
public static class ModalExtensions
|
||||
{
|
||||
public static ModalBuilder UpdateTextInput(this ModalBuilder modal, string customId, Action<TextInputBuilder> inputUpdater)
|
||||
{
|
||||
var components = modal.Components.ActionRows.SelectMany(r => r.Components).OfType<TextInputComponent>();
|
||||
var component = components.First(c => c.CustomId == customId);
|
||||
|
||||
var builder = new TextInputBuilder
|
||||
{
|
||||
CustomId = customId,
|
||||
Label = component.Label,
|
||||
MaxLength = component.MaxLength,
|
||||
MinLength = component.MinLength,
|
||||
Placeholder = component.Placeholder,
|
||||
Required = component.Required,
|
||||
Style = component.Style,
|
||||
Value = component.Value
|
||||
};
|
||||
|
||||
inputUpdater(builder);
|
||||
|
||||
foreach (var row in modal.Components.ActionRows.Where(row => row.Components.Any(c => c.CustomId == customId)))
|
||||
{
|
||||
row.Components.RemoveAll(c => c.CustomId == customId);
|
||||
row.AddComponent(builder.Build());
|
||||
}
|
||||
|
||||
return modal;
|
||||
}
|
||||
}
|
||||
38
Cocotte/Utils/NumberUtils.cs
Normal file
38
Cocotte/Utils/NumberUtils.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace Cocotte.Utils;
|
||||
|
||||
public static class NumberUtils
|
||||
{
|
||||
public static string FormatSpaced<T>(this INumber<T> number) where T : INumber<T>?
|
||||
{
|
||||
var stringNumber = number.ToString(null, null);
|
||||
|
||||
Span<char> result = stackalloc char[stringNumber.Length + stringNumber.Length / 3];
|
||||
int resultOffset = 0;
|
||||
|
||||
for (int i = 0; i < stringNumber.Length; i++)
|
||||
{
|
||||
// Add a space
|
||||
if (i > 2 && (i + 1) % 3 == 1)
|
||||
{
|
||||
result[resultOffset] = ' ';
|
||||
resultOffset++;
|
||||
}
|
||||
|
||||
result[resultOffset] = stringNumber[^(i + 1)];
|
||||
resultOffset++;
|
||||
}
|
||||
|
||||
var realResult = result[..resultOffset];
|
||||
Span<char> reversed = stackalloc char[resultOffset];
|
||||
|
||||
for (int i = 0; i < reversed.Length; i++)
|
||||
{
|
||||
reversed[i] = realResult[^(i + 1)];
|
||||
}
|
||||
|
||||
return reversed.ToString();
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ public static class RaidExtensions
|
||||
{
|
||||
public static void AddTestPlayers(this Raid raid)
|
||||
{
|
||||
raid.AddPlayer(0, "YamaRaja", PlayerRole.Healer, 30000, false);
|
||||
raid.AddPlayer(1, "Zaku", PlayerRole.Dps, 40000, false);
|
||||
raid.AddPlayer(2, "Juchi", PlayerRole.Tank, 40000, false);
|
||||
raid.AddPlayer(3, "Akeno", PlayerRole.Dps, 40000, true);
|
||||
raid.AddPlayer(new RosterPlayer(0, "YamaRaja", PlayerRole.Healer, 30000, false));
|
||||
raid.AddPlayer(new RosterPlayer(1, "Zaku", PlayerRole.Dps, 40000, false));
|
||||
raid.AddPlayer(new RosterPlayer(2, "Juchi", PlayerRole.Tank, 40000, false));
|
||||
raid.AddPlayer(new RosterPlayer(3, "Akeno", PlayerRole.Dps, 40000, true));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user