Skip to content

Commit

Permalink
Merge pull request #441 from MUnique/dev/exp-formulas-config
Browse files Browse the repository at this point in the history
Refactored exp tables to use formulas
  • Loading branch information
sven-n authored Aug 5, 2024
2 parents 654c167 + 5811c45 commit ee67dbd
Show file tree
Hide file tree
Showing 14 changed files with 5,116 additions and 74 deletions.
10 changes: 4 additions & 6 deletions src/DataModel/Configuration/GameConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,14 @@ public partial class GameConfiguration
public int MaximumVaultMoney { get; set; }

/// <summary>
/// Gets or sets the experience table. Index is the player level, value the needed experience to reach that level.
/// Gets or sets the experience formula per level. The variable name for the level is "level".
/// </summary>
[IgnoreWhenCloning]
public long[]? ExperienceTable { get; set; }
public string? ExperienceFormula { get; set; }

/// <summary>
/// Gets or sets the master experience table. Index is the player level, value the needed experience to reach that level.
/// Gets or sets the experience formula per master level. The variable name for the level is "level".
/// </summary>
[IgnoreWhenCloning]
public long[]? MasterExperienceTable { get; set; }
public string? MasterExperienceFormula { get; set; }

/// <summary>
/// Gets or sets the interval for attribute recoveries. See also MUnique.OpenMU.GameLogic.Attributes.Stats.Regeneration.
Expand Down
29 changes: 29 additions & 0 deletions src/GameLogic/GameContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ namespace MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.PlugIns;
using Nito.AsyncEx;
using org.mariuszgromada.math.mxparser;

