Started work on the xp system

This commit is contained in:
Master Kwoth 2017-08-22 05:48:45 +02:00
parent e5609a0708
commit 088d95340f
24 changed files with 2676 additions and 6 deletions

View File

@ -17,6 +17,8 @@
CurrencyDropAmountMax, CurrencyDropAmountMax,
MinimumBetAmount, MinimumBetAmount,
TriviaCurrencyReward, TriviaCurrencyReward,
XpPerMessage,
XpMinutesTimeout,
//ErrorColor, //after i fix the nadekobot.cs static variables //ErrorColor, //after i fix the nadekobot.cs static variables
//OkColor //OkColor

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class xpstuff : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "XpMinutesTimeout",
table: "BotConfig",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "XpPerMessage",
table: "BotConfig",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "UserXpStats",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildId = table.Column<ulong>(nullable: false),
NotifyOnLevelUp = table.Column<bool>(nullable: false),
UserId = table.Column<ulong>(nullable: false),
Xp = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserXpStats", x => x.Id);
});
migrationBuilder.CreateTable(
name: "XpSettings",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: false),
NotifyMessage = table.Column<string>(nullable: true),
ServerExcluded = table.Column<bool>(nullable: false),
XpRoleRewardExclusive = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_XpSettings", x => x.Id);
table.ForeignKey(
name: "FK_XpSettings_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ExcludedItem",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
ItemId = table.Column<ulong>(nullable: false),
ItemType = table.Column<int>(nullable: false),
XpSettingsId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ExcludedItem", x => x.Id);
table.ForeignKey(
name: "FK_ExcludedItem_XpSettings_XpSettingsId",
column: x => x.XpSettingsId,
principalTable: "XpSettings",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "XpRoleReward",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
Level = table.Column<int>(nullable: false),
RoleId = table.Column<ulong>(nullable: false),
XpSettingsId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_XpRoleReward", x => x.Id);
table.ForeignKey(
name: "FK_XpRoleReward_XpSettings_XpSettingsId",
column: x => x.XpSettingsId,
principalTable: "XpSettings",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_ExcludedItem_XpSettingsId",
table: "ExcludedItem",
column: "XpSettingsId");
migrationBuilder.CreateIndex(
name: "IX_UserXpStats_UserId_GuildId",
table: "UserXpStats",
columns: new[] { "UserId", "GuildId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_XpRoleReward_XpSettingsId",
table: "XpRoleReward",
column: "XpSettingsId");
migrationBuilder.CreateIndex(
name: "IX_XpSettings_GuildConfigId",
table: "XpSettings",
column: "GuildConfigId",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ExcludedItem");
migrationBuilder.DropTable(
name: "UserXpStats");
migrationBuilder.DropTable(
name: "XpRoleReward");
migrationBuilder.DropTable(
name: "XpSettings");
migrationBuilder.DropColumn(
name: "XpMinutesTimeout",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "XpPerMessage",
table: "BotConfig");
}
}
}

View File

@ -183,6 +183,10 @@ namespace NadekoBot.Migrations
b.Property<int>("TriviaCurrencyReward"); b.Property<int>("TriviaCurrencyReward");
b.Property<int>("XpMinutesTimeout");
b.Property<int>("XpPerMessage");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("BotConfig"); b.ToTable("BotConfig");
@ -445,6 +449,26 @@ namespace NadekoBot.Migrations
b.ToTable("EightBallResponses"); b.ToTable("EightBallResponses");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("ItemId");
b.Property<int>("ItemType");
b.Property<int?>("XpSettingsId");
b.HasKey("Id");
b.HasIndex("XpSettingsId");
b.ToTable("ExcludedItem");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -1252,6 +1276,29 @@ namespace NadekoBot.Migrations
b.ToTable("PokeGame"); b.ToTable("PokeGame");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("GuildId");
b.Property<bool>("NotifyOnLevelUp");
b.Property<ulong>("UserId");
b.Property<int>("Xp");
b.HasKey("Id");
b.HasIndex("UserId", "GuildId")
.IsUnique();
b.ToTable("UserXpStats");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -1393,6 +1440,49 @@ namespace NadekoBot.Migrations
b.ToTable("WarningPunishment"); b.ToTable("WarningPunishment");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int>("Level");
b.Property<ulong>("RoleId");
b.Property<int?>("XpSettingsId");
b.HasKey("Id");
b.HasIndex("XpSettingsId");
b.ToTable("XpRoleReward");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int>("GuildConfigId");
b.Property<string>("NotifyMessage");
b.Property<bool>("ServerExcluded");
b.Property<bool>("XpRoleRewardExclusive");
b.HasKey("Id");
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("XpSettings");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
{ {
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
@ -1470,6 +1560,13 @@ namespace NadekoBot.Migrations
.HasForeignKey("BotConfigId"); .HasForeignKey("BotConfigId");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.XpSettings")
.WithMany("ExclusionList")
.HasForeignKey("XpSettingsId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
{ {
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
@ -1707,6 +1804,21 @@ namespace NadekoBot.Migrations
.WithMany("WarnPunishments") .WithMany("WarnPunishments")
.HasForeignKey("GuildConfigId"); .HasForeignKey("GuildConfigId");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.XpSettings")
.WithMany("RoleRewards")
.HasForeignKey("XpSettingsId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
.WithOne("XpSettings")
.HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId")
.OnDelete(DeleteBehavior.Cascade);
});
} }
} }
} }

