-
Notifications
You must be signed in to change notification settings - Fork 584
Regens refactoring & add tier 2 chars default movement speeds #888
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
ze-dom
wants to merge
16
commits into
MUnique:master
Choose a base branch
from
ze-dom:regens_refactor_default_running_speed_chars
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
81c3443
First commit
ze-dom 5eaa28a
Fixed mana value
ze-dom 9dd90de
Fixed bad initial interpretation
ze-dom 36432c4
Fix previous wrong commit
ze-dom faaaa1a
Fixed relationships shield
ze-dom 2529abf
Added resting timer
ze-dom 2b776c0
Simplified resting recovery interval value calc
ze-dom c60d5d1
Adjusted indentation and value
ze-dom 0ede60a
Some refactoring based on Claude's input
ze-dom b71641b
Added update plugin
ze-dom 9f6356c
Fixed some update plugin bugs
ze-dom aabd4f7
Some fixes
ze-dom 92a4d1b
plugin fixes
ze-dom 8447be5
Added plugins for different versions
ze-dom 0a80c16
Added conditions for versions without shield (classic PvP)
ze-dom 084f446
Merge branch 'master' into regens_refactor_default_running_speed_chars
ze-dom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1173,7 +1173,31 @@ public class Stats | |
| /// Gets the shield recovery everywhere attribute definition. | ||
| /// By default, shield recovery is limited to the safezone only. With this attribute (value >= 1), recovery works everywhere on a map. | ||
| /// </summary> | ||
| public static AttributeDefinition ShieldRecoveryEverywhere { get; } = new(new Guid("3D0A78FF-CCD4-442E-8B4E-64E5082ABD78"), "Is Shield Recovery Active Everwhere", "By default, shield recovery is limited to the safezone only. With this attribute (value >= 1), recovery works everywhere on a map."); | ||
| public static AttributeDefinition ShieldRecoveryEverywhere { get; } = new(new Guid("3D0A78FF-CCD4-442E-8B4E-64E5082ABD78"), "Shield Recovery Active Everywhere", "By default, shield recovery is limited to the safezone only. With this attribute (value >= 1), recovery works everywhere on a map."); | ||
|
|
||
| /// <summary> | ||
| /// Gets the is shield recovery active attribute definition. | ||
| /// </summary> | ||
| public static AttributeDefinition IsShieldRecoveryActive { get; } = new(new Guid("8F2C4D7E-B1A9-4E3F-9C5D-2A1B7E8F3C4D"), "Is Shield Recovery Active", string.Empty); | ||
|
|
||
| /// <summary> | ||
| /// Gets the shield recovery hiatus (in seconds) attribute definition. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Must be equal or greater than <see cref="ShieldRegeneration"/>.HiatusThreshold for shield to regenerate. See <see cref="PlugIns.ShieldRecoveryHiatusPlugIn"/>. | ||
| /// </remarks> | ||
| public static AttributeDefinition ShieldRecoveryHiatus { get; } = new(new Guid("5A7E2B9C-3D1F-4A8E-B6C2-1E7D4A9F2B3C"), "Shield Recovery Hiatus", "The seconds since when shield recovery was last interrupted, either by leaving a safezone, the shield being damaged or maxing out."); | ||
|
|
||
| /// <summary> | ||
| /// Gets the shield recovery ramp factor attribute definition. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The factor by which shield recovery increases. Rises linearly with <see cref="ShieldRecoveryHiatus"/>. Original range is [2,3]. | ||
| /// </remarks> | ||
| public static AttributeDefinition ShieldRecoveryRampFactor { get; } = new(new Guid("C3E8F1A7-4B9D-4C5E-8A2F-3D6E1C9B5A7F"), "Shield Recovery Ramp Factor", "The factor by which shield recovery increases. Rises linearly with the uninterrupted shield recovery duration.") | ||
| { | ||
| MaximumValue = 3, | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// Gets the ability usage reduction attribute definition. Value ranges from 0 (no reduction) to 1 (full reduction). | ||
|
|
@@ -1437,7 +1461,12 @@ public class Stats | |
| /// <summary> | ||
| /// Gets the <see cref="IsInSafezone"/> attribute which defines if the character is located in a safezone of a game map. | ||
| /// </summary> | ||
| public static AttributeDefinition IsInSafezone { get; } = new(new Guid("82044DF9-F528-4AD6-9AAA-6FEAA4C786E7"), "Flag, if the character is located in a safezone of a game map", "Characters at the safezone recover additional health and shield."); | ||
| public static AttributeDefinition IsInSafezone { get; } = new(new Guid("82044DF9-F528-4AD6-9AAA-6FEAA4C786E7"), "Flag, if the character is located in a safezone of a game map", "Characters at the safezone recover shield and additional ability."); | ||
|
|
||
| /// <summary> | ||
| /// Gets the <see cref="IsResting"/> attribute which defines if the character is in a resting state: sitting, leaning or hanging. | ||
| /// </summary> | ||
| public static AttributeDefinition IsResting { get; } = new(new Guid("7A4E2D9F-B1C3-48F5-9D2E-1A6F8C3E5B7D"), "Flag, if the character is resting (sitting, leaning or hanging)", "Characters resting recover additional health and mana."); | ||
|
|
||
| /// <summary> | ||
| /// Gets the <see cref="IsUnderwater"/> attribute which defines if the character is located on an underwater game map. | ||
|
|
@@ -1588,13 +1617,43 @@ public static IEnumerable<Regeneration> AfterMonsterKillRegenerationAttributes | |
| } | ||
| } | ||
|
|
||
| private static Regeneration ManaRegeneration { get; } = new(ManaRecoveryMultiplier, MaximumMana, CurrentMana, ManaRecoveryAbsolute); | ||
| /// <summary> | ||
| /// Gets the mana regeneration. | ||
| /// </summary> | ||
| public static Regeneration ManaRegeneration { get; } = new(ManaRecoveryMultiplier, MaximumMana, CurrentMana, ManaRecoveryAbsolute) | ||
| { | ||
| IntervalResting = TimeSpan.FromSeconds(5), | ||
| }; | ||
|
|
||
| private static Regeneration HealthRegeneration { get; } = new(HealthRecoveryMultiplier, MaximumHealth, CurrentHealth, HealthRecoveryAbsolute); | ||
| /// <summary> | ||
| /// Gets the health regeneration. | ||
| /// </summary> | ||
| public static Regeneration HealthRegeneration { get; } = new(HealthRecoveryMultiplier, MaximumHealth, CurrentHealth, HealthRecoveryAbsolute) | ||
| { | ||
| Interval = TimeSpan.FromSeconds(7), | ||
| IntervalResting = TimeSpan.FromSeconds(5), | ||
| }; | ||
|
|
||
| private static Regeneration AbilityRegeneration { get; } = new(AbilityRecoveryMultiplier, MaximumAbility, CurrentAbility, AbilityRecoveryAbsolute); | ||
| /// <summary> | ||
| /// Gets the ability regeneration. | ||
| /// </summary> | ||
| public static Regeneration AbilityRegeneration { get; } = new(AbilityRecoveryMultiplier, MaximumAbility, CurrentAbility, AbilityRecoveryAbsolute); | ||
|
|
||
| private static Regeneration ShieldRegeneration { get; } = new(ShieldRecoveryMultiplier, MaximumShield, CurrentShield, ShieldRecoveryAbsolute); | ||
| /// <summary> | ||
| /// Gets the shield regeneration. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Shield recovery is only possible at safe zone, except the character has a specific attribute which has the effect that it's recovered everywhere. | ||
| /// This attribute is usually provided by level 380 armor with a Guardian Option. | ||
| /// Also, shield has a penalty (hiatus) period before the recovery starts, after which it increases linearly (see <see cref="ShieldRecoveryRampFactor"/>). | ||
| /// </remarks> | ||
| public static Regeneration ShieldRegeneration { get; } = new(ShieldRecoveryMultiplier, MaximumShield, CurrentShield, ShieldRecoveryAbsolute) | ||
| { | ||
| Interval = TimeSpan.FromSeconds(1), | ||
| IntervalResting = TimeSpan.FromSeconds(1), | ||
| EnablerAttribute = IsShieldRecoveryActive, | ||
| HiatusAttribute = ShieldRecoveryHiatus, | ||
| }; | ||
|
|
||
| private static Regeneration ManaRegenerationAfterMonsterKill { get; } = new(ManaAfterMonsterKillMultiplier, MaximumMana, CurrentMana, ManaAfterMonsterKillAbsolute); | ||
|
|
||
|
|
@@ -1613,8 +1672,8 @@ public class Regeneration | |
| { | ||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="Regeneration" /> class. | ||
| /// At regeneration the value of <paramref name="regenerationMultiplier" /> * <paramref name="maximumAttribute" /> is getting added to | ||
| /// <paramref name="currentAttribute" />, until the value of <paramref name="maximumAttribute" /> is reached. | ||
| /// At regeneration the value of (<paramref name="maximumAttribute" /> * <paramref name="regenerationMultiplier" />) + <paramref name="absoluteAttribute"/> | ||
| /// is getting added to <paramref name="currentAttribute" />, until the value of <paramref name="maximumAttribute" /> is reached. | ||
| /// </summary> | ||
| /// <param name="regenerationMultiplier">The regeneration multiplier.</param> | ||
| /// <param name="maximumAttribute">The maximum attribute.</param> | ||
|
|
@@ -1647,5 +1706,36 @@ public Regeneration(AttributeDefinition regenerationMultiplier, AttributeDefinit | |
| /// Gets the current attribute. | ||
| /// </summary> | ||
| public AttributeDefinition CurrentAttribute { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the interval at which the attribute would complete a full regeneration cycle. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Originally, the game had different regeneration cycles depending on attributes and the player's state (<see cref="IsResting"/>). | ||
| /// To avoid having different timers each with a regeneration interval, we run just one set to <see cref="GameConfiguration.RecoveryInterval"/>. | ||
| /// Then, we apply a compensation factor which is the ratio between elapsed time and <see cref="Interval"/>. | ||
| /// This way we can mimic original regeneration over time while still keeping it configurable. | ||
| /// </remarks> | ||
| public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(3); | ||
|
|
||
| /// <summary> | ||
| /// Gets the interval at which the attribute would complete a full regeneration cycle when the player is resting (<see cref="IsResting"/>). | ||
| /// </summary> | ||
| public TimeSpan IntervalResting { get; init; } = TimeSpan.FromSeconds(3); | ||
|
Comment on lines
+1719
to
+1724
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| /// <summary> | ||
| /// Gets the attribute which gates the regeneration, if it exists. | ||
| /// </summary> | ||
| public AttributeDefinition? EnablerAttribute { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the attribute which keeps the regeneration hiatus duration, if it exists. | ||
| /// </summary> | ||
| public AttributeDefinition? HiatusAttribute { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the hiatus threshold (in seconds) against wich <see cref="HiatusAttribute"/> is checked to allow the regeneration. | ||
| /// </summary> | ||
| public int HiatusThreshold { get; init; } = 10; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,6 @@ namespace MUnique.OpenMU.GameLogic; | |
| using MUnique.OpenMU.GameLogic.Views.Guild; | ||
| using MUnique.OpenMU.GameLogic.Views.Inventory; | ||
| using MUnique.OpenMU.GameLogic.Views.MuHelper; | ||
| using MUnique.OpenMU.GameLogic.Views.Pet; | ||
| using MUnique.OpenMU.GameLogic.Views.Quest; | ||
| using MUnique.OpenMU.GameLogic.Views.World; | ||
| using MUnique.OpenMU.Interfaces; | ||
|
|
@@ -77,6 +76,14 @@ public class Player : AsyncDisposable, IBucketMapObserver, IAttackable, IAttacke | |
|
|
||
| private DateTime _lastRegenerate = DateTime.UtcNow; | ||
|
|
||
| private Dictionary<Stats.Regeneration, DateTime> _lastRegeneration = new() | ||
| { | ||
| [Stats.ManaRegeneration] = DateTime.UtcNow, | ||
| [Stats.HealthRegeneration] = DateTime.UtcNow, | ||
| [Stats.AbilityRegeneration] = DateTime.UtcNow, | ||
| [Stats.ShieldRegeneration] = DateTime.UtcNow, | ||
| }; | ||
|
|
||
| private GameMap? _currentMap; | ||
|
|
||
| private IDisposable? _accountLoggingScope; | ||
|
|
@@ -240,8 +247,7 @@ public CharacterPose Pose | |
|
|
||
| set | ||
| { | ||
| var character = this._selectedCharacter; | ||
| if (character is null || character.Pose == this.Pose) | ||
| if (this._selectedCharacter is not { } character || character.Pose == value) | ||
| { | ||
| return; | ||
| } | ||
|
|
@@ -876,28 +882,42 @@ public async Task RegenerateAsync() | |
| { | ||
| try | ||
| { | ||
| var attributes = this.Attributes; | ||
| if (attributes is null) | ||
| if (this.Attributes is not { } attributes) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| foreach (var r in Stats.IntervalRegenerationAttributes.Where(r => | ||
| attributes[r.RegenerationMultiplier] > 0 || attributes[r.AbsoluteAttribute] > 0)) | ||
| var now = DateTime.UtcNow; | ||
| foreach (var r in Stats.IntervalRegenerationAttributes) | ||
| { | ||
| if (r.CurrentAttribute == Stats.CurrentShield && !this.IsAtSafezone() && | ||
| attributes[Stats.ShieldRecoveryEverywhere] < 1) | ||
| if ((r.EnablerAttribute is { } enabler && attributes[enabler] < 1) | ||
| || (r.HiatusAttribute is { } hiatus && attributes[hiatus] < r.HiatusThreshold)) | ||
| { | ||
| // Shield recovery is only possible at safe-zone, except the character has a specific attribute which has the effect that it's recovered everywhere. | ||
| // This attribute is usually provided by level 380 armor and a Guardian Option. | ||
| this._lastRegeneration[r] = now; | ||
| continue; | ||
| } | ||
|
|
||
| var factor = 0f; | ||
| var interval = r.Interval; | ||
| if (attributes[Stats.IsResting] > 0) | ||
| { | ||
| interval = r.IntervalResting; | ||
|
|
||
| if (r.CurrentAttribute == Stats.CurrentMana) | ||
| { | ||
| // Mana recovery while resting is on top of regular recovery | ||
| factor += (float)((now - this._lastRegeneration[r]) / r.Interval); | ||
| } | ||
| } | ||
|
|
||
| factor += (float)((now - this._lastRegeneration[r]) / interval); | ||
|
|
||
| attributes[r.CurrentAttribute] = Math.Min( | ||
| attributes[r.CurrentAttribute] + | ||
| ((attributes[r.MaximumAttribute] * attributes[r.RegenerationMultiplier]) + | ||
| attributes[r.AbsoluteAttribute]), | ||
| (((attributes[r.MaximumAttribute] * attributes[r.RegenerationMultiplier]) + attributes[r.AbsoluteAttribute]) * factor), | ||
| attributes[r.MaximumAttribute]); | ||
|
|
||
| this._lastRegeneration[r] = now; | ||
| } | ||
|
|
||
| await this.RegenerateHeroStateAsync().ConfigureAwait(false); | ||
|
|
@@ -1202,6 +1222,17 @@ internal async ValueTask AfterKilledPlayerAsync(Player killedPlayer) | |
| await this.ForEachWorldObserverAsync<IUpdateCharacterHeroStatePlugIn>(o => o.UpdateCharacterHeroStateAsync(this), true).ConfigureAwait(false); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Sets the current values of the regeneration attributes to their maximum values. | ||
| /// </summary> | ||
| internal void SetReclaimableAttributesToMaximum() | ||
| { | ||
| foreach (var regeneration in Stats.IntervalRegenerationAttributes) | ||
| { | ||
| this.Attributes![regeneration.CurrentAttribute] = this.Attributes[regeneration.MaximumAttribute]; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Sets the current map without raising the enter/leave map events, when the player is | ||
| /// removed from the game. | ||
|
|
@@ -1594,7 +1625,7 @@ private async ValueTask OnPlayerEnteredWorldAsync() | |
| this.AddMissingStatAttributes(); | ||
|
|
||
| this.Attributes = new ItemAwareAttributeSystem(this.Account!, selectedCharacter, this.GameContext.Configuration); | ||
| this.Attributes[Stats.NearbyPartyMemberCount] = 0; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was not a |
||
| this.Attributes[Stats.IsResting] = 0; | ||
| this.LogInvalidInventoryItems(); | ||
|
|
||
| this._storages.CreateForCharacter(selectedCharacter); | ||
|
|
@@ -1669,17 +1700,6 @@ private void SetReclaimableAttributesBeforeEnterGame() | |
| this.Attributes[Stats.CurrentHealth] = Math.Min(this.Attributes[Stats.CurrentHealth], this.Attributes[Stats.MaximumHealth]); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Sets the current values of the regeneration attributes to their maximum values. | ||
| /// </summary> | ||
| internal void SetReclaimableAttributesToMaximum() | ||
| { | ||
| foreach (var regeneration in Stats.IntervalRegenerationAttributes) | ||
| { | ||
| this.Attributes![regeneration.CurrentAttribute] = this.Attributes[regeneration.MaximumAttribute]; | ||
| } | ||
| } | ||
|
|
||
| [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "Catching all Exceptions.")] | ||
| private async void OnAttributeValueChanged(object? sender, IAttribute attribute) | ||
| { | ||
|
|
||
75 changes: 75 additions & 0 deletions
75
src/GameLogic/PlugIns/PeriodicTasks/ShieldRecoveryHiatusPlugIn.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // <copyright file="ShieldRecoveryHiatusPlugIn.cs" company="MUnique"> | ||
| // Licensed under the MIT License. See LICENSE file in the project root for full license information. | ||
| // </copyright> | ||
|
|
||
| namespace MUnique.OpenMU.GameLogic.PlugIns; | ||
|
|
||
| using System; | ||
| using System.Runtime.InteropServices; | ||
| using MUnique.OpenMU.AttributeSystem; | ||
| using MUnique.OpenMU.GameLogic.Attributes; | ||
| using MUnique.OpenMU.GameLogic.NPC; | ||
| using MUnique.OpenMU.PlugIns; | ||
|
|
||
| /// <summary> | ||
| /// Updates and resets the shield recovery hiatus attribute. | ||
| /// </summary> | ||
| [PlugIn] | ||
| [Display(Name = nameof(PlugInResources.ShieldRecoveryHiatusPlugIn_Name), Description = nameof(PlugInResources.ShieldRecoveryHiatusPlugIn_Description), ResourceType = typeof(PlugInResources))] | ||
| [Guid("7B4A9E2D-C1F5-48E3-A6B2-1E7D4C8F3A5B")] | ||
| public class ShieldRecoveryHiatusPlugIn : IPeriodicTaskPlugIn, IAttackableMovedPlugIn, IAttackableGotHitPlugIn | ||
| { | ||
| /// <inheritdoc /> | ||
| public async ValueTask ExecuteTaskAsync(GameContext gameContext) | ||
| { | ||
| await gameContext.ForEachPlayerAsync(player => | ||
| { | ||
| if (player.SelectedCharacter != null | ||
| && !player.PlayerState.CurrentState.IsDisconnectedOrFinished() | ||
| && player.Attributes is { } attributes | ||
| && attributes[Stats.MaximumShield] > 0) | ||
| { | ||
| attributes.AddElement(new SimpleElement(1, AggregateType.AddRaw), Stats.ShieldRecoveryHiatus); | ||
| } | ||
|
|
||
| return Task.CompletedTask; | ||
| }).ConfigureAwait(false); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void ForceStart() | ||
| { | ||
| // do nothing. | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void AttackableGotHit(IAttackable attackable, IAttacker attacker, HitInfo hitInfo) | ||
| { | ||
| var defender = attackable as Player ?? (attackable as Monster)?.SummonedBy; | ||
| var attackerPlayer = attacker as Player ?? (attacker as Monster)?.SummonedBy; | ||
| if (defender is null || attackerPlayer is null || defender == attackerPlayer || defender.Attributes is not { } attributes) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (attributes[Stats.MaximumShield] > 0 && hitInfo is { ShieldDamage: > 0 }) | ||
| { | ||
| attributes.GetComposableAttribute(Stats.ShieldRecoveryHiatus)?.RemoveAllElements(); | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void AttackableMoved(IAttackable attackable) | ||
| { | ||
| if (attackable is not Player player || player.Attributes is not { } attributes) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (attributes[Stats.MaximumShield] > 0 && | ||
| (attributes[Stats.IsShieldRecoveryActive] < 1 || attributes[Stats.CurrentShield] == attributes[Stats.MaximumShield])) | ||
| { | ||
| attributes.GetComposableAttribute(Stats.ShieldRecoveryHiatus)?.RemoveAllElements(); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interval: zTeamS6.3, emu
IntervalResting (includes mana too): zTeamS6.3, emu