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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# C2 45 - AddTransformedCharacterToScopeExtended (by server)

## Is sent when

The player wears a monster transformation ring (extended client).

## Causes the following actions on the client side

The character appears as monster, defined by the Skin property.

## Structure

| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC2 | [Packet type](PacketTypes.md) |
| 1 | 2 | Short | | Packet header - length of the packet |
| 3 | 1 | Byte | 0x45 | Packet header - packet type identifier |
| 4 | 1 | Byte | 1 | CharacterCount; The number of characters in this packet. This packet contains only one character, because the size of the appearance data depends on the used appearance serializer. |
| 5 | 2 | ShortBigEndian | | Id |
| 7 | 1 | Byte | | CurrentPositionX |
| 8 | 1 | Byte | | CurrentPositionY |
| 9 | 2 | ShortBigEndian | | Skin |
| 11 | 10 | String | | Name |
| 21 | 1 | Byte | | TargetPositionX |
| 22 | 1 | Byte | | TargetPositionY |
| 23 | 4 bit | Byte | | Rotation |
| 23 << 0 | 4 bit | CharacterHeroState | | HeroState |
| 24 | | Binary | | AppearanceAndEffects; The appearance data, followed by the number of effects and the effect ids. |

### CharacterHeroState Enum

Defines the hero state of a character.

| Value | Name | Description |
|-------|------|-------------|
| 0 | New | The character is new and has the highest state. |
| 1 | Hero | The character is a hero. |
| 2 | LightHero | The character is a hero, but the state is almost gone. |
| 3 | Normal | The character is in a neutral state. |
| 4 | PlayerKillWarning | The character killed another character, and has a kill warning. |
| 5 | PlayerKiller1stStage | The character killed two characters, and has some restrictions. |
| 6 | PlayerKiller2ndStage | The character killed more than two characters, and has hard restrictions. |
1 change: 1 addition & 0 deletions docs/Packets/ServerToClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
* [C1 44 - PartyHealthUpdate (by server)](C1-44-PartyHealthUpdate_by-server.md)
* [C2 45 - AddTransformedCharactersToScope075 (by server)](C2-45-AddTransformedCharactersToScope075_by-server.md)
* [C2 45 - AddTransformedCharactersToScope (by server)](C2-45-AddTransformedCharactersToScope_by-server.md)
* [C2 45 - AddTransformedCharacterToScopeExtended (by server)](C2-45-AddTransformedCharacterToScopeExtended_by-server.md)
* [C1 46 - ChangeTerrainAttributes (by server)](C1-46-ChangeTerrainAttributes_by-server.md)
* [C1 48 - ShowEffect (by server)](C1-48-ShowEffect_by-server.md)
* [C3 4A - RageAttack (by server)](C3-4A-RageAttack_by-server.md)
Expand Down
63 changes: 63 additions & 0 deletions src/GameServer/RemoteView/World/NewPlayersInScopeExtendedPlugIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,67 @@ int Write()

await connection.SendAsync(Write).ConfigureAwait(false);
}

/// <inheritdoc />
protected override async ValueTask SendTransformedCharacterAsync(Player newPlayer, bool isSpawned)
{
var connection = this.Player.Connection;
if (connection is null)
{
return;
}

var selectedCharacter = newPlayer.SelectedCharacter;
if (selectedCharacter is null)
{
return;
}

int Write()
{
var appearanceSerializer = this.Player.AppearanceSerializer;
var activeEffects = newPlayer.MagicEffectList.VisibleEffects;
var requiredSize = AddTransformedCharacterToScopeExtendedRef.GetRequiredSize(appearanceSerializer.NeededSpace + activeEffects.Count + 1);

var span = connection.Output.GetSpan(requiredSize)[..requiredSize];
var packet = new AddTransformedCharacterToScopeExtendedRef(span);

packet.Id = newPlayer.GetId(this.Player);
if (isSpawned)
{
packet.Id |= 0x8000;
}

packet.CurrentPositionX = newPlayer.Position.X;
packet.CurrentPositionY = newPlayer.Position.Y;
packet.Skin = (ushort)newPlayer.Attributes![Stats.TransformationSkin];
packet.Name = selectedCharacter.Name;
if (newPlayer.IsWalking)
{
packet.TargetPositionX = newPlayer.WalkTarget.X;
packet.TargetPositionY = newPlayer.WalkTarget.Y;
}
else
{
packet.TargetPositionX = newPlayer.Position.X;
packet.TargetPositionY = newPlayer.Position.Y;
}

packet.Rotation = newPlayer.Rotation.ToPacketByte();
packet.HeroState = selectedCharacter.State.Convert();

appearanceSerializer.WriteAppearanceData(packet.AppearanceAndEffects, newPlayer.AppearanceData, true);

var effectsStartIndex = appearanceSerializer.NeededSpace;
packet.AppearanceAndEffects[effectsStartIndex] = (byte)activeEffects.Count;
for (int e = 0; e < activeEffects.Count; ++e)
{
packet.AppearanceAndEffects[effectsStartIndex + 1 + e] = (byte)activeEffects[e].Id;
}

return span.Length;
}

await connection.SendAsync(Write).ConfigureAwait(false);
}
}
84 changes: 46 additions & 38 deletions src/GameServer/RemoteView/World/NewPlayersInScopePlugIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,44 +135,13 @@ int Write()
await connection.SendAsync(Write).ConfigureAwait(false);
}