View File

@ -28,7 +28,7 @@ namespace NadekoBot.Modules.Games.Services
public readonly ImmutableArray<string> EightBallResponses; public readonly ImmutableArray<string> EightBallResponses;
private readonly Timer _t; private readonly Timer _t;
private readonly DiscordSocketClient _client; private readonly CommandHandler _cmd;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
private readonly IImagesService _images; private readonly IImagesService _images;
private readonly Logger _log; private readonly Logger _log;
@ -38,11 +38,11 @@ namespace NadekoBot.Modules.Games.Services
public List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>(); public List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();
public GamesService(DiscordSocketClient client, IBotConfigProvider bc, IEnumerable<GuildConfig> gcs, public GamesService(CommandHandler cmd, IBotConfigProvider bc, IEnumerable<GuildConfig> gcs,
NadekoStrings strings, IImagesService images, CommandHandler cmdHandler) NadekoStrings strings, IImagesService images, CommandHandler cmdHandler)
{ {
_bc = bc; _bc = bc;
_client = client; _cmd = cmd;
_strings = strings; _strings = strings;
_images = images; _images = images;
_cmdHandler = cmdHandler; _cmdHandler = cmdHandler;
@ -59,7 +59,7 @@ namespace NadekoBot.Modules.Games.Services
}, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1)); }, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1));
//plantpick //plantpick
client.MessageReceived += PotentialFlowerGeneration; _cmd.OnMessageNoTrigger += PotentialFlowerGeneration;
GenerationChannels = new ConcurrentHashSet<ulong>(gcs GenerationChannels = new ConcurrentHashSet<ulong>(gcs
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId))); .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
@ -102,7 +102,7 @@ namespace NadekoBot.Modules.Games.Services
private string GetText(ITextChannel ch, string key, params object[] rep) private string GetText(ITextChannel ch, string key, params object[] rep)
=> _strings.GetText(key, ch.GuildId, "Games".ToLowerInvariant(), rep); => _strings.GetText(key, ch.GuildId, "Games".ToLowerInvariant(), rep);
private Task PotentialFlowerGeneration(SocketMessage imsg) private Task PotentialFlowerGeneration(IUserMessage imsg)
{ {
var msg = imsg as SocketUserMessage; var msg = imsg as SocketUserMessage;
if (msg == null || msg.Author.IsBot) if (msg == null || msg.Author.IsBot)

View File

@ -0,0 +1,34 @@
using NadekoBot.Modules.Xp.Services;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Xp.Extensions
{
public static class Extensions
{
public static (int Level, int LevelXp, int LevelRequiredXp) GetLevelData(this UserXpStats stats)
{
var baseXp = XpService.XP_REQUIRED_LVL_1;
var required = baseXp;
var totalXp = 0;
var lvl = 1;
while (true)
{
required = (int)(baseXp + baseXp / 4.0 * (lvl - 1));
if (required + totalXp > stats.Xp)
break;
totalXp += required;
lvl++;
}
return (lvl - 1, stats.Xp - totalXp, required);
}
}
}

View File

@ -0,0 +1,18 @@
namespace NadekoBot.Modules.Xp.Services
{
public class UserCacheItem
{
public ulong UserId { get; set; }
public ulong GuildId { get; set; }
public override int GetHashCode()
{
return UserId.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is UserCacheItem uci && uci.UserId == UserId;
}
}
}

View File

@ -0,0 +1,229 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Common.Collections;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Xp.Services
{
public class XpService : INService
{
private readonly DbService _db;
private readonly CommandHandler _cmd;
private readonly IBotConfigProvider _bc;
private readonly Logger _log;
public const int XP_REQUIRED_LVL_1 = 36;
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedRoles
= new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>();
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedChannels
= new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>();
private readonly ConcurrentHashSet<ulong> _excludedServers = new ConcurrentHashSet<ulong>();
private readonly ConcurrentHashSet<ulong> _rewardedUsers = new ConcurrentHashSet<ulong>();
private readonly ConcurrentQueue<UserCacheItem> _addMessageXp = new ConcurrentQueue<UserCacheItem>();
private readonly Timer updateXpTimer;
private readonly Timer clearRewardedUsersTimer;
public XpService(CommandHandler cmd, IBotConfigProvider bc,
IEnumerable<GuildConfig> allGuildConfigs, DbService db)
{
_db = db;
_cmd = cmd;
_bc = bc;
_log = LogManager.GetCurrentClassLogger();
_cmd.OnMessageNoTrigger += _cmd_OnMessageNoTrigger;
updateXpTimer = new Timer(_ =>
{
using (var uow = _db.UnitOfWork)
{
while (_addMessageXp.TryDequeue(out var usr))
{
var usrObj = uow.Xp.GetOrCreateUser(usr.GuildId, usr.UserId);
usrObj.Xp += _bc.BotConfig.XpPerMessage;
}
uow.Complete();
}
}, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
clearRewardedUsersTimer = new Timer(_ =>
{
_rewardedUsers.Clear();
}, null, TimeSpan.FromSeconds(bc.BotConfig.XpMinutesTimeout), TimeSpan.FromSeconds(bc.BotConfig.XpMinutesTimeout));
}
private Task _cmd_OnMessageNoTrigger(IUserMessage arg)
{
if (!(arg.Author is SocketGuildUser user) || user.IsBot)
return Task.CompletedTask;
var _ = Task.Run(() =>
{
if (!SetUserRewarded(user.Id))
return;
if (_excludedChannels.TryGetValue(user.Guild.Id, out var chans) &&
chans.Contains(arg.Channel.Id))
return;
if (_excludedServers.Contains(user.Guild.Id))
return;
if (_excludedRoles.TryGetValue(user.Guild.Id, out var roles) &&
user.Roles.Any(x => roles.Contains(x.Id)))
return;
_log.Info("Adding {0} xp to {1} on {2} server", _bc.BotConfig.XpPerMessage, user.ToString(), user.Guild.Name);
_addMessageXp.Enqueue(new UserCacheItem { GuildId = user.Guild.Id, UserId = user.Id });
});
return Task.CompletedTask;
}
public bool IsServerExcluded(ulong id)
{
return _excludedServers.Contains(id);
}
public IEnumerable<ulong> GetExcludedRoles(ulong id)
{
if (_excludedRoles.TryGetValue(id, out var val))
return val.ToArray();
return Enumerable.Empty<ulong>();
}
public IEnumerable<ulong> GetExcludedChannels(ulong id)
{
if (_excludedChannels.TryGetValue(id, out var val))
return val.ToArray();
return Enumerable.Empty<ulong>();
}
private bool SetUserRewarded(ulong userId)
{
return _rewardedUsers.Add(userId);
}
public UserXpStats GetUserStats(ulong guildId, ulong userId)
{
UserXpStats user;
using (var uow = _db.UnitOfWork)
{
user = uow.Xp.GetOrCreateUser(guildId, userId);
}
return user;
}
public string GenerateXpBar(int currentXp, int requiredXp)
{
//todo
return $"{currentXp}/{requiredXp}";
}
//todo exclude in database
public bool ToggleExcludeServer(ulong id)
{
using (var uow = _db.UnitOfWork)
{
var xpSetting = uow.GuildConfigs.XpSettingsFor(id);
if (_excludedServers.Add(id))
{
xpSetting.ServerExcluded = true;
uow.Complete();
return true;
}
_excludedServers.TryRemove(id);
xpSetting.ServerExcluded = false;
uow.Complete();
return false;
}
}
public bool ToggleExcludeRole(ulong guildId, ulong rId)
{
var roles = _excludedRoles.GetOrAdd(guildId, _ => new ConcurrentHashSet<ulong>());
using (var uow = _db.UnitOfWork)
{
var xpSetting = uow.GuildConfigs.XpSettingsFor(guildId);
var excludeObj = new ExcludedItem
{
ItemId = rId,
ItemType = ExcludedItemType.Role,
};
if (roles.Add(rId))
{
if (xpSetting.ExclusionList.Add(excludeObj))
{
uow.Complete();
}
return true;
}
else
{
roles.TryRemove(rId);
if (xpSetting.ExclusionList.Remove(excludeObj))
{
uow.Complete();
}
return false;
}
}
}
public bool ToggleExcludeChannel(ulong guildId, ulong chId)
{
var channels = _excludedChannels.GetOrAdd(guildId, _ => new ConcurrentHashSet<ulong>());
using (var uow = _db.UnitOfWork)
{
var xpSetting = uow.GuildConfigs.XpSettingsFor(guildId);
var excludeObj = new ExcludedItem
{
ItemId = chId,
ItemType = ExcludedItemType.Channel,
};
if (channels.Add(chId))
{
if (xpSetting.ExclusionList.Add(excludeObj))
{
uow.Complete();
}
return true;
}
else
{
channels.TryRemove(chId);
if (xpSetting.ExclusionList.Remove(excludeObj))
{
uow.Complete();
}
return false;
}
}
}
}
}

View File

@ -0,0 +1,105 @@
using Discord;
using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Xp.Extensions;
using NadekoBot.Modules.Xp.Services;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Xp
{
public partial class Xp : NadekoTopLevelModule<XpService>
{
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Experience(IUser user = null)
{
user = user ?? Context.User;
await Task.Delay(64).ConfigureAwait(false); // wait a bit in case user got XP with this message
var stats = _service.GetUserStats(Context.Guild.Id, user.Id);
var levelData = stats.GetLevelData();
var xpBarStr = _service.GenerateXpBar(levelData.LevelXp, levelData.LevelRequiredXp);
await Context.Channel.EmbedAsync(new EmbedBuilder()
.WithTitle(user.ToString())
//.AddField(GetText("server_level"), stats.ServerLevel.ToString(), true)
.AddField(GetText("level"), levelData.Level.ToString(), true)
//.AddField(GetText("club"), stats.ClubName ?? "-", true)
.AddField(GetText("xp"), xpBarStr, false)
.WithOkColor())
.ConfigureAwait(false);
}
public enum Server { Server };
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//[OwnerOnly]
//[Priority(1)]
//public async Task XpExclude(Server _, IGuild guild)
//{
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task XpExclude(Server _)
{
var ex = _service.ToggleExcludeServer(Context.Guild.Id);
await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(Context.Guild.ToString())).ConfigureAwait(false);
}
public enum Role { Role };
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireContext(ContextType.Guild)]
public async Task XpExclude(Role _, [Remainder] IRole role)
{
var ex = _service.ToggleExcludeRole(Context.Guild.Id, role.Id);
await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(role.ToString())).ConfigureAwait(false);
}
public enum Channel { Channel };
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageChannels)]
[RequireContext(ContextType.Guild)]
public async Task XpExclude(Channel _, [Remainder] ITextChannel channel)
{
var ex = _service.ToggleExcludeChannel(Context.Guild.Id, channel.Id);
await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(channel.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task XpExclusionList()
{
var serverExcluded = _service.IsServerExcluded(Context.Guild.Id);
var roles = _service.GetExcludedRoles(Context.Guild.Id)
.Select(x => Context.Guild.GetRole(x)?.Name)
.Where(x => x != null);
var chans = (await Task.WhenAll(_service.GetExcludedChannels(Context.Guild.Id)
.Select(x => Context.Guild.GetChannelAsync(x)))
.ConfigureAwait(false))
.Where(x => x != null)
.Select(x => x.Name);
var embed = new EmbedBuilder()
.WithTitle(GetText("exclusion_list"))
.WithDescription((serverExcluded ? GetText("server_is_excluded") : GetText("server_is_not_excluded")))
.AddField(GetText("excluded_roles"), roles.Any() ? string.Join("\n", roles) : "-", false)
.AddField(GetText("excluded_channels"), chans.Any() ? string.Join("\n", chans) : "-", false)
.WithOkColor();
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
}
}

View File

@ -91,4 +91,10 @@
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" /> <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="Resources\CommandStrings.Designer.cs">
<SubType>Designer</SubType>
</Compile>
</ItemGroup>
</Project> </Project>

View File

@ -3573,4 +3573,31 @@
<data name="nsfwtagblacklist_desc" xml:space="preserve"> <data name="nsfwtagblacklist_desc" xml:space="preserve">
<value>Toggles whether the tag is blacklisted or not in nsfw searches. Provide no parameters to see the list of blacklisted tags.</value> <value>Toggles whether the tag is blacklisted or not in nsfw searches. Provide no parameters to see the list of blacklisted tags.</value>
</data> </data>
<data name="experience_cmd" xml:space="preserve">
<value>experience xp</value>
</data>
<data name="experience_usage" xml:space="preserve">
<value>`{0}xp`</value>
</data>
<data name="experience_desc" xml:space="preserve">
<value>Shows your xp stats. Specify the user to show that user's stats instead.</value>
</data>
<data name="xpexclusionlist_cmd" xml:space="preserve">
<value>xpexclusionlist xpexl</value>
</data>
<data name="xpexclusionlist_usage" xml:space="preserve">
<value>`{0}xpexl`</value>
</data>
<data name="xpexclusionlist_desc" xml:space="preserve">
<value>Shows the roles and channels excluded from the XP system on this server, as well as whether the whole server is excluded.</value>
</data>
<data name="xpexclude_cmd" xml:space="preserve">
<value>xpexclude xpex</value>
</data>
<data name="xpexclude_usage" xml:space="preserve">
<value>`{0}xpex User @b1nzy` `{0}xpex Server`</value>
</data>
<data name="xpexclude_desc" xml:space="preserve">
<value>Exclude a user or a role from the xp system, or whole current server.</value>
</data>
</root> </root>

View File

@ -24,6 +24,7 @@ namespace NadekoBot.Services.Database
IWaifuRepository Waifus { get; } IWaifuRepository Waifus { get; }
IDiscordUserRepository DiscordUsers { get; } IDiscordUserRepository DiscordUsers { get; }
IWarningsRepository Warnings { get; } IWarningsRepository Warnings { get; }
IXpRepository Xp { get; }
int Complete(); int Complete();
Task<int> CompleteAsync(); Task<int> CompleteAsync();

View File

@ -68,6 +68,8 @@ Nadeko Support Server: https://discord.gg/nadekobot";
public int PermissionVersion { get; set; } public int PermissionVersion { get; set; }
public string DefaultPrefix { get; set; } = "."; public string DefaultPrefix { get; set; } = ".";
public bool CustomReactionsStartWith { get; set; } = false; public bool CustomReactionsStartWith { get; set; } = false;
public int XpPerMessage { get; set; } = 3;
public int XpMinutesTimeout { get; set; } = 5;
} }
public class BlockedCmdOrMdl : DbEntity public class BlockedCmdOrMdl : DbEntity

