baseline: OpenMU upstream b5a0961 (fresh source)

This commit is contained in:
Acentech Dev
2026-07-14 19:00:35 +03:00
parent 450402e47d
commit 36fc125d5c
11968 changed files with 748705 additions and 0 deletions

View File

@@ -0,0 +1,433 @@
// <copyright file="AppearanceSerializer.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;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Default serializer for the appearance of a player.
/// </summary>
[Guid("54847CAF-7827-48FB-BF53-AF458A694FAF")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AppearanceSerializer_Name), Description = nameof(PlugInResources.AppearanceSerializer_Description), ResourceType = typeof(PlugInResources))]
[MinimumClient(5, 0, ClientLanguage.Invariant)]
public class AppearanceSerializer : IAppearanceSerializer
{
/// <summary>
/// A cache which holds the results of the serializer.
/// </summary>
private static readonly ConcurrentDictionary<IAppearanceData, byte[]> Cache = new();
private enum PetIndex
{
Angel = 0,
Imp = 1,
Unicorn = 2,
Dinorant = 3,
DarkHorse = 4,
DarkRaven = 5,
Fenrir = 37,
Demon = 64,
SpiritOfGuardian = 65,
Rudolph = 66,
Panda = 80,
PetUnicorn = 106,
Skeleton = 123,
}
private enum WingIndex
{
WingsOfElf = 0,
WingsOfHeaven = 1,
WingsOfSatan = 2,
WingsOfMistery = 41,
WingsOfSpirit = 3,
WingsOfSoul = 4,
WingsOfDragon = 5,
WingsOfDarkness = 6,
CapeOfLord = 30, // other group, but index not overlapping with other wings
WingsOfDespair = 42,
CapeOfFighter = 49,
WingOfStorm = 36,
WingOfEternal = 37,
WingOfIllusion = 38,
WingOfRuin = 39,
CapeOfEmperor = 40,
WingOfDimension = 43,
CapeOfOverrule = 50,
SmallCapeOfLord = 130,
SmallWingsOfMistery = 131,
SmallWingsOfElf = 132,
SmallWingsOfHeaven = 133,
SmallWingsOfSatan = 134,
SmallCloakOfWarrior = 135,
}
/// <inheritdoc/>
public int NeededSpace => 18;
/// <inheritdoc/>
public void InvalidateCache(IAppearanceData appearance)
{
Cache.TryRemove(appearance, out _);
appearance.AppearanceChanged -= this.OnAppearanceOfAppearanceChanged;
}
/// <inheritdoc/>
public void WriteAppearanceData(Span<byte> target, IAppearanceData appearance, bool useCache)
{
if (target.Length < this.NeededSpace)
{
throw new ArgumentException($"Target span too small. Actual size: {target.Length}; Required: {this.NeededSpace}.", nameof(target));
}
if (useCache && Cache.TryGetValue(appearance, out var cached))
{
cached.CopyTo(target);
}
else
{
this.WritePreviewCharSet(target, appearance);
if (useCache)
{
var cacheEntry = target.Slice(0, this.NeededSpace).ToArray();
if (Cache.TryAdd(appearance, cacheEntry))
{
appearance.AppearanceChanged += this.OnAppearanceOfAppearanceChanged;
}
}
}
}
private void OnAppearanceOfAppearanceChanged(object? sender, EventArgs args) => this.InvalidateCache(sender as IAppearanceData ?? throw new ArgumentException($"sender must be of type {nameof(IAppearanceData)}"));
private void WritePreviewCharSet(Span<byte> target, IAppearanceData appearanceData)
{
ItemAppearance?[] itemArray = new ItemAppearance[InventoryConstants.EquippableSlotsCount];
for (byte i = 0; i < itemArray.Length; i++)
{
itemArray[i] = appearanceData.EquippedItems.FirstOrDefault(item => item.ItemSlot == i);
}
if (appearanceData.CharacterClass is not null)
{
target[0] = (byte)(appearanceData.CharacterClass.Number << 3 & 0xF8);
}
target[0] |= (byte)appearanceData.Pose;
this.SetHand(target, itemArray[InventoryConstants.LeftHandSlot], 1, 12);
this.SetHand(target, itemArray[InventoryConstants.RightHandSlot], 2, 13);
this.SetArmorPiece(target, itemArray[InventoryConstants.HelmSlot], 3, true, 0x80, 13, false);
this.SetArmorPiece(target, itemArray[InventoryConstants.ArmorSlot], 3, false, 0x40, 14, true);
this.SetArmorPiece(target, itemArray[InventoryConstants.PantsSlot], 4, true, 0x20, 14, false);
this.SetArmorPiece(target, itemArray[InventoryConstants.GlovesSlot], 4, false, 0x10, 15, true);
this.SetArmorPiece(target, itemArray[InventoryConstants.BootsSlot], 5, true, 0x08, 15, false);
this.SetItemLevels(target, itemArray);
if (appearanceData.FullAncientSetEquipped)
{
target[11] |= 0x01;
}
this.AddWing(target, itemArray[InventoryConstants.WingsSlot]);
this.AddPet(target, itemArray[InventoryConstants.PetSlot]);
}
private void SetHand(Span<byte> preview, ItemAppearance? item, int indexIndex, int groupIndex)
{
if (item?.Definition is null)
{
preview[indexIndex] = 0xFF;
preview[groupIndex] |= 0xF0;
}
else
{
preview[indexIndex] = (byte)item.Definition.Number;
preview[groupIndex] |= (byte)(item.Definition.Group << 5);
}
}
private byte GetOrMaskForHighNibble(int value)
{
return (byte)((value << 4) & 0xF0);
}
private byte GetOrMaskForLowNibble(int value)
{
return (byte)(value & 0x0F);
}
private void SetEmptyArmor(Span<byte> preview, int firstIndex, bool firstIndexHigh, byte secondIndexMask, int thirdIndex, bool thirdIndexHigh)
{
// if the item is not equipped every index bit is set to 1
preview[firstIndex] |= firstIndexHigh ? this.GetOrMaskForHighNibble(0x0F) : this.GetOrMaskForLowNibble(0x0F);
preview[9] |= secondIndexMask;
preview[thirdIndex] |= thirdIndexHigh ? this.GetOrMaskForHighNibble(0x0F) : this.GetOrMaskForLowNibble(0x0F);
}
private void SetArmorItemIndex(Span<byte> preview, ItemAppearance item, int firstIndex, bool firstIndexHigh, byte secondIndexMask, int thirdIndex, bool thirdIndexHigh)
{
preview[firstIndex] |= firstIndexHigh ? this.GetOrMaskForHighNibble(item.Definition!.Number) : this.GetOrMaskForLowNibble(item.Definition!.Number);
byte multi = (byte)(item.Definition.Number / 16);
if (multi > 0)
{
byte bit1 = (byte)(multi % 2);
byte byte2 = (byte)(multi / 2);
if (bit1 == 1)
{
preview[9] |= secondIndexMask;
}
if (byte2 > 0)
{
preview[thirdIndex] |= thirdIndexHigh ? this.GetOrMaskForHighNibble(byte2) : this.GetOrMaskForLowNibble(byte2);
}
}
}
private void SetArmorPiece(Span<byte> preview, ItemAppearance? item, int firstIndex, bool firstIndexHigh, byte secondIndexMask, int thirdIndex, bool thirdIndexHigh)
{
if (item?.Definition is null)
{
this.SetEmptyArmor(preview, firstIndex, firstIndexHigh, secondIndexMask, thirdIndex, thirdIndexHigh);
}
else
{
// item id
this.SetArmorItemIndex(preview, item, firstIndex, firstIndexHigh, secondIndexMask, thirdIndex, thirdIndexHigh);
// exc bit
if (this.IsExcellent(item))
{
preview[10] |= secondIndexMask;
}
// ancient bit
if (this.IsAncient(item))
{
preview[11] |= secondIndexMask;
}
}
}
private void SetItemLevels(Span<byte> preview, ItemAppearance?[] itemArray)
{
int levelIndex = 0;
for (int i = 0; i < 7; i++)
{
if (itemArray[i] is not null)
{
levelIndex |= itemArray[i]!.GetGlowLevel() << (i * 3);
}
}
preview[6] = (byte)((levelIndex >> 16) & 255);
preview[7] = (byte)((levelIndex >> 8) & 255);
preview[8] = (byte)(levelIndex & 255);
}
private void AddWing(Span<byte> preview, ItemAppearance? wing)
{
if (wing?.Definition is null)
{
return;
}
switch ((WingIndex)wing.Definition.Number)
{
case WingIndex.WingsOfElf:
case WingIndex.WingsOfHeaven:
case WingIndex.WingsOfSatan:
case WingIndex.WingsOfMistery:
preview[5] |= 0x04;
break;
case WingIndex.WingsOfSpirit:
case WingIndex.WingsOfSoul:
case WingIndex.WingsOfDragon:
case WingIndex.WingsOfDarkness:
case WingIndex.CapeOfLord:
case WingIndex.WingsOfDespair:
case WingIndex.CapeOfFighter:
preview[5] |= 0x08;
break;
case WingIndex.WingOfStorm:
case WingIndex.WingOfEternal:
case WingIndex.WingOfIllusion:
case WingIndex.WingOfRuin:
case WingIndex.CapeOfEmperor:
case WingIndex.WingOfDimension:
case WingIndex.CapeOfOverrule:
case WingIndex.SmallCapeOfLord:
case WingIndex.SmallWingsOfMistery:
case WingIndex.SmallWingsOfElf:
case WingIndex.SmallWingsOfHeaven:
case WingIndex.SmallWingsOfSatan:
case WingIndex.SmallCloakOfWarrior:
preview[5] |= 0x0C;
break;
default:
// nothing to do
break;
}
switch ((WingIndex)wing.Definition.Number)
{
case WingIndex.WingsOfElf:
case WingIndex.WingsOfSpirit:
case WingIndex.WingOfStorm:
preview[9] |= 0x01;
break;
case WingIndex.WingsOfHeaven:
case WingIndex.WingsOfSoul:
case WingIndex.WingOfEternal:
preview[9] |= 0x02;
break;
case WingIndex.WingsOfSatan:
case WingIndex.WingsOfDragon:
case WingIndex.WingOfIllusion:
preview[9] |= 0x03;
break;
case WingIndex.WingsOfMistery:
case WingIndex.WingsOfDarkness:
case WingIndex.WingOfRuin:
preview[9] |= 0x04;
break;
case WingIndex.CapeOfLord:
case WingIndex.CapeOfEmperor:
preview[9] |= 0x05;
break;
case WingIndex.WingsOfDespair:
case WingIndex.WingOfDimension:
preview[9] |= 0x06;
break;
case WingIndex.CapeOfFighter:
case WingIndex.CapeOfOverrule:
preview[9] |= 0x07;
break;
case WingIndex.SmallCapeOfLord:
preview[17] |= 0x20;
break;
case WingIndex.SmallWingsOfMistery:
preview[17] |= 0x40;
break;
case WingIndex.SmallWingsOfElf:
preview[17] |= 0x60;
break;
case WingIndex.SmallWingsOfHeaven:
preview[17] |= 0x80;
break;
case WingIndex.SmallWingsOfSatan:
preview[17] |= 0xA0;
break;
case WingIndex.SmallCloakOfWarrior:
preview[17] |= 0xC0;
break;
default:
// nothing to do
break;
}
}
private void AddPet(Span<byte> preview, ItemAppearance? pet)
{
if (pet?.Definition is null)
{
preview[5] |= 0b0000_0011;
return;
}
switch ((PetIndex)pet.Definition.Number)
{
case PetIndex.Angel:
case PetIndex.Imp:
case PetIndex.Unicorn:
preview[5] |= (byte)pet.Definition.Number;
break;
case PetIndex.Dinorant:
preview[5] |= 0x03;
preview[10] |= 0x01;
break;
case PetIndex.DarkHorse:
preview[5] |= 0x03;
preview[12] |= 0x01;
break;
case PetIndex.Fenrir:
preview[5] |= 0x03;
preview[10] &= 0xFE;
preview[12] &= 0xFE;
preview[12] |= 0x04;
preview[16] = 0x00;
if (pet.VisibleOptions.Contains(ItemOptionTypes.BlackFenrir))
{
preview[16] |= 0x01;
}
if (pet.VisibleOptions.Contains(ItemOptionTypes.BlueFenrir))
{
preview[16] |= 0x02;
}
if (pet.VisibleOptions.Contains(ItemOptionTypes.GoldFenrir))
{
preview[17] |= 0x01;
}
break;
default:
preview[5] |= 0x03;
break;
}
switch ((PetIndex)pet.Definition.Number)
{
case PetIndex.Panda:
preview[16] |= 0xE0;
break;
case PetIndex.PetUnicorn:
preview[16] |= 0xA0;
break;
case PetIndex.Skeleton:
preview[16] |= 0x60;
break;
case PetIndex.Rudolph:
preview[16] |= 0x80;
break;
case PetIndex.SpiritOfGuardian:
preview[16] |= 0x40;
break;
case PetIndex.Demon:
preview[16] |= 0x20;
break;
default:
// no further flag required.
break;
}
}
private bool IsExcellent(ItemAppearance item)
{
return item.VisibleOptions.Contains(ItemOptionTypes.Excellent);
}
private bool IsAncient(ItemAppearance item)
{
return item.VisibleOptions.Contains(ItemOptionTypes.AncientOption);
}
}

View File

@@ -0,0 +1,159 @@
// <copyright file="AppearanceSerializer075.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;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Serializer for the appearance of a player, compatible with the client of version 0.75.
/// </summary>
[Guid("D20EEBFA-12C1-4A86-B202-63121EB2A95B")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AppearanceSerializer075_Name), Description = nameof(PlugInResources.AppearanceSerializer075_Description), ResourceType = typeof(PlugInResources))]
[MinimumClient(0, 75, ClientLanguage.Invariant)]
public class AppearanceSerializer075 : IAppearanceSerializer
{
/// <summary>
/// A cache which holds the results of the serializer.
/// </summary>
private static readonly ConcurrentDictionary<IAppearanceData, byte[]> Cache = new();
/// <inheritdoc/>
public int NeededSpace => 9;
/// <inheritdoc/>
public void InvalidateCache(IAppearanceData appearance)
{
Cache.TryRemove(appearance, out _);
appearance.AppearanceChanged -= this.OnAppearanceOfAppearanceChanged;
}
/// <inheritdoc/>
public void WriteAppearanceData(Span<byte> target, IAppearanceData appearance, bool useCache)
{
if (target.Length < this.NeededSpace)
{
throw new ArgumentException($"Target span too small. Actual size: {target.Length}; Required: {this.NeededSpace}.", nameof(target));
}
if (useCache && Cache.TryGetValue(appearance, out var cached))
{
cached.CopyTo(target);
}
else
{
this.WritePreviewCharSet(target, appearance);
if (useCache)
{
var cacheEntry = target.Slice(0, this.NeededSpace).ToArray();
if (Cache.TryAdd(appearance, cacheEntry))
{
appearance.AppearanceChanged += this.OnAppearanceOfAppearanceChanged;
}
}
}
}
private void OnAppearanceOfAppearanceChanged(object? sender, EventArgs args) => this.InvalidateCache(sender as IAppearanceData ?? throw new ArgumentException($"sender must be of type {nameof(IAppearanceData)}"));
private void WritePreviewCharSet(Span<byte> target, IAppearanceData appearanceData)
{
ItemAppearance?[] itemArray = new ItemAppearance[InventoryConstants.BootsSlot + 1];
for (byte i = 0; i < itemArray.Length; i++)
{
itemArray[i] = appearanceData.EquippedItems.FirstOrDefault(item => item.ItemSlot == i && item.Definition?.Number < 16);
}
if (appearanceData.CharacterClass is not null)
{
target[0] = (byte)(appearanceData.CharacterClass.Number << 3);
}
target[0] |= (byte)appearanceData.Pose;
this.SetHand(target, itemArray[InventoryConstants.LeftHandSlot], 1);
this.SetHand(target, itemArray[InventoryConstants.RightHandSlot], 2);
this.SetArmorPiece(target, itemArray[InventoryConstants.HelmSlot], 3, true);
this.SetArmorPiece(target, itemArray[InventoryConstants.ArmorSlot], 3, false);
this.SetArmorPiece(target, itemArray[InventoryConstants.PantsSlot], 4, true);
this.SetArmorPiece(target, itemArray[InventoryConstants.GlovesSlot], 4, false);
this.SetArmorPiece(target, itemArray[InventoryConstants.BootsSlot], 5, true);
var wing = appearanceData.EquippedItems.FirstOrDefault(item => item.ItemSlot == InventoryConstants.WingsSlot && item.Definition?.Number < 3);
var pet = appearanceData.EquippedItems.FirstOrDefault(item => item.ItemSlot == InventoryConstants.PetSlot && item.Definition?.Number < 3);
target[5] |= (byte)((wing?.Definition?.Number & 0x03) << 2 ?? 0b1100);
target[5] |= (byte)(pet?.Definition?.Number & 0x03 ?? 0b0011);
this.SetItemLevels(target, itemArray);
}
private void SetHand(Span<byte> preview, ItemAppearance? item, int index)
{
if (item?.Definition is null)
{
preview[index] = 0xFF;
}
else
{
preview[index] = (byte)item.Definition.Number;
preview[index] |= (byte)(item.Definition.Group << 4);
}
}
private byte GetOrMaskForHighNibble(int value)
{
return (byte)((value << 4) & 0xF0);
}
private byte GetOrMaskForLowNibble(int value)
{
return (byte)(value & 0x0F);
}
private void SetArmorPiece(Span<byte> preview, ItemAppearance? item, int index, bool highNibble)
{
if (item?.Definition is null)
{
// if the item is not equipped every index bit is set to 1
preview[index] |= highNibble ? this.GetOrMaskForHighNibble(0x0F) : this.GetOrMaskForLowNibble(0x0F);
}
else
{
var number = item.Definition.Number;
if (number >= 10)
{
// Elf items start again at 0. What a stupid logic, as there are only 15 sets anyway.
number -= 10;
}
preview[index] |= highNibble ? this.GetOrMaskForHighNibble(number) : this.GetOrMaskForLowNibble(number);
}
}
private void SetItemLevels(Span<byte> preview, ItemAppearance?[] itemArray)
{
int levelIndex = 0;
for (int i = 0; i < 7; i++)
{
if (itemArray[i] is not null)
{
levelIndex |= itemArray[i]!.GetGlowLevel() << (i * 3);
}
}
preview[6] = (byte)((levelIndex >> 16) & 255);
preview[7] = (byte)((levelIndex >> 8) & 255);
preview[8] = (byte)(levelIndex & 255);
}
}

View File

@@ -0,0 +1,244 @@
// <copyright file="AppearanceSerializer095.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;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Serializer for the appearance of a player, compatible with the client of version 0.95.
/// </summary>
[Guid("99616318-38BC-4C0A-A818-29D821B78DE2")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AppearanceSerializer095_Name), Description = nameof(PlugInResources.AppearanceSerializer095_Description), ResourceType = typeof(PlugInResources))]
[MinimumClient(0, 95, ClientLanguage.Invariant)]
public class AppearanceSerializer095 : IAppearanceSerializer
{
/// <summary>
/// A cache which holds the results of the serializer.
/// </summary>
private static readonly ConcurrentDictionary<IAppearanceData, byte[]> Cache = new();
private enum Pets
{
Angel = 0,
Imp = 1,
Unicorn = 2,
Dinorant = 3,
}
private enum WingIndex
{
WingsOfElf = 0,
WingsOfHeaven = 1,
WingsOfSatan = 2,
None = 3,
}
/// <inheritdoc/>
public int NeededSpace => 11;
/// <inheritdoc/>
public void InvalidateCache(IAppearanceData appearance)
{
Cache.TryRemove(appearance, out _);
appearance.AppearanceChanged -= this.OnAppearanceOfAppearanceChanged;
}
/// <inheritdoc/>
public void WriteAppearanceData(Span<byte> target, IAppearanceData appearance, bool useCache)
{
if (target.Length < this.NeededSpace)
{
throw new ArgumentException($"Target span too small. Actual size: {target.Length}; Required: {this.NeededSpace}.", nameof(target));
}
if (useCache && Cache.TryGetValue(appearance, out var cached))
{
cached.CopyTo(target);
}
else
{
this.WritePreviewCharSet(target, appearance);
if (useCache)
{
var cacheEntry = target.Slice(0, this.NeededSpace).ToArray();
if (Cache.TryAdd(appearance, cacheEntry))
{
appearance.AppearanceChanged += this.OnAppearanceOfAppearanceChanged;
}
}
}
}
private void OnAppearanceOfAppearanceChanged(object? sender, EventArgs args) => this.InvalidateCache(sender as IAppearanceData ?? throw new ArgumentException($"sender must be of type {nameof(IAppearanceData)}"));
/// <summary>
/// Writes the preview character set.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="appearanceData">The appearance data.</param>
private void WritePreviewCharSet(Span<byte> target, IAppearanceData appearanceData)
{
ItemAppearance?[] itemArray = new ItemAppearance[InventoryConstants.EquippableSlotsCount];
for (byte i = 0; i < itemArray.Length; i++)
{
itemArray[i] = appearanceData.EquippedItems.FirstOrDefault(item => item.ItemSlot == i && item.Definition?.Number < 16);
}
if (appearanceData.CharacterClass is not null)
{
target[0] = (byte)(appearanceData.CharacterClass.Number << 3);
/* 00 Dark Wizard
10 Soul Master
20 Dark Knight
30 Blade Knight
40 Elf
50 Muse Elf
60 Magic Gladiator*/
}
target[0] |= (byte)appearanceData.Pose;
this.SetHand(target, itemArray[InventoryConstants.LeftHandSlot], 1);
this.SetHand(target, itemArray[InventoryConstants.RightHandSlot], 2);
this.SetArmorPiece(target, itemArray[InventoryConstants.HelmSlot], 3, true, 0x80);
this.SetArmorPiece(target, itemArray[InventoryConstants.ArmorSlot], 3, false, 0x40);
this.SetArmorPiece(target, itemArray[InventoryConstants.PantsSlot], 4, true, 0x20);
this.SetArmorPiece(target, itemArray[InventoryConstants.GlovesSlot], 4, false, 0x10);
this.SetArmorPiece(target, itemArray[InventoryConstants.BootsSlot], 5, true, 0x08);
target[5] |= (byte)((itemArray[InventoryConstants.WingsSlot]?.Definition?.Number & 0x03) << 2 ?? 0b1100);
this.SetPet(target, itemArray[InventoryConstants.PetSlot]);
// index9: upper 5 bits are the equipped flags of SetArmorPiece
this.SetItemLevels(target, itemArray);
}
private void SetPet(Span<byte> preview, ItemAppearance? item)
{
var pet = (Pets?)item?.Definition?.Number;
switch (pet)
{
case Pets.Angel:
case Pets.Imp:
case Pets.Unicorn:
preview[5] |= (byte)pet;
break;
case Pets.Dinorant:
preview[5] |= 0b11;
preview[9] |= 0b100;
break;
default:
preview[5] |= 0b11;
break;
}
}
private void SetHand(Span<byte> preview, ItemAppearance? item, int index)
{
if (item?.Definition is null)
{
preview[index] = 0xFF;
}
else
{
preview[index] = (byte)item.Definition.Number;
preview[index] |= (byte)(item.Definition.Group << 5);
}
}
private byte GetOrMaskForHighNibble(int value)
{
return (byte)((value << 4) & 0xF0);
}
private byte GetOrMaskForLowNibble(int value)
{
return (byte)(value & 0x0F);
}
private void SetArmorPiece(Span<byte> preview, ItemAppearance? item, int firstIndex, bool firstIndexHigh, byte secondIndexMask)
{
if (item?.Definition is null)
{
this.SetEmptyArmor(preview, firstIndex, firstIndexHigh, secondIndexMask);
}
else
{
// item id
this.SetArmorItemIndex(preview, item, firstIndex, firstIndexHigh, secondIndexMask);
// exc bit
if (this.IsExcellent(item))
{
preview[10] |= secondIndexMask;
}
}
}
private void SetEmptyArmor(Span<byte> preview, int firstIndex, bool firstIndexHigh, byte secondIndexMask)
{
// if the item is not equipped every index bit is set to 1
preview[firstIndex] |= firstIndexHigh ? this.GetOrMaskForHighNibble(0x0F) : this.GetOrMaskForLowNibble(0x0F);
preview[9] |= secondIndexMask;
}
private void SetArmorItemIndex(Span<byte> preview, ItemAppearance item, int firstIndex, bool firstIndexHigh, byte secondIndexMask)
{
preview[firstIndex] |= firstIndexHigh ? this.GetOrMaskForHighNibble(item.Definition!.Number) : this.GetOrMaskForLowNibble(item.Definition!.Number);
byte multi = (byte)(item.Definition.Number / 16);
if (multi > 0)
{
byte bit1 = (byte)(multi % 2);
if (bit1 == 1)
{
preview[9] |= secondIndexMask;
}
}
}
private bool IsExcellent(ItemAppearance item)
{
return item.VisibleOptions.Contains(ItemOptionTypes.Excellent);
}
private void SetItemLevels(Span<byte> preview, ItemAppearance?[] itemArray)
{
int levelIndex = 0;
for (int i = 0; i < 7; i++)
{
if (itemArray[i] is not null)
{
levelIndex |= itemArray[i]!.GetGlowLevel() << (i * 3);
}
}
preview[6] = (byte)((levelIndex >> 16) & 255);
preview[7] = (byte)((levelIndex >> 8) & 255);
preview[8] = (byte)(levelIndex & 255);
}
private void AddWing(Span<byte> preview, ItemAppearance? wing)
{
if (wing?.Definition is null)
{
preview[5] |= 0x0C;
return;
}
preview[5] |= (byte)((wing?.Definition?.Number & 0x03) << 2 ?? 0b1100);
}
}

