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
[](https://nightly.link/Crypto137/MHServerEmu/workflows/nightly-release-windows-x64/master?preview) [](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.)?**
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:
diff --git a/docs/ServerEmu/ServerCommands.md b/docs/ServerEmu/ServerCommands.md
index 2e800faf..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.01.21 10:37:50 UTC` using server version `0.8.1`.
+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. | Any | 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.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.Core/Serialization/Archive.cs b/src/MHServerEmu.Core/Serialization/Archive.cs
index 4265a572..2048e917 100644
--- a/src/MHServerEmu.Core/Serialization/Archive.cs
+++ b/src/MHServerEmu.Core/Serialization/Archive.cs
@@ -19,23 +19,30 @@ 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
+ }
+
+ 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
}
///
@@ -53,7 +60,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;
@@ -114,12 +121,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;
@@ -139,8 +146,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;
@@ -172,7 +179,7 @@ namespace MHServerEmu.Core.Serialization
public void Dispose()
{
- _bufferStream.SetLength(0);
+ _buffer.SetLength(0);
}
///
@@ -183,7 +190,7 @@ namespace MHServerEmu.Core.Serialization
///
public MemoryStream AccessAutoBuffer()
{
- return _bufferStream;
+ return _buffer;
}
///
@@ -192,12 +199,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);
}
@@ -210,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)
{
@@ -231,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)
{
@@ -280,7 +308,7 @@ namespace MHServerEmu.Core.Serialization
}
else
{
- _bufferStream.WriteByteAt(_lastBitEncodedOffset, bitBuffer);
+ _buffer.WriteByteAt(_lastBitEncodedOffset, bitBuffer);
if (numEncodedBits >= 5)
_lastBitEncodedOffset = 0;
}
@@ -633,7 +661,8 @@ namespace MHServerEmu.Core.Serialization
if (IsPacking)
{
- return WriteVarint(ioData);
+ WriteVarint(ioData);
+ UpdateSizeInArchive();
}
else
{
@@ -660,7 +689,8 @@ namespace MHServerEmu.Core.Serialization
if (IsPacking)
{
- return WriteVarint(ioData);
+ WriteVarint(ioData);
+ UpdateSizeInArchive();
}
else
{
@@ -702,7 +732,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 +758,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)
@@ -756,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
@@ -767,6 +831,9 @@ namespace MHServerEmu.Core.Serialization
{
_cos.WriteRawByte(value);
_cos.Flush();
+
+ UpdateSizeInArchive();
+
return true;
}
@@ -797,6 +864,9 @@ namespace MHServerEmu.Core.Serialization
_cos.WriteRawByte(@byte);
_cos.Flush();
+
+ UpdateSizeInArchive();
+
return true;
}
@@ -833,7 +903,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);
}
///
@@ -948,7 +1018,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;
diff --git a/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj b/src/MHServerEmu.DatabaseAccess/MHServerEmu.DatabaseAccess.csproj
index 64b526a9..ed0efa0b 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)
@@ -21,6 +21,7 @@
+
@@ -31,6 +32,7 @@
+
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,
}
///
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
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/CustomGameOptionsConfig.cs b/src/MHServerEmu.Games/CustomGameOptionsConfig.cs
index d1f73812..bbd9b14f 100644
--- a/src/MHServerEmu.Games/CustomGameOptionsConfig.cs
+++ b/src/MHServerEmu.Games/CustomGameOptionsConfig.cs
@@ -7,15 +7,18 @@ 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;
+ 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;
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/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",
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#."
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
}
]
}
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
diff --git a/src/MHServerEmu.Games/Entities/Avatars/Avatar.cs b/src/MHServerEmu.Games/Entities/Avatars/Avatar.cs
index ff7365bb..9cdb157e 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;
@@ -4717,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.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..cc6d3341 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;
@@ -532,6 +526,9 @@ namespace MHServerEmu.Games.Entities
public override void ExitGame()
{
+ if (_autosaveEvent.IsValid)
+ Game.GameEventScheduler.CancelEvent(_autosaveEvent);
+
CancelPlayerTrade();
SendMessage(NetMessageBeginExitGame.DefaultInstance);
@@ -4882,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/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();
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/MHServerEmu.Games.csproj b/src/MHServerEmu.Games/MHServerEmu.Games.csproj
index eb08885e..83410c0d 100644
--- a/src/MHServerEmu.Games/MHServerEmu.Games.csproj
+++ b/src/MHServerEmu.Games/MHServerEmu.Games.csproj
@@ -9,7 +9,7 @@
- 0.8.1
+ 1.0.0build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))$(InformationalVersion).0$(AssemblyVersion)
@@ -67,6 +67,9 @@
PreserveNewest
+
+ PreserveNewest
+ PreserveNewest
@@ -103,7 +106,7 @@
PreserveNewest
-
+ PreserveNewest
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
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}");
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.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
{
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);
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.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)
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..38cd45b6 100644
--- a/src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs
+++ b/src/MHServerEmu.PlayerManagement/Regions/RegionHandle.cs
@@ -73,8 +73,8 @@ 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 bool IsFull { get => IsTown == false && PlayerCount >= 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)
{
diff --git a/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html b/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html
index 76113824..a2ecc142 100644
--- a/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html
+++ b/src/MHServerEmu.WebFrontend/Data/Web/MTXStore/add-g.html
@@ -15,8 +15,8 @@
font: 18px/1.385 Verdana;
}
- *:focus {
- outline-color: #00e8ff;
+ *:focus, *:active {
+ outline: none;
}
h1, h2, h3, h4, h5, h6 {
@@ -26,43 +26,81 @@
}
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;
+ margin-top: 1px;
+ 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,43 @@
Select the amount of Eternity Splinters to convert to G.
-
ES
-
-
-
-
G
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
G
Please note that the amount of G after conversion may be subject to rounding errors.
-
+
+
+
You do not have enough Eternity Splinters to convert to G.
-
+
+
+
Please specify a valid amount of Eternity Splinters.
-
+
+
+
-
+
@@ -142,7 +254,9 @@
-
+
+
+
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",
};
}
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/Commands/Implementations/ItemCommands.cs b/src/MHServerEmu/Commands/Implementations/ItemCommands.cs
index 1163efee..13943267 100644
--- a/src/MHServerEmu/Commands/Implementations/ItemCommands.cs
+++ b/src/MHServerEmu/Commands/Implementations/ItemCommands.cs
@@ -147,29 +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")]
[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/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.";
}
}
diff --git a/src/MHServerEmu/Config.ini b/src/MHServerEmu/Config.ini
index 28466d08..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.
@@ -123,7 +125,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
@@ -197,11 +199,14 @@ 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.
; 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.
@@ -209,15 +214,18 @@ OrbisTrophiesEnabled=true
AutosaveIntervalMinutes=15
ESCooldownOverrideMinutes=-1.0
CombineESStacks=false
-AutoUnlockAvatars=true
-AutoUnlockTeamUps=true
+AutoUnlockAvatars=false
+AutoUnlockTeamUps=false
DisableMovementPowerChargeCost=false
AllowSameGroupTalents=false
+EnableCreditChestConversion=false
+CreditChestConversionMultiplier=2.0
DisableInstancedLoot=false
LootSpawnGridCellRadius=20.0
TrashedItemExpirationTimeMultiplier=1.0
DisableAccountBinding=false
DisableCharacterBinding=false
+DisableMissionXPBonuses=false
UsePrestigeLootTable=false
EnableUltimatePrestige=false
ApplyHiddenPvPDamageModifiers=false
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.0build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))$(InformationalVersion).0$(AssemblyVersion)
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 @@