private async ValueTask<(IList<Player>? ShopPlayers, IList<Player>? GuildPlayers)> SendCharactersAsync(IEnumerable<Player> newPlayers, bool isSpawned)
{
IList<Player>? shopPlayers = null;
IList<Player>? guildPlayers = null;

var connection = this.Player.Connection;
if (connection is null)
{
return (shopPlayers, guildPlayers);
}

var newPlayerList = newPlayers.ToList();
foreach (var newPlayer in newPlayerList)
{
if (newPlayer.Attributes?[Stats.TransformationSkin] == 0)
{
await this.SendCharacterAsync(newPlayer, isSpawned).ConfigureAwait(false);
}
else
{
await this.SendTransformedCharacterAsync(newPlayer, isSpawned).ConfigureAwait(false);
}

if (newPlayer.ShopStorage?.StoreOpen ?? false)
{
(shopPlayers ??= new List<Player>()).Add(newPlayer);
}

if (newPlayer.GuildStatus != null)
{
(guildPlayers ??= new List<Player>()).Add(newPlayer);
}
}

return (shopPlayers, guildPlayers);
}

private async ValueTask SendTransformedCharacterAsync(Player newPlayer, bool isSpawned)
/// <summary>
/// Sends information about a new transformed player which has come into view.
/// </summary>
/// <param name="newPlayer">The new player.</param>
/// <param name="isSpawned">If the player has spawned.</param>
/// <returns>A <see cref="ValueTask"/>.</returns>
protected virtual async ValueTask SendTransformedCharacterAsync(Player newPlayer, bool isSpawned)
{
var connection = this.Player.Connection;
if (connection is null)
Expand Down Expand Up @@ -242,4 +211,43 @@ int Write()

await connection.SendAsync(Write).ConfigureAwait(false);
}

private async ValueTask<(IList<Player>? ShopPlayers, IList<Player>? GuildPlayers)> SendCharactersAsync(IEnumerable<Player> newPlayers, bool isSpawned)
{
IList<Player>? shopPlayers = null;
IList<Player>? guildPlayers = null;

var connection = this.Player.Connection;
if (connection is null)
{
return (shopPlayers, guildPlayers);
}

var newPlayerList = newPlayers.ToList();
foreach (var newPlayer in newPlayerList)
{
if (newPlayer.Attributes?[Stats.TransformationSkin] == 0)
{
await this.SendCharacterAsync(newPlayer, isSpawned).ConfigureAwait(false);
}
else
{
await this.SendTransformedCharacterAsync(newPlayer, isSpawned).ConfigureAwait(false);
}

if (newPlayer.ShopStorage?.StoreOpen ?? false)
{
shopPlayers ??= new List<Player>();
shopPlayers.Add(newPlayer);
}

if (newPlayer.GuildStatus != null)
{
guildPlayers ??= new List<Player>();
guildPlayers.Add(newPlayer);
}
}

return (shopPlayers, guildPlayers);
}
}
48 changes: 48 additions & 0 deletions src/Network/Packets/ServerToClient/ConnectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2494,6 +2494,54 @@ int WritePacket()
await connection.SendAsync(WritePacket).ConfigureAwait(false);
}

/// <summary>
/// Sends a <see cref="AddTransformedCharacterToScopeExtended" /> to this connection.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="id">The id.</param>
/// <param name="currentPositionX">The current position x.</param>
/// <param name="currentPositionY">The current position y.</param>
/// <param name="skin">The skin.</param>
/// <param name="name">The name.</param>
/// <param name="targetPositionX">The target position x.</param>
/// <param name="targetPositionY">The target position y.</param>
/// <param name="rotation">The rotation.</param>
/// <param name="heroState">The hero state.</param>
/// <param name="appearanceAndEffects">The appearance data, followed by the number of effects and the effect ids.</param>
/// <param name="characterCount">The number of characters in this packet. This packet contains only one character, because the size of the appearance data depends on the used appearance serializer.</param>
/// <remarks>
/// Is sent by the server when: The player wears a monster transformation ring (extended client).
/// Causes reaction on client side: The character appears as monster, defined by the Skin property.
/// </remarks>
public static async ValueTask SendAddTransformedCharacterToScopeExtendedAsync(this IConnection? connection, ushort @id, byte @currentPositionX, byte @currentPositionY, ushort @skin, string @name, byte @targetPositionX, byte @targetPositionY, byte @rotation, CharacterHeroState @heroState, Memory<byte> @appearanceAndEffects, byte @characterCount = 1)
{
if (connection is null)
{
return;
}

int WritePacket()
{
var length = AddTransformedCharacterToScopeExtendedRef.GetRequiredSize(appearanceAndEffects.Length);
var packet = new AddTransformedCharacterToScopeExtendedRef(connection.Output.GetSpan(length)[..length]);
packet.CharacterCount = @characterCount;
packet.Id = @id;
packet.CurrentPositionX = @currentPositionX;
packet.CurrentPositionY = @currentPositionY;
packet.Skin = @skin;
packet.Name = @name;
packet.TargetPositionX = @targetPositionX;
packet.TargetPositionY = @targetPositionY;
packet.Rotation = @rotation;
packet.HeroState = @heroState;
@appearanceAndEffects.Span.CopyTo(packet.AppearanceAndEffects);

return packet.Header.Length;
}

await connection.SendAsync(WritePacket).ConfigureAwait(false);
}

/// <summary>
/// Sends a <see cref="ShowEffect" /> to this connection.
/// </summary>
Expand Down
Loading
Loading