View File

@@ -0,0 +1,284 @@
// <copyright file="AppearanceSerializerExtended.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;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Extended serializer for the appearance of a player.
/// </summary>
[Guid("A17EDFF7-236B-4EC9-899A-A6FC8BBA840C")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AppearanceSerializerExtended_Name), Description = nameof(PlugInResources.AppearanceSerializerExtended_Description), ResourceType = typeof(PlugInResources))]
[MinimumClient(106, 3, ClientLanguage.Invariant)]
public class AppearanceSerializerExtended : IAppearanceSerializer
{
/// <summary>
/// A cache which holds the results of the serializer.
/// </summary>
private static readonly ConcurrentDictionary<IAppearanceData, byte[]> Cache = new();
/// <inheritdoc/>
public int NeededSpace => 27;
/// <inheritdoc/>
public void InvalidateCache(IAppearanceData appearance)
{
Cache.TryRemove(appearance, out _);
appearance.AppearanceChanged -= this.OnAppearanceOfAppearanceChanged;
}
/// <inheritdoc/>
public void WriteAppearanceData(Span<byte> target, IAppearanceData appearance, bool useCache)
{
if (target.Length < this.NeededSpace)
{
throw new ArgumentException($"Target span too small. Actual size: {target.Length}; Required: {this.NeededSpace}.", nameof(target));
}
if (useCache && Cache.TryGetValue(appearance, out var cached))
{
cached.CopyTo(target);
}
else
{
this.WritePreviewCharSet(target, appearance);
if (useCache)
{
var cacheEntry = target.Slice(0, this.NeededSpace).ToArray();
if (Cache.TryAdd(appearance, cacheEntry))
{
appearance.AppearanceChanged += this.OnAppearanceOfAppearanceChanged;
}
}
}
}
private void OnAppearanceOfAppearanceChanged(object? sender, EventArgs args)
{
this.InvalidateCache(sender as IAppearanceData ?? throw new ArgumentException($"sender must be of type {nameof(IAppearanceData)}"));
}
private void WritePreviewCharSet(Span<byte> target, IAppearanceData appearanceData)
{
var itemArray = new ItemAppearance?[InventoryConstants.EquippableSlotsCount];
for (byte i = 0; i < itemArray.Length; i++)
{
itemArray[i] = appearanceData.EquippedItems.FirstOrDefault(item => item.ItemSlot == i);
}
if (appearanceData.CharacterClass is not null)
{
target[0] = appearanceData.CharacterClass.Number;
}
target[1] = (byte)appearanceData.Pose;
if (appearanceData.FullAncientSetEquipped)
{
target[1] |= 0x10;
}
if (appearanceData.CharacterStatus == CharacterStatus.GameMaster)
{
target[1] |= 0x20;
}
var items = target[2..];
this.SetShinyItem(items[0..3], itemArray[InventoryConstants.LeftHandSlot]);
this.SetShinyItem(items[3..6], itemArray[InventoryConstants.RightHandSlot]);
this.SetShinyItem(items[6..9], itemArray[InventoryConstants.HelmSlot]);
this.SetShinyItem(items[9..12], itemArray[InventoryConstants.ArmorSlot]);
this.SetShinyItem(items[12..15], itemArray[InventoryConstants.PantsSlot]);
this.SetShinyItem(items[15..18], itemArray[InventoryConstants.GlovesSlot]);
this.SetShinyItem(items[18..21], itemArray[InventoryConstants.BootsSlot]);
this.SetUnshinyItem(items[21..23], itemArray[InventoryConstants.WingsSlot]);
this.SetUnshinyItem(items[23..25], itemArray[InventoryConstants.PetSlot]);
var pet = itemArray[InventoryConstants.PetSlot];
if (pet is not null)
{
if (pet.VisibleOptions.Contains(ItemOptionTypes.BlackFenrir))
{
items[23] |= 0b10;
}
if (pet.VisibleOptions.Contains(ItemOptionTypes.BlueFenrir))
{
items[23] |= 0b100;
}
if (pet.VisibleOptions.Contains(ItemOptionTypes.GoldFenrir))
{
items[23] |= 0b110;
}
}
}
private void SetUnshinyItem(Span<byte> data, ItemAppearance? item)
{
var unshinyItem = new UnshinyItem(data);
if (item?.Definition is null)
{
unshinyItem.Group = 0xF;
unshinyItem.Number = 0xFFF;
}
else
{
unshinyItem.Group = item.Definition.Group;
unshinyItem.Number = (ushort)item.Definition.Number;
}
}
private void SetShinyItem(Span<byte> data, ItemAppearance? item)
{
var shinyItem = new ShinyItem(data);
if (item?.Definition is null)
{
shinyItem.Group = 0xF;
shinyItem.Number = 0xFFF;
shinyItem.GlowLevel = 0;
shinyItem.IsExcellent = false;
shinyItem.IsAncient = false;
}
else
{
shinyItem.Group = item.Definition.Group;
shinyItem.Number = (ushort)item.Definition.Number;
shinyItem.GlowLevel = item.GetGlowLevel();
shinyItem.IsExcellent = this.IsExcellent(item);
shinyItem.IsAncient = this.IsAncient(item);
}
}
private bool IsExcellent(ItemAppearance item)
{
return item.VisibleOptions.Contains(ItemOptionTypes.Excellent);
}
private bool IsAncient(ItemAppearance item)
{
return item.VisibleOptions.Contains(ItemOptionTypes.AncientOption);
}
/// <summary>
/// Armor/Weapon Item: (3 bytes)
/// Group:  4 bit
/// Number: 12 bit
/// Level:  4 bit
/// IsExc:  1 bit
/// IsAnc:  1 bit.
/// </summary>
private readonly ref struct ShinyItem(Span<byte> data)
{
private readonly Span<byte> _data = data;
public byte Group
{
get => (byte)((this._data[0] >> 4) & 0xF);
set
{
value <<= 4;
value |= (byte)(this._data[0] & 0xF);
this._data[0] = value;
}
}
public ushort Number
{
get => (ushort)(((this._data[0] & 0xF) << 8) + this._data[1]);
set
{
// Higher 4 bits of the first byte for the higher bits of the value
this._data[0] = (byte)((this._data[0] & 0xF0) | (((value & 0x0F00) >> 8) & 0xF));
// The lower bits in the second byte
this._data[1] = (byte)(value & 0xFF);
}
}
/// <summary>
/// Gets or sets the glow level of the item.
/// </summary>
public byte GlowLevel
{
get => (byte)((this._data[2] & 0xF0) >> 4);
set
{
value = (byte)((value & 0xF) << 4);
value |= (byte)(this._data[2] & 0xF);
this._data[2] = value;
}
}
public bool IsExcellent
{
get => (this._data[2] & 0x08) != 0;
set
{
if (value)
{
this._data[2] |= 0b00001000;
}
else
{
this._data[2] &= 0b11110111;
}
}
}
public bool IsAncient
{
get => (this._data[2] & 0x04) != 0;
set
{
if (value)
{
this._data[2] |= 0b00000100;
}
else
{
this._data[2] &= 0b11111011;
}
}
}
}
/// <summary>
/// Unshiny Item: (2 bytes)
/// Group:  4 bit
/// Number: 12 bit.
/// </summary>
private readonly ref struct UnshinyItem(Span<byte> data)
{
private readonly Span<byte> _data = data;
public byte Group
{
get => (byte)((this._data[0] >> 4) & 0xF);
set
{
value <<= 4;
value |= (byte)(this._data[0] & 0xF);
this._data[0] = value;
}
}
public ushort Number
{
get => (ushort)(((this._data[0] & 0xF) << 8) + this._data[1]);
set
{
this._data[1] = (byte)(value & 0xFF);
this._data[0] = (byte)((this._data[0] & 0xF0) | ((value >> 8) & 0xF));
}
}
}
}

View File

@@ -0,0 +1,72 @@
// <copyright file="AddExperienceExtendedPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The extended implementation of the <see cref="IAddExperiencePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.AddExperienceExtendedPlugIn_Name), Description = nameof(PlugInResources.AddExperienceExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("2C5AAEF1-47C9-498F-B3C1-D0F1B9AF0496")]
[MinimumClient(106, 3, ClientLanguage.Invariant)]
public class AddExperienceExtendedPlugIn : IAddExperiencePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="AddExperienceExtendedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public AddExperienceExtendedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask AddExperienceAsync(int exp, IAttackable? obj, ExperienceType experienceType)
{
uint damage = 0;
// Show damage only for party members.
if (obj is not null
&& this._player.Id != obj.LastDeath?.KillerId)
{
damage = (uint)Math.Min(obj.LastDeath?.FinalHit.HealthDamage ?? 0, uint.MaxValue);
}
var killedId = obj?.GetId(this._player) ?? 0;
var killerId = obj?.LastDeath?.KillerId ?? 0;
if (killerId == this._player.Id)
{
killerId = ViewExtensions.ConstantPlayerId;
}
await this._player.Connection.SendExperienceGainedExtendedAsync(
Convert(experienceType),
(uint)exp,
damage,
killedId,
killerId)
.ConfigureAwait(false);
}
private static ExperienceGainedExtended.AddResult Convert(ExperienceType experienceType)
{
return experienceType switch
{
ExperienceType.Normal => ExperienceGainedExtended.AddResult.Normal,
ExperienceType.Master => ExperienceGainedExtended.AddResult.Master,
ExperienceType.MaxLevelReached => ExperienceGainedExtended.AddResult.MaxLevelReached,
ExperienceType.MaxMasterLevelReached => ExperienceGainedExtended.AddResult.MaxMasterLevelReached,
ExperienceType.MonsterLevelTooLowForMasterExperience => ExperienceGainedExtended.AddResult.MonsterLevelTooLowForMasterExperience,
_ => ExperienceGainedExtended.AddResult.Undefined,
};
}
}

View File

@@ -0,0 +1,52 @@
// <copyright file="AddExperiencePlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IAddExperiencePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.AddExperiencePlugIn_Name), Description = nameof(PlugInResources.AddExperiencePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("cc400edd-3540-4727-9b23-8c0ded4f0b00")]
public class AddExperiencePlugIn : IAddExperiencePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="AddExperiencePlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public AddExperiencePlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask AddExperienceAsync(int exp, IAttackable? obj, ExperienceType experienceType)
{
var remainingExperience = exp;
ushort damage = 0;
if (obj is not null && obj.Id != obj.LastDeath?.KillerId)
{
damage = (ushort)Math.Min(obj.LastDeath?.FinalHit.HealthDamage ?? 0, ushort.MaxValue);
}
var id = (ushort)(obj.GetId(this._player) | 0x8000);
while (remainingExperience > 0)
{
// We send multiple exp packets if the value is bigger than ushort.MaxValue, because that's all what the packet can carry.
// On a normal exp server this should never be an issue, but with higher settings, it fixes the problem that the exp bar
// shows less exp than the player actually gained.
ushort sendExp = remainingExperience > ushort.MaxValue ? ushort.MaxValue : (ushort)remainingExperience;
await this._player.Connection.SendExperienceGainedAsync(id, sendExp, damage).ConfigureAwait(false);
damage = 0; // don't send damage again
remainingExperience -= sendExp;
}
}
}

View File

@@ -0,0 +1,45 @@
// <copyright file="ApplyKeyConfigurationPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IApplyKeyConfigurationPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ApplyKeyConfigurationPlugIn_Name), Description = nameof(PlugInResources.ApplyKeyConfigurationPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("6DFF0BF8-2E35-4C1D-9778-3406FCFB4716")]
public class ApplyKeyConfigurationPlugIn : IApplyKeyConfigurationPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ApplyKeyConfigurationPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ApplyKeyConfigurationPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask ApplyKeyConfigurationAsync()
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
var keyConfiguration = this._player.SelectedCharacter?.KeyConfiguration;
if (keyConfiguration is null || keyConfiguration.Length == 0)
{
return;
}
await connection.SendApplyKeyConfigurationAsync(keyConfiguration).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,62 @@
// <copyright file="CharacterAppearanceDataAdapter.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.Character;
using MUnique.OpenMU.DataModel;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic;
/// <summary>
/// Adapter which implements <see cref="IAppearanceData"/> and takes a <see cref="Character"/>.
/// </summary>
internal class CharacterAppearanceDataAdapter : IAppearanceData
{
private readonly Character _character;
/// <summary>
/// Initializes a new instance of the <see cref="CharacterAppearanceDataAdapter"/> class.
/// </summary>
/// <param name="character">The character.</param>
public CharacterAppearanceDataAdapter(Character character)
{
this._character = character;
}
/// <summary>
/// Occurs when the appearance of the player changed.
/// </summary>
/// <remarks>This never happens in this implementation.</remarks>
public event EventHandler? AppearanceChanged;
/// <inheritdoc/>
public CharacterClass? CharacterClass => this._character?.CharacterClass;
/// <inheritdoc/>
public CharacterStatus CharacterStatus => this._character?.CharacterStatus ?? default;
/// <inheritdoc />
public CharacterPose Pose => CharacterPose.Standing;
/// <inheritdoc />
public bool FullAncientSetEquipped => this._character.HasFullAncientSetEquipped();
/// <inheritdoc />
public IEnumerable<ItemAppearance> EquippedItems
{
get
{
if (this._character.Inventory != null)
{
return this._character.Inventory.Items
.Where(item => item.Definition is not null)
.Where(item => item.ItemSlot <= InventoryConstants.LastEquippableItemSlotIndex)
.Select(item => item.GetAppearance());
}
return Enumerable.Empty<ItemAppearance>();
}
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="CharacterFocusedPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="ICharacterFocusedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.CharacterFocusedPlugIn_Name), Description = nameof(PlugInResources.CharacterFocusedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("f6fa0149-3a2c-41c2-aeb1-94b647641ffb")]
public class CharacterFocusedPlugIn : ICharacterFocusedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="CharacterFocusedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public CharacterFocusedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public ValueTask CharacterFocusedAsync(Character character)
{
return this._player.Connection.SendCharacterFocusedAsync(character.Name);
}
}

View File

@@ -0,0 +1,51 @@
// <copyright file="CharacterStatusExtensions.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.Character;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.Network.Packets;
using CharacterStatus = MUnique.OpenMU.Network.Packets.ServerToClient.CharacterStatus;
/// <summary>
/// Extension methods for <see cref="DataModel.Entities.CharacterStatus"/>.
/// </summary>
internal static class CharacterStatusExtensions
{
/// <summary>
/// Converts the status of the data model to the status of the defined packets.
/// </summary>
/// <param name="status">The status of the character in the type of the data model.</param>
/// <returns>The status of the character in the type of the packet definitions.</returns>
public static CharacterStatus Convert(this DataModel.Entities.CharacterStatus status)
{
return status switch
{
DataModel.Entities.CharacterStatus.Normal => CharacterStatus.Normal,
DataModel.Entities.CharacterStatus.Banned => CharacterStatus.Banned,
DataModel.Entities.CharacterStatus.GameMaster => CharacterStatus.GameMaster,
_ => throw new ArgumentException($"Unknown character status {status}"),
};
}
/// <summary>
/// Converts the <see cref="HeroState"/> of the data model to the <see cref="CharacterHeroState"/> of the defined packets.
/// </summary>
/// <param name="heroState">The <see cref="HeroState"/> of the data model.</param>
/// <returns>The <see cref="CharacterHeroState"/> of the defined packets.</returns>
public static CharacterHeroState Convert(this HeroState heroState)
{
return heroState switch
{
HeroState.Normal => CharacterHeroState.Normal,
HeroState.Hero => CharacterHeroState.Hero,
HeroState.LightHero => CharacterHeroState.LightHero,
HeroState.New => CharacterHeroState.New,
HeroState.PlayerKillWarning => CharacterHeroState.PlayerKillWarning,
HeroState.PlayerKiller1stStage => CharacterHeroState.PlayerKiller1stStage,
HeroState.PlayerKiller2ndStage => CharacterHeroState.PlayerKiller2ndStage,
_ => throw new ArgumentException($"Unhandled case of {heroState}."),
};
}
}

View File

@@ -0,0 +1,83 @@
// <copyright file="FruitConsumptionResultPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IFruitConsumptionResponsePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.FruitConsumptionResultPlugIn_Name), Description = nameof(PlugInResources.FruitConsumptionResultPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("0B4D7CA8-181B-4AB5-9522-C826F6311CD8")]
public class FruitConsumptionResultPlugIn : IFruitConsumptionResponsePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="FruitConsumptionResultPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public FruitConsumptionResultPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowResponseAsync(FruitConsumptionResult result, byte statPoints, AttributeDefinition statAttribute)
{
await this._player.Connection.SendFruitConsumptionResponseAsync(Convert(result), statPoints, Convert(statAttribute)).ConfigureAwait(false);
}
private static FruitConsumptionResponse.FruitConsumptionResult Convert(FruitConsumptionResult result)
{
return result switch
{
FruitConsumptionResult.PlusSuccess => FruitConsumptionResponse.FruitConsumptionResult.PlusSuccess,
FruitConsumptionResult.PlusFailed => FruitConsumptionResponse.FruitConsumptionResult.PlusFailed,
FruitConsumptionResult.PlusPrevented => FruitConsumptionResponse.FruitConsumptionResult.PlusPrevented,
FruitConsumptionResult.MinusSuccess => FruitConsumptionResponse.FruitConsumptionResult.MinusSuccess,
FruitConsumptionResult.MinusFailed => FruitConsumptionResponse.FruitConsumptionResult.MinusFailed,
FruitConsumptionResult.MinusPrevented => FruitConsumptionResponse.FruitConsumptionResult.MinusPrevented,
FruitConsumptionResult.MinusSuccessCashShopFruit => FruitConsumptionResponse.FruitConsumptionResult.MinusSuccessCashShopFruit,
FruitConsumptionResult.PlusPreventedByMaximum => FruitConsumptionResponse.FruitConsumptionResult.PlusPreventedByMaximum,
FruitConsumptionResult.MinusPreventedByMaximum => FruitConsumptionResponse.FruitConsumptionResult.MinusPreventedByMaximum,
FruitConsumptionResult.MinusPreventedByDefault => FruitConsumptionResponse.FruitConsumptionResult.MinusPreventedByDefault,
_ => throw new ArgumentException($"Unknown result {result}", nameof(result)),
};
}
private static FruitConsumptionResponse.FruitStatType Convert(AttributeDefinition statAttribute)
{
if (statAttribute == Stats.BaseEnergy)
{
return FruitConsumptionResponse.FruitStatType.Energy;
}
if (statAttribute == Stats.BaseAgility)
{
return FruitConsumptionResponse.FruitStatType.Agility;
}
if (statAttribute == Stats.BaseStrength)
{
return FruitConsumptionResponse.FruitStatType.Strength;
}
if (statAttribute == Stats.BaseVitality)
{
return FruitConsumptionResponse.FruitStatType.Vitality;
}
if (statAttribute == Stats.BaseLeadership)
{
return FruitConsumptionResponse.FruitStatType.Leadership;
}
throw new ArgumentException($"Unknown stat {statAttribute}", nameof(statAttribute));
}
}

View File

