Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/Dapr/FriendServer.Host/FriendServerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ public async Task FriendResponseAsync([FromBody] FriendResponseArguments data)
await this._friendServer.FriendResponseAsync(data.CharacterName, data.FriendName, data.Accepted).ConfigureAwait(false);
}

/// <summary>
/// Determines whether two players are friends.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>True if the two players are friends; otherwise false.</returns>
[HttpPost(nameof(IFriendServer.IsFriendAsync))]
public async Task<bool> IsFriendAsync([FromBody] RequestArguments data)
{
return await this._friendServer.IsFriendAsync(data.Requester, data.Receiver).ConfigureAwait(false);
}

/// <summary>
/// Sends a friend request to the friend, and adds a new friend view item to the players friend list.
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions src/Dapr/ServerClients/FriendServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@ public async ValueTask SetPlayerVisibilityStateAsync(byte serverId, Guid charact
}
}

/// <inheritdoc />
public async ValueTask<bool> IsFriendAsync(string characterName, string friendName)
{
try
{
return await this._daprClient.InvokeMethodAsync<RequestArguments, bool>(this._targetAppId, nameof(this.IsFriendAsync), new RequestArguments(characterName, friendName)).ConfigureAwait(false);
}
catch (Exception ex)
{
this._logger.LogError(ex, "Unexpected error when checking friendship.");
return false;
}
}

/// <inheritdoc />
public async ValueTask<bool> FriendRequestAsync(string playerName, string friendName)
{
Expand Down
16 changes: 16 additions & 0 deletions src/FriendServer/FriendServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,32 @@ public async ValueTask<bool> FriendRequestAsync(string playerName, string friend
return friendIsNew && saveSuccess;
}

/// <inheritdoc/>
public async ValueTask<bool> IsFriendAsync(string characterName, string friendName)
{
if (this.OnlineFriends.TryGetValue(characterName, out var player)
&& this.OnlineFriends.TryGetValue(friendName, out var friend))
{
return player.HasSubscriber(friend);
}

using var context = this._persistenceContextProvider.CreateNewFriendServerContext();
var friendEntry = await context.GetFriendByNamesAsync(characterName, friendName).ConfigureAwait(false);
return friendEntry?.Accepted == true;
}

/// <inheritdoc/>
public async ValueTask DeleteFriendAsync(string playerName, string friendName)
{
if (this.OnlineFriends.TryGetValue(playerName, out var player) && this.OnlineFriends.TryGetValue(friendName, out var friend))
{
player.RemoveSubscriber(friend);
friend.RemoveSubscriber(player);
}

using var context = this._persistenceContextProvider.CreateNewFriendServerContext();
await context.DeleteAsync(playerName, friendName).ConfigureAwait(false);
await context.DeleteAsync(friendName, playerName).ConfigureAwait(false);
await context.SaveChangesAsync().ConfigureAwait(false);
}

Expand Down
12 changes: 12 additions & 0 deletions src/GameLogic/MuHelper/IMuHelperSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,16 @@ public interface IMuHelperSettings

/// <summary>Gets a value indicating whether to repair items.</summary>
bool RepairItem { get; }

/// <summary>Gets a value indicating whether to automatically defend against nearby monsters attacking the character.</summary>
bool UseSelfDefense { get; }

/// <summary>Gets a value indicating whether to automatically accept friend requests.</summary>
bool AutoAcceptFriend { get; }

/// <summary>Gets a value indicating whether to automatically accept guild join requests.</summary>
bool AutoAcceptGuild { get; }

/// <summary>Gets a value indicating whether to use basic attack as fallback when the configured skill cannot be used.</summary>
bool FallbackBasicAttack { get; }
}
100 changes: 100 additions & 0 deletions src/GameLogic/MuHelper/PartyRequestHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// <copyright file="PartyRequestHandler.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.MuHelper;

using MUnique.OpenMU.Interfaces;

