-
Notifications
You must be signed in to change notification settings - Fork 484
Feature/offlevel party #718
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
Open
eduardosmaniotto
wants to merge
42
commits into
MUnique:master
Choose a base branch
from
eduardosmaniotto:feature/offlevel-party
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.
Open
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
976b0d8
feature: offline party support
eduardosmaniotto 8dd0140
Merge branch 'master' into feature/offlevel-party
eduardosmaniotto d1d36f8
Merge branch 'feature/offlevel' into feature/offlevel-party
eduardosmaniotto a7a06e4
feature: offline party support bug fixing
eduardosmaniotto 353308d
fix: incorrect packet size causing test failure
eduardosmaniotto c6a678a
offlevel: implemented missing combat behaviour
eduardosmaniotto 5663e3c
Merge branch 'feature/offlevel' into feature/offlevel-party
eduardosmaniotto 454cb08
code review: minor adjustments
eduardosmaniotto 96829fd
feature: offline leveling pet support and some minor adjustments
eduardosmaniotto a04bc85
Merge branch 'feature/offlevel' into feature/offlevel-party
eduardosmaniotto 721a7e3
Merge branch 'feature/offlevel' into feature/offlevel-party
eduardosmaniotto c98e22a
fix: potential race condition on offline leveling login
eduardosmaniotto 32c9beb
Merge branch 'feature/offlevel' into feature/offlevel-party
eduardosmaniotto 368e499
Merge branch 'feature/offlevel' into feature/offlevel-party
eduardosmaniotto b91d7ca
fix: stop pet when player dies
eduardosmaniotto ef0154b
Merge branch 'feature/offlevel' into feature/offlevel-party
eduardosmaniotto 2c69d1f
Merge branch 'feature/offlevel' into feature/offlevel-party
eduardosmaniotto bb0504d
code review: fix file formatting
eduardosmaniotto 1168dcb
feature: offline leveling test methods
eduardosmaniotto 5b3ac10
fix: revert breaking change
eduardosmaniotto 18dc98d
code review: apply code suggestions
eduardosmaniotto bb35fbf
code review: apply code suggestions
eduardosmaniotto 27a2f05
Merge branch 'master' into feature/offlevel-party
sven-n c870e24
code review: revert code deleted by mistake
eduardosmaniotto e61f718
code review: fix warnings
eduardosmaniotto 20db643
bugfix: offlevel party
eduardosmaniotto be1ae3e
code review: fix warnings
eduardosmaniotto 5d5d021
code review: fix warnings
eduardosmaniotto 870ff76
code review: revert changes
eduardosmaniotto 5fcf2b0
code review: apply code changes, revert resources
eduardosmaniotto def5f49
code review: revert CurrentTarget access modifier
eduardosmaniotto 38c32c3
fix: soul barrier dmg reduction
eduardosmaniotto c0476bc
refactor: offline party, remove unecessary offline plugins
eduardosmaniotto 54b15ab
code review: fix warnings
eduardosmaniotto 00388aa
code review: revert unecessary changes
eduardosmaniotto f04bb25
code review: fix warnings
eduardosmaniotto 29f3e7c
code review
eduardosmaniotto d5cbc3d
add supress warning
eduardosmaniotto 2d62756
remove supress warning and try to fix
eduardosmaniotto 3796572
replace Timer with PeriodicTimer in Party.cs as recommended
eduardosmaniotto 60ed30b
replace Timer with PeriodicTimer in Party.cs as recommended
eduardosmaniotto 82162dd
code review: apply code changes
eduardosmaniotto 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
Some comments aren't visible on the classic Files Changed page.
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
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
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 |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // <copyright file="IPartyManager.cs" company="MUnique"> | ||
| // Licensed under the MIT License. See LICENSE file in the project root for full license information. | ||
| // </copyright> | ||
|
|
||
| namespace MUnique.OpenMU.GameLogic; | ||
|
|
||
| /// <summary> | ||
| /// Manages party creation and tracks party membership for member reconnection. | ||
| /// </summary> | ||
| public interface IPartyManager | ||
| { | ||
| /// <summary> | ||
| /// Creates a new party with the configured maximum party size. | ||
| /// </summary> | ||
| /// <returns>The newly created party.</returns> | ||
| Party CreateParty(); | ||
|
|
||
| /// <summary> | ||
| /// Called when a party member reconnects. Restores the live player into their previous party, | ||
| /// replacing the <see cref="OfflinePartyMember"/> snapshot that was created on disconnect. | ||
| /// </summary> | ||
| /// <param name="member">The reconnected member.</param> | ||
| ValueTask OnMemberReconnectedAsync(IPartyMember member); | ||
|
|
||
| /// <summary> | ||
| /// Registers that a character belongs to a party. Called by <see cref="Party"/> internally | ||
| /// when members are added, replaced, or removed. | ||
| /// </summary> | ||
| /// <param name="characterName">The character name.</param> | ||
| /// <param name="party">The party.</param> | ||
| internal void TrackMembership(string characterName, Party party); | ||
|
|
||
| /// <summary> | ||
| /// Removes the party tracking for a character. Called by <see cref="Party"/> internally | ||
| /// when members leave or are replaced. | ||
| /// </summary> | ||
| /// <param name="characterName">The character name.</param> | ||
| internal void UntrackMembership(string characterName); | ||
| } |
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
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 |
|---|---|---|
|
|
@@ -28,9 +28,6 @@ public class BasicMonsterIntelligence : INpcIntelligence, IDisposable | |
| /// <summary> | ||
| /// Gets or sets a value indicating whether this instance can walk on safezone. | ||
| /// </summary> | ||
| /// <value> | ||
| /// <c>true</c> if this instance can walk on safezone; otherwise, <c>false</c>. | ||
| /// </value> | ||
| public bool CanWalkOnSafezone { get; protected set; } | ||
|
|
||
| /// <inheritdoc/> | ||
|
|
@@ -91,6 +88,13 @@ public virtual bool CanWalkOn(Point target) | |
| return this.Monster.CurrentMap.Terrain.AIgrid[target.X, target.Y] == 1; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines whether the specified player is being targeted by this monster. | ||
| /// </summary> | ||
| /// <param name="player">The player to check.</param> | ||
| /// <returns>True if the player is the current target; otherwise, false.</returns> | ||
| public bool IsTargetingPlayer(Player player) => this.CurrentTarget == player; | ||
|
|
||
| /// <summary> | ||
| /// Called when the intelligence starts. | ||
| /// </summary> | ||
|
|
@@ -129,30 +133,28 @@ protected virtual void Dispose(bool managed) | |
| var possibleTargets = tempObservers.OfType<IAttackable>() | ||
| .Where(a => a.IsActive() && !a.IsAtSafezone() && a is not Player { IsInvisible: true }) | ||
| .ToList(); | ||
|
|
||
| // Also consider summoned monsters belonging to players in range. | ||
| var summons = possibleTargets.OfType<Player>() | ||
| .Select(p => p.Summon?.Item1) | ||
| .Where(s => s is not null) | ||
| .Cast<IAttackable>() | ||
| .WhereActive() | ||
| .ToList(); | ||
| possibleTargets.AddRange(summons); | ||
| var closestTarget = possibleTargets.MinBy(a => a.GetDistanceTo(this.Npc)); | ||
|
|
||
| return closestTarget; | ||
| possibleTargets.AddRange(summons); | ||
|
|
||
| // todo: check the walk distance | ||
| return possibleTargets.MinBy(a => a.GetDistanceTo(this.Npc)); | ||
eduardosmaniotto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines whether this instance can attack. | ||
| /// Determines whether this instance can attack this tick. | ||
| /// </summary> | ||
| /// <returns> | ||
| /// <c>true</c> if this instance can attack; otherwise, <c>false</c>. | ||
| /// </returns> | ||
| protected virtual ValueTask<bool> CanAttackAsync() => ValueTask.FromResult(true); | ||
|
|
||
| /// <summary> | ||
| /// Handles the tick without having a target. | ||
| /// Handles the tick when no target is available, moves the monster randomly. | ||
| /// </summary> | ||
| protected virtual async ValueTask TickWithoutTargetAsync() | ||
| { | ||
|
|
@@ -161,19 +163,15 @@ protected virtual async ValueTask TickWithoutTargetAsync() | |
| return; | ||
| } | ||
|
|
||
| // we move around randomly, so the monster does not look dead when watched from distance. | ||
| if (await this.IsObservedByAttackerAsync().ConfigureAwait(false)) | ||
| { | ||
| await this.Monster.RandomMoveAsync().ConfigureAwait(false); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines whether the handled monster is observed by an attacker. | ||
| /// Determines whether the handled monster is observed by any attacker. | ||
| /// </summary> | ||
| /// <returns> | ||
| /// <c>true</c> if the handled monster is observed by an attacker; otherwise, <c>false</c>. | ||
| /// </returns> | ||
| protected async ValueTask<bool> IsObservedByAttackerAsync() | ||
| { | ||
| using var readerLock = await this.Monster.ObserverLock.ReaderLockAsync(); | ||
|
|
@@ -197,7 +195,7 @@ private async void SafeTick() | |
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| // can be ignored. | ||
| // expected during shutdown. | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
|
|
@@ -228,36 +226,19 @@ private async ValueTask TickAsync() | |
| return; | ||
| } | ||
|
|
||
| var target = this.CurrentTarget; | ||
| if (target != null) | ||
| { | ||
| // Old Target out of Range? | ||
| if (!target.IsAlive | ||
| || target is Player { IsInvisible: true } | ||
| || target.IsTeleporting | ||
| || target.IsAtSafezone() | ||
| || !target.IsInRange(this.Monster.Position, this.Npc.Definition.ViewRange) | ||
| || (target is IWorldObserver && !await this.IsTargetInObserversAsync(target).ConfigureAwait(false))) | ||
| { | ||
| target = this.CurrentTarget = await this.SearchNextTargetAsync().ConfigureAwait(false); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| target = this.CurrentTarget = await this.SearchNextTargetAsync().ConfigureAwait(false); | ||
| } | ||
| this.CurrentTarget = await this.ResolveTargetAsync().ConfigureAwait(false); | ||
|
|
||
| // no target? | ||
| if (target is null) | ||
| if (this.CurrentTarget is null) | ||
| { | ||
| await this.TickWithoutTargetAsync().ConfigureAwait(false); | ||
| return; | ||
| } | ||
|
|
||
| // Target in Attack Range? | ||
| if (target.IsInRange(this.Monster.Position, this.Monster.Definition.AttackRange) && !this.Monster.IsAtSafezone()) | ||
| // Target in attack range — attack. | ||
| if (this.CurrentTarget.IsInRange(this.Monster.Position, this.Monster.Definition.AttackRange) | ||
| && !this.Monster.IsAtSafezone()) | ||
| { | ||
| await this.Monster.AttackAsync(target).ConfigureAwait(false); // yes, attack | ||
| await this.Monster.AttackAsync(this.CurrentTarget).ConfigureAwait(false); | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -266,18 +247,51 @@ private async ValueTask TickAsync() | |
| return; | ||
| } | ||
|
|
||
| // Target in View Range? | ||
| if (target.IsInRange(this.Monster.Position, this.Monster.Definition.ViewRange + 1)) | ||
| // Target visible but outside attack range, walk toward it. | ||
| if (this.CurrentTarget.IsInRange(this.Monster.Position, this.Monster.Definition.ViewRange + 1)) | ||
| { | ||
| // no, walk to the target | ||
| var walkTarget = this.Monster.CurrentMap!.Terrain.GetRandomCoordinate(target.Position, this.Monster.Definition.AttackRange); | ||
| var walkTarget = this.Monster.CurrentMap!.Terrain.GetRandomCoordinate(this.CurrentTarget.Position, this.Monster.Definition.AttackRange); | ||
| if (await this.Monster.WalkToAsync(walkTarget).ConfigureAwait(false)) | ||
| { | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| // we move around randomly, so the monster does not look dead when watched from distance. | ||
| // Nothing else to do, wander randomly. | ||
| await this.Monster.RandomMoveAsync().ConfigureAwait(false); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns the current target if still valid, otherwise searches for a new one. | ||
| /// </summary> | ||
| private async ValueTask<IAttackable?> ResolveTargetAsync() | ||
|
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. |
||
| { | ||
| if (this.CurrentTarget is not null && this.IsCurrentTargetValid()) | ||
| { | ||
| // Double-check the target is still within the observer list (needed for | ||
| // players who have moved out of view range server-side). | ||
| if (!await this.IsTargetInObserversAsync(this.CurrentTarget).ConfigureAwait(false) | ||
| && this.CurrentTarget is IWorldObserver) | ||
| { | ||
| return await this.SearchNextTargetAsync().ConfigureAwait(false); | ||
| } | ||
|
|
||
| return this.CurrentTarget; | ||
| } | ||
|
|
||
| return await this.SearchNextTargetAsync().ConfigureAwait(false); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns <c>true</c> if the current target is still a valid attack candidate. | ||
| /// </summary> | ||
| private bool IsCurrentTargetValid() | ||
| { | ||
| return this.CurrentTarget is not null | ||
| && this.CurrentTarget.IsAlive | ||
| && this.CurrentTarget is not Player { IsInvisible: true } | ||
| && !this.CurrentTarget.IsTeleporting | ||
| && !this.CurrentTarget.IsAtSafezone() | ||
| && this.CurrentTarget.IsInRange(this.Monster.Position, this.Npc.Definition.ViewRange); | ||
| } | ||
| } | ||
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.
The description for
ReflectedLightPinkshould consistently use 'reflected damage', similar to the other descriptions.