@@ -0,0 +1,48 @@
// <copyright file="MasterSkillLevelChangedPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IMasterSkillLevelChangedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.MasterSkillLevelChangedPlugIn_Name), Description = nameof(PlugInResources.MasterSkillLevelChangedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("0eba687e-c7af-421e-8e1e-921fcf31c027")]
public class MasterSkillLevelChangedPlugIn : IMasterSkillLevelChangedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="MasterSkillLevelChangedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public MasterSkillLevelChangedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask MasterSkillLevelChangedAsync(SkillEntry skillEntry)
{
var character = this._player.SelectedCharacter;
if (character?.CharacterClass is null || skillEntry.Skill is null)
{
return;
}
await this._player.Connection.SendMasterSkillLevelUpdateAsync(
true,
(ushort)character.MasterLevelUpPoints,
skillEntry.Skill.GetMasterSkillIndex(character.CharacterClass),
(ushort)skillEntry.Skill.Number,
(byte)skillEntry.Level,
skillEntry.CalculateDisplayValue(),
skillEntry.CalculateNextDisplayValue()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ShowCharacterCreationFailedPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowCharacterCreationFailedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowCharacterCreationFailedPlugIn_Name), Description = nameof(PlugInResources.ShowCharacterCreationFailedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("8fe59675-8625-4837-9872-e8d9c73471cd")]
public class ShowCharacterCreationFailedPlugIn : IShowCharacterCreationFailedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowCharacterCreationFailedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowCharacterCreationFailedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowCharacterCreationFailedAsync()
{
await this._player.Connection.SendCharacterCreationFailedAsync().ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,44 @@
// <copyright file="ShowCharacterDeleteResponsePlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowCharacterDeleteResponsePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowCharacterDeleteResponsePlugIn_Name), Description = nameof(PlugInResources.ShowCharacterDeleteResponsePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("303af9ef-4f7d-4e07-9976-a0241311e50d")]
public class ShowCharacterDeleteResponsePlugIn : IShowCharacterDeleteResponsePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowCharacterDeleteResponsePlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowCharacterDeleteResponsePlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowCharacterDeleteResponseAsync(CharacterDeleteResult result)
{
await this._player.Connection.SendCharacterDeleteResponseAsync(ConvertResult(result)).ConfigureAwait(false);
}
private static CharacterDeleteResponse.CharacterDeleteResult ConvertResult(CharacterDeleteResult result)
{
return result switch
{
CharacterDeleteResult.Successful => CharacterDeleteResponse.CharacterDeleteResult.Successful,
CharacterDeleteResult.Unsuccessful => CharacterDeleteResponse.CharacterDeleteResult.Unsuccessful,
CharacterDeleteResult.WrongSecurityCode => CharacterDeleteResponse.CharacterDeleteResult.WrongSecurityCode,
_ => throw new ArgumentException($"Case {result} is not handled."),
};
}
}

View File

@@ -0,0 +1,101 @@
// <copyright file="ShowCharacterListExtendedPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.GameServer.RemoteView.Guild;
using MUnique.OpenMU.Interfaces;
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="IShowCharacterListPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowCharacterListExtendedPlugIn_Name), Description = nameof(PlugInResources.ShowCharacterListExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("DDDEED0A-9421-4A9B-9ED8-0691B7051666")]
[MinimumClient(106, 3, ClientLanguage.Invariant)]
public class ShowCharacterListExtendedPlugIn : IShowCharacterListPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowCharacterListExtendedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowCharacterListExtendedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowCharacterListAsync()
{
var connection = this._player.Connection;
if (connection is null || this._player.Account is not { } account)
{
return;
}
var unlockFlags = CreateUnlockFlags(account);
await this.SendCharacterListAsync(connection, account, unlockFlags).ConfigureAwait(false);
if (unlockFlags > CharacterCreationUnlockFlags.None)
{
await connection.SendCharacterClassCreationUnlockAsync(unlockFlags).ConfigureAwait(false);
}
}
private static CharacterCreationUnlockFlags CreateUnlockFlags(Account account)
{
byte aggregatedFlags = 0;
var result = account.UnlockedCharacterClasses?
.Select(c => c.CreationAllowedFlag)
.Aggregate(aggregatedFlags, (current, flag) => (byte)(current | flag)) ?? 0;
return (CharacterCreationUnlockFlags)result;
}
private async ValueTask SendCharacterListAsync(IConnection connection, Account account, CharacterCreationUnlockFlags unlockFlags)
{
var guildPositions = new GuildPosition?[account.Characters.Count];
int i = 0;
foreach (var character in account.Characters)
{
guildPositions[i] = await this._player.GameServerContext.GuildServer.GetGuildPositionAsync(character.Id).ConfigureAwait(false);
i++;
}
var appearanceSerializer = this._player.AppearanceSerializer;
int Write()
{
var size = CharacterListExtendedRef.GetRequiredSize(account.Characters.Count);
var span = connection.Output.GetSpan(size)[..size];
var packet = new CharacterListExtendedRef(span)
{
UnlockFlags = unlockFlags,
CharacterCount = (byte)account.Characters.Count,
IsVaultExtended = account.IsVaultExtended,
};
var j = 0;
foreach (var character in account.Characters.OrderBy(c => c.CharacterSlot))
{
var characterData = packet[j];
characterData.SlotIndex = character.CharacterSlot;
characterData.Name = character.Name;
characterData.Level = (ushort)(character.Attributes.FirstOrDefault(s => s.Definition == Stats.Level)?.Value ?? 1);
characterData.Status = character.CharacterStatus.Convert();
characterData.GuildPosition = guildPositions[j].Convert();
appearanceSerializer.WriteAppearanceData(characterData.Appearance, new CharacterAppearanceDataAdapter(character), false);
j++;
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,101 @@
// <copyright file="ShowCharacterListPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.GameServer.RemoteView.Guild;
using MUnique.OpenMU.Interfaces;
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="IShowCharacterListPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowCharacterListPlugIn_Name), Description = nameof(PlugInResources.ShowCharacterListPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("9563dd2c-85cc-4b23-aa95-9d1a18582032")]
[MinimumClient(5, 0, ClientLanguage.Invariant)]
public class ShowCharacterListPlugIn : IShowCharacterListPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowCharacterListPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowCharacterListPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowCharacterListAsync()
{
var connection = this._player.Connection;
if (connection is null || this._player.Account is not { } account)
{
return;
}
var unlockFlags = CreateUnlockFlags(account);
await this.SendCharacterListAsync(connection, account, unlockFlags).ConfigureAwait(false);
if (unlockFlags > CharacterCreationUnlockFlags.None)
{
await connection.SendCharacterClassCreationUnlockAsync(unlockFlags).ConfigureAwait(false);
}
}
private static CharacterCreationUnlockFlags CreateUnlockFlags(Account account)
{
byte aggregatedFlags = 0;
var result = account.UnlockedCharacterClasses?
.Select(c => c.CreationAllowedFlag)
.Aggregate(aggregatedFlags, (current, flag) => (byte)(current | flag)) ?? 0;
return (CharacterCreationUnlockFlags)result;
}
private async ValueTask SendCharacterListAsync(IConnection connection, Account account, CharacterCreationUnlockFlags unlockFlags)
{
var guildPositions = new GuildPosition?[account.Characters.Count];
int i = 0;
foreach (var character in account.Characters)
{
guildPositions[i] = await this._player.GameServerContext.GuildServer.GetGuildPositionAsync(character.Id).ConfigureAwait(false);
i++;
}
var appearanceSerializer = this._player.AppearanceSerializer;
int Write()
{
var size = CharacterListRef.GetRequiredSize(account.Characters.Count);
var span = connection.Output.GetSpan(size)[..size];
var packet = new CharacterListRef(span)
{
UnlockFlags = unlockFlags,
CharacterCount = (byte)account.Characters.Count,
IsVaultExtended = account.IsVaultExtended,
};
var j = 0;
foreach (var character in account.Characters.OrderBy(c => c.CharacterSlot))
{
var characterData = packet[j];
characterData.SlotIndex = character.CharacterSlot;
characterData.Name = character.Name;
characterData.Level = (ushort)(character.Attributes.FirstOrDefault(s => s.Definition == Stats.Level)?.Value ?? 1);
characterData.Status = character.CharacterStatus.Convert();
characterData.GuildPosition = guildPositions[j].Convert();
appearanceSerializer.WriteAppearanceData(characterData.Appearance, new CharacterAppearanceDataAdapter(character), false);
j++;
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,71 @@
// <copyright file="ShowCharacterListPlugIn075.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.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="IShowCharacterListPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowCharacterListPlugIn075_Name), Description = nameof(PlugInResources.ShowCharacterListPlugIn075_Description), ResourceType = typeof(PlugInResources))]
[Guid("650A3478-729E-4995-ADAF-BDEB829A92E5")]
[MinimumClient(0, 75, ClientLanguage.Invariant)]
public class ShowCharacterListPlugIn075 : IShowCharacterListPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowCharacterListPlugIn075"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowCharacterListPlugIn075(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowCharacterListAsync()
{
var connection = this._player.Connection;
if (connection is null || this._player.Account is null)
{
return;
}
var appearanceSerializer = this._player.AppearanceSerializer;
// 0.75 doesn't support dark lord (number 16) and newer classes yet
var supportedCharacters = this._player.Account.Characters.Where(c => c.CharacterClass?.Number < 16).OrderBy(c => c.CharacterSlot).ToList();
int Write()
{
var size = CharacterList075Ref.GetRequiredSize(supportedCharacters.Count);
var span = connection.Output.GetSpan(size)[..size];
var packet = new CharacterList075Ref(span)
{
CharacterCount = (byte)supportedCharacters.Count,
};
int i = 0;
foreach (var character in supportedCharacters.OrderBy(c => c.CharacterSlot))
{
var characterBlock = packet[i];
characterBlock.SlotIndex = character.CharacterSlot;
characterBlock.Name = character.Name;
characterBlock.Level = (ushort)(character.Attributes.FirstOrDefault(s => s.Definition == Stats.Level)?.Value ?? 1);
characterBlock.Status = character.CharacterStatus.Convert();
appearanceSerializer.WriteAppearanceData(characterBlock.Appearance, new CharacterAppearanceDataAdapter(character), false);
i++;
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,71 @@
// <copyright file="ShowCharacterListPlugIn095.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.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="IShowCharacterListPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowCharacterListPlugIn095_Name), Description = nameof(PlugInResources.ShowCharacterListPlugIn095_Description), ResourceType = typeof(PlugInResources))]
[Guid("004E97F5-5817-45F5-BB0E-D4F78007768C")]
[MinimumClient(0, 95, ClientLanguage.Invariant)]
public class ShowCharacterListPlugIn095 : IShowCharacterListPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowCharacterListPlugIn095"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowCharacterListPlugIn095(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowCharacterListAsync()
{
var connection = this._player.Connection;
if (connection is null || this._player.Account is null)
{
return;
}
var appearanceSerializer = this._player.AppearanceSerializer;
// 0.95 doesn't support dark lord (number 16) and newer classes yet
var supportedCharacters = this._player.Account.Characters.Where(c => c.CharacterClass?.Number < 16).OrderBy(c => c.CharacterSlot).ToList();
int Write()
{
var size = CharacterList095Ref.GetRequiredSize(supportedCharacters.Count);
var span = connection.Output.GetSpan(size)[..size];
var packet = new CharacterList095Ref(span)
{
CharacterCount = (byte)supportedCharacters.Count,
};
int i = 0;
foreach (var character in supportedCharacters.OrderBy(c => c.CharacterSlot))
{
var characterBlock = packet[i];
characterBlock.SlotIndex = character.CharacterSlot;
characterBlock.Name = character.Name;
characterBlock.Level = (ushort)(character.Attributes.FirstOrDefault(s => s.Definition == Stats.Level)?.Value ?? 1);
characterBlock.Status = character.CharacterStatus.Convert();
appearanceSerializer.WriteAppearanceData(characterBlock.Appearance, new CharacterAppearanceDataAdapter(character), false);
i++;
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,60 @@
// <copyright file="ShowCreatedCharacterPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowCreatedCharacterPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowCreatedCharacterPlugIn_Name), Description = nameof(PlugInResources.ShowCreatedCharacterPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("f5494592-ff7b-4f7f-b0c1-bd242a69fb8f")]
public class ShowCreatedCharacterPlugIn : IShowCreatedCharacterPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowCreatedCharacterPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowCreatedCharacterPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowCreatedCharacterAsync(Character character)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
int Write()
{
var size = CharacterCreationSuccessfulRef.Length;
var span = connection.Output.GetSpan(size)[..size];
var packet = new CharacterCreationSuccessfulRef(span)
{
CharacterName = character.Name,
CharacterSlot = character.CharacterSlot,
Level = (ushort)(character.Attributes.FirstOrDefault(a => a.Definition == Stats.Level)?.Value ?? 0),
Class = (CharacterClassNumber)character.CharacterClass!.Number,
CharacterStatus = (byte)character.CharacterStatus,
};
packet.PreviewData.Fill(0xFF);
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ShowDialogPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowDialogPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowDialogPlugIn_Name), Description = nameof(PlugInResources.ShowDialogPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("DCAB5737-2B44-408F-A14D-C0FD3B5F6516")]
public class ShowDialogPlugIn : IShowDialogPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowDialogPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowDialogPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public ValueTask ShowDialogAsync(byte categoryNumber, byte dialogNumber)
{
return this._player.Connection.SendServerCommandAsync(categoryNumber, dialogNumber, 0);
}
}

View File

@@ -0,0 +1,53 @@
// <copyright file="ShowEffectPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowEffectPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowEffectPlugIn_Name), Description = nameof(PlugInResources.ShowEffectPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("EC433D54-F9CE-40FD-8848-F3515DDD43CF")]
public class ShowEffectPlugIn : IShowEffectPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowEffectPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowEffectPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask ShowEffectAsync(IIdentifiable target, IShowEffectPlugIn.EffectType effectType)
{
if (effectType == IShowEffectPlugIn.EffectType.Swirl)
{
await this._player.Connection.SendShowSwirlAsync(target.GetId(this._player)).ConfigureAwait(false);
}
else
{
await this._player.Connection.SendShowEffectAsync(target.GetId(this._player), Convert(effectType)).ConfigureAwait(false);
}
}
private static ShowEffect.EffectType Convert(IShowEffectPlugIn.EffectType effectType)
{
return effectType switch
{
IShowEffectPlugIn.EffectType.LevelUp => Network.Packets.ServerToClient.ShowEffect.EffectType.LevelUp,
IShowEffectPlugIn.EffectType.ShieldLost => Network.Packets.ServerToClient.ShowEffect.EffectType.ShieldLost,
IShowEffectPlugIn.EffectType.ShieldPotion => Network.Packets.ServerToClient.ShowEffect.EffectType.ShieldPotion,
_ => throw new ArgumentOutOfRangeException(nameof(effectType), $"Unknown value {effectType}."),
};
}
}

View File

@@ -0,0 +1,49 @@
// <copyright file="ShowItemDropEffectPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Pathfinding;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="ShowItemDropEffectPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowItemDropEffectPlugIn_Name), Description = nameof(PlugInResources.ShowItemDropEffectPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("AA949D5E-0CC6-424E-8412-DE12EA294E33")]
public class ShowItemDropEffectPlugIn : IShowItemDropEffectPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowItemDropEffectPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowItemDropEffectPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask ShowEffectAsync(ItemDropEffect effect, Point targetCoordinates)
{
var (x, y) = targetCoordinates;
switch (effect)
{
case ItemDropEffect.Fireworks:
await this._player.Connection.SendShowFireworksAsync(x, y).ConfigureAwait(false);
break;
case ItemDropEffect.ChristmasFireworks:
await this._player.Connection.SendShowChristmasFireworksAsync(x, y).ConfigureAwait(false);
break;
case ItemDropEffect.FanfareSound:
await this._player.Connection.SendPlayFanfareSoundAsync(x, y).ConfigureAwait(false);
break;
default:
throw new ArgumentOutOfRangeException(nameof(effect));
}
}
}

View File

@@ -0,0 +1,214 @@
// <copyright file="SkillListViewPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Views.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="ISkillListViewPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.SkillListViewPlugIn_Name), Description = nameof(PlugInResources.SkillListViewPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("E67BB791-5BE7-4CC8-B2C9-38E86158A356")]
[MinimumClient(3, 0, ClientLanguage.Invariant)]
public class SkillListViewPlugIn : ISkillListViewPlugIn
{
private const short Explosion79SkillId = 79;
private const short ForceSkillId = 60;
private const short ForceWaveSkillId = 66;
private const short ForceWaveStrengSkillId = 509;
private const short KillingBlowSkillId = 260;
private const short BeastUppercutSkillId = 261;
private const short KillingBlowStrengSkillId = 551;
private const short BeastUppercutStrengSkillId = 552;
private const short KillingBlowMasterySkillId = 554;
private const short BeastUppercutMasterySkillId = 555;
private readonly RemotePlayer _player;
/// <summary>
/// This contains again all available skills. However, we need this to maintain the indexes. It can happen that the list contains holes after a skill got removed!.
/// </summary>
private IList<Skill?>? _skillList;
/// <summary>
/// Initializes a new instance of the <see cref="SkillListViewPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public SkillListViewPlugIn(RemotePlayer player)
{
this._player = player;
}
/// <summary>
/// Gets the internal skill list.
/// </summary>
protected IList<Skill?> SkillList => this._skillList ??= new List<Skill?>();
/// <summary>
/// Gets the player.
/// </summary>
protected RemotePlayer Player => this._player;
/// <inheritdoc/>
public virtual async ValueTask AddSkillAsync(Skill skill)
{
if (skill.Number == ForceWaveSkillId)
{
return;
}
if (skill.Number == KillingBlowSkillId
&& this.SkillList.Any(s => s?.Number == KillingBlowStrengSkillId || s?.Number == KillingBlowMasterySkillId))
{
return;
}
if (skill.Number == BeastUppercutSkillId
&& this.SkillList.Any(s => s?.Number == BeastUppercutStrengSkillId || s?.Number == BeastUppercutMasterySkillId))
{
return;
}
var skillIndex = this.AddSkillToList(skill);
await this._player.Connection.SendSkillAddedAsync(skillIndex, (ushort)skill.Number, 0).ConfigureAwait(false);
}
/// <inheritdoc/>
public virtual async ValueTask RemoveSkillAsync(Skill skill)
{
if (skill.Number == ForceWaveSkillId
&& this.SkillList.Any(s => s?.Number == ForceWaveStrengSkillId)
&& this.SkillList.FirstOrDefault(s => s?.Number == ForceSkillId) is { } forceSkill)
{
// Force wave strengthener is replacing force skill
skill = forceSkill;
}
var skillIndex = this.SkillList.IndexOf(skill);
if (skillIndex >= 0)
{
await this._player.Connection.SendSkillRemovedAsync((byte)skillIndex, (ushort)skill.Number).ConfigureAwait(false);
this.SkillList[skillIndex] = null;
}
}
/// <inheritdoc/>
public virtual async ValueTask UpdateSkillListAsync()
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
this.BuildSkillList();
int Write()
{
var size = SkillListUpdateRef.GetRequiredSize(this.SkillList.Count);
var span = connection.Output.GetSpan(size)[..size];
var packet = new SkillListUpdateRef(span)
{
Count = (byte)this.SkillList.Count,
};
for (byte i = 0; i < this.SkillList.Count; i++)
{
var skillEntry = packet[i];
skillEntry.SkillIndex = i;
var skill = this.SkillList[i];
if (skill is not null)
{
skillEntry.SkillNumber = (ushort)skill.Number;
if (skill.MasterDefinition is not null)
{
skillEntry.SkillLevel = (byte)(this._player.SkillList!.GetSkill((ushort)skill.Number)?.Level ?? 0);
}
}
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
/// <inheritdoc />
public Skill? GetSkillByIndex(byte skillIndex)
{
if (this._skillList != null && this._skillList.Count > skillIndex)
{
return this._skillList[skillIndex];
}
return null;
}
/// <summary>
/// Builds the internal skill list, considering duplicates.
/// </summary>
protected void BuildSkillList()
{
this.SkillList.Clear();
var skills = this._player.SkillList!.Skills.ToList();
var replacedSkills = skills.Select(entry => entry.Skill?.MasterDefinition?.ReplacedSkill).Where(skill => skill != null);
skills.RemoveAll(s => replacedSkills.Contains(s.Skill));
skills.RemoveAll(s => s.Skill?.SkillType == SkillType.PassiveBoost);
skills.RemoveAll(s => s.Skill?.Number == ForceWaveSkillId || s.Skill?.Number == Explosion79SkillId);
if (skills.Any(s => s.Skill?.Number == ForceWaveStrengSkillId))
{
skills.RemoveAll(s => s.Skill?.Number == ForceSkillId);
}
foreach (var skillEntry in skills.Distinct(default(SkillEqualityComparer)))
{
this.SkillList.Add(skillEntry.Skill);
}
}
/// <summary>
/// Adds the skill to the internal skill list.
/// </summary>
/// <param name="skill">The skill to add.</param>
/// <returns>The index of the added skill.</returns>
protected byte AddSkillToList(Skill skill)
{
for (byte i = 0; i < this.SkillList.Count; i++)
{
if (this.SkillList[i] is null)
{
this.SkillList[i] = skill;
return i;
}
}
this.SkillList.Add(skill);
return (byte)(this.SkillList.Count - 1);
}
private struct SkillEqualityComparer : IEqualityComparer<SkillEntry>
{
public bool Equals(SkillEntry? left, SkillEntry? right)
{
return Equals(left?.Skill, right?.Skill);
}
public int GetHashCode(SkillEntry obj)
{
return obj.GetHashCode();
}
}
}

View File

@@ -0,0 +1,101 @@
// <copyright file="SkillListViewPlugIn075.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="ISkillListViewPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.SkillListViewPlugIn075_Name), Description = nameof(PlugInResources.SkillListViewPlugIn075_Description), ResourceType = typeof(PlugInResources))]
[Guid("D83A0CD8-AFEB-4782-8523-AF6D093D14CB")]
public class SkillListViewPlugIn075 : SkillListViewPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="SkillListViewPlugIn075"/> class.
/// </summary>
/// <param name="player">The player.</param>
public SkillListViewPlugIn075(RemotePlayer player)
: base(player)
{
}
/// <inheritdoc/>
public override async ValueTask AddSkillAsync(Skill skill)
{
var skillIndex = this.AddSkillToList(skill);
await this.Player.Connection.SendSkillAdded075Async(skillIndex, this.GetSkillNumberAndLevel(skill)).ConfigureAwait(false);
}
/// <inheritdoc/>
public override async ValueTask RemoveSkillAsync(Skill skill)
{
var skillIndex = (byte)this.SkillList.IndexOf(skill);
await this.Player.Connection.SendSkillRemoved075Async(skillIndex, this.GetSkillNumberAndLevel(skill)).ConfigureAwait(false);
this.SkillList[skillIndex] = null;
}
/// <inheritdoc/>
public override async ValueTask UpdateSkillListAsync()
{
var connection = this.Player.Connection;
if (connection is null)
{
return;
}
this.BuildSkillList();
int Write()
{
var size = SkillListUpdate075Ref.GetRequiredSize(this.SkillList.Count);
var span = connection.Output.GetSpan(size)[..size];
var packet = new SkillListUpdate075Ref(span)
{
Count = (byte)this.SkillList.Count,
};
for (byte i = 0; i < this.SkillList.Count; i++)
{
var skillEntry = packet[i];
skillEntry.SkillIndex = i;
skillEntry.SkillNumberAndLevel = this.GetSkillNumberAndLevel(this.SkillList[i]);
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
/// <summary>
/// Gets the value for the skill number and level.
/// </summary>
/// <param name="skill">The skill.</param>
/// <returns>The value for the skill number and level.</returns>
protected ushort GetSkillNumberAndLevel(Skill? skill)
{
if (skill is null)
{
return 0;
}
var result = (ushort)((skill.Number & 0xFF) << 8);
// The next lines seems strange but is correct. The same part of the skill number is already set in the first byte.
// Unfortunately it's unclear to us which skill got a level greater than 0 in these early versions.
// It might be the item level of a weapon with a skill, but this should not be of interest for the client.
// It could be the type of summoning orb skill, too. For now, we just don't send this level.
var skillLevel = 0;
var secondByte = (byte)((skill.Number & 7) | (skillLevel << 3));
return (ushort)(result + secondByte);
}
}

View File

@@ -0,0 +1,49 @@
// <copyright file="SkillListViewPlugIn095.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="ISkillListViewPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
/// <remarks>
/// Version 0.97d seems to be compatible to this implementation. This may be true until the end of season 2, but we're not sure.
/// </remarks>
[PlugIn]
[Display(Name = nameof(PlugInResources.SkillListViewPlugIn095_Name), Description = nameof(PlugInResources.SkillListViewPlugIn095_Description), ResourceType = typeof(PlugInResources))]
[Guid("60126898-8668-4774-B879-0F211CDD3617")]
[MinimumClient(0, 95, ClientLanguage.Invariant)]
public class SkillListViewPlugIn095 : SkillListViewPlugIn075
{
/// <summary>
/// Initializes a new instance of the <see cref="SkillListViewPlugIn095"/> class.
/// </summary>
/// <param name="player">The player.</param>
public SkillListViewPlugIn095(RemotePlayer player)
: base(player)
{
}
/// <inheritdoc/>
public override async ValueTask AddSkillAsync(Skill skill)
{
var skillIndex = this.AddSkillToList(skill);
await this.Player.Connection.SendSkillAdded095Async(skillIndex, this.GetSkillNumberAndLevel(skill)).ConfigureAwait(false);
}
/// <inheritdoc/>
public override async ValueTask RemoveSkillAsync(Skill skill)
{
var skillIndex = (byte)this.SkillList.IndexOf(skill);
await this.Player.Connection.SendSkillRemoved095Async(skillIndex, this.GetSkillNumberAndLevel(skill)).ConfigureAwait(false);
this.SkillList[skillIndex] = null;
}
}

View File

@@ -0,0 +1,49 @@
// <copyright file="StatIncreaseResultExtendedPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The extended implementation of the <see cref="IStatIncreaseResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.StatIncreaseResultExtendedPlugIn_Name), Description = nameof(PlugInResources.StatIncreaseResultExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("4B9AEFF1-B139-45F9-9277-0FBDA7A3C020")]
[MinimumClient(106, 3, ClientLanguage.Invariant)]
public class StatIncreaseResultExtendedPlugIn : IStatIncreaseResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="StatIncreaseResultExtendedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public StatIncreaseResultExtendedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask StatIncreaseResultAsync(AttributeDefinition attribute, ushort addedPoints)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
await connection.SendCharacterStatIncreaseResponseExtendedAsync(
attribute.GetStatType(),
addedPoints,
(uint)this._player.Attributes![Stats.MaximumHealth],
(uint)this._player.Attributes![Stats.MaximumMana],
(uint)this._player.Attributes![Stats.MaximumShield],
(uint)this._player.Attributes![Stats.MaximumAbility]).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,77 @@
// <copyright file="StatIncreaseResultPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.GameLogic.Views.World;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IStatIncreaseResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.StatIncreaseResultPlugIn_Name), Description = nameof(PlugInResources.StatIncreaseResultPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("ce603b3c-cf25-426f-9cb9-5cc367843de8")]
public class StatIncreaseResultPlugIn : IStatIncreaseResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="StatIncreaseResultPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public StatIncreaseResultPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask StatIncreaseResultAsync(AttributeDefinition attribute, ushort addedPoints)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
if (addedPoints <= 1)
{
#pragma warning disable SA1118 // Parameter should not span multiple lines
await connection.SendCharacterStatIncreaseResponseAsync(
addedPoints > 0,
attribute.GetStatType(),
attribute == Stats.BaseEnergy
? (ushort)this._player.Attributes![Stats.MaximumMana]
: attribute == Stats.BaseVitality
? (ushort)this._player.Attributes![Stats.MaximumHealth]
: default,
(ushort)this._player.Attributes![Stats.MaximumShield],
(ushort)this._player.Attributes[Stats.MaximumAbility]).ConfigureAwait(false);
#pragma warning restore SA1118 // Parameter should not span multiple lines
return;
}
// Workaround with multiple points for older clients
var player = this._player;
var map = player.CurrentMap!;
await player.InvokeViewPlugInAsync<IObjectsOutOfScopePlugIn>(p => p.ObjectsOutOfScopeAsync(player.GetAsEnumerable())).ConfigureAwait(false);
await player.InvokeViewPlugInAsync<IUpdateCharacterStatsPlugIn>(p => p.UpdateCharacterStatsAsync()).ConfigureAwait(false);
await player.InvokeViewPlugInAsync<IUpdateInventoryListPlugIn>(p => p.UpdateInventoryListAsync()).ConfigureAwait(false);
var currentGate = new Persistence.BasicModel.ExitGate
{
Map = map.Definition,
X1 = player.Position.X,
X2 = player.Position.X,
Y1 = player.Position.Y,
Y2 = player.Position.Y,
};
await player.WarpToAsync(currentGate).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,49 @@
// <copyright file="UpdateCharacterBaseStatsExtendedPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The extended implementation of the <see cref="IUpdateCharacterBaseStatsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateCharacterBaseStatsExtendedPlugIn_Name), Description = nameof(PlugInResources.UpdateCharacterBaseStatsExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("851C4579-FB3D-454C-A238-217542E8E6B9")]
[MinimumClient(106, 3, ClientLanguage.Invariant)]
public class UpdateCharacterBaseStatsExtendedPlugIn : IUpdateCharacterBaseStatsPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateCharacterBaseStatsExtendedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateCharacterBaseStatsExtendedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateCharacterBaseStatsAsync()
{
var connection = this._player.Connection;
if (connection is null || this._player.Account is null)
{
return;
}
await connection.SendBaseStatsExtendedAsync(
(uint)this._player.Attributes![Stats.BaseStrength],
(uint)this._player.Attributes[Stats.BaseAgility],
(uint)this._player.Attributes[Stats.BaseVitality],
(uint)this._player.Attributes[Stats.BaseEnergy],
(uint)this._player.Attributes[Stats.BaseLeadership])
.ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,38 @@
// <copyright file="UpdateCharacterHeroStatePlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IUpdateCharacterHeroStatePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateCharacterHeroStatePlugIn_Name), Description = nameof(PlugInResources.UpdateCharacterHeroStatePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("d1ce36d6-cbdd-4bcb-99c7-c7495d8597d9")]
public class UpdateCharacterHeroStatePlugIn : IUpdateCharacterHeroStatePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateCharacterHeroStatePlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateCharacterHeroStatePlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateCharacterHeroStateAsync(Player affectedPlayer)
{
if (affectedPlayer.SelectedCharacter is { } character)
{
await this._player.Connection.SendHeroStateChangedAsync(affectedPlayer.GetId(this._player), character.State.Convert()).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,81 @@
// <copyright file="UpdateCharacterStatsExtendedPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The extended implementation of the <see cref="IUpdateCharacterStatsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateCharacterStatsExtendedPlugIn_Name), Description = nameof(PlugInResources.UpdateCharacterStatsExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("851C4579-FB3D-454C-A238-217542E8E6B8")]
[MinimumClient(106, 3, ClientLanguage.Invariant)]
public class UpdateCharacterStatsExtendedPlugIn : IUpdateCharacterStatsPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateCharacterStatsExtendedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateCharacterStatsExtendedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateCharacterStatsAsync()
{
var connection = this._player.Connection;
if (connection is null || this._player.Account is null)
{
return;
}
var maxAttackSpeed = this._player.GameContext.Configuration.Attributes.FirstOrDefault(a => a == Stats.AttackSpeed)?.MaximumValue ?? 200;
await connection.SendCharacterInformationExtendedAsync(
this._player.Position.X,
this._player.Position.Y,
this._player.SelectedCharacter!.CurrentMap!.Number.ToUnsigned(),
(ulong)this._player.SelectedCharacter.Experience,
(ulong)this._player.GameServerContext.ExperienceTable[(int)this._player.Attributes![Stats.Level] + 1],
(ushort)Math.Max(0, this._player.SelectedCharacter.LevelUpPoints),
(ushort)this._player.Attributes[Stats.BaseStrength],
(ushort)this._player.Attributes[Stats.BaseAgility],
(ushort)this._player.Attributes[Stats.BaseVitality],
(ushort)this._player.Attributes[Stats.BaseEnergy],
(ushort)this._player.Attributes[Stats.BaseLeadership],
(uint)this._player.Attributes[Stats.CurrentHealth],
(uint)this._player.Attributes[Stats.MaximumHealth],
(uint)this._player.Attributes[Stats.CurrentMana],
(uint)this._player.Attributes[Stats.MaximumMana],
(uint)this._player.Attributes[Stats.CurrentShield],
(uint)this._player.Attributes[Stats.MaximumShield],
(uint)this._player.Attributes[Stats.CurrentAbility],
(uint)this._player.Attributes[Stats.MaximumAbility],
(uint)this._player.Money,
this._player.SelectedCharacter.State.Convert(),
this._player.SelectedCharacter.CharacterStatus.Convert(),
(ushort)this._player.SelectedCharacter.UsedFruitPoints,
this._player.SelectedCharacter.GetMaximumFruitPoints(),
(ushort)this._player.SelectedCharacter.UsedNegFruitPoints,
this._player.SelectedCharacter.GetMaximumFruitPoints(),
(ushort)this._player.Attributes[Stats.AttackSpeed],
(ushort)this._player.Attributes[Stats.MagicSpeed],
(ushort)maxAttackSpeed,
(byte)this._player.SelectedCharacter.InventoryExtensions,
(ushort)this._player.Attributes[Stats.Resets])
.ConfigureAwait(false);
if (this._player.SelectedCharacter.CharacterClass!.IsMasterClass)
{
await this._player.InvokeViewPlugInAsync<IUpdateMasterStatsPlugIn>(p => p.SendMasterStatsAsync()).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,76 @@
// <copyright file="UpdateCharacterStatsPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IUpdateCharacterStatsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateCharacterStatsPlugIn_Name), Description = nameof(PlugInResources.UpdateCharacterStatsPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("6eb967c2-b5a2-4510-9d88-5eccc963a6ea")]
[MinimumClient(5, 0, ClientLanguage.Invariant)]
public class UpdateCharacterStatsPlugIn : IUpdateCharacterStatsPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateCharacterStatsPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateCharacterStatsPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateCharacterStatsAsync()
{
var connection = this._player.Connection;
if (connection is null || this._player.Account is null)
{
return;
}
await connection.SendCharacterInformationAsync(
this._player.Position.X,
this._player.Position.Y,
this._player.SelectedCharacter!.CurrentMap!.Number.ToUnsigned(),
(ulong)this._player.SelectedCharacter.Experience,
(ulong)this._player.GameServerContext.ExperienceTable[(int)this._player.Attributes![Stats.Level] + 1],
(ushort)Math.Max(0, this._player.SelectedCharacter.LevelUpPoints),
(ushort)this._player.Attributes[Stats.BaseStrength],
(ushort)this._player.Attributes[Stats.BaseAgility],
(ushort)this._player.Attributes[Stats.BaseVitality],
(ushort)this._player.Attributes[Stats.BaseEnergy],
(ushort)this._player.Attributes[Stats.CurrentHealth],
(ushort)this._player.Attributes[Stats.MaximumHealth],
(ushort)this._player.Attributes[Stats.CurrentMana],
(ushort)this._player.Attributes[Stats.MaximumMana],
(ushort)this._player.Attributes[Stats.CurrentShield],
(ushort)this._player.Attributes[Stats.MaximumShield],
(ushort)this._player.Attributes[Stats.CurrentAbility],
(ushort)this._player.Attributes[Stats.MaximumAbility],
(uint)this._player.Money,
this._player.SelectedCharacter.State.Convert(),
this._player.SelectedCharacter.CharacterStatus.Convert(),
(ushort)this._player.SelectedCharacter.UsedFruitPoints,
this._player.SelectedCharacter.GetMaximumFruitPoints(),
(ushort)this._player.Attributes[Stats.BaseLeadership],
(ushort)this._player.SelectedCharacter.UsedNegFruitPoints,
this._player.SelectedCharacter.GetMaximumFruitPoints(),
(byte)this._player.SelectedCharacter.InventoryExtensions)
.ConfigureAwait(false);
if (this._player.SelectedCharacter.CharacterClass!.IsMasterClass)
{
await this._player.InvokeViewPlugInAsync<IUpdateMasterStatsPlugIn>(p => p.SendMasterStatsAsync()).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,63 @@
// <copyright file="UpdateCharacterStatsPlugIn075.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IUpdateCharacterStatsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateCharacterStatsPlugIn075_Name), Description = nameof(PlugInResources.UpdateCharacterStatsPlugIn075_Description), ResourceType = typeof(PlugInResources))]
[Guid("C180561D-E055-41CA-817C-44E7A985937C")]
[MinimumClient(0, 75, ClientLanguage.Invariant)]
public class UpdateCharacterStatsPlugIn075 : IUpdateCharacterStatsPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateCharacterStatsPlugIn075"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateCharacterStatsPlugIn075(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateCharacterStatsAsync()
{
var connection = this._player.Connection;
if (connection is null || this._player.Account is null)
{
return;
}
await connection.SendCharacterInformation075Async(
this._player.Position.X,
this._player.Position.Y,
(byte)this._player.SelectedCharacter!.CurrentMap!.Number,
(uint)this._player.SelectedCharacter.Experience,
(uint)this._player.GameServerContext.ExperienceTable[(int)this._player.Attributes![Stats.Level] + 1],
(ushort)Math.Max(this._player.SelectedCharacter.LevelUpPoints, 0),
(ushort)this._player.Attributes[Stats.BaseStrength],
(ushort)this._player.Attributes[Stats.BaseAgility],
(ushort)this._player.Attributes[Stats.BaseVitality],
(ushort)this._player.Attributes[Stats.BaseEnergy],
(ushort)this._player.Attributes[Stats.CurrentHealth],
(ushort)this._player.Attributes[Stats.MaximumHealth],
(ushort)this._player.Attributes[Stats.CurrentMana],
(ushort)this._player.Attributes[Stats.MaximumMana],
(uint)this._player.Money,
this._player.SelectedCharacter.State.Convert(),
this._player.SelectedCharacter.CharacterStatus.Convert())
.ConfigureAwait(false);
await this._player.InvokeViewPlugInAsync<IApplyKeyConfigurationPlugIn>(p => p.ApplyKeyConfigurationAsync()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,69 @@
// <copyright file="UpdateCharacterStatsPlugIn097.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IUpdateCharacterStatsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateCharacterStatsPlugIn097_Name), Description = nameof(PlugInResources.UpdateCharacterStatsPlugIn097_Description), ResourceType = typeof(PlugInResources))]
[Guid("8ACD9D6B-6FA7-42C3-8C07-E137655CB92F")]
[MinimumClient(0, 97, ClientLanguage.Invariant)]
public class UpdateCharacterStatsPlugIn097 : IUpdateCharacterStatsPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateCharacterStatsPlugIn097"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateCharacterStatsPlugIn097(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateCharacterStatsAsync()
{
var connection = this._player.Connection;
if (connection is null || this._player.Account is null)
{
return;
}
await connection.SendCharacterInformation097Async(
this._player.Position.X,
this._player.Position.Y,
(byte)this._player.SelectedCharacter!.CurrentMap!.Number,
this._player.Rotation.ToPacketByte(),
(uint)this._player.SelectedCharacter.Experience,
(uint)this._player.GameServerContext.ExperienceTable[(int)this._player.Attributes![Stats.Level] + 1],
(ushort)Math.Max(this._player.SelectedCharacter.LevelUpPoints, 0),
(ushort)this._player.Attributes[Stats.BaseStrength],
(ushort)this._player.Attributes[Stats.BaseAgility],
(ushort)this._player.Attributes[Stats.BaseVitality],
(ushort)this._player.Attributes[Stats.BaseEnergy],
(ushort)this._player.Attributes[Stats.CurrentHealth],
(ushort)this._player.Attributes[Stats.MaximumHealth],
(ushort)this._player.Attributes[Stats.CurrentMana],
(ushort)this._player.Attributes[Stats.MaximumMana],
(ushort)this._player.Attributes[Stats.CurrentAbility],
(ushort)this._player.Attributes[Stats.MaximumAbility],
(uint)this._player.Money,
this._player.SelectedCharacter.State.Convert(),
this._player.SelectedCharacter.CharacterStatus.Convert(),
(ushort)this._player.SelectedCharacter.UsedFruitPoints,
this._player.SelectedCharacter.GetMaximumFruitPoints(),
(ushort)this._player.Attributes[Stats.BaseLeadership])
.ConfigureAwait(false);
await this._player.InvokeViewPlugInAsync<IApplyKeyConfigurationPlugIn>(p => p.ApplyKeyConfigurationAsync()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,83 @@
// <copyright file="UpdateLevelExtendedPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Properties;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
using PlugInResources = MUnique.OpenMU.GameServer.Properties.PlugInResources;
/// <summary>
/// The extended implementation of the <see cref="IUpdateLevelPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateLevelExtendedPlugIn_Name), Description = nameof(PlugInResources.UpdateLevelExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("6F358202-6678-4417-9537-D8739AEF78C2")]
[MinimumClient(106, 3, ClientLanguage.Invariant)]
public class UpdateLevelExtendedPlugIn : IUpdateLevelPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateLevelExtendedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateLevelExtendedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateLevelAsync()
{
var selectedCharacter = this._player.SelectedCharacter;
var charStats = this._player.Attributes;
var connection = this._player.Connection;
if (selectedCharacter is null || charStats is null || connection is null)
{
return;
}
await connection.SendCharacterLevelUpdateExtendedAsync(
(ushort)charStats[Stats.Level],
(ushort)Math.Max(selectedCharacter.LevelUpPoints, 0),
(uint)charStats[Stats.MaximumHealth],
(uint)charStats[Stats.MaximumMana],
(uint)charStats[Stats.MaximumShield],
(uint)charStats[Stats.MaximumAbility],
(ushort)selectedCharacter.UsedFruitPoints,
selectedCharacter.GetMaximumFruitPoints(),
(ushort)selectedCharacter.UsedNegFruitPoints,
selectedCharacter.GetMaximumFruitPoints()).ConfigureAwait(false);
await this._player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.LevelUpCongrats), charStats[Stats.Level]).ConfigureAwait(false);
}
/// <inheritdoc/>
public async ValueTask UpdateMasterLevelAsync()
{
var selectedCharacter = this._player.SelectedCharacter;
var charStats = this._player.Attributes;
var connection = this._player.Connection;
if (selectedCharacter is null || charStats is null || connection is null)
{
return;
}
await connection.SendMasterCharacterLevelUpdateExtendedAsync(
(ushort)charStats[Stats.MasterLevel],
(ushort)charStats[Stats.MasterPointsPerLevelUp],
(ushort)selectedCharacter.MasterLevelUpPoints,
(ushort)this._player.GameContext.Configuration.MaximumMasterLevel,
(uint)charStats[Stats.MaximumHealth],
(uint)charStats[Stats.MaximumMana],
(uint)charStats[Stats.MaximumShield],
(uint)charStats[Stats.MaximumAbility]).ConfigureAwait(false);
await this._player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MasterLevelUpCongrats), charStats[Stats.MasterLevel]).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,81 @@
// <copyright file="UpdateLevelPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Properties;
using MUnique.OpenMU.GameLogic.Views.Character;
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="IUpdateLevelPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateLevelPlugIn_Name), Description = nameof(PlugInResources.UpdateLevelPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("1ff3709e-d99b-4c00-b926-efce281b3997")]
public class UpdateLevelPlugIn : IUpdateLevelPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateLevelPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateLevelPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateLevelAsync()
{
var selectedCharacter = this._player.SelectedCharacter;
var charStats = this._player.Attributes;
var connection = this._player.Connection;
if (selectedCharacter is null || charStats is null || connection is null)
{
return;
}
await connection.SendCharacterLevelUpdateAsync(
(ushort)charStats[Stats.Level],
(ushort)Math.Max(selectedCharacter.LevelUpPoints, 0),
(ushort)charStats[Stats.MaximumHealth],
(ushort)charStats[Stats.MaximumMana],
(ushort)charStats[Stats.MaximumShield],
(ushort)charStats[Stats.MaximumAbility],
(ushort)selectedCharacter.UsedFruitPoints,
selectedCharacter.GetMaximumFruitPoints(),
(ushort)selectedCharacter.UsedNegFruitPoints,
selectedCharacter.GetMaximumFruitPoints()).ConfigureAwait(false);
await this._player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.LevelUpCongrats), charStats[Stats.Level]).ConfigureAwait(false);
}
/// <inheritdoc/>
public async ValueTask UpdateMasterLevelAsync()
{
var selectedCharacter = this._player.SelectedCharacter;
var charStats = this._player.Attributes;
var connection = this._player.Connection;
if (selectedCharacter is null || charStats is null || connection is null)
{
return;
}
await connection.SendMasterCharacterLevelUpdateAsync(
(ushort)charStats[Stats.MasterLevel],
(ushort)charStats[Stats.MasterPointsPerLevelUp],
(ushort)selectedCharacter.MasterLevelUpPoints,
(ushort)this._player.GameContext.Configuration.MaximumMasterLevel,
(ushort)charStats[Stats.MaximumHealth],
(ushort)charStats[Stats.MaximumMana],
(ushort)charStats[Stats.MaximumShield],
(ushort)charStats[Stats.MaximumAbility]).ConfigureAwait(false);
await this._player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MasterLevelUpCongrats), charStats[Stats.MasterLevel]).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,70 @@
// <copyright file="UpdateMasterSkillsPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IUpdateMasterSkillsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateMasterSkillsPlugIn_Name), Description = nameof(PlugInResources.UpdateMasterSkillsPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("72942fe8-925d-43b0-a908-b814b2baa1f3")]
public class UpdateMasterSkillsPlugIn : IUpdateMasterSkillsPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateMasterSkillsPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateMasterSkillsPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask UpdateMasterSkillsAsync()
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
var masterSkills = this._player.SkillList?.Skills.Where(s => s.Skill?.MasterDefinition != null).ToList();
if (masterSkills is null || this._player.SelectedCharacter?.CharacterClass is null)
{
return;
}
int Write()
{
var size = MasterSkillListRef.GetRequiredSize(masterSkills.Count);
var span = connection.Output.GetSpan(size)[..size];
var packet = new MasterSkillListRef(span)
{
MasterSkillCount = (uint)masterSkills.Count,
};
int i = 0;
foreach (var masterSkill in masterSkills)
{
var skillsBlock = packet[i];
skillsBlock.MasterSkillIndex = masterSkill.Skill!.GetMasterSkillIndex(this._player.SelectedCharacter.CharacterClass);
skillsBlock.Level = (byte)masterSkill.Level;
skillsBlock.DisplayValue = masterSkill.CalculateDisplayValue();
skillsBlock.DisplayValueOfNextLevel = masterSkill.CalculateNextDisplayValue();
i++;
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,54 @@
// <copyright file="UpdateMasterStatsExtendedPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The extended implementation of the <see cref="IUpdateMasterStatsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateMasterStatsExtendedPlugIn_Name), Description = nameof(PlugInResources.UpdateMasterStatsExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("EF19E11B-AE26-44F0-AB3E-5ADD5CDEBC56")]
[MinimumClient(106, 3, ClientLanguage.Invariant)]
public class UpdateMasterStatsExtendedPlugIn : IUpdateMasterStatsPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateMasterStatsExtendedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateMasterStatsExtendedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask SendMasterStatsAsync()
{
var character = this._player.SelectedCharacter;
var connection = this._player.Connection;
if (character is null || this._player.Attributes is null || connection is null)
{
return;
}
await connection.SendMasterStatsUpdateExtendedAsync(
(ushort)this._player.Attributes[Stats.MasterLevel],
(ulong)character.MasterExperience,
(ulong)this._player.GameServerContext.MasterExperienceTable[(int)this._player.Attributes[Stats.MasterLevel] + 1],
(ushort)character.MasterLevelUpPoints,
(ushort)this._player.Attributes[Stats.MaximumHealth],
(ushort)this._player.Attributes[Stats.MaximumMana],
(ushort)this._player.Attributes[Stats.MaximumShield],
(ushort)this._player.Attributes[Stats.MaximumAbility]).ConfigureAwait(false);
await this._player.InvokeViewPlugInAsync<IUpdateMasterSkillsPlugIn>(p => p.UpdateMasterSkillsAsync()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,52 @@
// <copyright file="UpdateMasterStatsPlugIn.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.Character;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IUpdateMasterStatsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateMasterStatsPlugIn_Name), Description = nameof(PlugInResources.UpdateMasterStatsPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("41b27ec2-5bc6-4acf-b395-ddf9e81a3611")]
public class UpdateMasterStatsPlugIn : IUpdateMasterStatsPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateMasterStatsPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateMasterStatsPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask SendMasterStatsAsync()
{
var character = this._player.SelectedCharacter;
var connection = this._player.Connection;
if (character is null || this._player.Attributes is null || connection is null)
{
return;
}
await connection.SendMasterStatsUpdateAsync(
(ushort)this._player.Attributes[Stats.MasterLevel],
(ulong)character.MasterExperience,
(ulong)this._player.GameServerContext.MasterExperienceTable[(int)this._player.Attributes[Stats.MasterLevel] + 1],
(ushort)character.MasterLevelUpPoints,
(ushort)this._player.Attributes[Stats.MaximumHealth],
(ushort)this._player.Attributes[Stats.MaximumMana],
(ushort)this._player.Attributes[Stats.MaximumShield],
(ushort)this._player.Attributes[Stats.MaximumAbility]).ConfigureAwait(false);
await this._player.InvokeViewPlugInAsync<IUpdateMasterSkillsPlugIn>(p => p.UpdateMasterSkillsAsync()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,106 @@
// <copyright file="UpdateStatsBasePlugIn.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.Character;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Threading;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Character;
using UpdateAction = System.Func<MUnique.OpenMU.GameServer.RemoteView.RemotePlayer, System.Threading.Tasks.ValueTask>;
/// <summary>
/// The default implementation of the <see cref="IUpdateStatsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
public abstract class UpdateStatsBasePlugIn : Disposable, IUpdateStatsPlugIn
{
private static readonly int SendDelayMs = 16;
private static readonly ConcurrentDictionary<
FrozenDictionary<AttributeDefinition, UpdateAction>,
FrozenDictionary<UpdateAction, int>> ActionIndexMappings = new();
private readonly RemotePlayer _player;
private readonly FrozenDictionary<UpdateAction, int> _actionIndexMapping;
private readonly FrozenDictionary<AttributeDefinition, UpdateAction> _changeActions;
private readonly AutoResetEvent[] _resetEvents;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateStatsBasePlugIn" /> class.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="changeActions">The change actions.</param>
protected UpdateStatsBasePlugIn(RemotePlayer player, FrozenDictionary<AttributeDefinition, UpdateAction> changeActions)
{
this._player = player;
this._changeActions = changeActions;
this._actionIndexMapping = GetActionIndexMapping(changeActions);
this._resetEvents = new AutoResetEvent[changeActions.Count];
for (int i = 0; i < this._resetEvents.Length; i++)
{
this._resetEvents[i] = new AutoResetEvent(true);
}
}
/// <inheritdoc />
public async ValueTask UpdateStatsAsync(AttributeDefinition attribute, float value)
{
if (this._player.Attributes is null
|| !(this._player.Connection?.Connected ?? false))
{
return;
}
if (this._changeActions.TryGetValue(attribute, out var action))
{
_ = this.SendDelayedUpdateAsync(action);
}
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
foreach (var are in this._resetEvents)
{
are.Dispose();
}
base.Dispose(disposing);
}
private static FrozenDictionary<UpdateAction, int> GetActionIndexMapping(FrozenDictionary<AttributeDefinition, UpdateAction> changeActions)
{
return ActionIndexMappings.GetOrAdd(changeActions, CreateNewIndexDictionary);
FrozenDictionary<UpdateAction, int> CreateNewIndexDictionary(FrozenDictionary<AttributeDefinition, UpdateAction> dict)
{
return dict.Values.Distinct().Index().ToFrozenDictionary(tuple => tuple.Item, tuple => tuple.Index);
}
}
private async ValueTask SendDelayedUpdateAsync(UpdateAction action)
{
var autoResetEvent = this._resetEvents[this._actionIndexMapping[action]];
if (!autoResetEvent.WaitOne(0))
{
// We're sending an update already.
return;
}
try
{
await Task.Delay(SendDelayMs).ConfigureAwait(false);
await action(this._player).ConfigureAwait(false);
}
finally
{
autoResetEvent.Set();
}
}
}

View File

@@ -0,0 +1,67 @@
// <copyright file="UpdateStatsExtendedPlugIn.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.Character;
using System.Collections.Frozen;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The extended implementation of the <see cref="IUpdateStatsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateStatsExtendedPlugIn_Name), Description = nameof(PlugInResources.UpdateStatsExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("E9A1CCBE-416F-41BA-8E74-74CBEB7042DD")]
[MinimumClient(106, 3, ClientLanguage.Invariant)]
public class UpdateStatsExtendedPlugIn : UpdateStatsBasePlugIn
{
private static readonly FrozenDictionary<AttributeDefinition, Func<RemotePlayer, ValueTask>> AttributeChangeActions = new Dictionary<AttributeDefinition, Func<RemotePlayer, ValueTask>>
{
{ Stats.MaximumHealth, OnMaximumStatsChangedAsync },
{ Stats.MaximumShield, OnMaximumStatsChangedAsync },
{ Stats.MaximumMana, OnMaximumStatsChangedAsync },
{ Stats.MaximumAbility, OnMaximumStatsChangedAsync },
{ Stats.CurrentHealth, OnCurrentStatsChangedAsync },
{ Stats.CurrentShield, OnCurrentStatsChangedAsync },
{ Stats.CurrentMana, OnCurrentStatsChangedAsync },
{ Stats.CurrentAbility, OnCurrentStatsChangedAsync },
{ Stats.AttackSpeed, OnCurrentStatsChangedAsync },
{ Stats.MagicSpeed, OnCurrentStatsChangedAsync },
}.ToFrozenDictionary();
/// <summary>
/// Initializes a new instance of the <see cref="UpdateStatsExtendedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateStatsExtendedPlugIn(RemotePlayer player)
: base(player, AttributeChangeActions)
{
}
private static async ValueTask OnMaximumStatsChangedAsync(RemotePlayer player)
{
await player.Connection.SendMaximumStatsExtendedAsync(
(uint)player.Attributes![Stats.MaximumHealth],
(uint)player.Attributes[Stats.MaximumShield],
(uint)player.Attributes[Stats.MaximumMana],
(uint)player.Attributes[Stats.MaximumAbility]).ConfigureAwait(false);
}
private static async ValueTask OnCurrentStatsChangedAsync(RemotePlayer player)
{
await player.Connection.SendCurrentStatsExtendedAsync(
(uint)player.Attributes![Stats.CurrentHealth],
(uint)player.Attributes[Stats.CurrentShield],
(uint)player.Attributes[Stats.CurrentMana],
(uint)player.Attributes[Stats.CurrentAbility],
(ushort)player.Attributes[Stats.AttackSpeed],
(ushort)player.Attributes[Stats.MagicSpeed]).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,71 @@
// <copyright file="UpdateStatsPlugIn.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.Character;
using System.Collections.Frozen;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IUpdateStatsPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateStatsPlugIn_Name), Description = nameof(PlugInResources.UpdateStatsPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("2A8BFB0C-2AFF-4A52-B390-5A68D5C5F26A")]
public class UpdateStatsPlugIn : UpdateStatsBasePlugIn
{
private static readonly FrozenDictionary<AttributeDefinition, Func<RemotePlayer, ValueTask>> AttributeChangeActions = new Dictionary<AttributeDefinition, Func<RemotePlayer, ValueTask>>
{
{ Stats.CurrentHealth, OnCurrentHealthOrShieldChangedAsync },
{ Stats.CurrentShield, OnCurrentHealthOrShieldChangedAsync },
{ Stats.MaximumHealth, OnMaximumHealthOrShieldChangedAsync },
{ Stats.MaximumShield, OnMaximumHealthOrShieldChangedAsync },
{ Stats.CurrentMana, OnCurrentManaOrAbilityChangedAsync },
{ Stats.CurrentAbility, OnCurrentManaOrAbilityChangedAsync },
{ Stats.MaximumMana, OnMaximumManaOrAbilityChangedAsync },
{ Stats.MaximumAbility, OnMaximumManaOrAbilityChangedAsync },
}.ToFrozenDictionary();
/// <summary>
/// Initializes a new instance of the <see cref="UpdateStatsPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateStatsPlugIn(RemotePlayer player)
: base(player, AttributeChangeActions)
{
}
private static async ValueTask OnMaximumHealthOrShieldChangedAsync(RemotePlayer player)
{
await player.Connection.SendMaximumHealthAndShieldAsync(
(ushort)Math.Max(player.Attributes![Stats.MaximumHealth], 0f),
(ushort)Math.Max(player.Attributes[Stats.MaximumShield], 0f)).ConfigureAwait(false);
}
private static async ValueTask OnMaximumManaOrAbilityChangedAsync(RemotePlayer player)
{
await player.Connection.SendMaximumManaAndAbilityAsync(
(ushort)Math.Max(player.Attributes![Stats.MaximumMana], 0f),
(ushort)Math.Max(player.Attributes[Stats.MaximumAbility], 0f)).ConfigureAwait(false);
}
private static async ValueTask OnCurrentHealthOrShieldChangedAsync(RemotePlayer player)
{
await player.Connection.SendCurrentHealthAndShieldAsync(
(ushort)Math.Max(player.Attributes![Stats.CurrentHealth], 0f),
(ushort)Math.Max(player.Attributes[Stats.CurrentShield], 0f)).ConfigureAwait(false);
}
private static async ValueTask OnCurrentManaOrAbilityChangedAsync(RemotePlayer player)
{
await player.Connection.SendCurrentManaAndAbilityAsync(
(ushort)Math.Max(player.Attributes![Stats.CurrentMana], 0f),
(ushort)Math.Max(player.Attributes[Stats.CurrentAbility], 0f)).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,53 @@
// <copyright file="CharacterStatTypeExtensions.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;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Network.Packets;
/// <summary>
/// Extensions for <see cref="CharacterStatAttribute"/>.
/// </summary>
public static class CharacterStatTypeExtensions
{
private static readonly Dictionary<AttributeDefinition, CharacterStatAttribute> AttributesToStatTypes = new()
{
{ Stats.BaseAgility, CharacterStatAttribute.Agility },
{ Stats.BaseEnergy, CharacterStatAttribute.Energy },
{ Stats.BaseStrength, CharacterStatAttribute.Strength },
{ Stats.BaseVitality, CharacterStatAttribute.Vitality },
{ Stats.BaseLeadership, CharacterStatAttribute.Leadership },
};
private static readonly Dictionary<CharacterStatAttribute, AttributeDefinition> StatTypesToAttributes = new()
{
{ CharacterStatAttribute.Agility, Stats.BaseAgility },
{ CharacterStatAttribute.Energy, Stats.BaseEnergy },
{ CharacterStatAttribute.Strength, Stats.BaseStrength },
{ CharacterStatAttribute.Vitality, Stats.BaseVitality },
{ CharacterStatAttribute.Leadership, Stats.BaseLeadership },
};
/// <summary>
/// Gets the attribute definition of a <see cref="CharacterStatAttribute"/>.
/// </summary>
/// <param name="statType">Type of the stat.</param>
/// <returns>The corresponding <see cref="AttributeDefinition"/>.</returns>
public static AttributeDefinition GetAttributeDefinition(this CharacterStatAttribute statType)
{
return StatTypesToAttributes[statType];
}
/// <summary>
/// Gets the <see cref="CharacterStatAttribute"/> of the specified <see cref="AttributeDefinition"/>.
/// </summary>
/// <param name="attributeDefinition">The attribute definition.</param>
/// <returns>The corresponding <see cref="CharacterStatAttribute"/>.</returns>
public static CharacterStatAttribute GetStatType(this AttributeDefinition attributeDefinition)
{
return AttributesToStatTypes[attributeDefinition];
}
}

View File

@@ -0,0 +1,46 @@
// <copyright file="ChatViewPlugIn.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;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the chat view which is forwarding everything to the game client which specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ChatViewPlugIn_Name), Description = nameof(PlugInResources.ChatViewPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("F0B5BAD4-B97C-49F1-84E0-25EDC796B0E4")]
public class ChatViewPlugIn : IChatViewPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ChatViewPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ChatViewPlugIn(RemotePlayer player)
{
this._player = player;
}
/// <inheritdoc/>
public async ValueTask ChatMessageAsync(string message, string sender, ChatMessageType type)
{
await this._player.Connection.SendChatMessageAsync(ConvertChatMessageType(type), sender, message).ConfigureAwait(false);
}
private static ChatMessage.ChatMessageType ConvertChatMessageType(ChatMessageType type)
{
if (type == ChatMessageType.Whisper)
{
return Network.Packets.ServerToClient.ChatMessage.ChatMessageType.Whisper;
}
return Network.Packets.ServerToClient.ChatMessage.ChatMessageType.Normal;
}
}

View File

@@ -0,0 +1,52 @@
// <copyright file="ConsumeSpecialItemPlugIn.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;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IConsumeSpecialItemPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ConsumeSpecialItemPlugIn_Name), Description = nameof(PlugInResources.ConsumeSpecialItemPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("a31546d8-bf79-43dd-872c-52f24ea9bca9")]
public class ConsumeSpecialItemPlugIn : IConsumeSpecialItemPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ConsumeSpecialItemPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ConsumeSpecialItemPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask ConsumeSpecialItemAsync(Item item, ushort effectTimeInSeconds)
{
if (item.Definition is null)
{
return;
}
var itemIdentifier = new ItemIdentifier(item.Definition.Number, item.Definition.Group);
if (itemIdentifier == ItemConstants.Alcohol)
{
await this._player.Connection.SendConsumeItemWithEffectAsync(ConsumeItemWithEffect.ConsumedItemType.Ale, effectTimeInSeconds).ConfigureAwait(false);
}
else if (itemIdentifier == ItemConstants.SiegePotion && item.Level == 1)
{
await this._player.Connection.SendConsumeItemWithEffectAsync(ConsumeItemWithEffect.ConsumedItemType.PotionOfSoul, effectTimeInSeconds).ConfigureAwait(false);
}
else
{
// do nothing
}
}
}

View File

@@ -0,0 +1,38 @@
// <copyright file="DuelEndedPlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Duel;
using MUnique.OpenMU.GameServer.RemoteView.World;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IDuelEndedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.DuelEndedPlugIn_Name), Description = nameof(PlugInResources.DuelEndedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("4FBC822B-F35B-4CB1-AFE6-180243171074")]
[MinimumClient(4, 0, ClientLanguage.Invariant)]
public class DuelEndedPlugIn : IDuelEndedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="DuelEndedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public DuelEndedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask DuelEndedAsync()
{
await this._player.Connection.SendDuelEndAsync(0, string.Empty).ConfigureAwait(false);
await this._player.Connection.SendMagicEffectStatusAsync(false, this._player.GetId(this._player), EffectNumbers.DuelSpectatorHealthBar).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,36 @@
// <copyright file="DuelFinishedPlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Duel;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IDuelFinishedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.DuelFinishedPlugIn_Name), Description = nameof(PlugInResources.DuelFinishedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("0DA9F6D1-3DC2-4A75-BA9C-BB77C2A7EB62")]
[MinimumClient(4, 0, ClientLanguage.Invariant)]
public class DuelFinishedPlugIn : IDuelFinishedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="DuelFinishedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public DuelFinishedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask DuelFinishedAsync(Player winner, Player loser)
{
await this._player.Connection.SendDuelFinishedAsync(winner.Name, loser.Name).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,53 @@
// <copyright file="DuelHealthUpdatePlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Duel;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IDuelHealthUpdatePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.DuelHealthUpdatePlugIn_Name), Description = nameof(PlugInResources.DuelHealthUpdatePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("40CE7F73-F9DF-4F4E-BBCE-04938604A72C")]
[MinimumClient(4, 0, ClientLanguage.Invariant)]
public class DuelHealthUpdatePlugIn : IDuelHealthUpdatePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="DuelHealthUpdatePlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public DuelHealthUpdatePlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask UpdateHealthAsync(DuelRoom duelRoom)
{
var player1 = this._player == duelRoom.Opponent ? duelRoom.Opponent : duelRoom.Requester;
var player2 = this._player == duelRoom.Opponent ? duelRoom.Requester : duelRoom.Opponent;
var player1Health = player1.Attributes![Stats.CurrentHealth] / (player1.Attributes[Stats.MaximumHealth] / 100f);
var player1Shield = player1.Attributes[Stats.CurrentShield] / (player1.Attributes[Stats.MaximumShield] / 100f);
var player2Health = player2.Attributes![Stats.CurrentHealth] / (player1.Attributes[Stats.MaximumHealth] / 100f);
var player2Shield = player2.Attributes[Stats.CurrentShield] / (player1.Attributes[Stats.MaximumShield] / 100f);
await this._player.Connection.SendDuelHealthUpdateAsync(
player1.GetId(this._player),
player2.GetId(this._player),
(byte)player1Health,
(byte)player2Health,
(byte)player1Shield,
(byte)player2Shield)
.ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,36 @@
// <copyright file="DuelSpectatorAddedPlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Duel;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IDuelSpectatorAddedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.DuelSpectatorAddedPlugIn_Name), Description = nameof(PlugInResources.DuelSpectatorAddedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("BB91E97F-E1F9-4152-9AA1-573D917ACD35")]
[MinimumClient(4, 0, ClientLanguage.Invariant)]
public class DuelSpectatorAddedPlugIn : IDuelSpectatorAddedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="DuelSpectatorAddedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public DuelSpectatorAddedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask SpectatorAddedAsync(Player spectator)
{
await this._player.Connection.SendDuelSpectatorAddedAsync(spectator.Name).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,56 @@
// <copyright file="DuelSpectatorListUpdatePlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Duel;
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="IDuelSpectatorListUpdatePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.DuelSpectatorListUpdatePlugIn_Name), Description = nameof(PlugInResources.DuelSpectatorListUpdatePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("1A8EC472-6924-4150-9B4C-4352AFF03AC0")]
[MinimumClient(4, 0, ClientLanguage.Invariant)]
public class DuelSpectatorListUpdatePlugIn : IDuelSpectatorListUpdatePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="DuelSpectatorListUpdatePlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public DuelSpectatorListUpdatePlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask UpdateSpectatorListAsync(IList<Player> spectators)
{
if (this._player.Connection is not { } connection)
{
return;
}
int WritePacket()
{
var length = DuelSpectatorListRef.Length;
var packet = new DuelSpectatorListRef(connection.Output.GetSpan(length)[..length]);
for (int i = 0; i < spectators.Count; i++)
{
var spectator = spectators[i];
var spectatorStruct = packet[i];
spectatorStruct.Name = spectator.Name;
}
return length;
}
await connection.SendAsync(WritePacket).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,36 @@
// <copyright file="DuelSpectatorRemovedPlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Duel;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IDuelSpectatorRemovedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.DuelSpectatorRemovedPlugIn_Name), Description = nameof(PlugInResources.DuelSpectatorRemovedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("89DFA45D-FED2-4FAA-BDB0-683EB337629B")]
[MinimumClient(4, 0, ClientLanguage.Invariant)]
public class DuelSpectatorRemovedPlugIn : IDuelSpectatorRemovedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="DuelSpectatorRemovedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public DuelSpectatorRemovedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask SpectatorRemovedAsync(Player spectator)
{
await this._player.Connection.SendDuelSpectatorRemovedAsync(spectator.Name).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,62 @@
// <copyright file="DuelStatusUpdatePlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Duel;
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="IDuelStatusUpdatePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.DuelStatusUpdatePlugIn_Name), Description = nameof(PlugInResources.DuelStatusUpdatePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("9C37DF93-7E1C-44BB-914D-DB8B3F96FEE0")]
[MinimumClient(4, 0, ClientLanguage.Invariant)]
public class DuelStatusUpdatePlugIn : IDuelStatusUpdatePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="DuelStatusUpdatePlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public DuelStatusUpdatePlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask UpdateStatusAsync(DuelRoom?[] duelRooms)
{
if (this._player.Connection is not { } connection)
{
return;
}
int WritePacket()
{
var length = DuelStatusRef.Length;
var packet = new DuelStatusRef(connection.Output.GetSpan(length)[..length]);
for (int i = 0; i < duelRooms.Length; i++)
{
var duelRoom = duelRooms[i];
var duelStatus = packet[i];
if (duelRoom is not null)
{
duelStatus.Player1Name = duelRoom.Requester.Name;
duelStatus.Player2Name = duelRoom.Opponent.Name;
duelStatus.DuelRunning = duelRoom.State == DuelState.DuelStarted;
duelStatus.DuelOpen = duelRoom.IsOpen;
}
}
return length;
}
await connection.SendAsync(WritePacket).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,52 @@
// <copyright file="InitializeDuelPlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Duel;
using MUnique.OpenMU.GameServer.RemoteView.World;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IInitializeDuelPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.InitializeDuelPlugIn_Name), Description = nameof(PlugInResources.InitializeDuelPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("41ECFA38-3EAE-4408-B7AC-82F26D8DCCD7")]
[MinimumClient(4, 0, ClientLanguage.Invariant)]
public class InitializeDuelPlugIn : IInitializeDuelPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="InitializeDuelPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public InitializeDuelPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask InitializeDuelAsync(DuelRoom duelRoom)
{
var player1 = this._player == duelRoom.Opponent ? duelRoom.Opponent : duelRoom.Requester;
var player2 = this._player == duelRoom.Opponent ? duelRoom.Requester : duelRoom.Opponent;
await this._player.Connection.SendDuelInitAsync(
0,
(byte)duelRoom.Index,
player1.Name,
player2.Name,
player1.GetId(this._player),
player2.GetId(this._player))
.ConfigureAwait(false);
if (!duelRoom.IsDuelist(this._player))
{
await this._player.Connection.SendDuelHealthBarInitAsync().ConfigureAwait(false);
await this._player.Connection.SendMagicEffectStatusAsync(true, this._player.GetId(this._player), EffectNumbers.DuelSpectatorHealthBar).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,37 @@
// <copyright file="ShowDuelRequestPlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Duel;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowDuelRequestPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowDuelRequestPlugIn_Name), Description = nameof(PlugInResources.ShowDuelRequestPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("708847EE-B64F-42A3-BDAC-C4FD2417B6A5")]
[MinimumClient(4, 0, ClientLanguage.Invariant)]
public class ShowDuelRequestPlugIn : IShowDuelRequestPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowDuelRequestPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowDuelRequestPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask ShowDuelRequestAsync(Player requester)
{
await this._player.Connection.SendDuelStartRequestAsync(requester.GetId(this._player), requester.Name).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,49 @@
// <copyright file="ShowDuelRequestResultPlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Duel;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowDuelRequestResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowDuelRequestResultPlugIn_Name), Description = nameof(PlugInResources.ShowDuelRequestResultPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("3A389377-71BC-4EEB-922E-00B343DF1893")]
public class ShowDuelRequestResultPlugIn : IShowDuelRequestResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowDuelRequestResultPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowDuelRequestResultPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowDuelRequestResultAsync(GameLogic.Views.Duel.DuelStartResult result, Player opponent)
{
await this._player.Connection.SendDuelStartResultAsync(Convert(result), opponent.GetId(this._player), opponent.Name).ConfigureAwait(false);
}
private static Network.Packets.ServerToClient.DuelStartResult.DuelStartResultType Convert(GameLogic.Views.Duel.DuelStartResult result)
{
return result switch
{
GameLogic.Views.Duel.DuelStartResult.Success => Network.Packets.ServerToClient.DuelStartResult.DuelStartResultType.Success,
GameLogic.Views.Duel.DuelStartResult.Refused => Network.Packets.ServerToClient.DuelStartResult.DuelStartResultType.Refused,
GameLogic.Views.Duel.DuelStartResult.FailedByError => Network.Packets.ServerToClient.DuelStartResult.DuelStartResultType.FailedByError,
GameLogic.Views.Duel.DuelStartResult.FailedByNoFreeRoom => Network.Packets.ServerToClient.DuelStartResult.DuelStartResultType.FailedByNoFreeRoom,
GameLogic.Views.Duel.DuelStartResult.FailedByNotEnoughMoney => Network.Packets.ServerToClient.DuelStartResult.DuelStartResultType.FailedByNotEnoughMoney,
GameLogic.Views.Duel.DuelStartResult.FailedByTooLowLevel => Network.Packets.ServerToClient.DuelStartResult.DuelStartResultType.FailedByTooLowLevel,
_ => throw new ArgumentOutOfRangeException(nameof(result)),
};
}
}

View File

@@ -0,0 +1,44 @@
// <copyright file="ShowDuelScoreUpdatePlugIn.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.Duel;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Duel;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowDuelScoreUpdatePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowDuelScoreUpdatePlugIn_Name), Description = nameof(PlugInResources.ShowDuelScoreUpdatePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("328E366D-B801-4780-B65D-B250C388E6B0")]
[MinimumClient(4, 0, ClientLanguage.Invariant)]
public class ShowDuelScoreUpdatePlugIn : IShowDuelScoreUpdatePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowDuelScoreUpdatePlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowDuelScoreUpdatePlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask UpdateScoreAsync(DuelRoom duelRoom)
{
if (this._player == duelRoom.Opponent)
{
await this._player.Connection.SendDuelScoreAsync(duelRoom.Opponent.GetId(this._player), duelRoom.Requester.GetId(this._player), duelRoom.ScoreOpponent, duelRoom.ScoreRequester).ConfigureAwait(false);
}
else
{
await this._player.Connection.SendDuelScoreAsync(duelRoom.Requester.GetId(this._player), duelRoom.Opponent.GetId(this._player), duelRoom.ScoreRequester, duelRoom.ScoreOpponent).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,111 @@
// <copyright file="AssignPlayersToGuildPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Guild;
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="IAssignPlayersToGuildPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.AssignPlayersToGuildPlugIn_Name), Description = nameof(PlugInResources.AssignPlayersToGuildPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("f42f571e-0cd1-4c22-ba53-8344848ba998")]
[MinimumClient(0, 90, ClientLanguage.Invariant)]
public class AssignPlayersToGuildPlugIn : IAssignPlayersToGuildPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="AssignPlayersToGuildPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public AssignPlayersToGuildPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask AssignPlayersToGuildAsync(ICollection<Player> guildPlayers, bool appearsNew)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
// C2 00 11
// 65
// 01
// 34 4B 00 00 80 00 00
// A4 F2 00 00 00
int Write()
{
var size = AssignCharacterToGuildRef.GetRequiredSize(guildPlayers.Count);
var span = connection.Output.GetSpan(size)[..size];
var packet = new AssignCharacterToGuildRef(span)
{
PlayerCount = (byte)guildPlayers.Count,
};
int i = 0;
foreach (var guildPlayer in guildPlayers)
{
this.SetGuildPlayerBlock(packet[i], guildPlayer, appearsNew);
i++;
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
/// <inheritdoc />
public async ValueTask AssignPlayerToGuildAsync(Player guildPlayer, bool appearsNew)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
// C2 00 11
// 65
// 01
// 34 4B 00 00 80 00 00
// A4 F2 00 00 00
int Write()
{
var size = AssignCharacterToGuildRef.GetRequiredSize(1);
var span = connection.Output.GetSpan(size)[..size];
var packet = new AssignCharacterToGuildRef(span)
{
PlayerCount = 1,
};
this.SetGuildPlayerBlock(packet[0], guildPlayer, appearsNew);
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
private void SetGuildPlayerBlock(AssignCharacterToGuildRef.GuildMemberRelationRef playerBlock, Player guildPlayer, bool appearsNew)
{
if (guildPlayer.GuildStatus is null)
{
return;
}
playerBlock.GuildId = guildPlayer.GuildStatus.GuildId;
playerBlock.Role = guildPlayer.GuildStatus.Position.Convert();
playerBlock.PlayerId = guildPlayer.GetId(this._player);
playerBlock.IsPlayerAppearingNew = appearsNew;
}
}

View File

@@ -0,0 +1,166 @@
// <copyright file="AssignPlayersToGuildPlugIn075.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Guild;
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="IAssignPlayersToGuildPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.AssignPlayersToGuildPlugIn075_Name), Description = nameof(PlugInResources.AssignPlayersToGuildPlugIn075_Description), ResourceType = typeof(PlugInResources))]
[Guid("ABFA2CBD-1AB0-4F56-97A7-FCF458865ACF")]
[MaximumClient(0, 89, ClientLanguage.Invariant)]
public class AssignPlayersToGuildPlugIn075 : BaseGuildInfoPlugIn<AssignPlayersToGuildPlugIn075>, IAssignPlayersToGuildPlugIn
{
private readonly RemotePlayer _player;
private readonly HashSet<uint> _transmittedGuilds = new();
/// <summary>
/// Initializes a new instance of the <see cref="AssignPlayersToGuildPlugIn075"/> class.
/// </summary>
/// <param name="player">The player.</param>
public AssignPlayersToGuildPlugIn075(RemotePlayer player)
: base(player)
{
this._player = player;
}
/// <inheritdoc />
public async ValueTask AssignPlayersToGuildAsync(ICollection<Player> guildPlayers, bool appearsNew)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
foreach (var guildPlayer in guildPlayers)
{
await this.SendGuildInfoIfRequiredAsync(guildPlayer).ConfigureAwait(false);
}
int Write()
{
var size = AssignCharacterToGuild075Ref.GetRequiredSize(guildPlayers.Count);
var span = connection.Output.GetSpan(size)[..size];
var packet = new AssignCharacterToGuild075Ref(span)
{
PlayerCount = (byte)guildPlayers.Count,
};
int i = 0;
foreach (var guildPlayer in guildPlayers)
{
this.SetGuildPlayerBlock(packet[i], guildPlayer);
i++;
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
/// <inheritdoc />
public async ValueTask AssignPlayerToGuildAsync(Player guildPlayer, bool appearsNew)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
await this.SendGuildInfoIfRequiredAsync(guildPlayer).ConfigureAwait(false);
// C2 00 11
// 65
// 01
// 34 4B 00 00 80 00 00
// A4 F2 00 00 00
int Write()
{
var size = AssignCharacterToGuild075Ref.GetRequiredSize(1);
var span = connection.Output.GetSpan(size)[..size];
var packet = new AssignCharacterToGuild075Ref(span)
{
PlayerCount = 1,
};
this.SetGuildPlayerBlock(packet[0], guildPlayer);
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
/// <inheritdoc />
protected override Memory<byte> Serialize(Interfaces.Guild guild, uint guildId)
{
var array = new byte[GuildInformations075.GetRequiredSize(1)];
var result = new GuildInformations075(array) { GuildCount = 1 };
var guildInfo = result[0];
guildInfo.GuildId = (ushort)guildId;
guildInfo.GuildName = guild.Name ?? string.Empty;
guild.Logo.CopyTo(guildInfo.Logo);
return array.AsMemory();
}
private async ValueTask SendGuildInfoIfRequiredAsync(Player guildPlayer)
{
if (guildPlayer.GuildStatus is not { } guildStatus)
{
return;
}
if (this._transmittedGuilds.Contains(guildStatus.GuildId))
{
return;
}
var data = await this.GetGuildDataAsync(guildStatus.GuildId).ConfigureAwait(false);
if (data.Length == 0)
{
return;
}
var connection = this.Player.Connection;
if (connection is null)
{
return;
}
this._transmittedGuilds.Add(guildStatus.GuildId);
// guildInfo is the cached, serialized result of the GuildInformation-Class.
int Write()
{
var target = connection.Output.GetSpan(data.Length);
data.Span.CopyTo(target);
return data.Length;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
private void SetGuildPlayerBlock(AssignCharacterToGuild075Ref.GuildMemberRelationRef playerBlock, Player guildPlayer)
{
if (guildPlayer.GuildStatus is null)
{
return;
}
playerBlock.GuildId = (ushort)guildPlayer.GuildStatus.GuildId;
playerBlock.PlayerId = guildPlayer.GetId(this._player);
}
}

View File

@@ -0,0 +1,92 @@
// <copyright file="BaseGuildInfoPlugIn.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.Guild;
using System.Collections.Concurrent;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
/// <summary>
/// Abstract base class for a <see cref="IShowGuildInfoPlugIn" /> which allows to cache serialized guild infos.
/// </summary>
/// <typeparam name="T">The type of the actual <see cref="IShowGuildInfoPlugIn"/>. Required, so there is one cache per type.</typeparam>
// ReSharper disable once UnusedTypeParameter we just use it to get type specific static fields.
public abstract class BaseGuildInfoPlugIn<T>
{
/// <summary>
/// The cache for already serialized guilds. This data doesn't change, but is requested often.
/// </summary>
// ReSharper disable once StaticMemberInGenericType That's what we want
private static readonly ConcurrentDictionary<uint, Memory<byte>> Cache = new();
// ReSharper disable once StaticMemberInGenericType That's what we want
private static readonly HashSet<IGameServerContext> AppendedGuildDeletedSenders = new();
/// <summary>
/// Initializes a new instance of the <see cref="BaseGuildInfoPlugIn{T}"/> class.
/// </summary>
/// <param name="player">The player.</param>
protected BaseGuildInfoPlugIn(RemotePlayer player)
{
this.Player = player;
lock (AppendedGuildDeletedSenders)
{
if (AppendedGuildDeletedSenders.Add(player.GameServerContext))
{
// to make sure we just add one event handler
this.Player.GameServerContext.GuildDeleted += OnGuildChanged;
this.Player.GameServerContext.GuildChanged += OnGuildChanged;
}
}
}
/// <summary>
/// Gets the player.
/// </summary>
/// <value>
/// The player.
/// </value>
protected RemotePlayer Player { get; }
/// <summary>
/// Serializes the specified guild.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="guildId">The guild identifier.</param>
/// <returns>The serialized guild data packet.</returns>
protected abstract Memory<byte> Serialize(Guild guild, uint guildId);
/// <summary>
/// Returns the Guild Info Data of a Guild. It will either
/// take the data out of the Cache, or get it from the database and serializes it.
/// </summary>
/// <param name="guildId">The id of the guild.</param>
/// <returns>
/// The data of the guild.
/// </returns>
protected async ValueTask<Memory<byte>> GetGuildDataAsync(uint guildId)
{
if (Cache.TryGetValue(guildId, out var guildInfo))
{
return guildInfo;
}
var guild = await this.Player.GameServerContext.GuildServer.GetGuildAsync(guildId).ConfigureAwait(false);
if (guild is null)
{
return Memory<byte>.Empty;
}
var data = this.Serialize(guild, guildId);
Cache.TryAdd(guildId, data);
return data;
}
private static void OnGuildChanged(object? sender, GuildEventArgs args)
{
Cache.TryRemove(args.GuildId, out _);
}
}

View File

@@ -0,0 +1,217 @@
// <copyright file="EnumExtensions.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.Guild;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using static MUnique.OpenMU.Network.Packets.ServerToClient.GuildJoinResponse;
/// <summary>
/// Extension methods for enum types.
/// </summary>
public static class EnumExtensions
{
/// <summary>
/// Gets the view value.
/// </summary>
/// <param name="playerPosition">The player position.</param>
/// <returns>The value which is used in the message for the corresponding enum value.</returns>
public static GuildMemberRole Convert(this GuildPosition? playerPosition)
{
if (playerPosition.HasValue)
{
return playerPosition.Value.Convert();
}
return GuildMemberRole.Undefined;
}
/// <summary>
/// Gets the view value.
/// </summary>
/// <param name="playerPosition">The player position.</param>
/// <returns>The value which is used in the message for the corresponding enum value.</returns>
public static GuildMemberRole Convert(this GuildPosition playerPosition)
{
return playerPosition switch
{
GuildPosition.GuildMaster => GuildMemberRole.GuildMaster,
GuildPosition.AssistantMaster => GuildMemberRole.AssistantMaster,
GuildPosition.NormalMember => GuildMemberRole.NormalMember,
GuildPosition.BattleMaster => GuildMemberRole.BattleMaster,
_ => GuildMemberRole.Undefined,
};
}
/// <summary>
/// Converts the <see cref="GuildRequestAnswerResult"/> into a <see cref="GuildJoinRequestResult"/>.
/// </summary>
/// <param name="result">The <see cref="GuildRequestAnswerResult"/> which should be converted.</param>
/// <returns>The converted value.</returns>
public static GuildJoinRequestResult Convert(this GuildRequestAnswerResult result)
{
return result switch
{
GuildRequestAnswerResult.Refused => GuildJoinRequestResult.Refused,
GuildRequestAnswerResult.Accepted => GuildJoinRequestResult.Accepted,
GuildRequestAnswerResult.GuildFull => GuildJoinRequestResult.GuildFull,
GuildRequestAnswerResult.Disconnected => GuildJoinRequestResult.Disconnected,
GuildRequestAnswerResult.NotTheGuildMaster => GuildJoinRequestResult.NotTheGuildMaster,
GuildRequestAnswerResult.AlreadyHaveGuild => GuildJoinRequestResult.AlreadyHaveGuild,
GuildRequestAnswerResult.GuildMasterOrRequesterIsBusy => GuildJoinRequestResult.GuildMasterOrRequesterIsBusy,
GuildRequestAnswerResult.MinimumLevel6 => GuildJoinRequestResult.MinimumLevel6,
_ => throw new NotImplementedException($"The case {result} is not implemented."),
};
}
/// <summary>
/// Converts a <see cref="GuildKickSuccess"/> into a <see cref="GuildKickResponse.GuildKickSuccess"/>.
/// </summary>
/// <param name="success">The <see cref="GuildKickSuccess"/> which should be converted.</param>
/// <returns>The converted <see cref="GuildKickResponse.GuildKickSuccess"/>.</returns>
public static GuildKickResponse.GuildKickSuccess Convert(this GuildKickSuccess success)
{
return success switch
{
GuildKickSuccess.Failed => GuildKickResponse.GuildKickSuccess.Failed,
GuildKickSuccess.FailedBecausePlayerIsNotGuildMaster => GuildKickResponse.GuildKickSuccess.KickFailedBecausePlayerIsNotGuildMaster,
GuildKickSuccess.KickSucceeded => GuildKickResponse.GuildKickSuccess.KickSucceeded,
GuildKickSuccess.GuildDisband => GuildKickResponse.GuildKickSuccess.GuildDisband,
_ => throw new NotImplementedException($"The case {success} is not implemented."),
};
}
/// <summary>
/// Converts a <see cref="GuildCreateErrorDetail"/> into a <see cref="GuildCreationResult.GuildCreationErrorType"/>.
/// </summary>
/// <param name="errorDetail">The <see cref="GuildCreateErrorDetail"/> which should be converted.</param>
/// <returns>The converted <see cref="GuildCreationResult.GuildCreationErrorType"/>.</returns>
public static GuildCreationResult.GuildCreationErrorType Convert(this GuildCreateErrorDetail errorDetail)
{
return errorDetail switch
{
GuildCreateErrorDetail.None => GuildCreationResult.GuildCreationErrorType.None,
GuildCreateErrorDetail.GuildAlreadyExist => GuildCreationResult.GuildCreationErrorType.GuildNameAlreadyTaken,
_ => throw new NotImplementedException($"The case {errorDetail} is not implemented."),
};
}
/// <summary>
/// Converts a <see cref="GameLogic.GuildWar.GuildWarType"/> into a <see cref="Network.Packets.ServerToClient.GuildWarType"/>.
/// </summary>
/// <param name="guildWarType">The <see cref="GameLogic.GuildWar.GuildWarType"/> which should be converted.</param>
/// <returns>The converted <see cref="Network.Packets.ServerToClient.GuildWarType"/>.</returns>
public static Network.Packets.ServerToClient.GuildWarType Convert(this GameLogic.GuildWar.GuildWarType guildWarType)
{
return guildWarType switch
{
GameLogic.GuildWar.GuildWarType.Normal => Network.Packets.ServerToClient.GuildWarType.Normal,
GameLogic.GuildWar.GuildWarType.Soccer => Network.Packets.ServerToClient.GuildWarType.Soccer,
_ => throw new NotImplementedException($"The case {guildWarType} is not implemented."),
};
}
/// <summary>
/// Converts a <see cref="GameLogic.Views.Guild.GuildWarResult"/> into a <see cref="GuildWarEnded.GuildWarResult"/>.
/// </summary>
/// <param name="guildWarResult">The <see cref="GameLogic.Views.Guild.GuildWarResult"/> which should be converted.</param>
/// <returns>The converted <see cref="GuildWarEnded.GuildWarResult"/>.</returns>
public static GuildWarEnded.GuildWarResult Convert(this GuildWarResult guildWarResult)
{
return guildWarResult switch
{
GuildWarResult.Lost => GuildWarEnded.GuildWarResult.Lost,
GuildWarResult.Won => GuildWarEnded.GuildWarResult.Won,
GuildWarResult.CancelledWar => GuildWarEnded.GuildWarResult.CancelledWar,
GuildWarResult.OtherGuildMasterCancelledWar => GuildWarEnded.GuildWarResult.OtherGuildMasterCancelledWar,
_ => throw new NotImplementedException($"The case {guildWarResult} is not implemented."),
};
}
/// <summary>
/// Converts a <see cref="GameLogic.Views.Guild.GuildWarRequestResult"/> into a <see cref="Network.Packets.ServerToClient.GuildWarRequestResult.RequestResult"/>.
/// </summary>
/// <param name="result">The <see cref="GameLogic.Views.Guild.GuildWarRequestResult"/> which should be converted.</param>
/// <returns>The converted <see cref="Network.Packets.ServerToClient.GuildWarRequestResult.RequestResult"/>.</returns>
public static Network.Packets.ServerToClient.GuildWarRequestResult.RequestResult Convert(this GameLogic.Views.Guild.GuildWarRequestResult result)
{
return result switch
{
GameLogic.Views.Guild.GuildWarRequestResult.AlreadyInWar => Network.Packets.ServerToClient.GuildWarRequestResult.RequestResult.AlreadyInWar,
GameLogic.Views.Guild.GuildWarRequestResult.Failed => Network.Packets.ServerToClient.GuildWarRequestResult.RequestResult.Failed,
GameLogic.Views.Guild.GuildWarRequestResult.GuildMasterOffline => Network.Packets.ServerToClient.GuildWarRequestResult.RequestResult.GuildMasterOffline,
GameLogic.Views.Guild.GuildWarRequestResult.GuildNotFound => Network.Packets.ServerToClient.GuildWarRequestResult.RequestResult.GuildNotFound,
GameLogic.Views.Guild.GuildWarRequestResult.NotInGuild => Network.Packets.ServerToClient.GuildWarRequestResult.RequestResult.NotInGuild,
GameLogic.Views.Guild.GuildWarRequestResult.NotTheGuildMaster => Network.Packets.ServerToClient.GuildWarRequestResult.RequestResult.NotTheGuildMaster,
GameLogic.Views.Guild.GuildWarRequestResult.RequestSentToGuildMaster => Network.Packets.ServerToClient.GuildWarRequestResult.RequestResult.RequestSentToGuildMaster,
_ => throw new NotImplementedException($"The case {result} is not implemented."),
};
}
/// <summary>
/// Converts the specified relationship type.
/// </summary>
/// <param name="relationshipType">Type of the relationship.</param>
/// <returns>The converted <see cref="Network.Packets.ServerToClient.GuildRelationshipType"/>.</returns>
public static Network.Packets.ServerToClient.GuildRelationshipType Convert(this GameLogic.Views.Guild.GuildRelationshipType relationshipType)
{
return relationshipType switch
{
GameLogic.Views.Guild.GuildRelationshipType.Undefined => Network.Packets.ServerToClient.GuildRelationshipType.Undefined,
GameLogic.Views.Guild.GuildRelationshipType.Alliance => Network.Packets.ServerToClient.GuildRelationshipType.Alliance,
GameLogic.Views.Guild.GuildRelationshipType.Hostility => Network.Packets.ServerToClient.GuildRelationshipType.Hostility,
_ => throw new NotImplementedException($"The case {relationshipType} is not implemented."),
};
}
/// <summary>
/// Converts the specified request type.
/// </summary>
/// <param name="requestType">Type of the request.</param>
/// <returns>The converted <see cref="Network.Packets.ServerToClient.GuildRelationshipRequestType"/>.</returns>
public static Network.Packets.ServerToClient.GuildRelationshipRequestType Convert(this GameLogic.Views.Guild.GuildRelationshipRequestType requestType)
{
return requestType switch
{
GameLogic.Views.Guild.GuildRelationshipRequestType.Undefined => Network.Packets.ServerToClient.GuildRelationshipRequestType.Undefined,
GameLogic.Views.Guild.GuildRelationshipRequestType.Join => Network.Packets.ServerToClient.GuildRelationshipRequestType.Join,
GameLogic.Views.Guild.GuildRelationshipRequestType.Leave => Network.Packets.ServerToClient.GuildRelationshipRequestType.Leave,
_ => throw new NotImplementedException($"The case {requestType} is not implemented."),
};
}
/// <summary>
/// Converts the specified result type.
/// </summary>
/// <param name="result">The <see cref="GameLogic.Views.Guild.GuildRelationshipChangeResultType"/> which should be converted.</param>
/// <returns>The converted <see cref="Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType"/>.</returns>
public static Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType Convert(this GameLogic.Views.Guild.GuildRelationshipChangeResultType result)
{
return result switch
{
GameLogic.Views.Guild.GuildRelationshipChangeResultType.Failed => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.Failed,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.Success => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.Success,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.GuildNotFound => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.GuildNotFound,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.NoAuthorization => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.NoAuthorization,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.AlreadyInAlliance => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.AlreadyInAlliance,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.FailedDuringCastleSiege => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.FailedDuringCastleSiege,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.AlreadyInHostility => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.AlreadyInHostility,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.GuildAllianceExists => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.GuildAllianceExists,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.HostileGuildExists => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.HostileGuildExists,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.GuildAllianceDoesNotExist => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.GuildAllianceDoesNotExist,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.HostileGuildDoesNotExist => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.HostileGuildDoesNotExist,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.NotMasterOfGuildAlliance => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.NotMasterOfGuildAlliance,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.NotGuildRival => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.NotGuildRival,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.IncompleteRequirementsToCreateAlliance => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.IncompleteRequirementsToCreateAlliance,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.MaximumNumberOfGuildsInAllianceReached => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.MaximumNumberOfGuildsInAllianceReached,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.RequestCancelled => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.RequestCancelled,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.AllianceMasterNotInGens => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.AllianceMasterNotInGens,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.GuildMasterNotInGens => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.GuildMasterNotInGens,
GameLogic.Views.Guild.GuildRelationshipChangeResultType.DifferentGens => Network.Packets.ServerToClient.GuildRelationshipChangeResult.GuildRelationshipChangeResultType.DifferentGens,
_ => throw new NotImplementedException($"The case {result} is not implemented."),
};
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="GuildJoinResponsePlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IGuildJoinResponsePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.GuildJoinResponsePlugIn_Name), Description = nameof(PlugInResources.GuildJoinResponsePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("4a8bd97c-a544-4cac-b8cd-2e73945bcfdc")]
public class GuildJoinResponsePlugIn : IGuildJoinResponsePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="GuildJoinResponsePlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public GuildJoinResponsePlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowGuildJoinResponseAsync(GuildRequestAnswerResult result)
{
await this._player.Connection.SendGuildJoinResponseAsync(result.Convert()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="GuildKickResultPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IGuildKickResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.GuildKickResultPlugIn_Name), Description = nameof(PlugInResources.GuildKickResultPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("0e91e131-12c4-4add-9439-febb7d444083")]
public class GuildKickResultPlugIn : IGuildKickResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="GuildKickResultPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public GuildKickResultPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask GuildKickResultAsync(GuildKickSuccess successCode)
{
await this._player.Connection.SendGuildKickResponseAsync(successCode.Convert()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,36 @@
// <copyright file="GuildWarScoreUpdatePlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IGuildWarScoreUpdatePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.GuildWarScoreUpdatePlugIn_Name), Description = nameof(PlugInResources.GuildWarScoreUpdatePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("24E5E0C1-DAE7-4E34-810D-C622F8F9B70F")]
public class GuildWarScoreUpdatePlugIn : IGuildWarScoreUpdatePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="GuildWarScoreUpdatePlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public GuildWarScoreUpdatePlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateScoreAsync()
{
if (this._player.GuildWarContext is { } guildWarContext)
{
await this._player.Connection.SendGuildWarScoreUpdateAsync(guildWarContext.ThisScore, guildWarContext.EnemyScore).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,39 @@
// <copyright file="PlayerLeftGuildPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IPlayerLeftGuildPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.PlayerLeftGuildPlugIn_Name), Description = nameof(PlugInResources.PlayerLeftGuildPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("e37868e8-6fdf-41fb-aa7f-01e6f569f5c0")]
public class PlayerLeftGuildPlugIn : IPlayerLeftGuildPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="PlayerLeftGuildPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public PlayerLeftGuildPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask PlayerLeftGuildAsync(Player player)
{
await this._player.Connection.SendGuildMemberLeftGuildAsync(
player.GetId(this._player),
player.GuildStatus?.Position == GuildPosition.GuildMaster)
.ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,64 @@
// <copyright file="ShowAllianceListPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowAllianceListPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowAllianceListPlugIn_Name), Description = nameof(PlugInResources.ShowAllianceListPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("A8B9C0D1-E2F3-4A4B-5C6D-7E8F9A0B1C2D")]
public class ShowAllianceListPlugIn : IShowAllianceListPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowAllianceListPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowAllianceListPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowListAsync(IEnumerable<AllianceGuildEntry> guilds)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
var guildList = guilds.ToList();
int guildCount = guildList.Count;
int Write()
{
var size = AllianceList.GetRequiredSize(guildCount);
var packet = new AllianceListRef(connection.Output.GetSpan(size)[..size])
{
GuildCount = (byte)guildCount,
Success = true,
};
for (int i = 0; i < guildCount; i++)
{
var entry = packet[i];
entry.GuildName = guildList[i].GuildName;
entry.MemberCount = (byte)guildList[i].MemberCount;
guildList[i].Logo.Span.CopyTo(entry.Logo);
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ShowGuildCreateResultPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowGuildCreateResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildCreateResultPlugIn_Name), Description = nameof(PlugInResources.ShowGuildCreateResultPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("ff6c5a06-4699-461b-9004-756269393c40")]
public class ShowGuildCreateResultPlugIn : IShowGuildCreateResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildCreateResultPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildCreateResultPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowGuildCreateResultAsync(GuildCreateErrorDetail errorDetail)
{
await this._player.Connection.SendGuildCreationResultAsync(errorDetail == GuildCreateErrorDetail.None, errorDetail.Convert()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ShowGuildCreationDialogPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowGuildCreationDialogPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildCreationDialogPlugIn_Name), Description = nameof(PlugInResources.ShowGuildCreationDialogPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("ed6fbe5f-7a27-477d-b238-e6e77cf113d8")]
public class ShowGuildCreationDialogPlugIn : IShowGuildCreationDialogPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildCreationDialogPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildCreationDialogPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowGuildCreationDialogAsync()
{
await this._player.Connection.SendShowGuildCreationDialogAsync().ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,83 @@
// <copyright file="ShowGuildInfoPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowGuildInfoPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildInfoPlugIn_Name), Description = nameof(PlugInResources.ShowGuildInfoPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("65f9d310-4adf-48b4-ace2-16779b254ecf")]
public class ShowGuildInfoPlugIn : BaseGuildInfoPlugIn<ShowGuildInfoPlugIn>, IShowGuildInfoPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildInfoPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildInfoPlugIn(RemotePlayer player)
: base(player)
{
}
/// <inheritdoc/>
public async ValueTask ShowGuildInfoAsync(uint guildId)
{
var data = await this.GetGuildDataAsync(guildId).ConfigureAwait(false);
if (data.Length == 0)
{
return;
}
var connection = this.Player.Connection;
if (connection is null)
{
return;
}
// guildInfo is the cached, serialized result of the GuildInformation-Class.
int Write()
{
var target = connection.Output.GetSpan(data.Length);
data.Span.CopyTo(target);
return data.Length;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override Memory<byte> Serialize(Guild guild, uint guildId)
{
/*
* C1 3C 66 00
87 38 00 00 // guild number
00 // guild type
54 68 65 4F 6E 65 00 00 //TheOne - Maintain
41 76 61 6C 6F 6E 00 2B //Avalon - Assistant
18 88 88 81 18 66 66 81 18 61 16 81 18 61 16 81 18 66 66 81 18 61 16 81 18 61 16 81 18 61 16 81 //Guild Logo
F9 96 7C //?
*/
var array = new byte[GuildInformation.Length];
var result = new GuildInformation(array)
{
GuildId = guildId,
GuildName = guild.Name ?? string.Empty,
};
if (guild.AllianceGuild != null)
{
result.AllianceGuildName = guild.AllianceGuild.Name ?? string.Empty;
}
guild.Logo.CopyTo(result.Logo);
return array.AsMemory();
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="ShowGuildJoinRequestPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowGuildJoinRequestPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildJoinRequestPlugIn_Name), Description = nameof(PlugInResources.ShowGuildJoinRequestPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("521ff03d-c8ad-44d9-a23d-8f98c4c174ae")]
public class ShowGuildJoinRequestPlugIn : IShowGuildJoinRequestPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildJoinRequestPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildJoinRequestPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowGuildJoinRequestAsync(Player requester)
{
await this._player.Connection.SendGuildJoinRequestAsync(requester.Id).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,70 @@
// <copyright file="ShowGuildListPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
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="IShowGuildListPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildListPlugIn_Name), Description = nameof(PlugInResources.ShowGuildListPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("f72a9968-100b-481f-aba1-1dd597fdad47")]
[MinimumClient(0, 90, ClientLanguage.Invariant)]
public class ShowGuildListPlugIn : IShowGuildListPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildListPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildListPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowGuildListAsync(IReadOnlyCollection<OpenMU.Interfaces.GuildListEntry> players, Interfaces.Guild guild)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
var playerCount = players.Count;
int Write()
{
var size = GuildListRef.GetRequiredSize(playerCount);
var span = connection.Output.GetSpan(size)[..size];
var packet = new GuildListRef(span)
{
GuildMemberCount = (byte)playerCount,
IsInGuild = playerCount > 0,
RivalGuildName = guild.Hostility?.Name ?? string.Empty,
CurrentScore = 0, // TODO: What is this? Maybe a rank?
TotalScore = (uint)guild.Score,
};
int i = 0;
foreach (var member in players)
{
var memberBlock = packet[i];
memberBlock.Name = member.PlayerName;
memberBlock.Role = member.PlayerPosition.Convert();
memberBlock.ServerId = member.ServerId;
memberBlock.ServerId2 = (byte)(member.ServerId == 0xFF ? 0x7F : 0x80 + member.ServerId);
i++;
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,73 @@
// <copyright file="ShowGuildListPlugIn075.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
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="IShowGuildListPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildListPlugIn075_Name), Description = nameof(PlugInResources.ShowGuildListPlugIn075_Description), ResourceType = typeof(PlugInResources))]
[Guid("1A7148DF-6E1B-47C7-9148-426F5E35F421")]
[MaximumClient(0, 89, ClientLanguage.Invariant)]
public class ShowGuildListPlugIn075 : IShowGuildListPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildListPlugIn075"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildListPlugIn075(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowGuildListAsync(IReadOnlyCollection<GuildListEntry> players, Guild guild)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
var sortedPlayers = players
.OrderBy(p => p.PlayerPosition != GuildPosition.GuildMaster)
.ThenBy(p => p.PlayerName)
.ToList();
var playerCount = sortedPlayers.Count;
int Write()
{
var size = GuildList075Ref.GetRequiredSize(playerCount);
var span = connection.Output.GetSpan(size)[..size];
var packet = new GuildList075Ref(span)
{
GuildMemberCount = (byte)playerCount,
IsInGuild = playerCount > 0,
CurrentScore = 0, // TODO
TotalScore = (uint)guild.Score,
};
int i = 0;
foreach (var member in sortedPlayers)
{
var memberBlock = packet[i];
memberBlock.Name = member.PlayerName;
memberBlock.ServerId = member.ServerId;
memberBlock.ServerId2 = (byte)(member.ServerId == 0xFF ? 0x7F : 0x80 + member.ServerId);
i++;
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ShowGuildMasterDialogPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowGuildMasterDialogPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildMasterDialogPlugIn_Name), Description = nameof(PlugInResources.ShowGuildMasterDialogPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("77d430e0-8bed-425b-8bb5-7bbafa9bfbff")]
public class ShowGuildMasterDialogPlugIn : IShowGuildMasterDialogPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildMasterDialogPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildMasterDialogPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowGuildMasterDialogAsync()
{
await this._player.Connection.SendShowGuildMasterDialogAsync().ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,53 @@
// <copyright file="ShowGuildRelationshipChangeResultPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IGuildRelationshipChangeResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildRelationshipChangeResultPlugIn_Name), Description = nameof(PlugInResources.ShowGuildRelationshipChangeResultPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("E6F7A8B9-C0D1-4E2F-3A4B-5C6D7E8F9A0B")]
public class ShowGuildRelationshipChangeResultPlugIn : IGuildRelationshipChangeResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildRelationshipChangeResultPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildRelationshipChangeResultPlugIn(RemotePlayer player)
{
this._player = player;
}
/// <inheritdoc/>
public async ValueTask ShowRemoveResultAsync(
bool result,
GameLogic.Views.Guild.GuildRelationshipType relationshipType = GameLogic.Views.Guild.GuildRelationshipType.Alliance,
GameLogic.Views.Guild.GuildRelationshipRequestType requestType = GameLogic.Views.Guild.GuildRelationshipRequestType.Leave)
{
await this._player.Connection.SendRemoveAllianceGuildResultAsync(result, requestType.Convert(), relationshipType.Convert()).ConfigureAwait(false);
}
/// <inheritdoc/>
public async ValueTask ShowResultAsync(
GameLogic.Views.Guild.GuildRelationshipType relationshipType,
GameLogic.Views.Guild.GuildRelationshipRequestType requestType,
GameLogic.Views.Guild.GuildRelationshipChangeResultType result,
ushort? guildMasterId)
{
await this._player.Connection.SendGuildRelationshipChangeResultAsync(
relationshipType.Convert(),
requestType.Convert(),
result.Convert(),
guildMasterId ?? 0).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,38 @@
// <copyright file="ShowGuildRelationshipRequestPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowGuildRelationshipRequestPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildRelationshipRequestPlugIn_Name), Description = nameof(PlugInResources.ShowGuildRelationshipRequestPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("F7A8B9C0-D1E2-4F3A-4B5C-6D7E8F9A0B1C")]
public class ShowGuildRelationshipRequestPlugIn : IShowGuildRelationshipRequestPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildRelationshipRequestPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildRelationshipRequestPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowRequestAsync(Player requestingGuildMaster, GameLogic.Views.Guild.GuildRelationshipType relationshipType, GameLogic.Views.Guild.GuildRelationshipRequestType requestType)
{
await this._player.Connection.SendGuildRelationshipRequestAsync(
relationshipType.Convert(),
requestType.Convert(),
requestingGuildMaster.GetId(this._player)).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,36 @@
// <copyright file="ShowGuildWarDeclaredPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowGuildWarDeclaredPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildWarDeclaredPlugIn_Name), Description = nameof(PlugInResources.ShowGuildWarDeclaredPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("50393D5D-01F2-43F8-B5D7-243D91B905BC")]
public class ShowGuildWarDeclaredPlugIn : IShowGuildWarDeclaredPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildWarDeclaredPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildWarDeclaredPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowDeclaredAsync()
{
if (this._player.GuildWarContext is { } guildWarContext)
{
await this._player.Connection.SendGuildWarDeclaredAsync(guildWarContext.EnemyTeamName, guildWarContext.WarType.Convert(), (byte)guildWarContext.Team).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ShowGuildWarRequestPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowGuildWarRequestPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildWarRequestPlugIn_Name), Description = nameof(PlugInResources.ShowGuildWarRequestPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("128250B3-6CFC-4C89-BF8A-B50C892A78D3")]
public class ShowGuildWarRequestPlugIn : IShowGuildWarRequestPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildWarRequestPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildWarRequestPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowRequestAsync(string requestingGuildName, GameLogic.GuildWar.GuildWarType warType)
{
await this._player.Connection.SendGuildWarRequestAsync(requestingGuildName, warType.Convert()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ShowGuildWarResultPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowGuildWarResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowGuildWarResultPlugIn_Name), Description = nameof(PlugInResources.ShowGuildWarResultPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("991247B9-D4A3-466D-A7CE-2621843CA94F")]
public class ShowGuildWarResultPlugIn : IShowGuildWarResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowGuildWarResultPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowGuildWarResultPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowResultAsync(string hostileGuildName, GuildWarResult result)
{
await this._player.Connection.SendGuildWarEndedAsync(result.Convert(), hostileGuildName).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ShowShowGuildWarRequestResultPlugIn.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.Guild;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IShowShowGuildWarRequestResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowShowGuildWarRequestResultPlugIn_Name), Description = nameof(PlugInResources.ShowShowGuildWarRequestResultPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("7DDF834C-218B-4F35-B66C-54579BE485D5")]
public class ShowShowGuildWarRequestResultPlugIn : IShowShowGuildWarRequestResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ShowShowGuildWarRequestResultPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ShowShowGuildWarRequestResultPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ShowResultAsync(GameLogic.Views.Guild.GuildWarRequestResult result)
{
await this._player.Connection.SendGuildWarRequestResultAsync(result.Convert()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="IAppearanceSerializer.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;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Views;
/// <summary>
/// Serializer of <see cref="IAppearanceData"/> objects.
/// </summary>
public interface IAppearanceSerializer : IViewPlugIn
{
/// <summary>
/// Gets the needed space for a serialized <see cref="IAppearanceData"/>.
/// </summary>
int NeededSpace { get; }
/// <summary>
/// Serializes the appearance data into the target span.
/// </summary>
/// <param name="target">The target which should be at least as big as <see cref="NeededSpace"/>.</param>
/// <param name="appearance">The appearance which should be serialized.</param>
/// <param name="useCache">If set to <c>true</c>, the result is cached and used in subsequent calls.</param>
void WriteAppearanceData(Span<byte> target, IAppearanceData appearance, bool useCache);
/// <summary>
/// Invalidates the cache for the given appearance.
/// </summary>
/// <param name="appearance">The appearance.</param>
void InvalidateCache(IAppearanceData appearance);
}

View File

@@ -0,0 +1,38 @@
// <copyright file="IItemSerializer.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;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.Persistence;
/// <summary>
/// Serializes the items into a byte array.
/// </summary>
public interface IItemSerializer : IViewPlugIn
{
/// <summary>
/// Gets the needed space for a serialized item.
/// </summary>
int NeededSpace { get; }
/// <summary>
/// Serializes the item into a byte array at the specified index.
/// </summary>
/// <param name="target">The target span.</param>
/// <param name="item">The item.</param>
/// <returns>The size of the serialized item.</returns>
int SerializeItem(Span<byte> target, Item item);
/// <summary>
/// Deserializes the byte array into a new item instance.
/// </summary>
/// <param name="source">The source span.</param>
/// <param name="gameConfiguration">The game configuration. Required to determine the item definition.</param>
/// <param name="persistenceContext">The persistence context. Required to create new objects.</param>
/// <returns>The created item instance.</returns>
Item DeserializeItem(Span<byte> source, GameConfiguration gameConfiguration, IContext persistenceContext);
}

View File

@@ -0,0 +1,33 @@
// <copyright file="BuyNpcItemFailedPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IBuyNpcItemFailedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.BuyNpcItemFailedPlugIn_Name), Description = nameof(PlugInResources.BuyNpcItemFailedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("915324d5-ccdf-42c0-b7c9-9479969346d8")]
public class BuyNpcItemFailedPlugIn : IBuyNpcItemFailedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="BuyNpcItemFailedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public BuyNpcItemFailedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public ValueTask BuyNpcItemFailedAsync()
{
return this._player.Connection.SendNpcItemBuyFailedAsync();
}
}

View File

@@ -0,0 +1,109 @@
// <copyright file="EnumExtensions.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.Inventory;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.GameLogic.Views.PlayerShop;
using MUnique.OpenMU.Network.Packets;
using MUnique.OpenMU.Network.Packets.ServerToClient;
/// <summary>
/// Extension methods to convert enum values.
/// </summary>
public static class EnumExtensions
{
/// <summary>
/// Converts the enum value into the enum type used in the packets.
/// </summary>
/// <param name="storage">The enum value.</param>
/// <returns>The converted value.</returns>
public static ItemStorageKind Convert(this Storages storage)
{
return storage switch
{
Storages.Inventory => ItemStorageKind.Inventory,
Storages.ChaosMachine => ItemStorageKind.ChaosMachine,
Storages.PersonalStore => ItemStorageKind.PlayerShop,
Storages.Trade => ItemStorageKind.Trade,
Storages.Vault => ItemStorageKind.Vault,
Storages.PetTrainer => ItemStorageKind.PetTrainer,
Storages.Refinery => ItemStorageKind.Refinery,
Storages.Smelting => ItemStorageKind.Smelting,
Storages.ItemRestore => ItemStorageKind.ItemRestore,
Storages.ChaosCardMaster => ItemStorageKind.ChaosCardMaster,
Storages.CherryBlossomSpirit => ItemStorageKind.CherryBlossomSpirit,
Storages.SeedCrafting => ItemStorageKind.SeedCrafting,
Storages.SeedSphereCrafting => ItemStorageKind.SeedSphereCrafting,
Storages.SeedMountCrafting => ItemStorageKind.SeedMountCrafting,
Storages.SeedUnmountCrafting => ItemStorageKind.SeedUnmountCrafting,
_ => throw new NotImplementedException($"Unhandled case {storage}."),
};
}
/// <summary>
/// Converts the enum value into the enum type used in the packets.
/// </summary>
/// <param name="storage">The enum value.</param>
/// <returns>The converted value.</returns>
public static Storages Convert(this ItemStorageKind storage)
{
return storage switch
{
ItemStorageKind.Inventory => Storages.Inventory,
ItemStorageKind.ChaosMachine => Storages.ChaosMachine,
ItemStorageKind.PlayerShop => Storages.PersonalStore,
ItemStorageKind.Trade => Storages.Trade,
ItemStorageKind.Vault => Storages.Vault,
ItemStorageKind.PetTrainer => Storages.PetTrainer,
ItemStorageKind.Refinery => Storages.Refinery,
ItemStorageKind.Smelting => Storages.Smelting,
ItemStorageKind.ItemRestore => Storages.ItemRestore,
ItemStorageKind.ChaosCardMaster => Storages.ChaosCardMaster,
ItemStorageKind.CherryBlossomSpirit => Storages.CherryBlossomSpirit,
ItemStorageKind.SeedCrafting => Storages.SeedCrafting,
ItemStorageKind.SeedSphereCrafting => Storages.SeedSphereCrafting,
ItemStorageKind.SeedMountCrafting => Storages.SeedMountCrafting,
ItemStorageKind.SeedUnmountCrafting => Storages.SeedUnmountCrafting,
_ => throw new NotImplementedException($"Unhandled case {storage}."),
};
}
/// <summary>
/// Converts the enum value into the enum type used in the packets.
/// </summary>
/// <param name="reason">The enum value.</param>
/// <returns>The converted value.</returns>
public static ItemPickUpRequestFailed.ItemPickUpFailReason Convert(this ItemPickFailReason reason)
{
return reason switch
{
ItemPickFailReason.General => ItemPickUpRequestFailed.ItemPickUpFailReason.General,
ItemPickFailReason.ItemStacked => ItemPickUpRequestFailed.ItemPickUpFailReason.ItemStacked,
ItemPickFailReason.MaximumInventoryMoneyReached => ItemPickUpRequestFailed.ItemPickUpFailReason.__MaximumInventoryMoneyReached,
_ => throw new NotImplementedException($"Unhandled case {reason}"),
};
}
/// <summary>
/// Converts the enum value into the enum type used in the packets.
/// </summary>
/// <param name="result">The enum value.</param>
/// <returns>The converted value.</returns>
public static PlayerShopSetItemPriceResponse.ItemPriceSetResult Convert(this ItemPriceResult result)
{
return result switch
{
ItemPriceResult.Failed => PlayerShopSetItemPriceResponse.ItemPriceSetResult.Failed,
ItemPriceResult.Success => PlayerShopSetItemPriceResponse.ItemPriceSetResult.Success,
ItemPriceResult.ItemSlotOutOfRange => PlayerShopSetItemPriceResponse.ItemPriceSetResult.ItemSlotOutOfRange,
ItemPriceResult.ItemNotFound => PlayerShopSetItemPriceResponse.ItemPriceSetResult.ItemNotFound,
ItemPriceResult.PriceNegative => PlayerShopSetItemPriceResponse.ItemPriceSetResult.PriceNegative,
ItemPriceResult.ItemIsBlocked => PlayerShopSetItemPriceResponse.ItemPriceSetResult.ItemIsBlocked,
ItemPriceResult.CharacterLevelTooLow => PlayerShopSetItemPriceResponse.ItemPriceSetResult.CharacterLevelTooLow,
_ => throw new NotImplementedException($"Unhandled case {result}."),
};
}
}

View File

@@ -0,0 +1,57 @@
// <copyright file="ItemAppearPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemAppearPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemAppearPlugIn_Name), Description = nameof(PlugInResources.ItemAppearPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("525105ee-c1bf-4800-b80b-bdcd6c8ce704")]
public class ItemAppearPlugIn : IItemAppearPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemAppearPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemAppearPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ItemAppearAsync(Item newItem)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
int Write()
{
var itemSerializer = this._player.ItemSerializer;
var size = ItemAddedToInventoryRef.GetRequiredSize(itemSerializer.NeededSpace);
var span = connection.Output.GetSpan(size)[..size];
var packet = new ItemAddedToInventoryRef(span)
{
InventorySlot = newItem.ItemSlot,
};
var itemSize = itemSerializer.SerializeItem(packet.ItemData, newItem);
var actualSize = ItemAddedToInventoryRef.GetRequiredSize(itemSize);
span.Slice(0, actualSize).SetPacketSize();
return actualSize;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ItemDropResultPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemDropResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemDropResultPlugIn_Name), Description = nameof(PlugInResources.ItemDropResultPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("377cd4cb-7334-4c74-a165-058e6bb46baf")]
public class ItemDropResultPlugIn : IItemDropResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemDropResultPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemDropResultPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ItemDropResultAsync(byte slot, bool success)
{
await this._player.Connection.SendItemDropResponseAsync(success, slot).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,35 @@
// <copyright file="ItemDurabilityChangedPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemDurabilityChangedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemDurabilityChangedPlugIn_Name), Description = nameof(PlugInResources.ItemDurabilityChangedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("f121286f-2e43-4a66-8f34-5dbe69304e1e")]
public class ItemDurabilityChangedPlugIn : IItemDurabilityChangedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemDurabilityChangedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemDurabilityChangedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ItemDurabilityChangedAsync(Item item, bool afterConsumption)
{
await this._player.Connection.SendItemDurabilityChangedAsync(item.ItemSlot, item.Durability(), afterConsumption).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,58 @@
// <copyright file="ItemMoveFailedPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemMoveFailedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemMoveFailedPlugIn_Name), Description = nameof(PlugInResources.ItemMoveFailedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("7fc3b870-a6a2-4751-bfa7-156ed97a1c87")]
public class ItemMoveFailedPlugIn : IItemMoveFailedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemMoveFailedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemMoveFailedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ItemMoveFailedAsync(Item? item)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
int Write()
{
var itemSerializer = this._player.ItemSerializer;
var size = ItemMoveRequestFailedRef.GetRequiredSize(itemSerializer.NeededSpace);
var span = connection.Output.GetSpan(size)[..size];
var packet = new ItemMoveRequestFailedRef(span);
if (item != null)
{
var itemSize = itemSerializer.SerializeItem(packet.ItemData, item);
var actualSize = ItemMoveRequestFailedRef.GetRequiredSize(itemSize);
span.Slice(0, actualSize).SetPacketSize();
return actualSize;
}
return size;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,66 @@
// <copyright file="ItemMovedPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemMovedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemMovedPlugIn_Name), Description = nameof(PlugInResources.ItemMovedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("5c4c20fe-763d-42b6-bdfa-2ec943b191bc")]
public class ItemMovedPlugIn : IItemMovedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemMovedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemMovedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ItemMovedAsync(Item item, byte toSlot, Storages storage)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
var itemSerializer = this._player.ItemSerializer;
var targetStorage = storage.Convert();
if (targetStorage == ItemStorageKind.PlayerShop)
{
targetStorage = ItemStorageKind.Inventory;
}
int Write()
{
var size = ItemMovedRef.GetRequiredSize(itemSerializer.NeededSpace);
var span = connection.Output.GetSpan(size)[..size];
var message = new ItemMovedRef(span)
{
TargetStorageType = targetStorage,
TargetSlot = toSlot,
};
var itemSize = itemSerializer.SerializeItem(message.ItemData, item);
var actualSize = ItemMovedRef.GetRequiredSize(itemSize);
span.Slice(0, actualSize).SetPacketSize();
return actualSize;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ItemPickUpFailedPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemPickUpFailedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemPickUpFailedPlugIn_Name), Description = nameof(PlugInResources.ItemPickUpFailedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("f73a2cee-14bb-4404-a321-767f848e3571")]
public class ItemPickUpFailedPlugIn : IItemPickUpFailedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemPickUpFailedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemPickUpFailedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask ItemPickUpFailedAsync(ItemPickFailReason reason)
{
await this._player.Connection.SendItemPickUpRequestFailedAsync(reason.Convert()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="ItemPriceSetResponsePlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.GameLogic.Views.PlayerShop;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemPriceSetResponsePlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemPriceSetResponsePlugIn_Name), Description = nameof(PlugInResources.ItemPriceSetResponsePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("f83daf12-28cb-47bc-bb23-7f8eba21c97c")]
public class ItemPriceSetResponsePlugIn : IItemPriceSetResponsePlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemPriceSetResponsePlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemPriceSetResponsePlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask ItemPriceSetResponseAsync(byte itemSlot, ItemPriceResult result)
{
await this._player.Connection.SendPlayerShopSetItemPriceResponseAsync(itemSlot, result.Convert()).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ItemRemovedPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemRemovedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemRemovedPlugIn_Name), Description = nameof(PlugInResources.ItemRemovedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("06f1b02c-32b5-4d88-8d09-719246c8ebfe")]
public class ItemRemovedPlugIn : IItemRemovedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemRemovedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemRemovedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask RemoveItemAsync(byte inventorySlot)
{
await this._player.Connection.SendItemRemovedAsync(inventorySlot).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="ItemSoldByPlayerShopPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemSoldByPlayerShopPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemSoldByPlayerShopPlugIn_Name), Description = nameof(PlugInResources.ItemSoldByPlayerShopPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("9594f5db-53b3-491f-a99c-11554c077942")]
public class ItemSoldByPlayerShopPlugIn : IItemSoldByPlayerShopPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemSoldByPlayerShopPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemSoldByPlayerShopPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ItemSoldByPlayerShopAsync(byte slot, Player buyer)
{
await this._player.Connection.SendPlayerShopItemSoldToPlayerAsync(slot, buyer.SelectedCharacter!.Name).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ItemSoldToNpcPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemSoldToNpcPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemSoldToNpcPlugIn_Name), Description = nameof(PlugInResources.ItemSoldToNpcPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("8372476a-7fb9-4f6e-a857-41c39c7d377c")]
public class ItemSoldToNpcPlugIn : IItemSoldToNpcPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemSoldToNpcPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemSoldToNpcPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ItemSoldToNpcAsync(bool success)
{
await this._player.Connection.SendNpcItemSellResultAsync(success, (uint)this._player.Money).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,56 @@
// <copyright file="ItemUpgradedPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IItemUpgradedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemUpgradedPlugIn_Name), Description = nameof(PlugInResources.ItemUpgradedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("ce4ed0a2-ec4e-4cbe-aabe-5573df86a659")]
public class ItemUpgradedPlugIn : IItemUpgradedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="ItemUpgradedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public ItemUpgradedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask ItemUpgradedAsync(Item item)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
int Write()
{
var itemSerializer = this._player.ItemSerializer;
var size = InventoryItemUpgradedRef.GetRequiredSize(itemSerializer.NeededSpace);
var span = connection.Output.GetSpan(size)[..size];
var packet = new InventoryItemUpgradedRef(span)
{
InventorySlot = item.ItemSlot,
};
var itemSize = itemSerializer.SerializeItem(packet.ItemData, item);
var actualSize = InventoryItemUpgradedRef.GetRequiredSize(itemSize);
span.Slice(0, actualSize).SetPacketSize();
return actualSize;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,57 @@
// <copyright file="NpcItemBoughtPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="INpcItemBoughtPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.NpcItemBoughtPlugIn_Name), Description = nameof(PlugInResources.NpcItemBoughtPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("cf45b5e2-158a-4998-bc73-fed4d4d31c0c")]
public class NpcItemBoughtPlugIn : INpcItemBoughtPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="NpcItemBoughtPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public NpcItemBoughtPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask NpcItemBoughtAsync(Item newItem)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
var itemSerializer = this._player.ItemSerializer;
int Write()
{
var size = ItemBoughtRef.GetRequiredSize(itemSerializer.NeededSpace);
var span = connection.Output.GetSpan(size)[..size];
var packet = new ItemBoughtRef(span)
{
InventorySlot = newItem.ItemSlot,
};
var itemSize = itemSerializer.SerializeItem(packet.ItemData, newItem);
var actualSize = ItemBoughtRef.GetRequiredSize(itemSize);
span.Slice(0, actualSize).SetPacketSize();
return actualSize;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,70 @@
// <copyright file="PlayerShopBuyRequestResultExtendedPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The extended implementation of the <see cref="IPlayerShopBuyRequestResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.PlayerShopBuyRequestResultExtendedPlugIn_Name), Description = nameof(PlugInResources.PlayerShopBuyRequestResultExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("83C89473-5977-4E08-82CF-94BC68C50676")]
[MinimumClient(106, 3, ClientLanguage.Invariant)]
public class PlayerShopBuyRequestResultExtendedPlugIn : IPlayerShopBuyRequestResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="PlayerShopBuyRequestResultExtendedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public PlayerShopBuyRequestResultExtendedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask ShowResultAsync(IIdentifiable? seller, ItemBuyResult result, Item? item)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
if (item is not null)
{
var itemSerializer = this._player.ItemSerializer;
var array = new byte[itemSerializer.NeededSpace];
itemSerializer.SerializeItem(array, item);
await connection.SendPlayerShopBuyResultExtendedAsync(seller?.GetId(this._player) ?? ushort.MaxValue, Convert(result), item.ItemSlot, array).ConfigureAwait(false);
return;
}
await connection.SendPlayerShopBuyResultExtendedAsync(seller?.GetId(this._player) ?? ushort.MaxValue, Convert(result), 0, Array.Empty<byte>()).ConfigureAwait(false);
}
private static PlayerShopBuyResultExtended.ResultKind Convert(ItemBuyResult result)
{
return result switch
{
ItemBuyResult.Success => PlayerShopBuyResultExtended.ResultKind.Success,
ItemBuyResult.NotAvailable => PlayerShopBuyResultExtended.ResultKind.NotAvailable,
ItemBuyResult.ShopNotOpened => PlayerShopBuyResultExtended.ResultKind.ShopNotOpened,
ItemBuyResult.InTransaction => PlayerShopBuyResultExtended.ResultKind.InTransaction,
ItemBuyResult.InvalidShopSlot => PlayerShopBuyResultExtended.ResultKind.InvalidShopSlot,
ItemBuyResult.NameMismatchOrPriceMissing => PlayerShopBuyResultExtended.ResultKind.NameMismatchOrPriceMissing,
ItemBuyResult.LackOfMoney => PlayerShopBuyResultExtended.ResultKind.LackOfMoney,
ItemBuyResult.MoneyOverflowOrNotEnoughSpace => PlayerShopBuyResultExtended.ResultKind.MoneyOverflowOrNotEnoughSpace,
ItemBuyResult.ItemBlock => PlayerShopBuyResultExtended.ResultKind.ItemBlock,
_ => PlayerShopBuyResultExtended.ResultKind.Undefined,
};
}
}

View File

@@ -0,0 +1,68 @@
// <copyright file="PlayerShopBuyRequestResultPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IPlayerShopBuyRequestResultPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.PlayerShopBuyRequestResultPlugIn_Name), Description = nameof(PlugInResources.PlayerShopBuyRequestResultPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("3b2498f2-3ae8-4700-8a61-1ffe49822caf")]
public class PlayerShopBuyRequestResultPlugIn : IPlayerShopBuyRequestResultPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="PlayerShopBuyRequestResultPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public PlayerShopBuyRequestResultPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
public async ValueTask ShowResultAsync(IIdentifiable? seller, ItemBuyResult result, Item? item)
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
if (item is not null)
{
var itemSerializer = this._player.ItemSerializer;
var array = new byte[itemSerializer.NeededSpace];
itemSerializer.SerializeItem(array, item);
await connection.SendPlayerShopBuyResultAsync(Convert(result), seller?.GetId(this._player) ?? ushort.MaxValue, array, item.ItemSlot).ConfigureAwait(false);
return;
}
await connection.SendPlayerShopBuyResultAsync(Convert(result), seller?.GetId(this._player) ?? ushort.MaxValue, Array.Empty<byte>(), 0).ConfigureAwait(false);
}
private static PlayerShopBuyResult.ResultKind Convert(ItemBuyResult result)
{
return result switch
{
ItemBuyResult.Success => PlayerShopBuyResult.ResultKind.Success,
ItemBuyResult.NotAvailable => PlayerShopBuyResult.ResultKind.NotAvailable,
ItemBuyResult.ShopNotOpened => PlayerShopBuyResult.ResultKind.ShopNotOpened,
ItemBuyResult.InTransaction => PlayerShopBuyResult.ResultKind.InTransaction,
ItemBuyResult.InvalidShopSlot => PlayerShopBuyResult.ResultKind.InvalidShopSlot,
ItemBuyResult.NameMismatchOrPriceMissing => PlayerShopBuyResult.ResultKind.NameMismatchOrPriceMissing,
ItemBuyResult.LackOfMoney => PlayerShopBuyResult.ResultKind.LackOfMoney,
ItemBuyResult.MoneyOverflowOrNotEnoughSpace => PlayerShopBuyResult.ResultKind.MoneyOverflowOrNotEnoughSpace,
ItemBuyResult.ItemBlock => PlayerShopBuyResult.ResultKind.ItemBlock,
_ => PlayerShopBuyResult.ResultKind.Undefined,
};
}
}

View File

@@ -0,0 +1,45 @@
// <copyright file="RequestedItemConsumptionFailedExtendedPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The extended implementation of the <see cref="IRequestedItemConsumptionFailedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.RequestedItemConsumptionFailedExtendedPlugIn_Name), Description = nameof(PlugInResources.RequestedItemConsumptionFailedExtendedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("8F98AAF4-E329-4DE2-B8D5-9169B64E20B2")]
[MinimumClient(106, 3, ClientLanguage.English)]
public class RequestedItemConsumptionFailedExtendedPlugIn : IRequestedItemConsumptionFailedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="RequestedItemConsumptionFailedExtendedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public RequestedItemConsumptionFailedExtendedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
/// <remarks>The server sends the current health/shield to the client, with <see cref="ItemConsumptionFailed"/>.</remarks>
public async ValueTask RequestedItemConsumptionFailedAsync()
{
if (this._player.Attributes is null)
{
return;
}
await this._player.Connection.SendItemConsumptionFailedExtendedAsync(
(uint)Math.Max(this._player.Attributes[Stats.CurrentHealth], 0f),
(uint)Math.Max(this._player.Attributes[Stats.CurrentShield], 0f))
.ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,43 @@
// <copyright file="RequestedItemConsumptionFailedPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IRequestedItemConsumptionFailedPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.RequestedItemConsumptionFailedPlugIn_Name), Description = nameof(PlugInResources.RequestedItemConsumptionFailedPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("c3a03a1c-71c7-4581-a244-0b1b31497f05")]
public class RequestedItemConsumptionFailedPlugIn : IRequestedItemConsumptionFailedPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="RequestedItemConsumptionFailedPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public RequestedItemConsumptionFailedPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc />
/// <remarks>The server sends the current health/shield to the client, with <see cref="ItemConsumptionFailed"/>.</remarks>
public async ValueTask RequestedItemConsumptionFailedAsync()
{
if (this._player.Attributes is null)
{
return;
}
await this._player.Connection.SendItemConsumptionFailedAsync(
(ushort)Math.Max(this._player.Attributes[Stats.CurrentHealth], 0f),
(ushort)Math.Max(this._player.Attributes[Stats.CurrentShield], 0f))
.ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,91 @@
// <copyright file="UpdateInventoryListPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IUpdateInventoryListPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateInventoryListPlugIn_Name), Description = nameof(PlugInResources.UpdateInventoryListPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("ba8ca7c7-a497-497e-b2f7-9f9366ff6ac5")]
public class UpdateInventoryListPlugIn : IUpdateInventoryListPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateInventoryListPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateInventoryListPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateInventoryListAsync()
{
var connection = this._player.Connection;
if (connection is null)
{
return;
}
// C4 00 00 00 F3 10 ...
var items = (this._player.Inventory?.Items is { } inventoryItems
? inventoryItems.Concat(this._player.ShopStorage?.Items ?? Enumerable.Empty<Item>())
: this._player.SelectedCharacter?.Inventory?.Items ?? Enumerable.Empty<Item>())
.OrderBy(item => item.ItemSlot)
.ToList();
int Write()
{
var itemSerializer = this._player.ItemSerializer;
var lengthPerItem = StoredItemRef.GetRequiredSize(itemSerializer.NeededSpace);
var size = CharacterInventoryRef.GetRequiredSize(items.Count, lengthPerItem);
var span = connection.Output.GetSpan(size)[..size];
var packet = new CharacterInventoryRef(span)
{
ItemCount = 0,
};
int headerSize = CharacterInventoryRef.GetRequiredSize(0, 0);
int actualSize = headerSize;
var seenSlots = new HashSet<byte>();
foreach (var item in items)
{
if (item.Definition is null)
{
this._player.Logger.LogWarning("Item {0} has no definition.", item);
continue;
}
if (!seenSlots.Add(item.ItemSlot))
{
this._player.Logger.LogWarning(
"Duplicate item slot {Slot} detected in inventory list update for player {Player}. Skipping item {Item}.",
item.ItemSlot,
this._player,
item);
continue;
}
var storedItem = new StoredItemRef(span[actualSize..]);
storedItem.ItemSlot = item.ItemSlot;
var itemSize = itemSerializer.SerializeItem(storedItem.ItemData, item);
actualSize += StoredItemRef.GetRequiredSize(itemSize);
packet.ItemCount++;
}
span.Slice(0, actualSize).SetPacketSize();
return actualSize;
}
await connection.SendAsync(Write).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="UpdateMoneyPlugIn.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.Inventory;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Inventory;
using MUnique.OpenMU.Network.Packets.ServerToClient;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The default implementation of the <see cref="IUpdateMoneyPlugIn"/> which is forwarding everything to the game client with specific data packets.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.UpdateMoneyPlugIn_Name), Description = nameof(PlugInResources.UpdateMoneyPlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("7a13a613-7098-4407-8ef5-39bae08ce12d")]
public class UpdateMoneyPlugIn : IUpdateMoneyPlugIn
{
private readonly RemotePlayer _player;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateMoneyPlugIn"/> class.
/// </summary>
/// <param name="player">The player.</param>
public UpdateMoneyPlugIn(RemotePlayer player) => this._player = player;
/// <inheritdoc/>
public async ValueTask UpdateMoneyAsync()
{
await this._player.Connection.SendInventoryMoneyUpdateAsync((uint)this._player.Money).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,29 @@
// <copyright file="ItemExtensions.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;
using MUnique.OpenMU.DataModel.Entities;
/// <summary>
/// Message relevant extensions for items.
/// </summary>
public static class ItemExtensions
{
/// <summary>
/// Gets the glow level of the item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The glow level of the item.</returns>
public static byte GetGlowLevel(this Item item) => GetGlowLevel(item.Level);
/// <summary>
/// Gets the glow level of the item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The glow level of the item.</returns>
public static byte GetGlowLevel(this ItemAppearance item) => GetGlowLevel(item.Level);
private static byte GetGlowLevel(int itemLevel) => (byte)((itemLevel - 1) / 2);
}

Some files were not shown because too many files have changed in this diff Show More