Skip to content

Commit

Permalink
Merge pull request #543 from ze-dom/bugfix/item_level_upgrade_chaos_mix
Browse files Browse the repository at this point in the history
Bugfix/item level upgrade chaos mix
  • Loading branch information
sven-n authored Jan 1, 2025
2 parents 413beee + a738c13 commit ba5ace8
Show file tree
Hide file tree
Showing 53 changed files with 6,854 additions and 458 deletions.
8 changes: 4 additions & 4 deletions src/DataModel/Configuration/ItemCrafting/MixResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ public enum MixResult
StaysAsIs = 1,

/// <summary>
/// The item will be downgraded to level 0.
/// The item will be downgraded to a random level, may lose its skill, and its item option may be reduced by 1 level.
/// </summary>
DowngradedTo0 = 3,
ChaosWeaponAndFirstWingsDowngradedRandom = 2,

/// <summary>
/// The item will be downgraded to a random level.
/// The item will be downgraded 2 or 3 levels and its item option will be removed.
/// </summary>
DowngradedRandom = 4,
ThirdWingsDowngradedRandom = 3,
}
11 changes: 11 additions & 0 deletions src/DataModel/Configuration/ItemCrafting/SimpleCraftingSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public partial class SimpleCraftingSettings
/// </summary>
public int MoneyPerFinalSuccessPercentage { get; set; }

/// <summary>
/// Gets or sets the NPC price divisor for the sum of crafting items' prices. For each full division, the percentage gets increased by 1 percent, and the mix price rises.
/// </summary>
/// <remarks>Used for Chaos Weapon and 1st Level Wing craftings.</remarks>
public int NpcPriceDivisor { get; set; }

/// <summary>
/// Gets or sets the success percent.
/// </summary>
Expand Down Expand Up @@ -69,6 +75,11 @@ public partial class SimpleCraftingSettings
/// </summary>
public int SuccessPercentageAdditionForAncientItem { get; set; }

/// <summary>
/// Gets or sets the success percentage addition for a "380 item" which gets modified.
/// </summary>
public int SuccessPercentageAdditionForGuardianItem { get; set; }

/// <summary>
/// Gets or sets the success percentage addition for a socket item which gets modified.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/DataModel/Configuration/Items/IncreasableItemOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public enum LevelType
/// It's increased by the level of the item which has the option.
/// </summary>
/// <remarks>
/// As far as I know, this is only required for wing options, e.g. 'Increase max HP +50~115'. That's why <see cref="OptionLevel"/> is the default, too.
/// As far as I know, this is only required for wing options, e.g. 'Increase max HP +50~125'. That's why <see cref="OptionLevel"/> is the default, too.
/// </remarks>
ItemLevel,
}
Expand Down
13 changes: 13 additions & 0 deletions src/GameLogic/ItemExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,19 @@ public static bool IsExcellent(this Item item)
return item.ItemOptions.Any(link => link.ItemOption?.OptionType == ItemOptionTypes.Excellent);
}

/// <summary>
/// Determines whether this instance is a "380 item", that is, if it can be upgraded with Jewel of Guardian.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>
/// <c>true</c> if the specified item is a "380 item"; otherwise, <c>false</c>.
/// </returns>
public static bool IsGuardian(this Item item)
{
return item.Definition!.PossibleItemOptions.Any(pio => pio.PossibleOptions
.Any(po => po.OptionType == ItemOptionTypes.GuardianOption));
}

/// <summary>
/// Determines whether this item is a defensive item.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/GameLogic/ItemPowerUpFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ private IEnumerable<PowerUpWrapper> GetPowerUpsOfItemOptions(Item item, Attribut
var level = option.LevelType == LevelType.ItemLevel ? item.Level : optionLink.Level;

