diff --git a/src/DataModel/Configuration/DropItemGroup.cs b/src/DataModel/Configuration/DropItemGroup.cs
index 35ec995ea..bb9bbcccd 100644
--- a/src/DataModel/Configuration/DropItemGroup.cs
+++ b/src/DataModel/Configuration/DropItemGroup.cs
@@ -41,6 +41,11 @@ public enum SpecialItemType
/// The money special item type.
///
Money,
+
+ ///
+ /// The jewel special item type.
+ ///
+ Jewel,
}
///
diff --git a/src/GameLogic/DefaultDropGenerator.cs b/src/GameLogic/DefaultDropGenerator.cs
index f0d80bf52..5fc1fe2cc 100644
--- a/src/GameLogic/DefaultDropGenerator.cs
+++ b/src/GameLogic/DefaultDropGenerator.cs
@@ -18,6 +18,8 @@ public class DefaultDropGenerator : IDropGenerator
///
public static readonly int BaseMoneyDrop = 7;
+ private static readonly int DropLevelMaxGap = 12;
+
private readonly IRandomizer _randomizer;
///
@@ -433,7 +435,23 @@ private void AddRandomExcOptions(Item item)
else
{
var monsterLevel = (int)monster[Stats.Level];
- var filteredPossibleItems = selectedGroup.PossibleItems.Where(it => it.DropLevel == 0 || ((it.DropLevel <= monsterLevel) && (it.DropLevel > monsterLevel - 12))).ToArray();
+ List filteredPossibleItems;
+
+ if (selectedGroup.ItemType == SpecialItemType.Jewel)
+ {
+ filteredPossibleItems = [.. selectedGroup.PossibleItems.Where(it => it.DropLevel <= monsterLevel)];
+
+ if (monsterLevel > 66)
+ {
+ // Jewel of Chaos doesn't drop after a certain monster level
+ filteredPossibleItems.RemoveAll(it => it.Group == 12 && it.Number == 15);
+ }
+ }
+ else
+ {
+ filteredPossibleItems = [.. selectedGroup.PossibleItems.Where(it => it.DropLevel == 0 || (it.DropLevel <= monsterLevel && it.DropLevel > monsterLevel - DropLevelMaxGap))];
+ }
+
return this.GenerateItemDrop(selectedGroup, filteredPossibleItems);
}
}
@@ -490,7 +508,7 @@ private void AddRandomExcOptions(Item item)
return this._droppableItemsPerMonsterLevel[monsterLevel]
??= (from it in this._droppableItems
where (it.DropLevel <= monsterLevel)
- && (it.DropLevel > monsterLevel - 12)
+ && (it.DropLevel > monsterLevel - DropLevelMaxGap)
&& (!socketItems || it.MaximumSockets > 0)
select it).ToList();
}
diff --git a/src/Persistence/Initialization/GameConfigurationInitializerBase.cs b/src/Persistence/Initialization/GameConfigurationInitializerBase.cs
index 6c384066c..f62343be1 100644
--- a/src/Persistence/Initialization/GameConfigurationInitializerBase.cs
+++ b/src/Persistence/Initialization/GameConfigurationInitializerBase.cs
@@ -167,7 +167,7 @@ private void AddItemDropGroups()
var jewelsDropItemGroup = this.Context.CreateNew();
jewelsDropItemGroup.SetGuid(4);
jewelsDropItemGroup.Chance = 0.001;
- jewelsDropItemGroup.ItemType = SpecialItemType.RandomItem;
+ jewelsDropItemGroup.ItemType = SpecialItemType.Jewel;
jewelsDropItemGroup.Description = "The jewels drop item group (0.1 % drop chance)";
this.GameConfiguration.DropItemGroups.Add(jewelsDropItemGroup);
BaseMapInitializer.RegisterDefaultDropItemGroup(jewelsDropItemGroup);
diff --git a/src/Persistence/Initialization/InitializerBase.cs b/src/Persistence/Initialization/InitializerBase.cs
index 2569d90c2..fbbc04969 100644
--- a/src/Persistence/Initialization/InitializerBase.cs
+++ b/src/Persistence/Initialization/InitializerBase.cs
@@ -195,4 +195,15 @@ protected IncreasableItemOption CreateItemOption(int number, AttributeDefinition
return itemOption;
}
+
+ ///
+ /// Register the jewel (or jewel-like item) in the drop item group for jewels.
+ ///
+ /// The jewel you want to register.
+ protected void AddItemToJewelItemDrop(ItemDefinition item)
+ {
+ var id = GuidHelper.CreateGuid(4);
+ var jewelsItemDrop = this.GameConfiguration.DropItemGroups.First(x => x.GetId() == id);
+ jewelsItemDrop.PossibleItems.Add(item);
+ }
}
\ No newline at end of file
diff --git a/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugIn075.cs b/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugIn075.cs
new file mode 100644
index 000000000..84c188f98
--- /dev/null
+++ b/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugIn075.cs
@@ -0,0 +1,30 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.Initialization.Updates;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.DataModel.Configuration;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// This update removes the existing drop level gap condition for jewels and similar items that should always drop.
+///
+[PlugIn]
+[Display(Name = PlugInName, Description = PlugInDescription)]
+[Guid("CD958BC1-F17A-4C60-B66D-BD29D49B6ADA")]
+public class RemoveJewelDropLevelGapPlugIn075 : RemoveJewelDropLevelGapPlugInBase
+{
+ ///
+ public override string DataInitializationKey => Version075.DataInitialization.Id;
+
+ ///
+ public override UpdateVersion Version => UpdateVersion.RemoveJewelDropLevelGap075;
+
+ ///
+ protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
+ {
+ await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
+ }
+}
\ No newline at end of file
diff --git a/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugIn095d.cs b/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugIn095d.cs
new file mode 100644
index 000000000..8bd82191a
--- /dev/null
+++ b/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugIn095d.cs
@@ -0,0 +1,30 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.Initialization.Updates;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.DataModel.Configuration;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// This update removes the existing drop level gap condition for jewels and similar items that should always drop.
+///
+[PlugIn]
+[Display(Name = PlugInName, Description = PlugInDescription)]
+[Guid("6614E91E-5749-478A-96A4-3240E7C1280E")]
+public class RemoveJewelDropLevelGapPlugIn095D : RemoveJewelDropLevelGapPlugInBase
+{
+ ///
+ public override string DataInitializationKey => Version095d.DataInitialization.Id;
+
+ ///
+ public override UpdateVersion Version => UpdateVersion.RemoveJewelDropLevelGap095d;
+
+ ///
+ protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
+ {
+ await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
+ }
+}
\ No newline at end of file
diff --git a/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugInBase.cs b/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugInBase.cs
new file mode 100644
index 000000000..f383bce2e
--- /dev/null
+++ b/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugInBase.cs
@@ -0,0 +1,58 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.Initialization.Updates;
+
+using MUnique.OpenMU.DataModel.Configuration;
+using MUnique.OpenMU.Persistence.Initialization.Items;
+
+///
+/// This update removes the existing drop level gap condition for jewels and similar items that should always drop.
+///
+public abstract class RemoveJewelDropLevelGapPlugInBase : UpdatePlugInBase
+{
+ ///
+ /// The plug in name.
+ ///
+ internal const string PlugInName = "Remove Jewel Drop Level Gap";
+
+ ///
+ /// The plug in description.
+ ///
+ internal const string PlugInDescription = "This update removes the existing drop level gap condition for jewels and similar items that should always drop.";
+
+ ///
+ public override string Name => PlugInName;
+
+ ///
+ public override string Description => PlugInDescription;
+
+ ///
+ public override bool IsMandatory => true;
+
+ ///
+ public override DateTime CreatedAt => new(2026, 3, 5, 16, 0, 0, DateTimeKind.Utc);
+
+ ///
+ protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
+ {
+ var jewelsDropGroupId = new Guid("00000200-0004-0000-0000-000000000000");
+ var jewelsDropGroup = gameConfiguration.DropItemGroups.First(x => x.GetId() == jewelsDropGroupId);
+ jewelsDropGroup.ItemType = SpecialItemType.Jewel;
+
+ Dictionary> itemGroupToItemNumbers = new()
+ {
+ { ItemGroups.Misc1, [0, 1, 2] }, // angel, imp, uniria
+ { ItemGroups.Misc2, [9, 10] }, // alcohol, town portal scroll
+ };
+ foreach (var entry in itemGroupToItemNumbers)
+ {
+ var items = gameConfiguration.Items.Where(i => i.Group == (int)entry.Key && entry.Value.Contains(i.Number));
+ foreach (var item in items)
+ {
+ jewelsDropGroup.PossibleItems.Add(item);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugInSeason6.cs b/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugInSeason6.cs
new file mode 100644
index 000000000..3e2dc2947
--- /dev/null
+++ b/src/Persistence/Initialization/Updates/RemoveJewelDropLevelGapPlugInSeason6.cs
@@ -0,0 +1,36 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.Persistence.Initialization.Updates;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.DataModel.Configuration;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// This update removes the existing drop level gap condition for jewels and similar items that should always drop.
+///
+[PlugIn]
+[Display(Name = PlugInName, Description = PlugInDescription)]
+[Guid("AB1C9F8B-5E3B-4F2A-BDCD-9C0F1E5A6B7C")]
+public class RemoveJewelDropLevelGapPlugInSeason6 : RemoveJewelDropLevelGapPlugInBase
+{
+ ///
+ public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
+
+ ///
+ public override UpdateVersion Version => UpdateVersion.RemoveJewelDropLevelGapSeason6;
+
+ ///
+ protected override async ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration)
+ {
+ await base.ApplyAsync(context, gameConfiguration).ConfigureAwait(false);
+
+ var jewelOfGuardianDropGroupId = new Guid("00000200-001f-0001-0000-000000000000");
+ if (gameConfiguration.DropItemGroups.FirstOrDefault(x => x.GetId() == jewelOfGuardianDropGroupId) is { } jewelOfGuardianDropGroup)
+ {
+ jewelOfGuardianDropGroup.ItemType = SpecialItemType.Jewel;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Persistence/Initialization/Updates/UpdateVersion.cs b/src/Persistence/Initialization/Updates/UpdateVersion.cs
index 1e771cc39..3c2993923 100644
--- a/src/Persistence/Initialization/Updates/UpdateVersion.cs
+++ b/src/Persistence/Initialization/Updates/UpdateVersion.cs
@@ -344,4 +344,19 @@ public enum UpdateVersion
/// The version of the .
///
AddProjectileCountToTripleShot = 67,
+
+ ///
+ /// The version of the .
+ ///
+ RemoveJewelDropLevelGap075 = 68,
+
+ ///
+ /// The version of the .
+ ///
+ RemoveJewelDropLevelGap095d = 69,
+
+ ///
+ /// The version of the .
+ ///
+ RemoveJewelDropLevelGapSeason6 = 70,
}
\ No newline at end of file
diff --git a/src/Persistence/Initialization/Version075/Items/Jewels.cs b/src/Persistence/Initialization/Version075/Items/Jewels.cs
index e08b7f0fb..961a4a51f 100644
--- a/src/Persistence/Initialization/Version075/Items/Jewels.cs
+++ b/src/Persistence/Initialization/Version075/Items/Jewels.cs
@@ -30,17 +30,6 @@ public override void Initialize()
this.GameConfiguration.Items.Add(this.CreateJewelOfChaos());
}
- ///
- /// Register the jewel in the drop item group for jewels.
- ///
- /// The jewel you want to register.
- protected void AddItemToJewelItemDrop(ItemDefinition item)
- {
- var id = GuidHelper.CreateGuid(4);
- var jewelsItemDrop = this.GameConfiguration.DropItemGroups.First(x => x.GetId() == id);
- jewelsItemDrop.PossibleItems.Add(item);
- }
-
///
/// Creates an for the 'Jewel of Bless'.
///
diff --git a/src/Persistence/Initialization/Version075/Items/Pets.cs b/src/Persistence/Initialization/Version075/Items/Pets.cs
index e814c4775..1d16abe57 100644
--- a/src/Persistence/Initialization/Version075/Items/Pets.cs
+++ b/src/Persistence/Initialization/Version075/Items/Pets.cs
@@ -29,9 +29,12 @@ public Pets(IContext context, GameConfiguration gameConfiguration)
///
public override void Initialize()
{
- this.CreatePet(0, "Guardian Angel", 23, (Stats.DamageReceiveDecrement, 0.8f, AggregateType.Multiplicate), (Stats.MaximumHealth, 50f, AggregateType.AddRaw));
- this.CreatePet(1, "Imp", 28, (Stats.AttackDamageIncrease, 1.3f, AggregateType.Multiplicate));
- this.CreatePet(2, "Horn of Uniria", 25);
+ var angel = this.CreatePet(0, "Guardian Angel", 23, (Stats.DamageReceiveDecrement, 0.8f, AggregateType.Multiplicate), (Stats.MaximumHealth, 50f, AggregateType.AddRaw));
+ this.AddItemToJewelItemDrop(angel);
+ var imp = this.CreatePet(1, "Imp", 28, (Stats.AttackDamageIncrease, 1.3f, AggregateType.Multiplicate));
+ this.AddItemToJewelItemDrop(imp);
+ var uniria = this.CreatePet(2, "Horn of Uniria", 25);
+ this.AddItemToJewelItemDrop(uniria);
}
private ItemDefinition CreatePet(byte number, string name, int dropLevelAndLevelRequirement, params (AttributeDefinition, float, AggregateType)[] basePowerUps)
diff --git a/src/Persistence/Initialization/Version075/Items/Potions.cs b/src/Persistence/Initialization/Version075/Items/Potions.cs
index fb5f0e090..3a56d4b60 100644
--- a/src/Persistence/Initialization/Version075/Items/Potions.cs
+++ b/src/Persistence/Initialization/Version075/Items/Potions.cs
@@ -54,6 +54,7 @@ private ItemDefinition CreateAlcohol()
alcohol.Height = 2;
alcohol.SetGuid(alcohol.Group, alcohol.Number);
alcohol.ConsumeEffect = this.GameConfiguration.MagicEffects.First(effect => effect.Number == (short)MagicEffectNumber.Alcohol);
+ this.AddItemToJewelItemDrop(alcohol);
return alcohol;
}
@@ -230,6 +231,7 @@ private ItemDefinition CreateTownPortalScroll()
definition.Width = 1;
definition.Height = 2;
definition.SetGuid(definition.Group, definition.Number);
+ this.AddItemToJewelItemDrop(definition);
return definition;
}
}
\ No newline at end of file
diff --git a/src/Persistence/Initialization/Version095d/Items/Pets.cs b/src/Persistence/Initialization/Version095d/Items/Pets.cs
index 00650045c..d535ec284 100644
--- a/src/Persistence/Initialization/Version095d/Items/Pets.cs
+++ b/src/Persistence/Initialization/Version095d/Items/Pets.cs
@@ -30,9 +30,12 @@ public Pets(IContext context, GameConfiguration gameConfiguration)
///
public override void Initialize()
{
- this.CreatePet(0, 0, "Guardian Angel", 23, true, (Stats.DamageReceiveDecrement, 0.8f, AggregateType.Multiplicate), (Stats.MaximumHealth, 50f, AggregateType.AddRaw));
- this.CreatePet(1, 0, "Imp", 28, true, (Stats.AttackDamageIncrease, 1.3f, AggregateType.Multiplicate));
- this.CreatePet(2, 0, "Horn of Uniria", 25, true);
+ var angel = this.CreatePet(0, 0, "Guardian Angel", 23, true, (Stats.DamageReceiveDecrement, 0.8f, AggregateType.Multiplicate), (Stats.MaximumHealth, 50f, AggregateType.AddRaw));
+ this.AddItemToJewelItemDrop(angel);
+ var imp = this.CreatePet(1, 0, "Imp", 28, true, (Stats.AttackDamageIncrease, 1.3f, AggregateType.Multiplicate));
+ this.AddItemToJewelItemDrop(imp);
+ var uniria = this.CreatePet(2, 0, "Horn of Uniria", 25, true);
+ this.AddItemToJewelItemDrop(uniria);
var dinorant = this.CreatePet(3, SkillNumber.FireBreath, "Horn of Dinorant", 110, false, (Stats.IsDinorantEquipped, 1, AggregateType.AddRaw), (Stats.DamageReceiveDecrement, 0.9f, AggregateType.Multiplicate), (Stats.AttackDamageIncrease, 1.15f, AggregateType.Multiplicate));
this.AddDinorantOptions(dinorant);
diff --git a/src/Persistence/Initialization/VersionSeasonSix/Items/Pets.cs b/src/Persistence/Initialization/VersionSeasonSix/Items/Pets.cs
index facdc13d2..898571207 100644
--- a/src/Persistence/Initialization/VersionSeasonSix/Items/Pets.cs
+++ b/src/Persistence/Initialization/VersionSeasonSix/Items/Pets.cs
@@ -37,12 +37,15 @@ public Pets(IContext context, GameConfiguration gameConfiguration)
public override void Initialize()
{
#pragma warning disable SA1117 // Parameters should be on same line or separete lines
- this.CreatePet(0, 0, 1, 1, "Guardian Angel", 23, true, true,
+ var angel = this.CreatePet(0, 0, 1, 1, "Guardian Angel", 23, true, true,
(Stats.DamageReceiveDecrement, 0.8f, AggregateType.Multiplicate),
(Stats.MaximumHealth, 50f, AggregateType.AddRaw));
- this.CreatePet(1, 0, 1, 1, "Imp", 28, true, true,
+ this.AddItemToJewelItemDrop(angel);
+ var imp = this.CreatePet(1, 0, 1, 1, "Imp", 28, true, true,
(Stats.AttackDamageIncrease, 1.3f, AggregateType.Multiplicate));
- this.CreatePet(2, 0, 1, 1, "Horn of Uniria", 25, true, true);
+ this.AddItemToJewelItemDrop(imp);
+ var uniria = this.CreatePet(2, 0, 1, 1, "Horn of Uniria", 25, true, true);
+ this.AddItemToJewelItemDrop(uniria);
var dinorant = this.CreatePet(3, SkillNumber.FireBreath, 1, 1, "Horn of Dinorant", 110, false, true,
(Stats.IsDinorantEquipped, 1, AggregateType.AddRaw),
diff --git a/src/Persistence/Initialization/VersionSeasonSix/Items/Potions.cs b/src/Persistence/Initialization/VersionSeasonSix/Items/Potions.cs
index 771d54f66..bfa2d41de 100644
--- a/src/Persistence/Initialization/VersionSeasonSix/Items/Potions.cs
+++ b/src/Persistence/Initialization/VersionSeasonSix/Items/Potions.cs
@@ -75,6 +75,7 @@ private ItemDefinition CreateAlcohol()
alcohol.Height = 2;
alcohol.SetGuid(alcohol.Group, alcohol.Number);
alcohol.ConsumeEffect = this.GameConfiguration.MagicEffects.First(effect => effect.Number == (short)MagicEffectNumber.Alcohol);
+ this.AddItemToJewelItemDrop(alcohol);
return alcohol;
}
@@ -375,6 +376,7 @@ private ItemDefinition CreateTownPortalScroll()
definition.Width = 1;
definition.Height = 2;
definition.SetGuid(definition.Group, definition.Number);
+ this.AddItemToJewelItemDrop(definition);
return definition;
}
diff --git a/src/Persistence/Initialization/VersionSeasonSix/Maps/LandOfTrials.cs b/src/Persistence/Initialization/VersionSeasonSix/Maps/LandOfTrials.cs
index e36603364..cf058611e 100644
--- a/src/Persistence/Initialization/VersionSeasonSix/Maps/LandOfTrials.cs
+++ b/src/Persistence/Initialization/VersionSeasonSix/Maps/LandOfTrials.cs
@@ -457,7 +457,7 @@ protected override void InitializeDropItemGroups()
var jewelsDropItemGroup = this.Context.CreateNew();
jewelsDropItemGroup.SetGuid(this.MapNumber, 1);
jewelsDropItemGroup.Chance = 0.001;
- jewelsDropItemGroup.ItemType = SpecialItemType.RandomItem;
+ jewelsDropItemGroup.ItemType = SpecialItemType.Jewel;
jewelsDropItemGroup.Description = "Jewel of Guardian";
var jewelOfGuardian = this.GameConfiguration.Items.First(y => y.Group == 14 && y.Number == 31);