View File

@ -86,6 +86,8 @@ namespace NadekoBot.Services.Database.Models
public StreamRoleSettings StreamRole { get; set; } public StreamRoleSettings StreamRole { get; set; }
public XpSettings XpSettings { get; set; }
//public List<ProtectionIgnoredChannel> ProtectionIgnoredChannels { get; set; } = new List<ProtectionIgnoredChannel>(); //public List<ProtectionIgnoredChannel> ProtectionIgnoredChannels { get; set; } = new List<ProtectionIgnoredChannel>();
} }

View File

@ -0,0 +1,10 @@
namespace NadekoBot.Services.Database.Models
{
public class UserXpStats : DbEntity
{
public ulong UserId { get; set; }
public ulong GuildId { get; set; }
public int Xp { get; set; }
public bool NotifyOnLevelUp { get; set; }
}
}

View File

@ -0,0 +1,50 @@
using System.Collections.Generic;
namespace NadekoBot.Services.Database.Models
{
public class XpSettings : DbEntity
{
public int GuildConfigId { get; set; }
public GuildConfig GuildConfig { get; set; }
public HashSet<XpRoleReward> RoleRewards { get; set; } = new HashSet<XpRoleReward>();
public bool XpRoleRewardExclusive { get; set; }
public string NotifyMessage { get; set; } = "Congratulations {0}! You have reached level {1}!";
public HashSet<ExcludedItem> ExclusionList { get; set; } = new HashSet<ExcludedItem>();
public bool ServerExcluded { get; set; }
}
public enum ExcludedItemType { Channel, Role }
public class XpRoleReward : DbEntity
{
public int Level { get; set; }
public ulong RoleId { get; set; }
public override int GetHashCode()
{
return Level.GetHashCode() ^ RoleId.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is XpRoleReward xrr && xrr.Level == Level && xrr.RoleId == RoleId;
}
}
public class ExcludedItem : DbEntity
{
public ulong ItemId { get; set; }
public ExcludedItemType ItemType { get; set; }
public override int GetHashCode()
{
return ItemId.GetHashCode() ^ ItemType.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is ExcludedItem ei && ei.ItemId == ItemId && ei.ItemType == ItemType;
}
}
}

