Merge pull request #193 from Crypto137/dev

1.0.0 changes
This commit is contained in:
Crypto137 2026-03-20 10:00:10 +03:00 committed by GitHub
commit b19e72b447
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 680 additions and 391 deletions

View file

@ -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.)?**

View file

@ -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:

View file

@ -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 |

View file

@ -8,7 +8,7 @@
</PropertyGroup>
<PropertyGroup>
<AssemblyVersion>0.8.1.0</AssemblyVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<InformationalVersion>$(AssemblyVersion)</InformationalVersion>
</PropertyGroup>

View file

@ -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
}
/// <summary>
@ -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);
}
/// <summary>
@ -183,7 +190,7 @@ namespace MHServerEmu.Core.Serialization
/// </remarks>
public MemoryStream AccessAutoBuffer()
{
return _bufferStream;
return _buffer;
}
/// <summary>
@ -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<byte> 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
/// </summary>
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
/// </summary>
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<uint> sizeToken = GetSizeTokenAtOffset(0);
if (sizeToken.Length == 0)
{
SetError("Error accessing size in the buffer!");
return false;
}
sizeToken[0] = (uint)CurrentOffset;
return true;
}
private Span<uint> GetSizeTokenAtOffset(uint offset)
{
if (CurrentOffset < offset + sizeof(uint))
{
SetError("Failed writing size in use!");
return default;
}
Span<byte> sizeToken = new(_buffer.GetBuffer(), (int)offset, sizeof(uint));
return MemoryMarshal.Cast<byte, uint>(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);
}
/// <summary>
@ -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;

View file

@ -8,7 +8,7 @@
</PropertyGroup>
<PropertyGroup>
<AssemblyVersion>0.8.1.0</AssemblyVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<InformationalVersion>$(AssemblyVersion)</InformationalVersion>
</PropertyGroup>
@ -21,6 +21,7 @@
<None Remove="SQLite\Scripts\Migrations\2.sql" />
<None Remove="SQLite\Scripts\Migrations\3.sql" />
<None Remove="SQLite\Scripts\Migrations\4.sql" />
<None Remove="SQLite\Scripts\Migrations\5.sql" />
</ItemGroup>
<ItemGroup>
@ -31,6 +32,7 @@
<EmbeddedResource Include="SQLite\Scripts\Migrations\2.sql" />
<EmbeddedResource Include="SQLite\Scripts\Migrations\3.sql" />
<EmbeddedResource Include="SQLite\Scripts\Migrations\4.sql" />
<EmbeddedResource Include="SQLite\Scripts\Migrations\5.sql" />
</ItemGroup>
<ItemGroup>

View file

@ -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,
}
/// <summary>

View file

@ -13,7 +13,8 @@ namespace MHServerEmu.DatabaseAccess.SQLite
/// </summary>
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)

View file

@ -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" (

View file

@ -0,0 +1 @@
-- Empty version, this is used only to prevent 0.x database files from being loaded in 1.x+.

View file

@ -8,7 +8,7 @@
</PropertyGroup>
<PropertyGroup>
<AssemblyVersion>0.8.1.0</AssemblyVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<InformationalVersion>$(AssemblyVersion)</InformationalVersion>
</PropertyGroup>

View file

@ -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;

View file

@ -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",

View file

@ -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#."

View file

@ -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
}
]
}

View file

@ -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",

View file

@ -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
}
]

View file

@ -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");

View file

@ -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);

View file

@ -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
}
}

View file

@ -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();

View file

@ -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();

View file

@ -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;

View file

@ -9,7 +9,7 @@
</PropertyGroup>
<PropertyGroup>
<InformationalVersion>0.8.1</InformationalVersion>
<InformationalVersion>1.0.0</InformationalVersion>
<SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))</SourceRevisionId>
<AssemblyVersion>$(InformationalVersion).0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
@ -67,6 +67,9 @@
<None Update="Data\Game\Achievements\Off\AchievementStringMap_05_Collection.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\Game\LiveTuning\LiveTuningData.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\Game\LiveTuning\LiveTuningDataPvP.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@ -103,7 +106,7 @@
<None Update="Data\Game\Patches\PatchDataBugFixes.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\Game\LiveTuning\LiveTuningData.json">
<None Update="Data\Game\LiveTuning\LiveTuningDataGlobal.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Data\Game\LiveTuning\LiveTuningDataBugFixes.json">

View file

@ -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

View file

