diff --git a/src/DataModel/Configuration/ItemCrafting/MixResult.cs b/src/DataModel/Configuration/ItemCrafting/MixResult.cs
index 3cba8d892..8d61672b2 100644
--- a/src/DataModel/Configuration/ItemCrafting/MixResult.cs
+++ b/src/DataModel/Configuration/ItemCrafting/MixResult.cs
@@ -20,12 +20,12 @@ public enum MixResult
StaysAsIs = 1,
///
- /// 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.
///
- DowngradedTo0 = 3,
+ ChaosWeaponAndFirstWingsDowngradedRandom = 2,
///
- /// 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.
///
- DowngradedRandom = 4,
+ ThirdWingsDowngradedRandom = 3,
}
\ No newline at end of file
diff --git a/src/DataModel/Configuration/ItemCrafting/SimpleCraftingSettings.cs b/src/DataModel/Configuration/ItemCrafting/SimpleCraftingSettings.cs
index cf05037eb..b1c9ae921 100644
--- a/src/DataModel/Configuration/ItemCrafting/SimpleCraftingSettings.cs
+++ b/src/DataModel/Configuration/ItemCrafting/SimpleCraftingSettings.cs
@@ -22,6 +22,12 @@ public partial class SimpleCraftingSettings
///
public int MoneyPerFinalSuccessPercentage { get; set; }
+ ///
+ /// 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.
+ ///
+ /// Used for Chaos Weapon and 1st Level Wing craftings.
+ public int NpcPriceDivisor { get; set; }
+
///
/// Gets or sets the success percent.
///
@@ -69,6 +75,11 @@ public partial class SimpleCraftingSettings
///
public int SuccessPercentageAdditionForAncientItem { get; set; }
+ ///
+ /// Gets or sets the success percentage addition for a "380 item" which gets modified.
+ ///
+ public int SuccessPercentageAdditionForGuardianItem { get; set; }
+
///
/// Gets or sets the success percentage addition for a socket item which gets modified.
///
diff --git a/src/DataModel/Configuration/Items/IncreasableItemOption.cs b/src/DataModel/Configuration/Items/IncreasableItemOption.cs
index 95f222570..9d846d6ed 100644
--- a/src/DataModel/Configuration/Items/IncreasableItemOption.cs
+++ b/src/DataModel/Configuration/Items/IncreasableItemOption.cs
@@ -23,7 +23,7 @@ public enum LevelType
/// It's increased by the level of the item which has the option.
///
///
- /// As far as I know, this is only required for wing options, e.g. 'Increase max HP +50~115'. That's why 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 is the default, too.
///
ItemLevel,
}
diff --git a/src/GameLogic/ItemExtensions.cs b/src/GameLogic/ItemExtensions.cs
index 34849077e..858acd630 100644
--- a/src/GameLogic/ItemExtensions.cs
+++ b/src/GameLogic/ItemExtensions.cs
@@ -102,6 +102,19 @@ public static bool IsExcellent(this Item item)
return item.ItemOptions.Any(link => link.ItemOption?.OptionType == ItemOptionTypes.Excellent);
}
+ ///
+ /// Determines whether this instance is a "380 item", that is, if it can be upgraded with Jewel of Guardian.
+ ///
+ /// The item.
+ ///
+ /// true if the specified item is a "380 item"; otherwise, false.
+ ///
+ public static bool IsGuardian(this Item item)
+ {
+ return item.Definition!.PossibleItemOptions.Any(pio => pio.PossibleOptions
+ .Any(po => po.OptionType == ItemOptionTypes.GuardianOption));
+ }
+
///
/// Determines whether this item is a defensive item.
///
diff --git a/src/GameLogic/ItemPowerUpFactory.cs b/src/GameLogic/ItemPowerUpFactory.cs
index 227328b09..6f7c0664c 100644
--- a/src/GameLogic/ItemPowerUpFactory.cs
+++ b/src/GameLogic/ItemPowerUpFactory.cs
@@ -221,7 +221,7 @@ private IEnumerable 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;
diff --git a/src/GameLogic/ItemPriceCalculator.cs b/src/GameLogic/ItemPriceCalculator.cs
index aca1a089b..5b9b58928 100644
--- a/src/GameLogic/ItemPriceCalculator.cs
+++ b/src/GameLogic/ItemPriceCalculator.cs
@@ -18,10 +18,15 @@ namespace MUnique.OpenMU.GameLogic;
public class ItemPriceCalculator
{
private const short ForceWaveSkillId = 66;
- private const long MaximumPrice = 3000000000;
+ private const short ExplosionSkillId = 223;
+ private const short RequiemSkillId = 224;
+ private const short PollutionSkillId = 225;
+ private const long MaximumPrice = 3_000_000_000;
private const float DestroyedPetPenalty = 2.0f;
private const float DestroyedItemPenalty = 1.4f;
+ private static readonly List WorthlessSkills = [ForceWaveSkillId, ExplosionSkillId, RequiemSkillId, PollutionSkillId];
+
private static readonly Dictionary DropLevelIncreaseByLevel = new()
{
{ 5, 4 },
@@ -37,21 +42,28 @@ public class ItemPriceCalculator
{ 15, 365 },
};
+ private static readonly IDictionary SpecialItemOldValueDictionary = new Dictionary
+ {
+ { (int)SpecialItems.Bless, 100_000 },
+ { (int)SpecialItems.Soul, 70_000 },
+ { (int)SpecialItems.Chaos, 40_000 },
+ { (int)SpecialItems.Life, 450_000 },
+ { (int)SpecialItems.Creation, 450_000 },
+ };
+
private static readonly IDictionary> SpecialItemDictionary = new Dictionary>
{
{
(int)SpecialItems.Arrow, item =>
{
int gold = 0;
- int baseprice = 70;
- if (item.Level == 1)
+ int baseprice = item.Level switch
{
- baseprice = 1200;
- }
- else if (item.Level == 2)
- {
- baseprice = 2000;
- }
+ 1 => 1200,
+ 2 => 2000,
+ 3 => 2800,
+ _ => 70,
+ };
if (item.Durability > 0)
{
@@ -65,15 +77,13 @@ public class ItemPriceCalculator
(int)SpecialItems.Bolt, item =>
{
int gold = 0;
- int baseprice = 100;
- if (item.Level == 1)
+ int baseprice = item.Level switch
{
- baseprice = 1400;
- }
- else if (item.Level == 2)
- {
- baseprice = 2200;
- }
+ 1 => 1400,
+ 2 => 2200,
+ 3 => 3000,
+ _ => 100,
+ };
if (item.Durability > 0)
{
@@ -88,21 +98,25 @@ public class ItemPriceCalculator
{ (int)SpecialItems.Chaos, _ => 810000 },
{ (int)SpecialItems.Life, _ => 45000000 },
{ (int)SpecialItems.Creation, _ => 36000000 },
+ { (int)SpecialItems.Guardian, _ => 60000000 },
+ { (int)SpecialItems.Gemstone, _ => 18600 },
+ { (int)SpecialItems.Harmony, _ => 18600 },
+ { (int)SpecialItems.LowerRefineStone, _ => 18600 },
+ { (int)SpecialItems.HigherRefineStone, _ => 18600 },
{ (int)SpecialItems.PackedBless, item => (item.Level + 1) * 9000000 * 10 },
{ (int)SpecialItems.PackedSoul, item => (item.Level + 1) * 6000000 * 10 },
+ { (int)SpecialItems.PackedChaos, item => (item.Level + 1) * 810000 * 10 },
{ (int)SpecialItems.PackedLife, item => (item.Level + 1) * 45000000 * 10 },
{ (int)SpecialItems.PackedCreation, item => (item.Level + 1) * 36000000 * 10 },
{ (int)SpecialItems.PackedGuardian, item => (item.Level + 1) * 60000000 * 10 },
- { (int)SpecialItems.PackedGemstone, item => (item.Level + 1) * 186000 * 10 },
- { (int)SpecialItems.PackedHarmony, item => (item.Level + 1) * 186000 * 10 },
- { (int)SpecialItems.PackedChaos, item => (item.Level + 1) * 810000 * 10 },
- { (int)SpecialItems.PackedLowerRefineStone, item => (item.Level + 1) * 186000 * 10 },
- { (int)SpecialItems.PackedHigherRefineStone, item => (item.Level + 1) * 186000 * 10 },
+ { (int)SpecialItems.PackedGemstone, item => (item.Level + 1) * 18600 * 10 },
+ { (int)SpecialItems.PackedHarmony, item => (item.Level + 1) * 18600 * 10 },
+ { (int)SpecialItems.PackedLowerRefineStone, item => (item.Level + 1) * 18600 * 10 },
+ { (int)SpecialItems.PackedHigherRefineStone, item => (item.Level + 1) * 18600 * 10 },
{ (int)SpecialItems.Fruits, _ => 33000000 },
{ (int)SpecialItems.LochFeather, item => item.Level == 1 ? 7500000 : 180000 },
- { (int)SpecialItems.JewelGuardian, _ => 60000000 },
{ (int)SpecialItems.SiegePotion, item => item.Durability() * (item.Level == 0 ? 900000 : 450000) },
- { (int)SpecialItems.OrderGuardianLifeStone, item => item.Level == 1 ? 2400000 : 0 },
+ { (int)SpecialItems.OrderGuardianLifeStone, item => item.Level == 1 ? 2400000 : 1000000 },
{ (int)SpecialItems.ContractSummon, item => item.Level == 0 ? 1500000 : item.Level == 1 ? 1200000 : 0 },
{ (int)SpecialItems.SplinterOfArmor, item => item.Durability() * 150 },
{ (int)SpecialItems.BlessOfGuardian, item => item.Durability() * 300 },
@@ -121,55 +135,58 @@ public class ItemPriceCalculator
{
(int)SpecialItems.Dinorant, item =>
{
- var gold = 960000;
- var opt = item.ItemOptions.FirstOrDefault(o => o.ItemOption?.OptionType == ItemOptionTypes.Option);
- var optionLevel = opt?.Level ?? 0;
- gold += 300000 * optionLevel;
- return gold;
+ var opts = item.ItemOptions.Where(o => o.ItemOption?.OptionType == ItemOptionTypes.Option).Count();
+ return 960000 + (300000 * opts);
}
},
- { (int)SpecialItems.DevilEye, item => item.Level == 1 ? 15000 : item.Level == 2 ? 21000 : (item.Level - 1) * 15000 },
- { (int)SpecialItems.DevilKey, item => item.Level == 1 ? 15000 : item.Level == 2 ? 21000 : (item.Level - 1) * 15000 },
- { (int)SpecialItems.DevilInvitation, item => item.Level == 1 ? 60000 : item.Level == 2 ? 84000 : (item.Level - 1) * 60000 },
- { (int)SpecialItems.RedemyOfLove, _ => 900 },
- { (int)SpecialItems.Rena, item => item.Level == 3 ? item.Durability() * 3900 : 9000 },
- { (int)SpecialItems.Ale, _ => 1000 },
- {
- (int)SpecialItems.InvisibleCloak, item => item.Level == 1 ? 150000 :
- item.Level == 2 ? 660000 :
- item.Level == 3 ? 720000 :
- item.Level == 4 ? 780000 :
- item.Level == 5 ? 840000 :
- item.Level == 6 ? 900000 :
- item.Level == 7 ? 960000 :
- item.Level == 8 ? 1200000 : 60000
- },
{
- (int)SpecialItems.BloodBone, item => item.Level == 1 ? 15000 :
- item.Level == 2 ? 21000 :
- item.Level == 3 ? 30000 :
- item.Level == 4 ? 39000 :
- item.Level == 5 ? 48000 :
- item.Level == 6 ? 60000 :
- item.Level == 7 ? 75000 :
- item.Level == 8 ? 90000 : 15000
+ (int)SpecialItems.DevilEye, item => item.Level == 1 ? 10000 :
+ item.Level == 2 ? 50000 :
+ item.Level == 3 ? 100000 :
+ item.Level == 4 ? 300000 :
+ item.Level == 5 ? 500000 :
+ item.Level == 6 ? 800000 :
+ item.Level == 7 ? 1000000 : 10000
},
{
- (int)SpecialItems.ScrollOfArchangel, item => item.Level == 1 ? 15000 :
- item.Level == 2 ? 21000 :
- item.Level == 3 ? 30000 :
- item.Level == 4 ? 39000 :
- item.Level == 5 ? 48000 :
- item.Level == 6 ? 60000 :
- item.Level == 7 ? 75000 :
- item.Level == 8 ? 90000 : 15000
+ (int)SpecialItems.DevilKey, item => item.Level == 1 ? 15000 :
+ item.Level == 2 ? 75000 :
+ item.Level == 3 ? 150000 :
+ item.Level == 4 ? 450000 :
+ item.Level == 5 ? 750000 :
+ item.Level == 6 ? 1200000 :
+ item.Level == 7 ? 1500000 : 15000
},
+ { (int)SpecialItems.DevilInvitation, item => item.Level is 1 ? 60000 : item.Level == 2 ? 84000 : (item.Level - 1) * 60000 }, // +7 sell price on S6E3 client is 60k (same as +4). Bug?
+ { (int)SpecialItems.RemedyOfLove, _ => 900 },
+ { (int)SpecialItems.Rena, item => item.Level == 3 ? item.Durability() * 3900 : 9000 },
+ { (int)SpecialItems.Ale, _ => 750 },
+ { (int)SpecialItems.InvisibleCloak, item => item.Level == 1 ? 150000 : 600000 + ((item.Level - 1) * 60000) },
{
- (int)SpecialItems.OldScroll, item => item.Level == 1 ? 500000 : (item.Level + 1) * 200000
+ (int)SpecialItems.ScrollOfArchangel, item => item.Level == 1 ? 10000 :
+ item.Level == 2 ? 50000 :
+ item.Level == 3 ? 100000 :
+ item.Level == 4 ? 300000 :
+ item.Level == 5 ? 500000 :
+ item.Level == 6 ? 800000 :
+ item.Level == 7 ? 1000000 :
+ item.Level == 8 ? 1200000 : 10000
},
{
- (int)SpecialItems.IllusionSorcererCovenant, item => item.Level == 1 ? 500000 : (item.Level + 1) * 200000
+ (int)SpecialItems.BloodBone, item => item.Level == 1 ? 10000 :
+ item.Level == 2 ? 50000 :
+ item.Level == 3 ? 100000 :
+ item.Level == 4 ? 300000 :
+ item.Level == 5 ? 500000 :
+ item.Level == 6 ? 800000 :
+ item.Level == 7 ? 1000000 :
+ item.Level == 8 ? 1200000 : 10000
},
+ { (int)SpecialItems.OldScroll, item => item.Level == 1 ? 500000 : (item.Level + 1) * 200000 },
+ { (int)SpecialItems.IllusionSorcererCovenant, item => item.Level == 1 ? 500000 : (item.Level + 1) * 200000 },
+ { (int)SpecialItems.ScrollOfBlood, item => item.Level == 1 ? 500000 : (item.Level + 1) * 200000 },
+ { (int)SpecialItems.FlameOfCondor, _ => 3000000 },
+ { (int)SpecialItems.FeatherOfCondor, _ => 3000000 },
{ (int)SpecialItems.ArmorGuardman, _ => 5000 },
{ (int)SpecialItems.WizardsRing, item => item.Level == 0 ? 30000 : 0 },
{ (int)SpecialItems.SpiritPet, item => item.Level == 0 ? 30000000 : item.Level == 1 ? 15000000 : 0 },
@@ -182,6 +199,21 @@ public class ItemPriceCalculator
{ (int)SpecialItems.Halloween5, item => 150 * item.Durability() },
{ (int)SpecialItems.Halloween6, item => 150 * item.Durability() },
{ (int)SpecialItems.GemOfSecret, item => item.Level == 0 ? 60000 : 0 },
+ { (int)SpecialItems.SuspiciousScrapOfPaper, item => 30000 * item.Durability() },
+ { (int)SpecialItems.GaionsOrder, item => 30000 * item.Durability() },
+ { (int)SpecialItems.FirstSecromiconFragment, item => 30000 * item.Durability() },
+ { (int)SpecialItems.SecondSecromiconFragment, item => 30000 * item.Durability() },
+ { (int)SpecialItems.ThirdSecromiconFragment, item => 30000 * item.Durability() },
+ { (int)SpecialItems.FourthSecromiconFragment, item => 30000 * item.Durability() },
+ { (int)SpecialItems.FifthSecromiconFragment, item => 30000 * item.Durability() },
+ { (int)SpecialItems.SixthSecromiconFragment, item => 30000 * item.Durability() },
+ { (int)SpecialItems.CompleteSecromicon, item => 30000 * item.Durability() },
+ { (int)SpecialItems.ChristmasStar, _ => 200000 },
+ { (int)SpecialItems.Firecracker, _ => 200000 },
+ { (int)SpecialItems.CherryBlossomWine, item => 300 * item.Durability() },
+ { (int)SpecialItems.CherryBlossomRiceCake, item => 300 * item.Durability() },
+ { (int)SpecialItems.CherryBlossomFlowerPetal, item => 300 * item.Durability() },
+ { (int)SpecialItems.GoldenCherryBlossomBranch, item => 300 * item.Durability() },
};
private enum SpecialItems
@@ -193,19 +225,23 @@ private enum SpecialItems
Chaos = 0xF0C, // getId(12,15),
Life = 0x100E, // getId(14,16),
Creation = 0x160E, // getId(14,22),
+ Guardian = 0x1F0E, // getId(14,31),
+ Gemstone = 0x290E,
+ Harmony = 0x2A0E,
+ LowerRefineStone = 0x2B0E,
+ HigherRefineStone = 0x2C0E,
PackedBless = 0x1E0C, // getId(12,30),
PackedSoul = 0x1F0C, // getId(12,31),
+ PackedChaos = 0x8D0C,
PackedLife = 0x880C,
PackedCreation = 0x890C,
PackedGuardian = 0x8A0C,
PackedGemstone = 0x8B0C,
PackedHarmony = 0x8C0C,
- PackedChaos = 0x8D0C,
PackedLowerRefineStone = 0x8E0C,
PackedHigherRefineStone = 0x8F0C,
Fruits = 0xF0D, // getId(13,15),
LochFeather = 0xE0D, // getId(13,14),
- JewelGuardian = 0x1F0E, // getId(14,31),
LargeHealPotion = 0x030E,
LargeManaPotion = 0x060E,
SiegePotion = 0x70E, // getId(14,7),
@@ -220,14 +256,14 @@ private enum SpecialItems
SmallSdPotion = 0x230E, // getId(14,35),
SdPotion = 0x240E, // getId(14, 36),
LargeSdPotion = 0x250E, // getId(14,37),
- SmallComplexPotion = 0x280E, // getId(14,38),
- ComplexPotion = 0x290E, // getId(14,39),
- LargeComplexPotion = 0x2A0E, // getId(14,40),
+ SmallComplexPotion = 0x260E, // getId(14,38),
+ ComplexPotion = 0x270E, // getId(14,39),
+ LargeComplexPotion = 0x280E, // getId(14,40),
Dinorant = 0x30D, // getId(13,3),
DevilEye = 0x110E, // getId(14,17),
DevilKey = 0x120E, // getId(14,18),
DevilInvitation = 0x130E, // getId(14,19),
- RedemyOfLove = 0x140E, // getId(14,20),
+ RemedyOfLove = 0x140E, // getId(14,20),
Rena = 0x150E, // getId(14,21),
Ale = 0x90E, // getId(14,9),
InvisibleCloak = 0x120D, // getId(13,18),
@@ -247,25 +283,62 @@ private enum SpecialItems
GemOfSecret = 0x1A0C, // getId(12,26),
OldScroll = 0x310D,
IllusionSorcererCovenant = 0x320D,
+ ScrollOfBlood = 0x330D,
+ FlameOfCondor = 0x340D,
+ FeatherOfCondor = 0x350D,
+ SuspiciousScrapOfPaper = 0x650E,
+ GaionsOrder = 0x660E,
+ FirstSecromiconFragment = 0x670E,
+ SecondSecromiconFragment = 0x680E,
+ ThirdSecromiconFragment = 0x690E,
+ FourthSecromiconFragment = 0x6A0E,
+ FifthSecromiconFragment = 0x6B0E,
+ SixthSecromiconFragment = 0x6C0E,
+ CompleteSecromicon = 0x6D0E,
+ ChristmasStar = 0x330E,
+ Firecracker = 0x3F0E,
+ CherryBlossomWine = 0x550E,
+ CherryBlossomRiceCake = 0x560E,
+ CherryBlossomFlowerPetal = 0x570E,
+ GoldenCherryBlossomBranch = 0x5A0E,
}
+ ///
+ /// Calculates the selling price of the item for its maximum durability.
+ ///
+ /// The item.
+ /// The selling price.
+ public long CalculateSellingPrice(Item item) => this.CalculateSellingPrice(item, item.GetMaximumDurabilityOfOnePiece());
+
///
/// Calculates the selling price of the item, which the player gets if he is selling an item to a merchant.
- /// It's usually a third of the buying price.
+ /// It's usually a third of the buying price, minus a durability factor.
///
/// The item.
+ /// The current durability of the .
/// The selling price.
- public long CalculateSellingPrice(Item item)
+ public long CalculateSellingPrice(Item item, byte durability)
{
item.ThrowNotInitializedProperty(item.Definition is null, nameof(item.Definition));
- var sellingPrice = this.CalculateBuyingPrice(item) / 3;
- if (item.Definition.Group == 14 && (item.Definition.Number <= 8))
+ var sellingPrice = CalculateBuyingPrice(item) / 3;
+ if (item.Definition.Group == 14 && item.Definition.Number <= 8)
{
// Potions + Antidote
return sellingPrice / 10 * 10;
}
+ if (!item.IsTrainablePet())
+ {
+ var maxDurability = item.GetMaximumDurabilityOfOnePiece();
+ if (maxDurability > 1 && maxDurability > durability)
+ {
+ float multiplier = 1.0f - ((float)durability / maxDurability);
+ long loss = (long)(sellingPrice * 0.6 * multiplier);
+ sellingPrice -= loss;
+ }
+ }
+
return RoundPrice(sellingPrice);
}
@@ -282,10 +355,9 @@ public long CalculateRepairPrice(Item item, bool npcDiscount)
return 0;
}
- const long maximumBasePrice = 400000000;
+ const long maximumBasePrice = 400_000_000;
var isPet = item.IsTrainablePet();
- var maximumDurability = item.GetMaximumDurabilityOfOnePiece();
- var basePrice = Math.Min(isPet ? CalculateBuyingPrice(item, maximumDurability) : CalculateBuyingPrice(item, maximumDurability) / 3, maximumBasePrice);
+ var basePrice = Math.Min(this.CalculateFinalBuyingPrice(item) / (isPet ? 1 : 3), maximumBasePrice);
basePrice = RoundPrice(basePrice);
float squareRootOfBasePrice = (float)Math.Sqrt(basePrice);
@@ -313,20 +385,74 @@ public long CalculateRepairPrice(Item item, bool npcDiscount)
}
///
- /// Calculates the buying price of the item, which the player has to pay if he wants to buy the item from a merchant.
+ /// Calculates the final buying price of the item, which the player has to pay if he wants to buy the item from a merchant.
///
/// The item.
/// The buying price.
- public long CalculateBuyingPrice(Item item) => CalculateBuyingPrice(item, item.Durability());
+ public long CalculateFinalBuyingPrice(Item item) => RoundPrice(CalculateBuyingPrice(item));
+
+ ///
+ /// Calculates the final "old" buying price of the item.
+ /// Supposedly in earlier versions jewel reference prices were different, and those were used since for Chaos Weapon and First Wings craftings rate calculations.
+ ///
+ /// The item.
+ /// The "old" buying price.
+ public long CalculateFinalOldBuyingPrice(Item item)
+ {
+ if (SpecialItemOldValueDictionary.TryGetValue(GetId(item.Definition!.Group, item.Definition.Number), out var oldValue))
+ {
+ return RoundPrice(oldValue);
+ }
+ else
+ {
+ return this.CalculateFinalBuyingPrice(item);
+ }
+ }
+
+ private static int GetId(byte group, int id)
+ {
+ return (id << 8) + group;
+ }
+
+ private static long RoundPrice(long price)
+ {
+ var result = price;
+ if (result >= 1000)
+ {
+ result = result / 100 * 100;
+ }
+ else if (result >= 100)
+ {
+ result = result / 10 * 10;
+ }
+ else
+ {
+ // no rounding for smaller values.
+ }
+
+ return result;
+ }
- private static long CalculateBuyingPrice(Item item, byte durability)
+ private static long CalculateBuyingPrice(Item item)
{
item.ThrowNotInitializedProperty(item.Definition is null, nameof(item.Definition));
var definition = item.Definition!;
if (definition.Value > 0 && (definition.Group == 15 || definition.Group == 12))
{
- return RoundPrice(definition.Value);
+ return definition.Value;
+ }
+
+ if (item.IsTrainablePet())
+ {
+ if (item.IsDarkRaven())
+ {
+ return item.Level * 1_000_000;
+ }
+ else
+ {
+ return item.Level * 2_000_000;
+ }
}
long price = 0;
@@ -343,7 +469,7 @@ private static long CalculateBuyingPrice(Item item, byte durability)
}
else if (definition.Value > 0)
{
- price += (definition.Value * definition.Value * 10) / 12;
+ price += definition.Value * definition.Value * 10 / 12;
if (item.Definition.Group == 14 && (item.Definition.Number <= 8))
{
// Potions + Antidote
@@ -357,9 +483,21 @@ private static long CalculateBuyingPrice(Item item, byte durability)
return price;
}
}
- else if ((item.Definition.Group == 12 && item.Definition.Number > 6) || item.Definition.Group == 13 || item.Definition.Group == 15)
+ else if ((item.Definition.Group == 12
+ && ((item.Definition.Number > 6 && item.Definition.Number < 36)
+ || (item.Definition.Number > 43 && item.Definition.Number != 50)))
+ || item.Definition.Group == 13
+ || item.Definition.Group == 15)
{
+ // Cape of Lord and Cape of Fighter go here
price = (dropLevel * dropLevel * dropLevel) + 100;
+
+ if (item.ItemOptions.FirstOrDefault(o => o.ItemOption?.OptionType == ItemOptionTypes.Option) is { } opt
+ && opt.ItemOption?.PowerUpDefinition?.TargetAttribute == Stats.HealthRecoveryMultiplier)
+ {
+ // Rings, pendants, and capes. Capes with physical damage option have no extra value (possibly a source bug).
+ price += price * opt.Level;
+ }
}
else
{
@@ -379,14 +517,14 @@ private static long CalculateBuyingPrice(Item item, byte durability)
price = ((dropLevel + 40) * dropLevel * dropLevel / 8) + 100;
}
- var isOneHandedWeapon = item.Definition.Group < 6 && definition.Width < 2 && definition.BasePowerUpAttributes.Any(o => o.TargetAttribute == Stats.MinimumPhysBaseDmg);
+ var isOneHandedWeapon = item.Definition.Group < 6 && definition.Width < 2;
var isShield = item.Definition.Group == 6;
if (isOneHandedWeapon || isShield)
{
price = price * 80 / 100;
}
- if (item.HasSkill && definition.Skill?.Number != ForceWaveSkillId)
+ if (item.HasSkill && !WorthlessSkills.Contains(definition.Skill?.Number ?? 0))
{
price += (long)(price * 1.5);
}
@@ -438,38 +576,6 @@ private static long CalculateBuyingPrice(Item item, byte durability)
price = MaximumPrice;
}
- var maxDurability = item.GetMaximumDurabilityOfOnePiece();
- if (maxDurability > 1 && maxDurability > durability)
- {
- float multiplier = 1.0f - ((float)durability / maxDurability);
- long loss = (long)(price * 0.6 * multiplier);
- price -= loss;
- }
-
- return RoundPrice(price);
- }
-
- private static int GetId(byte group, int id)
- {
- return (id << 8) + group;
- }
-
- private static long RoundPrice(long price)
- {
- var result = price;
- if (result >= 1000)
- {
- result = result / 100 * 100;
- }
- else if (result >= 100)
- {
- result = result / 10 * 10;
- }
- else
- {
- // no rounding for smaller values.
- }
-
- return result;
+ return price;
}
}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/Craftings/BaseEventTicketCrafting.cs b/src/GameLogic/PlayerActions/Craftings/BaseEventTicketCrafting.cs
index 0c803bb2f..ba8e0e515 100644
--- a/src/GameLogic/PlayerActions/Craftings/BaseEventTicketCrafting.cs
+++ b/src/GameLogic/PlayerActions/Craftings/BaseEventTicketCrafting.cs
@@ -100,7 +100,7 @@ protected sealed override int GetPrice(byte successRate, IList
- protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketIndex)
+ protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketIndex, byte successRate)
{
var item = player.PersistenceContext.CreateNew- ();
item.Definition = player.GameContext.Configuration.Items.First(i => i.Name == this._resultItemName);
diff --git a/src/GameLogic/PlayerActions/Craftings/ChaosWeaponAndFirstWingsCrafting.cs b/src/GameLogic/PlayerActions/Craftings/ChaosWeaponAndFirstWingsCrafting.cs
new file mode 100644
index 000000000..382865840
--- /dev/null
+++ b/src/GameLogic/PlayerActions/Craftings/ChaosWeaponAndFirstWingsCrafting.cs
@@ -0,0 +1,67 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.GameLogic.PlayerActions.Craftings;
+
+using MUnique.OpenMU.DataModel.Configuration.ItemCrafting;
+using MUnique.OpenMU.DataModel.Configuration.Items;
+using MUnique.OpenMU.GameLogic.PlayerActions.Items;
+
+///
+/// Crafting for Chaos Weapon and First Wings.
+///
+public class ChaosWeaponAndFirstWingsCrafting : SimpleItemCraftingHandler
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The settings.
+ public ChaosWeaponAndFirstWingsCrafting(SimpleCraftingSettings settings)
+ : base(settings)
+ {
+ }
+
+ ///
+ 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();
+ link.ItemOption = option.PossibleOptions.First();
+ link.Level = 3 - i;
+ resultItem.ItemOptions.Add(link);
+ }
+ }
+ }
+
+ ///
+ 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();
+ luckOption.ItemOption = luck.PossibleOptions.First();
+ resultItem.ItemOptions.Add(luckOption);
+ }
+ }
+
+ ///
+ protected override void AddRandomSkill(Item resultItem, byte successRate)
+ {
+ if (Rand.NextRandomBool((successRate / 5) + 6)
+ && !resultItem.HasSkill
+ && resultItem.Definition!.Skill is { })
+ {
+ resultItem.HasSkill = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/Craftings/DevilSquareTicketCrafting.cs b/src/GameLogic/PlayerActions/Craftings/DevilSquareTicketCrafting.cs
index fc2899e5b..fe606a3ce 100644
--- a/src/GameLogic/PlayerActions/Craftings/DevilSquareTicketCrafting.cs
+++ b/src/GameLogic/PlayerActions/Craftings/DevilSquareTicketCrafting.cs
@@ -35,6 +35,6 @@ protected override int GetPrice(int eventLevel)
///
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
}
}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/Craftings/DinorantCrafting.cs b/src/GameLogic/PlayerActions/Craftings/DinorantCrafting.cs
new file mode 100644
index 000000000..eb2f29bbd
--- /dev/null
+++ b/src/GameLogic/PlayerActions/Craftings/DinorantCrafting.cs
@@ -0,0 +1,86 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+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;
+
+///
+/// Crafting for Dinorant.
+///
+public class DinorantCrafting : SimpleItemCraftingHandler
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The settings.
+ public DinorantCrafting(SimpleCraftingSettings settings)
+ : base(settings)
+ {
+ }
+
+ ///
+ protected override CraftingResult? TryGetRequiredItems(Player player, out IList 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;
+ }
+
+ ///
+ 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();
+ 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();
+ 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/Craftings/FenrirUpgradeCrafting.cs b/src/GameLogic/PlayerActions/Craftings/FenrirUpgradeCrafting.cs
index 083902394..6e48e4c89 100644
--- a/src/GameLogic/PlayerActions/Craftings/FenrirUpgradeCrafting.cs
+++ b/src/GameLogic/PlayerActions/Craftings/FenrirUpgradeCrafting.cs
@@ -31,16 +31,15 @@ protected override int GetPrice(byte successRate, IList 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())
@@ -82,22 +81,22 @@ protected override int GetPrice(byte successRate, IList
- protected override async ValueTask
> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketIndex)
+ protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketIndex, byte successRate)
{
var fenrir = requiredItems.First(i => i.ItemRequirement.Reference == 1).Items.First();
+ fenrir.Durability = 255;
IEnumerable fenrirOptions;
if (requiredItems.Any(i => i.ItemRequirement.Reference == 2))
diff --git a/src/GameLogic/PlayerActions/Craftings/FenrirUpgradeCraftingGold.cs b/src/GameLogic/PlayerActions/Craftings/FenrirUpgradeCraftingGold.cs
index b2ff1b36b..7b9aee1de 100644
--- a/src/GameLogic/PlayerActions/Craftings/FenrirUpgradeCraftingGold.cs
+++ b/src/GameLogic/PlayerActions/Craftings/FenrirUpgradeCraftingGold.cs
@@ -31,33 +31,36 @@ protected override int GetPrice(byte successRate, IList(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())
@@ -131,34 +134,35 @@ protected override int GetPrice(byte successRate, IList
- protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketIndex)
+ protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketIndex, byte successRate)
{
var fenrir = requiredItems.First(i => i.ItemRequirement.Reference == 1).Items.First();
+ fenrir.Durability = 255;
IEnumerable fenrirOptions;
if (requiredItems.Any(i => i.ItemRequirement.Reference == 2))
diff --git a/src/GameLogic/PlayerActions/Craftings/GuardianOptionCrafting.cs b/src/GameLogic/PlayerActions/Craftings/GuardianOptionCrafting.cs
index 78db6da90..3decf0645 100644
--- a/src/GameLogic/PlayerActions/Craftings/GuardianOptionCrafting.cs
+++ b/src/GameLogic/PlayerActions/Craftings/GuardianOptionCrafting.cs
@@ -29,7 +29,7 @@ public GuardianOptionCrafting(SimpleCraftingSettings settings)
public static byte ItemReference { get; } = 0x88;
///
- protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot)
+ protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot, byte successRate)
{
var item = requiredItems.First(i => i.ItemRequirement.Reference == ItemReference && i.Items.Any()).Items.First();
foreach (var optionDefinition in item.Definition!.PossibleItemOptions.First(o => o.PossibleOptions.Any(p => p.OptionType == ItemOptionTypes.GuardianOption)).PossibleOptions)
diff --git a/src/GameLogic/PlayerActions/Craftings/IllusionTempleTicketCrafting.cs b/src/GameLogic/PlayerActions/Craftings/IllusionTempleTicketCrafting.cs
index 32c656e9d..0451c4f33 100644
--- a/src/GameLogic/PlayerActions/Craftings/IllusionTempleTicketCrafting.cs
+++ b/src/GameLogic/PlayerActions/Craftings/IllusionTempleTicketCrafting.cs
@@ -19,9 +19,6 @@ public IllusionTempleTicketCrafting()
{
}
- ///
- protected override CraftingResult IncorrectMixItemsResult => CraftingResult.IncorrectBloodCastleItems;
-
///
protected override int GetPrice(int eventLevel)
{
diff --git a/src/GameLogic/PlayerActions/Craftings/MountSeedSphereCrafting.cs b/src/GameLogic/PlayerActions/Craftings/MountSeedSphereCrafting.cs
index 5c68b5658..e7a7842a9 100644
--- a/src/GameLogic/PlayerActions/Craftings/MountSeedSphereCrafting.cs
+++ b/src/GameLogic/PlayerActions/Craftings/MountSeedSphereCrafting.cs
@@ -34,7 +34,7 @@ public MountSeedSphereCrafting(SimpleCraftingSettings settings)
public static byte SocketItemReference { get; } = 0x88;
///
- protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot)
+ protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot, byte successRate)
{
var seedSphere = requiredItems.Single(i => i.ItemRequirement.Reference == SeedSphereReference).Items.Single();
var socketItem = requiredItems.Single(i => i.ItemRequirement.Reference == SocketItemReference).Items.Single();
diff --git a/src/GameLogic/PlayerActions/Craftings/RefineStoneCrafting.cs b/src/GameLogic/PlayerActions/Craftings/RefineStoneCrafting.cs
index 7689fe174..7f870e24c 100644
--- a/src/GameLogic/PlayerActions/Craftings/RefineStoneCrafting.cs
+++ b/src/GameLogic/PlayerActions/Craftings/RefineStoneCrafting.cs
@@ -135,7 +135,7 @@ protected override bool RequiredItemMatches(Item item, ItemCraftingRequiredItem
}
///
- protected override async ValueTask> CreateOrModifyResultItemsAsync(IList referencedItems, Player player, byte socketSlot)
+ protected override async ValueTask> CreateOrModifyResultItemsAsync(IList referencedItems, Player player, byte socketSlot, byte successRate)
{
var higherRefineStoneItems = referencedItems
.FirstOrDefault(r => r.ItemRequirement.Reference == HigherRefineStoneReference)?.Items.Count() ?? 0;
diff --git a/src/GameLogic/PlayerActions/Craftings/RemoveSeedSphereCrafting.cs b/src/GameLogic/PlayerActions/Craftings/RemoveSeedSphereCrafting.cs
index c640af903..2c722e8b5 100644
--- a/src/GameLogic/PlayerActions/Craftings/RemoveSeedSphereCrafting.cs
+++ b/src/GameLogic/PlayerActions/Craftings/RemoveSeedSphereCrafting.cs
@@ -28,7 +28,7 @@ public RemoveSeedSphereCrafting(SimpleCraftingSettings settings)
public static byte SocketItemReference { get; } = 0x88;
///
- protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot)
+ protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot, byte successRate)
{
var socketItem = requiredItems.Single(i => i.ItemRequirement.Reference == SocketItemReference).Items.Single();
diff --git a/src/GameLogic/PlayerActions/Craftings/RestoreItemCrafting.cs b/src/GameLogic/PlayerActions/Craftings/RestoreItemCrafting.cs
index f82a46df8..636deffdf 100644
--- a/src/GameLogic/PlayerActions/Craftings/RestoreItemCrafting.cs
+++ b/src/GameLogic/PlayerActions/Craftings/RestoreItemCrafting.cs
@@ -34,7 +34,7 @@ protected override int GetPrice(byte successRate, IList
- protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot)
+ protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot, byte successRate)
{
var item = requiredItems.First().Items.First();
var johOptionLink = item.ItemOptions.First(link => link.ItemOption?.OptionType == ItemOptionTypes.HarmonyOption);
diff --git a/src/GameLogic/PlayerActions/Craftings/SecondWingsCrafting.cs b/src/GameLogic/PlayerActions/Craftings/SecondWingsCrafting.cs
new file mode 100644
index 000000000..9035532c1
--- /dev/null
+++ b/src/GameLogic/PlayerActions/Craftings/SecondWingsCrafting.cs
@@ -0,0 +1,55 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.GameLogic.PlayerActions.Craftings;
+
+using MUnique.OpenMU.DataModel.Configuration.ItemCrafting;
+using MUnique.OpenMU.DataModel.Configuration.Items;
+using MUnique.OpenMU.GameLogic.PlayerActions.Items;
+
+///
+/// Crafting for Second Wings (including first capes).
+///
+public class SecondWingsCrafting : SimpleItemCraftingHandler
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The settings.
+ public SecondWingsCrafting(SimpleCraftingSettings settings)
+ : base(settings)
+ {
+ }
+
+ ///
+ protected override void AddRandomItemOption(Item resultItem, Player player, byte successRate)
+ {
+ if (resultItem.Definition!.PossibleItemOptions.Where(o => o.PossibleOptions.Any(p => p.OptionType == ItemOptionTypes.Option))
+ is { } options && options.Any())
+ {
+ (int chance, int level) = Rand.NextInt(0, 3) switch
+ {
+ 0 => (20, 1),
+ 1 => (10, 2),
+ _ => (4, 3),
+ }; // From 300 created wings about 20+10+4=34 (~11%) will have item option
+
+ if (Rand.NextRandomBool(chance))
+ {
+ var link = player.PersistenceContext.CreateNew();
+ link.Level = level;
+ if (options.Count() > 1)
+ {
+ link.ItemOption = options.ElementAt(Rand.NextInt(0, 2)).PossibleOptions.First();
+ }
+ else
+ {
+ link.ItemOption = options.ElementAt(0).PossibleOptions.First(); // Cape of Lord
+ }
+
+ resultItem.ItemOptions.Add(link);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/Craftings/SeedSphereCrafting.cs b/src/GameLogic/PlayerActions/Craftings/SeedSphereCrafting.cs
index 32b69e64f..44fa69451 100644
--- a/src/GameLogic/PlayerActions/Craftings/SeedSphereCrafting.cs
+++ b/src/GameLogic/PlayerActions/Craftings/SeedSphereCrafting.cs
@@ -34,7 +34,7 @@ public SeedSphereCrafting(SimpleCraftingSettings settings)
public static byte SeedReference { get; } = 0x77;
///
- protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot)
+ protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot, byte successRate)
{
var seed = requiredItems.Single(i => i.ItemRequirement.Reference == SeedReference).Items.Single();
var sphere = requiredItems.Single(i => i.ItemRequirement.Reference == SphereReference).Items.Single();
diff --git a/src/GameLogic/PlayerActions/Craftings/ThirdWingsCrafting.cs b/src/GameLogic/PlayerActions/Craftings/ThirdWingsCrafting.cs
new file mode 100644
index 000000000..b6615f432
--- /dev/null
+++ b/src/GameLogic/PlayerActions/Craftings/ThirdWingsCrafting.cs
@@ -0,0 +1,83 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.GameLogic.PlayerActions.Craftings;
+
+using MUnique.OpenMU.DataModel.Configuration.ItemCrafting;
+using MUnique.OpenMU.DataModel.Configuration.Items;
+using MUnique.OpenMU.GameLogic.PlayerActions.Items;
+
+///
+/// Crafting for Third Wings.
+///
+public class ThirdWingsCrafting : SimpleItemCraftingHandler
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The settings.
+ public ThirdWingsCrafting(SimpleCraftingSettings settings)
+ : base(settings)
+ {
+ }
+
+ ///
+ protected override void AddRandomItemOption(Item resultItem, Player player, byte successRate)
+ {
+ if (resultItem.Definition!.PossibleItemOptions.Where(o => o.PossibleOptions.Any(p => p.OptionType == ItemOptionTypes.Option))
+ is { } options && options.Any())
+ {
+ (int chance1, int level) = Rand.NextInt(0, 4) switch
+ {
+ 0 => (0, 0),
+ 1 => (12, 1),
+ 2 => (6, 2),
+ _ => (3, 3),
+ }; // From 400 created wings about 0+12+6+3=21 (~5%) will have item option
+
+ if (Rand.NextRandomBool(chance1))
+ {
+ var link = player.PersistenceContext.CreateNew();
+ link.Level = level;
+ (int chance2, int type) = Rand.NextRandomBool()
+ ? (40, 1)
+ : (30, 2);
+
+ if (Rand.NextRandomBool(chance2))
+ {
+ link.ItemOption = options.ElementAt(type).PossibleOptions.First(); // Additional dmg (phys, wiz, curse) or defense
+ }
+ else
+ {
+ link.ItemOption = options.ElementAt(0).PossibleOptions.First(); // HP recovery %
+ }
+
+ resultItem.ItemOptions.Add(link);
+ }
+ }
+ }
+
+ ///
+ protected override void AddRandomExcellentOptions(Item resultItem, Player player)
+ {
+ if (resultItem.Definition!.PossibleItemOptions.FirstOrDefault(o => o.PossibleOptions.Any(p => p.OptionType == ItemOptionTypes.Wing))
+ is { } wingOption)
+ {
+ (int chance, int type) = Rand.NextInt(0, 4) switch
+ {
+ 0 => (4, 0), // Ignore def
+ 1 => (2, 1), // 5% full reflect
+ 2 => (7, 2), // 5% HP restore
+ _ => (7, 3), // 5% mana restore
+ }; // From 400 created wings about 4+2+7+7=20 (5%) will have wing "exc" option
+
+ if (Rand.NextRandomBool(chance))
+ {
+ var link = player.PersistenceContext.CreateNew();
+ link.ItemOption = wingOption.PossibleOptions.ElementAt(type);
+ resultItem.ItemOptions.Add(link);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GameLogic/PlayerActions/Items/BaseItemCraftingHandler.cs b/src/GameLogic/PlayerActions/Items/BaseItemCraftingHandler.cs
index 8e9933378..29d870623 100644
--- a/src/GameLogic/PlayerActions/Items/BaseItemCraftingHandler.cs
+++ b/src/GameLogic/PlayerActions/Items/BaseItemCraftingHandler.cs
@@ -5,6 +5,7 @@
namespace MUnique.OpenMU.GameLogic.PlayerActions.Items;
using MUnique.OpenMU.DataModel.Configuration.ItemCrafting;
+using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.PlugIns;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.GameLogic.Views.NPC;
@@ -42,7 +43,7 @@ public abstract class BaseItemCraftingHandler : IItemCraftingHandler
if (success)
{
player.Logger.LogInformation("Crafting succeeded with success chance: {successRate} %", successRate);
- if (await this.DoTheMixAsync(items, player, socketSlot).ConfigureAwait(false) is { } item)
+ if (await this.DoTheMixAsync(items, player, socketSlot, successRate).ConfigureAwait(false) is { } item)
{
player.Logger.LogInformation("Crafted item: {item}", item);
player.BackupInventory = null;
@@ -88,8 +89,9 @@ public abstract class BaseItemCraftingHandler : IItemCraftingHandler
/// The required items.
/// The player.
/// The slot of the socket.
+ /// The success rate of the combination.
/// The created or modified items.
- protected abstract ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot);
+ protected abstract ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot, byte successRate);
///
/// Performs the crafting with the specified items.
@@ -97,17 +99,18 @@ public abstract class BaseItemCraftingHandler : IItemCraftingHandler
/// The required items.
/// The player.
/// The slot of the socket.
+ /// The success rate of the combination.
///
/// The created or modified item. If there are multiple, only the last one is returned.
///
- private async ValueTask- DoTheMixAsync(IList requiredItems, Player player, byte socketSlot)
+ private async ValueTask
- DoTheMixAsync(IList requiredItems, Player player, byte socketSlot, byte successRate)
{
foreach (var requiredItemLink in requiredItems)
{
await this.RequiredItemChangeAsync(player, requiredItemLink, true).ConfigureAwait(false);
}
- var resultItems = await this.CreateOrModifyResultItemsAsync(requiredItems, player, socketSlot).ConfigureAwait(false);
+ var resultItems = await this.CreateOrModifyResultItemsAsync(requiredItems, player, socketSlot, successRate).ConfigureAwait(false);
return resultItems.LastOrDefault();
}
@@ -134,20 +137,56 @@ private async ValueTask RequiredItemChangeAsync(Player player, CraftingRequiredI
}
break;
- case MixResult.DowngradedRandom:
+ case MixResult.ChaosWeaponAndFirstWingsDowngradedRandom:
itemLink.Items.ForEach(item =>
{
var previousLevel = item.Level;
- item.Level = (byte)Rand.NextInt(0, item.Level);
- player.Logger.LogDebug("Item {0} was downgraded from {1} to {2}.", item, previousLevel, item.Level);
+ var hadSkill = item.HasSkill;
+ var optionLowered = false;
+ var previousMaxDurability = item.GetMaximumDurabilityOfOnePiece();
+
+ item.Level = (byte)Rand.NextInt(0, previousLevel);
+ if (item.HasSkill && !item.IsExcellent() && Rand.NextRandomBool())
+ {
+ item.HasSkill = false;
+ }
+
+ if (item.ItemOptions.FirstOrDefault(o => o.ItemOption?.OptionType == ItemOptionTypes.Option) is { } optionLink && Rand.NextRandomBool())
+ {
+ optionLowered = true;
+ if (optionLink.Level > 1)
+ {
+ optionLink.Level--;
+ }
+ else
+ {
+ item.ItemOptions.Remove(optionLink);
+ }
+ }
+
+ item.Durability = item.GetMaximumDurabilityOfOnePiece() * item.Durability / previousMaxDurability;
+ player.Logger.LogDebug(
+ "Item {0} was downgraded from level {1} to {2}. Skill removed: {3}. Item option lowered by 1 level: {4}.",
+ item,
+ previousLevel,
+ item.Level,
+ hadSkill && !item.HasSkill,
+ optionLowered);
});
break;
- case MixResult.DowngradedTo0:
+ case MixResult.ThirdWingsDowngradedRandom:
itemLink.Items.ForEach(item =>
{
- player.Logger.LogDebug("Item {0} is getting downgraded to level 0.", item);
- item.Level = 0;
+ var previousLevel = item.Level;
+ item.Level -= (byte)(Rand.NextRandomBool() ? 2 : 3);
+ if (item.ItemOptions.FirstOrDefault(o => o.ItemOption?.OptionType == ItemOptionTypes.Option) is { } optionLink)
+ {
+ item.ItemOptions.Remove(optionLink);
+ }
+
+ item.Durability = item.GetMaximumDurabilityOfOnePiece();
+ player.Logger.LogDebug("Item {0} was downgraded from level {1} to {2}. Item option was removed.", item, previousLevel, item.Level);
});
break;
diff --git a/src/GameLogic/PlayerActions/Items/BuyNpcItemAction.cs b/src/GameLogic/PlayerActions/Items/BuyNpcItemAction.cs
index 0d3c43d00..b7b90ad1b 100644
--- a/src/GameLogic/PlayerActions/Items/BuyNpcItemAction.cs
+++ b/src/GameLogic/PlayerActions/Items/BuyNpcItemAction.cs
@@ -94,7 +94,7 @@ public async ValueTask BuyItemAsync(Player player, byte slot)
private bool CheckMoney(Player player, Item item)
{
- var price = this._priceCalculator.CalculateBuyingPrice(item);
+ var price = this._priceCalculator.CalculateFinalBuyingPrice(item);
if (!player.TryRemoveMoney((int)price))
{
return false;
diff --git a/src/GameLogic/PlayerActions/Items/ItemRepairAction.cs b/src/GameLogic/PlayerActions/Items/ItemRepairAction.cs
index 8d7f7c91d..b18f55fa4 100644
--- a/src/GameLogic/PlayerActions/Items/ItemRepairAction.cs
+++ b/src/GameLogic/PlayerActions/Items/ItemRepairAction.cs
@@ -56,7 +56,7 @@ public async ValueTask RepairItemAsync(Player player, byte slot)
///
/// The player.
///
- /// The client calculates a sum based on all items in the inventory, even these which are not equipped.
+ /// The client calculates a sum based on all items in the inventory, even those which are not equipped.
/// However, it should really just repair the equipped ones.
///
public async ValueTask RepairAllItemsAsync(Player player)
diff --git a/src/GameLogic/PlayerActions/Items/SellItemToNpcAction.cs b/src/GameLogic/PlayerActions/Items/SellItemToNpcAction.cs
index 782533213..85caf4636 100644
--- a/src/GameLogic/PlayerActions/Items/SellItemToNpcAction.cs
+++ b/src/GameLogic/PlayerActions/Items/SellItemToNpcAction.cs
@@ -57,7 +57,7 @@ public async ValueTask SellItemAsync(Player player, byte slot)
private async ValueTask SellItemAsync(Player player, Item item)
{
- var sellingPrice = (int)this._itemPriceCalculator.CalculateSellingPrice(item);
+ var sellingPrice = (int)this._itemPriceCalculator.CalculateSellingPrice(item, item.Durability());
player.Logger.LogDebug("Calculated selling price {0} for item {1}", sellingPrice, item);
if (player.TryAddMoney(sellingPrice))
{
diff --git a/src/GameLogic/PlayerActions/Items/SimpleItemCraftingHandler.cs b/src/GameLogic/PlayerActions/Items/SimpleItemCraftingHandler.cs
index ca78673a3..546bb4db8 100644
--- a/src/GameLogic/PlayerActions/Items/SimpleItemCraftingHandler.cs
+++ b/src/GameLogic/PlayerActions/Items/SimpleItemCraftingHandler.cs
@@ -53,6 +53,7 @@ protected virtual bool RequiredItemMatches(Item item, ItemCraftingRequiredItem r
{
successRate = 0;
int rate = this._settings.SuccessPercent;
+ long totalCraftingPrice = 0;
items = new List(this._settings.RequiredItems.Count);
var storage = player.TemporaryStorage?.Items.ToList() ?? new List- ();
foreach (var requiredItem in this._settings.RequiredItems.OrderByDescending(i => i.MinimumAmount))
@@ -71,36 +72,49 @@ protected virtual bool RequiredItemMatches(Item item, ItemCraftingRequiredItem r
return CraftingResult.TooManyItems;
}
- rate += (byte)(requiredItem.AddPercentage * (itemCount - requiredItem.MinimumAmount));
- if (requiredItem.NpcPriceDivisor > 0)
+ if (this._settings.NpcPriceDivisor > 0)
{
- rate += (byte)(foundItems.Sum(this._priceCalculator.CalculateBuyingPrice) /
- requiredItem.NpcPriceDivisor);
+ totalCraftingPrice += foundItems.Sum(this._priceCalculator.CalculateFinalOldBuyingPrice);
}
-
- foreach (var item in foundItems)
+ else
{
- if (this._settings.SuccessPercentageAdditionForLuck != default
- && item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Luck))
- {
- rate = (byte)(rate + this._settings.SuccessPercentageAdditionForLuck);
- }
-
- if (this._settings.SuccessPercentageAdditionForExcellentItem != default
- && item.IsExcellent())
+ rate += (byte)(requiredItem.AddPercentage * (itemCount - requiredItem.MinimumAmount));
+ if (requiredItem.NpcPriceDivisor > 0)
{
- rate = (byte)(rate + this._settings.SuccessPercentageAdditionForExcellentItem);
+ rate += (byte)(foundItems.Sum(this._priceCalculator.CalculateFinalBuyingPrice)
+ / requiredItem.NpcPriceDivisor);
}
- if (this._settings.SuccessPercentageAdditionForAncientItem != default
- && item.IsAncient())
+ foreach (var item in foundItems)
{
- rate = (byte)(rate + this._settings.SuccessPercentageAdditionForAncientItem);
- }
-
- if (this._settings.SuccessPercentageAdditionForSocketItem != default && item.SocketCount > 0)
- {
- rate = (byte)(rate + this._settings.SuccessPercentageAdditionForSocketItem);
+ if (this._settings.SuccessPercentageAdditionForLuck != default
+ && item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Luck))
+ {
+ rate = (byte)(rate + this._settings.SuccessPercentageAdditionForLuck);
+ }
+
+ if (this._settings.SuccessPercentageAdditionForExcellentItem != default
+ && item.IsExcellent())
+ {
+ rate = (byte)(rate + this._settings.SuccessPercentageAdditionForExcellentItem);
+ }
+
+ if (this._settings.SuccessPercentageAdditionForAncientItem != default
+ && item.IsAncient())
+ {
+ rate = (byte)(rate + this._settings.SuccessPercentageAdditionForAncientItem);
+ }
+
+ if (this._settings.SuccessPercentageAdditionForGuardianItem != default
+ && item.IsGuardian())
+ {
+ rate = (byte)(rate + this._settings.SuccessPercentageAdditionForGuardianItem);
+ }
+
+ if (this._settings.SuccessPercentageAdditionForSocketItem != default && item.SocketCount > 0)
+ {
+ rate = (byte)(rate + this._settings.SuccessPercentageAdditionForSocketItem);
+ }
}
}
@@ -114,6 +128,11 @@ protected virtual bool RequiredItemMatches(Item item, ItemCraftingRequiredItem r
return CraftingResult.IncorrectMixItems;
}
+ if (totalCraftingPrice > 0)
+ {
+ rate = (byte)(totalCraftingPrice / this._settings.NpcPriceDivisor);
+ }
+
if (this._settings.MaximumSuccessPercent > 0)
{
rate = Math.Min(this._settings.MaximumSuccessPercent, rate);
@@ -125,7 +144,7 @@ protected virtual bool RequiredItemMatches(Item item, ItemCraftingRequiredItem r
}
///
- protected override async ValueTask
> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot)
+ protected override async ValueTask> CreateOrModifyResultItemsAsync(IList requiredItems, Player player, byte socketSlot, byte successRate)
{
var resultItems = this._settings.ResultItemSelect == ResultItemSelection.All
? this._settings.ResultItems
@@ -139,7 +158,9 @@ protected override async ValueTask> CreateOrModifyResultItemsAsync(IL
{
foreach (var item in referencedItem.Items)
{
+ var previousMaxDurability = item.GetMaximumDurabilityOfOnePiece();
item.Level += craftingResultItem.AddLevel;
+ item.Durability = item.GetMaximumDurabilityOfOnePiece() * item.Durability / previousMaxDurability;
resultList.Add(item);
}
@@ -153,53 +174,62 @@ protected override async ValueTask> CreateOrModifyResultItemsAsync(IL
continue;
}
- resultList.AddRange(await this.CreateResultItemsAsync(player, requiredItems, craftingResultItem).ConfigureAwait(false));
+ resultList.AddRange(await this.CreateResultItemsAsync(player, requiredItems, craftingResultItem, successRate).ConfigureAwait(false));
}
return resultList;
}
- private async ValueTask> CreateResultItemsAsync(Player player, IList referencedItems, ItemCraftingResultItem craftingResultItem)
+ ///
+ /// Randomly adds an excellent option to the .
+ ///
+ /// The result item.
+ /// The player.
+ protected virtual void AddRandomExcellentOptions(Item resultItem, Player player)
{
- int resultItemCount = this._settings.MultipleAllowed
- ? referencedItems.FirstOrDefault(r => r.ItemRequirement.Reference > 0)?.Items.Count() ?? 1
- : 1;
-
- var resultList = new List- (resultItemCount);
- for (int i = 0; i < resultItemCount; i++)
+ if (this._settings.ResultItemExcellentOptionChance > 0
+ && resultItem.Definition!.PossibleItemOptions.FirstOrDefault(o =>
+ o.PossibleOptions.Any(p => p.OptionType == ItemOptionTypes.Excellent || p.OptionType == ItemOptionTypes.Wing))
+ is { } optionDefinition)
{
- // Create new Item
- var resultItem = player.PersistenceContext.CreateNew
- ();
- resultItem.Definition = craftingResultItem.ItemDefinition ?? throw Error.NotInitializedProperty(craftingResultItem, nameof(craftingResultItem.ItemDefinition));
- resultItem.Level = (byte)Rand.NextInt(
- craftingResultItem.RandomMinimumLevel,
- craftingResultItem.RandomMaximumLevel + 1);
- resultItem.Durability =
- craftingResultItem.Durability ?? resultItem.GetMaximumDurabilityOfOnePiece();
-
- this.AddRandomLuckOption(resultItem, player);
- this.AddRandomExcellentOptions(resultItem, player);
- if (!resultItem.HasSkill
- && this._settings.ResultItemSkillChance > 0
- && Rand.NextRandomBool(this._settings.ResultItemSkillChance)
- && resultItem.Definition!.Skill is { })
+ for (int j = 0;
+ j < optionDefinition.MaximumOptionsPerItem && Rand.NextRandomBool(this._settings.ResultItemExcellentOptionChance);
+ j++)
{
- resultItem.HasSkill = true;
+ var link = player.PersistenceContext.CreateNew();
+ link.ItemOption = optionDefinition.PossibleOptions
+ .Except(resultItem.ItemOptions.Select(io => io.ItemOption)).SelectRandom();
+ resultItem.ItemOptions.Add(link);
+ if (resultItem.Definition.Skill != null)
+ {
+ // Excellent items always have skill.
+ resultItem.HasSkill = true;
+ }
}
-
- await player.TemporaryStorage!.AddItemAsync(resultItem).ConfigureAwait(false);
- resultList.Add(resultItem);
}
+ }
- return resultList;
+ ///
+ /// Randomly adds item option to the .
+ ///
+ /// The result item.
+ /// The player.
+ /// The crafting combination success rate.
+ protected virtual void AddRandomItemOption(Item resultItem, Player player, byte successRate)
+ {
}
- private void AddRandomLuckOption(Item resultItem, Player player)
+ ///
+ /// Randomly adds luck option to the .
+ ///
+ /// The result item.
+ /// The player.
+ /// The crafting combination success rate.
+ protected virtual void AddRandomLuckOption(Item resultItem, Player player, byte successRate)
{
- if (this._settings.ResultItemLuckOptionChance > 0
- && Rand.NextRandomBool(this._settings.ResultItemLuckOptionChance)
- && resultItem.Definition!.PossibleItemOptions
- .FirstOrDefault(o => o.PossibleOptions.Any(po => po.OptionType == ItemOptionTypes.Luck))
+ if (this._settings.ResultItemLuckOptionChance > 0 && Rand.NextRandomBool(this._settings.ResultItemLuckOptionChance)
+ && resultItem.Definition!.PossibleItemOptions.FirstOrDefault(o =>
+ o.PossibleOptions.Any(po => po.OptionType == ItemOptionTypes.Luck))
is { } luck)
{
var luckOption = player.PersistenceContext.CreateNew();
@@ -208,27 +238,47 @@ private void AddRandomLuckOption(Item resultItem, Player player)
}
}
- private void AddRandomExcellentOptions(Item resultItem, Player player)
+ ///
+ /// Randomly adds skill to the .
+ ///
+ /// The result item.
+ /// The crafting combination success rate.
+ protected virtual void AddRandomSkill(Item resultItem, byte successRate)
{
- if (this._settings.ResultItemExcellentOptionChance > 0
- && resultItem.Definition!.PossibleItemOptions.FirstOrDefault(o =>
- o.PossibleOptions.Any(p => p.OptionType == ItemOptionTypes.Excellent || p.OptionType == ItemOptionTypes.Wing))
- is { } optionDefinition)
+ if (this._settings.ResultItemSkillChance > 0 && Rand.NextRandomBool(this._settings.ResultItemSkillChance)
+ && !resultItem.HasSkill
+ && resultItem.Definition!.Skill is { })
{
- for (int j = 0;
- j < optionDefinition.MaximumOptionsPerItem && Rand.NextRandomBool(this._settings.ResultItemExcellentOptionChance);
- j++)
- {
- var link = player.PersistenceContext.CreateNew();
- link.ItemOption = optionDefinition.PossibleOptions
- .Except(resultItem.ItemOptions.Select(io => io.ItemOption)).SelectRandom();
- resultItem.ItemOptions.Add(link);
- if (resultItem.Definition.Skill != null)
- {
- // Excellent items always have skill.
- resultItem.HasSkill = true;
- }
- }
+ resultItem.HasSkill = true;
}
}
+
+ private async ValueTask
> CreateResultItemsAsync(Player player, IList referencedItems, ItemCraftingResultItem craftingResultItem, byte successRate)
+ {
+ int resultItemCount = this._settings.MultipleAllowed
+ ? referencedItems.FirstOrDefault(r => r.ItemRequirement.Reference > 0)?.Items.Count() ?? 1
+ : 1;
+
+ var resultList = new List- (resultItemCount);
+ for (int i = 0; i < resultItemCount; i++)
+ {
+ // Create new Item
+ var resultItem = player.PersistenceContext.CreateNew
- ();
+ resultItem.Definition = craftingResultItem.ItemDefinition ?? throw Error.NotInitializedProperty(craftingResultItem, nameof(craftingResultItem.ItemDefinition));
+ resultItem.Level = resultItem.Definition.Group == ItemConstants.Fruits.Group && resultItem.Definition.Number == ItemConstants.Fruits.Number
+ ? (byte)new List { 0, 1, 2, 3, 4 }.SelectWeightedRandom([30, 25, 20, 20, 5])
+ : (byte)Rand.NextInt(craftingResultItem.RandomMinimumLevel, craftingResultItem.RandomMaximumLevel + 1);
+ resultItem.Durability = craftingResultItem.Durability ?? resultItem.GetMaximumDurabilityOfOnePiece();
+
+ this.AddRandomLuckOption(resultItem, player, successRate);
+ this.AddRandomItemOption(resultItem, player, successRate);
+ this.AddRandomSkill(resultItem, successRate);
+ this.AddRandomExcellentOptions(resultItem, player);
+
+ await player.TemporaryStorage!.AddItemAsync(resultItem).ConfigureAwait(false);
+ resultList.Add(resultItem);
+ }
+
+ return resultList;
+ }
}
\ No newline at end of file
diff --git a/src/GameLogic/PlugIns/ChatCommands/ItemChatCommandPlugIn.cs b/src/GameLogic/PlugIns/ChatCommands/ItemChatCommandPlugIn.cs
index 2435da8d7..ead5d2cbd 100644
--- a/src/GameLogic/PlugIns/ChatCommands/ItemChatCommandPlugIn.cs
+++ b/src/GameLogic/PlugIns/ChatCommands/ItemChatCommandPlugIn.cs
@@ -6,6 +6,7 @@ namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration.Items;
+using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
@@ -77,13 +78,41 @@ private static void AddOption(TemporaryItem item, ItemChatCommandArgs arguments)
{
if (item.Definition != null && arguments.Opt > default(byte))
{
- var itemOption = item.Definition.PossibleItemOptions
+ var allOptions = item.Definition.PossibleItemOptions
.SelectMany(o => o.PossibleOptions)
- .First(o => o.OptionType == ItemOptionTypes.Option);
+ .Where(o => o.OptionType == ItemOptionTypes.Option);
+ IncreasableItemOption itemOption;
- var level = arguments.Opt;
- var optionLink = new ItemOptionLink { ItemOption = itemOption, Level = level };
- item.ItemOptions.Add(optionLink);
+ if (item.Definition.Skill?.Number == 49) // Dinorant
+ {
+ if ((arguments.Opt & 1) > 0)
+ {
+ itemOption = allOptions.First(o => o.PowerUpDefinition!.TargetAttribute == Stats.DamageReceiveDecrement);
+ var dinoOptionLink = new ItemOptionLink { ItemOption = itemOption, Level = 1 };
+ item.ItemOptions.Add(dinoOptionLink);
+ }
+
+ if ((arguments.Opt & 2) > 0)
+ {
+ itemOption = allOptions.First(o => o.PowerUpDefinition!.TargetAttribute == Stats.MaximumAbility);
+ var dinoOptionLink = new ItemOptionLink { ItemOption = itemOption, Level = 2 };
+ item.ItemOptions.Add(dinoOptionLink);
+ }
+
+ if ((arguments.Opt & 4) > 0)
+ {
+ itemOption = allOptions.First(o => o.PowerUpDefinition!.TargetAttribute == Stats.AttackSpeed);
+ var dinoOptionLink = new ItemOptionLink { ItemOption = itemOption, Level = 4 };
+ item.ItemOptions.Add(dinoOptionLink);
+ }
+ }
+ else
+ {
+ itemOption = allOptions.First();
+ var level = arguments.Opt;
+ var optionLink = new ItemOptionLink { ItemOption = itemOption, Level = level };
+ item.ItemOptions.Add(optionLink);
+ }
}
}
diff --git a/src/GameLogic/Rand.cs b/src/GameLogic/Rand.cs
index ec49d7ab6..33a297e00 100644
--- a/src/GameLogic/Rand.cs
+++ b/src/GameLogic/Rand.cs
@@ -31,7 +31,7 @@ public static IRandomizer GetRandomizer()
/// Random boolean value.
public static bool NextRandomBool()
{
- int a = RandomInstance.Next(0, 1);
+ int a = RandomInstance.Next(0, 2);
return a == 1;
}
diff --git a/src/GameServer/RemoteView/ItemSerializer.cs b/src/GameServer/RemoteView/ItemSerializer.cs
index 79e691f2f..0ea0f75ae 100644
--- a/src/GameServer/RemoteView/ItemSerializer.cs
+++ b/src/GameServer/RemoteView/ItemSerializer.cs
@@ -9,6 +9,7 @@ namespace MUnique.OpenMU.GameServer.RemoteView;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.DataModel.Entities;
+using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.PlugIns;
@@ -52,9 +53,19 @@ public int SerializeItem(Span target, Item item)
var itemOption = item.ItemOptions.FirstOrDefault(o => o.ItemOption?.OptionType == ItemOptionTypes.Option);
if (itemOption != null)
{
+ var optionLevel = itemOption.Level;
+
+ // A dinorant can normally have up to 2 options, all being coded in the item option level.
+ // A one-option dino has level = 1, 2, or 4; a two-option has level = 3, 5, or 6.
+ if (item.Definition.Skill?.Number == 49)
+ {
+ item.ItemOptions.Where(o => o.ItemOption?.OptionType == ItemOptionTypes.Option && o != itemOption)
+ .ForEach(o => optionLevel |= o.Level);
+ }
+
// The item option level is splitted into 2 parts. Webzen... :-/
- target[1] += (byte)(itemOption.Level & 3); // setting the first 2 bits
- target[3] = (byte)((itemOption.Level & 4) << 4); // The highest bit is placed into the 2nd bit of the exc byte (0x40).
+ target[1] += (byte)(optionLevel & 3); // setting the first 2 bits
+ target[3] = (byte)((optionLevel & 4) << 4); // The highest bit is placed into the 2nd bit of the exc byte (0x40).
// Some items (wings) can have different options (3rd wings up to 3!)
// Alternate options are set at array[startIndex + 3] |= 0x20 and 0x10
diff --git a/src/GameServer/RemoteView/ItemSerializer095.cs b/src/GameServer/RemoteView/ItemSerializer095.cs
index 2c667372c..b72bd3569 100644
--- a/src/GameServer/RemoteView/ItemSerializer095.cs
+++ b/src/GameServer/RemoteView/ItemSerializer095.cs
@@ -9,6 +9,7 @@ namespace MUnique.OpenMU.GameServer.RemoteView;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.DataModel.Entities;
+using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.PlugIns;
@@ -65,8 +66,18 @@ public int SerializeItem(Span target, Item item)
var itemOption = item.ItemOptions.FirstOrDefault(o => o.ItemOption?.OptionType == ItemOptionTypes.Option);
if (itemOption != null)
{
- target[1] |= (byte)(itemOption.Level & 3);
- target[3] |= (byte)((itemOption.Level & 4) << 4); // The highest bit is placed into the 2nd bit of the exc byte (0x40).
+ var optionLevel = itemOption.Level;
+
+ // A dinorant can normally have up to 2 options, all being coded in the item option level.
+ // A one-option dino has level = 1, 2, or 4; a two-option has level = 3, 5, or 6.
+ if (item.Definition.Skill?.Number == 49)
+ {
+ item.ItemOptions.Where(o => o.ItemOption?.OptionType == ItemOptionTypes.Option && o != itemOption)
+ .ForEach(o => optionLevel |= o.Level);
+ }
+
+ target[1] |= (byte)(optionLevel & 3);
+ target[3] |= (byte)((optionLevel & 4) << 4); // The highest bit is placed into the 2nd bit of the exc byte (0x40).
}
target[3] |= GetExcellentByte(item);
diff --git a/src/Persistence/EntityFramework/Migrations/20241204090233_UpdateSimpleCraftingSettings.Designer.cs b/src/Persistence/EntityFramework/Migrations/20241204090233_UpdateSimpleCraftingSettings.Designer.cs
new file mode 100644
index 000000000..8acf6f9a9
--- /dev/null
+++ b/src/Persistence/EntityFramework/Migrations/20241204090233_UpdateSimpleCraftingSettings.Designer.cs
@@ -0,0 +1,5082 @@
+//
+using System;
+using MUnique.OpenMU.Persistence.EntityFramework;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace MUnique.OpenMU.Persistence.EntityFramework.Migrations
+{
+ [DbContext(typeof(EntityDataContext))]
+ [Migration("20241204090233_UpdateSimpleCraftingSettings")]
+ partial class UpdateSimpleCraftingSettings
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Account", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ChatBanUntil")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("EMail")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsVaultExtended")
+ .HasColumnType("boolean");
+
+ b.Property("LoginName")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("character varying(10)");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RegistrationDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SecurityCode")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("State")
+ .HasColumnType("integer");
+
+ b.Property("TimeZone")
+ .HasColumnType("smallint");
+
+ b.Property("VaultId")
+ .HasColumnType("uuid");
+
+ b.Property("VaultPassword")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LoginName")
+ .IsUnique();
+
+ b.HasIndex("VaultId")
+ .IsUnique();
+
+ b.ToTable("Account", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AccountCharacterClass", b =>
+ {
+ b.Property("AccountId")
+ .HasColumnType("uuid");
+
+ b.Property("CharacterClassId")
+ .HasColumnType("uuid");
+
+ b.HasKey("AccountId", "CharacterClassId");
+
+ b.HasIndex("CharacterClassId");
+
+ b.ToTable("AccountCharacterClass", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AppearanceData", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CharacterClassId")
+ .HasColumnType("uuid");
+
+ b.Property("FullAncientSetEquipped")
+ .HasColumnType("boolean");
+
+ b.Property("Pose")
+ .HasColumnType("smallint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CharacterClassId");
+
+ b.ToTable("AppearanceData", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AreaSkillSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("DelayBetweenHits")
+ .HasColumnType("interval");
+
+ b.Property("DelayPerOneDistance")
+ .HasColumnType("interval");
+
+ b.Property("FrustumDistance")
+ .HasColumnType("real");
+
+ b.Property("FrustumEndWidth")
+ .HasColumnType("real");
+
+ b.Property("FrustumStartWidth")
+ .HasColumnType("real");
+
+ b.Property("HitChancePerDistanceMultiplier")
+ .HasColumnType("real");
+
+ b.Property("MaximumNumberOfHitsPerAttack")
+ .HasColumnType("integer");
+
+ b.Property("MaximumNumberOfHitsPerTarget")
+ .HasColumnType("integer");
+
+ b.Property("MinimumNumberOfHitsPerTarget")
+ .HasColumnType("integer");
+
+ b.Property("TargetAreaDiameter")
+ .HasColumnType("real");
+
+ b.Property("UseDeferredHits")
+ .HasColumnType("boolean");
+
+ b.Property("UseFrustumFilter")
+ .HasColumnType("boolean");
+
+ b.Property("UseTargetAreaFilter")
+ .HasColumnType("boolean");
+
+ b.HasKey("Id");
+
+ b.ToTable("AreaSkillSettings", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Designation")
+ .HasColumnType("text");
+
+ b.Property("GameConfigurationId")
+ .HasColumnType("uuid");
+
+ b.Property("MaximumValue")
+ .HasColumnType("real");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GameConfigurationId");
+
+ b.ToTable("AttributeDefinition", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeRelationship", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CharacterClassId")
+ .HasColumnType("uuid");
+
+ b.Property("InputAttributeId")
+ .HasColumnType("uuid");
+
+ b.Property("InputOperand")
+ .HasColumnType("real");
+
+ b.Property("InputOperator")
+ .HasColumnType("integer");
+
+ b.Property("OperandAttributeId")
+ .HasColumnType("uuid");
+
+ b.Property("PowerUpDefinitionValueId")
+ .HasColumnType("uuid");
+
+ b.Property("TargetAttributeId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CharacterClassId");
+
+ b.HasIndex("InputAttributeId");
+
+ b.HasIndex("OperandAttributeId");
+
+ b.HasIndex("PowerUpDefinitionValueId");
+
+ b.HasIndex("TargetAttributeId");
+
+ b.ToTable("AttributeRelationship", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.AttributeRequirement", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AttributeId")
+ .HasColumnType("uuid");
+
+ b.Property("GameMapDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("ItemDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("MinimumValue")
+ .HasColumnType("integer");
+
+ b.Property("SkillId")
+ .HasColumnType("uuid");
+
+ b.Property("SkillId1")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AttributeId");
+
+ b.HasIndex("GameMapDefinitionId");
+
+ b.HasIndex("ItemDefinitionId");
+
+ b.HasIndex("SkillId");
+
+ b.HasIndex("SkillId1");
+
+ b.ToTable("AttributeRequirement", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.BattleZoneDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("GroundId")
+ .HasColumnType("uuid");
+
+ b.Property("LeftGoalId")
+ .HasColumnType("uuid");
+
+ b.Property("LeftTeamSpawnPointX")
+ .HasColumnType("smallint");
+
+ b.Property("LeftTeamSpawnPointY")
+ .HasColumnType("smallint");
+
+ b.Property("RightGoalId")
+ .HasColumnType("uuid");
+
+ b.Property("RightTeamSpawnPointX")
+ .HasColumnType("smallint");
+
+ b.Property("RightTeamSpawnPointY")
+ .HasColumnType("smallint");
+
+ b.Property("Type")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GroundId")
+ .IsUnique();
+
+ b.HasIndex("LeftGoalId")
+ .IsUnique();
+
+ b.HasIndex("RightGoalId")
+ .IsUnique();
+
+ b.ToTable("BattleZoneDefinition", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.Character", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AccountId")
+ .HasColumnType("uuid");
+
+ b.Property("CharacterClassId")
+ .HasColumnType("uuid");
+
+ b.Property("CharacterSlot")
+ .HasColumnType("smallint");
+
+ b.Property("CharacterStatus")
+ .HasColumnType("integer");
+
+ b.Property("CreateDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CurrentMapId")
+ .HasColumnType("uuid");
+
+ b.Property("Experience")
+ .HasColumnType("bigint");
+
+ b.Property("InventoryExtensions")
+ .HasColumnType("integer");
+
+ b.Property("InventoryId")
+ .HasColumnType("uuid");
+
+ b.Property("KeyConfiguration")
+ .HasColumnType("bytea");
+
+ b.Property("LevelUpPoints")
+ .HasColumnType("integer");
+
+ b.Property("MasterExperience")
+ .HasColumnType("bigint");
+
+ b.Property("MasterLevelUpPoints")
+ .HasColumnType("integer");
+
+ b.Property("MuHelperConfiguration")
+ .HasColumnType("bytea");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("character varying(10)");
+
+ b.Property("PlayerKillCount")
+ .HasColumnType("integer");
+
+ b.Property("Pose")
+ .HasColumnType("smallint");
+
+ b.Property("PositionX")
+ .HasColumnType("smallint");
+
+ b.Property("PositionY")
+ .HasColumnType("smallint");
+
+ b.Property("State")
+ .HasColumnType("integer");
+
+ b.Property("StateRemainingSeconds")
+ .HasColumnType("integer");
+
+ b.Property("UsedFruitPoints")
+ .HasColumnType("integer");
+
+ b.Property("UsedNegFruitPoints")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccountId");
+
+ b.HasIndex("CharacterClassId");
+
+ b.HasIndex("CurrentMapId");
+
+ b.HasIndex("InventoryId")
+ .IsUnique();
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("Character", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterClass", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CanGetCreated")
+ .HasColumnType("boolean");
+
+ b.Property("ComboDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("CreationAllowedFlag")
+ .HasColumnType("smallint");
+
+ b.Property("FruitCalculation")
+ .HasColumnType("integer");
+
+ b.Property("GameConfigurationId")
+ .HasColumnType("uuid");
+
+ b.Property("HomeMapId")
+ .HasColumnType("uuid");
+
+ b.Property("IsMasterClass")
+ .HasColumnType("boolean");
+
+ b.Property("LevelRequirementByCreation")
+ .HasColumnType("smallint");
+
+ b.Property("LevelWarpRequirementReductionPercent")
+ .HasColumnType("integer");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("NextGenerationClassId")
+ .HasColumnType("uuid");
+
+ b.Property("Number")
+ .HasColumnType("smallint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ComboDefinitionId")
+ .IsUnique();
+
+ b.HasIndex("GameConfigurationId");
+
+ b.HasIndex("HomeMapId");
+
+ b.HasIndex("NextGenerationClassId");
+
+ b.ToTable("CharacterClass", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterDropItemGroup", b =>
+ {
+ b.Property("CharacterId")
+ .HasColumnType("uuid");
+
+ b.Property("DropItemGroupId")
+ .HasColumnType("uuid");
+
+ b.HasKey("CharacterId", "DropItemGroupId");
+
+ b.HasIndex("DropItemGroupId");
+
+ b.ToTable("CharacterDropItemGroup", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CharacterQuestState", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ActiveQuestId")
+ .HasColumnType("uuid");
+
+ b.Property("CharacterId")
+ .HasColumnType("uuid");
+
+ b.Property("ClientActionPerformed")
+ .HasColumnType("boolean");
+
+ b.Property("Group")
+ .HasColumnType("smallint");
+
+ b.Property("LastFinishedQuestId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ActiveQuestId");
+
+ b.HasIndex("CharacterId");
+
+ b.HasIndex("LastFinishedQuestId");
+
+ b.ToTable("CharacterQuestState", "data");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ClientCleanUpInterval")
+ .HasColumnType("interval");
+
+ b.Property("ClientTimeout")
+ .HasColumnType("interval");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("MaximumConnections")
+ .HasColumnType("integer");
+
+ b.Property("RoomCleanUpInterval")
+ .HasColumnType("interval");
+
+ b.Property("ServerId")
+ .HasColumnType("smallint");
+
+ b.HasKey("Id");
+
+ b.ToTable("ChatServerDefinition", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ChatServerEndpoint", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ChatServerDefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("ClientId")
+ .HasColumnType("uuid");
+
+ b.Property("NetworkPort")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChatServerDefinitionId");
+
+ b.HasIndex("ClientId");
+
+ b.ToTable("ChatServerEndpoint", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.CombinationBonusRequirement", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ItemOptionCombinationBonusId")
+ .HasColumnType("uuid");
+
+ b.Property("MinimumCount")
+ .HasColumnType("integer");
+
+ b.Property("OptionTypeId")
+ .HasColumnType("uuid");
+
+ b.Property("SubOptionType")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ItemOptionCombinationBonusId");
+
+ b.HasIndex("OptionTypeId");
+
+ b.ToTable("CombinationBonusRequirement", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConfigurationUpdate", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("InstalledAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Version")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("ConfigurationUpdate", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConfigurationUpdateState", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CurrentInstalledVersion")
+ .HasColumnType("integer");
+
+ b.Property("InitializationKey")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("ConfigurationUpdateState", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConnectServerDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CheckMaxConnectionsPerAddress")
+ .HasColumnType("boolean");
+
+ b.Property("ClientId")
+ .HasColumnType("uuid");
+
+ b.Property("ClientListenerPort")
+ .HasColumnType("integer");
+
+ b.Property("CurrentPatchVersion")
+ .HasColumnType("bytea");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("DisconnectOnUnknownPacket")
+ .HasColumnType("boolean");
+
+ b.Property("ListenerBacklog")
+ .HasColumnType("integer");
+
+ b.Property("MaxConnections")
+ .HasColumnType("integer");
+
+ b.Property("MaxConnectionsPerAddress")
+ .HasColumnType("integer");
+
+ b.Property("MaxFtpRequests")
+ .HasColumnType("integer");
+
+ b.Property("MaxIpRequests")
+ .HasColumnType("integer");
+
+ b.Property("MaxServerListRequests")
+ .HasColumnType("integer");
+
+ b.Property("MaximumReceiveSize")
+ .HasColumnType("smallint");
+
+ b.Property("PatchAddress")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ServerId")
+ .HasColumnType("smallint");
+
+ b.Property("Timeout")
+ .HasColumnType("interval");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClientId");
+
+ b.ToTable("ConnectServerDefinition", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.ConstValueAttribute", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CharacterClassId")
+ .HasColumnType("uuid");
+
+ b.Property("DefinitionId")
+ .HasColumnType("uuid");
+
+ b.Property("Value")
+ .HasColumnType("real");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CharacterClassId");
+
+ b.HasIndex("DefinitionId");
+
+ b.ToTable("ConstValueAttribute", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroup", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Chance")
+ .HasColumnType("double precision");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("GameConfigurationId")
+ .HasColumnType("uuid");
+
+ b.Property("ItemLevel")
+ .HasColumnType("smallint");
+
+ b.Property("ItemType")
+ .HasColumnType("integer");
+
+ b.Property("MaximumMonsterLevel")
+ .HasColumnType("smallint");
+
+ b.Property("MinimumMonsterLevel")
+ .HasColumnType("smallint");
+
+ b.Property("MonsterId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GameConfigurationId");
+
+ b.HasIndex("MonsterId");
+
+ b.ToTable("DropItemGroup", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DropItemGroupItemDefinition", b =>
+ {
+ b.Property("DropItemGroupId")
+ .HasColumnType("uuid");
+
+ b.Property("ItemDefinitionId")
+ .HasColumnType("uuid");
+
+ b.HasKey("DropItemGroupId", "ItemDefinitionId");
+
+ b.HasIndex("ItemDefinitionId");
+
+ b.ToTable("DropItemGroupItemDefinition", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelArea", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("DuelConfigurationId")
+ .HasColumnType("uuid");
+
+ b.Property("FirstPlayerGateId")
+ .HasColumnType("uuid");
+
+ b.Property("Index")
+ .HasColumnType("smallint");
+
+ b.Property("SecondPlayerGateId")
+ .HasColumnType("uuid");
+
+ b.Property("SpectatorsGateId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DuelConfigurationId");
+
+ b.HasIndex("FirstPlayerGateId");
+
+ b.HasIndex("SecondPlayerGateId");
+
+ b.HasIndex("SpectatorsGateId");
+
+ b.ToTable("DuelArea", "config");
+ });
+
+ modelBuilder.Entity("MUnique.OpenMU.Persistence.EntityFramework.Model.DuelConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("EntranceFee")
+ .HasColumnType("integer");
+
+ b.Property("ExitId")
+ .HasColumnType("uuid");
+
+ b.Property