View File

@ -43,6 +43,7 @@ namespace NadekoBot.Services.Database
public DbSet<UserPokeTypes> PokeGame { get; set; } public DbSet<UserPokeTypes> PokeGame { get; set; }
public DbSet<WaifuUpdate> WaifuUpdates { get; set; } public DbSet<WaifuUpdate> WaifuUpdates { get; set; }
public DbSet<Warning> Warnings { get; set; } public DbSet<Warning> Warnings { get; set; }
public DbSet<UserXpStats> UserXpStats { get; set; }
//logging //logging
public DbSet<LogSetting> LogSettings { get; set; } public DbSet<LogSetting> LogSettings { get; set; }
@ -292,6 +293,18 @@ namespace NadekoBot.Services.Database
pr.HasIndex(x => x.UserId) pr.HasIndex(x => x.UserId)
.IsUnique(); .IsUnique();
#endregion #endregion
#region XpStatas
modelBuilder.Entity<UserXpStats>()
.HasIndex(x => new { x.UserId, x.GuildId })
.IsUnique();
#endregion
#region XpSettings
modelBuilder.Entity<XpSettings>()
.HasOne(x => x.GuildConfig)
.WithOne(x => x.XpSettings);
#endregion
} }
} }
} }

View File

@ -16,5 +16,6 @@ namespace NadekoBot.Services.Database.Repositories
void SetCleverbotEnabled(ulong id, bool cleverbotEnabled); void SetCleverbotEnabled(ulong id, bool cleverbotEnabled);
IEnumerable<GuildConfig> Permissionsv2ForAll(List<long> include); IEnumerable<GuildConfig> Permissionsv2ForAll(List<long> include);
GuildConfig GcWithPermissionsv2For(ulong guildId); GuildConfig GcWithPermissionsv2For(ulong guildId);
XpSettings XpSettingsFor(ulong guildId);
} }
} }

