From 2433769dbb402ce4384fc5e7a992d07e7722412f Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Fri, 6 Mar 2026 20:32:43 +0300 Subject: [PATCH 01/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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."; } } From 93f227260b4cae33c1db147a4e9bb466b98e12e9 Mon Sep 17 00:00:00 2001 From: AlexBond Date: Sun, 15 Mar 2026 00:39:48 +0300 Subject: [PATCH 18/30] Fix Russian text for Achievements --- .../Achievements/AchievementStringMap.json | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/MHServerEmu.Games/Data/Game/Achievements/AchievementStringMap.json b/src/MHServerEmu.Games/Data/Game/Achievements/AchievementStringMap.json index bd5bb852..b61c8b33 100644 --- a/src/MHServerEmu.Games/Data/Game/Achievements/AchievementStringMap.json +++ b/src/MHServerEmu.Games/Data/Game/Achievements/AchievementStringMap.json @@ -525,7 +525,7 @@ }, "340531828750317262": { "en_us": "Distinguished Service", - "ru_ru": "Выдающийся Служба" + "ru_ru": "Выдающаяся Служба" }, "342069245999437418": { "en_us": "Battle Axe", @@ -2157,7 +2157,7 @@ }, "1467535944805143146": { "en_us": "The Ring", - "ru_ru": "Кольцо" + "ru_ru": "Ринг" }, "1469073364201746950": { "en_us": "A.I.M. Weapon Facility", @@ -2943,7 +2943,7 @@ }, "2006108042620502170": { "en_us": "Danger: Escalated", - "ru_ru": "Опасность: Повышенная" + "ru_ru": "Опасность: Запредельная" }, "2007645459869622326": { "en_us": "Shark Figurine", @@ -3417,7 +3417,7 @@ }, "2329866266920875458": { "en_us": "Skrull Psylocke", - "ru_ru": "Скрулль Псайлок" + "ru_ru": "Скрулл Псайлок" }, "2331403686317479262": { "en_us": "Increased Melee Damage", @@ -4075,7 +4075,7 @@ }, "2765643682947327554": { "en_us": "Test of Lightning", - "ru_ru": "Испытание молнией" + "ru_ru": "Тест молнии" }, "2767181100196447710": { "en_us": "As Human Torch, group with Mister Fantastic, Invisible Woman, and The Thing to defeat #purplecard#Doctor Doom#/purplecard# in a #purplecard#Terminal#/purplecard#.", @@ -4594,7 +4594,7 @@ }, "3104776086181353346": { "en_us": "Tools of Vengeance", - "ru_ru": "Инструменты Мщения" + "ru_ru": "Инструменты мщения" }, "3106313505577957150": { "en_us": "Reward: Infinity Orb", @@ -4642,7 +4642,7 @@ }, "3145411503110740246": { "en_us": "Test of Endurance", - "ru_ru": "Испытание на выносливость" + "ru_ru": "Тест на выносливость" }, "3146948920359860402": { "en_us": "Reward: Coffer of Achievement", @@ -4890,7 +4890,7 @@ }, "3307290615260926890": { "en_us": "Test of Survival", - "ru_ru": "Испытание на выживание" + "ru_ru": "Тест на выживание" }, "3308828032510047046": { "en_us": "Kraven", @@ -4946,7 +4946,7 @@ }, "3331889301984267626": { "en_us": "Defeat any 10 #purplecard#opponents#/purplecard# in #purplecard#PvP#/purplecard# using 50 different #purplecard#heroes#/purplecard#.", - "ru_ru": "Играя за каждого #purplecard#героя#/purplecard#, победите 10 различных #purplecard#противников#/purplecard# в #purplecard#PvP#/purplecard#." + "ru_ru": "Играя за 50 различных #purplecard#героев#/purplecard#, победите по 10 #purplecard#противников#/purplecard# в #purplecard#PvP#/purplecard#." }, "3357150537832518374": { "en_us": "Elektra", @@ -5202,7 +5202,7 @@ }, "3525179323274152938": { "en_us": "Heads Up", - "ru_ru": "Будь на чеку" + "ru_ru": "Смотри вверх" }, "3526716740523273094": { "en_us": "Fantastic Teamwork", @@ -5702,7 +5702,7 @@ }, "3855087220865974146": { "en_us": "I Have a Bow and Arrow", - "ru_ru": "У меня есть Лук и Стрелы" + "ru_ru": "У меня есть лук и стрелы" }, "3856624638115094302": { "en_us": "As She-Hulk, defeat 1,980 #purplecard#elite#/purplecard# enemies using #purplecard#Finishers#/purplecard# as the finishing blow.", @@ -6697,7 +6697,7 @@ }, "4511828177256408954": { "en_us": "That's Freakin' Metal", - "ru_ru": "Тяжелый Металл" + "ru_ru": "Тяжёлый металл" }, "4513365596653012758": { "en_us": "Bad Lieutenant", @@ -6741,7 +6741,7 @@ }, "4552463594185795854": { "en_us": "Yellow Cupcake", - "ru_ru": "Жёлтый Кекс" + "ru_ru": "Жёлтый кекс" }, "4554001011434916010": { "en_us": "Industry City Patrol", @@ -6917,7 +6917,7 @@ }, "4671566800814142867": { "en_us": "The Smart Aleck Detective", - "ru_ru": "Умник-Детектив" + "ru_ru": "Детектив-всезнайка" }, "4673104220210746671": { "en_us": "Marvel Heroes", @@ -7527,7 +7527,7 @@ }, "5068246219307811867": { "en_us": "As Gambit, defeat #purplecard#Lady Deathstrike#/purplecard# in the #purplecard#S.H.I.E.L.D. Holo-Sim#/purplecard# while teamed with #purplecard#Wolverine#/purplecard#.", - "ru_ru": "Играя за Гамбита, победите #purplecard#Леди Смертельный Удар#/purplecard# в #purplecard#оло-симуляторе ЩИТа#/purplecard#, находясь в команде с #purplecard#Росомахой#/purplecard#." + "ru_ru": "Играя за Гамбита, победите #purplecard#Леди Смертельный Удар#/purplecard# в #purplecard#Голо-симуляторе ЩИТа#/purplecard#, находясь в команде с #purplecard#Росомахой#/purplecard#." }, "5069783636556932023": { "en_us": "Very Unstable Molecules", @@ -7663,7 +7663,7 @@ }, "5166428647201874679": { "en_us": "Test of Simplicity", - "ru_ru": "Испытание простотой" + "ru_ru": "Тест на простоту" }, "5167966064450994835": { "en_us": "Reward: Coffer of Achievement", @@ -8223,7 +8223,7 @@ }, "5546196467365287371": { "en_us": "Test of Fortitude", - "ru_ru": "Испытание стойкости" + "ru_ru": "Тест на стойкость" }, "5547733884614407527": { "en_us": "Invisible Woman and The Team", @@ -8338,7 +8338,7 @@ }, "5612967986119651515": { "en_us": "Metal Militia", - "ru_ru": "Металлическая Милиция" + "ru_ru": "Металл-милиция" }, "5614505405516255319": { "en_us": "Cyclops vs Doctor Doom", @@ -8505,7 +8505,7 @@ }, "5720374923950886207": { "en_us": "As Daredevil, defeat #purplecard#Elektra#/purplecard# in each zone in which she appears. It was a bad breakup.", - "ru_ru": "Играя за Сорвиголову, победите #purplecard#Электру#/purplecard# в каждой зоне, где она появляется. Это было тяжелое расставание." + "ru_ru": "Играя за Сорвиголову, победите #purplecard#Электру#/purplecard# в каждой зоне, где она появляется. Это было тяжёлое расставание." }, "5721912341200006363": { "en_us": "As Hawkeye, defeat 1,964 #purplecard#elite#/purplecard# enemies using any #purplecard#Trick Arrow#/purplecard# power as the finishing blow.", @@ -9131,7 +9131,7 @@ }, "6148465247289286579": { "en_us": "Mean Green Medallion", - "ru_ru": "Злобный Зелёный медальон" + "ru_ru": "Медальон Свирепого Зелёного" }, "6150002666685890383": { "en_us": "Ultimate Nullifier", @@ -10883,7 +10883,7 @@ }, "7300068052214936847": { "en_us": "X-Defense Grandmaster", - "ru_ru": "Великий мастер \"Защиты Икс\"" + "ru_ru": "Великий мастер Защиты Икс" }, "7301605469464057003": { "en_us": "Ash", @@ -11023,7 +11023,7 @@ }, "7401325316754723619": { "en_us": "As Wolverine, defeat the #purplecard#Mega-Sentinel#/purplecard# in #purplecard#Midtown Manhattan#/purplecard# while teamed with #purplecard#Cyclops#/purplecard#.", - "ru_ru": "Играя за Росомаху, победите #purplecard#Мегатража#/purplecard# в #purplecard#Мидтаун Манхэттена#/purplecard#, будучи в команде с #purplecard#Циклопом#/purplecard#." + "ru_ru": "Играя за Росомаху, победите #purplecard#Мега-Стража#/purplecard# в #purplecard#Мидтаун Манхэттена#/purplecard#, будучи в команде с #purplecard#Циклопом#/purplecard#." }, "7402862734003843775": { "en_us": "Reward: Infinity Orb", @@ -12009,7 +12009,7 @@ }, "8092552018930581055": { "en_us": "Defeat #purplecard#S.H.I.E.L.D.#/purplecard# infiltrators and show them that this place is only big enough for one S.H.I.E.L.D.", - "ru_ru": "Победите внедрённых #purplecard#агентов ЩИТа#/purplecard# и покажите им, что это место достаточно только для одного ЩИТа" + "ru_ru": "Победите внедрённых #purplecard#агентов ЩИТа#/purplecard# и покажите им, что здесь хватит места только для одного ЩИТа" }, "8094089436179701211": { "en_us": "Reward: Discarded Purse", @@ -13150,7 +13150,7 @@ }, "8865924418794455139": { "en_us": "Mind Over Metal", - "ru_ru": "Разум сильнее Металла" + "ru_ru": "Разум сильнее металла" }, "8867461838191058943": { "en_us": "Experimental Synthetic Explosive Pack", @@ -13813,7 +13813,7 @@ }, "9354033522694890104": { "en_us": "The Cosmic Cupcake", - "ru_ru": "Космический Кекс" + "ru_ru": "Космический кекс" }, "9355570939944010260": { "en_us": "No wait, NOW this is Journalism", @@ -14912,7 +14912,7 @@ }, "10080620836632896664": { "en_us": "Distinguished Valor", - "ru_ru": "Выдающийся Доблесть" + "ru_ru": "Выдающаяся Доблесть" }, "10082158253882016820": { "en_us": "Defeat #purplecard#Slag#/purplecard# without losing any of the #purplecard#Asgardian prisoners#/purplecard#.", @@ -15592,7 +15592,7 @@ }, "10561645921336096128": { "en_us": "Joy Ride", - "ru_ru": "Веселая Поездка" + "ru_ru": "Увлекательная прогулка" }, "10563183338585216284": { "en_us": "Subterranean Peacekeeping", @@ -15812,7 +15812,7 @@ }, "10717375362342318500": { "en_us": "When I Feel Heavy Metal", - "ru_ru": "Когда я чувствую тяжелый метал" + "ru_ru": "Когда я чувствую Тяжёлый металл" }, "10718912779591438656": { "en_us": "" @@ -15999,7 +15999,7 @@ }, "10835544223064877932": { "en_us": "Powered Up", - "ru_ru": "Полная Мощь" + "ru_ru": "Полная мощность" }, "10837081640313998088": { "en_us": "Old School Telepath", @@ -17377,7 +17377,7 @@ }, "11799131809720397016": { "en_us": "Test of Precision", - "ru_ru": "Испытание точности" + "ru_ru": "Тест на точность" }, "11800669226969517172": { "en_us": "Reward: Coffer of Achievement", @@ -17636,7 +17636,7 @@ }, "11965623175765427776": { "en_us": "Complete 365 #purplecard#Shared Quests#/purplecard#.", - "ru_ru": "Выполнить 365 #purplecard#Совместные квесты#/purplecard#." + "ru_ru": "Выполнить 365 #purplecard#Совместных квестов#/purplecard#." }, "11967160593014547932": { "en_us": "Living Laser", @@ -17836,7 +17836,7 @@ }, "12093016544277675440": { "en_us": "Heavy Metal Medallion", - "ru_ru": "Медальон Тяжелого металла" + "ru_ru": "Медальон Тяжёлого металла" }, "12094553961526795596": { "en_us": "Reward: Random Boost", @@ -17944,7 +17944,7 @@ }, "12178899629883809708": { "en_us": "Test of Brutality", - "ru_ru": "Испытание Жестокости" + "ru_ru": "Тест сокрушимости" }, "12180437047132929864": { "en_us": "Mister Fantastic and The Team", @@ -18508,7 +18508,7 @@ }, "12558667450047222400": { "en_us": "Test of Tactics", - "ru_ru": "Испытание тактики" + "ru_ru": "Тест на тактику" }, "12560204867296342556": { "en_us": "As Invisible Woman, group with Mister Fantastic, The Thing, and Human Torch to defeat #purplecard#Doctor Doom#/purplecard# in a #purplecard#Terminal#/purplecard#.", @@ -18994,7 +18994,7 @@ }, "12883963093744199492": { "en_us": "Frenemies", - "ru_ru": "Заклятые Друзья" + "ru_ru": "Заклятые друзья" }, "12885500510993319648": { "en_us": "Mystic Mystery, Volume 4", @@ -19409,7 +19409,7 @@ }, "13156323978223861140": { "en_us": "Complete all events in the #purplecard#Military Base Danger Room#/purplecard# scenario.", - "ru_ru": "Завершите все события в сценарии #purplecard#Комнаты опасности: Военная База#/purplecard#." + "ru_ru": "Завершите все события в сценарии #purplecard#Комнаты опасности: Военная база#/purplecard#." }, "13157861395472981296": { "en_us": "Defeat all #purplecard#Infernal Limbo#/purplecard# encounters with #purplecard#Magik#/purplecard# summoned as your Team-Up.", @@ -19421,7 +19421,7 @@ }, "13160936229971221608": { "en_us": "Heavy Metal Medallion", - "ru_ru": "Медальон Тяжелого металла" + "ru_ru": "Медальон Тяжёлого металла" }, "13162473649367825412": { "en_us": "Excalibur", @@ -20041,7 +20041,7 @@ }, "13585951720958865316": { "en_us": "Blue Cupcake", - "ru_ru": "Синий Кекс" + "ru_ru": "Синий кекс" }, "13587489138207985472": { "en_us": "Reward: Coffer of Achievement", @@ -20967,7 +20967,7 @@ }, "14199916773974944141": { "en_us": "Test of Blood", - "ru_ru": "Испытание Кровью" + "ru_ru": "Тест крови" }, "14201454191224064297": { "en_us": "Human Torch and The Team", @@ -21358,7 +21358,7 @@ }, "14466127987310641517": { "en_us": "Checkmate", - "ru_ru": "Шах и Мат" + "ru_ru": "Шах и мат" }, "14467665404559761673": { "en_us": "As Angela, defeat 500 #purplecard#elite enemies#/purplecard# with \"Lady Gamora\" summoned as a Team-Up and share all the sporting moments you two can find.", @@ -21538,7 +21538,7 @@ }, "14579684594138356833": { "en_us": "Test of Restraint", - "ru_ru": "Испытание сдержанности" + "ru_ru": "Тест на сдержанность" }, "14581222011387476989": { "en_us": "As Mister Fantastic, group with Invisible Woman, The Thing, and Human Torch to defeat #purplecard#Doctor Doom#/purplecard# in a #purplecard#Terminal#/purplecard#.", @@ -22094,7 +22094,7 @@ }, "14956377577656045565": { "en_us": "Upgraded Tech", - "ru_ru": "Улучшенная Техника" + "ru_ru": "Продвинутые технологии" }, "14957914997052649369": { "en_us": "Target Acquired: Elektra Imposter", @@ -22102,7 +22102,7 @@ }, "14959452414301769525": { "en_us": "Test of Skill", - "ru_ru": "Испытание навыков" + "ru_ru": "Тест навыков" }, "14960989831550889681": { "en_us": "Reward: Coffer of Achievement", @@ -22274,7 +22274,7 @@ }, "15071471603880364685": { "en_us": "Bad Lieutenant", - "ru_ru": "Плохой Лейтенант" + "ru_ru": "Плохой лейтенант" }, "15073009021129484841": { "en_us": "Starship Commander Quill", @@ -23400,7 +23400,7 @@ }, "15827932407561466109": { "en_us": "Military Base Elimination", - "ru_ru": "Ликвидация: Военная База" + "ru_ru": "Ликвидация: Военная база" }, "15829469824810586265": { "en_us": "Thing", @@ -23484,7 +23484,7 @@ }, "15882404584027901709": { "en_us": "Military Base Elimination", - "ru_ru": "Ликвидация: Военная База" + "ru_ru": "Ликвидация: Военная база" }, "15883942003424505513": { "en_us": "Reward: Coffer of Achievement", @@ -23628,7 +23628,7 @@ }, "15986736685213412441": { "en_us": "Red Cupcake", - "ru_ru": "Красный Кекс" + "ru_ru": "Красный кекс" }, "15988274102462532597": { "en_us": "Hightown Patrol", @@ -24524,7 +24524,7 @@ }, "16587468047888291493": { "en_us": "Simulation Spoils", - "ru_ru": "Добыча Симуляции" + "ru_ru": "Добыча симуляции" }, "16589005465137411649": { "en_us": "Cable", @@ -25184,7 +25184,7 @@ }, "17029395135058707861": { "en_us": "Welcome to Die!", - "ru_ru": "Добро пожаловать Умирать!" + "ru_ru": "Добро пожаловать умирать!" }, "17030932552307828017": { "en_us": "Expert Assassin", @@ -25396,7 +25396,7 @@ }, "17188199410563170545": { "en_us": "Software Upgrade", - "ru_ru": "Обновление Программного обеспечения" + "ru_ru": "Обновление ПО" }, "17191274247208894505": { "en_us": "As Silver Surfer, defeat 500 enemies in #purplecard#Doop Space Nine#/purplecard# using #purplecard#Movement powers#/purplecard# as the finishing blow.", @@ -26080,7 +26080,7 @@ }, "17669224495266370009": { "en_us": "Latest Version", - "ru_ru": "Последняя Версия" + "ru_ru": "Последняя версия" }, "17670761914662973813": { "en_us": "Goblin Gear", From c0ff50296ee7f91427c453ef9160d628c5959eef Mon Sep 17 00:00:00 2001 From: AlexBond Date: Sun, 15 Mar 2026 00:40:29 +0300 Subject: [PATCH 19/30] Fix Forge Region --- .../Off/AchievementInfoMap_03_Restore.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/MHServerEmu.Games/Data/Game/Achievements/Off/AchievementInfoMap_03_Restore.json b/src/MHServerEmu.Games/Data/Game/Achievements/Off/AchievementInfoMap_03_Restore.json index b8c0d3fa..6f7e4227 100644 --- a/src/MHServerEmu.Games/Data/Game/Achievements/Off/AchievementInfoMap_03_Restore.json +++ b/src/MHServerEmu.Games/Data/Game/Achievements/Off/AchievementInfoMap_03_Restore.json @@ -108,20 +108,10 @@ "Prototype": 3485315955694711100, "IncludeChildren": false }, - { - "ContextType": 4, - "Prototype": 2098118535044283872, - "IncludeChildren": false - }, { "ContextType": 4, "Prototype": -2896508327528813735, "IncludeChildren": false - }, - { - "ContextType": 4, - "Prototype": 6453444457900686765, - "IncludeChildren": false } ] } From 7b315562ce9790b5b5024f2cdf37bf81952fa686 Mon Sep 17 00:00:00 2001 From: AlexBond Date: Sun, 15 Mar 2026 01:02:57 +0300 Subject: [PATCH 20/30] Fix Maggia > Hammerhead --- .../AchievementStringMap_00_MainPatch.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/MHServerEmu.Games/Data/Game/Achievements/AchievementStringMap_00_MainPatch.json b/src/MHServerEmu.Games/Data/Game/Achievements/AchievementStringMap_00_MainPatch.json index be0daa23..578566d3 100644 --- a/src/MHServerEmu.Games/Data/Game/Achievements/AchievementStringMap_00_MainPatch.json +++ b/src/MHServerEmu.Games/Data/Game/Achievements/AchievementStringMap_00_MainPatch.json @@ -7,6 +7,22 @@ "en_us": "Hammerhead Hideout", "ru_ru": "Убежище Кувалды" }, + "10074471165488932392": { + "en_us": "As Punisher, defeat 1,000 #purplecard#Hammerhead#/purplecard# using his #purplecard#Flamethrower#/purplecard# as the finishing blow.", + "ru_ru": "Играя за Карателя, победите 1 000 членов #purplecard#Кувалды#/purplecard#, добивая их #purplecard#Огнемётом#/purplecard#." + }, + "10496411817683368492": { + "en_us": "Hammerhead", + "ru_ru": "Кувалда" + }, + "17455948041147988077": { + "en_us": "Hammerhead Takedown", + "ru_ru": "Устранение Кувалды" + }, + "2301530194426900750": { + "en_us": "Hammerhead Gang Takeover", + "ru_ru": "Захват банды Кувалды" + }, "17474397056727364541": { "en_us": "Defeat #purplecard#Surtur#/purplecard# 100 times in the #purplecard#Muspelheim Raid#/purplecard#.", "ru_ru": "Победите 100 раз #purplecard#Суртура#/purplecard# в ходе #purplecard#рейда в Муспельхейм#/purplecard#." From 54a406501af445b3a6dd15ed6995b707785d47f0 Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sun, 15 Mar 2026 04:32:55 +0300 Subject: [PATCH 21/30] Add EnableTownPlayerLimit config setting --- src/MHServerEmu.PlayerManagement/PlayerManagerConfig.cs | 1 + src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs | 2 +- src/MHServerEmu/Config.ini | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/MHServerEmu.PlayerManagement/PlayerManagerConfig.cs b/src/MHServerEmu.PlayerManagement/PlayerManagerConfig.cs index a10985f9..edb2aa5a 100644 --- a/src/MHServerEmu.PlayerManagement/PlayerManagerConfig.cs +++ b/src/MHServerEmu.PlayerManagement/PlayerManagerConfig.cs @@ -15,5 +15,6 @@ namespace MHServerEmu.PlayerManagement public string NewsUrl { get; private set; } = "http://localhost/"; public int ServerCapacity { get; private set; } = 0; public int MaxLoginQueueClients { get; private set; } = 10000; + public bool EnableTownPlayerLimit { get; private set; } = false; } } diff --git a/src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs b/src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs index 2cb0d25c..05da57b1 100644 --- a/src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs +++ b/src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs @@ -74,7 +74,7 @@ namespace MHServerEmu.PlayerManagement.Regions public int PlayerCount { get => _players.Count; } public int PlayerLimit { get => Prototype.PlayerLimit; } - public bool IsFull { get => IsTown == false && PlayerCount >= PlayerLimit; } + public bool IsFull { get => PlayerCount >= PlayerLimit && (IsTown == false || PlayerManagerService.Instance.Config.EnableTownPlayerLimit); } public RegionHandle(GameHandle game, ulong id, PrototypeId regionProtoRef, NetStructCreateRegionParams createParams, RegionFlags flags) { diff --git a/src/MHServerEmu/Config.ini b/src/MHServerEmu/Config.ini index 2eccce49..14c07402 100644 --- a/src/MHServerEmu/Config.ini +++ b/src/MHServerEmu/Config.ini @@ -78,6 +78,7 @@ DashboardUrlPath=/Dashboard/ ; ServerCapacity: Indicates the maximum concurrent number of players allowed on the server. Players who exceed this capacity will be put in a queue. ; Values <= 0 indicate unlimited capacity. ; MaxLoginQueueClients: The maximum number of normal priority clients that can be put into the login queue at the same time. +; EnableTownPlayerLimit: When enabled, town regions respect player limits defined in region prototypes. EnablePersistence=true UseJsonDBManager=false @@ -87,6 +88,7 @@ ShowNewsOnLogin=false NewsUrl=http://localhost/news/index.html ServerCapacity=0 MaxLoginQueueClients=10000 +EnableTownPlayerLimit=false [SQLiteDBManager] ; FileName: Name of the SQLite database file to use relative to the server's Data directory. From 189791120199fc96b7765e757a5948f48ac3e60a Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Sun, 15 Mar 2026 05:06:14 +0300 Subject: [PATCH 22/30] Implement eRTV_PlayerLimit tuning variable --- .../GameData/Prototypes/RegionPrototype.cs | 8 +++++++- src/MHServerEmu.Games/Populations/SpawnMap.cs | 2 +- src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/MHServerEmu.Games/GameData/Prototypes/RegionPrototype.cs b/src/MHServerEmu.Games/GameData/Prototypes/RegionPrototype.cs index 354b1847..24f8ea65 100644 --- a/src/MHServerEmu.Games/GameData/Prototypes/RegionPrototype.cs +++ b/src/MHServerEmu.Games/GameData/Prototypes/RegionPrototype.cs @@ -1,4 +1,5 @@ -using MHServerEmu.Core.Extensions; +using Gazillion; +using MHServerEmu.Core.Extensions; using MHServerEmu.Core.Logging; using MHServerEmu.Core.Memory; using MHServerEmu.Games.Entities; @@ -462,6 +463,11 @@ namespace MHServerEmu.Games.GameData.Prototypes return false; } + public int GetPlayerLimit() + { + return (int)(PlayerLimit * LiveTuningManager.GetLiveRegionTuningVar(this, RegionTuningVar.eRTV_PlayerLimit)); + } + private int GetLargestTeamSize() { int largestTeamSize = 0; diff --git a/src/MHServerEmu.Games/Populations/SpawnMap.cs b/src/MHServerEmu.Games/Populations/SpawnMap.cs index c46b0d1b..2e63e5e8 100644 --- a/src/MHServerEmu.Games/Populations/SpawnMap.cs +++ b/src/MHServerEmu.Games/Populations/SpawnMap.cs @@ -298,7 +298,7 @@ namespace MHServerEmu.Games.Populations float dencity; int playerCount = Area.PopulationArea.PlayerCount; - int playerLimit = Area.Region.Prototype.PlayerLimit; + int playerLimit = Area.Region.Prototype.GetPlayerLimit(); if (DensityMin == DensityMax) { diff --git a/src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs b/src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs index 05da57b1..38cd45b6 100644 --- a/src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs +++ b/src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs @@ -73,7 +73,7 @@ namespace MHServerEmu.PlayerManagement.Regions public RegionPlayerAccessVar PlayerAccess { get; private set; } = RegionPlayerAccessVar.eRPA_Invalid; public int PlayerCount { get => _players.Count; } - public int PlayerLimit { get => Prototype.PlayerLimit; } + public int PlayerLimit { get => Prototype.GetPlayerLimit(); } public bool IsFull { get => PlayerCount >= PlayerLimit && (IsTown == false || PlayerManagerService.Instance.Config.EnableTownPlayerLimit); } public RegionHandle(GameHandle game, ulong id, PrototypeId regionProtoRef, NetStructCreateRegionParams createParams, RegionFlags flags) From cb747a5ae9a9f55ae26827b9cc5304854233de2a Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Mon, 16 Mar 2026 03:09:10 +0300 Subject: [PATCH 23/30] Allow store purchases in the tutorial when auto unlocks are disabled --- src/MHServerEmu.Games/MTXStore/CatalogManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/MHServerEmu.Games/MTXStore/CatalogManager.cs b/src/MHServerEmu.Games/MTXStore/CatalogManager.cs index 29fc2c45..f9f85bed 100644 --- a/src/MHServerEmu.Games/MTXStore/CatalogManager.cs +++ b/src/MHServerEmu.Games/MTXStore/CatalogManager.cs @@ -207,8 +207,10 @@ namespace MHServerEmu.Games.MTXStore private BuyItemResultErrorCodes BuyItem(Player buyer, Player recipient, long skuId, long clientPrice) { - // Make sure the player has already finished the tutorial, which could unlock characters depending on server settings. - if (buyer.HasFinishedTutorial() == false) + var config = buyer.Game.CustomGameOptions; + + // Do not allow purchases during the tutorial if we are going to unlock avatars or team-ups after the player finishes it. + if ((config.AutoUnlockAvatars || config.AutoUnlockTeamUps) && buyer.HasFinishedTutorial() == false) return BuyItemResultErrorCodes.BUY_RESULT_ERROR_UNKNOWN; // Validate the order From dac7d90b213b8fd87cdb17236b2aa8b75d7bd5ba Mon Sep 17 00:00:00 2001 From: AlexBond Date: Tue, 17 Mar 2026 20:20:44 +0300 Subject: [PATCH 24/30] New UI for Add G --- .../Data/Web/MTXStore/add-g.html | 212 +++++++++++++++--- 1 file changed, 180 insertions(+), 32 deletions(-) diff --git a/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html b/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html index 76113824..c0be4f28 100644 --- a/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html +++ b/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html @@ -15,8 +15,9 @@ font: 18px/1.385 Verdana; } - *:focus { - outline-color: #00e8ff; + *:focus, *:active { + outline: none; + box-shadow: none; } h1, h2, h3, h4, h5, h6 { @@ -26,43 +27,80 @@ } button, input { - width: 100%; box-sizing: border-box; border: 1px solid black; - box-shadow: 0 0 1px 1px #00aaff; background: #1c1c1c; color: inherit; font: inherit; } - button:not(.sign-button) { - padding: 8px; - } - - button:hover { - box-shadow: 0 0 1px 1px white; - } - input { width: 100%; text-align: center; } input[type="range"] { + -webkit-appearance: none; + appearance: none; margin: 4px 0px; + background: transparent; + vertical-align: middle; } - input::-webkit-slider-thumb { + input[type="text"] { + box-sizing: border-box; + height: 30px; + border: 1px solid #505350; + border-radius: 4px; + background: linear-gradient( + #010501 0%, + #120F0E 10%, + #252525 100% + ); + height: 30px; + width: 70px; + color: white; + font-family: "Bebas Neue", "Trebuchet MS", Verdana, sans-serif; + font-size: 24px; + line-height: 24px; + vertical-align: middle; + } + + input[type="range"]::-webkit-slider-runnable-track { + height: 8px; + background: black; + background-image: url('data:image/svg+xml;utf8,'); + background-repeat: repeat; + border-radius: 4px; + box-shadow: inset 0 1px 1px rgba(0,0,0,0.3); + } + + input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 26px; - height: 26px; - background: #00aaff; + height: 16px; + margin-top: -4px; + border: 1px solid #41DAE5; + border-radius: 4px; + background: linear-gradient( + -35deg, + #0082C3 45%, + #23A4E5 55%, + #1196D6 70%, + #0082C3 100% + ); + box-shadow: + inset 0 0 8px rgba(255,255,255,0.6), + 0 0 8px rgba(65, 218, 229, 0.8); } table { width: 100%; margin: 12px 0px; - border-spacing: 4px; + border-spacing: 16px; + border: 1px solid #383838; + border-radius: 6px; + box-shadow: inset 0 0 8px #383838; } div.content { @@ -94,6 +132,76 @@ justify-content: center; align-items: center; } + + button { + padding: 8px 40px 6px 40px; + font: normal 23px / 20px "Bebas Neue", "Trebuchet MS", Verdana, sans-serif; + color: white; + border: 2px solid #1ac04c; + cursor: pointer; + border-radius: 5px; + text-shadow: 1px 1px 2px black; + text-transform: uppercase; + background: linear-gradient(337deg, #087A10 49%, #299F2C 51%, #0D7D16 70%, #087A10 100%); + box-shadow: inset 0 0 8px #39cb42, 0 0 4px #39cb42; + } + + button:hover { + box-shadow: inset 0 0 8px #03ff0d, 0 0 4px #08cf10; + background: linear-gradient(337deg, #009E08 49%, #30DF39 51%, #24992b 70%, #009E08 100%); + } + button.sign-button, button.close-button{ + border: 1px solid #4CF4F6; + background: linear-gradient(337deg, #0082C3 49%, #29A6E7 51%, #007CB8 70%, #0082C3 100%); + box-shadow: inset 0 0 8px #32B5F5, 0 0 4px #32B5F5; + } + button.sign-button{ + font-size: 32px; + line-height: 16px; + vertical-align: middle; + width: 32px; + padding: 8px 8px 4px 8px; + background: linear-gradient(337deg, #0082C3 49%, #29A6E7 51%, #007CB8 70%, #0082C3 100%); + box-shadow: inset 0 0 8px #32B5F5; + } + button.sign-button:hover{ + box-shadow: inset 0 0 8px #00f0ff; + background: linear-gradient(337deg, #299ED9 49%, #4DC5FF 51%, #2f98cd 70%, #299ED9 100%); + } + button.close-button:hover { + box-shadow: inset 0 0 8px #00f0ff, 0 0 4px #26BFFA; + background: linear-gradient(337deg, #299ED9 49%, #4DC5FF 51%, #2f98cd 70%, #299ED9 100%); + } + .center { + text-align: center; + width: 100%; + } + .g { + color: #00090e; + font: bold 18px / 1 Arial; + text-shadow: 1px 2px 1px #00B2FE; + display: inline-flex; + vertical-align: middle; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + background: linear-gradient( + -35deg, + #007FFF 45%, + #03C2F7 54%, + #00B2FE 70%, + #007FFF 100% + ); + border: 1px solid #00B2FE; + border-radius: 50%; + margin-left: 8px; + } + .es { + display: inline-block; + vertical-align: middle; + width: 48px; + } @@ -102,39 +210,77 @@

Select the amount of Eternity Splinters to convert to G.

- - - - - - - - - + + - - + +
ESG
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
G

Please note that the amount of G after conversion may be subject to rounding errors.

- +
+ +
From 08cccf392a7af3c90406c76bd26a325e769cc4e5 Mon Sep 17 00:00:00 2001 From: AlexBond Date: Tue, 17 Mar 2026 20:56:12 +0300 Subject: [PATCH 25/30] New UI for Bundles --- .../Scripts/bundle_asset_builder/style.css | 85 +++++++++++++++++-- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/src/Tools/Scripts/bundle_asset_builder/style.css b/src/Tools/Scripts/bundle_asset_builder/style.css index 60db6a3b..a8036e7e 100644 --- a/src/Tools/Scripts/bundle_asset_builder/style.css +++ b/src/Tools/Scripts/bundle_asset_builder/style.css @@ -32,16 +32,53 @@ button:hover { } div.content { - margin: 10px auto; padding: 20px; border: 1px solid #00717c; background-color: #00090e; + position: absolute; + top: 10px; + right: 10px; + bottom: 80px; + left: 10px; + overflow-y: auto; } div.buttons { display: flex; + padding: 10px 20px; justify-content: space-between; align-items: center; + top: auto; + bottom: 10px; +} + +.content::-webkit-scrollbar { + width: 8px; +} +.content::-webkit-scrollbar-track { + background: #none; +} +.content::-webkit-scrollbar-thumb { + background: #13AAF4; + border-radius: 10px; + border: 1px solid #46DDF0; +} + +div.buttons button { + padding: 8px 40px 6px 40px; + font: normal 23px / 20px "Bebas Neue", "Trebuchet MS", Verdana, sans-serif; + color: white; + border: 1px solid #65E872; + border-radius: 4px; + text-shadow: 1px 1px 2px black; + text-transform: uppercase; + background: linear-gradient(337deg, #087A10 49%, #299F2C 51%, #0D7D16 70%, #087A10 100%); + box-shadow: inset 0 0 8px #39cb42, 0 0 4px #39cb42; +} + +div.buttons button:hover { + box-shadow: inset 0 0 8px #03ff0d, 0 0 4px #08cf10; + background: linear-gradient(337deg, #009E08 49%, #30DF39 51%, #24992b 70%, #009E08 100%); } span.enhanced-keyword { @@ -49,8 +86,46 @@ span.enhanced-keyword { } span.price { - color: #00aaff; - font-family: "Bebas Neue", "Trebuchet MS", Verdana, sans-serif; - font-weight: normal; - font-size: 32px; + color: #ffffff; + font: normal 32px / 32px "Bebas Neue", "Trebuchet MS", Verdana, sans-serif; + padding-right: 25px; + position: relative; + display: inline-block; + padding-top: 5px; +} +.price::before { + content: ''; + position: absolute; + right: 0; + top: 0; + width: 42px; + height: 100%; + background: #00090e; + z-index: 1; +} + +.price::after { + content: 'G'; + color: #00090e; + font: bold 18px / 1 Arial; + text-shadow: 1px 2px 1px #00B2FE; + position: absolute; + right: 6px; + top: 6px; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + background: linear-gradient( + -35deg, + #007FFF 45%, + #03C2F7 54%, + #00B2FE 70%, + #007FFF 100% + ); + border: 1px solid #00B2FE; + border-radius: 50%; + + z-index: 2; } From b79f02e541213e77c44ba6629613cd531dcc1603 Mon Sep 17 00:00:00 2001 From: AlexBond Date: Tue, 17 Mar 2026 22:56:27 +0300 Subject: [PATCH 26/30] Packed SVG --- .../Data/Web/MTXStore/add-g.html | 44 +++---------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html b/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html index c0be4f28..0680a48e 100644 --- a/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html +++ b/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html @@ -211,45 +211,11 @@ +
+ +
+ + From 1e4ada5896015d16a253300f7fe760513ff214c3 Mon Sep 17 00:00:00 2001 From: AlexBond Date: Tue, 17 Mar 2026 23:54:10 +0300 Subject: [PATCH 27/30] Small fix --- src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html b/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html index 0680a48e..a2ecc142 100644 --- a/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html +++ b/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html @@ -17,7 +17,6 @@ *:focus, *:active { outline: none; - box-shadow: none; } h1, h2, h3, h4, h5, h6 { @@ -43,6 +42,7 @@ -webkit-appearance: none; appearance: none; margin: 4px 0px; + margin-top: 1px; background: transparent; vertical-align: middle; } From 6750e6b62a8cd9c12d0b8773fc62d80a033300eb Mon Sep 17 00:00:00 2001 From: AlexBond Date: Wed, 18 Mar 2026 02:00:52 +0300 Subject: [PATCH 28/30] G element --- .../Scripts/bundle_asset_builder/info.html | 2 +- .../Scripts/bundle_asset_builder/style.css | 31 +++---------------- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/Tools/Scripts/bundle_asset_builder/info.html b/src/Tools/Scripts/bundle_asset_builder/info.html index 56a54e1b..a0c6fc26 100644 --- a/src/Tools/Scripts/bundle_asset_builder/info.html +++ b/src/Tools/Scripts/bundle_asset_builder/info.html @@ -18,7 +18,7 @@
- {PRICE} G + {PRICE} G
diff --git a/src/Tools/Scripts/bundle_asset_builder/style.css b/src/Tools/Scripts/bundle_asset_builder/style.css index a8036e7e..34f52b85 100644 --- a/src/Tools/Scripts/bundle_asset_builder/style.css +++ b/src/Tools/Scripts/bundle_asset_builder/style.css @@ -93,39 +93,18 @@ span.price { display: inline-block; padding-top: 5px; } -.price::before { - content: ''; - position: absolute; - right: 0; - top: 0; - width: 42px; - height: 100%; - background: #00090e; - z-index: 1; -} - -.price::after { - content: 'G'; +.g { color: #00090e; font: bold 18px / 1 Arial; text-shadow: 1px 2px 1px #00B2FE; - position: absolute; - right: 6px; - top: 6px; - display: flex; + display: inline-flex; + vertical-align: middle; align-items: center; justify-content: center; width: 24px; height: 24px; - background: linear-gradient( - -35deg, - #007FFF 45%, - #03C2F7 54%, - #00B2FE 70%, - #007FFF 100% - ); + background: linear-gradient(-35deg, #007FFF 45%, #03C2F7 54%, #00B2FE 70%, #007FFF 100%); border: 1px solid #00B2FE; border-radius: 50%; - - z-index: 2; + margin-left: 6px; } From 1066c0ee58c29b2d5cd30424204846c205fdc5c4 Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Wed, 18 Mar 2026 16:56:55 +0300 Subject: [PATCH 29/30] Update README --- README.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 18ab47cf..673993d0 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,13 @@ You can always upgrade from stable to nightly simply by downloading the latest n [![Nightly Release (Windows x64)](https://github.com/Crypto137/MHServerEmu/actions/workflows/nightly-release-windows-x64.yml/badge.svg)](https://nightly.link/Crypto137/MHServerEmu/workflows/nightly-release-windows-x64/master?preview) [![Nightly Release (Linux x64)](https://github.com/Crypto137/MHServerEmu/actions/workflows/nightly-release-linux-x64.yml/badge.svg)](https://nightly.link/Crypto137/MHServerEmu/workflows/nightly-release-linux-x64/master?preview) -## Features - -MHServerEmu is currently feature-complete, but still in the polishing stage. All the major gameplay and social systems that existed in 2017 have been restored, and we are currently focusing on optimization and bug fixes. **Currently we aim to release version 1.0 in the first quarter of 2026.** - -You can find up to date information on what we are working on in [our roadmap](https://github.com/users/Crypto137/projects/5). - ## FAQ -**Where can I download the game?** +**Is the game fully playable?** + +All systems and content that were in the game when it was shut down in 2017 have been restored. + +**Where can I download the game client?** We do not provide download links for the game client for legal reasons. If you have played the game through Steam when it was live, you should be able to download it in your Steam library. @@ -46,13 +44,11 @@ We do not provide download links for the game client for legal reasons. If you h Download the latest stable or nightly build and overwrite your existing files. Nightly builds can be potentially unstable, so it is recommended to back up your account database file located in `MHServerEmu\Data\Account.db` before updating. -**Will there be any wipes?** - -We plan to force a fresh start when version 1.0 comes out in early 2026. Your data will not be deleted, but it will no longer be compatible with the server. You will be able to continue using your existing data on whatever the last 0.x version is going to be. - **Are you going to support other versions of the game, like the ones from before the Biggest Update Ever (BUE) came out?** -Yes, we do plan to implement support for other versions of the game after 1.52 is fully restored. The final pre-BUE version (1.48) has the highest priority. +Yes, we do plan to implement support for other versions, including the final pre-BUE version (1.48) from late 2016. Currently there are no timeframes for when this is going to happen. The current work-in-progress 1.48 code is available on the [v48](https://github.com/Crypto137/MHServerEmu/tree/v48) branch. + +Some early work has also been done to support version 1.10 from mid 2013. You can find the code for it in the [MHServerEmu2013](https://github.com/Crypto137/MHServerEmu2013) repository. **Are you going to add new content to the game (heroes, team-ups, powers, etc.)?** From 58f9fa217cd6d1e00baf9643c8d70ed91b5bbd6b Mon Sep 17 00:00:00 2001 From: Crypto137 Date: Wed, 18 Mar 2026 18:31:51 +0300 Subject: [PATCH 30/30] Fix autosaves triggering during region transfers --- src/MHServerEmu.Games/Entities/Player.cs | 6 ++++++ src/MHServerEmu.Games/Network/PlayerConnection.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/MHServerEmu.Games/Entities/Player.cs b/src/MHServerEmu.Games/Entities/Player.cs index de1818ba..cc6d3341 100644 --- a/src/MHServerEmu.Games/Entities/Player.cs +++ b/src/MHServerEmu.Games/Entities/Player.cs @@ -526,6 +526,9 @@ namespace MHServerEmu.Games.Entities public override void ExitGame() { + if (_autosaveEvent.IsValid) + Game.GameEventScheduler.CancelEvent(_autosaveEvent); + CancelPlayerTrade(); SendMessage(NetMessageBeginExitGame.DefaultInstance); @@ -4876,6 +4879,9 @@ namespace MHServerEmu.Games.Entities if (PlayerConnection == null) return; + if (IsInGame == false) + return; + Logger.Info($"Autosaving {this}..."); PlayerConnection.SaveWithNotification(); ScheduleAutosave(); diff --git a/src/MHServerEmu.Games/Network/PlayerConnection.cs b/src/MHServerEmu.Games/Network/PlayerConnection.cs index 0b94eb75..35a5b9e1 100644 --- a/src/MHServerEmu.Games/Network/PlayerConnection.cs +++ b/src/MHServerEmu.Games/Network/PlayerConnection.cs @@ -120,6 +120,9 @@ namespace MHServerEmu.Games.Network /// public bool SaveWithNotification() { + if (HasPendingRegionTransfer) + return false; + if (SaveToDBAccount(false) == false) { Logger.Error($"SaveWithNotification(): Save failed for {this}");
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-