@ -120,6 +120,9 @@ namespace MHServerEmu.Games.Network
/// </summary>
public bool SaveWithNotification()
{
if (HasPendingRegionTransfer)
return false;
if (SaveToDBAccount(false) == false)
{
Logger.Error($"SaveWithNotification(): Save failed for {this}");

View file

@ -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)
{

View file

@ -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
{

View file

@ -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);

View file

@ -8,7 +8,7 @@
</PropertyGroup>
<PropertyGroup>
<AssemblyVersion>0.8.1.0</AssemblyVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<InformationalVersion>$(AssemblyVersion)</InformationalVersion>
</PropertyGroup>

View file

@ -8,7 +8,7 @@
</PropertyGroup>
<PropertyGroup>
<AssemblyVersion>0.8.1.0</AssemblyVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<InformationalVersion>$(AssemblyVersion)</InformationalVersion>
</PropertyGroup>

View file

@ -8,7 +8,7 @@
</PropertyGroup>
<PropertyGroup>
<AssemblyVersion>0.8.1.0</AssemblyVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<InformationalVersion>$(AssemblyVersion)</InformationalVersion>
</PropertyGroup>

View file

@ -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)

View file

@ -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;
}
}

View file

@ -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)
{

View file

@ -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,<svg width="52" height="20" viewBox="0 0 52 20" xmlns="http://www.w3.org/2000/svg"><rect x="10" width="50" height="20" fill="black"/><rect x="15" width="2" height="20" fill="%233C3C3D"/></svg>');
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;
}
</style>
</head>
<body>
@ -102,39 +210,43 @@
<p>Select the amount of Eternity Splinters to convert to G.</p>
<table>
<tr>
<th style="width: 70px;">ES</th>
<th style="width: 28px;"></th>
<th></th>
<th style="width: 28px;"></th>
<th style="width: 70px;">G</th>
</tr>
<tr>
<td><input type="text" value="0" id="es-text-input" onchange="setES(this.value)" /></td>
<td><button onclick="adjustES(-requestContext.conversionStep)" class="sign-button">-</button></td>
<td style="width:128px; text-align: right;">
<div class="es">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="48" height="35" viewBox="0 0 128 96"><path fill="#a2cfe9" d="m32.56 24.948 25.043 27.933 4.816 9.151a575 575 0 0 1-9.391 20.228c-2.554 2.71-12.763 12.522-12.763 12.522l-3.371-7.224 1.146-50.446-6.444-6.385z"/><path fill="#e6f2f9" d="m32.56 24.948 20.468 4.816L85.792 64.62s12.8 21.06 12.791 21.188L80.567 79.1l-2.776-2.611-5.01 11.829-2.61-18.996-12.568-16.44z"/><path fill="#b9e4f3" d="m21.964 3.035 14.448 10.114 16.616 16.615 4.575-7.224 16.375-6.501-18.69 16.238-2.26-2.513 3.612 10.114-24.08-14.93-.964 5.78-8.428-15.894z"/><path fill="#b6ddef" d="m83.601 61.477 24.09-8.596 18.3 12.763-18.06 2.649-7.706 1.445-14.433-5.118z"/><path fill="#6daad5" d="m37.375 18.687-4.816 6.261L21.964 3.035a706 706 0 0 1 15.411 15.652"/><path fill="#8ac8e4" d="M37.375 18.687q9.899 10.38 19.265 21.19l5.779 22.155-4.816-9.15-4.275-15.057L32.56 24.948z"/><path fill="#b3d9ed" d="m59.53 36.988 14.448-20.95c-.029.014-5.539 16.134-5.539 16.134l-5.538 7.465z"/><path fill="#89c8e4" d="m59.53 36.988-4.241-4.712c.042-.061 5.685-7.087 5.685-7.087l13.004-9.15z"/><path fill="#b8e5f4" d="m59.53 36.988 3.37 2.65 21.722.984-1.02 20.855 2.19 3.143 4.801 4.636 7.99 16.552-10.639-16.311-24.08-22.876z"/><path fill="#4583ca" d="m105.282 55.771 20.71 9.873-18.06 2.649-6.259-1.492c-.383-.001 3.415-11.03 3.609-11.03"/><path fill="#e3f0f8" d="m42.19 41.058 10.597 10.138-4.335 2.167z"/><path fill="#8ec8e3" d="m27.261 44.694 8.67 6.742-3.372 3.372-3.853-1.686L8.96 56.493l-1.926-.24z"/><path fill="#87c7e6" d="m59.048 42.768 4.816 3.853 24.08 22.876-9.15.24a44147 44147 0 0 1-19.746-26.97"/><path fill="#dcecf4" d="m26.78 50.955 9.15.481-7.224 1.686-18.301 6.261-8.163-.617 4.792-2.513 1.926.24z"/><path fill="#68a3d1" d="M84.622 40.622c.047.021 9.997 5.54 9.997 5.54l-9.564 4.07 22.635 2.65-2.408 2.89-30.341-3.854-2.844-3.097z"/><path fill="#3670bf" d="m52.787 51.196 3.853 4.094-8.188-1.927z"/><path fill="#2d69c0" d="m74.941 51.918 30.341 3.853-3.608 11.03L83.6 61.477z"/><path fill="#4785cb" d="m84.622 40.622-6.72 8.775-5.805-.576-2.572-2.892z"/><path fill="#6badd7" d="m48.452 53.363 8.188 1.927.24 8.187a740 740 0 0 0-6.26 16.856l-7.947.963z"/><path fill="#e5f2f8" d="m48.452 53.363-5.78 27.933-2.407-16.375z"/><path fill="#8cc8e4" d="m42.19 41.058 6.262 12.305-10.895 4.997-.19 8.362-10.106 9.036-3.852-3.13z"/><path fill="#bae4f4" d="m42.19 41.058-4.092 7.97-.482 5.78a304 304 0 0 0-14.207 17.82l-4.428 7.747 2.26-10.155L32.56 54.808l3.371-3.372-9.15-.481.481-6.26a187 187 0 0 1 10.779-7.583z"/><path fill="#72afd8" d="m37.557 58.36 2.708 6.561 2.408 16.375-5.78 6.261z"/><path fill="#8fc7e5" d="m72.617 71.546 5.215 4.98-3.819 8.88z"/><path fill="#6ea9d4" d="m23.409 72.628 3.852 3.13-8.28 4.616z"/><path fill="#4784cd" d="m56.64 39.878 2.408 2.89q9.855 13.47 19.745 26.97l1.775 9.362-18.15-17.068z"/></svg>
</div>
<input type="text" value="0" id="es-text-input" onchange="setES(this.value)" />
</td>
<td width="28"><button onclick="adjustES(-requestContext.conversionStep)" class="sign-button">-</button></td>
<td><input type="range" value="0" min="0" id="es-range-input" onchange="setES(this.value)" /></td>
<td><button onclick="adjustES(requestContext.conversionStep)" class="sign-button">+</button></td>
<td><input type="text" value="0" id="g-text-input" onchange="setESFromG(this.value)" /></td>
<td width="28"><button onclick="adjustES(requestContext.conversionStep)" class="sign-button">+</button></td>
<td style="width:108px;"><input type="text" value="0" id="g-text-input" onchange="setESFromG(this.value)" /><div class="g">G</div></td>
</tr>
</table>
<p>Please note that the amount of G after conversion may be subject to rounding errors.</p>
<button onclick="convert()">Convert</button>
<div class="center">
<button onclick="convert()">Convert</button>
</div>
</div>
<div class="modal-popup" id="popup">
<div class="content" style="width: 600px;">
<div id="popup-low-balance">
<p>You do not have enough Eternity Splinters to convert to G.</p>
<button onclick="closePanel()">Close</button>
<div class="center">
<button class="close-button" onclick="closePanel()">Close</button>
</div>
</div>
<div id="popup-invalid-amount">
<p>Please specify a valid amount of Eternity Splinters.</p>
<button onclick="closePopup()">Close</button>
<div class="center">
<button class="close-button" onclick="closePopup()">Close</button>
</div>
</div>
<div id="popup-confirm">
<p id="popup-confirm-text"></p>
<div class="button-container">
<button onclick="convertConfirm()">Confirm</button>
<button onclick="closePopup()">Cancel</button>
<button class="close-button" onclick="closePopup()">Cancel</button>
</div>
</div>
<div id="popup-pending">
@ -142,7 +254,9 @@
</div>
<div id="popup-result">
<p id="popup-result-text"></p>
<button onclick="closePanel()">Close</button>
<div class="center">
<button class="close-button" onclick="closePanel()">Close</button>
</div>
</div>
</div>
</div>