View File

@ -0,0 +1,9 @@
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services.Database.Repositories
{
public interface IXpRepository : IRepository<UserXpStats>
{
UserXpStats GetOrCreateUser(ulong guildId, ulong userId);
}
}

View File

@ -189,5 +189,19 @@ namespace NadekoBot.Services.Database.Repositories.Impl
conf.CleverbotEnabled = cleverbotEnabled; conf.CleverbotEnabled = cleverbotEnabled;
} }
public XpSettings XpSettingsFor(ulong guildId)
{
var gc = For(guildId,
set => set.Include(x => x.XpSettings)
.ThenInclude(x => x.RoleRewards)
.Include(x => x.XpSettings)
.ThenInclude(x => x.ExclusionList));
if (gc.XpSettings == null)
gc.XpSettings = new XpSettings();
return gc.XpSettings;
}
} }
} }

View File

@ -0,0 +1,31 @@
using NadekoBot.Services.Database.Models;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace NadekoBot.Services.Database.Repositories.Impl
{
public class XpRepository : Repository<UserXpStats>, IXpRepository
{
public XpRepository(DbContext context) : base(context)
{
}
public UserXpStats GetOrCreateUser(ulong guildId, ulong userId)
{
var usr = _set.FirstOrDefault(x => x.UserId == userId);
if (usr == null)
{
_context.Add(usr = new UserXpStats()
{
Xp = 0,
UserId = userId,
NotifyOnLevelUp = false,
GuildId = guildId,
});
}
return usr;
}
}
}