var optionOfLevel = option.LevelDependentOptions?.FirstOrDefault(l => l.Level == level);
if (optionOfLevel is null && level > 1)
if (optionOfLevel is null && level > 1 && item.Definition!.Skill?.Number != 49) // Dinorant options are an exception
{
this._logger.LogWarning("Item {item} (id {itemId}) has IncreasableItemOption ({option}, id {optionId}) with level {level}, but no definition in LevelDependentOptions.", item, item.GetId(), option, option.GetId(), level);
continue;
Expand Down
340 changes: 223 additions & 117 deletions src/GameLogic/ItemPriceCalculator.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ protected sealed override int GetPrice(byte successRate, IList<CraftingRequiredI
protected abstract byte GetSuccessRate(int eventLevel);

/// <inheritdoc />
protected override async ValueTask<List<Item>> CreateOrModifyResultItemsAsync(IList<CraftingRequiredItemLink> requiredItems, Player player, byte socketIndex)
protected override async ValueTask<List<Item>> CreateOrModifyResultItemsAsync(IList<CraftingRequiredItemLink> requiredItems, Player player, byte socketIndex, byte successRate)
{
var item = player.PersistenceContext.CreateNew<Item>();
item.Definition = player.GameContext.Configuration.Items.First(i => i.Name == this._resultItemName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// <copyright file="ChaosWeaponAndFirstWingsCrafting.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.PlayerActions.Craftings;

using MUnique.OpenMU.DataModel.Configuration.ItemCrafting;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.PlayerActions.Items;

/// <summary>
/// Crafting for Chaos Weapon and First Wings.
/// </summary>
public class ChaosWeaponAndFirstWingsCrafting : SimpleItemCraftingHandler
{
/// <summary>
/// Initializes a new instance of the <see cref="ChaosWeaponAndFirstWingsCrafting"/> class.
/// </summary>
/// <param name="settings">The settings.</param>
public ChaosWeaponAndFirstWingsCrafting(SimpleCraftingSettings settings)
: base(settings)
{
}

/// <inheritdoc/>
protected override void AddRandomItemOption(Item resultItem, Player player, byte successRate)
{
if (resultItem.Definition!.PossibleItemOptions.FirstOrDefault(o =>
o.PossibleOptions.Any(p => p.OptionType == ItemOptionTypes.Option))
is { } option)
{
int i = Rand.NextInt(0, 3);
if (Rand.NextRandomBool((successRate / 5) + (4 * (i + 1))))
{
var link = player.PersistenceContext.CreateNew<ItemOptionLink>();
link.ItemOption = option.PossibleOptions.First();
link.Level = 3 - i;
resultItem.ItemOptions.Add(link);
}
}
}

/// <inheritdoc/>
protected override void AddRandomLuckOption(Item resultItem, Player player, byte successRate)
{
if (Rand.NextRandomBool((successRate / 5) + 4)
&& resultItem.Definition!.PossibleItemOptions.FirstOrDefault(o =>
o.PossibleOptions.Any(po => po.OptionType == ItemOptionTypes.Luck))
is { } luck)
{
var luckOption = player.PersistenceContext.CreateNew<ItemOptionLink>();
luckOption.ItemOption = luck.PossibleOptions.First();
resultItem.ItemOptions.Add(luckOption);
}
}

/// <inheritdoc/>
protected override void AddRandomSkill(Item resultItem, byte successRate)
{
if (Rand.NextRandomBool((successRate / 5) + 6)
&& !resultItem.HasSkill
&& resultItem.Definition!.Skill is { })
{
resultItem.HasSkill = true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ protected override int GetPrice(int eventLevel)
/// <inheritdoc />
protected override byte GetSuccessRate(int eventLevel)
{
return (byte)(80 - (eventLevel * 5));
return (byte)(eventLevel < 5 ? 80 : 70); // Future to-do: There is a +10% increase if Crywolf event is beaten
}
}
86 changes: 86 additions & 0 deletions src/GameLogic/PlayerActions/Craftings/DinorantCrafting.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// <copyright file="DinorantCrafting.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.PlayerActions.Craftings;

using MUnique.OpenMU.DataModel.Configuration.ItemCrafting;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.PlayerActions.Items;
using MUnique.OpenMU.GameLogic.Views.NPC;

/// <summary>
/// Crafting for Dinorant.
/// </summary>
public class DinorantCrafting : SimpleItemCraftingHandler
{
/// <summary>
/// Initializes a new instance of the <see cref="DinorantCrafting"/> class.
/// </summary>
/// <param name="settings">The settings.</param>
public DinorantCrafting(SimpleCraftingSettings settings)
: base(settings)
{
}

/// <inheritdoc />
protected override CraftingResult? TryGetRequiredItems(Player player, out IList<CraftingRequiredItemLink> items, out byte successRate)
{
var craftingResult = base.TryGetRequiredItems(player, out items, out successRate);

var uniriaLink = items.Where(i => i.ItemRequirement.PossibleItems.Any(i => i.Name == "Horn of Uniria"));
foreach (var item in uniriaLink.First().Items)
{
if (item.Durability < 255)
{
return CraftingResult.IncorrectMixItems;
}
}

return craftingResult;
}

/// <inheritdoc/>
protected override void AddRandomItemOption(Item resultItem, Player player, byte successRate)
{
if (Rand.NextRandomBool(30)
&& resultItem.Definition!.PossibleItemOptions.FirstOrDefault(o =>
o.PossibleOptions.Any(p => p.OptionType == ItemOptionTypes.Option))
is { } option)
{
var link = player.PersistenceContext.CreateNew<ItemOptionLink>();
link.ItemOption = option.PossibleOptions.SelectRandom();
resultItem.ItemOptions.Add(link);

// There is a second rollout for an additional bonus option to the first (but only if it doesn't coincide).
if (Rand.NextRandomBool(20))
{
var bonusOpt = option.PossibleOptions.SelectRandom();
if (bonusOpt != link.ItemOption)
{
var bonusLink = player.PersistenceContext.CreateNew<ItemOptionLink>();
bonusLink.ItemOption = bonusOpt;
resultItem.ItemOptions.Add(bonusLink);
}
}
}

// Dinorant options were originally coded within the normal item option; each has a different level.
foreach (var dinoOption in resultItem.ItemOptions)
{
if (dinoOption.ItemOption!.PowerUpDefinition!.TargetAttribute == Stats.DamageReceiveDecrement)
{
dinoOption.Level = 1;
}
else if (dinoOption.ItemOption!.PowerUpDefinition!.TargetAttribute == Stats.MaximumAbility)
{
dinoOption.Level = 2;
}
else
{
dinoOption.Level = 4;
}
}
}
}
19 changes: 9 additions & 10 deletions src/GameLogic/PlayerActions/Craftings/FenrirUpgradeCrafting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,15 @@ protected override int GetPrice(byte successRate, IList<CraftingRequiredItemLink
var inputItems = player.TemporaryStorage!.Items.ToList();
var itemsLevelAndOption4 = inputItems
.Where(item => item.Level >= 4
&& item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Option))
&& item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Option))
.ToList();
var randomWeapons = itemsLevelAndOption4
.Where(item => item.IsWearable()
&& item.Definition!.BasePowerUpAttributes.Any(a =>
a.TargetAttribute == Stats.MaximumPhysBaseDmgByWeapon))
&& item.Definition!.BasePowerUpAttributes.Any(a => a.TargetAttribute == Stats.AttackSpeedByWeapon))
.ToList();

var randomArmors = itemsLevelAndOption4
.Where(item => item.IsWearable() && item.Definition!.BasePowerUpAttributes.Any(a => a.TargetAttribute == Stats.DefenseBase))
.Where(item => item.IsWearable()
&& item.Definition!.BasePowerUpAttributes.Any(a => a.TargetAttribute == Stats.DefenseBase))
.ToList();

if (randomArmors.Any() && randomWeapons.Any())
Expand Down Expand Up @@ -82,22 +81,22 @@ protected override int GetPrice(byte successRate, IList<CraftingRequiredItemLink
if (randomWeapons.Any())
{
items.Add(new CraftingRequiredItemLink(randomWeapons, new TransientItemCraftingRequiredItem { MinimumAmount = 1, MaximumAmount = 1, Reference = 2 }));
successRateByItems = (byte)Math.Min(79, randomWeapons.Sum(this._priceCalculator.CalculateBuyingPrice) * 100 / 1_000_000);
successRateByItems = (byte)Math.Min(79, randomWeapons.Sum(this._priceCalculator.CalculateSellingPrice) * 100 / 1_000_000);
}

if (randomArmors.Any())
else
{
items.Add(new CraftingRequiredItemLink(randomArmors, new TransientItemCraftingRequiredItem { MinimumAmount = 1, MaximumAmount = 1, Reference = 3 }));
successRateByItems = (byte)Math.Min(79, randomArmors.Sum(this._priceCalculator.CalculateBuyingPrice) * 100 / 1_000_000);
successRateByItems = (byte)Math.Min(79, randomArmors.Sum(this._priceCalculator.CalculateSellingPrice) * 100 / 1_000_000);
}

return null;
}

/// <inheritdoc/>
protected override async ValueTask<List<Item>> CreateOrModifyResultItemsAsync(IList<CraftingRequiredItemLink> requiredItems, Player player, byte socketIndex)
protected override async ValueTask<List<Item>> CreateOrModifyResultItemsAsync(IList<CraftingRequiredItemLink> requiredItems, Player player, byte socketIndex, byte successRate)
{
var fenrir = requiredItems.First(i => i.ItemRequirement.Reference == 1).Items.First();
fenrir.Durability = 255;

IEnumerable<IncreasableItemOption> fenrirOptions;
if (requiredItems.Any(i => i.ItemRequirement.Reference == 2))
Expand Down
42 changes: 23 additions & 19 deletions src/GameLogic/PlayerActions/Craftings/FenrirUpgradeCraftingGold.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,33 +31,36 @@ protected override int GetPrice(byte successRate, IList<CraftingRequiredItemLink
items = new List<CraftingRequiredItemLink>(4);
var inputItems = player.TemporaryStorage!.Items.ToList();
var itemsLevelAndOption4gold = inputItems
.Where(item => item.Level >= 11
&& item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Option)
&& item.ItemOptions.Any(e => e.ItemOption?.OptionType == ItemOptionTypes.Excellent))
.ToList();
.Where(item => item.Level >= 11
&& item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Option)
&& item.IsExcellent())
.ToList();
var itemsLevelAndOption4 = inputItems
.Where(item => (item.Level >= 11 && !item.ItemOptions.Any(e => e.ItemOption?.OptionType == ItemOptionTypes.Excellent)
&& item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Option))
|| (item.Level >= 4 && item.Level <= 10 && item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Option)))
.ToList();
.Where(item => (item.Level >= 11
&& item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Option)
&& !item.IsExcellent())
|| (item.Level >= 4
&& item.Level <= 10
&& item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Option)))
.ToList();
var randomWeapons = itemsLevelAndOption4
.Where(item => item.IsWearable()
&& item.Definition!.BasePowerUpAttributes.Any(a =>
a.TargetAttribute == Stats.MaximumPhysBaseDmgByWeapon))
&& item.Definition!.BasePowerUpAttributes.Any(a => a.TargetAttribute == Stats.AttackSpeedByWeapon))
.ToList();