/// <summary>
/// The game context which holds all data of the game together.
/// </summary>
public class GameContext : AsyncDisposable, IGameContext
{
private const string DefaultExperienceFormula = "if(level == 0, 0, if(level < 256, 10 * (level + 8) * (level - 1) * (level - 1), (10 * (level + 8) * (level - 1) * (level - 1)) + (1000 * (level - 247) * (level - 256) * (level - 256))))";
private const string DefaultMasterExperienceFormula = "(505 * level * level * level) + (35278500 * level) + (228045 * level * level)";

private static readonly Meter Meter = new(MeterName);

private static readonly Counter<int> PlayerCounter = Meter.CreateCounter<int>("PlayerCount");
Expand Down Expand Up @@ -79,6 +83,8 @@ public GameContext(GameConfiguration configuration, IPersistenceContextProvider
this.FeaturePlugIns = new FeaturePlugInContainer(this.PlugInManager);
this._configChangeHandlerRegistration = this.ConfigurationChangeMediator.RegisterObject(this.Configuration, this, this.OnGameConfigurationChangeAsync);
this.DuelRoomManager = new DuelRoomManager(this.Configuration.DuelConfiguration!);
this.ExperienceTable = CreateExpTable(this.Configuration.ExperienceFormula ?? DefaultExperienceFormula, this.Configuration.MaximumLevel);
this.MasterExperienceTable = CreateExpTable(this.Configuration.MasterExperienceFormula ?? DefaultMasterExperienceFormula, this.Configuration.MaximumMasterLevel);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -107,6 +113,12 @@ public GameContext(GameConfiguration configuration, IPersistenceContextProvider
/// <inheritdoc/>
public GameConfiguration Configuration { get; }

/// <inheritdoc/>
public long[] ExperienceTable { get; private set; }

/// <inheritdoc/>
public long[] MasterExperienceTable { get; private set; }

/// <inheritdoc/>
public IConfigurationChangeMediator ConfigurationChangeMediator { get; }

Expand Down Expand Up @@ -380,11 +392,28 @@ protected override async ValueTask DisposeAsyncCore()
await base.DisposeAsyncCore().ConfigureAwait(false);
}

private static long[] CreateExpTable(string experienceFormula, short maximumLevel)
{
var argument = new Argument("level");
var expression = new Expression(experienceFormula);
expression.addArguments(argument);

return Enumerable.Range(0, maximumLevel + 2)
.Select(level =>
{
argument.setArgumentValue(level);
return (long)expression.calculate();
})
.ToArray();
}

#pragma warning disable CS1998
private async ValueTask OnGameConfigurationChangeAsync(Action unregisterAction, GameConfiguration gameConfiguration, GameContext context)
#pragma warning restore CS1998
{
this._recoverTimer.Change(gameConfiguration.RecoveryInterval, gameConfiguration.RecoveryInterval);
this.ExperienceTable = CreateExpTable(gameConfiguration.ExperienceFormula ?? DefaultExperienceFormula, gameConfiguration.MaximumLevel);
this.MasterExperienceTable = CreateExpTable(gameConfiguration.MasterExperienceFormula ?? DefaultMasterExperienceFormula, gameConfiguration.MaximumMasterLevel);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "Catching all Exceptions.")]
Expand Down
10 changes: 10 additions & 0 deletions src/GameLogic/IGameContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ public interface IGameContext
/// </summary>
GameConfiguration Configuration { get; }

/// <summary>
/// Gets the experience table. Index is the player level, value the needed experience to reach that level.
/// </summary>
long[] ExperienceTable { get; }

/// <summary>
/// Gets the master experience table. Index is the player level, value the needed experience to reach that level.
/// </summary>
long[] MasterExperienceTable { get; }

/// <summary>
/// Gets the configuration change mediator.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/GameLogic/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ public async ValueTask AddMasterExperienceAsync(int experience, IAttackable? kil

// Add the Exp
bool lvlup = false;
var expTable = this.GameContext.Configuration.MasterExperienceTable ?? throw Error.NotInitializedProperty(this.GameContext.Configuration, nameof(GameConfiguration.MasterExperienceTable));
var expTable = this.GameContext.MasterExperienceTable;
if (expTable[(int)this.Attributes[Stats.MasterLevel] + 1] - this.SelectedCharacter!.MasterExperience < exp)
{
exp = expTable[(int)this.Attributes[Stats.MasterLevel] + 1] - this.SelectedCharacter.MasterExperience;
Expand Down Expand Up @@ -1087,7 +1087,7 @@ public async ValueTask AddExperienceAsync(int experience, IAttackable? killedObj

long exp = experience;
bool isLevelUp = false;
var expTable = this.GameContext.Configuration.ExperienceTable ?? throw Error.NotInitializedProperty(this.GameContext.Configuration, nameof(GameConfiguration.ExperienceTable));
var expTable = this.GameContext.ExperienceTable;
var expForNextLevel = expTable[(int)this.Attributes[Stats.Level] + 1];
if (expForNextLevel - this.SelectedCharacter!.Experience < exp)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ await connection.SendCharacterInformationAsync(
this._player.Position.Y,
this._player.SelectedCharacter!.CurrentMap!.Number.ToUnsigned(),
(ulong)this._player.SelectedCharacter.Experience,
(ulong)this._player.GameServerContext.Configuration.ExperienceTable![(int)this._player.Attributes![Stats.Level] + 1],
(ulong)this._player.GameServerContext.ExperienceTable[(int)this._player.Attributes![Stats.Level] + 1],
(ushort)Math.Max(0, this._player.SelectedCharacter.LevelUpPoints),
(ushort)this._player.Attributes[Stats.BaseStrength],
(ushort)this._player.Attributes[Stats.BaseAgility],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ await connection.SendCharacterInformation075Async(
this._player.Position.Y,
(byte)this._player.SelectedCharacter!.CurrentMap!.Number,
(uint)this._player.SelectedCharacter.Experience,
(uint)this._player.GameServerContext.Configuration.ExperienceTable![(int)this._player.Attributes![Stats.Level] + 1],
(uint)this._player.GameServerContext.ExperienceTable[(int)this._player.Attributes![Stats.Level] + 1],
(ushort)Math.Max(this._player.SelectedCharacter.LevelUpPoints, 0),
(ushort)this._player.Attributes[Stats.BaseStrength],
(ushort)this._player.Attributes[Stats.BaseAgility],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ await connection.SendCharacterInformation097Async(
(byte)this._player.SelectedCharacter!.CurrentMap!.Number,
this._player.Rotation.ToPacketByte(),
(uint)this._player.SelectedCharacter.Experience,
(uint)this._player.GameServerContext.Configuration.ExperienceTable![(int)this._player.Attributes![Stats.Level] + 1],
(uint)this._player.GameServerContext.ExperienceTable[(int)this._player.Attributes![Stats.Level] + 1],
(ushort)Math.Max(this._player.SelectedCharacter.LevelUpPoints, 0),
(ushort)this._player.Attributes[Stats.BaseStrength],
(ushort)this._player.Attributes[Stats.BaseAgility],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public async ValueTask SendMasterStatsAsync()
await connection.SendMasterStatsUpdateAsync(
(ushort)this._player.Attributes[Stats.MasterLevel],
(ulong)character.MasterExperience,
(ulong)this._player.GameServerContext.Configuration.MasterExperienceTable![(int)this._player.Attributes[Stats.MasterLevel] + 1],
(ulong)this._player.GameServerContext.MasterExperienceTable[(int)this._player.Attributes[Stats.MasterLevel] + 1],
(ushort)character.MasterLevelUpPoints,
(ushort)this._player.Attributes[Stats.MaximumHealth],
(ushort)this._player.Attributes[Stats.MaximumMana],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@ namespace MUnique.OpenMU.Persistence.EntityFramework;
/// <summary>
/// The game configuration repository, which loads the configuration by using the
/// <see cref="JsonObjectLoader"/>, to speed up loading the whole object graph.
/// Additionally it fills the experience table, because the entity framework can't map arrays.
/// </summary>
internal class CachingGameConfigurationRepository : CachingGenericRepository<GameConfiguration>
{
private readonly JsonObjectLoader _objectLoader;
private const long MinLevel = 0;
private const long MaxLevel = 256;

/// <summary>
/// Initializes a new instance of the <see cref="CachingGameConfigurationRepository" /> class.
Expand All @@ -43,13 +40,7 @@ public CachingGameConfigurationRepository(IContextAwareRepositoryProvider reposi
await database.OpenConnectionAsync().ConfigureAwait(false);
try
{
if (await this._objectLoader.LoadObjectAsync<GameConfiguration>(id, currentContext.Context).ConfigureAwait(false) is { } config)
{
this.SetExperienceTables(config);
return config;
}

return null;
return await this._objectLoader.LoadObjectAsync<GameConfiguration>(id, currentContext.Context).ConfigureAwait(false);
}
finally
{
Expand All @@ -70,7 +61,6 @@ public override async ValueTask<IEnumerable<GameConfiguration>> GetAllAsync()
try
{
var configs = (await this._objectLoader.LoadAllObjectsAsync<GameConfiguration>(currentContext.Context).ConfigureAwait(false)).ToList();
configs.ForEach(this.SetExperienceTables);

var oldConfig = ((EntityDataContext)currentContext.Context).CurrentGameConfiguration;
try
Expand All @@ -93,40 +83,4 @@ public override async ValueTask<IEnumerable<GameConfiguration>> GetAllAsync()
await database.CloseConnectionAsync().ConfigureAwait(false);
}
}

private void SetExperienceTables(GameConfiguration gameConfiguration)
{
gameConfiguration.ExperienceTable =
Enumerable.Range(0, gameConfiguration.MaximumLevel + 2)
.Select(level => this.CalculateNeededExperience(level))
.ToArray();
gameConfiguration.MasterExperienceTable =
Enumerable.Range(0, 201).Select(level => this.CalcNeededMasterExp(level)).ToArray();
}

/// <summary>
/// The equation
/// f(x) = 505 * x^3 + 35278500 * x + 228045 * x^2
/// </summary>
/// <param name="lvl"></param>
/// <returns> long. </returns>
private long CalcNeededMasterExp(long lvl)
{
return (505 * lvl * lvl * lvl) + (35278500 * lvl) + (228045 * lvl * lvl);
}

/// <summary>
/// The equation for calculate needed experience.
/// </summary>
/// <param name="level"></param>
/// <returns> long. </returns>
private long CalculateNeededExperience(long level)
{
return level switch
{
MinLevel => 0,
< MaxLevel => 10 * (level + 8) * (level - 1) * (level - 1),
_ => (10 * (level + 8) * (level - 1) * (level - 1)) + (1000 * (level - 247) * (level - 256) * (level - 256)),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ internal static class GameConfigurationExtensions
public static void Apply(this EntityTypeBuilder<GameConfiguration> builder)
{
builder.Property(c => c.ItemDropDuration).HasDefaultValue(TimeSpan.FromSeconds(60));

// TODO:
builder.Ignore(c => c.ExperienceTable)
.Ignore(c => c.MasterExperienceTable);
builder.Property(c => c.ExperienceFormula).HasDefaultValue("if(level == 0, 0, if(level < 256, 10 * (level + 8) * (level - 1) * (level - 1), (10 * (level + 8) * (level - 1) * (level - 1)) + (1000 * (level - 247) * (level - 256) * (level - 256))))");
builder.Property(c => c.MasterExperienceFormula).HasDefaultValue("(505 * level * level * level) + (35278500 * level) + (228045 * level * level)");
}
}
Loading

0 comments on commit ee67dbd

Please sign in to comment.