View File

@ -57,6 +57,9 @@ namespace NadekoBot.Services.Database
private IWarningsRepository _warnings; private IWarningsRepository _warnings;
public IWarningsRepository Warnings => _warnings ?? (_warnings = new WarningsRepository(_context)); public IWarningsRepository Warnings => _warnings ?? (_warnings = new WarningsRepository(_context));
private IXpRepository _xp;
public IXpRepository Xp => _xp ?? (_xp = new XpRepository(_context));
public UnitOfWork(NadekoContext context) public UnitOfWork(NadekoContext context)
{ {
_context = context; _context = context;

View File

@ -122,6 +122,18 @@ namespace NadekoBot.Services.Impl
else else
return false; return false;
break; break;
case BotConfigEditType.XpPerMessage:
if (int.TryParse(newValue, out var xp) && xp > 0)
bc.XpPerMessage = xp;
else
return false;
break;
case BotConfigEditType.XpMinutesTimeout:
if (int.TryParse(newValue, out var min) && min > 0)
bc.XpMinutesTimeout = min;
else
return false;
break;
default: default:
return false; return false;
} }

View File

@ -822,5 +822,16 @@
"administration_prefix_current": "Prefix on this server is {0}", "administration_prefix_current": "Prefix on this server is {0}",
"administration_prefix_new": "Changed prefix on this server from {0} to {1}", "administration_prefix_new": "Changed prefix on this server from {0} to {1}",
"administration_defprefix_current": "Default bot prefix is {0}", "administration_defprefix_current": "Default bot prefix is {0}",
"administration_defprefix_new": "Changed Default bot prefix from {0} to {1}" "administration_defprefix_new": "Changed Default bot prefix from {0} to {1}",
"xp_server_level": "Server Level",
"xp_level": "Level",
"xp_club": "Club",
"xp_xp": "Experience",
"xp_excluded": "{0} has been excluded from the XP system on this server.",
"xp_not_excluded": "{0} is no longer excluded from the XP system on this server.",
"xp_exclusion_list": "Exclusion List",
"xp_server_is_excluded": "This server is excluded.",
"xp_server_is_not_excluded": "This server is not excluded.",
"xp_excluded_roles": "Excluded Roles",
"xp_excluded_channels": "Excluded Channels"
} }