baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// <copyright file="AppearanceChangedExtendedPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Entities;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The extended implementation of the <see cref="IAppearanceChangedPlugIn"/> which is forwarding appearance changes of other players to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.AppearanceChangedExtendedPlugIn_Name), Description = nameof(PlugInResources.AppearanceChangedExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("A2F298E4-9F48-402A-B30D-9BC2BA8DEB2E")]
|
||||
[MinimumClient(106, 3, ClientLanguage.Invariant)]
|
||||
public class AppearanceChangedExtendedPlugIn : IAppearanceChangedPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AppearanceChangedExtendedPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public AppearanceChangedExtendedPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask AppearanceChangedAsync(Player changedPlayer, Item item, bool isEquipped)
|
||||
{
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null || changedPlayer.Inventory is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await connection.SendAppearanceChangedExtendedAsync(
|
||||
changedPlayer.GetId(this._player),
|
||||
item.ItemSlot,
|
||||
(byte)((isEquipped ? item.Definition?.Group : 0xFF) ?? 0xFF),
|
||||
(ushort)(item.Definition?.Number ?? 0xFFFF),
|
||||
item.Level,
|
||||
(byte)(ItemSerializerHelper.GetExcellentByte(item) | ItemSerializerHelper.GetFenrirByte(item)),
|
||||
(byte)(item.ItemSetGroups.FirstOrDefault(set => set.AncientSetDiscriminator != 0)?.AncientSetDiscriminator ?? 0),
|
||||
changedPlayer.SelectedCharacter?.HasFullAncientSetEquipped() is true)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
79
src/GameServer/RemoteView/World/AppearanceChangedPlugIn.cs
Normal file
79
src/GameServer/RemoteView/World/AppearanceChangedPlugIn.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
// <copyright file="AppearanceChangedPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Entities;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IAppearanceChangedPlugIn"/> which is forwarding appearance changes of other players to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.AppearanceChangedPlugIn_Name), Description = nameof(PlugInResources.AppearanceChangedPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("1d097399-d5af-40de-a97d-a812f13c2f20")]
|
||||
public class AppearanceChangedPlugIn : IAppearanceChangedPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AppearanceChangedPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public AppearanceChangedPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask AppearanceChangedAsync(Player changedPlayer, Item item, bool isEquipped)
|
||||
{
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null || changedPlayer.Inventory is not { } inventory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int Write()
|
||||
{
|
||||
var itemSerializer = this._player.ItemSerializer;
|
||||
var size = AppearanceChanged.GetRequiredSize(itemSerializer.NeededSpace);
|
||||
var span = connection!.Output.GetSpan(size)[..size];
|
||||
var packet = new AppearanceChangedRef(span)
|
||||
{
|
||||
ChangedPlayerId = changedPlayer.GetId(this._player),
|
||||
};
|
||||
|
||||
if (inventory!.EquippedItems.Contains(item))
|
||||
{
|
||||
itemSerializer.SerializeItem(packet.ItemData, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
packet.ItemData.Fill(0xFF);
|
||||
}
|
||||
|
||||
// The byte with index 1 usually now holds the item level and one part of the item option level.
|
||||
// This full information is irrelevant. For this message, we just need the "glow" level, which means the one of the appearance serializer.
|
||||
// In the available space, the item position is serialized.
|
||||
// To summarize: The 4 higher bits hold the item position, the 4 lower bits hold the "glow" level
|
||||
packet.ItemData[1] = (byte)(item.ItemSlot << 4);
|
||||
packet.ItemData[1] |= item.GetGlowLevel();
|
||||
|
||||
// We could also continue to dumb down information here as this packet reveals all of the options of an item to
|
||||
// other players - something that is probably not in the interest of the players.
|
||||
// However, for now we keep this logic close to the original server, which doesn't do a thing about it.
|
||||
|
||||
// Additionally, we could think of ignoring changes of rings and pendants, as they are usually not visible in the game client, except
|
||||
// maybe transformation rings. So we'll leave it as it is, too.
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// <copyright file="DeActivateMagicEffectPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.AttributeSystem;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IActivateMagicEffectPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.DeActivateMagicEffectPlugIn_Name), Description = nameof(PlugInResources.DeActivateMagicEffectPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("67642604-8abb-44b9-a668-989cb3b28e89")]
|
||||
[MinimumClient(0, 90, ClientLanguage.Invariant)]
|
||||
public class DeActivateMagicEffectPlugIn : IActivateMagicEffectPlugIn, IDeactivateMagicEffectPlugIn
|
||||
{
|
||||
private static readonly ReadOnlyDictionary<AttributeDefinition, EffectItemConsumption.EffectType> EffectTypeMapping = new(
|
||||
new Dictionary<AttributeDefinition, EffectItemConsumption.EffectType>
|
||||
{
|
||||
{ Stats.AttackSpeedAny, EffectItemConsumption.EffectType.AttackSpeed },
|
||||
{ Stats.BaseDamageBonus, EffectItemConsumption.EffectType.Damage },
|
||||
{ Stats.DefenseBase, EffectItemConsumption.EffectType.Defense },
|
||||
{ Stats.MaximumHealth, EffectItemConsumption.EffectType.MaximumHealth },
|
||||
{ Stats.MaximumMana, EffectItemConsumption.EffectType.MaximumMana },
|
||||
});
|
||||
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeActivateMagicEffectPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public DeActivateMagicEffectPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask ActivateMagicEffectAsync(MagicEffect effect, IAttackable affectedObject)
|
||||
{
|
||||
return this.SendMagicEffectStatusAsync(effect, affectedObject, true, effect.Definition.SendDuration ? effect.Duration : TimeSpan.Zero);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask DeactivateMagicEffectAsync(MagicEffect effect, IAttackable affectedObject)
|
||||
{
|
||||
return this.SendMagicEffectStatusAsync(effect, affectedObject, false, TimeSpan.Zero);
|
||||
}
|
||||
|
||||
private async ValueTask SendMagicEffectStatusAsync(MagicEffect effect, IAttackable affectedObject, bool isActive, TimeSpan duration)
|
||||
{
|
||||
if (!(this._player.Connection?.Connected ?? false)
|
||||
|| effect.Definition.Number <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool effectWasSent = false;
|
||||
var objectId = affectedObject.GetId(this._player);
|
||||
if (isActive && affectedObject == this._player)
|
||||
{
|
||||
foreach (var powerUpDefinition in effect.Definition.PowerUpDefinitions)
|
||||
{
|
||||
if (powerUpDefinition.TargetAttribute is { } targetAttribute
|
||||
&& EffectTypeMapping.TryGetValue(targetAttribute, out var effectType))
|
||||
{
|
||||
var origin = EffectItemConsumption.EffectOrigin.HalloweenAndCherryBlossomEvent; // Basically, all normal consumable items which add effects
|
||||
var action = EffectItemConsumption.EffectAction.Add;
|
||||
await this._player.Connection.SendEffectItemConsumptionAsync(origin, effectType, action, (uint)duration.TotalSeconds, (byte)effect.Definition.Number).ConfigureAwait(false);
|
||||
effectWasSent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!effectWasSent)
|
||||
{
|
||||
await this._player.Connection.SendMagicEffectStatusAsync(isActive, objectId, (byte)effect.Id).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// <copyright file="DeActivateMagicEffectPlugIn075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IActivateMagicEffectPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.DeActivateMagicEffectPlugIn075_Name), Description = nameof(PlugInResources.DeActivateMagicEffectPlugIn075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("3CF8BFBA-FBDA-431D-9806-86B3DEC3AD54")]
|
||||
[MaximumClient(0, 89, ClientLanguage.Invariant)]
|
||||
public class DeActivateMagicEffectPlugIn075 : IDeactivateMagicEffectPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeActivateMagicEffectPlugIn075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public DeActivateMagicEffectPlugIn075(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask DeactivateMagicEffectAsync(MagicEffect effect, IAttackable affectedObject)
|
||||
{
|
||||
if (effect.Definition.Number <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We assume, that the magic effect number is equal to the skill number.
|
||||
// In early versions, the magic effect number is not used elsewhere.
|
||||
await this._player.Connection.SendMagicEffectCancelled075Async((byte)effect.Definition.Number, affectedObject.GetId(this._player)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// <copyright file="DroppedItemsDisappearedPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IDroppedItemsDisappearedPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.DroppedItemsDisappearedPlugIn_Name), Description = nameof(PlugInResources.DroppedItemsDisappearedPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("ecd14e95-33be-44f7-bb9b-1429a57a7a94")]
|
||||
public class DroppedItemsDisappearedPlugIn : IDroppedItemsDisappearedPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DroppedItemsDisappearedPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public DroppedItemsDisappearedPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask DroppedItemsDisappearedAsync(IEnumerable<ushort> disappearedItemIds)
|
||||
{
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
////C2 00 07 21 01 00 0C
|
||||
int count = disappearedItemIds.Count();
|
||||
|
||||
int Write()
|
||||
{
|
||||
var size = ItemDropRemovedRef.GetRequiredSize(count);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var message = new ItemDropRemovedRef(span)
|
||||
{
|
||||
ItemCount = (byte)count,
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (var dropId in disappearedItemIds)
|
||||
{
|
||||
var drop = message[i];
|
||||
drop.Id = dropId;
|
||||
i++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
41
src/GameServer/RemoteView/World/EffectNumbers.cs
Normal file
41
src/GameServer/RemoteView/World/EffectNumbers.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// <copyright file="EffectNumbers.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
/// <summary>
|
||||
/// Skill effect flags which were used in earlier versions, like 0.75.
|
||||
/// </summary>
|
||||
internal static class EffectNumbers
|
||||
{
|
||||
/// <summary>
|
||||
/// The undefined effect. No effect.
|
||||
/// </summary>
|
||||
public const int Undefined = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The object has a damage buff.
|
||||
/// </summary>
|
||||
public const int DamageBuff = 0x01;
|
||||
|
||||
/// <summary>
|
||||
/// The object has a defense buff.
|
||||
/// </summary>
|
||||
public const int DefenseBuff = 0x02;
|
||||
|
||||
/// <summary>
|
||||
/// The object is poisoned.
|
||||
/// </summary>
|
||||
public const int Poisoned = 0x37;
|
||||
|
||||
/// <summary>
|
||||
/// The object is iced.
|
||||
/// </summary>
|
||||
public const int Iced = 0x38;
|
||||
|
||||
/// <summary>
|
||||
/// Shows the health bar for duel spectators.
|
||||
/// </summary>
|
||||
public const int DuelSpectatorHealthBar = 0x62;
|
||||
}
|
||||
55
src/GameServer/RemoteView/World/MapChangePlugIn.cs
Normal file
55
src/GameServer/RemoteView/World/MapChangePlugIn.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
// <copyright file="MapChangePlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IMapChangePlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.MapChangePlugIn_Name), Description = nameof(PlugInResources.MapChangePlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("234b477d-6fe9-4caa-a03f-78cb25518b39")]
|
||||
[MinimumClient(1, 0, ClientLanguage.Invariant)]
|
||||
public class MapChangePlugIn : IMapChangePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapChangePlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public MapChangePlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask MapChangeAsync()
|
||||
{
|
||||
return this.SendMessageAsync(true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask MapChangeFailedAsync()
|
||||
{
|
||||
return this.SendMessageAsync(false);
|
||||
}
|
||||
|
||||
private async ValueTask SendMessageAsync(bool success)
|
||||
{
|
||||
if (this._player.SelectedCharacter?.CurrentMap is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapNumber = this._player.SelectedCharacter.CurrentMap.Number.ToUnsigned();
|
||||
var position = this._player.IsWalking ? this._player.WalkTarget : this._player.Position;
|
||||
|
||||
await this._player.Connection.SendMapChangedAsync(mapNumber, position.X, position.Y, this._player.Rotation.ToPacketByte(), success).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
48
src/GameServer/RemoteView/World/MapChangePlugIn075.cs
Normal file
48
src/GameServer/RemoteView/World/MapChangePlugIn075.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
// <copyright file="MapChangePlugIn075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IMapChangePlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.MapChangePlugIn075_Name), Description = nameof(PlugInResources.MapChangePlugIn075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("88195844-06C7-4EDA-8501-8B75A8B4B3F4")]
|
||||
public class MapChangePlugIn075 : IMapChangePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapChangePlugIn075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public MapChangePlugIn075(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask MapChangeAsync()
|
||||
{
|
||||
if (this._player.SelectedCharacter?.CurrentMap is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapNumber = (byte)this._player.SelectedCharacter.CurrentMap.Number;
|
||||
var position = this._player.IsWalking ? this._player.WalkTarget : this._player.Position;
|
||||
|
||||
await this._player.Connection.SendMapChanged075Async(mapNumber, position.X, position.Y, this._player.Rotation.ToPacketByte()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask MapChangeFailedAsync()
|
||||
{
|
||||
// not implemented?
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
43
src/GameServer/RemoteView/World/MapEventStateUpdatePlugIn.cs
Normal file
43
src/GameServer/RemoteView/World/MapEventStateUpdatePlugIn.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// <copyright file="MapEventStateUpdatePlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.PlugIns;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IMapEventStateUpdatePlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.MapEventStateUpdatePlugIn_Name), Description = nameof(PlugInResources.MapEventStateUpdatePlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("8A34C69D-59CC-4251-9E8E-D80154A7AC8C")]
|
||||
public class MapEventStateUpdatePlugIn : IMapEventStateUpdatePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapEventStateUpdatePlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public MapEventStateUpdatePlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask UpdateStateAsync(bool enabled, MapEventType mapEventType)
|
||||
{
|
||||
await this._player.Connection.SendMapEventStateAsync(enabled, Convert(mapEventType)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static MapEventState.Events Convert(MapEventType eventType)
|
||||
{
|
||||
return eventType switch
|
||||
{
|
||||
MapEventType.RedDragonInvasion => MapEventState.Events.RedDragon,
|
||||
MapEventType.GoldenDragonInvasion => MapEventState.Events.GoldenDragon,
|
||||
_ => throw new ArgumentException($"Unknown map's event type {eventType}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
168
src/GameServer/RemoteView/World/NewNpcsInScopePlugIn.cs
Normal file
168
src/GameServer/RemoteView/World/NewNpcsInScopePlugIn.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
// <copyright file="NewNpcsInScopePlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="INewNpcsInScopePlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.NewNpcsInScopePlugIn_Name), Description = nameof(PlugInResources.NewNpcsInScopePlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("35449477-0fba-48cb-9371-f337433b0f9d")]
|
||||
[MinimumClient(5, 0, ClientLanguage.Invariant)]
|
||||
public class NewNpcsInScopePlugIn : INewNpcsInScopePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewNpcsInScopePlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public NewNpcsInScopePlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask NewNpcsInScopeAsync(IEnumerable<NonPlayerCharacter> newObjects, bool isSpawned = true)
|
||||
{
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null || newObjects is null || !newObjects.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var summons = newObjects.OfType<ISummonable>().Where(m => m.SummonedBy is { }).ToList();
|
||||
var npcs = newObjects.Except(summons.OfType<NonPlayerCharacter>()).ToList();
|
||||
|
||||
if (npcs.Any())
|
||||
{
|
||||
await NpcsInScopeAsync(isSpawned, connection, npcs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (summons.Any())
|
||||
{
|
||||
await SummonedMonstersInScopeAsync(isSpawned, connection, summons).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask NpcsInScopeAsync(bool isSpawned, IConnection connection, ICollection<NonPlayerCharacter> npcs)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var size = AddNpcsToScopeRef.GetRequiredSize(npcs.Count);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new AddNpcsToScopeRef(span)
|
||||
{
|
||||
NpcCount = (byte)npcs.Count,
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (var npc in npcs)
|
||||
{
|
||||
var npcBlock = packet[i];
|
||||
npcBlock.Id = npc.Id;
|
||||
if (isSpawned)
|
||||
{
|
||||
npcBlock.Id |= 0x8000;
|
||||
}
|
||||
|
||||
npcBlock.TypeNumber = (ushort)(npc.Definition?.Number ?? 0);
|
||||
npcBlock.CurrentPositionX = npc.Position.X;
|
||||
npcBlock.CurrentPositionY = npc.Position.Y;
|
||||
|
||||
var supportWalk = npc as ISupportWalk;
|
||||
if (supportWalk?.IsWalking ?? false)
|
||||
{
|
||||
npcBlock.TargetPositionX = supportWalk.WalkTarget.X;
|
||||
npcBlock.TargetPositionY = supportWalk.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
npcBlock.TargetPositionX = npc.Position.X;
|
||||
npcBlock.TargetPositionY = npc.Position.Y;
|
||||
}
|
||||
|
||||
npcBlock.Rotation = npc.Rotation.ToPacketByte();
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask SummonedMonstersInScopeAsync(bool isSpawned, IConnection connection, ICollection<ISummonable> summons)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
const int estimatedEffectsPerPlayer = 5;
|
||||
var estimatedSizePerCharacter = AddSummonedMonstersToScopeRef.SummonedMonsterDataRef.GetRequiredSize(estimatedEffectsPerPlayer);
|
||||
var estimatedSize = AddSummonedMonstersToScopeRef.GetRequiredSize(summons.Count, estimatedSizePerCharacter);
|
||||
var span = connection.Output.GetSpan(estimatedSize)[..estimatedSize];
|
||||
var packet = new AddSummonedMonstersToScopeRef(span)
|
||||
{
|
||||
MonsterCount = (byte)summons.Count,
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (var summon in summons)
|
||||
{
|
||||
var block = packet[i];
|
||||
block.Id = summon.Id;
|
||||
if (isSpawned)
|
||||
{
|
||||
block.Id |= 0x8000;
|
||||
}
|
||||
|
||||
block.TypeNumber = (ushort)(summon.Definition?.Number ?? 0);
|
||||
block.CurrentPositionX = summon.Position.X;
|
||||
block.CurrentPositionY = summon.Position.Y;
|
||||
|
||||
if (summon is ISupportWalk walker && walker.IsWalking)
|
||||
{
|
||||
block.TargetPositionX = walker.WalkTarget.X;
|
||||
block.TargetPositionY = walker.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
block.TargetPositionX = summon.Position.X;
|
||||
block.TargetPositionY = summon.Position.Y;
|
||||
}
|
||||
|
||||
block.Rotation = summon.Rotation.ToPacketByte();
|
||||
block.OwnerCharacterName = summon.SummonedBy?.Name ?? string.Empty;
|
||||
|
||||
if (summon is IAttackable attackable)
|
||||
{
|
||||
var activeEffects = attackable.MagicEffectList.VisibleEffects;
|
||||
block.EffectCount = (byte)activeEffects.Count;
|
||||
for (int e = block.EffectCount - 1; e >= 0; e--)
|
||||
{
|
||||
var effectBlock = block[e];
|
||||
effectBlock.Id = (byte)activeEffects[e].Id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
block.EffectCount = 0;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return estimatedSize;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
160
src/GameServer/RemoteView/World/NewNpcsInScopePlugIn075.cs
Normal file
160
src/GameServer/RemoteView/World/NewNpcsInScopePlugIn075.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
// <copyright file="NewNpcsInScopePlugIn075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="T:MUnique.OpenMU.GameLogic.Views.World.INewNpcsInScopePlugIn" /> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.NewNpcsInScopePlugIn075_Name), Description = nameof(PlugInResources.NewNpcsInScopePlugIn075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("7E9CE800-E59F-4E90-A6F1-28214483213C")]
|
||||
[MinimumClient(0, 75, ClientLanguage.Invariant)]
|
||||
public class NewNpcsInScopePlugIn075 : INewNpcsInScopePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewNpcsInScopePlugIn075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public NewNpcsInScopePlugIn075(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask NewNpcsInScopeAsync(IEnumerable<NonPlayerCharacter> newObjects, bool isSpawned = true)
|
||||
{
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null || newObjects is null || !newObjects.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var summons = newObjects.OfType<Monster>().Where(m => m.SummonedBy is { }).ToList();
|
||||
var npcs = newObjects.Except(summons).ToList();
|
||||
if (npcs.Any())
|
||||
{
|
||||
await NpcsInScopeAsync(isSpawned, connection, npcs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (summons.Any())
|
||||
{
|
||||
await SummonedMonstersInScopeAsync(isSpawned, connection, summons).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask NpcsInScopeAsync(bool isSpawned, IConnection connection, ICollection<NonPlayerCharacter> npcs)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var size = AddNpcsToScope075Ref.GetRequiredSize(npcs.Count);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new AddNpcsToScope075Ref(span)
|
||||
{
|
||||
NpcCount = (byte)npcs.Count,
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (var npc in npcs)
|
||||
{
|
||||
var npcBlock = packet[i];
|
||||
npcBlock.Id = npc.Id;
|
||||
if (isSpawned)
|
||||
{
|
||||
npcBlock.Id |= 0x8000;
|
||||
}
|
||||
|
||||
npcBlock.TypeNumber = (byte)npc.Definition.Number;
|
||||
npcBlock.CurrentPositionX = npc.Position.X;
|
||||
npcBlock.CurrentPositionY = npc.Position.Y;
|
||||
if (npc is Monster monster)
|
||||
{
|
||||
npcBlock.IsPoisoned = monster.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Poisoned);
|
||||
npcBlock.IsIced = monster.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Iced);
|
||||
npcBlock.IsDamageBuffed = monster.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DamageBuff);
|
||||
npcBlock.IsDefenseBuffed = monster.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DefenseBuff);
|
||||
}
|
||||
|
||||
var supportWalk = npc as ISupportWalk;
|
||||
if (supportWalk?.IsWalking ?? false)
|
||||
{
|
||||
npcBlock.TargetPositionX = supportWalk.WalkTarget.X;
|
||||
npcBlock.TargetPositionY = supportWalk.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
npcBlock.TargetPositionX = npc.Position.X;
|
||||
npcBlock.TargetPositionY = npc.Position.Y;
|
||||
}
|
||||
|
||||
npcBlock.Rotation = npc.Rotation.ToPacketByte();
|
||||
i++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask SummonedMonstersInScopeAsync(bool isSpawned, IConnection connection, ICollection<Monster> summons)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var size = AddSummonedMonstersToScope075Ref.GetRequiredSize(summons.Count);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new AddSummonedMonstersToScope075Ref(span)
|
||||
{
|
||||
MonsterCount = (byte)summons.Count,
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (var summon in summons)
|
||||
{
|
||||
var block = packet[i];
|
||||
block.Id = summon.Id;
|
||||
if (isSpawned)
|
||||
{
|
||||
block.Id |= 0x8000;
|
||||
}
|
||||
|
||||
block.TypeNumber = (byte)summon.Definition.Number;
|
||||
block.CurrentPositionX = summon.Position.X;
|
||||
block.CurrentPositionY = summon.Position.Y;
|
||||
block.IsPoisoned = summon.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Poisoned);
|
||||
block.IsIced = summon.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Iced);
|
||||
block.IsDamageBuffed = summon.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DamageBuff);
|
||||
block.IsDefenseBuffed = summon.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DefenseBuff);
|
||||
|
||||
var supportWalk = summon as ISupportWalk;
|
||||
if (supportWalk?.IsWalking ?? false)
|
||||
{
|
||||
block.TargetPositionX = supportWalk.WalkTarget.X;
|
||||
block.TargetPositionY = supportWalk.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
block.TargetPositionX = summon.Position.X;
|
||||
block.TargetPositionY = summon.Position.Y;
|
||||
}
|
||||
|
||||
block.Rotation = summon.Rotation.ToPacketByte();
|
||||
block.OwnerCharacterName = summon.SummonedBy?.Name ?? string.Empty;
|
||||
i++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
160
src/GameServer/RemoteView/World/NewNpcsInScopePlugIn095.cs
Normal file
160
src/GameServer/RemoteView/World/NewNpcsInScopePlugIn095.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
// <copyright file="NewNpcsInScopePlugIn095.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="T:MUnique.OpenMU.GameLogic.Views.World.INewNpcsInScopePlugIn" /> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.NewNpcsInScopePlugIn095_Name), Description = nameof(PlugInResources.NewNpcsInScopePlugIn095_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("ECCD99EB-425D-4C9B-8F04-2711BA7A4C1E")]
|
||||
[MinimumClient(0, 95, ClientLanguage.Invariant)]
|
||||
public class NewNpcsInScopePlugIn095 : INewNpcsInScopePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewNpcsInScopePlugIn095"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public NewNpcsInScopePlugIn095(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask NewNpcsInScopeAsync(IEnumerable<NonPlayerCharacter> newObjects, bool isSpawned = true)
|
||||
{
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null || newObjects is null || !newObjects.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var summons = newObjects.OfType<Monster>().Where(m => m.SummonedBy is { }).ToList();
|
||||
var npcs = newObjects.Except(summons).ToList();
|
||||
if (npcs.Any())
|
||||
{
|
||||
await NpcsInScopeAsync(isSpawned, connection, npcs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (summons.Any())
|
||||
{
|
||||
await SummonedMonstersInScopeAsync(isSpawned, connection, summons).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask NpcsInScopeAsync(bool isSpawned, IConnection connection, ICollection<NonPlayerCharacter> npcs)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var size = AddNpcsToScope095Ref.GetRequiredSize(npcs.Count);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new AddNpcsToScope095Ref(span)
|
||||
{
|
||||
NpcCount = (byte)npcs.Count,
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (var npc in npcs)
|
||||
{
|
||||
var npcBlock = packet[i];
|
||||
npcBlock.Id = npc.Id;
|
||||
if (isSpawned)
|
||||
{
|
||||
npcBlock.Id |= 0x8000;
|
||||
}
|
||||
|
||||
npcBlock.TypeNumber = (byte)npc.Definition.Number;
|
||||
npcBlock.CurrentPositionX = npc.Position.X;
|
||||
npcBlock.CurrentPositionY = npc.Position.Y;
|
||||
if (npc is Monster monster)
|
||||
{
|
||||
npcBlock.IsPoisoned = monster.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Poisoned);
|
||||
npcBlock.IsIced = monster.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Iced);
|
||||
npcBlock.IsDamageBuffed = monster.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DamageBuff);
|
||||
npcBlock.IsDefenseBuffed = monster.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DefenseBuff);
|
||||
}
|
||||
|
||||
var supportWalk = npc as ISupportWalk;
|
||||
if (supportWalk?.IsWalking ?? false)
|
||||
{
|
||||
npcBlock.TargetPositionX = supportWalk.WalkTarget.X;
|
||||
npcBlock.TargetPositionY = supportWalk.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
npcBlock.TargetPositionX = npc.Position.X;
|
||||
npcBlock.TargetPositionY = npc.Position.Y;
|
||||
}
|
||||
|
||||
npcBlock.Rotation = npc.Rotation.ToPacketByte();
|
||||
i++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask SummonedMonstersInScopeAsync(bool isSpawned, IConnection connection, ICollection<Monster> summons)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var size = AddSummonedMonstersToScope095Ref.GetRequiredSize(summons.Count);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new AddSummonedMonstersToScope095Ref(span)
|
||||
{
|
||||
MonsterCount = (byte)summons.Count,
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (var summon in summons)
|
||||
{
|
||||
var block = packet[i];
|
||||
block.Id = summon.Id;
|
||||
if (isSpawned)
|
||||
{
|
||||
block.Id |= 0x8000;
|
||||
}
|
||||
|
||||
block.TypeNumber = (byte)summon.Definition.Number;
|
||||
block.CurrentPositionX = summon.Position.X;
|
||||
block.CurrentPositionY = summon.Position.Y;
|
||||
block.IsPoisoned = summon.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Poisoned);
|
||||
block.IsIced = summon.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Iced);
|
||||
block.IsDamageBuffed = summon.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DamageBuff);
|
||||
block.IsDefenseBuffed = summon.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DefenseBuff);
|
||||
|
||||
var supportWalk = summon as ISupportWalk;
|
||||
if (supportWalk?.IsWalking ?? false)
|
||||
{
|
||||
block.TargetPositionX = supportWalk.WalkTarget.X;
|
||||
block.TargetPositionY = supportWalk.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
block.TargetPositionX = summon.Position.X;
|
||||
block.TargetPositionY = summon.Position.Y;
|
||||
}
|
||||
|
||||
block.Rotation = summon.Rotation.ToPacketByte();
|
||||
block.OwnerCharacterName = summon.SummonedBy?.Name ?? string.Empty;
|
||||
i++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// <copyright file="NewPlayersInScopeExtendedPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.Character;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The extended implementation of the <see cref="INewPlayersInScopePlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.NewPlayersInScopeExtendedPlugIn_Name), Description = nameof(PlugInResources.NewPlayersInScopeExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("83E30752-501E-4A40-9698-9A5097825C30")]
|
||||
[MinimumClient(106, 3, ClientLanguage.Invariant)]
|
||||
public class NewPlayersInScopeExtendedPlugIn : NewPlayersInScopePlugIn, INewPlayersInScopePlugIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewPlayersInScopeExtendedPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public NewPlayersInScopeExtendedPlugIn(RemotePlayer player)
|
||||
: base(player)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async ValueTask SendCharacterAsync(Player newPlayer, bool isSpawned)
|
||||
{
|
||||
var connection = this.Player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedCharacter = newPlayer.SelectedCharacter;
|
||||
if (selectedCharacter is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int Write()
|
||||
{
|
||||
var appearanceSerializer = this.Player.AppearanceSerializer;
|
||||
Span<byte> activeEffects = stackalloc byte[newPlayer.MagicEffectList.VisibleEffects.Count];
|
||||
for (int i = 0; i < activeEffects.Length && i < newPlayer.MagicEffectList.VisibleEffects.Count; i++)
|
||||
{
|
||||
activeEffects[i] = (byte)newPlayer.MagicEffectList.VisibleEffects[i].Id;
|
||||
}
|
||||
|
||||
var requiredSize = AddCharacterToScopeExtendedRef.GetRequiredSize(appearanceSerializer.NeededSpace + activeEffects.Length + 1);
|
||||
|
||||
var span = connection.Output.GetSpan(requiredSize)[..requiredSize];
|
||||
var packet = new AddCharacterToScopeExtendedRef(span);
|
||||
|
||||
packet.Id = newPlayer.GetId(this.Player);
|
||||
if (isSpawned)
|
||||
{
|
||||
packet.Id |= 0x8000;
|
||||
}
|
||||
|
||||
packet.CurrentPositionX = newPlayer.Position.X;
|
||||
packet.CurrentPositionY = newPlayer.Position.Y;
|
||||
|
||||
packet.Name = selectedCharacter.Name;
|
||||
if (newPlayer.IsWalking)
|
||||
{
|
||||
packet.TargetPositionX = newPlayer.WalkTarget.X;
|
||||
packet.TargetPositionY = newPlayer.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
packet.TargetPositionX = newPlayer.Position.X;
|
||||
packet.TargetPositionY = newPlayer.Position.Y;
|
||||
}
|
||||
|
||||
packet.Rotation = newPlayer.Rotation.ToPacketByte();
|
||||
packet.HeroState = selectedCharacter.State.Convert();
|
||||
packet.AttackSpeed = (ushort)(newPlayer.Attributes?[Stats.AttackSpeed] ?? 0);
|
||||
packet.MagicSpeed = (ushort)(newPlayer.Attributes?[Stats.MagicSpeed] ?? 0);
|
||||
|
||||
appearanceSerializer.WriteAppearanceData(packet.AppearanceAndEffects, newPlayer.AppearanceData, true);
|
||||
|
||||
var effectsStartIndex = appearanceSerializer.NeededSpace;
|
||||
packet.AppearanceAndEffects[effectsStartIndex] = (byte)activeEffects.Length;
|
||||
for (int e = 0; e < activeEffects.Length; ++e)
|
||||
{
|
||||
packet.AppearanceAndEffects[effectsStartIndex + 1 + e] = activeEffects[e];
|
||||
}
|
||||
|
||||
return span.Length;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
245
src/GameServer/RemoteView/World/NewPlayersInScopePlugIn.cs
Normal file
245
src/GameServer/RemoteView/World/NewPlayersInScopePlugIn.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
// <copyright file="NewPlayersInScopePlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.Guild;
|
||||
using MUnique.OpenMU.GameLogic.Views.PlayerShop;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.Character;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="INewPlayersInScopePlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.NewPlayersInScopePlugIn_Name), Description = nameof(PlugInResources.NewPlayersInScopePlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("4cd64537-ae5f-4030-bca1-7fa30ebff6c6")]
|
||||
[MinimumClient(5, 0, ClientLanguage.Invariant)]
|
||||
public class NewPlayersInScopePlugIn : INewPlayersInScopePlugIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewPlayersInScopePlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public NewPlayersInScopePlugIn(RemotePlayer player) => this.Player = player;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player of this view.
|
||||
/// </summary>
|
||||
protected RemotePlayer Player { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask NewPlayersInScopeAsync(IEnumerable<Player> newPlayers, bool isSpawned = true)
|
||||
{
|
||||
if (newPlayers is null || !newPlayers.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var (shopPlayers, guildPlayers) = await this.SendCharactersAsync(newPlayers, isSpawned).ConfigureAwait(false);
|
||||
|
||||
if (shopPlayers != null)
|
||||
{
|
||||
await this.Player.InvokeViewPlugInAsync<IShowShopsOfPlayersPlugIn>(p => p.ShowShopsOfPlayersAsync(shopPlayers)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (guildPlayers != null)
|
||||
{
|
||||
await this.Player.InvokeViewPlugInAsync<IAssignPlayersToGuildPlugIn>(p => p.AssignPlayersToGuildAsync(guildPlayers, true)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends information about a new player which has come into view.
|
||||
/// </summary>
|
||||
/// <param name="newPlayer">The new player.</param>
|
||||
/// <param name="isSpawned">If the player has spawned.</param>
|
||||
/// <returns>A <see cref="ValueTask"/>.</returns>
|
||||
protected virtual async ValueTask SendCharacterAsync(Player newPlayer, bool isSpawned)
|
||||
{
|
||||
var connection = this.Player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedCharacter = newPlayer.SelectedCharacter;
|
||||
if (selectedCharacter is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int Write()
|
||||
{
|
||||
var appearanceSerializer = this.Player.AppearanceSerializer;
|
||||
var activeEffects = newPlayer.MagicEffectList.VisibleEffects;
|
||||
const int estimatedEffectsPerPlayer = 5;
|
||||
var estimatedSizePerCharacter = AddCharactersToScope.CharacterData.GetRequiredSize(Math.Max(estimatedEffectsPerPlayer, activeEffects.Count));
|
||||
var estimatedSize = AddCharactersToScope.GetRequiredSize(1, estimatedSizePerCharacter);
|
||||
|
||||
var span = connection.Output.GetSpan(estimatedSize)[..estimatedSize];
|
||||
var packet = new AddCharactersToScopeRef(span)
|
||||
{
|
||||
CharacterCount = 1,
|
||||
};
|
||||
|
||||
var playerBlock = packet[0];
|
||||
playerBlock.Id = newPlayer.GetId(this.Player);
|
||||
if (isSpawned)
|
||||
{
|
||||
playerBlock.Id |= 0x8000;
|
||||
}
|
||||
|
||||
playerBlock.CurrentPositionX = newPlayer.Position.X;
|
||||
playerBlock.CurrentPositionY = newPlayer.Position.Y;
|
||||
|
||||
appearanceSerializer.WriteAppearanceData(playerBlock.Appearance, newPlayer.AppearanceData, true); // 4 ... 21
|
||||
playerBlock.Name = selectedCharacter.Name;
|
||||
if (newPlayer.IsWalking)
|
||||
{
|
||||
playerBlock.TargetPositionX = newPlayer.WalkTarget.X;
|
||||
playerBlock.TargetPositionY = newPlayer.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
playerBlock.TargetPositionX = newPlayer.Position.X;
|
||||
playerBlock.TargetPositionY = newPlayer.Position.Y;
|
||||
}
|
||||
|
||||
playerBlock.Rotation = newPlayer.Rotation.ToPacketByte();
|
||||
playerBlock.HeroState = selectedCharacter.State.Convert();
|
||||
|
||||
playerBlock.EffectCount = (byte)activeEffects.Count;
|
||||
for (int e = playerBlock.EffectCount - 1; e >= 0; e--)
|
||||
{
|
||||
var effectBlock = playerBlock[e];
|
||||
effectBlock.Id = (byte)activeEffects[e].Id;
|
||||
}
|
||||
|
||||
// The calculation of the final size is not a requirement, but we do it to save some traffic.
|
||||
// The original server also doesn't send more bytes than necessary.
|
||||
var finalSize = packet.FinalSize;
|
||||
span.Slice(0, finalSize).SetPacketSize();
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask<(IList<Player>? ShopPlayers, IList<Player>? GuildPlayers)> SendCharactersAsync(IEnumerable<Player> newPlayers, bool isSpawned)
|
||||
{
|
||||
IList<Player>? shopPlayers = null;
|
||||
IList<Player>? guildPlayers = null;
|
||||
|
||||
var connection = this.Player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return (shopPlayers, guildPlayers);
|
||||
}
|
||||
|
||||
var newPlayerList = newPlayers.ToList();
|
||||
foreach (var newPlayer in newPlayerList)
|
||||
{
|
||||
if (newPlayer.Attributes?[Stats.TransformationSkin] == 0)
|
||||
{
|
||||
await this.SendCharacterAsync(newPlayer, isSpawned).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.SendTransformedCharacterAsync(newPlayer, isSpawned).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (newPlayer.ShopStorage?.StoreOpen ?? false)
|
||||
{
|
||||
(shopPlayers ??= new List<Player>()).Add(newPlayer);
|
||||
}
|
||||
|
||||
if (newPlayer.GuildStatus != null)
|
||||
{
|
||||
(guildPlayers ??= new List<Player>()).Add(newPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
return (shopPlayers, guildPlayers);
|
||||
}
|
||||
|
||||
private async ValueTask SendTransformedCharacterAsync(Player newPlayer, bool isSpawned)
|
||||
{
|
||||
var connection = this.Player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedCharacter = newPlayer.SelectedCharacter;
|
||||
if (selectedCharacter is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int Write()
|
||||
{
|
||||
var appearanceSerializer = this.Player.AppearanceSerializer;
|
||||
var activeEffects = newPlayer.MagicEffectList.VisibleEffects;
|
||||
const int estimatedEffectsPerPlayer = 5;
|
||||
var estimatedSizePerCharacter = AddTransformedCharactersToScopeRef.CharacterDataRef.GetRequiredSize(Math.Max(estimatedEffectsPerPlayer, activeEffects.Count));
|
||||
var estimatedSize = AddTransformedCharactersToScopeRef.GetRequiredSize(1, estimatedSizePerCharacter);
|
||||
var span = connection.Output.GetSpan(estimatedSize)[..estimatedSize];
|
||||
var packet = new AddTransformedCharactersToScopeRef(span)
|
||||
{
|
||||
CharacterCount = 1,
|
||||
};
|
||||
|
||||
var playerBlock = packet[0];
|
||||
playerBlock.Id = newPlayer.GetId(this.Player);
|
||||
if (isSpawned)
|
||||
{
|
||||
playerBlock.Id |= 0x8000;
|
||||
}
|
||||
|
||||
playerBlock.CurrentPositionX = newPlayer.Position.X;
|
||||
playerBlock.CurrentPositionY = newPlayer.Position.Y;
|
||||
|
||||
appearanceSerializer.WriteAppearanceData(playerBlock.Appearance, newPlayer.AppearanceData, true); // 4 ... 21
|
||||
playerBlock.Name = selectedCharacter.Name;
|
||||
if (newPlayer.IsWalking)
|
||||
{
|
||||
playerBlock.TargetPositionX = newPlayer.WalkTarget.X;
|
||||
playerBlock.TargetPositionY = newPlayer.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
playerBlock.TargetPositionX = newPlayer.Position.X;
|
||||
playerBlock.TargetPositionY = newPlayer.Position.Y;
|
||||
}
|
||||
|
||||
playerBlock.Rotation = newPlayer.Rotation.ToPacketByte();
|
||||
playerBlock.HeroState = selectedCharacter.State.Convert();
|
||||
|
||||
playerBlock.EffectCount = (byte)activeEffects.Count;
|
||||
playerBlock.Skin = (ushort)newPlayer.Attributes![Stats.TransformationSkin];
|
||||
for (int e = playerBlock.EffectCount - 1; e >= 0; e--)
|
||||
{
|
||||
var effectBlock = playerBlock[e];
|
||||
effectBlock.Id = (byte)activeEffects[e].Id;
|
||||
}
|
||||
|
||||
// The calculation of the final size is not a requirement, but we do it to save some traffic.
|
||||
// The original server also doesn't send more bytes than necessary.
|
||||
var finalSize = packet.FinalSize;
|
||||
span.Slice(0, finalSize).SetPacketSize();
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
172
src/GameServer/RemoteView/World/NewPlayersInScopePlugIn075.cs
Normal file
172
src/GameServer/RemoteView/World/NewPlayersInScopePlugIn075.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
// <copyright file="NewPlayersInScopePlugIn075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.Guild;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.Character;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The implementation of the <see cref="INewPlayersInScopePlugIn"/> which is forwarding everything to the game client of version 0.75 with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.NewPlayersInScopePlugIn075_Name), Description = nameof(PlugInResources.NewPlayersInScopePlugIn075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("1B68C660-34DD-4733-834D-DEE8DC1517D3")]
|
||||
[MinimumClient(0, 75, ClientLanguage.Invariant)]
|
||||
public class NewPlayersInScopePlugIn075 : INewPlayersInScopePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewPlayersInScopePlugIn075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public NewPlayersInScopePlugIn075(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask NewPlayersInScopeAsync(IEnumerable<Player> newPlayers, bool isSpawned = true)
|
||||
{
|
||||
if (!newPlayers.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<Player>? guildPlayers = null;
|
||||
var playerList = newPlayers.ToList();
|
||||
foreach (var newPlayer in playerList)
|
||||
{
|
||||
if (newPlayer.Attributes?[Stats.TransformationSkin] == 0)
|
||||
{
|
||||
await this.SendAddToScopeMessageAsync(isSpawned, connection, newPlayer).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.SendAddTransformedToScopeMessageAsync(isSpawned, connection, newPlayer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (newPlayer.GuildStatus != null)
|
||||
{
|
||||
(guildPlayers ??= new List<Player>()).Add(newPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
if (guildPlayers != null)
|
||||
{
|
||||
await this._player.InvokeViewPlugInAsync<IAssignPlayersToGuildPlugIn>(p => p.AssignPlayersToGuildAsync(guildPlayers, true)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask SendAddTransformedToScopeMessageAsync(bool isSpawned, IConnection connection, Player transformedPlayer)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var size = AddTransformedCharactersToScope075Ref.GetRequiredSize(1);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new AddTransformedCharactersToScope075Ref(span)
|
||||
{
|
||||
CharacterCount = 1,
|
||||
};
|
||||
|
||||
var playerId = transformedPlayer.GetId(this._player);
|
||||
var playerBlock = packet[0];
|
||||
playerBlock.Id = playerId;
|
||||
if (isSpawned)
|
||||
{
|
||||
playerBlock.Id |= 0x8000;
|
||||
}
|
||||
|
||||
playerBlock.Skin = (byte)(transformedPlayer.Attributes?[Stats.TransformationSkin] ?? 0);
|
||||
|
||||
playerBlock.CurrentPositionX = transformedPlayer.Position.X;
|
||||
playerBlock.CurrentPositionY = transformedPlayer.Position.Y;
|
||||
|
||||
playerBlock.IsPoisoned = transformedPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Poisoned);
|
||||
playerBlock.IsIced = transformedPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Iced);
|
||||
playerBlock.IsDamageBuffed = transformedPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DamageBuff);
|
||||
playerBlock.IsDefenseBuffed = transformedPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DefenseBuff);
|
||||
playerBlock.Name = transformedPlayer.SelectedCharacter!.Name;
|
||||
|
||||
if (transformedPlayer.IsWalking)
|
||||
{
|
||||
playerBlock.TargetPositionX = transformedPlayer.WalkTarget.X;
|
||||
playerBlock.TargetPositionY = transformedPlayer.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
playerBlock.TargetPositionX = transformedPlayer.Position.X;
|
||||
playerBlock.TargetPositionY = transformedPlayer.Position.Y;
|
||||
}
|
||||
|
||||
playerBlock.Rotation = transformedPlayer.Rotation.ToPacketByte();
|
||||
playerBlock.HeroState = transformedPlayer.SelectedCharacter.State.Convert();
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask SendAddToScopeMessageAsync(bool isSpawned, IConnection connection, Player newPlayer)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var appearanceSerializer = this._player.AppearanceSerializer;
|
||||
var size = AddCharactersToScope075Ref.GetRequiredSize(1);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new AddCharactersToScope075Ref(span)
|
||||
{
|
||||
CharacterCount = 1,
|
||||
};
|
||||
|
||||
var playerId = newPlayer.GetId(this._player);
|
||||
var playerBlock = packet[0];
|
||||
playerBlock.Id = playerId;
|
||||
if (isSpawned)
|
||||
{
|
||||
playerBlock.Id |= 0x8000;
|
||||
}
|
||||
|
||||
playerBlock.CurrentPositionX = newPlayer.Position.X;
|
||||
playerBlock.CurrentPositionY = newPlayer.Position.Y;
|
||||
appearanceSerializer.WriteAppearanceData(playerBlock.Appearance, newPlayer.AppearanceData, true); // 4 ... 12
|
||||
|
||||
playerBlock.IsPoisoned = newPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Poisoned);
|
||||
playerBlock.IsIced = newPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Iced);
|
||||
playerBlock.IsDamageBuffed = newPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DamageBuff);
|
||||
playerBlock.IsDefenseBuffed = newPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DefenseBuff);
|
||||
playerBlock.Name = newPlayer.SelectedCharacter!.Name;
|
||||
|
||||
if (newPlayer.IsWalking)
|
||||
{
|
||||
playerBlock.TargetPositionX = newPlayer.WalkTarget.X;
|
||||
playerBlock.TargetPositionY = newPlayer.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
playerBlock.TargetPositionX = newPlayer.Position.X;
|
||||
playerBlock.TargetPositionY = newPlayer.Position.Y;
|
||||
}
|
||||
|
||||
playerBlock.Rotation = newPlayer.Rotation.ToPacketByte();
|
||||
playerBlock.HeroState = newPlayer.SelectedCharacter.State.Convert();
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
172
src/GameServer/RemoteView/World/NewPlayersInScopePlugIn095.cs
Normal file
172
src/GameServer/RemoteView/World/NewPlayersInScopePlugIn095.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
// <copyright file="NewPlayersInScopePlugIn095.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.Guild;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.Character;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The implementation of the <see cref="INewPlayersInScopePlugIn"/> which is forwarding everything to the game client of version 0.75 with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.NewPlayersInScopePlugIn095_Name), Description = nameof(PlugInResources.NewPlayersInScopePlugIn095_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("400ACDFB-7A75-4339-802C-758178DF8305")]
|
||||
[MinimumClient(0, 95, ClientLanguage.Invariant)]
|
||||
public class NewPlayersInScopePlugIn095 : INewPlayersInScopePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewPlayersInScopePlugIn095"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public NewPlayersInScopePlugIn095(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask NewPlayersInScopeAsync(IEnumerable<Player> newPlayers, bool isSpawned = true)
|
||||
{
|
||||
if (!newPlayers.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<Player>? guildPlayers = null;
|
||||
var playerList = newPlayers.ToList();
|
||||
foreach (var newPlayer in playerList)
|
||||
{
|
||||
if (newPlayer.Attributes?[Stats.TransformationSkin] == 0)
|
||||
{
|
||||
await this.SendAddToScopeMessageAsync(isSpawned, connection, newPlayer).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.SendAddTransformedToScopeMessageAsync(isSpawned, connection, newPlayer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (newPlayer.GuildStatus != null)
|
||||
{
|
||||
(guildPlayers ??= new List<Player>()).Add(newPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
if (guildPlayers != null)
|
||||
{
|
||||
await this._player.InvokeViewPlugInAsync<IAssignPlayersToGuildPlugIn>(p => p.AssignPlayersToGuildAsync(guildPlayers, true)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask SendAddTransformedToScopeMessageAsync(bool isSpawned, IConnection connection, Player transformedPlayer)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var size = AddTransformedCharactersToScope075Ref.GetRequiredSize(1);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new AddTransformedCharactersToScope075Ref(span)
|
||||
{
|
||||
CharacterCount = 1,
|
||||
};
|
||||
|
||||
var playerId = transformedPlayer.GetId(this._player);
|
||||
var playerBlock = packet[0];
|
||||
playerBlock.Id = playerId;
|
||||
if (isSpawned)
|
||||
{
|
||||
playerBlock.Id |= 0x8000;
|
||||
}
|
||||
|
||||
playerBlock.Skin = (byte)(transformedPlayer.Attributes?[Stats.TransformationSkin] ?? 0);
|
||||
|
||||
playerBlock.CurrentPositionX = transformedPlayer.Position.X;
|
||||
playerBlock.CurrentPositionY = transformedPlayer.Position.Y;
|
||||
|
||||
playerBlock.IsPoisoned = transformedPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Poisoned);
|
||||
playerBlock.IsIced = transformedPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Iced);
|
||||
playerBlock.IsDamageBuffed = transformedPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DamageBuff);
|
||||
playerBlock.IsDefenseBuffed = transformedPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DefenseBuff);
|
||||
playerBlock.Name = transformedPlayer.SelectedCharacter!.Name;
|
||||
|
||||
if (transformedPlayer.IsWalking)
|
||||
{
|
||||
playerBlock.TargetPositionX = transformedPlayer.WalkTarget.X;
|
||||
playerBlock.TargetPositionY = transformedPlayer.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
playerBlock.TargetPositionX = transformedPlayer.Position.X;
|
||||
playerBlock.TargetPositionY = transformedPlayer.Position.Y;
|
||||
}
|
||||
|
||||
playerBlock.Rotation = transformedPlayer.Rotation.ToPacketByte();
|
||||
playerBlock.HeroState = transformedPlayer.SelectedCharacter.State.Convert();
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask SendAddToScopeMessageAsync(bool isSpawned, IConnection connection, Player newPlayer)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var appearanceSerializer = this._player.AppearanceSerializer;
|
||||
var size = AddCharactersToScope095Ref.GetRequiredSize(1);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new AddCharactersToScope095Ref(span)
|
||||
{
|
||||
CharacterCount = 1,
|
||||
};
|
||||
|
||||
var playerId = newPlayer.GetId(this._player);
|
||||
var playerBlock = packet[0];
|
||||
playerBlock.Id = playerId;
|
||||
if (isSpawned || newPlayer == this._player)
|
||||
{
|
||||
playerBlock.Id |= 0x8000;
|
||||
}
|
||||
|
||||
playerBlock.CurrentPositionX = newPlayer.Position.X;
|
||||
playerBlock.CurrentPositionY = newPlayer.Position.Y;
|
||||
appearanceSerializer.WriteAppearanceData(playerBlock.Appearance, newPlayer.AppearanceData, true); // 4 ... 12
|
||||
|
||||
playerBlock.IsPoisoned = newPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Poisoned);
|
||||
playerBlock.IsIced = newPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.Iced);
|
||||
playerBlock.IsDamageBuffed = newPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DamageBuff);
|
||||
playerBlock.IsDefenseBuffed = newPlayer.MagicEffectList.ActiveEffects.ContainsKey(EffectNumbers.DefenseBuff);
|
||||
playerBlock.Name = newPlayer.SelectedCharacter!.Name;
|
||||
|
||||
if (newPlayer.IsWalking)
|
||||
{
|
||||
playerBlock.TargetPositionX = newPlayer.WalkTarget.X;
|
||||
playerBlock.TargetPositionY = newPlayer.WalkTarget.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
playerBlock.TargetPositionX = newPlayer.Position.X;
|
||||
playerBlock.TargetPositionY = newPlayer.Position.Y;
|
||||
}
|
||||
|
||||
playerBlock.Rotation = newPlayer.Rotation.ToPacketByte();
|
||||
playerBlock.HeroState = newPlayer.SelectedCharacter.State.Convert();
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
51
src/GameServer/RemoteView/World/ObjectGotKilledPlugIn.cs
Normal file
51
src/GameServer/RemoteView/World/ObjectGotKilledPlugIn.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
// <copyright file="ObjectGotKilledPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Properties;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
using PlugInResources = MUnique.OpenMU.GameServer.Properties.PlugInResources;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IObjectGotKilledPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ObjectGotKilledPlugIn_Name), Description = nameof(PlugInResources.ObjectGotKilledPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("fbe6666e-4425-4f33-b7c7-fc9b5fa36430")]
|
||||
public class ObjectGotKilledPlugIn : IObjectGotKilledPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectGotKilledPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ObjectGotKilledPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ObjectGotKilledAsync(IAttackable killed, IAttacker? killer)
|
||||
{
|
||||
if (this._player.Connection is not { } connection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var killedId = killed.GetId(this._player);
|
||||
var killerId = killer.GetId(this._player);
|
||||
var skillId = killed.LastDeath?.SkillNumber.ToUnsigned() ?? 0;
|
||||
var isCombo = killed.LastDeath?.FinalHit.Attributes.HasFlag(DamageAttributes.Combo) ?? false;
|
||||
skillId = isCombo ? ShowSkillAnimationPlugIn.ComboSkillId : skillId;
|
||||
await connection.SendObjectGotKilledAsync(killedId, skillId, killerId).ConfigureAwait(false);
|
||||
if (this._player == killed && killer is Player killerPlayer && this._player.DuelRoom is null)
|
||||
{
|
||||
await this._player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.YouGotKilledBy), killerPlayer.Name).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
228
src/GameServer/RemoteView/World/ObjectMovedPlugIn.cs
Normal file
228
src/GameServer/RemoteView/World/ObjectMovedPlugIn.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
// <copyright file="ObjectMovedPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IObjectMovedPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ObjectMovedPlugIn_Name), Description = nameof(PlugInResources.ObjectMovedPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("29ee689f-636c-47e7-a930-b60ce8e8993c")]
|
||||
[MinimumClient(1, 0, ClientLanguage.Invariant)]
|
||||
public class ObjectMovedPlugIn : IObjectMovedPlugIn
|
||||
{
|
||||
private const short TeleportTargetNumber = 0x0F;
|
||||
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectMovedPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ObjectMovedPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the directions provided by <see cref="ISupportWalk.GetDirectionsAsync"/> should be send when an object moved.
|
||||
/// This is usually not required, because the game client calculates a proper path anyway and doesn't use the suggested path.
|
||||
/// </summary>
|
||||
public bool SendWalkDirections { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ObjectMovedAsync(ILocateable obj, MoveType type)
|
||||
{
|
||||
if (this._player.Connection is not { } connection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var objectId = obj.GetId(this._player);
|
||||
switch (type)
|
||||
{
|
||||
case MoveType.Instant:
|
||||
await connection.SendObjectMovedAsync(this.GetInstantMoveCode(), objectId, obj.Position.X, obj.Position.Y).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case MoveType.Teleport when obj is Player movedPlayer && movedPlayer != this._player:
|
||||
await this._player.InvokeViewPlugInAsync<INewPlayersInScopePlugIn>(p => p.NewPlayersInScopeAsync(movedPlayer.GetAsEnumerable(), false)).ConfigureAwait(false);
|
||||
await this._player.InvokeViewPlugInAsync<IShowSkillAnimationPlugIn>(p => p.ShowSkillAnimationAsync(movedPlayer, movedPlayer, TeleportTargetNumber, true)).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case MoveType.Teleport when obj is NonPlayerCharacter movedNpc:
|
||||
await this._player.InvokeViewPlugInAsync<INewNpcsInScopePlugIn>(p => p.NewNpcsInScopeAsync(movedNpc.GetAsEnumerable(), false)).ConfigureAwait(false);
|
||||
if (movedNpc is Monster monster)
|
||||
{
|
||||
await this._player.InvokeViewPlugInAsync<IShowSkillAnimationPlugIn>(p => p.ShowSkillAnimationAsync(monster, monster, TeleportTargetNumber, true)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MoveType.Teleport:
|
||||
// no other types available
|
||||
break;
|
||||
|
||||
case MoveType.Walk:
|
||||
await this.ObjectWalkedAsync(obj).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the network message.
|
||||
/// </summary>
|
||||
/// <param name="connection">The connection.</param>
|
||||
/// <param name="objectId">The object identifier.</param>
|
||||
/// <param name="sourcePoint">The origin point.</param>
|
||||
/// <param name="targetPoint">The target point.</param>
|
||||
/// <param name="steps">The steps.</param>
|
||||
/// <param name="rotation">The rotation.</param>
|
||||
/// <param name="stepsLength">Length of the steps.</param>
|
||||
protected virtual async ValueTask SendWalkAsync(IConnection connection, ushort objectId, Point sourcePoint, Point targetPoint, Memory<Direction> steps, Direction rotation, int stepsLength)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var stepsSize = stepsLength == 0 ? 0 : (stepsLength / 2) + 2;
|
||||
var size = ObjectWalkedRef.GetRequiredSize(stepsSize);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
|
||||
var walkPacket = new ObjectWalkedRef(span)
|
||||
{
|
||||
HeaderCode = this.GetWalkCode(),
|
||||
ObjectId = objectId,
|
||||
TargetX = targetPoint.X,
|
||||
TargetY = targetPoint.Y,
|
||||
TargetRotation = rotation.ToPacketByte(),
|
||||
StepCount = (byte)stepsLength,
|
||||
};
|
||||
|
||||
this.SetStepData(walkPacket, steps.Span[..stepsLength], stepsSize);
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the walk code for the current client version.
|
||||
/// </summary>
|
||||
/// <returns>The walk code.</returns>
|
||||
protected byte GetWalkCode()
|
||||
{
|
||||
if (this._player.ClientVersion.Season == 0)
|
||||
{
|
||||
return 0x10;
|
||||
}
|
||||
|
||||
switch (this._player.ClientVersion.Language)
|
||||
{
|
||||
case ClientLanguage.English: return 0xD4;
|
||||
case ClientLanguage.Japanese: return 0x1D;
|
||||
case ClientLanguage.Chinese:
|
||||
case ClientLanguage.Vietnamese:
|
||||
return 0xD9;
|
||||
case ClientLanguage.Filipino: return 0xDD;
|
||||
case ClientLanguage.Korean: return 0xD3;
|
||||
case ClientLanguage.Thai: return 0xD7;
|
||||
default:
|
||||
return (byte)PacketType.Walk;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask ObjectWalkedAsync(ILocateable obj)
|
||||
{
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var objectId = obj.GetId(this._player);
|
||||
|
||||
using var rentArray = this.SendWalkDirections ? MemoryPool<Direction>.Shared.Rent(16) : null;
|
||||
var steps = rentArray?.Memory.Slice(0, 16) ?? Memory<Direction>.Empty;
|
||||
|
||||
var stepsLength = 0;
|
||||
Point targetPoint;
|
||||
var rotation = Direction.Undefined;
|
||||
if (obj is IRotatable rotatable)
|
||||
{
|
||||
rotation = rotatable.Rotation;
|
||||
}
|
||||
|
||||
if (obj is ISupportWalk supportWalk)
|
||||
{
|
||||
if (this.SendWalkDirections)
|
||||
{
|
||||
stepsLength = await supportWalk.GetDirectionsAsync(steps).ConfigureAwait(false);
|
||||
if (stepsLength > 0)
|
||||
{
|
||||
// The last one is the rotation
|
||||
rotation = steps.Span[stepsLength - 1];
|
||||
steps = steps[..(stepsLength - 1)];
|
||||
stepsLength--;
|
||||
}
|
||||
}
|
||||
|
||||
targetPoint = supportWalk.WalkTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPoint = obj.Position;
|
||||
}
|
||||
|
||||
await this.SendWalkAsync(connection, objectId, obj.Position, targetPoint, steps, rotation, stepsLength).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void SetStepData(ObjectWalkedRef walkPacket, Span<Direction> steps, int stepsSize)
|
||||
{
|
||||
if (steps == default || walkPacket.StepCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
walkPacket.StepData[0] = (byte)(steps[0].ToPacketByte() << 4 | stepsSize);
|
||||
for (int i = 0; i < stepsSize - 1; i += 2)
|
||||
{
|
||||
var index = 1 + (i / 2);
|
||||
var firstStep = steps[i].ToPacketByte();
|
||||
var secondStep = steps.Length > i + 1 ? steps[i + 1].ToPacketByte() : 0;
|
||||
walkPacket.StepData[index] = (byte)(firstStep << 4 | secondStep);
|
||||
}
|
||||
}
|
||||
|
||||
private byte GetInstantMoveCode()
|
||||
{
|
||||
if (this._player.ClientVersion.Season == 0)
|
||||
{
|
||||
return 0x11;
|
||||
}
|
||||
|
||||
switch (this._player.ClientVersion.Language)
|
||||
{
|
||||
case ClientLanguage.Japanese: return 0xDC;
|
||||
case ClientLanguage.English:
|
||||
case ClientLanguage.Vietnamese:
|
||||
return 0x15;
|
||||
case ClientLanguage.Filipino: return 0xD6;
|
||||
case ClientLanguage.Chinese:
|
||||
case ClientLanguage.Korean: return 0xD7;
|
||||
case ClientLanguage.Thai: return 0xD9;
|
||||
default:
|
||||
return (byte)PacketType.Teleport;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/GameServer/RemoteView/World/ObjectMovedPlugIn075.cs
Normal file
37
src/GameServer/RemoteView/World/ObjectMovedPlugIn075.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// <copyright file="ObjectMovedPlugIn075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The implementation of the <see cref="IObjectMovedPlugIn"/> for version 0.75 which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ObjectMovedPlugIn075_Name), Description = nameof(PlugInResources.ObjectMovedPlugIn075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("3B387A61-F9E2-4866-BBD6-F236582E350A")]
|
||||
public class ObjectMovedPlugIn075 : ObjectMovedPlugIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectMovedPlugIn075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ObjectMovedPlugIn075(RemotePlayer player)
|
||||
: base(player)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ValueTask SendWalkAsync(IConnection connection, ushort objectId, Point sourcePoint, Point targetPoint, Memory<Direction> steps, Direction rotation, int stepsLength)
|
||||
{
|
||||
return connection.SendObjectWalked075Async(objectId, targetPoint.X, targetPoint.Y, rotation.ToPacketByte());
|
||||
}
|
||||
}
|
||||
82
src/GameServer/RemoteView/World/ObjectMovedPlugInExtended.cs
Normal file
82
src/GameServer/RemoteView/World/ObjectMovedPlugInExtended.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
// <copyright file="ObjectMovedPlugInExtended.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IObjectMovedPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ObjectMovedPlugInExtended_Name), Description = nameof(PlugInResources.ObjectMovedPlugInExtended_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("a56b7400-e51d-4fd6-930b-479c14673719")]
|
||||
[MinimumClient(106, 3, ClientLanguage.Invariant)]
|
||||
public class ObjectMovedPlugInExtended : ObjectMovedPlugIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectMovedPlugInExtended"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ObjectMovedPlugInExtended(RemotePlayer player)
|
||||
: base(player)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async ValueTask SendWalkAsync(IConnection connection, ushort objectId, Point sourcePoint, Point targetPoint, Memory<Direction> steps, Direction rotation, int stepsLength)
|
||||
{
|
||||
int Write()
|
||||
{
|
||||
var stepsSize = stepsLength == 0 ? 0 : (stepsLength / 2) + 2;
|
||||
var size = ObjectWalkedExtended.GetRequiredSize(stepsSize);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
|
||||
var walkPacket = new ObjectWalkedExtendedRef(span)
|
||||
{
|
||||
HeaderCode = this.GetWalkCode(),
|
||||
ObjectId = objectId,
|
||||
SourceX = sourcePoint.X,
|
||||
SourceY = sourcePoint.Y,
|
||||
TargetX = targetPoint.X,
|
||||
TargetY = targetPoint.Y,
|
||||
TargetRotation = rotation.ToPacketByte(),
|
||||
StepCount = (byte)stepsLength,
|
||||
};
|
||||
|
||||
this.SetStepData(walkPacket, steps.Span[..stepsLength], stepsSize);
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void SetStepData(ObjectWalkedExtendedRef walkPacket, Span<Direction> steps, int stepsSize)
|
||||
{
|
||||
if (steps == default || walkPacket.StepCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
walkPacket.StepData[0] = (byte)(steps[0].ToPacketByte() << 4 | stepsSize);
|
||||
for (int i = 0; i < stepsSize - 1; i += 2)
|
||||
{
|
||||
var index = 1 + (i / 2);
|
||||
var firstStep = steps[i].ToPacketByte();
|
||||
var secondStep = steps.Length > i + 1 ? steps[i + 1].ToPacketByte() : 0;
|
||||
walkPacket.StepData[index] = (byte)(firstStep << 4 | secondStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/GameServer/RemoteView/World/ObjectsOutOfScopePlugIn.cs
Normal file
63
src/GameServer/RemoteView/World/ObjectsOutOfScopePlugIn.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
// <copyright file="ObjectsOutOfScopePlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IObjectsOutOfScopePlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ObjectsOutOfScopePlugIn_Name), Description = nameof(PlugInResources.ObjectsOutOfScopePlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("88cea9ce-c186-4fd0-b6cc-6466d8a7531c")]
|
||||
public class ObjectsOutOfScopePlugIn : IObjectsOutOfScopePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectsOutOfScopePlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ObjectsOutOfScopePlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ObjectsOutOfScopeAsync(IEnumerable<IIdentifiable> objects)
|
||||
{
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var count = objects.Count();
|
||||
int Write()
|
||||
{
|
||||
var size = MapObjectOutOfScopeRef.GetRequiredSize(count);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new MapObjectOutOfScopeRef(span)
|
||||
{
|
||||
ObjectCount = (byte)count,
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
foreach (var m in objects)
|
||||
{
|
||||
var objectId = packet[i];
|
||||
objectId.Id = m.GetId(this._player);
|
||||
i++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// <copyright file="RespawnAfterDeathExtendedPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IRespawnAfterDeathPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.RespawnAfterDeathExtendedPlugIn_Name), Description = nameof(PlugInResources.RespawnAfterDeathExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("91C636A7-6D92-4AC0-BAD5-859F60E5345F")]
|
||||
[MinimumClient(106, 0, ClientLanguage.Invariant)]
|
||||
public class RespawnAfterDeathExtendedPlugIn : IRespawnAfterDeathPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RespawnAfterDeathExtendedPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public RespawnAfterDeathExtendedPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask RespawnAsync()
|
||||
{
|
||||
if (this._player.SelectedCharacter?.CurrentMap is null || this._player.Attributes is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapNumber = (byte)this._player.SelectedCharacter.CurrentMap.Number;
|
||||
var position = this._player.IsWalking ? this._player.WalkTarget : this._player.Position;
|
||||
var isMaster = this._player.SelectedCharacter.CharacterClass?.IsMasterClass is true;
|
||||
|
||||
await this._player.Connection.SendRespawnAfterDeathExtendedAsync(
|
||||
position.X,
|
||||
position.Y,
|
||||
mapNumber,
|
||||
this._player.Rotation.ToPacketByte(),
|
||||
(uint)this._player.Attributes[Stats.CurrentHealth],
|
||||
(uint)this._player.Attributes[Stats.CurrentMana],
|
||||
(uint)this._player.Attributes[Stats.CurrentShield],
|
||||
(uint)this._player.Attributes[Stats.CurrentAbility],
|
||||
(ulong)(isMaster ? this._player.SelectedCharacter.MasterExperience : this._player.SelectedCharacter.Experience),
|
||||
(uint)this._player.Money)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
55
src/GameServer/RemoteView/World/RespawnAfterDeathPlugIn.cs
Normal file
55
src/GameServer/RemoteView/World/RespawnAfterDeathPlugIn.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
// <copyright file="RespawnAfterDeathPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IRespawnAfterDeathPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.RespawnAfterDeathPlugIn_Name), Description = nameof(PlugInResources.RespawnAfterDeathPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("71CE460D-1025-45A2-94B7-46EC651A2664")]
|
||||
[MinimumClient(2, 0, ClientLanguage.Invariant)]
|
||||
public class RespawnAfterDeathPlugIn : IRespawnAfterDeathPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RespawnAfterDeathPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public RespawnAfterDeathPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask RespawnAsync()
|
||||
{
|
||||
if (this._player.SelectedCharacter?.CurrentMap is null || this._player.Attributes is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapNumber = (byte)this._player.SelectedCharacter.CurrentMap.Number;
|
||||
var position = this._player.IsWalking ? this._player.WalkTarget : this._player.Position;
|
||||
var isMaster = this._player.SelectedCharacter.CharacterClass?.IsMasterClass is true;
|
||||
await this._player.Connection.SendRespawnAfterDeathAsync(
|
||||
position.X,
|
||||
position.Y,
|
||||
mapNumber,
|
||||
this._player.Rotation.ToPacketByte(),
|
||||
(ushort)this._player.Attributes[Stats.CurrentHealth],
|
||||
(ushort)this._player.Attributes[Stats.CurrentMana],
|
||||
(ushort)this._player.Attributes[Stats.CurrentShield],
|
||||
(ushort)this._player.Attributes[Stats.CurrentAbility],
|
||||
(ulong)(isMaster ? this._player.SelectedCharacter.MasterExperience : this._player.SelectedCharacter.Experience),
|
||||
(uint)this._player.Money)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// <copyright file="RespawnAfterDeathPlugIn075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IRespawnAfterDeathPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.RespawnAfterDeathPlugIn075_Name), Description = nameof(PlugInResources.RespawnAfterDeathPlugIn075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("FE2D99D4-CA19-4E94-BDF1-B51B463AD28A")]
|
||||
[MaximumClient(0, 89, ClientLanguage.Invariant)]
|
||||
public class RespawnAfterDeathPlugIn075 : IRespawnAfterDeathPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RespawnAfterDeathPlugIn075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public RespawnAfterDeathPlugIn075(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask RespawnAsync()
|
||||
{
|
||||
if (this._player.SelectedCharacter?.CurrentMap is null || this._player.Attributes is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapNumber = (byte)this._player.SelectedCharacter.CurrentMap.Number;
|
||||
var position = this._player.IsWalking ? this._player.WalkTarget : this._player.Position;
|
||||
|
||||
await this._player.Connection.SendRespawnAfterDeath075Async(
|
||||
position.X,
|
||||
position.Y,
|
||||
mapNumber,
|
||||
this._player.Rotation.ToPacketByte(),
|
||||
(ushort)this._player.Attributes[Stats.CurrentHealth],
|
||||
(ushort)this._player.Attributes[Stats.CurrentMana],
|
||||
(uint)this._player.SelectedCharacter.Experience,
|
||||
(uint)this._player.Money)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// <copyright file="RespawnAfterDeathPlugIn095.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IRespawnAfterDeathPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.RespawnAfterDeathPlugIn095_Name), Description = nameof(PlugInResources.RespawnAfterDeathPlugIn095_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("02CBC7FF-73F1-4240-9859-AA1F6656E02C")]
|
||||
[MinimumClient(0, 90, ClientLanguage.Invariant)]
|
||||
public class RespawnAfterDeathPlugIn095 : IRespawnAfterDeathPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RespawnAfterDeathPlugIn095"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public RespawnAfterDeathPlugIn095(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask RespawnAsync()
|
||||
{
|
||||
if (this._player.SelectedCharacter?.CurrentMap is null || this._player.Attributes is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapNumber = (byte)this._player.SelectedCharacter.CurrentMap.Number;
|
||||
var position = this._player.IsWalking ? this._player.WalkTarget : this._player.Position;
|
||||
|
||||
await this._player.Connection.SendRespawnAfterDeath095Async(
|
||||
position.X,
|
||||
position.Y,
|
||||
mapNumber,
|
||||
this._player.Rotation.ToPacketByte(),
|
||||
(ushort)this._player.Attributes[Stats.CurrentHealth],
|
||||
(ushort)this._player.Attributes[Stats.CurrentMana],
|
||||
(ushort)this._player.Attributes[Stats.CurrentAbility],
|
||||
(uint)this._player.SelectedCharacter.Experience,
|
||||
(uint)this._player.Money)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
55
src/GameServer/RemoteView/World/ShowAnimationPlugIn.cs
Normal file
55
src/GameServer/RemoteView/World/ShowAnimationPlugIn.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
// <copyright file="ShowAnimationPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowAnimationPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowAnimationPlugIn_Name), Description = nameof(PlugInResources.ShowAnimationPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("d89cbf82-5ac1-423b-a478-f792136fce3c")]
|
||||
[MinimumClient(0, 90, ClientLanguage.Invariant)]
|
||||
public class ShowAnimationPlugIn : IShowAnimationPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowAnimationPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowAnimationPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the monster attack animation id.
|
||||
/// </summary>
|
||||
protected virtual byte MonsterAttackAnimation => 120;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// This Packet is sent to the Server when an Object does an animation, including attacking other players.
|
||||
/// It will create the animation at the client side.
|
||||
/// </remarks>
|
||||
public async ValueTask ShowAnimationAsync(IIdentifiable animatingObj, byte animation, IIdentifiable? targetObj, Direction direction)
|
||||
{
|
||||
var animatingId = animatingObj.GetId(this._player);
|
||||
var targetId = targetObj?.GetId(this._player) ?? 0;
|
||||
await this._player.Connection.SendObjectAnimationAsync(animatingId, direction.ToPacketByte(), animation, targetId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask ShowMonsterAttackAnimationAsync(IIdentifiable animatingObj, IIdentifiable? targetObj, Direction direction)
|
||||
{
|
||||
return this.ShowAnimationAsync(animatingObj, this.MonsterAttackAnimation, targetObj, direction);
|
||||
}
|
||||
}
|
||||
34
src/GameServer/RemoteView/World/ShowAnimationPlugIn075.cs
Normal file
34
src/GameServer/RemoteView/World/ShowAnimationPlugIn075.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// <copyright file="ShowAnimationPlugIn075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowAnimationPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowAnimationPlugIn075_Name), Description = nameof(PlugInResources.ShowAnimationPlugIn075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("AF89AC54-B902-490E-987F-3ED87145A884")]
|
||||
[MaximumClient(0, 89, ClientLanguage.Invariant)]
|
||||
public class ShowAnimationPlugIn075 : ShowAnimationPlugIn, IShowAnimationPlugIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowAnimationPlugIn075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowAnimationPlugIn075(RemotePlayer player)
|
||||
: base(player)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the monster attack animation id.
|
||||
/// </summary>
|
||||
protected override byte MonsterAttackAnimation => 100;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// <copyright file="ShowAreaSkillAnimationPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowAreaSkillAnimationPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowAreaSkillAnimationPlugIn_Name), Description = nameof(PlugInResources.ShowAreaSkillAnimationPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("4cc09cdd-55a3-4191-94fc-b8e684b87cac")]
|
||||
[MinimumClient(3, 0, ClientLanguage.Invariant)]
|
||||
public class ShowAreaSkillAnimationPlugIn : IShowAreaSkillAnimationPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowAreaSkillAnimationPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowAreaSkillAnimationPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowAreaSkillAnimationAsync(Player playerWhichPerformsSkill, Skill skill, Point point, byte rotation)
|
||||
{
|
||||
var skillId = NumberConversionExtensions.ToUnsigned(skill.Number);
|
||||
var playerId = playerWhichPerformsSkill.GetId(this._player);
|
||||
await this._player.Connection.SendAreaSkillAnimationAsync(skillId, playerId, point.X, point.Y, rotation).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// <copyright file="ShowAreaSkillAnimationPlugIn075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowAreaSkillAnimationPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowAreaSkillAnimationPlugIn075_Name), Description = nameof(PlugInResources.ShowAreaSkillAnimationPlugIn075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("DB239A54-A5BD-4796-A96C-50D247D288F3")]
|
||||
public class ShowAreaSkillAnimationPlugIn075 : IShowAreaSkillAnimationPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowAreaSkillAnimationPlugIn075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowAreaSkillAnimationPlugIn075(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowAreaSkillAnimationAsync(Player playerWhichPerformsSkill, Skill skill, Point point, byte rotation)
|
||||
{
|
||||
var skillId = (byte)skill.Number;
|
||||
var playerId = playerWhichPerformsSkill.GetId(this._player);
|
||||
await this._player.Connection.SendAreaSkillAnimation075Async(skillId, playerId, point.X, point.Y, rotation).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// <copyright file="ShowAreaSkillAnimationPlugIn095.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowAreaSkillAnimationPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowAreaSkillAnimationPlugIn095_Name), Description = nameof(PlugInResources.ShowAreaSkillAnimationPlugIn095_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("553D9388-3648-4029-959E-F6D74399D51E")]
|
||||
[MinimumClient(0, 95, ClientLanguage.Invariant)]
|
||||
public class ShowAreaSkillAnimationPlugIn095 : IShowAreaSkillAnimationPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowAreaSkillAnimationPlugIn095"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowAreaSkillAnimationPlugIn095(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowAreaSkillAnimationAsync(Player playerWhichPerformsSkill, Skill skill, Point point, byte rotation)
|
||||
{
|
||||
var skillId = (byte)skill.Number;
|
||||
var playerId = playerWhichPerformsSkill.GetId(this._player);
|
||||
await this._player.Connection.SendAreaSkillAnimation095Async(skillId, playerId, point.X, point.Y, rotation).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
63
src/GameServer/RemoteView/World/ShowChainLightningPlugIn.cs
Normal file
63
src/GameServer/RemoteView/World/ShowChainLightningPlugIn.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
// <copyright file="ShowChainLightningPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowChainLightningPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowChainLightningPlugIn_Name), Description = nameof(PlugInResources.ShowChainLightningPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("8A78EE23-7AD5-4D08-BE17-B0F6B0CB7309")]
|
||||
[MinimumClient(4, 0, ClientLanguage.Invariant)]
|
||||
public class ShowChainLightningPlugIn : IShowChainLightningPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowChainLightningPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowChainLightningPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowLightningChainAnimationAsync(IAttacker attacker, Skill skill, IReadOnlyCollection<IAttackable> targets)
|
||||
{
|
||||
if (this._player?.Connection is not { Connected: true } connection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int WritePacket()
|
||||
{
|
||||
var length = ChainLightningHitInfoRef.GetRequiredSize(targets.Count);
|
||||
var packet = new ChainLightningHitInfoRef(connection.Output.GetSpan(length)[..length]);
|
||||
packet.SkillNumber = (ushort)skill.Number;
|
||||
packet.TargetCount = (byte)targets.Count;
|
||||
packet.PlayerId = attacker.GetId(this._player);
|
||||
|
||||
var i = 0;
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var objectId = packet[i];
|
||||
objectId.TargetId = target.GetId(this._player);
|
||||
i++;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
await connection.SendAsync(WritePacket).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
76
src/GameServer/RemoteView/World/ShowDroppedItemsPlugIn.cs
Normal file
76
src/GameServer/RemoteView/World/ShowDroppedItemsPlugIn.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
// <copyright file="ShowDroppedItemsPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowDroppedItemsPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowDroppedItemsPlugIn_Name), Description = nameof(PlugInResources.ShowDroppedItemsPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("f89308c3-5fe7-46e2-adfc-85a56ba23233")]
|
||||
public class ShowDroppedItemsPlugIn : IShowDroppedItemsPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowDroppedItemsPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowDroppedItemsPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowDroppedItemsAsync(IEnumerable<DroppedItem> droppedItems, bool freshDrops)
|
||||
{
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int itemCount = droppedItems.Count();
|
||||
int Write()
|
||||
{
|
||||
var itemSerializer = this._player.ItemSerializer;
|
||||
var droppedItemLength = ItemsDroppedRef.DroppedItemRef.GetRequiredSize(itemSerializer.NeededSpace);
|
||||
var size = ItemsDroppedRef.GetRequiredSize(itemCount, droppedItemLength);
|
||||
var span = connection.Output.GetSpan(size)[..size];
|
||||
var packet = new ItemsDroppedRef(span)
|
||||
{
|
||||
ItemCount = (byte)itemCount,
|
||||
};
|
||||
|
||||
int headerSize = ItemsDroppedRef.GetRequiredSize(0, 0);
|
||||
int actualSize = headerSize;
|
||||
int i = 0;
|
||||
foreach (var item in droppedItems)
|
||||
{
|
||||
var itemBlock = new ItemsDroppedRef.DroppedItemRef(span[actualSize..]);
|
||||
itemBlock.Id = item.Id;
|
||||
if (freshDrops)
|
||||
{
|
||||
itemBlock.IsFreshDrop = true;
|
||||
}
|
||||
|
||||
itemBlock.PositionX = item.Position.X;
|
||||
itemBlock.PositionY = item.Position.Y;
|
||||
var itemSize = itemSerializer.SerializeItem(itemBlock.ItemData, item.Item);
|
||||
actualSize += ItemsDroppedRef.DroppedItemRef.GetRequiredSize(itemSize);
|
||||
i++;
|
||||
}
|
||||
|
||||
span.Slice(0, actualSize).SetPacketSize();
|
||||
return actualSize;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
114
src/GameServer/RemoteView/World/ShowHitExtendedPlugIn.cs
Normal file
114
src/GameServer/RemoteView/World/ShowHitExtendedPlugIn.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
// <copyright file="ShowHitExtendedPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.AttributeSystem;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The extended implementation of the <see cref="IShowHitPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowHitExtendedPlugIn_Name), Description = nameof(PlugInResources.ShowHitExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("E79C8065-21A8-4774-B84F-5B8658F6A820")]
|
||||
[MinimumClient(106, 3, ClientLanguage.English)]
|
||||
public class ShowHitExtendedPlugIn : IShowHitPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowHitExtendedPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowHitExtendedPlugIn(RemotePlayer player)
|
||||
{
|
||||
this._player = player;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// This Packet is sent to the Client when a Player or Monster got hit and damaged.
|
||||
/// It includes which Player/Monster got hit by who, and the Damage Type.
|
||||
/// It is obvious that the mu online protocol only supports 16 bits for each damage value. To prevent bugs (own player health)
|
||||
/// and to make it somehow visible that the damage exceeds 65k, we send more than one packet, if the 16bits are not enough.
|
||||
/// </remarks>
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowHitAsync(IAttackable target, HitInfo hitInfo)
|
||||
{
|
||||
if (this._player.Connection is not { } connection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var healthStatus = this.CalcStatStatus(target, Stats.CurrentHealth, Stats.MaximumHealth);
|
||||
var shieldStatus = this.CalcStatStatus(target, Stats.CurrentShield, Stats.MaximumShield);
|
||||
var targetId = target.GetId(this._player);
|
||||
await connection.SendObjectHitExtendedAsync(
|
||||
this.GetDamageKind(hitInfo.Attributes),
|
||||
hitInfo.Attributes.HasFlag(DamageAttributes.RageFighterStreakHit),
|
||||
hitInfo.Attributes.HasFlag(DamageAttributes.RageFighterStreakFinalHit),
|
||||
hitInfo.Attributes.HasFlag(DamageAttributes.Double),
|
||||
hitInfo.Attributes.HasFlag(DamageAttributes.Triple),
|
||||
targetId,
|
||||
healthStatus,
|
||||
shieldStatus,
|
||||
hitInfo.HealthDamage,
|
||||
hitInfo.ShieldDamage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private byte CalcStatStatus(IAttackable target, AttributeDefinition currentStat, AttributeDefinition maximumStat)
|
||||
{
|
||||
var current = target.Attributes[currentStat];
|
||||
var maximum = target.Attributes[maximumStat];
|
||||
|
||||
if (maximum == 0 || float.IsNaN(maximum))
|
||||
{
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
if (current <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (byte)Math.Round(current / maximum * 250, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
private DamageKind GetDamageKind(DamageAttributes attributes)
|
||||
{
|
||||
if (attributes.HasFlag(DamageAttributes.IgnoreDefense))
|
||||
{
|
||||
return DamageKind.IgnoreDefenseCyan;
|
||||
}
|
||||
|
||||
if (attributes.HasFlag(DamageAttributes.Excellent))
|
||||
{
|
||||
return DamageKind.ExcellentLightGreen;
|
||||
}
|
||||
|
||||
if (attributes.HasFlag(DamageAttributes.Critical))
|
||||
{
|
||||
return DamageKind.CriticalBlue;
|
||||
}
|
||||
|
||||
if (attributes.HasFlag(DamageAttributes.Reflected))
|
||||
{
|
||||
return DamageKind.ReflectedLightPink;
|
||||
}
|
||||
|
||||
if (attributes.HasFlag(DamageAttributes.Poison))
|
||||
{
|
||||
return DamageKind.PoisonDarkGreen;
|
||||
}
|
||||
|
||||
return DamageKind.NormalRed;
|
||||
}
|
||||
}
|
||||
134
src/GameServer/RemoteView/World/ShowHitPlugIn.cs
Normal file
134
src/GameServer/RemoteView/World/ShowHitPlugIn.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
// <copyright file="ShowHitPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowHitPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowHitPlugIn_Name), Description = nameof(PlugInResources.ShowHitPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("bb59de05-d3a1-4b52-a1c6-975decf0f1a3")]
|
||||
public class ShowHitPlugIn : IShowHitPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
private readonly byte _operation;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowHitPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowHitPlugIn(RemotePlayer player)
|
||||
{
|
||||
this._player = player;
|
||||
this._operation = this.DetermineOperation();
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// This Packet is sent to the Client when a Player or Monster got hit and damaged.
|
||||
/// It includes which Player/Monster got hit by who, and the Damage Type.
|
||||
/// It is obvious that the mu online protocol only supports 16 bits for each damage value. To prevent bugs (own player health)
|
||||
/// and to make it somehow visible that the damage exceeds 65k, we send more than one packet, if the 16bits are not enough.
|
||||
/// </remarks>
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowHitAsync(IAttackable target, HitInfo hitInfo)
|
||||
{
|
||||
var targetId = target.GetId(this._player);
|
||||
var remainingHealthDamage = hitInfo.HealthDamage;
|
||||
var remainingShieldDamage = hitInfo.ShieldDamage;
|
||||
|
||||
if (this._player.Connection is not { } connection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// do/while, so that a 'miss' with 0 damage sends a message, too.
|
||||
do
|
||||
{
|
||||
var healthDamage = (ushort)System.Math.Min(0xFFFF, remainingHealthDamage);
|
||||
var shieldDamage = (ushort)System.Math.Min(0xFFFF, remainingShieldDamage);
|
||||
|
||||
await connection.SendObjectHitAsync(
|
||||
this._operation,
|
||||
targetId,
|
||||
healthDamage,
|
||||
this.GetDamageKind(hitInfo.Attributes),
|
||||
hitInfo.Attributes.HasFlag(DamageAttributes.RageFighterStreakHit),
|
||||
hitInfo.Attributes.HasFlag(DamageAttributes.RageFighterStreakFinalHit),
|
||||
hitInfo.Attributes.HasFlag(DamageAttributes.Double),
|
||||
hitInfo.Attributes.HasFlag(DamageAttributes.Triple),
|
||||
shieldDamage).ConfigureAwait(false);
|
||||
|
||||
remainingShieldDamage -= shieldDamage;
|
||||
remainingHealthDamage -= healthDamage;
|
||||
}
|
||||
while (remainingHealthDamage > 0 || remainingShieldDamage > 0);
|
||||
}
|
||||
|
||||
private byte DetermineOperation()
|
||||
{
|
||||
if (this._player.ClientVersion.Season < 1)
|
||||
{
|
||||
return 0x15;
|
||||
}
|
||||
|
||||
switch (this._player.ClientVersion.Language)
|
||||
{
|
||||
case ClientLanguage.English:
|
||||
return 0x11;
|
||||
case ClientLanguage.Japanese:
|
||||
return 0xD6;
|
||||
case ClientLanguage.Vietnamese:
|
||||
return 0xDC;
|
||||
case ClientLanguage.Filipino:
|
||||
case ClientLanguage.Korean:
|
||||
return 0xDF;
|
||||
case ClientLanguage.Chinese:
|
||||
return 0xD0;
|
||||
case ClientLanguage.Thai:
|
||||
return 0xD2;
|
||||
default:
|
||||
return (byte)MUnique.OpenMU.GameServer.PacketType.Hit;
|
||||
}
|
||||
}
|
||||
|
||||
private DamageKind GetDamageKind(DamageAttributes attributes)
|
||||
{
|
||||
if (attributes.HasFlag(DamageAttributes.IgnoreDefense))
|
||||
{
|
||||
return DamageKind.IgnoreDefenseCyan;
|
||||
}
|
||||
|
||||
if (attributes.HasFlag(DamageAttributes.Excellent))
|
||||
{
|
||||
return DamageKind.ExcellentLightGreen;
|
||||
}
|
||||
|
||||
if (attributes.HasFlag(DamageAttributes.Critical))
|
||||
{
|
||||
return DamageKind.CriticalBlue;
|
||||
}
|
||||
|
||||
if (attributes.HasFlag(DamageAttributes.Reflected))
|
||||
{
|
||||
return DamageKind.ReflectedLightPink;
|
||||
}
|
||||
|
||||
if (attributes.HasFlag(DamageAttributes.Poison))
|
||||
{
|
||||
return DamageKind.PoisonDarkGreen;
|
||||
}
|
||||
|
||||
return DamageKind.NormalRed;
|
||||
}
|
||||
}
|
||||
36
src/GameServer/RemoteView/World/ShowMoneyDrop075.cs
Normal file
36
src/GameServer/RemoteView/World/ShowMoneyDrop075.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// <copyright file="ShowMoneyDrop075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowDroppedItemsPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowMoneyDrop075_Name), Description = nameof(PlugInResources.ShowMoneyDrop075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("2C00F283-3229-48A8-A974-3DE0C543DC17")]
|
||||
[MaximumClient(0, 89, ClientLanguage.Invariant)]
|
||||
public class ShowMoneyDrop075 : IShowMoneyDropPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowMoneyDrop075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowMoneyDrop075(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask ShowMoneyAsync(ushort itemId, bool isFreshDrop, uint amount, Point point)
|
||||
{
|
||||
return this._player.Connection.SendMoneyDropped075Async(itemId, isFreshDrop, point.X, point.Y, amount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// <copyright file="ShowMoneyDropExtendedPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The extended implementation of the <see cref="IShowDroppedItemsPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowMoneyDropExtendedPlugIn_Name), Description = nameof(PlugInResources.ShowMoneyDropExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("292D399E-3F48-4AF0-9480-F83267BB8619")]
|
||||
[MinimumClient(106, 3, ClientLanguage.Invariant)]
|
||||
public class ShowMoneyDropExtendedPlugIn : IShowMoneyDropPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowMoneyDropExtendedPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowMoneyDropExtendedPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask ShowMoneyAsync(ushort itemId, bool isFreshDrop, uint amount, Point point)
|
||||
{
|
||||
return this._player.Connection.SendMoneyDroppedExtendedAsync(isFreshDrop, itemId, point.X, point.Y, amount);
|
||||
}
|
||||
}
|
||||
36
src/GameServer/RemoteView/World/ShowMoneyDropPlugIn.cs
Normal file
36
src/GameServer/RemoteView/World/ShowMoneyDropPlugIn.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// <copyright file="ShowMoneyDropPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowDroppedItemsPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowMoneyDropPlugIn_Name), Description = nameof(PlugInResources.ShowMoneyDropPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("f89308c3-5fe7-46e2-adfc-85a56ba23232")]
|
||||
[MinimumClient(0, 90, ClientLanguage.Invariant)]
|
||||
public class ShowMoneyDropPlugIn : IShowMoneyDropPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowMoneyDropPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowMoneyDropPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask ShowMoneyAsync(ushort itemId, bool isFreshDrop, uint amount, Point point)
|
||||
{
|
||||
return this._player.Connection.SendMoneyDroppedAsync(itemId, isFreshDrop, point.X, point.Y, amount);
|
||||
}
|
||||
}
|
||||
37
src/GameServer/RemoteView/World/ShowRageAttackPlugIn.cs
Normal file
37
src/GameServer/RemoteView/World/ShowRageAttackPlugIn.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// <copyright file="ShowRageAttackPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowRageAttackPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowRageAttackPlugIn_Name), Description = nameof(PlugInResources.ShowRageAttackPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("F3E2FA03-AA87-4D61-855C-8ECAF990E108")]
|
||||
[MinimumClient(6, 0, ClientLanguage.Invariant)]
|
||||
public class ShowRageAttackPlugIn : IShowRageAttackPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowRageAttackPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowRageAttackPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowAttackAsync(IIdentifiable attacker, IIdentifiable? target, ushort skillId)
|
||||
{
|
||||
await this._player.Connection.SendRageAttackAsync(skillId, attacker.GetId(this._player), (ushort)(target.GetId(this._player) | 0x8000)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
68
src/GameServer/RemoteView/World/ShowRageAttackRangePlugIn.cs
Normal file
68
src/GameServer/RemoteView/World/ShowRageAttackRangePlugIn.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
// <copyright file="ShowRageAttackRangePlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowRageAttackRangePlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowRageAttackRangePlugIn_Name), Description = nameof(PlugInResources.ShowRageAttackRangePlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("C480C639-A3EA-4A5C-BB82-422FCD24C920")]
|
||||
[MinimumClient(6, 0, ClientLanguage.Invariant)]
|
||||
public class ShowRageAttackRangePlugIn : IShowRageAttackRangePlugIn
|
||||
{
|
||||
private const ushort UndefinedTargetId = 10000;
|
||||
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowRageAttackRangePlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowRageAttackRangePlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ShowRageAttackRangeAsync(ushort skillId, IEnumerable<IIdentifiable> targets)
|
||||
{
|
||||
var connection = this._player.Connection;
|
||||
if (connection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int Write()
|
||||
{
|
||||
var span = connection.Output.GetSpan(RageAttackRangeResponseRef.Length)[..RageAttackRangeResponseRef.Length];
|
||||
var packet = new RageAttackRangeResponseRef(span);
|
||||
packet.SkillId = skillId;
|
||||
var i = 0;
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var block = packet[i];
|
||||
block.TargetId = target.GetId(this._player);
|
||||
i++;
|
||||
}
|
||||
|
||||
for (; i < 5; i++)
|
||||
{
|
||||
var block = packet[i];
|
||||
block.TargetId = UndefinedTargetId;
|
||||
}
|
||||
|
||||
return RageAttackRangeResponseRef.Length;
|
||||
}
|
||||
|
||||
await connection.SendAsync(Write).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
77
src/GameServer/RemoteView/World/ShowSkillAnimationPlugIn.cs
Normal file
77
src/GameServer/RemoteView/World/ShowSkillAnimationPlugIn.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
// <copyright file="ShowSkillAnimationPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowSkillAnimationPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowSkillAnimationPlugIn_Name), Description = nameof(PlugInResources.ShowSkillAnimationPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("a25cc420-c848-4a87-81e5-b86c4241af35")]
|
||||
[MinimumClient(3, 0, ClientLanguage.Invariant)]
|
||||
public class ShowSkillAnimationPlugIn : IShowSkillAnimationPlugIn
|
||||
{
|
||||
/// <summary>
|
||||
/// The combo skill identifier.
|
||||
/// </summary>
|
||||
internal const ushort ComboSkillId = 59;
|
||||
|
||||
private const ushort NovaStartSkillId = 58;
|
||||
private const short ForceSkillId = 60;
|
||||
private const short ForceWaveSkillId = 66;
|
||||
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowSkillAnimationPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowSkillAnimationPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask ShowSkillAnimationAsync(IAttacker attacker, IAttackable? target, Skill skill, bool effectApplied)
|
||||
{
|
||||
return this.ShowSkillAnimationAsync(attacker, target, skill.Number, effectApplied);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowSkillAnimationAsync(IAttacker attacker, IAttackable? target, short skillNumber, bool effectApplied)
|
||||
{
|
||||
if (skillNumber == ForceWaveSkillId)
|
||||
{
|
||||
skillNumber = ForceSkillId;
|
||||
}
|
||||
|
||||
var playerId = attacker.GetId(this._player);
|
||||
var targetId = target.GetId(this._player);
|
||||
var skillId = NumberConversionExtensions.ToUnsigned(skillNumber);
|
||||
await this._player.Connection.SendSkillAnimationAsync(skillId, playerId, targetId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowComboAnimationAsync(IAttacker attacker, IAttackable? target)
|
||||
{
|
||||
var playerId = attacker.GetId(this._player);
|
||||
var targetId = ((IIdentifiable?)target ?? attacker).GetId(this._player);
|
||||
await this._player.Connection.SendSkillAnimationAsync(ComboSkillId, playerId, targetId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ShowNovaStartAsync(IAttacker attacker)
|
||||
{
|
||||
var playerId = attacker.GetId(this._player);
|
||||
await this._player.Connection.SendSkillAnimationAsync(NovaStartSkillId, playerId, 0).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// <copyright file="ShowSkillAnimationPlugIn075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowSkillAnimationPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowSkillAnimationPlugIn075_Name), Description = nameof(PlugInResources.ShowSkillAnimationPlugIn075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("8DED7CDF-AB3E-4CCB-A817-604560120320")]
|
||||
public class ShowSkillAnimationPlugIn075 : IShowSkillAnimationPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowSkillAnimationPlugIn075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowSkillAnimationPlugIn075(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask ShowSkillAnimationAsync(IAttacker attacker, IAttackable? target, Skill skill, bool effectApplied)
|
||||
{
|
||||
return this.ShowSkillAnimationAsync(attacker, target, skill.Number, effectApplied);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowSkillAnimationAsync(IAttacker attacker, IAttackable? target, short skillNumber, bool effectApplied)
|
||||
{
|
||||
var playerId = attacker.GetId(this._player);
|
||||
var targetId = target.GetId(this._player);
|
||||
var skillId = (byte)skillNumber;
|
||||
|
||||
await this._player.Connection.SendSkillAnimation075Async(skillId, playerId, targetId, effectApplied).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask ShowComboAnimationAsync(IAttacker attacker, IAttackable? target)
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask ShowNovaStartAsync(IAttacker attacker)
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// <copyright file="ShowSkillAnimationPlugIn095.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowSkillAnimationPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ShowSkillAnimationPlugIn095_Name), Description = nameof(PlugInResources.ShowSkillAnimationPlugIn095_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("105E727A-A8B5-4050-B6FE-1CC5F5DDC9E4")]
|
||||
[MinimumClient(0, 95, ClientLanguage.Invariant)]
|
||||
public class ShowSkillAnimationPlugIn095 : IShowSkillAnimationPlugIn
|
||||
{
|
||||
private const byte NovaStartSkillId = 58;
|
||||
private const byte ComboSkillId = 59;
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShowSkillAnimationPlugIn095"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public ShowSkillAnimationPlugIn095(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask ShowSkillAnimationAsync(IAttacker attacker, IAttackable? target, Skill skill, bool effectApplied)
|
||||
{
|
||||
return this.ShowSkillAnimationAsync(attacker, target, skill.Number, effectApplied);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowSkillAnimationAsync(IAttacker attacker, IAttackable? target, short skillNumber, bool effectApplied)
|
||||
{
|
||||
var playerId = attacker.GetId(this._player);
|
||||
var targetId = target.GetId(this._player);
|
||||
await this._player.Connection.SendSkillAnimation095Async((byte)skillNumber, playerId, targetId, effectApplied).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowComboAnimationAsync(IAttacker attacker, IAttackable? target)
|
||||
{
|
||||
var playerId = attacker.GetId(this._player);
|
||||
var targetId = ((IIdentifiable?)target ?? attacker).GetId(this._player);
|
||||
await this._player.Connection.SendSkillAnimation095Async(ComboSkillId, playerId, targetId, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ShowNovaStartAsync(IAttacker attacker)
|
||||
{
|
||||
var playerId = attacker.GetId(this._player);
|
||||
await this._player.Connection.SendSkillAnimation095Async(NovaStartSkillId, playerId, 0, false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// <copyright file="SkillStageUpdatePlugInPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IShowSkillStageUpdatePlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.SkillStageUpdatePlugInPlugIn_Name), Description = nameof(PlugInResources.SkillStageUpdatePlugInPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("9EE927BA-E82B-46DC-872B-F8B5F646A4A5")]
|
||||
public class SkillStageUpdatePlugInPlugIn : IShowSkillStageUpdatePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkillStageUpdatePlugInPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public SkillStageUpdatePlugInPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask UpdateSkillStageAsync(IAttacker attacker, short skillNumber, byte stageNumber)
|
||||
{
|
||||
if (this._player.Connection is not { } connection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var attackerId = attacker.GetId(this._player);
|
||||
await connection.SendSkillStageUpdateAsync(attackerId, stageNumber, (byte)skillNumber).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
43
src/GameServer/RemoteView/World/TeleportPlugIn.cs
Normal file
43
src/GameServer/RemoteView/World/TeleportPlugIn.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// <copyright file="TeleportPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="ITeleportPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.TeleportPlugIn_Name), Description = nameof(PlugInResources.TeleportPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("9506F77B-CA72-4150-87E3-57C889C91F02")]
|
||||
[MinimumClient(1, 0, ClientLanguage.Invariant)]
|
||||
public class TeleportPlugIn : ITeleportPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TeleportPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public TeleportPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowTeleportedAsync()
|
||||
{
|
||||
if (this._player.SelectedCharacter?.CurrentMap is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapNumber = this._player.SelectedCharacter.CurrentMap.Number.ToUnsigned();
|
||||
var position = this._player.Position;
|
||||
await this._player.Connection.SendMapChangedAsync(mapNumber, position.X, position.Y, this._player.Rotation.ToPacketByte(), false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
40
src/GameServer/RemoteView/World/TeleportPlugIn075.cs
Normal file
40
src/GameServer/RemoteView/World/TeleportPlugIn075.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
// <copyright file="TeleportPlugIn075.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="ITeleportPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.TeleportPlugIn075_Name), Description = nameof(PlugInResources.TeleportPlugIn075_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("490DB5E5-9DB6-4068-9708-E7D69F82BF3B")]
|
||||
public class TeleportPlugIn075 : ITeleportPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TeleportPlugIn075"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public TeleportPlugIn075(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShowTeleportedAsync()
|
||||
{
|
||||
if (this._player.SelectedCharacter?.CurrentMap is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapNumber = (byte)this._player.SelectedCharacter.CurrentMap.Number;
|
||||
var position = this._player.Position;
|
||||
await this._player.Connection.SendMapChanged075Async(mapNumber, position.X, position.Y, this._player.Rotation.ToPacketByte(), isMapChange: false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
33
src/GameServer/RemoteView/World/UpdateRotationPlugIn.cs
Normal file
33
src/GameServer/RemoteView/World/UpdateRotationPlugIn.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// <copyright file="UpdateRotationPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.Character;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IUpdateRotationPlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.UpdateRotationPlugIn_Name), Description = nameof(PlugInResources.UpdateRotationPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("2ce3ba17-fd67-4674-88c5-f29c83608310")]
|
||||
public class UpdateRotationPlugIn : IUpdateRotationPlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UpdateRotationPlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public UpdateRotationPlugIn(RemotePlayer player) => this._player = player;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask UpdateRotationAsync()
|
||||
{
|
||||
//// TODO: Implement Rotation, packet: { 0xc1, 0x04, 0x0F, 0x12 }
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
36
src/GameServer/RemoteView/World/WeatherStatusUpdatePlugIn.cs
Normal file
36
src/GameServer/RemoteView/World/WeatherStatusUpdatePlugIn.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// <copyright file="WeatherStatusUpdatePlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameServer.RemoteView.World;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Views.World;
|
||||
using MUnique.OpenMU.Network.Packets.ServerToClient;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of the <see cref="IWeatherStatusUpdatePlugIn"/> which is forwarding everything to the game client with specific data packets.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.WeatherStatusUpdatePlugIn_Name), Description = nameof(PlugInResources.WeatherStatusUpdatePlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("44369927-4EE4-47D7-9C6C-DD74FC824071")]
|
||||
public class WeatherStatusUpdatePlugIn : IWeatherStatusUpdatePlugIn
|
||||
{
|
||||
private readonly RemotePlayer _player;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WeatherStatusUpdatePlugIn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
public WeatherStatusUpdatePlugIn(RemotePlayer player)
|
||||
{
|
||||
this._player = player;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask ShowWeatherAsync(byte weather, byte variation)
|
||||
{
|
||||
return this._player.Connection.SendWeatherStatusUpdateAsync(weather, variation);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user