diff --git a/TPP.Core/Commands/Definitions/TransmuteCommands.cs b/TPP.Core/Commands/Definitions/TransmuteCommands.cs index 40765768..cc1e3430 100644 --- a/TPP.Core/Commands/Definitions/TransmuteCommands.cs +++ b/TPP.Core/Commands/Definitions/TransmuteCommands.cs @@ -22,8 +22,9 @@ public class TransmuteCommands : ICommandCollection { new Command("transmute", Transmute) { - Description = "Transform 3 or more badges into a (usually) rarer badge. Costs one token. " + - "Arguments: (at least 3) t1" + Description = "Transform 3 or more badges into a (usually) rarer badge. Costs one or ten token(s). T1 shall be provided" + + " for regular transmutation, T10 will start a special transmutation that results in a fakemon badge. " + + "Arguments: (at least 3) T1 / T10" }, }; @@ -52,7 +53,7 @@ public async Task Transmute(CommandContext context) if (!tokensArg.IsPresent) return new CommandResult { - Response = $"Please include payment 'T{ITransmutationCalculator.TransmutationCost}' in your command" + Response = $"Please include payment 'T{ITransmutationCalculator.TransmutationCostStandard}' or 'T{ITransmutationCalculator.TransmutationCostSpecial}' in your command" }; ImmutableList speciesList = speciesArg.Values; diff --git a/TPP.Core/Setups.cs b/TPP.Core/Setups.cs index 0d1b1671..a8b534e1 100644 --- a/TPP.Core/Setups.cs +++ b/TPP.Core/Setups.cs @@ -73,15 +73,21 @@ public static ITransmuter SetUpTransmuter( Databases databases, OverlayConnection overlayConnection) { - ImmutableSortedSet transmutableSpecies = knownSpecies + ImmutableSortedSet transmutableSpeciesStandard = knownSpecies .Where(s => s.GetGeneration() is Generation.Gen1 or Generation.Gen2 or Generation.Gen3 or Generation.Gen4 or Generation.Gen5 or Generation.Gen6 or Generation.Gen7 or Generation.Gen8 ) .ToImmutableSortedSet(); + ImmutableSortedSet transmutableSpeciesSpecial = knownSpecies + .Where(s => s.GetGeneration() + is Generation.GenFake + ) + .ToImmutableSortedSet(); ITransmutationCalculator transmutationCalculator = new TransmutationCalculator( badgeStatsRepo: databases.BadgeStatsRepo, - transmutableSpecies: transmutableSpecies, + transmutableSpeciesStandard: transmutableSpeciesStandard, + transmutableSpeciesSpecial: transmutableSpeciesSpecial, random: new Random().NextDouble); ITransmuter transmuter = new Transmuter( databases.BadgeRepo, transmutationCalculator, databases.TokensBank, databases.TransmutationLogRepo, diff --git a/TPP.Core/Transmutation.cs b/TPP.Core/Transmutation.cs index f4f327d1..4d5b31b4 100644 --- a/TPP.Core/Transmutation.cs +++ b/TPP.Core/Transmutation.cs @@ -18,10 +18,13 @@ public TransmuteException(string? message) : base(message) { } public interface ITransmutationCalculator { - public const int TransmutationCost = 1; + public const int TransmutationCostStandard = 1; + public const int TransmutationCostSpecial = 10; + public const double TransmutationSpecialMinRarity = 11.0; // Minimum logarithmic inverse rarity at least one of + // the badges used in special transmutation needs to have public const int MinTransmuteBadges = 3; - Task Transmute(IImmutableList inputSpecies); + Task Transmute(IImmutableList inputSpecies, int tokens); } /// @@ -36,18 +39,23 @@ public class TransmutationCalculator : ITransmutationCalculator private readonly IBadgeStatsRepo _badgeStatsRepo; private readonly Func _random; - private readonly IImmutableSet _transmutableSpecies; + private readonly IImmutableSet _transmutableSpeciesStandard; + private readonly IImmutableSet _transmutableSpeciesSpecial; public TransmutationCalculator( IBadgeStatsRepo badgeStatsRepo, - IImmutableSet transmutableSpecies, + IImmutableSet transmutableSpeciesStandard, + IImmutableSet transmutableSpeciesSpecial, Func random) { _badgeStatsRepo = badgeStatsRepo; - _transmutableSpecies = transmutableSpecies; + _transmutableSpeciesStandard = transmutableSpeciesStandard; + _transmutableSpeciesSpecial = transmutableSpeciesSpecial; _random = random; - if (!transmutableSpecies.Any()) - throw new ArgumentException("must provide at least 1 transmutable", nameof(transmutableSpecies)); + if (!transmutableSpeciesStandard.Any()) + throw new ArgumentException("must provide at least 1 transmutable", nameof(transmutableSpeciesStandard)); + if (!transmutableSpeciesSpecial.Any()) + throw new ArgumentException("must provide at least 1 transmutable", nameof(transmutableSpeciesSpecial)); } private static double CombineRarities(IEnumerable rarities) => @@ -103,13 +111,23 @@ private PkmnSpecies GetBadgeForRarity(double rarity, IDictionary Transmute(IImmutableList inputSpecies) + public async Task Transmute(IImmutableList inputSpecies, int tokens) { if (inputSpecies.Count < ITransmutationCalculator.MinTransmuteBadges) throw new TransmuteException( $"Must transmute at least {ITransmutationCalculator.MinTransmuteBadges} badges"); IDictionary stats = await _badgeStatsRepo.GetBadgeStats(); - IImmutableSet transmutables = _transmutableSpecies + if (tokens == ITransmutationCalculator.TransmutationCostSpecial) + { + double minRarity = inputSpecies.Where(i => stats.ContainsKey(i)).Select(i => stats[i]).Min(t => t.Rarity); + double logInverseRarity = -1 * Math.Log(minRarity); + if (logInverseRarity < ITransmutationCalculator.TransmutationSpecialMinRarity) + throw new TransmuteException( + $"Have to provide a badge that has at least {ITransmutationCalculator.TransmutationSpecialMinRarity} in logarithmic inverse rarity"); + } + + IImmutableSet transmutables = (tokens == ITransmutationCalculator.TransmutationCostSpecial + ? _transmutableSpeciesSpecial : _transmutableSpeciesStandard) .Except(inputSpecies) .Where(t => stats.ContainsKey(t) && stats[t].Rarity > 0) .ToImmutableHashSet(); @@ -118,7 +136,7 @@ public async Task Transmute(IImmutableList inputSpecie throw new TransmuteException( "there are no transmutables left after removing all input species from the pool"); } - HashSet illegalInputs = inputSpecies.Except(_transmutableSpecies).ToHashSet(); + HashSet illegalInputs = inputSpecies.Except(_transmutableSpeciesStandard).ToHashSet(); if (illegalInputs.Any()) throw new TransmuteException(string.Join(", ", illegalInputs) + " cannot be used for transmutation"); @@ -193,15 +211,15 @@ public Transmuter( public async Task Transmute(User user, int tokens, IImmutableList speciesList) { - if (tokens != ITransmutationCalculator.TransmutationCost) + if (tokens != ITransmutationCalculator.TransmutationCostStandard && tokens != ITransmutationCalculator.TransmutationCostSpecial) throw new TransmuteException( - $"Must pay exactly {ITransmutationCalculator.TransmutationCost} token to transmute."); + $"Must pay exactly {ITransmutationCalculator.TransmutationCostStandard} or {ITransmutationCalculator.TransmutationCostSpecial} token to transmute."); if (speciesList.Count < ITransmutationCalculator.MinTransmuteBadges) throw new TransmuteException( $"Must transmute at least {ITransmutationCalculator.MinTransmuteBadges} badges."); if (await _tokenBank.GetAvailableMoney(user) < tokens) throw new TransmuteException( - $"You don't have the T{ITransmutationCalculator.TransmutationCost} required to transmute."); + $"You don't have the T{tokens} required to transmute."); IImmutableList inputBadges = ImmutableList.Empty; foreach (var grouping in speciesList.GroupBy(s => s)) @@ -215,7 +233,7 @@ public async Task Transmute(User user, int tokens, IImmutableList additionalData = new(); IImmutableList consumedBadges = await _badgeRepo @@ -231,15 +249,15 @@ await _tokenBank.PerformTransaction(new Transaction(user, -tokens, })); await _transmutationLogRepo.Log(user.Id, _clock.GetCurrentInstant(), tokens, inputIds, resultBadge.Id); - await OnTransmuted(user, speciesList, resultSpecies); + await OnTransmuted(user, speciesList, resultSpecies, tokens); return resultBadge; } - private async Task OnTransmuted(User user, IImmutableList inputs, PkmnSpecies output) + private async Task OnTransmuted(User user, IImmutableList inputs, PkmnSpecies output, int tokens) { List candidates = new(); for (int i = 0; i < 5; i++) - candidates.Add(await _transmutationCalculator.Transmute(inputs)); + candidates.Add(await _transmutationCalculator.Transmute(inputs, tokens)); candidates.Insert(Random.Next(0, candidates.Count), output); var args = new TransmuteEventArgs(user, inputs, output, candidates.ToImmutableList()); Transmuted?.Invoke(this, args); diff --git a/tests/TPP.Core.Tests/TransmutationTest.cs b/tests/TPP.Core.Tests/TransmutationTest.cs index befd47c3..5a8af10a 100644 --- a/tests/TPP.Core.Tests/TransmutationTest.cs +++ b/tests/TPP.Core.Tests/TransmutationTest.cs @@ -43,13 +43,14 @@ public void TestSomeTransmutationCalculations() ITransmutationCalculator transmutationCalculator = new TransmutationCalculator( badgeStatsRepoMock.Object, badgeStats.Keys.ToImmutableSortedSet(), + badgeStats.Keys.ToImmutableSortedSet(), random: random.NextDouble ); ImmutableSortedDictionary result = Enumerable .Range(0, numTransmutations) .Select(_ => transmutationCalculator - .Transmute(ImmutableList.Create(speciesCommon, speciesCommon, speciesCommon)).Result) + .Transmute(ImmutableList.Create(speciesCommon, speciesCommon, speciesCommon), 1).Result) .GroupBy(species => species) .ToImmutableSortedDictionary(grp => grp.Key, grp => grp.Count()); @@ -82,13 +83,14 @@ public void TestCannotTransmuteNonExistingBadge() ITransmutationCalculator transmutationCalculator = new TransmutationCalculator( badgeStatsRepoMock.Object, badgeStats.Keys.ToImmutableSortedSet(), + badgeStats.Keys.ToImmutableSortedSet(), random: random.NextDouble ); ImmutableSortedDictionary result = Enumerable .Range(0, numTransmutations) .Select(_ => transmutationCalculator - .Transmute(Enumerable.Repeat(speciesUncommon, 100).ToImmutableList()).Result) + .Transmute(Enumerable.Repeat(speciesUncommon, 100).ToImmutableList(), 1).Result) .GroupBy(species => species) .ToImmutableSortedDictionary(grp => grp.Key, grp => grp.Count()); @@ -119,11 +121,12 @@ public void TestCannotTransmuteUntransmutableBadge() ITransmutationCalculator transmutationCalculator = new TransmutationCalculator( badgeStatsRepoMock.Object, ImmutableHashSet.Create(speciesTransmutable), + ImmutableHashSet.Create(speciesUntransmutable), random: () => 12345 ); TransmuteException exception = Assert.ThrowsAsync(async () => await transmutationCalculator - .Transmute(ImmutableList.Create(speciesUntransmutable, speciesUntransmutable, speciesUntransmutable)))!; + .Transmute(ImmutableList.Create(speciesUntransmutable, speciesUntransmutable, speciesUntransmutable), 1))!; Assert.That(exception.Message, Is.EqualTo(speciesUntransmutable + " cannot be used for transmutation")); } } @@ -161,7 +164,7 @@ public async Task TestSuccessfulTransmute() bankMock.Setup(b => b.GetAvailableMoney(user)).ReturnsAsync(1); List> transferData = new(); - transmutationCalculatorMock.Setup(c => c.Transmute(inputSpeciesList)).ReturnsAsync(speciesOut); + transmutationCalculatorMock.Setup(c => c.Transmute(inputSpeciesList, 1)).ReturnsAsync(speciesOut); badgeRepoMock.Setup(r => r.FindByUserAndSpecies(user.Id, speciesIn, inputSpeciesList.Count)) .ReturnsAsync(inputBadges); badgeRepoMock.Setup(r => r.TransferBadges(inputBadges, null, "transmutation", Capture.In(transferData)))