From 2433769dbb402ce4384fc5e7a992d07e7722412f Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Fri, 6 Mar 2026 20:32:43 +0300 Subject: [PATCH 01/17] Bump version to 1.0.0 --- src/MHServerEmu.Core/MHServerEmu.Core.csproj | 2 +- .../MHServerEmu.DatabaseAccess.csproj | 2 +- src/MHServerEmu.Frontend/MHServerEmu.Frontend.csproj | 2 +- src/MHServerEmu.Games/MHServerEmu.Games.csproj | 2 +- src/MHServerEmu.Grouping/MHServerEmu.Grouping.csproj | 2 +- src/MHServerEmu.Leaderboards/MHServerEmu.Leaderboards.csproj | 2 +- .../MHServerEmu.PlayerManagement.csproj | 2 +- src/MHServerEmu.WebFrontend/MHServerEmu.WebFrontend.csproj | 2 +- src/MHServerEmu/MHServerEmu.csproj | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/MHServerEmu.Core/MHServerEmu.Core.csproj b/src/MHServerEmu.Core/MHServerEmu.Core.csproj index 760a086e..1536a21d 100644 --- a/src/MHServerEmu.Core/MHServerEmu.Core.csproj +++ b/src/MHServerEmu.Core/MHServerEmu.Core.csproj @@ -8,7 +8,7 @@ - 0.8.1.0 + 1.0.0.0 $(AssemblyVersion) $(AssemblyVersion) diff --git a/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj b/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj index 64b526a9..22df9f5b 100644 --- a/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj +++ b/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj @@ -8,7 +8,7 @@ - 0.8.1.0 + 1.0.0.0 $(AssemblyVersion) $(AssemblyVersion) diff --git a/src/MHServerEmu.Frontend/MHServerEmu.Frontend.csproj b/src/MHServerEmu.Frontend/MHServerEmu.Frontend.csproj index a9cb80d5..ce3fd954 100644 --- a/src/MHServerEmu.Frontend/MHServerEmu.Frontend.csproj +++ b/src/MHServerEmu.Frontend/MHServerEmu.Frontend.csproj @@ -8,7 +8,7 @@ - 0.8.1.0 + 1.0.0.0 $(AssemblyVersion) $(AssemblyVersion) diff --git a/src/MHServerEmu.Games/MHServerEmu.Games.csproj b/src/MHServerEmu.Games/MHServerEmu.Games.csproj index eb08885e..39af41ad 100644 --- a/src/MHServerEmu.Games/MHServerEmu.Games.csproj +++ b/src/MHServerEmu.Games/MHServerEmu.Games.csproj @@ -9,7 +9,7 @@ - 0.8.1 + 1.0.0 build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss")) $(InformationalVersion).0 $(AssemblyVersion) diff --git a/src/MHServerEmu.Grouping/MHServerEmu.Grouping.csproj b/src/MHServerEmu.Grouping/MHServerEmu.Grouping.csproj index 1523f94b..1f27e9e4 100644 --- a/src/MHServerEmu.Grouping/MHServerEmu.Grouping.csproj +++ b/src/MHServerEmu.Grouping/MHServerEmu.Grouping.csproj @@ -8,7 +8,7 @@ - 0.8.1.0 + 1.0.0.0 $(AssemblyVersion) $(AssemblyVersion) diff --git a/src/MHServerEmu.Leaderboards/MHServerEmu.Leaderboards.csproj b/src/MHServerEmu.Leaderboards/MHServerEmu.Leaderboards.csproj index 85d4ebd0..a0eda1c9 100644 --- a/src/MHServerEmu.Leaderboards/MHServerEmu.Leaderboards.csproj +++ b/src/MHServerEmu.Leaderboards/MHServerEmu.Leaderboards.csproj @@ -8,7 +8,7 @@ - 0.8.1.0 + 1.0.0.0 $(AssemblyVersion) $(AssemblyVersion) diff --git a/src/MHServerEmu.PlayerManagement/MHServerEmu.PlayerManagement.csproj b/src/MHServerEmu.PlayerManagement/MHServerEmu.PlayerManagement.csproj index 591351fc..4da8c6d8 100644 --- a/src/MHServerEmu.PlayerManagement/MHServerEmu.PlayerManagement.csproj +++ b/src/MHServerEmu.PlayerManagement/MHServerEmu.PlayerManagement.csproj @@ -8,7 +8,7 @@ - 0.8.1.0 + 1.0.0.0 $(AssemblyVersion) $(AssemblyVersion) diff --git a/src/MHServerEmu.WebFrontend/MHServerEmu.WebFrontend.csproj b/src/MHServerEmu.WebFrontend/MHServerEmu.WebFrontend.csproj index f8a00ff0..2d28365b 100644 --- a/src/MHServerEmu.WebFrontend/MHServerEmu.WebFrontend.csproj +++ b/src/MHServerEmu.WebFrontend/MHServerEmu.WebFrontend.csproj @@ -8,7 +8,7 @@ - 0.8.1.0 + 1.0.0.0 $(AssemblyVersion) $(AssemblyVersion) diff --git a/src/MHServerEmu/MHServerEmu.csproj b/src/MHServerEmu/MHServerEmu.csproj index a35cae8d..beab9dd2 100644 --- a/src/MHServerEmu/MHServerEmu.csproj +++ b/src/MHServerEmu/MHServerEmu.csproj @@ -9,7 +9,7 @@ - 0.8.1 + 1.0.0 build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss")) $(InformationalVersion).0 $(AssemblyVersion) From 4e82fbe611ded5ef7cbe161fb650b91c7c879941 Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sat, 7 Mar 2026 15:56:57 +0300 Subject: [PATCH 02/17] Disable character auto unlocks by default --- src/MHServerEmu/Config.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MHServerEmu/Config.ini b/src/MHServerEmu/Config.ini index 28466d08..0c5266f9 100644 --- a/src/MHServerEmu/Config.ini +++ b/src/MHServerEmu/Config.ini @@ -123,7 +123,7 @@ PlayerName=Player ServerName=MHServerEmu ServerPrestigeLevel=6 -MotdText=Welcome back to Marvel Heroes! Type !commands to see a list of available server commands. +MotdText=Welcome to Marvel Heroes! Type !commands for a list of available server commands. LogPrivateChatRooms=false EnableChatTips=false ChatTipFileName=ChatTips.txt @@ -209,8 +209,8 @@ OrbisTrophiesEnabled=true AutosaveIntervalMinutes=15 ESCooldownOverrideMinutes=-1.0 CombineESStacks=false -AutoUnlockAvatars=true -AutoUnlockTeamUps=true +AutoUnlockAvatars=false +AutoUnlockTeamUps=false DisableMovementPowerChargeCost=false AllowSameGroupTalents=false DisableInstancedLoot=false From 3496897ea8f741b9ff8ebea92be8f76a2fcf80cf Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sat, 7 Mar 2026 16:15:17 +0300 Subject: [PATCH 03/17] Move global live tuning data to a separate file and remove baseline buffs --- .../Data/Game/LiveTuning/LiveTuningData.json | 130 ----------------- .../Game/LiveTuning/LiveTuningDataGlobal.json | 132 ++++++++++++++++++ 2 files changed, 132 insertions(+), 130 deletions(-) create mode 100644 src/MHServerEmu.Games/Data/Game/LiveTuning/LiveTuningDataGlobal.json diff --git a/src/MHServerEmu.Games/Data/Game/LiveTuning/LiveTuningData.json b/src/MHServerEmu.Games/Data/Game/LiveTuning/LiveTuningData.json index d5e71561..3fc6ac25 100644 --- a/src/MHServerEmu.Games/Data/Game/LiveTuning/LiveTuningData.json +++ b/src/MHServerEmu.Games/Data/Game/LiveTuning/LiveTuningData.json @@ -1,134 +1,4 @@ [ - { - "Prototype": "", - "Setting": "eGTV_VendorBuyPrice", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_VendorSellPrice", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_VendorXPGain", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_PVPEnabled", - "Value": 0.0 - }, - { - "Prototype": "", - "Setting": "eGTV_XPGain", - "Value": 1.5 - }, - { - "Prototype": "", - "Setting": "eGTV_LootDropRate", - "Value": 1.5 - }, - { - "Prototype": "", - "Setting": "eGTV_LootSpecialDropRate", - "Value": 1.5 - }, - { - "Prototype": "", - "Setting": "eGTV_LootRarity", - "Value": 1.5 - }, - { - "Prototype": "", - "Setting": "eGTV_PartyXPBonusPct", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_PlayerTradeEnabled", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_CosmicPrestigeXPPct", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_LootVaporizationEnabled", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_XPBuffDisplay", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_SIFBuffDisplay", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_RIFBuffDisplay", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_OmegaXPPct", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_RespectLevelForGlobalXP", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_RespectLevelForGlobalRIF", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_RespectLevelForGlobalSIF", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_RespectLevelForOmegaXP", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_RespectLevelForAvatarXP", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_RespectLevelForRegionXP", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_ServerBonusUnlockLevelOverride", - "Value": 0.0 - }, - { - "Prototype": "", - "Setting": "eGTV_BoostTimersRunning", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_InfinityXPPct", - "Value": 1.0 - }, - { - "Prototype": "", - "Setting": "eGTV_RespectLevelForInfinityXP", - "Value": 1.0 - }, { "Prototype": "Powers/EnemyPowers/Boss/MindlessOneBoss/GroundStomp.prototype", "Setting": "ePTV_PowerDamagePVE", diff --git a/src/MHServerEmu.Games/Data/Game/LiveTuning/LiveTuningDataGlobal.json b/src/MHServerEmu.Games/Data/Game/LiveTuning/LiveTuningDataGlobal.json new file mode 100644 index 00000000..1d5a00b5 --- /dev/null +++ b/src/MHServerEmu.Games/Data/Game/LiveTuning/LiveTuningDataGlobal.json @@ -0,0 +1,132 @@ +[ + { + "Prototype": "", + "Setting": "eGTV_VendorBuyPrice", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_VendorSellPrice", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_VendorXPGain", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_PVPEnabled", + "Value": 0.0 + }, + { + "Prototype": "", + "Setting": "eGTV_XPGain", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_LootDropRate", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_LootSpecialDropRate", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_LootRarity", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_PartyXPBonusPct", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_PlayerTradeEnabled", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_CosmicPrestigeXPPct", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_LootVaporizationEnabled", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_XPBuffDisplay", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_SIFBuffDisplay", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_RIFBuffDisplay", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_OmegaXPPct", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_RespectLevelForGlobalXP", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_RespectLevelForGlobalRIF", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_RespectLevelForGlobalSIF", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_RespectLevelForOmegaXP", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_RespectLevelForAvatarXP", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_RespectLevelForRegionXP", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_ServerBonusUnlockLevelOverride", + "Value": 0.0 + }, + { + "Prototype": "", + "Setting": "eGTV_BoostTimersRunning", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_InfinityXPPct", + "Value": 1.0 + }, + { + "Prototype": "", + "Setting": "eGTV_RespectLevelForInfinityXP", + "Value": 1.0 + } +] \ No newline at end of file From 5c44fcff96518260550071362b41eedfdf839659 Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sat, 7 Mar 2026 16:26:17 +0300 Subject: [PATCH 04/17] Fix LiveTuningDataGlobal.json output copy settings --- src/MHServerEmu.Games/MHServerEmu.Games.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/MHServerEmu.Games/MHServerEmu.Games.csproj b/src/MHServerEmu.Games/MHServerEmu.Games.csproj index 39af41ad..83410c0d 100644 --- a/src/MHServerEmu.Games/MHServerEmu.Games.csproj +++ b/src/MHServerEmu.Games/MHServerEmu.Games.csproj @@ -67,6 +67,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -103,7 +106,7 @@ PreserveNewest - + PreserveNewest From bfd161d517486ceda5c4745ff6c11136262a019c Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sat, 7 Mar 2026 16:31:25 +0300 Subject: [PATCH 05/17] Remove legacy 0.x archive versioning code --- src/MHServerEmu.Core/Serialization/Archive.cs | 19 ++------ .../Entities/Avatars/Avatar.cs | 3 +- .../Entities/ConditionCollection.cs | 2 +- .../Entities/Persistence/PlayerVersioning.cs | 24 +--------- src/MHServerEmu.Games/Entities/Player.cs | 48 ++++++++----------- .../Regions/Maps/MapDiscoveryData.cs | 4 +- 6 files changed, 30 insertions(+), 70 deletions(-) diff --git a/src/MHServerEmu.Core/Serialization/Archive.cs b/src/MHServerEmu.Core/Serialization/Archive.cs index 4265a572..fc12adf5 100644 --- a/src/MHServerEmu.Core/Serialization/Archive.cs +++ b/src/MHServerEmu.Core/Serialization/Archive.cs @@ -19,23 +19,14 @@ namespace MHServerEmu.Core.Serialization Disk = 4 // Server <-> File } - // We are most likely not going to have as much versioning as the original game, - // so we will use ArchiveVersion for all archive versioning. At some point when - // things get more stable we may want to clear this and force a wipe of everything. public enum ArchiveVersion : uint { Invalid = 0, - Initial = 1, - AddedMissions = 2, - AddedVendorPurchaseData = 3, - ImplementedConditionPersistence = 4, - ImplementedLoginRewards = 5, - ImplementedMapDiscoveryDataPersistence = 6, - AddedRegionProtoRefToMapDiscoveryData = 7, - AddedUltimatePrestigeLevel = 8, + // Versions 1-8 were used in the 0.x branch, so we start at 9 here. + Initial = 9, // Update the current version if you add any <--------- - Current = AddedUltimatePrestigeLevel + Current = Initial } /// @@ -702,7 +693,7 @@ namespace MHServerEmu.Core.Serialization /// private bool StartSizeChecking(ref long startPosition, ref uint size) { - if (IsPersistent == false || Version < ArchiveVersion.AddedMissions) + if (IsPersistent == false) return true; // NOTE: COS/CIS are buffered, so we need to use their position, and not the one from the underlying stream. @@ -728,7 +719,7 @@ namespace MHServerEmu.Core.Serialization /// private bool EndSizeChecking(ref long startPosition, ref uint size, bool skip) { - if (IsPersistent == false || Version < ArchiveVersion.AddedMissions) + if (IsPersistent == false) return true; if (IsPacking) diff --git a/src/MHServerEmu.Games/Entities/Avatars/Avatar.cs b/src/MHServerEmu.Games/Entities/Avatars/Avatar.cs index ff7365bb..a94a6774 100644 --- a/src/MHServerEmu.Games/Entities/Avatars/Avatar.cs +++ b/src/MHServerEmu.Games/Entities/Avatars/Avatar.cs @@ -219,8 +219,7 @@ namespace MHServerEmu.Games.Entities.Avatars // Custom data if (archive.IsPersistent) { - if (archive.Version >= ArchiveVersion.AddedUltimatePrestigeLevel) - success &= Serializer.Transfer(archive, ref _ultimatePrestigeLevel); + success &= Serializer.Transfer(archive, ref _ultimatePrestigeLevel); } return success; diff --git a/src/MHServerEmu.Games/Entities/ConditionCollection.cs b/src/MHServerEmu.Games/Entities/ConditionCollection.cs index 896b825e..efa87e36 100644 --- a/src/MHServerEmu.Games/Entities/ConditionCollection.cs +++ b/src/MHServerEmu.Games/Entities/ConditionCollection.cs @@ -105,7 +105,7 @@ namespace MHServerEmu.Games.Entities } } } - else if (archive.IsPersistent && archive.Version >= ArchiveVersion.ImplementedConditionPersistence) + else if (archive.IsPersistent) { uint numConditions = (uint)GetPersistentConditionCount(); success &= Serializer.Transfer(archive, ref numConditions); diff --git a/src/MHServerEmu.Games/Entities/Persistence/PlayerVersioning.cs b/src/MHServerEmu.Games/Entities/Persistence/PlayerVersioning.cs index e51fb36d..cd67015a 100644 --- a/src/MHServerEmu.Games/Entities/Persistence/PlayerVersioning.cs +++ b/src/MHServerEmu.Games/Entities/Persistence/PlayerVersioning.cs @@ -1,7 +1,5 @@ using MHServerEmu.Core.Logging; using MHServerEmu.Core.Serialization; -using MHServerEmu.Games.Entities.Avatars; -using MHServerEmu.Games.Properties; namespace MHServerEmu.Games.Entities.Persistence { @@ -19,8 +17,8 @@ namespace MHServerEmu.Games.Entities.Persistence switch (loadedVersion) { + // add versioning code here as needed case ArchiveVersion.Initial: - success |= V2_ClearProperties(player); break; } @@ -33,25 +31,5 @@ namespace MHServerEmu.Games.Entities.Persistence return true; } - - #region V2 - - private static bool V2_ClearProperties(Player player) - { - player.Properties.RemovePropertyRange(PropertyEnum.Waypoint); - player.Properties.RemovePropertyRange(PropertyEnum.UISystemLock); - - // Team-ups seem to be invisible if they are summoned before the tutorial starts, so just unsummon them for all avatars - foreach (Avatar avatar in new AvatarIterator(player)) - { - avatar.Properties.RemoveProperty(PropertyEnum.AvatarTeamUpIsSummoned); - avatar.Properties.RemoveProperty(PropertyEnum.AvatarTeamUpStartTime); - avatar.Properties.RemoveProperty(PropertyEnum.AvatarTeamUpDuration); - } - - return true; - } - - #endregion } } diff --git a/src/MHServerEmu.Games/Entities/Player.cs b/src/MHServerEmu.Games/Entities/Player.cs index 66b32707..de1818ba 100644 --- a/src/MHServerEmu.Games/Entities/Player.cs +++ b/src/MHServerEmu.Games/Entities/Player.cs @@ -403,8 +403,7 @@ namespace MHServerEmu.Games.Entities bool success = base.Serialize(archive); - if (archive.Version >= ArchiveVersion.AddedMissions) - success &= Serializer.Transfer(archive, MissionManager); + success &= Serializer.Transfer(archive, MissionManager); success &= Serializer.Transfer(archive, ref _avatarProperties); @@ -455,39 +454,34 @@ namespace MHServerEmu.Games.Entities if (archive.InvolvesClient == false) { - if (archive.Version >= ArchiveVersion.ImplementedMapDiscoveryDataPersistence) - success &= Serializer.Transfer(archive, ref _mapDiscoveryDict); + success &= Serializer.Transfer(archive, ref _mapDiscoveryDict); - if (archive.Version >= ArchiveVersion.AddedVendorPurchaseData) + // Vendor purchase data + uint numVendorPurchaseData = (uint)_vendorPurchaseDataDict.Count; + success &= Serializer.Transfer(archive, ref numVendorPurchaseData); + + if (archive.IsPacking) { - uint numVendorPurchaseData = (uint)_vendorPurchaseDataDict.Count; - success &= Serializer.Transfer(archive, ref numVendorPurchaseData); + foreach (VendorPurchaseData purchaseData in _vendorPurchaseDataDict.Values) + success &= Serializer.Transfer(archive, purchaseData); + } + else + { + _vendorPurchaseDataDict.Clear(); - if (archive.IsPacking) + for (uint i = 0; i < numVendorPurchaseData; i++) { - foreach (VendorPurchaseData purchaseData in _vendorPurchaseDataDict.Values) - success &= Serializer.Transfer(archive, purchaseData); - } - else - { - _vendorPurchaseDataDict.Clear(); + VendorPurchaseData purchaseData = new(PrototypeId.Invalid); + success &= Serializer.Transfer(archive, ref purchaseData); - for (uint i = 0; i < numVendorPurchaseData; i++) - { - VendorPurchaseData purchaseData = new(PrototypeId.Invalid); - success &= Serializer.Transfer(archive, ref purchaseData); - - if (_vendorPurchaseDataDict.TryAdd(purchaseData.InventoryProtoRef, purchaseData) == false) - Logger.Warn($"Serialize(): Failed to add deserialized vendor purchase data {purchaseData}"); - } + if (_vendorPurchaseDataDict.TryAdd(purchaseData.InventoryProtoRef, purchaseData) == false) + Logger.Warn($"Serialize(): Failed to add deserialized vendor purchase data {purchaseData}"); } } - if (archive.Version >= ArchiveVersion.ImplementedLoginRewards) - { - success &= Serializer.Transfer(archive, ref _loginCount); - success &= Serializer.Transfer(archive, ref _loginRewardCooldownTimeStart); - } + // Login count + success &= Serializer.Transfer(archive, ref _loginCount); + success &= Serializer.Transfer(archive, ref _loginRewardCooldownTimeStart); } return success; diff --git a/src/MHServerEmu.Games/Regions/Maps/MapDiscoveryData.cs b/src/MHServerEmu.Games/Regions/Maps/MapDiscoveryData.cs index 6cd9e2b1..1add5de8 100644 --- a/src/MHServerEmu.Games/Regions/Maps/MapDiscoveryData.cs +++ b/src/MHServerEmu.Games/Regions/Maps/MapDiscoveryData.cs @@ -41,9 +41,7 @@ namespace MHServerEmu.Games.Regions.Maps bool success = true; success &= Serializer.Transfer(archive, ref _regionId); - - if (archive.Version >= ArchiveVersion.AddedRegionProtoRefToMapDiscoveryData) - success &= Serializer.Transfer(archive, ref _regionProtoRef); + success &= Serializer.Transfer(archive, ref _regionProtoRef); success &= Serializer.Transfer(archive, ref _accessTimestamp); success &= Serializer.Transfer(archive, ref _discoveredEntities); From 00e3bcb084079c64ac83d4199aa53484fa767502 Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sat, 7 Mar 2026 16:54:43 +0300 Subject: [PATCH 06/17] Rename _bufferStream to _buffer in Archive for client consistency --- src/MHServerEmu.Core/Serialization/Archive.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/MHServerEmu.Core/Serialization/Archive.cs b/src/MHServerEmu.Core/Serialization/Archive.cs index fc12adf5..1efbc9d0 100644 --- a/src/MHServerEmu.Core/Serialization/Archive.cs +++ b/src/MHServerEmu.Core/Serialization/Archive.cs @@ -44,7 +44,7 @@ namespace MHServerEmu.Core.Serialization [ThreadStatic] private static MemoryStream SharedAutoBuffer; - private readonly MemoryStream _bufferStream; // MemoryStream replaces AutoBuffer from the original implementation + private readonly MemoryStream _buffer; // MemoryStream replaces StreamAutoBuffer from the original implementation // C# coded stream implementation is buffered, so we have to use the same stream for the whole archive private readonly CodedOutputStream _cos; @@ -105,12 +105,12 @@ namespace MHServerEmu.Core.Serialization InitializeBuffers(); // Reuse the same stream for all packing archives - _bufferStream = SharedAutoBuffer; - if (_bufferStream.Length > 0) - _bufferStream.SetLength(0); + _buffer = SharedAutoBuffer; + if (_buffer.Length > 0) + _buffer.SetLength(0); // Use reflection hackery to reuse the same buffer for all coded output streams, see ProtobufHelper for details. - _cos = ProtobufHelper.CodedOutputStreamEx.CreateInstance(_bufferStream, WriteBuffer); + _cos = ProtobufHelper.CodedOutputStreamEx.CreateInstance(_buffer, WriteBuffer); SerializeType = serializeType; ReplicationPolicy = replicationPolicy; @@ -130,8 +130,8 @@ namespace MHServerEmu.Core.Serialization InitializeBuffers(); - _bufferStream = new(buffer); - _cis = CodedInputStream.CreateInstance(_bufferStream, ReadBuffer); + _buffer = new(buffer); + _cis = CodedInputStream.CreateInstance(_buffer, ReadBuffer); SerializeType = serializeType; IsPacking = false; @@ -163,7 +163,7 @@ namespace MHServerEmu.Core.Serialization public void Dispose() { - _bufferStream.SetLength(0); + _buffer.SetLength(0); } /// @@ -174,7 +174,7 @@ namespace MHServerEmu.Core.Serialization /// public MemoryStream AccessAutoBuffer() { - return _bufferStream; + return _buffer; } /// @@ -183,12 +183,12 @@ namespace MHServerEmu.Core.Serialization public ByteString ToByteString() { // We use ByteString.Unsafe here to avoid copying data one extra time (Stream -> ByteString instead of Stream -> Buffer -> ByteString). - return ByteString.Unsafe.FromBytes(_bufferStream.ToArray()); + return ByteString.Unsafe.FromBytes(_buffer.ToArray()); } public Span AsSpan() { - byte[] buffer = _bufferStream.GetBuffer(); + byte[] buffer = _buffer.GetBuffer(); return buffer.AsSpan(0, (int)CurrentOffset); } @@ -271,7 +271,7 @@ namespace MHServerEmu.Core.Serialization } else { - _bufferStream.WriteByteAt(_lastBitEncodedOffset, bitBuffer); + _buffer.WriteByteAt(_lastBitEncodedOffset, bitBuffer); if (numEncodedBits >= 5) _lastBitEncodedOffset = 0; } @@ -824,7 +824,7 @@ namespace MHServerEmu.Core.Serialization // NOTE: PropertyCollection::serializeWithDefault() manipulates the archive buffer directly. First it allocates 4 bytes // for the number of properties, than it writes all the properties, and then it goes back and updates the number. // NOTE2: Persistent archives also do this for all ISerialize objects, except it writes the number of bytes written. - return _bufferStream.WriteUInt32At(position, value); + return _buffer.WriteUInt32At(position, value); } /// @@ -939,7 +939,7 @@ namespace MHServerEmu.Core.Serialization { if (_lastBitEncodedOffset == 0) return null; - if (_bufferStream.ReadByteAt(_lastBitEncodedOffset, out byte lastBitEncoded) == false) + if (_buffer.ReadByteAt(_lastBitEncodedOffset, out byte lastBitEncoded) == false) { SetError("Failed getting last bit encoded!"); return null; From 18bbcb4e42ca626996d2bcc5bc3e8e808ebf361e Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sat, 7 Mar 2026 18:10:10 +0300 Subject: [PATCH 07/17] Add archive size and game build number to persistent archive headers --- src/MHServerEmu.Core/Serialization/Archive.cs | 83 ++++++++++++++++++- .../Properties/PropertyCollection.cs | 8 +- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/MHServerEmu.Core/Serialization/Archive.cs b/src/MHServerEmu.Core/Serialization/Archive.cs index 1efbc9d0..2048e917 100644 --- a/src/MHServerEmu.Core/Serialization/Archive.cs +++ b/src/MHServerEmu.Core/Serialization/Archive.cs @@ -29,6 +29,22 @@ namespace MHServerEmu.Core.Serialization Current = Initial } + public enum GameBuildNumber : uint + { + Invalid = 0, + + // Changelist is the most consistent single number we have to identify different builds of the game, so use it for persistent archives. + // Add more changelist numbers here for any other versions of the game we are going to support. + _1_10_0_69 = 324, + _1_10_0_643 = 16688, + _1_48_0_1618 = 380454, + _1_48_0_1712 = 391562, + _1_52_0_1700 = 479899, + _1_53_0_203 = 493640, + + Current = _1_52_0_1700 + } + /// /// An implementation of the custom Gazillion serialization archive format. /// @@ -201,8 +217,14 @@ namespace MHServerEmu.Core.Serialization if (IsPersistent) { + // Write archive size placeholder that will be updated via UpdateSizeInArchive() when other data is written. + WriteUnencodedStream(0u); + uint version = (uint)Version; success &= Transfer(ref version); + + uint gameBuildNumber = (uint)GameBuildNumber.Current; + success &= Transfer(ref gameBuildNumber); } else if (IsReplication) { @@ -222,9 +244,24 @@ namespace MHServerEmu.Core.Serialization if (IsPersistent) { + uint sizeUsed = 0; + success &= ReadUnencodedStream(ref sizeUsed); + + if (sizeUsed != _buffer.Length) + { + SetError("Buffer size mismatch!"); + return false; + } + uint version = 0; success &= Transfer(ref version); Version = (ArchiveVersion)version; + + // For now just log a warning if there is a game build mismatch, in the future we can use this for migration between versions. + uint gameBuildNumber = 0; + success &= Transfer(ref gameBuildNumber); + if (gameBuildNumber != (uint)GameBuildNumber.Current) + Logger.Warn($"Game build number mismatch: expected {(uint)GameBuildNumber.Current}, got {gameBuildNumber}"); } else if (IsReplication) { @@ -624,7 +661,8 @@ namespace MHServerEmu.Core.Serialization if (IsPacking) { - return WriteVarint(ioData); + WriteVarint(ioData); + UpdateSizeInArchive(); } else { @@ -651,7 +689,8 @@ namespace MHServerEmu.Core.Serialization if (IsPacking) { - return WriteVarint(ioData); + WriteVarint(ioData); + UpdateSizeInArchive(); } else { @@ -747,6 +786,40 @@ namespace MHServerEmu.Core.Serialization return true; } + private bool UpdateSizeInArchive() + { + if (IsPersistent == false) + return false; + + if (IsPacking == false) + { + SetError("Cant use on unpack!"); + return false; + } + + Span sizeToken = GetSizeTokenAtOffset(0); + if (sizeToken.Length == 0) + { + SetError("Error accessing size in the buffer!"); + return false; + } + + sizeToken[0] = (uint)CurrentOffset; + return true; + } + + private Span GetSizeTokenAtOffset(uint offset) + { + if (CurrentOffset < offset + sizeof(uint)) + { + SetError("Failed writing size in use!"); + return default; + } + + Span sizeToken = new(_buffer.GetBuffer(), (int)offset, sizeof(uint)); + return MemoryMarshal.Cast(sizeToken); + } + #endregion #region Stream IO @@ -758,6 +831,9 @@ namespace MHServerEmu.Core.Serialization { _cos.WriteRawByte(value); _cos.Flush(); + + UpdateSizeInArchive(); + return true; } @@ -788,6 +864,9 @@ namespace MHServerEmu.Core.Serialization _cos.WriteRawByte(@byte); _cos.Flush(); + + UpdateSizeInArchive(); + return true; } diff --git a/src/MHServerEmu.Games/Properties/PropertyCollection.cs b/src/MHServerEmu.Games/Properties/PropertyCollection.cs index 406233e1..7cb4205f 100644 --- a/src/MHServerEmu.Games/Properties/PropertyCollection.cs +++ b/src/MHServerEmu.Games/Properties/PropertyCollection.cs @@ -771,9 +771,9 @@ namespace MHServerEmu.Games.Properties if (archive.IsPacking) { - // NOTE: PropertyCollection::serializeWithDefault() does a weird thing where it manipulates the archive buffer directly. - // First it allocates 4 bytes for the number of properties, than it writes all the properties, and then it goes back - // and updates the number. + // NOTE: PropertyCollection::serializeWithDefault() manipulates the archive buffer directly. + // It allocates 4 bytes for the number of properties, writes all the properties, and then + // it goes back and updates the number. // Remember current offset and reserve 4 bytes long numPropertiesOffset = archive.CurrentOffset; @@ -785,7 +785,7 @@ namespace MHServerEmu.Games.Properties success &= SerializePropertyForPacking(kvp, ref numProperties, archive, defaultCollection); // Write the number of serialized properties to the reserved bytes - archive.WriteUnencodedStream(numProperties, numPropertiesOffset); + archive.WriteUnencodedStream(numProperties, numPropertiesOffset); } else { From 69cf3dc8b2a8bd1fed40eac3a52e91f9465588fd Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sat, 7 Mar 2026 18:52:25 +0300 Subject: [PATCH 08/17] Add minimum schema version when loading SQLite db files --- .../MHServerEmu.DatabaseAccess.csproj | 2 ++ .../SQLite/SQLiteDBManager.cs | 15 ++++++++++++++- .../SQLite/Scripts/InitializeDatabase.sql | 2 +- .../SQLite/Scripts/Migrations/5.sql | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/MHServerEmu.DatabaseAccess/SQLite/Scripts/Migrations/5.sql diff --git a/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj b/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj index 22df9f5b..ed0efa0b 100644 --- a/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj +++ b/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj @@ -21,6 +21,7 @@ + @@ -31,6 +32,7 @@ + diff --git a/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteDBManager.cs b/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteDBManager.cs index 969cbfdb..68d11e80 100644 --- a/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteDBManager.cs +++ b/src/MHServerEmu.DatabaseAccess/SQLite/SQLiteDBManager.cs @@ -13,7 +13,8 @@ namespace MHServerEmu.DatabaseAccess.SQLite /// public class SQLiteDBManager : IDBManager { - private const int CurrentSchemaVersion = 5; // Increment this when making changes to the database schema + private const int CurrentSchemaVersion = 6; // Increment this when making changes to the database schema + private const int MinimumSchemaVersion = 6; // Used to ignore legacy 0.x database files. private const int NumTestAccounts = 5; // Number of test accounts to create for new database files private const int NumPlayerDataWriteAttempts = 3; // Number of write attempts to do when saving player data @@ -397,6 +398,18 @@ namespace MHServerEmu.DatabaseAccess.SQLite if (schemaVersion > CurrentSchemaVersion) return Logger.ErrorReturn(false, $"Initialize(): Existing database file uses unsupported schema version {schemaVersion} (current = {CurrentSchemaVersion})"); + if (schemaVersion < MinimumSchemaVersion) + { + Logger.Warn($"Found existing database file with legacy schema version {schemaVersion}, which is not supported by this version of MHServerEmu"); + + // Need to dispose the connection before moving the database file so that the file is not in use. + connection.Dispose(); + File.Move(_dbFilePath, $"{_dbFilePath}.old"); + + InitializeDatabaseFile(); + return true; + } + Logger.Info($"Found existing database file with schema version {schemaVersion} (current = {CurrentSchemaVersion})"); if (schemaVersion == CurrentSchemaVersion) diff --git a/src/MHServerEmu.DatabaseAccess/SQLite/Scripts/InitializeDatabase.sql b/src/MHServerEmu.DatabaseAccess/SQLite/Scripts/InitializeDatabase.sql index 4399495f..0bf35129 100644 --- a/src/MHServerEmu.DatabaseAccess/SQLite/Scripts/InitializeDatabase.sql +++ b/src/MHServerEmu.DatabaseAccess/SQLite/Scripts/InitializeDatabase.sql @@ -1,6 +1,6 @@ -- Initialize a new database file using the current schema version -PRAGMA user_version=5; +PRAGMA user_version=6; PRAGMA journal_mode=WAL; CREATE TABLE "Account" ( diff --git a/src/MHServerEmu.DatabaseAccess/SQLite/Scripts/Migrations/5.sql b/src/MHServerEmu.DatabaseAccess/SQLite/Scripts/Migrations/5.sql new file mode 100644 index 00000000..1298879f --- /dev/null +++ b/src/MHServerEmu.DatabaseAccess/SQLite/Scripts/Migrations/5.sql @@ -0,0 +1 @@ +-- Empty version, this is used only to prevent 0.x database files from being loaded in 1.x+. \ No newline at end of file From 9a8beaa7e3c973d35ca436d85c0e77e4c9d6f284 Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sun, 8 Mar 2026 16:35:37 +0300 Subject: [PATCH 09/17] Fix region instances being created for empty matches --- src/MHServerEmu.PlayerManagement/Matchmaking/Match.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/MHServerEmu.PlayerManagement/Matchmaking/Match.cs b/src/MHServerEmu.PlayerManagement/Matchmaking/Match.cs index fcee44a0..99c1ec0c 100644 --- a/src/MHServerEmu.PlayerManagement/Matchmaking/Match.cs +++ b/src/MHServerEmu.PlayerManagement/Matchmaking/Match.cs @@ -102,6 +102,9 @@ namespace MHServerEmu.PlayerManagement.Matchmaking if (IsFull() == false && IsBypass == false && Queue.Prototype.QueueDoNotWaitToFull == false) return false; + if (IsEmpty()) + return false; + foreach (MatchTeam team in _teams) { foreach ((RegionRequestGroup group, _) in team.Groups) From d386cff233eac0818acf7f47c0be86012a451183 Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sun, 8 Mar 2026 16:38:00 +0300 Subject: [PATCH 10/17] Restrict !item creditchest command to admins --- docs/ServerEmu/ServerCommands.md | 4 ++-- src/MHServerEmu/Commands/Implementations/ItemCommands.cs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/ServerEmu/ServerCommands.md b/docs/ServerEmu/ServerCommands.md index 2e800faf..21eef7eb 100644 --- a/docs/ServerEmu/ServerCommands.md +++ b/docs/ServerEmu/ServerCommands.md @@ -1,6 +1,6 @@ # Server Commands -This list was automatically generated on `2026.01.21 10:37:50 UTC` using server version `0.8.1`. +This list was automatically generated on `2026.03.08 13:37:05 UTC` using server version `1.0.0`. To see an up to date list of all commands, type !commands in the server console or the in-game chat. When invoking a command from in-game your account has to meet the user level requirement for the command. @@ -108,7 +108,7 @@ Commands for managing items. | Command | Description | User Level | Invoker Type | | ---------------------------- | -------------------------------------------------------------------------- | ---------- | ------------ | | !item cleardeliverybox | Destroys all items contained in the delivery box inventory. | Any | Client | -| !item creditchest | Converts 500k credits to a sellable chest item. | Any | Client | +| !item creditchest | Converts 500k credits to a sellable chest item. | Admin | Client | | !item destroyindestructible | Destroys indestructible items contained in the player's general inventory. | Any | Client | | !item drop [pattern] [count] | Creates and drops the specified item from the current avatar. | Admin | Client | | !item give [pattern] [count] | Creates and gives the specified item to the current player. | Admin | Client | diff --git a/src/MHServerEmu/Commands/Implementations/ItemCommands.cs b/src/MHServerEmu/Commands/Implementations/ItemCommands.cs index 1163efee..41a4d47a 100644 --- a/src/MHServerEmu/Commands/Implementations/ItemCommands.cs +++ b/src/MHServerEmu/Commands/Implementations/ItemCommands.cs @@ -149,6 +149,7 @@ namespace MHServerEmu.Commands.Implementations [Command("creditchest")] [CommandDescription("Converts 500k credits to a sellable chest item.")] [CommandUsage("item creditchest")] + [CommandUserLevel(AccountUserLevel.Admin)] [CommandInvokerType(CommandInvokerType.Client)] public string CreditChest(string[] @params, NetClient client) { From 48c2ce65b2d7dd2404f7f8fb1a302ec99aa07a40 Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sun, 8 Mar 2026 16:42:54 +0300 Subject: [PATCH 11/17] Remove the Linux compatibility mode account flag --- src/MHServerEmu.DatabaseAccess/Models/DBAccount.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/MHServerEmu.DatabaseAccess/Models/DBAccount.cs b/src/MHServerEmu.DatabaseAccess/Models/DBAccount.cs index ad1e7435..f34e017d 100644 --- a/src/MHServerEmu.DatabaseAccess/Models/DBAccount.cs +++ b/src/MHServerEmu.DatabaseAccess/Models/DBAccount.cs @@ -16,12 +16,12 @@ namespace MHServerEmu.DatabaseAccess.Models [Flags] public enum AccountFlags { - None = 0, - IsBanned = 1 << 0, - IsArchived = 1 << 1, - IsPasswordExpired = 1 << 2, - DEPRECATEDLinuxCompatibilityMode = 1 << 3, // This flag used to disable session token verification, but it is no longer needed for Linux users - IsWhitelisted = 1 << 4, + None = 0, + IsBanned = 1 << 0, + IsArchived = 1 << 1, + IsPasswordExpired = 1 << 2, + // 1 << 3 was previously used in 0.x for the Linux compatibility mode, it should not be set in any 1.x+ databases. + IsWhitelisted = 1 << 4, } /// From 6f2ada76329614e7ca5256e9163ae782407ffa72 Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Mon, 9 Mar 2026 18:09:55 +0300 Subject: [PATCH 12/17] Add config settings for the !item creditchest command --- docs/ServerEmu/ServerCommands.md | 4 ++-- .../CustomGameOptionsConfig.cs | 2 ++ .../Commands/Implementations/ItemCommands.cs | 23 ++++++++++++------- src/MHServerEmu/Config.ini | 4 ++++ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/docs/ServerEmu/ServerCommands.md b/docs/ServerEmu/ServerCommands.md index 21eef7eb..8334ad37 100644 --- a/docs/ServerEmu/ServerCommands.md +++ b/docs/ServerEmu/ServerCommands.md @@ -1,6 +1,6 @@ # Server Commands -This list was automatically generated on `2026.03.08 13:37:05 UTC` using server version `1.0.0`. +This list was automatically generated on `2026.03.09 15:06:03 UTC` using server version `1.0.0`. To see an up to date list of all commands, type !commands in the server console or the in-game chat. When invoking a command from in-game your account has to meet the user level requirement for the command. @@ -108,7 +108,7 @@ Commands for managing items. | Command | Description | User Level | Invoker Type | | ---------------------------- | -------------------------------------------------------------------------- | ---------- | ------------ | | !item cleardeliverybox | Destroys all items contained in the delivery box inventory. | Any | Client | -| !item creditchest | Converts 500k credits to a sellable chest item. | Admin | Client | +| !item creditchest | Converts credits to a sellable chest item. | Any | Client | | !item destroyindestructible | Destroys indestructible items contained in the player's general inventory. | Any | Client | | !item drop [pattern] [count] | Creates and drops the specified item from the current avatar. | Admin | Client | | !item give [pattern] [count] | Creates and gives the specified item to the current player. | Admin | Client | diff --git a/src/MHServerEmu.Games/CustomGameOptionsConfig.cs b/src/MHServerEmu.Games/CustomGameOptionsConfig.cs index d1f73812..90ce0679 100644 --- a/src/MHServerEmu.Games/CustomGameOptionsConfig.cs +++ b/src/MHServerEmu.Games/CustomGameOptionsConfig.cs @@ -11,6 +11,8 @@ namespace MHServerEmu.Games public bool AutoUnlockTeamUps { get; private set; } = true; public bool DisableMovementPowerChargeCost { get; private set; } = false; public bool AllowSameGroupTalents { get; private set; } = false; + public bool EnableCreditChestConversion { get; private set; } = false; + public float CreditChestConversionMultiplier { get; private set; } = 2f; public bool DisableInstancedLoot { get; private set; } = false; public float LootSpawnGridCellRadius { get; private set; } = 20f; public float TrashedItemExpirationTimeMultiplier { get; private set; } = 1f; diff --git a/src/MHServerEmu/Commands/Implementations/ItemCommands.cs b/src/MHServerEmu/Commands/Implementations/ItemCommands.cs index 41a4d47a..13943267 100644 --- a/src/MHServerEmu/Commands/Implementations/ItemCommands.cs +++ b/src/MHServerEmu/Commands/Implementations/ItemCommands.cs @@ -147,30 +147,37 @@ namespace MHServerEmu.Commands.Implementations } [Command("creditchest")] - [CommandDescription("Converts 500k credits to a sellable chest item.")] + [CommandDescription("Converts credits to a sellable chest item.")] [CommandUsage("item creditchest")] - [CommandUserLevel(AccountUserLevel.Admin)] [CommandInvokerType(CommandInvokerType.Client)] public string CreditChest(string[] @params, NetClient client) { - const PrototypeId CreditItemProtoRef = (PrototypeId)13983056721138685632; + const PrototypeId CreditItemProtoRef = (PrototypeId)13983056721138685632; // Entity/Items/Crafting/Ingredients/CreditItem500k.prototype const int CreditItemPrice = 500000; PlayerConnection playerConnection = (PlayerConnection)client; Player player = playerConnection.Player; + var options = player.Game.CustomGameOptions; + + if (options.EnableCreditChestConversion == false) + return "Credit chest conversion is disabled by server settings"; + PropertyId creditsProperty = new(PropertyEnum.Currency, GameDatabase.CurrencyGlobalsPrototype.Credits); + int price = (int)(CreditItemPrice * options.CreditChestConversionMultiplier); - if (player.Properties[creditsProperty] < CreditItemPrice) - return "You need at least 500 000 credits to use this command."; + if (price <= 0) + return "Failed to calculate credit chest price."; - // Entity/Items/Crafting/Ingredients/CreditItem500k.prototype - player.Properties.AdjustProperty(-CreditItemPrice, creditsProperty); + if (player.Properties[creditsProperty] < price) + return $"You need at least {price} credits to use this command."; + + player.Properties.AdjustProperty(-price, creditsProperty); player.Game.LootManager.GiveItem(CreditItemProtoRef, LootContext.CashShop, player); Logger.Trace($"CreditChest(): {player}"); - return $"Converted 500 000 credits to a Credit Chest."; + return $"Converted {price} credits to a Credit Chest."; } [Command("cleardeliverybox")] diff --git a/src/MHServerEmu/Config.ini b/src/MHServerEmu/Config.ini index 0c5266f9..adced732 100644 --- a/src/MHServerEmu/Config.ini +++ b/src/MHServerEmu/Config.ini @@ -197,6 +197,8 @@ OrbisTrophiesEnabled=true ; AutoUnlockTeamUps: Automatically unlocks all team-ups for players who have finished the tutorial. ; DisableMovementPowerChargeCost: Disables charge costs for movement powers to imitate pre-BUE behavior. ; AllowSameGroupTalents: Allows mutually exclusive talents to be enabled at the same time. +; EnableCreditChestConversion: Allows conversion of credits to sellable chest items via the !item creditchest command. +; CreditChestConversionMultiplier: Cost multiplier for converting credits to chest items. ; DisableInstancedLoot: Makes loot free-for-all, Diablo 2 style. ; LootSpawnGridCellRadius: Affects the spacing of loot that drops [16-128]. ; TrashedItemExpirationTimeMultiplier: Affects how long items trashed by players remain on the ground. @@ -213,6 +215,8 @@ AutoUnlockAvatars=false AutoUnlockTeamUps=false DisableMovementPowerChargeCost=false AllowSameGroupTalents=false +EnableCreditChestConversion=false +CreditChestConversionMultiplier=2.0 DisableInstancedLoot=false LootSpawnGridCellRadius=20.0 TrashedItemExpirationTimeMultiplier=1.0 From bfa1cb3db45e241b70ae9e38152daf2968a6355d Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Tue, 10 Mar 2026 03:12:10 +0300 Subject: [PATCH 13/17] Add -locale launch parameter to documentation --- docs/Client/ClientLaunchParameters.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/Client/ClientLaunchParameters.md b/docs/Client/ClientLaunchParameters.md index bb5e8923..b6a050f6 100644 --- a/docs/Client/ClientLaunchParameters.md +++ b/docs/Client/ClientLaunchParameters.md @@ -22,7 +22,7 @@ Some of these parameters can be set automatically by using [Bifrost](https://git These require further checking for compatibility with various versions of the client. -### ClientApp::Initialize +### ClientApp::Initialize() | Parameter | Description | Values | | ----------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | @@ -42,7 +42,7 @@ These require further checking for compatibility with various versions of the cl | -novalidate | | | | -nohardwareinfo | | | -### ClientAppSettingsPostCoreInit::Initialize +### ClientAppSettingsPostCoreInit::Initialize() | Parameter | Description | Values | | ---------------------- | -------------------------------------------------------------------------------------------- | ------------ | @@ -86,7 +86,7 @@ These require further checking for compatibility with various versions of the cl | -streaming | | | | -platform= | | orbis, dingo | -### SiteConfig::ParseCommandLine +### SiteConfig::ParseCommandLine() | Parameter | Description | Values | | ------------ | ----------- | ------ | @@ -94,44 +94,50 @@ These require further checking for compatibility with various versions of the cl | -authserver= | | | | -authurl= | | | -### ClientGame::Initialize +### ClientGame::Initialize() | Parameter | Description | Values | | ----------- | ----------- | ------ | | -nooverwolf | | | -### Atlas::Initialize +### Atlas::Initialize() | Parameter | Description | Values | | ------------- | ----------- | ------ | | -nomapmarkers | | | -### AudioManager::Initialize +### AudioManager::Initialize() | Parameter | Description | Values | | -------------- | ----------- | ------ | | -nomusic | | | | -foleyineditor | | | -### UnrealAudioManager::Initialize +### UnrealAudioManager::Initialize() | Parameter | Description | Values | | ------------------ | ----------- | ------ | | -audioglobalfocus | | | | -audiofilepackages | | | -### UnrealGameAdapter::Initialize +### UnrealGameAdapter::Initialize() | Parameter | Description | Values | | ---------------- | ----------- | ------ | | -dependencygraph | | | -### UnrealGameAdapter::SendSteamUserInfo +### UnrealGameAdapter::SendSteamUserInfo() | Parameter | Description | Values | | ----------------------- | ----------- | ------ | | -steamachievementupdate | | | +### InitGazillionSystems() + +| Parameter | Description | Values | +| --------- | ------------------------ | ------------------------------------------------------------------------------ | +| -locale= | Overrides client locale. | Name of locale file under `Data\Game\Loco` without extension (e.g. `eng.all`). | + ## Logging Channels You can specify channels for verbose logging using the following syntax: `-LoggingChannels=-ALL,+GAME`. Available channels: From 947b284f39228d902154fc418eb493634216607a Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Wed, 11 Mar 2026 22:24:54 +0300 Subject: [PATCH 14/17] Add DisableMissionXPBonuses config setting --- src/MHServerEmu.Games/CustomGameOptionsConfig.cs | 7 ++++--- src/MHServerEmu.Games/Entities/Avatars/Avatar.cs | 3 +++ src/MHServerEmu/Config.ini | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/MHServerEmu.Games/CustomGameOptionsConfig.cs b/src/MHServerEmu.Games/CustomGameOptionsConfig.cs index 90ce0679..bbd9b14f 100644 --- a/src/MHServerEmu.Games/CustomGameOptionsConfig.cs +++ b/src/MHServerEmu.Games/CustomGameOptionsConfig.cs @@ -7,8 +7,8 @@ namespace MHServerEmu.Games public int AutosaveIntervalMinutes { get; private set; } = 15; public float ESCooldownOverrideMinutes { get; private set; } = -1f; public bool CombineESStacks { get; private set; } = false; - public bool AutoUnlockAvatars { get; private set; } = true; - public bool AutoUnlockTeamUps { get; private set; } = true; + public bool AutoUnlockAvatars { get; private set; } = false; + public bool AutoUnlockTeamUps { get; private set; } = false; public bool DisableMovementPowerChargeCost { get; private set; } = false; public bool AllowSameGroupTalents { get; private set; } = false; public bool EnableCreditChestConversion { get; private set; } = false; @@ -17,7 +17,8 @@ namespace MHServerEmu.Games public float LootSpawnGridCellRadius { get; private set; } = 20f; public float TrashedItemExpirationTimeMultiplier { get; private set; } = 1f; public bool DisableAccountBinding { get; private set; } = false; - public bool DisableCharacterBinding { get; private set; } = true; + public bool DisableCharacterBinding { get; private set; } = false; + public bool DisableMissionXPBonuses { get; private set; } = false; public bool UsePrestigeLootTable { get; private set; } = false; public bool EnableUltimatePrestige { get; private set; } = false; public bool ApplyHiddenPvPDamageModifiers { get; private set; } = false; diff --git a/src/MHServerEmu.Games/Entities/Avatars/Avatar.cs b/src/MHServerEmu.Games/Entities/Avatars/Avatar.cs index a94a6774..9cdb157e 100644 --- a/src/MHServerEmu.Games/Entities/Avatars/Avatar.cs +++ b/src/MHServerEmu.Games/Entities/Avatars/Avatar.cs @@ -4716,6 +4716,9 @@ namespace MHServerEmu.Games.Entities.Avatars if (IsInWorld == false) return 0f; + if (Game.CustomGameOptions.DisableMissionXPBonuses) + return 1f; + TuningPrototype tuningProto = tuningTable.Prototype; if (tuningProto == null) return Logger.WarnReturn(0f, "GetMissionXPMultiplier(): tuningProto == null"); diff --git a/src/MHServerEmu/Config.ini b/src/MHServerEmu/Config.ini index adced732..2eccce49 100644 --- a/src/MHServerEmu/Config.ini +++ b/src/MHServerEmu/Config.ini @@ -204,6 +204,7 @@ OrbisTrophiesEnabled=true ; TrashedItemExpirationTimeMultiplier: Affects how long items trashed by players remain on the ground. ; DisableAccountBinding: Disables account binding on pickup for items. ; DisableCharacterBinding: Disables character binding on equip for items. +; DisableMissionXPBonuses: Prevents mission experience rewards from being affected by experience bonuses. ; UsePrestigeLootTable: Replaces starting costume prestige rewards with the loot table specified in game data. ; EnableUltimatePrestige: Allows prestige level to be reset after reaching the prestige level cap. ; ApplyHiddenPvPDamageModifiers: Enables hidden damage modifiers based on K/D ratio, match history, and Omega level in PvP combat. @@ -222,6 +223,7 @@ LootSpawnGridCellRadius=20.0 TrashedItemExpirationTimeMultiplier=1.0 DisableAccountBinding=false DisableCharacterBinding=false +DisableMissionXPBonuses=false UsePrestigeLootTable=false EnableUltimatePrestige=false ApplyHiddenPvPDamageModifiers=false From cf7123a5ea67f85b4b0b450b576ac7cf4220029a Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Fri, 13 Mar 2026 09:14:35 +0300 Subject: [PATCH 15/17] Add MIME types for xml and json to StaticFileWebHandler --- src/MHServerEmu.WebFrontend/Handlers/StaticFileWebHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MHServerEmu.WebFrontend/Handlers/StaticFileWebHandler.cs b/src/MHServerEmu.WebFrontend/Handlers/StaticFileWebHandler.cs index 7ca30ebb..601a07a1 100644 --- a/src/MHServerEmu.WebFrontend/Handlers/StaticFileWebHandler.cs +++ b/src/MHServerEmu.WebFrontend/Handlers/StaticFileWebHandler.cs @@ -62,6 +62,8 @@ namespace MHServerEmu.WebFrontend.Handlers ".jpeg" => "image/jpeg", ".gif" => "image/gif", ".ico" => "image/x-icon", + ".xml" => "application/xml", + ".json" => "application/json", _ => "application/octet-stream", }; } From de7d68cba1fa33c16de65cf1a97baa9a58eecbb4 Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sat, 14 Mar 2026 00:09:03 +0300 Subject: [PATCH 16/17] Increase Event.InfiniteLoopCheckLimit to match the client --- src/MHServerEmu.Games/Events/Event.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MHServerEmu.Games/Events/Event.cs b/src/MHServerEmu.Games/Events/Event.cs index ce170bae..e78c83c4 100644 --- a/src/MHServerEmu.Games/Events/Event.cs +++ b/src/MHServerEmu.Games/Events/Event.cs @@ -4,7 +4,7 @@ namespace MHServerEmu.Games.Events { public class Event { - public const int InfiniteLoopCheckLimit = 5000; // in client 100000 + public const int InfiniteLoopCheckLimit = 100000; private static readonly Logger Logger = LogManager.CreateLogger(); From 2c0ab87016cad781c4eadcc22b4c16c54cfebdff Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sat, 14 Mar 2026 10:17:28 +0300 Subject: [PATCH 17/17] Add emote unlock to the !dance command --- .../Commands/Implementations/MiscCommands.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/MHServerEmu/Commands/Implementations/MiscCommands.cs b/src/MHServerEmu/Commands/Implementations/MiscCommands.cs index 504dad9c..a562fea2 100644 --- a/src/MHServerEmu/Commands/Implementations/MiscCommands.cs +++ b/src/MHServerEmu/Commands/Implementations/MiscCommands.cs @@ -118,11 +118,17 @@ namespace MHServerEmu.Commands.Implementations [CommandInvokerType(CommandInvokerType.Client)] public string Dance(string[] @params, NetClient client) { - PlayerConnection playerConnection = (PlayerConnection)client; + const PrototypeId DancePowerRef = (PrototypeId)773103106671775187; // Powers/Emotes/EmoteDance.prototype - Avatar avatar = playerConnection.Player.CurrentAvatar; - var avatarPrototypeId = (AvatarPrototypeId)avatar.PrototypeDataRef; - switch (avatarPrototypeId) + Player player = ((PlayerConnection)client).Player; + Avatar avatar = player.CurrentAvatar; + + if (avatar == null) + return string.Empty; + + PrototypeId avatarProtoRef = avatar.PrototypeDataRef; + + switch ((AvatarPrototypeId)avatarProtoRef) { case AvatarPrototypeId.BlackPanther: case AvatarPrototypeId.BlackWidow: @@ -137,18 +143,20 @@ namespace MHServerEmu.Commands.Implementations case AvatarPrototypeId.Storm: case AvatarPrototypeId.Thing: case AvatarPrototypeId.Thor: - const PrototypeId dancePowerRef = (PrototypeId)773103106671775187; // Powers/Emotes/EmoteDance.prototype - - Power dancePower = avatar.GetPower(dancePowerRef); - if (dancePower == null) return "Dance power is not assigned to the current avatar."; + if (player.HasAvatarEmoteUnlocked(avatarProtoRef, DancePowerRef) == false) + player.UnlockAvatarEmote(avatarProtoRef, DancePowerRef); PowerActivationSettings settings = new(avatar.Id, avatar.RegionLocation.Position, avatar.RegionLocation.Position); settings.Flags = PowerActivationSettingsFlags.NotifyOwner; - avatar.ActivatePower(dancePowerRef, ref settings); + PowerUseResult result = avatar.ActivatePower(DancePowerRef, ref settings); + + if (result != PowerUseResult.Success) + return $"Failed to activate dance power ({result})."; + + return $"{avatarProtoRef.GetNameFormatted()} begins to dance."; - return $"{avatarPrototypeId} begins to dance"; default: - return $"{avatarPrototypeId} doesn't want to dance"; + return $"{avatarProtoRef.GetNameFormatted()} doesn't want to dance."; } }