var randomArmors = itemsLevelAndOption4
.Where(item => item.IsWearable() && item.Definition!.BasePowerUpAttributes.Any(a => a.TargetAttribute == Stats.DefenseBase))
.Where(item => item.IsWearable()
&& item.Definition!.BasePowerUpAttributes.Any(a => a.TargetAttribute == Stats.DefenseBase))
.ToList();

var randomWeaponsGold = itemsLevelAndOption4gold
.Where(item => item.IsWearable()
&& item.Definition!.BasePowerUpAttributes.Any(a =>
a.TargetAttribute == Stats.MaximumPhysBaseDmgByWeapon))
&& item.Definition!.BasePowerUpAttributes.Any(a => a.TargetAttribute == Stats.AttackSpeedByWeapon))
.ToList();

var randomArmorsGold = itemsLevelAndOption4gold
.Where(item => item.IsWearable() && item.Definition!.BasePowerUpAttributes.Any(a => a.TargetAttribute == Stats.DefenseBase))
.Where(item => item.IsWearable()
&& item.Definition!.BasePowerUpAttributes.Any(a => a.TargetAttribute == Stats.DefenseBase))
.ToList();

if (randomArmors.Any() && randomWeapons.Any())
Expand Down Expand Up @@ -131,34 +134,35 @@ protected override int GetPrice(byte successRate, IList<CraftingRequiredItemLink
if (randomWeapons.Any())
{
items.Add(new CraftingRequiredItemLink(randomWeapons, new TransientItemCraftingRequiredItem { MinimumAmount = 1, MaximumAmount = 1, Reference = 2 }));
successRateByItems = (byte)Math.Min(79, randomWeapons.Sum(this._priceCalculator.CalculateBuyingPrice) * 100 / 1_000_000);
successRateByItems = (byte)Math.Min(79, randomWeapons.Sum(this._priceCalculator.CalculateSellingPrice) * 100 / 1_000_000);
}

if (randomArmors.Any())
{
items.Add(new CraftingRequiredItemLink(randomArmors, new TransientItemCraftingRequiredItem { MinimumAmount = 1, MaximumAmount = 1, Reference = 3 }));
successRateByItems = (byte)Math.Min(79, randomArmors.Sum(this._priceCalculator.CalculateBuyingPrice) * 100 / 1_000_000);
successRateByItems = (byte)Math.Min(79, randomArmors.Sum(this._priceCalculator.CalculateSellingPrice) * 100 / 1_000_000);
}

if (randomWeaponsGold.Any())
{
items.Add(new CraftingRequiredItemLink(randomWeaponsGold, new TransientItemCraftingRequiredItem { MinimumAmount = 1, MaximumAmount = 1, Reference = 4 }));
successRateByItems = (byte)Math.Min(79, randomWeaponsGold.Sum(this._priceCalculator.CalculateBuyingPrice) * 100 / 1_000_000);
successRateByItems = (byte)Math.Min(79, randomWeaponsGold.Sum(this._priceCalculator.CalculateSellingPrice) * 100 / 1_000_000);
}

if (randomArmorsGold.Any())
{
items.Add(new CraftingRequiredItemLink(randomArmorsGold, new TransientItemCraftingRequiredItem { MinimumAmount = 1, MaximumAmount = 1, Reference = 4 }));
successRateByItems = (byte)Math.Min(79, randomArmorsGold.Sum(this._priceCalculator.CalculateBuyingPrice) * 100 / 1_000_000);
successRateByItems = (byte)Math.Min(79, randomArmorsGold.Sum(this._priceCalculator.CalculateSellingPrice) * 100 / 1_000_000);
}

return null;
}

/// <inheritdoc/>
protected override async ValueTask<List<Item>> CreateOrModifyResultItemsAsync(IList<CraftingRequiredItemLink> requiredItems, Player player, byte socketIndex)
protected override async ValueTask<List<Item>> CreateOrModifyResultItemsAsync(IList<CraftingRequiredItemLink> requiredItems, Player player, byte socketIndex, byte successRate)
{
var fenrir = requiredItems.First(i => i.ItemRequirement.Reference == 1).Items.First();
fenrir.Durability = 255;

IEnumerable<IncreasableItemOption> fenrirOptions;
if (requiredItems.Any(i => i.ItemRequirement.Reference == 2))
Expand Down
Loading

0 comments on commit ba5ace8

Please sign in to comment.