/// <summary>
/// Handles auto-accepting incoming party requests from friends and guild members
/// based on the player's MU Helper settings flags.
/// </summary>
public static class PartyRequestHandler
{
/// <summary>
/// Automatically accepts a party request if the receiver has the relevant flag enabled.
/// </summary>
/// <param name="receiver">The player receiving the party request.</param>
/// <param name="requester">The player who sent the party request.</param>
/// <returns>True if the criteria matched (auto-accept was attempted, regardless of success); false if no criteria matched.</returns>
public static async ValueTask<bool> TryAutoAcceptPartyRequestAsync(Player receiver, Player requester)
{
var settings = receiver.MuHelperSettings;
if (settings is null)
{
return false;
}

if (settings.AutoAcceptGuild && AreGuildMembers(receiver, requester))
{
await AcceptPartyRequestAsync(receiver, requester).ConfigureAwait(false);
return true;
}

if (settings.AutoAcceptFriend && await AreFriendsAsync(receiver, requester).ConfigureAwait(false))
{
await AcceptPartyRequestAsync(receiver, requester).ConfigureAwait(false);
return true;
}

return false;
}
Comment thread
eduardosmaniotto marked this conversation as resolved.

private static bool AreGuildMembers(Player receiver, Player requester)
{
return receiver.GuildStatus?.GuildId != null
&& receiver.GuildStatus.GuildId == requester.GuildStatus?.GuildId;
}

private static async ValueTask<bool> AreFriendsAsync(Player receiver, Player requester)
{
if (receiver.SelectedCharacter is null || requester.SelectedCharacter is null)
{
return false;
}

var friendServer = (receiver.GameContext as IGameServerContext)?.FriendServer;
if (friendServer is null)
{
return false;
}

return await friendServer.IsFriendAsync(receiver.SelectedCharacter.Name, requester.SelectedCharacter.Name).ConfigureAwait(false);
}

private static async ValueTask<bool> AcceptPartyRequestAsync(Player receiver, Player requester)
{
bool success = false;
try
{
if (receiver.Party != null)
{
if (requester.Party == null)
{
// Receiver is the offline party master; add the solo requester to the existing party.
success = await receiver.Party.AddAsync(requester).ConfigureAwait(false);
}
}
else if (requester.Party != null)
{
// Requester already has a party; add the offline receiver to it.
success = await requester.Party.AddAsync(receiver).ConfigureAwait(false);
}
else
{
// Neither side has a party; create a new one.
var party = receiver.GameContext.PartyManager.CreateParty();
success = await party.AddAsync(requester).ConfigureAwait(false)
&& await party.AddAsync(receiver).ConfigureAwait(false);
}
}
finally
{
await receiver.PlayerState.TryAdvanceToAsync(PlayerState.EnteredWorld).ConfigureAwait(false);
receiver.LastPartyRequester = null;
}

return success;
}
}
25 changes: 12 additions & 13 deletions src/GameLogic/Offline/CombatHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@ namespace MUnique.OpenMU.GameLogic.Offline;
public sealed class CombatHandler
{
private const byte DefaultRange = 1;
private const byte BowRange = 6;
private const int ComboFinisherDelayTicks = 3;
private const int InterSkillDelayTicks = 1;
private const int MinComboSkillCount = 3;

private static readonly TargetedSkillDefaultPlugin DefaultPlugin = new();

private const short DrainLifeBaseSkillId = 214;
private const short DrainLifeStrengthenerSkillId = 458;
private const short DrainLifeMasterySkillId = 462;

private static readonly TargetedSkillDefaultPlugin DefaultPlugin = new();

private readonly OfflinePlayer _player;
private readonly IMuHelperSettings? _config;
private readonly MovementHandler _movementHandler;
private readonly BuffHandler _buffHandler;
private readonly Point _originPosition;
private readonly ConditionalSkillSlot[] _conditionalSkillSlots;

Expand All @@ -46,14 +46,12 @@ public sealed class CombatHandler
/// <param name="player">The offline player.</param>
/// <param name="config">The MU helper settings.</param>
/// <param name="movementHandler">The movement handler.</param>
/// <param name="buffHandler">The buff handler.</param>
/// <param name="originPosition">The original position to hunt around.</param>
public CombatHandler(OfflinePlayer player, IMuHelperSettings? config, MovementHandler movementHandler, BuffHandler buffHandler, Point originPosition)
public CombatHandler(OfflinePlayer player, IMuHelperSettings? config, MovementHandler movementHandler, Point originPosition)
{
this._player = player;
this._config = config;
this._movementHandler = movementHandler;
this._buffHandler = buffHandler;
this._originPosition = originPosition;
this._conditionalSkillSlots = config is null ? [] :
[
Expand Down Expand Up @@ -163,14 +161,9 @@ public async ValueTask PerformDrainLifeRecoveryAsync()
private async ValueTask ExecuteAttackAsync(IAttackable target)
{
var skill = this.SelectAttackSkill();
if (skill == null)
if (skill == null && this._config?.FallbackBasicAttack != true)
{
// Do not attack if there are buffs configured to handle buff-only classes.
var buffs = this._buffHandler.ConfiguredBuffIds;
if (buffs.Any(id => id > 0))
{
return;
}
return;
}

await this.ExecuteAttackAsync(target, skill, false).ConfigureAwait(false);
Expand Down Expand Up @@ -522,6 +515,12 @@ private byte GetEffectiveAttackRange()
}
}

if (this._player.Attributes is { } attributes
&& (attributes[Stats.IsBowEquipped] > 0 || attributes[Stats.IsCrossBowEquipped] > 0))
{
return BowRange;
}

return DefaultRange;
}

Expand Down
2 changes: 1 addition & 1 deletion src/GameLogic/Offline/OfflinePlayerMuHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public OfflinePlayerMuHelper(OfflinePlayer player)
this._healingHandler = new HealingHandler(player, config);
this._itemPickupHandler = new ItemPickupHandler(player, config);
this._movementHandler = new MovementHandler(player, config, originalPosition);
this._combatHandler = new CombatHandler(player, config, this._movementHandler, this._buffHandler, originalPosition);
this._combatHandler = new CombatHandler(player, config, this._movementHandler, originalPosition);
this._repairHandler = new RepairHandler(player, config);
this._zenHandler = new ZenConsumptionHandler(player);
this._petHandler = new PetHandler(player, config);
Expand Down
31 changes: 30 additions & 1 deletion src/GameLogic/PlayerActions/Party/PartyRequestAction.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// <copyright file="PartyRequestAction.cs" company="MUnique">
// <copyright file="PartyRequestAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.PlayerActions.Party;

using MUnique.OpenMU.GameLogic.MuHelper;
using MUnique.OpenMU.GameLogic.Views.Party;

/// <summary>
Expand All @@ -27,6 +28,14 @@ public async ValueTask HandlePartyRequestAsync(Player player, Player toRequest)

if (toRequest.Party != null || toRequest.LastPartyRequester != null)
{
if (toRequest.Party != null && Equals(toRequest.Party.PartyMaster, toRequest))
{
if (await PartyRequestHandler.TryAutoAcceptPartyRequestAsync(toRequest, player).ConfigureAwait(false))
{
return;
}
}

await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.PlayerIsAlreadyInParty), toRequest.Name).ConfigureAwait(false);
return;
}
Expand All @@ -37,6 +46,17 @@ public async ValueTask HandlePartyRequestAsync(Player player, Player toRequest)
return;
}