View file

@ -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",
};
}

View file

@ -8,7 +8,7 @@
</PropertyGroup>
<PropertyGroup>
<AssemblyVersion>0.8.1.0</AssemblyVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<InformationalVersion>$(AssemblyVersion)</InformationalVersion>
</PropertyGroup>

View file

@ -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")]

View file

@ -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.";
}
}

View file

@ -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

View file

@ -9,7 +9,7 @@
</PropertyGroup>
<PropertyGroup>
<InformationalVersion>0.8.1</InformationalVersion>
<InformationalVersion>1.0.0</InformationalVersion>
<SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))</SourceRevisionId>
<AssemblyVersion>$(InformationalVersion).0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>

View file

@ -18,7 +18,7 @@
</ul>
</div>
<div class="buttons content">
<span class="price">{PRICE} G</span>
<span class="price">{PRICE} <span class="g">G</span></span>
<button onclick="myApi.BuyBundleFromJS('{SKU_ID}')">Buy Now!</button>
</div>
</body>

View file

@ -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,25 @@ 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;
}
.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: 24px;
height: 24px;
background: linear-gradient(-35deg, #007FFF 45%, #03C2F7 54%, #00B2FE 70%, #007FFF 100%);
border: 1px solid #00B2FE;
border-radius: 50%;
margin-left: 6px;
}