Merge branch 'wip' into 1.9

This commit is contained in:
Master Kwoth 2017-11-15 14:27:55 +01:00
commit 1be9bed764
10 changed files with 2255 additions and 64 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class currencylevelupreward : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "XpCurrencyReward",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Amount = table.Column<int>(type: "INTEGER", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
Level = table.Column<int>(type: "INTEGER", nullable: false),
XpSettingsId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_XpCurrencyReward", x => x.Id);
table.ForeignKey(
name: "FK_XpCurrencyReward_XpSettings_XpSettingsId",
column: x => x.XpSettingsId,
principalTable: "XpSettings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_XpCurrencyReward_XpSettingsId",
table: "XpCurrencyReward",
column: "XpSettingsId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "XpCurrencyReward");
}
}
}

View File

@ -1551,6 +1551,26 @@ namespace NadekoBot.Migrations
b.ToTable("WarningPunishment"); b.ToTable("WarningPunishment");
}); });
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpCurrencyReward", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Amount");
b.Property<DateTime?>("DateAdded");
b.Property<int>("Level");
b.Property<int>("XpSettingsId");
b.HasKey("Id");
b.HasIndex("XpSettingsId");
b.ToTable("XpCurrencyReward");
});
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpRoleReward", b => modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpRoleReward", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -1972,6 +1992,14 @@ namespace NadekoBot.Migrations
.HasForeignKey("GuildConfigId"); .HasForeignKey("GuildConfigId");
}); });
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpCurrencyReward", b =>
{
b.HasOne("NadekoBot.Core.Services.Database.Models.XpSettings", "XpSettings")
.WithMany("CurrencyRewards")
.HasForeignKey("XpSettingsId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpRoleReward", b => modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpRoleReward", b =>
{ {
b.HasOne("NadekoBot.Core.Services.Database.Models.XpSettings", "XpSettings") b.HasOne("NadekoBot.Core.Services.Database.Models.XpSettings", "XpSettings")

View File

@ -36,6 +36,8 @@ namespace NadekoBot.Modules.Xp.Services
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
private readonly IDataCache _cache; private readonly IDataCache _cache;
private readonly FontProvider _fonts; private readonly FontProvider _fonts;
private readonly IBotCredentials _creds;
private readonly CurrencyService _cs;
public const int XP_REQUIRED_LVL_1 = 36; public const int XP_REQUIRED_LVL_1 = 36;
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedRoles private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedRoles
@ -60,7 +62,7 @@ namespace NadekoBot.Modules.Xp.Services
public XpService(CommandHandler cmd, IBotConfigProvider bc, public XpService(CommandHandler cmd, IBotConfigProvider bc,
NadekoBot bot, DbService db, NadekoStrings strings, IDataCache cache, NadekoBot bot, DbService db, NadekoStrings strings, IDataCache cache,
FontProvider fonts) FontProvider fonts, IBotCredentials creds, CurrencyService cs)
{ {
_db = db; _db = db;
_cmd = cmd; _cmd = cmd;
@ -70,6 +72,8 @@ namespace NadekoBot.Modules.Xp.Services
_strings = strings; _strings = strings;
_cache = cache; _cache = cache;
_fonts = fonts; _fonts = fonts;
_creds = creds;
_cs = cs;
//load settings //load settings
var allGuildConfigs = bot.AllGuildConfigs.Where(x => x.XpSettings != null); var allGuildConfigs = bot.AllGuildConfigs.Where(x => x.XpSettings != null);
@ -105,6 +109,7 @@ namespace NadekoBot.Modules.Xp.Services
{ {
var toNotify = new List<(IMessageChannel MessageChannel, IUser User, int Level, XpNotificationType NotifyType, NotifOf NotifOf)>(); var toNotify = new List<(IMessageChannel MessageChannel, IUser User, int Level, XpNotificationType NotifyType, NotifOf NotifOf)>();
var roleRewards = new Dictionary<ulong, List<XpRoleReward>>(); var roleRewards = new Dictionary<ulong, List<XpRoleReward>>();
var curRewards = new Dictionary<ulong, List<XpCurrencyReward>>();
var toAddTo = new List<UserCacheItem>(); var toAddTo = new List<UserCacheItem>();
while (_addMessageXp.TryDequeue(out var usr)) while (_addMessageXp.TryDequeue(out var usr))
@ -120,14 +125,13 @@ namespace NadekoBot.Modules.Xp.Services
{ {
var xp = item.Select(x => bc.BotConfig.XpPerMessage).Sum(); var xp = item.Select(x => bc.BotConfig.XpPerMessage).Sum();
//1. Mass query discord users and userxpstats and get them from local dict
//2. (better but much harder) Move everything to the database, and get old and new xp
// amounts for every user (in order to give rewards)
var usr = uow.Xp.GetOrCreateUser(item.Key.GuildId, item.Key.User.Id); var usr = uow.Xp.GetOrCreateUser(item.Key.GuildId, item.Key.User.Id);
var du = uow.DiscordUsers.GetOrCreate(item.Key.User); var du = uow.DiscordUsers.GetOrCreate(item.Key.User);
if (du.LastXpGain + TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout) > DateTime.UtcNow)
continue;
du.LastXpGain = DateTime.UtcNow;
var globalXp = du.TotalXp; var globalXp = du.TotalXp;
var oldGlobalLevelData = new LevelStats(globalXp); var oldGlobalLevelData = new LevelStats(globalXp);
var newGlobalLevelData = new LevelStats(globalXp + xp); var newGlobalLevelData = new LevelStats(globalXp + xp);
@ -156,21 +160,34 @@ namespace NadekoBot.Modules.Xp.Services
toNotify.Add((first.Channel, first.User, newGuildLevelData.Level, usr.NotifyOnLevelUp, NotifOf.Server)); toNotify.Add((first.Channel, first.User, newGuildLevelData.Level, usr.NotifyOnLevelUp, NotifOf.Server));
//give role //give role
if (!roleRewards.TryGetValue(usr.GuildId, out var rewards)) if (!roleRewards.TryGetValue(usr.GuildId, out var rrews))
{ {
rewards = uow.GuildConfigs.XpSettingsFor(usr.GuildId).RoleRewards.ToList(); rrews = uow.GuildConfigs.XpSettingsFor(usr.GuildId).RoleRewards.ToList();
roleRewards.Add(usr.GuildId, rewards); roleRewards.Add(usr.GuildId, rrews);
} }
var rew = rewards.FirstOrDefault(x => x.Level == newGuildLevelData.Level); if (!curRewards.TryGetValue(usr.GuildId, out var crews))
if (rew != null)
{ {
var role = first.User.Guild.GetRole(rew.RoleId); crews = uow.GuildConfigs.XpSettingsFor(usr.GuildId).CurrencyRewards.ToList();
curRewards.Add(usr.GuildId, crews);
}
var rrew = rrews.FirstOrDefault(x => x.Level == newGuildLevelData.Level);
if (rrew != null)
{
var role = first.User.Guild.GetRole(rrew.RoleId);
if (role != null) if (role != null)
{ {
var __ = first.User.AddRoleAsync(role); var __ = first.User.AddRoleAsync(role);
} }
} }
//get currency reward for this level
var crew = crews.FirstOrDefault(x => x.Level == newGuildLevelData.Level);
if (crew != null)
{
//give the user the reward if it exists
await _cs.AddAsync(item.Key.User.Id, "Level-up Reward", crew.Amount, uow);
}
} }
} }
@ -241,6 +258,50 @@ namespace NadekoBot.Modules.Xp.Services
}, token); }, token);
} }
public void SetCurrencyReward(ulong guildId, int level, int amount)
{
using (var uow = _db.UnitOfWork)
{
var settings = uow.GuildConfigs.XpSettingsFor(guildId);
if (amount <= 0)
{
var toRemove = settings.CurrencyRewards.FirstOrDefault(x => x.Level == level);
if (toRemove != null)
{
uow._context.Remove(toRemove);
settings.CurrencyRewards.Remove(toRemove);
}
}
else
{
var rew = settings.CurrencyRewards.FirstOrDefault(x => x.Level == level);
if (rew != null)
rew.Amount = amount;
else
settings.CurrencyRewards.Add(new XpCurrencyReward()
{
Level = level,
Amount = amount,
});
}
uow.Complete();
}
}
public IEnumerable<XpCurrencyReward> GetCurrencyRewards(ulong id)
{
using (var uow = _db.UnitOfWork)
{
return uow.GuildConfigs.XpSettingsFor(id)
.CurrencyRewards
.ToArray();
}
}
public IEnumerable<XpRoleReward> GetRoleRewards(ulong id) public IEnumerable<XpRoleReward> GetRoleRewards(ulong id)
{ {
using (var uow = _db.UnitOfWork) using (var uow = _db.UnitOfWork)
@ -385,7 +446,13 @@ namespace NadekoBot.Modules.Xp.Services
private bool SetUserRewarded(ulong userId) private bool SetUserRewarded(ulong userId)
{ {
return _rewardedUsers.Add(userId); var r = _cache.Redis.GetDatabase();
var key = $"{_creds.RedisKey()}_user_xp_gain_{userId}";
return r.StringSet(key,
true,
TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout),
StackExchange.Redis.When.NotExists);
} }
public FullUserStats GetUserStats(IGuildUser user) public FullUserStats GetUserStats(IGuildUser user)

View File

@ -17,40 +17,18 @@ namespace NadekoBot.Modules.Xp
{ {
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private readonly DbService _db; private readonly DbService _db;
private readonly IBotConfigProvider _bc;
public Xp(DiscordSocketClient client,DbService db) public Xp(DiscordSocketClient client,DbService db, IBotConfigProvider bc)
{ {
_client = client; _client = client;
_db = db; _db = db;
_bc = bc;
} }
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//[OwnerOnly]
//public async Task Populate()
//{
// var rng = new NadekoRandom();
// using (var uow = _db.UnitOfWork)
// {
// for (var i = 0ul; i < 1000000; i++)
// {
// uow.DiscordUsers.Add(new DiscordUser()
// {
// AvatarId = i.ToString(),
// Discriminator = "1234",
// UserId = i,
// Username = i.ToString(),
// Club = null,
// });
// var xp = uow.Xp.GetOrCreateUser(Context.Guild.Id, i);
// xp.Xp = rng.Next(100, 100000);
// }
// uow.Complete();
// }
//}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
//todo add ratelimit attribute
//[Ratelimit(30)] //[Ratelimit(30)]
public async Task Experience([Remainder]IUser user = null) public async Task Experience([Remainder]IUser user = null)
{ {
@ -69,34 +47,39 @@ namespace NadekoBot.Modules.Xp
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public Task XpRoleRewards(int page = 1) public Task XpLevelUpRewards(int page = 1)
{ {
page--; page--;
if (page < 0 || page > 100) if (page < 0 || page > 100)
return Task.CompletedTask; return Task.CompletedTask;
var roles = _service.GetRoleRewards(Context.Guild.Id)
.OrderBy(x => x.Level)
.Skip(page * 9)
.Take(9);
var embed = new EmbedBuilder() var embed = new EmbedBuilder()
.WithTitle(GetText("role_rewards")) .WithTitle(GetText("level_up_rewards"))
.WithOkColor(); .WithOkColor();
if (!roles.Any()) var rewards = _service.GetRoleRewards(Context.Guild.Id)
return Context.Channel.EmbedAsync(embed.WithDescription(GetText("no_role_rewards"))); .OrderBy(x => x.Level)
.Select(x =>
{
var str = Context.Guild.GetRole(x.RoleId)?.ToString();
if (str != null)
str = GetText("role_reward", Format.Bold(str));
return (x.Level, RoleStr: str);
})
.Where(x => x.RoleStr != null)
.Concat(_service.GetCurrencyRewards(Context.Guild.Id)
.OrderBy(x => x.Level)
.Select(x => (x.Level, Format.Bold(x.Amount + _bc.BotConfig.CurrencySign))))
.GroupBy(x => x.Level)
.OrderBy(x => x.Key)
.Skip(page * 9)
.Take(9)
.ForEach(x => embed.AddField(GetText("level_x", x.Key), string.Join("\n", x.Select(y => y.Item2))));
foreach (var rolerew in roles) if (!rewards.Any())
{ return Context.Channel.EmbedAsync(embed.WithDescription(GetText("no_level_up_rewards")));
var role = Context.Guild.GetRole(rolerew.RoleId);
if (role == null)
continue;
embed.AddField(GetText("level_x", Format.Bold(rolerew.Level.ToString())), role.ToString());
}
return Context.Channel.EmbedAsync(embed); return Context.Channel.EmbedAsync(embed);
} }
@ -116,6 +99,22 @@ namespace NadekoBot.Modules.Xp
await ReplyConfirmLocalized("role_reward_added", level, Format.Bold(role.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("role_reward_added", level, Format.Bold(role.ToString())).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task XpCurrencyReward(int level, int amount=0)
{
if (level < 1 || amount < 0)
return;
_service.SetCurrencyReward(Context.Guild.Id, level, amount);
if (amount == 0)
await ReplyConfirmLocalized("cur_reward_cleared", level, _bc.BotConfig.CurrencySign).ConfigureAwait(false);
else
await ReplyConfirmLocalized("cur_reward_added", level, Format.Bold(amount + _bc.BotConfig.CurrencySign)).ConfigureAwait(false);
}
public enum NotifyPlace public enum NotifyPlace
{ {
Server = 0, Server = 0,

View File

@ -8,6 +8,7 @@ namespace NadekoBot.Core.Services.Database.Models
public GuildConfig GuildConfig { get; set; } public GuildConfig GuildConfig { get; set; }
public HashSet<XpRoleReward> RoleRewards { get; set; } = new HashSet<XpRoleReward>(); public HashSet<XpRoleReward> RoleRewards { get; set; } = new HashSet<XpRoleReward>();
public HashSet<XpCurrencyReward> CurrencyRewards { get; set; } = new HashSet<XpCurrencyReward>();
public bool XpRoleRewardExclusive { get; set; } public bool XpRoleRewardExclusive { get; set; }
public string NotifyMessage { get; set; } = "Congratulations {0}! You have reached level {1}!"; public string NotifyMessage { get; set; } = "Congratulations {0}! You have reached level {1}!";
public HashSet<ExcludedItem> ExclusionList { get; set; } = new HashSet<ExcludedItem>(); public HashSet<ExcludedItem> ExclusionList { get; set; } = new HashSet<ExcludedItem>();
@ -35,6 +36,25 @@ namespace NadekoBot.Core.Services.Database.Models
} }
} }
public class XpCurrencyReward : DbEntity
{
public int XpSettingsId { get; set; }
public XpSettings XpSettings { get; set; }
public int Level { get; set; }
public int Amount { get; set; }
public override int GetHashCode()
{
return Level.GetHashCode() ^ XpSettingsId.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is XpCurrencyReward xrr && xrr.Level == Level && xrr.XpSettingsId == XpSettingsId;
}
}
public class ExcludedItem : DbEntity public class ExcludedItem : DbEntity
{ {
public ulong ItemId { get; set; } public ulong ItemId { get; set; }

View File

@ -200,6 +200,8 @@ namespace NadekoBot.Core.Services.Database.Repositories.Impl
set => set.Include(x => x.XpSettings) set => set.Include(x => x.XpSettings)
.ThenInclude(x => x.RoleRewards) .ThenInclude(x => x.RoleRewards)
.Include(x => x.XpSettings) .Include(x => x.XpSettings)
.ThenInclude(x => x.CurrencyRewards)
.Include(x => x.XpSettings)
.ThenInclude(x => x.ExclusionList)); .ThenInclude(x => x.ExclusionList));
if (gc.XpSettings == null) if (gc.XpSettings == null)

View File

@ -21,7 +21,7 @@ namespace NadekoBot.Core.Services.Impl
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
private readonly DateTime _started; private readonly DateTime _started;
public const string BotVersion = "2.4.4"; public const string BotVersion = "2.5.0";
public string Author => "Kwoth#2560"; public string Author => "Kwoth#2560";
public string Library => "Discord.Net"; public string Library => "Discord.Net";

View File

@ -845,9 +845,10 @@
"xp_role_reward_added": "Users who reach level {0} will receive {1} role.", "xp_role_reward_added": "Users who reach level {0} will receive {1} role.",
"xp_cur_reward_cleared": "Reaching level {0} will no longer reward any {1}.", "xp_cur_reward_cleared": "Reaching level {0} will no longer reward any {1}.",
"xp_cur_reward_added": "Users who reach level {0} will receive {1}.", "xp_cur_reward_added": "Users who reach level {0} will receive {1}.",
"xp_role_rewards": "Role Rewards", "xp_level_up_rewards": "Level Up Rewards",
"xp_level_x": "Level {0}", "xp_level_x": "Level {0}",
"xp_no_role_rewards": "No role reward on this page.", "xp_role_reward": "{0} role",
"xp_no_level_up_rewards": "No level up reward on this page.",
"xp_server_leaderboard": "Server XP Leaderboard", "xp_server_leaderboard": "Server XP Leaderboard",
"xp_global_leaderboard": "Global XP Leaderboard", "xp_global_leaderboard": "Global XP Leaderboard",
"xp_modified": "Modified server XP of the user {0} by {1}", "xp_modified": "Modified server XP of the user {0} by {1}",

View File

@ -2815,11 +2815,11 @@
"{0}xpn server channel" "{0}xpn server channel"
] ]
}, },
"xprolerewards": { "xpleveluprewards": {
"Cmd": "xprolerewards xprrs", "Cmd": "xplvluprewards xprews xpcrs xprrs xprolerewards xpcurrewards",
"Desc": "Shows currently set role rewards.", "Desc": "Shows currently set level up rewards.",
"Usage": [ "Usage": [
"{0}xprrs" "{0}xprews"
] ]
}, },
"xprolereward": { "xprolereward": {
@ -2829,6 +2829,13 @@
"{0}xprr 3 Social" "{0}xprr 3 Social"
] ]
}, },
"xpcurrencyreward": {
"Cmd": "xpcurreward xpcr",
"Desc": "Sets a currency reward on a specified level. Provide no amount in order to remove the reward.",
"Usage": [
"{0}xpcr 3 50"
]
},
"xpleaderboard": { "xpleaderboard": {
"Cmd": "xpleaderboard xplb", "Cmd": "xpleaderboard xplb",
"Desc": "Shows current server's xp leaderboard.", "Desc": "Shows current server's xp leaderboard.",