if (toRequest is Offline.OfflinePlayer)
{
if (await PartyRequestHandler.TryAutoAcceptPartyRequestAsync(toRequest, player).ConfigureAwait(false))
{
return;
}

await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.PlayerIsAlreadyInParty), toRequest.Name).ConfigureAwait(false);
return;
}

if (await toRequest.PlayerState.TryAdvanceToAsync(PlayerState.PartyRequest).ConfigureAwait(false))
{
await this.SendPartyRequestAsync(toRequest, player).ConfigureAwait(false);
Expand All @@ -52,6 +72,15 @@ private async ValueTask SendPartyRequestAsync(IPartyMember toRequest, IPartyMemb
}

toRequest.LastPartyRequester = requester;

if (toRequest is Player receiver && requester is Player requesterPlayer)
{
if (await PartyRequestHandler.TryAutoAcceptPartyRequestAsync(receiver, requesterPlayer).ConfigureAwait(false))
{
return;
}
}

await toRequest.InvokeViewPlugInAsync<IShowPartyRequestPlugIn>(p => p.ShowPartyRequestAsync(requester)).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace MUnique.OpenMU.GameServer.MessageHandler.MuHelper;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.PlayerActions.MuHelper;
using MUnique.OpenMU.GameServer.RemoteView.MuHelper;
using MUnique.OpenMU.Network.Packets.ClientToServer;
using MUnique.OpenMU.PlugIns;

Expand Down Expand Up @@ -36,5 +37,9 @@ public async ValueTask HandlePacketAsync(Player player, Memory<byte> packet)
var memory = memoryOwner.Memory[..dataSize];
message.HelperData.CopyTo(memory.Span);
await this._updateMuBotConfigurationAction.SaveDataAsync(player, memory).ConfigureAwait(false);
if (player.SelectedCharacter?.MuHelperConfiguration is { } configuration)
{
player.MuHelperSettings = MuHelperSettingsSerializer.TryDeserialize(configuration);
}
}
}
Loading