A Core/NosSmooth.Game/Events/Battle/AoESkillUsedEvent.cs => Core/NosSmooth.Game/Events/Battle/AoESkillUsedEvent.cs +26 -0
@@ 0,0 1,26 @@
+//
+// AoESkillUsedEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Characters;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Packets.Enums.Battle;
+
+namespace NosSmooth.Game.Events.Battle;
+
+/// <summary>
+/// An AoE skill has been used. (bs packet)
+/// </summary>
+/// <remarks>
+/// The damage to various entities will be sent in respective Su packets.
+/// TODO find out connections between su and bs packets.
+/// </remarks>
+public record AoESkillUsedEvent
+(
+ ILivingEntity Caster,
+ Skill Skill,
+ Position TargetPosition
+) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Battle/SkillUsedEvent.cs => Core/NosSmooth.Game/Events/Battle/SkillUsedEvent.cs +32 -0
@@ 0,0 1,32 @@
+//
+// SkillUsedEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Characters;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Packets.Enums.Battle;
+
+namespace NosSmooth.Game.Events.Battle;
+
+/// <summary>
+/// A skill has been used.
+/// </summary>
+/// <param name="Caster">The caster entity of the skill.</param>
+/// <param name="Target">The target entity of the skill.</param>
+/// <param name="Skill">The skill that has been used with the information about the skill.</param>
+/// <param name="SkillVNum">The vnum of the skill.</param>
+/// <param name="TargetPosition">The position of the target.</param>
+/// <param name="Hit"></param>
+/// <param name="Damage"></param>
+public record SkillUsedEvent
+(
+ ILivingEntity Caster,
+ ILivingEntity Target,
+ Skill Skill,
+ Position? TargetPosition,
+ HitMode? Hit,
+ uint Damage
+) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Characters/CharacterDiedEvent.cs => Core/NosSmooth.Game/Events/Characters/CharacterDiedEvent.cs +14 -0
@@ 0,0 1,14 @@
+//
+// CharacterDiedEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Characters;
+
+namespace NosSmooth.Game.Events.Characters;
+
+/// <summary>
+/// The playing character has died.
+/// </summary>
+public record CharacterDiedEvent(Skill? KillSkill) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Characters/CharacterStunEvent.cs => Core/NosSmooth.Game/Events/Characters/CharacterStunEvent.cs +13 -0
@@ 0,0 1,13 @@
+//
+// CharacterStunEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace NosSmooth.Game.Events.Characters;
+
+/// <summary>
+/// The character has been stunned or unstunned.
+/// </summary>
+/// <param name="Stunned">Whether the character is stunned.</param>
+public record CharacterStunEvent(bool Stunned);<
\ No newline at end of file
M Core/NosSmooth.Game/Events/Characters/ReceivedCharacterDataEvent.cs => Core/NosSmooth.Game/Events/Characters/ReceivedCharacterDataEvent.cs +0 -2
@@ 11,10 11,8 @@ namespace NosSmooth.Game.Events.Characters;
/// <summary>
/// Represents received new updated character data.
/// </summary>
-/// <param name="OldCharacter">The old data.</param>
/// <param name="Character">The newly received data.</param>
public record ReceivedCharacterDataEvent
(
- Character? OldCharacter,
Character Character
) : IGameEvent;=
\ No newline at end of file
M Core/NosSmooth.Game/Events/Characters/SkillsReceivedEvent.cs => Core/NosSmooth.Game/Events/Characters/SkillsReceivedEvent.cs +4 -0
@@ 8,4 8,8 @@ using NosSmooth.Game.Data.Characters;
namespace NosSmooth.Game.Events.Characters;
+/// <summary>
+/// Received skills of the character.
+/// </summary>
+/// <param name="Skills">The skills.</param>
public record SkillsReceivedEvent(Skills Skills) : IGameEvent;=
\ No newline at end of file
A Core/NosSmooth.Game/Events/Entities/EntityDiedEvent.cs => Core/NosSmooth.Game/Events/Entities/EntityDiedEvent.cs +21 -0
@@ 0,0 1,21 @@
+//
+// EntityDiedEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Characters;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Events.Characters;
+
+namespace NosSmooth.Game.Events.Entities;
+
+/// <summary>
+/// An entity has died.
+/// </summary>
+/// <remarks>
+/// Is not emitted for the character, see <see cref="CharacterDiedEvent"/>.
+/// </remarks>
+/// <param name="Entity">The entity that has died.</param>
+/// <param name="KillSkill">The skill that was used to kill the entity, if known.</param>
+public record EntityDiedEvent(ILivingEntity Entity, Skill? KillSkill) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Entities/EntityJoinedMapEvent.cs => Core/NosSmooth.Game/Events/Entities/EntityJoinedMapEvent.cs +15 -0
@@ 0,0 1,15 @@
+//
+// EntityJoinedMapEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Entities;
+
+namespace NosSmooth.Game.Events.Entities;
+
+/// <summary>
+/// The given entity has joined the map.
+/// </summary>
+/// <param name="Entity">The entity.</param>
+public record EntityJoinedMapEvent(IEntity Entity) : IGameEvent;<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Entities/EntityLeftMapEvent.cs => Core/NosSmooth.Game/Events/Entities/EntityLeftMapEvent.cs +21 -0
@@ 0,0 1,21 @@
+//
+// EntityLeftMapEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Maps;
+
+namespace NosSmooth.Game.Events.Entities;
+
+/// <summary>
+/// An entity has left the map.
+/// </summary>
+/// <param name="Entity">The entity that has left.</param>
+public record EntityLeftMapEvent
+(
+ IEntity Entity,
+ Portal? Portal,
+ bool? Died
+) : IGameEvent;<
\ No newline at end of file
R Core/NosSmooth.Game/Events/Entities/MovedEvent.cs => Core/NosSmooth.Game/Events/Entities/EntityMovedEvent.cs +8 -3
@@ 1,5 1,5 @@
//
-// MovedEvent.cs
+// EntityMovedEvent.cs
//
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
@@ 9,10 9,15 @@ using NosSmooth.Game.Data.Info;
namespace NosSmooth.Game.Events.Entities;
-public record MovedEvent
+/// <summary>
+/// An entity has moved.
+/// </summary>
+/// <param name="Entity">The entity.</param>
+/// <param name="OldPosition">The previous position of the entity.</param>
+/// <param name="NewPosition">The new position of the entity.</param>
+public record EntityMovedEvent
(
IEntity Entity,
- long EntityId,
Position? OldPosition,
Position NewPosition
) : IGameEvent;=
\ No newline at end of file
A Core/NosSmooth.Game/Events/Entities/EntityRevivedEvent.cs => Core/NosSmooth.Game/Events/Entities/EntityRevivedEvent.cs +15 -0
@@ 0,0 1,15 @@
+//
+// EntityRevivedEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Entities;
+
+namespace NosSmooth.Game.Events.Entities;
+
+/// <summary>
+/// The given entity has been revived.
+/// </summary>
+/// <param name="Entity"></param>
+public record EntityRevivedEvent(ILivingEntity Entity);<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Entities/ItemDroppedEvent.cs => Core/NosSmooth.Game/Events/Entities/ItemDroppedEvent.cs +15 -0
@@ 0,0 1,15 @@
+//
+// ItemDroppedEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Entities;
+
+namespace NosSmooth.Game.Events.Entities;
+
+/// <summary>
+/// An item has been dropped.
+/// </summary>
+/// <param name="Item">The item that has been dropped.</param>
+public record ItemDroppedEvent(IEntity Item) : IGameEvent;<
\ No newline at end of file
D Core/NosSmooth.Game/Events/Login/LogoutEvent.cs => Core/NosSmooth.Game/Events/Login/LogoutEvent.cs +0 -14
@@ 1,14 0,0 @@
-//
-// LogoutEvent.cs
-//
-// Copyright (c) František Boháček. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-namespace NosSmooth.Game.Events.Login;
-
-/// <summary>
-/// Represents event that is emitted if the user is logged out / changing the server etc.
-/// </summary>
-public class LogoutEvent : IGameEvent
-{
-}>
\ No newline at end of file
A Core/NosSmooth.Game/Events/Map/MapChangedEvent.cs => Core/NosSmooth.Game/Events/Map/MapChangedEvent.cs +18 -0
@@ 0,0 1,18 @@
+//
+// MapChangedEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace NosSmooth.Game.Events.Map;
+
+/// <summary>
+/// A map has been changed.
+/// </summary>
+/// <param name="PreviousMap">The previous map.</param>
+/// <param name="CurrentMap">The new map.</param>
+public record MapChangedEvent
+(
+ Data.Maps.Map? PreviousMap,
+ Data.Maps.Map CurrentMap
+) : IGameEvent;<
\ No newline at end of file
D Core/NosSmooth.Game/Events/Players/SkillUsedEvent.cs => Core/NosSmooth.Game/Events/Players/SkillUsedEvent.cs +0 -13
@@ 1,13 0,0 @@
-//
-// SkillUsedEvent.cs
-//
-// Copyright (c) František Boháček. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using NosSmooth.Game.Data.Characters;
-using NosSmooth.Game.Data.Entities;
-using NosSmooth.Game.Data.Info;
-
-namespace NosSmooth.Game.Events.Players;
-
-public record SkillUsedEvent(ILivingEntity? Entity, long EntityId, Skill? Skill, long SkillVNum, long TargetId, Position? TargetPosition) : IGameEvent;>
\ No newline at end of file
M Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs => Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs +14 -1
@@ 11,6 11,7 @@ using NosSmooth.Game.Apis;
using NosSmooth.Game.Events.Core;
using NosSmooth.Game.PacketHandlers.Characters;
using NosSmooth.Game.PacketHandlers.Entities;
+using NosSmooth.Game.PacketHandlers.Map;
namespace NosSmooth.Game.Extensions;
@@ 36,7 37,19 @@ public static class ServiceCollectionExtensions
.AddPacketResponder<CharacterInitResponder>()
.AddPacketResponder<SkillResponder>()
.AddPacketResponder<WalkResponder>()
- .AddPacketResponder<SkillUsedResponder>();
+ .AddPacketResponder<SkillUsedResponder>()
+ .AddPacketResponder<AoeSkillUsedResponder>()
+ .AddPacketResponder<AtResponder>()
+ .AddPacketResponder<CMapResponder>()
+ .AddPacketResponder<DropResponder>()
+ .AddPacketResponder<GpPacketResponder>()
+ .AddPacketResponder<InResponder>()
+ .AddPacketResponder<MoveResponder>()
+ .AddPacketResponder<OutResponder>()
+ .AddPacketResponder<StatPacketResponder>()
+ .AddPacketResponder<StPacketResponder>()
+ .AddPacketResponder<CondPacketResponder>()
+ .AddPacketResponder<EqResponder>();
serviceCollection
.AddTransient<NostaleChatPacketApi>()
M Core/NosSmooth.Game/Extensions/SkillsExtensions.cs => Core/NosSmooth.Game/Extensions/SkillsExtensions.cs +30 -1
@@ 18,9 18,38 @@ public static class SkillsExtensions
/// Tries to get the skill of the specified vnum.
/// </summary>
/// <param name="skills">The skills of the player.</param>
+ /// <param name="castId">The cast id to search for.</param>
+ /// <returns>The skill, if found.</returns>
+ public static Result<Skill> TryGetSkillByCastId(this Skills skills, short castId)
+ {
+ if (skills.PrimarySkill.Info?.CastId == castId)
+ {
+ return skills.PrimarySkill;
+ }
+
+ if (skills.SecondarySkill.Info?.CastId == castId)
+ {
+ return skills.SecondarySkill;
+ }
+
+ foreach (Skill skill in skills.OtherSkills)
+ {
+ if (skill.Info?.CastId == castId)
+ {
+ return skill;
+ }
+ }
+
+ return new NotFoundError();
+ }
+
+ /// <summary>
+ /// Tries to get the skill of the specified vnum.
+ /// </summary>
+ /// <param name="skills">The skills of the player.</param>
/// <param name="skillVNum">The vnum to search for.</param>
/// <returns>The skill, if found.</returns>
- public static Result<Skill> TryGetSkill(this Skills skills, long skillVNum)
+ public static Result<Skill> TryGetSkillByVNum(this Skills skills, long skillVNum)
{
if (skills.PrimarySkill.SkillVNum == skillVNum)
{
M Core/NosSmooth.Game/Game.cs => Core/NosSmooth.Game/Game.cs +30 -8
@@ 51,7 51,6 @@ public class Game
internal set
{
_currentMap = value;
- MapChanged?.CancelAfter(TimeSpan.FromSeconds(_options.EntityCacheDuration));
}
}
@@ 64,11 63,6 @@ public class Game
public Raid? CurrentRaid { get; internal set; }
/// <summary>
- /// Cancellation token for changing the map to use in memory cache.
- /// </summary>
- internal CancellationTokenSource? MapChanged { get; private set; }
-
- /// <summary>
/// Creates the character if it is null, or updates the current character.
/// </summary>
/// <param name="create">The function for creating the character.</param>
@@ 103,9 97,9 @@ public class Game
/// <param name="releaseSemaphore">Whether to release the semaphore used for changing the map.</param>
/// <param name="ct">The cancellation token for cancelling the operation.</param>
/// <returns>The updated character.</returns>
- internal async Task<Map> CreateMapAsync
+ internal async Task<Map?> CreateMapAsync
(
- Func<Map> create,
+ Func<Map?> create,
bool releaseSemaphore = true,
CancellationToken ct = default
)
@@ 120,6 114,34 @@ public class Game
);
}
+ /// <summary>
+ /// Creates the map if it is null, or updates the current map.
+ /// </summary>
+ /// <param name="create">The function for creating the map.</param>
+ /// <param name="update">The function for updating the map.</param>
+ /// <param name="releaseSemaphore">Whether to release the semaphore used for changing the map.</param>
+ /// <param name="ct">The cancellation token for cancelling the operation.</param>
+ /// <returns>The updated character.</returns>
+ internal async Task<Map?> CreateOrUpdateMapAsync
+ (
+ Func<Map?> create,
+ Func<Map?, Map?> update,
+ bool releaseSemaphore = true,
+ CancellationToken ct = default
+ )
+ {
+ return await CreateOrUpdateAsync<Map?>
+ (
+ GameSemaphoreType.Map,
+ () => CurrentMap,
+ m => CurrentMap = m,
+ create,
+ update,
+ releaseSemaphore,
+ ct
+ );
+ }
+
private async Task<T> CreateAsync<T>
(
GameSemaphoreType type,
A Core/NosSmooth.Game/Helpers/EntityHelpers.cs => Core/NosSmooth.Game/Helpers/EntityHelpers.cs +51 -0
@@ 0,0 1,51 @@
+//
+// EntityHelpers.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Packets.Enums;
+
+namespace NosSmooth.Game.Helpers;
+
+/// <summary>
+/// Helper methods for various operations with entities.
+/// </summary>
+public static class EntityHelpers
+{
+ /// <summary>
+ /// Create an entity from the given type and id.
+ /// </summary>
+ /// <param name="type">The entity type.</param>
+ /// <param name="entityId">The entity id.</param>
+ /// <returns>The entity.</returns>
+ public static IEntity CreateEntity(EntityType type, long entityId)
+ {
+ switch (type)
+ {
+ case EntityType.Npc:
+ return new Npc
+ {
+ Id = entityId
+ };
+ case EntityType.Monster:
+ return new Monster
+ {
+ Id = entityId
+ };
+ case EntityType.Player:
+ return new Player
+ {
+ Id = entityId
+ };
+ case EntityType.Object:
+ return new GroundItem
+ {
+ Id = entityId
+ };
+ }
+
+ throw new Exception("Unknown entity type.");
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/Helpers/EquipmentHelpers.cs => Core/NosSmooth.Game/Helpers/EquipmentHelpers.cs +97 -0
@@ 0,0 1,97 @@
+//
+// EquipmentHelpers.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Reflection;
+using NosSmooth.Data.Abstractions;
+using NosSmooth.Game.Data.Items;
+using NosSmooth.Packets.Server.Maps;
+using NosSmooth.Packets.Server.Weapons;
+using Remora.Results;
+
+namespace NosSmooth.Game.Helpers;
+
+/// <summary>
+/// Helpers for creating equipment from packets.
+/// </summary>
+public static class EquipmentHelpers
+{
+ /// <summary>
+ /// Create <see cref="Equipment"/> from the given in equipment subpacket.
+ /// </summary>
+ /// <param name="infoService">The info service.</param>
+ /// <param name="equipmentSubPacket">The subpacket.</param>
+ /// <param name="weaponUpgradeRare">The weapon upgrade.</param>
+ /// <param name="armorUpgradeRare">The armor upgrade.</param>
+ /// <param name="ct">The cancellation token for cancelling the operation.</param>
+ /// <returns>The equipment or an error.</returns>
+ public static async Task<Equipment> CreateEquipmentFromInSubpacketAsync
+ (
+ IInfoService infoService,
+ InEquipmentSubPacket equipmentSubPacket,
+ UpgradeRareSubPacket? weaponUpgradeRare = default,
+ UpgradeRareSubPacket? armorUpgradeRare = default,
+ CancellationToken ct = default
+ )
+ {
+ var fairy = await CreateItemAsync(infoService, equipmentSubPacket.FairyVNum, ct);
+ var mask = await CreateItemAsync(infoService, equipmentSubPacket.MaskVNum, ct);
+ var hat = await CreateItemAsync(infoService, equipmentSubPacket.HatVNum, ct);
+ var costumeSuit = await CreateItemAsync(infoService, equipmentSubPacket.CostumeSuitVNum, ct);
+ var costumeHat = await CreateItemAsync(infoService, equipmentSubPacket.CostumeHatVNum, ct);
+ var mainWeapon = await CreateItemAsync(infoService, equipmentSubPacket.MainWeaponVNum, weaponUpgradeRare, ct);
+ var secondaryWeapon = await CreateItemAsync(infoService, equipmentSubPacket.SecondaryWeaponVNum, null, ct);
+ var armor = await CreateItemAsync(infoService, equipmentSubPacket.ArmorVNum, armorUpgradeRare, ct);
+
+ return new Equipment
+ (
+ hat,
+ armor,
+ mainWeapon,
+ secondaryWeapon,
+ mask,
+ fairy,
+ costumeSuit,
+ costumeHat,
+ equipmentSubPacket.WeaponSkin,
+ equipmentSubPacket.WingSkin
+ );
+ }
+
+ private static async Task<Item?> CreateItemAsync(IInfoService infoService, int? itemVNum, CancellationToken ct = default)
+ {
+ if (itemVNum is null)
+ {
+ return null;
+ }
+
+ var itemInfo = await infoService.GetItemInfoAsync(itemVNum.Value, ct);
+
+ return new Item
+ (
+ itemVNum.Value,
+ itemInfo.IsSuccess ? itemInfo.Entity : null
+ );
+ }
+
+ private static async Task<UpgradeableItem?> CreateItemAsync
+ (IInfoService infoService, int? itemVNum, UpgradeRareSubPacket? upgradeRareSubPacket, CancellationToken ct = default)
+ {
+ if (itemVNum is null)
+ {
+ return null;
+ }
+
+ var itemInfo = await infoService.GetItemInfoAsync(itemVNum.Value, ct);
+
+ return new UpgradeableItem
+ (
+ itemVNum.Value,
+ itemInfo.IsSuccess ? itemInfo.Entity : null,
+ upgradeRareSubPacket?.Upgrade,
+ upgradeRareSubPacket?.Rare
+ );
+ }
+}<
\ No newline at end of file
M Core/NosSmooth.Game/NosSmooth.Game.csproj => Core/NosSmooth.Game/NosSmooth.Game.csproj +2 -1
@@ 9,11 9,12 @@
<ItemGroup>
<Folder Include="Apis" />
- <Folder Include="Data\Quests" />
+ <Folder Include="Events\Players" />
<Folder Include="PacketHandlers" />
</ItemGroup>
<ItemGroup>
+ <ProjectReference Include="..\..\Data\NosSmooth.Data.Abstractions\NosSmooth.Data.Abstractions.csproj" />
<ProjectReference Include="..\NosSmooth.Core\NosSmooth.Core.csproj" />
</ItemGroup>
M Core/NosSmooth.Game/PacketHandlers/Characters/CharacterInitResponder.cs => Core/NosSmooth.Game/PacketHandlers/Characters/CharacterInitResponder.cs +69 -68
@@ 43,51 43,51 @@ public class CharacterInitResponder : IPacketResponder<CInfoPacket>, IPacketResp
var character = await _game.CreateOrUpdateCharacterAsync
(
() => new Character
- (
- Family: new Family(packet.FamilyId, packet.FamilyName, packet.FamilyLevel),
- Group: new Group(packet.GroupId, default, default),
- Id: packet.CharacterId,
- Name: packet.Name,
- AuthorityType: packet.Authority,
- Sex: packet.Sex,
- HairStyle: packet.HairStyle,
- HairColor: packet.HairColor,
- Class: packet.Class,
- Icon: packet.Icon,
- Compliment: packet.Compliment,
- Morph: new Morph(packet.MorphVNum, packet.MorphUpgrade),
- Invisible: packet.IsInvisible,
- ArenaWinner: packet.ArenaWinner
- ),
- (character)
- => character with
+ {
+ Family = new Family(packet.FamilyId, null, packet.FamilyName, packet.FamilyLevel, null),
+ Group = new Group(packet.GroupId, default, default),
+ Id = packet.CharacterId,
+ Name = packet.Name,
+ Authority = packet.Authority,
+ Sex = packet.Sex,
+ HairStyle = packet.HairStyle,
+ HairColor = packet.HairColor,
+ Class = packet.Class,
+ Icon = packet.Icon,
+ Compliment = packet.Compliment,
+ Morph = new Morph(packet.MorphVNum, packet.MorphUpgrade),
+ IsInvisible = packet.IsInvisible,
+ ArenaWinner = packet.ArenaWinner
+ },
+ (character) =>
+ {
+ character.Id = packet.CharacterId;
+ character.Authority = packet.Authority;
+ character.Sex = packet.Sex;
+ character.HairStyle = packet.HairStyle;
+ character.HairColor = packet.HairColor;
+ character.Class = packet.Class;
+ character.Icon = packet.Icon;
+ character.Compliment = packet.Compliment;
+ character.Group = (character.Group ?? new Group(packet.GroupId, null, null)) with
+ {
+ Id = packet.GroupId
+ };
+ character.Morph = (character.Morph ?? new Morph(packet.MorphVNum, packet.MorphUpgrade)) with
{
- Id = packet.CharacterId,
- AuthorityType = packet.Authority,
- Sex = packet.Sex,
- HairStyle = packet.HairStyle,
- HairColor = packet.HairColor,
- Class = packet.Class,
- Icon = packet.Icon,
- Compliment = packet.Compliment,
- Group = (character.Group ?? new Group(packet.GroupId, null, null)) with
- {
- Id = packet.GroupId
- },
- Morph = (character.Morph ?? new Morph(packet.MorphVNum, packet.MorphUpgrade)) with
- {
- VNum = packet.MorphVNum, Upgrade = packet.MorphUpgrade
- },
- ArenaWinner = packet.ArenaWinner,
- Invisible = packet.IsInvisible,
- Family = new Family(packet.FamilyId, packet.FamilyName, packet.FamilyLevel)
- },
+ VNum = packet.MorphVNum, Upgrade = packet.MorphUpgrade
+ };
+ character.ArenaWinner = packet.ArenaWinner;
+ character.IsInvisible = packet.IsInvisible;
+ character.Family = new Family(packet.FamilyId, null, packet.FamilyName, packet.FamilyLevel, null);
+ return character;
+ },
ct: ct
);
if (character != oldCharacter)
{
- return await _eventDispatcher.DispatchEvent(new ReceivedCharacterDataEvent(oldCharacter, character), ct);
+ return await _eventDispatcher.DispatchEvent(new ReceivedCharacterDataEvent(character), ct);
}
return Result.FromSuccess();
@@ 102,28 102,28 @@ public class CharacterInitResponder : IPacketResponder<CInfoPacket>, IPacketResp
var character = await _game.CreateOrUpdateCharacterAsync
(
() => new Character
- (
- SkillCp: packet.SkillCp,
- Reputation: packet.Reputation,
- Level: new Level(packet.Level, packet.LevelXp, packet.XpLoad),
- JobLevel: new Level(packet.JobLevel, packet.JobLevelXp, packet.JobXpLoad),
- HeroLevel: new Level(packet.HeroLevel, packet.HeroLevelXp, packet.HeroXpLoad)
- ),
+ {
+ SkillCp = packet.SkillCp,
+ Reputation = packet.Reputation,
+ PlayerLevel = new Level(packet.Level, packet.LevelXp, packet.XpLoad),
+ JobLevel = new Level(packet.JobLevel, packet.JobLevelXp, packet.JobXpLoad),
+ HeroLevelStruct = new Level(packet.HeroLevel, packet.HeroLevelXp, packet.HeroXpLoad)
+ },
(character) =>
- character with
- {
- SkillCp = packet.SkillCp,
- Reputation = packet.Reputation,
- Level = new Level(packet.Level, packet.LevelXp, packet.XpLoad),
- JobLevel = new Level(packet.JobLevel, packet.JobLevelXp, packet.JobXpLoad),
- HeroLevel = new Level(packet.HeroLevel, packet.HeroLevelXp, packet.HeroXpLoad)
- },
+ {
+ character.SkillCp = packet.SkillCp;
+ character.Reputation = packet.Reputation;
+ character.PlayerLevel = new Level(packet.Level, packet.LevelXp, packet.XpLoad);
+ character.JobLevel = new Level(packet.JobLevel, packet.JobLevelXp, packet.JobXpLoad);
+ character.HeroLevelStruct = new Level(packet.HeroLevel, packet.HeroLevelXp, packet.HeroXpLoad);
+ return character;
+ },
ct: ct
);
if (character != oldCharacter)
{
- return await _eventDispatcher.DispatchEvent(new ReceivedCharacterDataEvent(oldCharacter, character), ct);
+ return await _eventDispatcher.DispatchEvent(new ReceivedCharacterDataEvent(character), ct);
}
return Result.FromSuccess();
@@ 144,24 144,25 @@ public class CharacterInitResponder : IPacketResponder<CInfoPacket>, IPacketResp
(
() => throw new NotImplementedException(),
(character) =>
- character with
- {
- Morph = new Morph
- (
- packet.MorphVNum,
- packet.MorphUpgrade,
- packet.MorphDesign,
- packet.MorphBonus,
- packet.MorphSkin
- ),
- Size = packet.Size
- },
+ {
+ character.Morph = new Morph
+ (
+ packet.MorphVNum,
+ packet.MorphUpgrade,
+ packet.MorphDesign,
+ packet.MorphBonus,
+ packet.MorphSkin
+ );
+
+ character.Size = packet.Size;
+ return character;
+ },
ct: ct
);
if (oldCharacter != character)
{
- return await _eventDispatcher.DispatchEvent(new ReceivedCharacterDataEvent(oldCharacter, character), ct);
+ return await _eventDispatcher.DispatchEvent(new ReceivedCharacterDataEvent(character), ct);
}
return Result.FromSuccess();
M Core/NosSmooth.Game/PacketHandlers/Characters/SkillResponder.cs => Core/NosSmooth.Game/PacketHandlers/Characters/SkillResponder.cs +42 -11
@@ 4,7 4,10 @@
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using Microsoft.Extensions.Logging;
+using NosSmooth.Core.Extensions;
using NosSmooth.Core.Packets;
+using NosSmooth.Data.Abstractions;
using NosSmooth.Game.Data.Characters;
using NosSmooth.Game.Events.Characters;
using NosSmooth.Game.Events.Core;
@@ 20,52 23,65 @@ public class SkillResponder : IPacketResponder<SkiPacket>
{
private readonly Game _game;
private readonly EventDispatcher _eventDispatcher;
+ private readonly IInfoService _infoService;
+ private readonly ILogger<SkillResponder> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="SkillResponder"/> class.
/// </summary>
/// <param name="game">The nostale game.</param>
/// <param name="eventDispatcher">The event dispatcher.</param>
- public SkillResponder(Game game, EventDispatcher eventDispatcher)
+ /// <param name="infoService">The info service.</param>
+ /// <param name="logger">The logger.</param>
+ public SkillResponder
+ (
+ Game game,
+ EventDispatcher eventDispatcher,
+ IInfoService infoService,
+ ILogger<SkillResponder> logger
+ )
{
_game = game;
_eventDispatcher = eventDispatcher;
+ _infoService = infoService;
+ _logger = logger;
}
/// <inheritdoc />
public async Task<Result> Respond(PacketEventArgs<SkiPacket> packetArgs, CancellationToken ct = default)
{
+ // TODO: put all code into CreateOrUpdate to avoid concurrent problems.
var packet = packetArgs.Packet;
Skill primarySkill, secondarySkill;
var character = _game.Character;
- if (character is not null && packet.PrimarySkillId == character.Skills?.PrimarySkill.SkillVNum)
+ if (character is not null && packet.PrimarySkillVNum == character.Skills?.PrimarySkill.SkillVNum)
{
primarySkill = character.Skills.PrimarySkill;
}
else
{
- primarySkill = new Skill(packet.PrimarySkillId);
+ primarySkill = await CreateSkill(packet.PrimarySkillVNum, default);
}
- if (character is not null && packet.PrimarySkillId == packet.SecondarySkillId)
+ if (character is not null && packet.PrimarySkillVNum == packet.SecondarySkillVNum)
{
secondarySkill = primarySkill;
}
- else if (character is not null && packet.SecondarySkillId == character.Skills?.SecondarySkill.SkillVNum)
+ else if (character is not null && packet.SecondarySkillVNum == character.Skills?.SecondarySkill.SkillVNum)
{
secondarySkill = character.Skills.SecondarySkill;
}
else
{
- secondarySkill = new Skill(packet.SecondarySkillId);
+ secondarySkill = await CreateSkill(packet.SecondarySkillVNum, default);
}
- var skillsFromPacket = packet.SkillSubPackets?.Select(x => x.SkillId).ToList() ?? new List<long>();
+ var skillsFromPacket = packet.SkillSubPackets?.Select(x => x.SkillVNum).ToList() ?? new List<int>();
var skillsFromCharacter = character?.Skills is null
- ? new List<long>()
+ ? new List<int>()
: character.Skills.OtherSkills.Select(x => x.SkillVNum).ToList();
var newSkills = skillsFromPacket.Except(skillsFromCharacter);
var oldSkills = skillsFromCharacter.Except(skillsFromPacket);
@@ 75,15 91,19 @@ public class SkillResponder : IPacketResponder<SkiPacket>
foreach (var newSkill in newSkills)
{
- otherSkillsFromCharacter.Add(new Skill(newSkill));
+ otherSkillsFromCharacter.Add(await CreateSkill(newSkill, default));
}
var skills = new Skills(primarySkill, secondarySkill, otherSkillsFromCharacter);
await _game.CreateOrUpdateCharacterAsync
(
- () => new Character(Skills: skills),
- c => c with { Skills = skills },
+ () => new Character { Skills = skills },
+ c =>
+ {
+ c.Skills = skills;
+ return c;
+ },
ct: ct
);
@@ 91,4 111,15 @@ public class SkillResponder : IPacketResponder<SkiPacket>
return Result.FromSuccess();
}
+
+ private async Task<Skill> CreateSkill(int vnum, int? level)
+ {
+ var infoResult = await _infoService.GetSkillInfoAsync(vnum);
+ if (!infoResult.IsSuccess)
+ {
+ _logger.LogWarning("Could not obtain a skill info for vnum {vnum}: {error}", vnum, infoResult.ToFullString());
+ }
+
+ return new Skill(vnum, level, infoResult.IsSuccess ? infoResult.Entity : null);
+ }
}=
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Characters/StatPacketResponder.cs => Core/NosSmooth.Game/PacketHandlers/Characters/StatPacketResponder.cs +70 -0
@@ 0,0 1,70 @@
+//
+// StatPacketResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Packets.Server.Entities;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Characters;
+
+/// <summary>
+/// Responder to stat packet.
+/// </summary>
+public class StatPacketResponder : IPacketResponder<StatPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StatPacketResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public StatPacketResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> Respond(PacketEventArgs<StatPacket> packetArgs, CancellationToken ct = default)
+ {
+ var character = _game.Character;
+ if (character is null)
+ {
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ var packet = packetArgs.Packet;
+ if (character.Hp is null)
+ {
+ character.Hp = new Health
+ {
+ Amount = packet.Hp,
+ Maximum = packet.HpMaximum
+ };
+ }
+ else
+ {
+ character.Hp.Amount = packet.Hp;
+ character.Hp.Maximum = packet.HpMaximum;
+ }
+
+ if (character.Mp is null)
+ {
+ character.Mp = new Health
+ {
+ Amount = packet.Mp,
+ Maximum = packet.MpMaximum
+ };
+ }
+ else
+ {
+ character.Mp.Amount = packet.Mp;
+ character.Mp.Maximum = packet.MpMaximum;
+ }
+
+ return Task.FromResult(Result.FromSuccess());
+ }
+}<
\ No newline at end of file
M Core/NosSmooth.Game/PacketHandlers/Characters/WalkResponder.cs => Core/NosSmooth.Game/PacketHandlers/Characters/WalkResponder.cs +20 -38
@@ 38,46 38,28 @@ public class WalkResponder : IPacketResponder<WalkPacket>
{
var character = _game.Character;
var packet = packetArgs.Packet;
- if (character is not null && character.Position is not null)
- {
- var oldPosition = new Position
- {
- X = character.Position.X,
- Y = character.Position.Y
- };
+ var oldPosition = character?.Position;
+ var position = new Position(packet.PositionX, packet.PositionY);
- character.Position.X = packet.PositionX;
- character.Position.Y = packet.PositionY;
+ character = await _game.CreateOrUpdateCharacterAsync
+ (
+ () => new Character
+ {
+ Position = position
+ },
+ (c) =>
+ {
+ c.Position = position;
+ return c;
+ },
+ ct: ct
+ );
- return await _eventDispatcher.DispatchEvent
- (
- new MovedEvent(character, character.Id, oldPosition, character.Position),
- ct
- );
- }
- else if (character?.Position is null)
- {
- await _game.CreateOrUpdateCharacterAsync
- (
- () => new Character
- (
- Position: new Position()
- {
- X = packet.PositionX,
- Y = packet.PositionY
- }
- ),
- (c) => c with
- {
- Position = new Position()
- {
- X = packet.PositionX,
- Y = packet.PositionY
- }
- },
- ct: ct
- );
- }
+ return await _eventDispatcher.DispatchEvent
+ (
+ new EntityMovedEvent(character, oldPosition, character.Position!.Value),
+ ct
+ );
return Result.FromSuccess();
}
A Core/NosSmooth.Game/PacketHandlers/Entities/AoeSkillUsedResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/AoeSkillUsedResponder.cs +101 -0
@@ 0,0 1,101 @@
+//
+// AoeSkillUsedResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Extensions.Logging;
+using NosSmooth.Core.Extensions;
+using NosSmooth.Core.Packets;
+using NosSmooth.Data.Abstractions;
+using NosSmooth.Game.Data.Characters;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Game.Events.Battle;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Extensions;
+using NosSmooth.Game.Helpers;
+using NosSmooth.Packets.Server.Battle;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Entities;
+
+/// <summary>
+/// Responder to bs packet.
+/// </summary>
+public class AoeSkillUsedResponder : IPacketResponder<BsPacket>
+{
+ private readonly Game _game;
+ private readonly EventDispatcher _eventDispatcher;
+ private readonly IInfoService _infoService;
+ private readonly Logger<AoeSkillUsedResponder> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AoeSkillUsedResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ /// <param name="infoService">The info service.</param>
+ /// <param name="logger">The logger.</param>
+ public AoeSkillUsedResponder
+ (
+ Game game,
+ EventDispatcher eventDispatcher,
+ IInfoService infoService,
+ Logger<AoeSkillUsedResponder> logger
+ )
+ {
+ _game = game;
+ _eventDispatcher = eventDispatcher;
+ _infoService = infoService;
+ _logger = logger;
+
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<BsPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var caster = _game.CurrentMap?.Entities.GetEntity<ILivingEntity>
+ (packet.CasterEntityId) ?? (ILivingEntity)EntityHelpers.CreateEntity
+ (packet.CasterEntityType, packet.CasterEntityId);
+ Skill? skillEntity = null;
+
+ if (caster is Character character)
+ {
+ var skillResult = character.Skills?.TryGetSkillByVNum(packet.SkillVNum);
+ if (skillResult?.IsSuccess ?? false)
+ {
+ skillEntity = skillResult.Value.Entity;
+ }
+ }
+
+ if (skillEntity is null)
+ {
+ var skillInfoResult = await _infoService.GetSkillInfoAsync(packet.SkillVNum, ct);
+ _logger.LogWarning
+ (
+ "Could not obtain a skill info for vnum {vnum}: {error}",
+ packet.SkillVNum,
+ skillInfoResult.ToFullString()
+ );
+
+ skillEntity = new Skill(packet.SkillVNum, Info: skillInfoResult.IsSuccess ? skillInfoResult.Entity : null);
+ }
+
+ return await _eventDispatcher.DispatchEvent
+ (
+ new AoESkillUsedEvent
+ (
+ caster,
+ skillEntity,
+ new Position
+ (
+ packet.X,
+ packet.Y
+ )
+ ),
+ ct
+ );
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Entities/CondPacketResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/CondPacketResponder.cs +53 -0
@@ 0,0 1,53 @@
+//
+// CondPacketResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Packets.Server.Entities;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Entities;
+
+/// <summary>
+/// Responder to cond packet.
+/// </summary>
+public class CondPacketResponder : IPacketResponder<CondPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CondPacketResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public CondPacketResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> Respond(PacketEventArgs<CondPacket> packetArgs, CancellationToken ct = default)
+ {
+ var map = _game.CurrentMap;
+ if (map is null)
+ {
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ var packet = packetArgs.Packet;
+ var entity = map.Entities.GetEntity<ILivingEntity>(packet.EntityId);
+
+ if (entity is null)
+ {
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ entity.Speed = packet.Speed;
+ entity.CantAttack = packet.CantAttack;
+ entity.CantMove = packet.CantMove;
+
+ return Task.FromResult(Result.FromSuccess());
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Entities/EqResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/EqResponder.cs +71 -0
@@ 0,0 1,71 @@
+//
+// EqResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Core.Packets;
+using NosSmooth.Data.Abstractions;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Items;
+using NosSmooth.Game.Helpers;
+using NosSmooth.Packets.Server.Entities;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Entities;
+
+/// <summary>
+/// Responder to eq packet.
+/// </summary>
+public class EqResponder : IPacketResponder<EqPacket>
+{
+ private readonly Game _game;
+ private readonly IInfoService _infoService;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="EqResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="infoService">The info service.</param>
+ public EqResponder(Game game, IInfoService infoService)
+ {
+ _game = game;
+ _infoService = infoService;
+
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<EqPacket> packetArgs, CancellationToken ct = default)
+ {
+ var map = _game.CurrentMap;
+ if (map is null)
+ {
+ return Result.FromSuccess();
+ }
+
+ var packet = packetArgs.Packet;
+ var entity = map.Entities.GetEntity<Player>(packet.CharacterId);
+
+ if (entity is null)
+ {
+ return Result.FromSuccess();
+ }
+
+ entity.Sex = packet.Sex;
+ entity.Class = packet.Class;
+ entity.Size = packet.Size;
+ entity.Authority = packet.AuthorityType;
+ entity.HairColor = packet.HairColor;
+ entity.HairStyle = packet.HairStyle;
+ entity.Equipment = await EquipmentHelpers.CreateEquipmentFromInSubpacketAsync
+ (
+ _infoService,
+ packet.EquipmentSubPacket,
+ packet.WeaponUpgradeRareSubPacket,
+ packet.ArmorUpgradeRareSubPacket,
+ ct
+ );
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
M Core/NosSmooth.Game/PacketHandlers/Entities/SkillUsedResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/SkillUsedResponder.cs +92 -35
@@ 4,12 4,21 @@
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using Microsoft.Extensions.Logging;
+using Microsoft.VisualBasic;
+using NosSmooth.Core.Extensions;
using NosSmooth.Core.Packets;
+using NosSmooth.Data.Abstractions;
+using NosSmooth.Data.Abstractions.Enums;
+using NosSmooth.Game.Data.Characters;
+using NosSmooth.Game.Data.Entities;
using NosSmooth.Game.Data.Info;
+using NosSmooth.Game.Events.Battle;
using NosSmooth.Game.Events.Characters;
using NosSmooth.Game.Events.Core;
-using NosSmooth.Game.Events.Players;
+using NosSmooth.Game.Events.Entities;
using NosSmooth.Game.Extensions;
+using NosSmooth.Game.Helpers;
using NosSmooth.Packets.Server.Battle;
using NosSmooth.Packets.Server.Skills;
using Remora.Results;
@@ 23,68 32,116 @@ public class SkillUsedResponder : IPacketResponder<SuPacket>, IPacketResponder<S
{
private readonly Game _game;
private readonly EventDispatcher _eventDispatcher;
+ private readonly IInfoService _infoService;
+ private readonly ILogger<SkillUsedResponder> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="SkillUsedResponder"/> class.
/// </summary>
/// <param name="game">The game.</param>
/// <param name="eventDispatcher">The event dispatcher.</param>
- public SkillUsedResponder(Game game, EventDispatcher eventDispatcher)
+ /// <param name="infoService">The info service.</param>
+ /// <param name="logger">The logger.</param>
+ public SkillUsedResponder
+ (
+ Game game,
+ EventDispatcher eventDispatcher,
+ IInfoService infoService,
+ ILogger<SkillUsedResponder> logger
+ )
{
_game = game;
_eventDispatcher = eventDispatcher;
+ _infoService = infoService;
+ _logger = logger;
}
/// <inheritdoc />
public async Task<Result> Respond(PacketEventArgs<SuPacket> packetArgs, CancellationToken ct = default)
{
var packet = packetArgs.Packet;
- var character = _game.Character;
+ var map = _game.CurrentMap;
+
+ // TODO: add to map if the entity is created?
+ var caster = map?.Entities?.GetEntity<ILivingEntity>
+ (packet.CasterEntityId) ?? (ILivingEntity)EntityHelpers.CreateEntity
+ (packet.CasterEntityType, packet.CasterEntityId);
+ var target = map?.Entities?.GetEntity<ILivingEntity>
+ (packet.TargetEntityId) ?? (ILivingEntity)EntityHelpers.CreateEntity
+ (packet.TargetEntityType, packet.TargetEntityId);
- if (character is not null && character.Id == packet.CasterEntityId && character.Skills is not null)
+ if (target.Hp is null)
{
- var skillResult = character.Skills.TryGetSkill(packet.SkillVNum);
+ target.Hp = new Health
+ {
+ Percentage = packet.HpPercentage
+ };
+ }
+ else
+ {
+ target.Hp.Percentage = packet.HpPercentage;
+ }
- if (skillResult.IsDefined(out var skillEntity))
+ Skill? skillEntity;
+ if (caster is Character character && character.Skills is not null)
+ {
+ var skillResult = character.Skills.TryGetSkillByVNum(packet.SkillVNum);
+
+ if (skillResult.IsDefined(out skillEntity))
{
skillEntity.LastUseTime = DateTimeOffset.Now;
- skillEntity.Cooldown = TimeSpan.FromSeconds(packet.SkillCooldown / 10.0);
skillEntity.IsOnCooldown = true;
}
-
- await _eventDispatcher.DispatchEvent(
- new SkillUsedEvent
- (
- character,
- character.Id,
- skillResult.IsSuccess ? skillEntity : null,
- packet.SkillVNum,
- packet.TargetEntityId,
- new Position { X = packet.PositionX, Y = packet.PositionY }
- ),
- ct);
+ else
+ {
+ var skillInfoResult = await _infoService.GetSkillInfoAsync(packet.SkillVNum, ct);
+ skillEntity = new Skill
+ (packet.SkillVNum, null, skillInfoResult.IsSuccess ? skillInfoResult.Entity : null);
+ }
}
else
{
- // TODO: add entity from the map, if exists.
- await _eventDispatcher.DispatchEvent(
- new SkillUsedEvent
+ var skillInfoResult = await _infoService.GetSkillInfoAsync(packet.SkillVNum, ct);
+ if (!skillInfoResult.IsSuccess)
+ {
+ _logger.LogWarning
(
- null,
- packet.CasterEntityId,
- null,
+ "Could not obtain a skill info for vnum {vnum}: {error}",
packet.SkillVNum,
- packet.TargetEntityId,
- new Position
- {
- X = packet.PositionX, Y = packet.PositionY
- }
- ),
- ct
- );
+ skillInfoResult.ToFullString()
+ );
+ }
+
+ skillEntity = new Skill
+ (packet.SkillVNum, null, skillInfoResult.IsSuccess ? skillInfoResult.Entity : null);
}
- return Result.FromSuccess();
+ var dispatchResult = await _eventDispatcher.DispatchEvent
+ (
+ new SkillUsedEvent
+ (
+ caster,
+ target,
+ skillEntity,
+ new Position(packet.PositionX, packet.PositionY),
+ packet.HitMode,
+ packet.Damage
+ ),
+ ct
+ );
+
+ if (!packet.TargetIsAlive)
+ {
+ var diedResult = await _eventDispatcher.DispatchEvent(new EntityDiedEvent(target, skillEntity), ct);
+ if (!diedResult.IsSuccess)
+ {
+ return dispatchResult.IsSuccess
+ ? diedResult
+ : new AggregateError(diedResult, dispatchResult);
+ }
+ }
+
+ return dispatchResult;
}
/// <inheritdoc />
@@ 95,7 152,7 @@ public class SkillUsedResponder : IPacketResponder<SuPacket>, IPacketResponder<S
if (character is not null && character.Skills is not null)
{
- var skillResult = character.Skills.TryGetSkill(packet.SkillId);
+ var skillResult = character.Skills.TryGetSkillByCastId(packet.SkillId);
if (skillResult.IsDefined(out var skillEntity))
{
A Core/NosSmooth.Game/PacketHandlers/Entities/StPacketResponder.cs => Core/NosSmooth.Game/PacketHandlers/Entities/StPacketResponder.cs +85 -0
@@ 0,0 1,85 @@
+//
+// StPacketResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Packets.Server.Entities;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Entities;
+
+/// <summary>
+/// Responds to st packet.
+/// </summary>
+public class StPacketResponder : IPacketResponder<StPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StPacketResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public StPacketResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> Respond(PacketEventArgs<StPacket> packetArgs, CancellationToken ct = default)
+ {
+ var map = _game.CurrentMap;
+
+ if (map is null)
+ {
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ var packet = packetArgs.Packet;
+ var entity = map.Entities.GetEntity<ILivingEntity>(packet.EntityId);
+ if (entity is null)
+ {
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ entity.EffectsVNums = packet.BuffVNums;
+ entity.Level = packet.Level;
+ if (entity is Player player)
+ {
+ player.HeroLevel = packet.HeroLevel;
+ }
+
+ if (entity.Hp is null)
+ {
+ entity.Hp = new Health
+ {
+ Amount = packet.Hp,
+ Percentage = packet.HpPercentage
+ };
+ }
+ else
+ {
+ entity.Hp.Amount = packet.Hp;
+ entity.Hp.Percentage = packet.HpPercentage;
+ }
+
+ if (entity.Mp is null)
+ {
+ entity.Mp = new Health
+ {
+ Amount = packet.Mp,
+ Percentage = packet.MpPercentage
+ };
+ }
+ else
+ {
+ entity.Mp.Amount = packet.Mp;
+ entity.Mp.Percentage = packet.MpPercentage;
+ }
+
+ return Task.FromResult(Result.FromSuccess());
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Map/AtResponder.cs => Core/NosSmooth.Game/PacketHandlers/Map/AtResponder.cs +49 -0
@@ 0,0 1,49 @@
+//
+// AtResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Packets.Server.Maps;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Map;
+
+/// <summary>
+/// Responds to at packet.
+/// </summary>
+public class AtResponder : IPacketResponder<AtPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AtResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public AtResponder(Game game)
+ {
+ _game = game;
+
+ }
+
+ /// <inheritdoc />
+ public Task<Result> Respond(PacketEventArgs<AtPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var map = _game.CurrentMap;
+ if (map is null)
+ {
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ var entity = map.Entities.GetEntity(packet.CharacterId);
+ if (entity is not null)
+ {
+ entity.Position = new Position(packet.X, packet.Y);
+ }
+
+ return Task.FromResult(Result.FromSuccess());
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Map/CMapResponder.cs => Core/NosSmooth.Game/PacketHandlers/Map/CMapResponder.cs +91 -0
@@ 0,0 1,91 @@
+//
+// CMapResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Concurrent;
+using Microsoft.Extensions.Logging;
+using NosSmooth.Core.Extensions;
+using NosSmooth.Core.Packets;
+using NosSmooth.Data.Abstractions;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Maps;
+using NosSmooth.Packets.Server.Maps;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Map;
+
+/// <summary>
+/// Responds to <see cref="CMapResponder"/> by creating a new map.
+/// </summary>
+public class CMapResponder : IPacketResponder<CMapPacket>
+{
+ private readonly Game _game;
+ private readonly IInfoService _infoService;
+ private readonly ILogger<CMapResponder> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CMapResponder"/> class.
+ /// </summary>
+ /// <param name="game">The nostale game.</param>
+ /// <param name="infoService">The info service.</param>
+ /// <param name="logger">The logger.</param>
+ public CMapResponder(Game game, IInfoService infoService, ILogger<CMapResponder> logger)
+ {
+ _game = game;
+ _infoService = infoService;
+ _logger = logger;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<CMapPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var mapInfoResult = await _infoService.GetMapInfoAsync(packet.Id, ct);
+ if (!mapInfoResult.IsSuccess)
+ {
+ _logger.LogWarning
+ (
+ "Could not obtain a map info for id {id}: {error}",
+ packet.Id,
+ mapInfoResult.ToFullString()
+ );
+ }
+
+ await _game.CreateMapAsync
+ (
+ () =>
+ {
+ var map = packet.Id == 20001
+ ? new Miniland
+ (
+ packet.Id,
+ packet.Type,
+ mapInfoResult.IsSuccess ? mapInfoResult.Entity : null,
+ new MapEntities(),
+ Array.Empty<Portal>(),
+ Array.Empty<MinilandObject>()
+ )
+ : new Data.Maps.Map
+ (
+ packet.Id,
+ packet.Type,
+ mapInfoResult.IsSuccess ? mapInfoResult.Entity : null,
+ new MapEntities(),
+ Array.Empty<Portal>()
+ );
+
+ var character = _game.Character;
+ if (character is not null)
+ {
+ map.Entities.AddEntity(character);
+ }
+ return map;
+ },
+ ct: ct
+ );
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Map/DropResponder.cs => Core/NosSmooth.Game/PacketHandlers/Map/DropResponder.cs +86 -0
@@ 0,0 1,86 @@
+//
+// DropResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Extensions.Logging;
+using NosSmooth.Core.Extensions;
+using NosSmooth.Core.Packets;
+using NosSmooth.Data.Abstractions;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Entities;
+using NosSmooth.Packets.Server.Entities;
+using NosSmooth.Packets.Server.Maps;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Map;
+
+/// <summary>
+/// Responds to drop packet.
+/// </summary>
+public class DropResponder : IPacketResponder<DropPacket>
+{
+ private readonly Game _game;
+ private readonly EventDispatcher _eventDispatcher;
+ private readonly IInfoService _infoService;
+ private readonly ILogger<DropResponder> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DropResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ /// <param name="infoService">The info service.</param>
+ /// <param name="logger">The logger.</param>
+ public DropResponder
+ (
+ Game game,
+ EventDispatcher eventDispatcher,
+ IInfoService infoService,
+ ILogger<DropResponder> logger
+ )
+ {
+ _game = game;
+ _eventDispatcher = eventDispatcher;
+ _infoService = infoService;
+ _logger = logger;
+
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<DropPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var itemInfoResult = await _infoService.GetItemInfoAsync(packet.ItemVNum, ct);
+ if (!itemInfoResult.IsDefined(out var itemInfo))
+ {
+ _logger.LogWarning("Could not obtain item info for vnum {vnum}: {error}", packet.ItemVNum, itemInfoResult.ToFullString());
+ }
+
+ var entity = new GroundItem
+ {
+ Amount = packet.Amount,
+ Id = packet.DropId,
+ IsQuestRelated = packet.IsQuestRelated,
+ ItemInfo = itemInfo,
+ OwnerId = null,
+ Position = new Position
+ (
+ packet.X,
+ packet.Y
+ ),
+ VNum = packet.ItemVNum
+ };
+
+ var map = _game.CurrentMap;
+ if (map is not null)
+ {
+ map.Entities.AddEntity(entity);
+ }
+
+ return await _eventDispatcher.DispatchEvent(new ItemDroppedEvent(entity), ct);
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Map/GpPacketResponder.cs => Core/NosSmooth.Game/PacketHandlers/Map/GpPacketResponder.cs +60 -0
@@ 0,0 1,60 @@
+//
+// GpPacketResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Game.Data.Maps;
+using NosSmooth.Packets.Server.Portals;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Map;
+
+/// <summary>
+/// Responder to gp packet.
+/// </summary>
+public class GpPacketResponder : IPacketResponder<GpPacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="GpPacketResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ public GpPacketResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<GpPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ await _game.CreateOrUpdateMapAsync
+ (
+ () => null,
+ (map) => map! with
+ {
+ Portals = map.Portals.Concat
+ (
+ new[]
+ {
+ new Portal
+ (
+ packet.PortalId,
+ new Position(packet.X, packet.Y),
+ packet.TargetMapId,
+ packet.PortalType,
+ packet.IsDisabled
+ )
+ }
+ ).ToArray()
+ },
+ ct: ct
+ );
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Map/InResponder.cs => Core/NosSmooth.Game/PacketHandlers/Map/InResponder.cs +201 -0
@@ 0,0 1,201 @@
+//
+// InResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Extensions.Logging;
+using NosSmooth.Core.Extensions;
+using NosSmooth.Core.Packets;
+using NosSmooth.Data.Abstractions;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Game.Data.Items;
+using NosSmooth.Game.Data.Social;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Entities;
+using NosSmooth.Game.Helpers;
+using NosSmooth.Packets.Server.Entities;
+using NosSmooth.Packets.Server.Maps;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Map;
+
+/// <summary>
+/// Responds to in packet.
+/// </summary>
+public class InResponder : IPacketResponder<InPacket>
+{
+ private readonly Game _game;
+ private readonly EventDispatcher _eventDispatcher;
+ private readonly IInfoService _infoService;
+ private readonly ILogger<InResponder> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ /// <param name="infoService">The info service.</param>
+ /// <param name="logger">The logger.</param>
+ public InResponder
+ (
+ Game game,
+ EventDispatcher eventDispatcher,
+ IInfoService infoService,
+ ILogger<InResponder> logger
+ )
+ {
+ _game = game;
+ _eventDispatcher = eventDispatcher;
+ _infoService = infoService;
+ _logger = logger;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<InPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var map = _game.CurrentMap;
+ if (map is null)
+ {
+ return Result.FromSuccess();
+ }
+
+ var entities = map.Entities;
+
+ // add entity to the map
+ var entity = await CreateEntityFromInPacket(packet, ct);
+ entities.AddEntity(entity);
+
+ return await _eventDispatcher.DispatchEvent(new EntityJoinedMapEvent(entity), ct);
+ }
+
+ private async Task<IEntity> CreateEntityFromInPacket(InPacket packet, CancellationToken ct)
+ {
+ if (packet.ItemSubPacket is not null)
+ {
+ return await CreateGroundItem(packet, packet.ItemSubPacket, ct);
+ }
+ if (packet.PlayerSubPacket is not null)
+ {
+ return await CreatePlayer(packet, packet.PlayerSubPacket, ct);
+ }
+ if (packet.NonPlayerSubPacket is not null)
+ {
+ return await CreateMonster(packet, packet.NonPlayerSubPacket, ct);
+ }
+
+ throw new Exception("The in packet did not contain any subpacket. Bug?");
+ }
+
+ private async Task<GroundItem> CreateGroundItem
+ (InPacket packet, InItemSubPacket itemSubPacket, CancellationToken ct)
+ {
+ if (packet.VNum is null)
+ {
+ throw new Exception("The vnum from the in packet cannot be null for items.");
+ }
+ var itemInfoResult = await _infoService.GetItemInfoAsync(packet.VNum.Value, ct);
+ if (!itemInfoResult.IsDefined(out var itemInfo))
+ {
+ _logger.LogWarning
+ (
+ "Could not obtain an item info for vnum {vnum}: {error}",
+ packet.VNum.Value,
+ itemInfoResult.ToFullString()
+ );
+ }
+
+ return new GroundItem
+ {
+ Amount = itemSubPacket.Amount,
+ Id = packet.EntityId,
+ OwnerId = itemSubPacket.OwnerId,
+ IsQuestRelated = itemSubPacket.IsQuestRelative,
+ ItemInfo = itemInfo,
+ Position = new Position(packet.PositionX, packet.PositionY),
+ VNum = packet.VNum.Value,
+ };
+ }
+
+ private async Task<Player> CreatePlayer(InPacket packet, InPlayerSubPacket playerSubPacket, CancellationToken ct)
+ {
+ return new Player
+ {
+ Position = new Position(packet.PositionX, packet.PositionY),
+ Id = packet.EntityId,
+ Name = packet.Name?.Name,
+ ArenaWinner = playerSubPacket.ArenaWinner,
+ Class = playerSubPacket.Class,
+ Compliment = playerSubPacket.Compliment,
+ Direction = packet.Direction,
+ Equipment = await EquipmentHelpers.CreateEquipmentFromInSubpacketAsync
+ (
+ _infoService,
+ playerSubPacket.Equipment,
+ playerSubPacket.WeaponUpgradeRareSubPacket,
+ playerSubPacket.ArmorUpgradeRareSubPacket,
+ ct
+ ),
+ Faction = playerSubPacket.Faction,
+ Size = playerSubPacket.Size,
+ Authority = playerSubPacket.Authority,
+ Sex = playerSubPacket.Sex,
+ HairStyle = playerSubPacket.HairStyle,
+ HairColor = playerSubPacket.HairColor,
+ Icon = playerSubPacket.ReputationIcon,
+ IsInvisible = playerSubPacket.IsInvisible,
+ Title = playerSubPacket.Title,
+ Level = playerSubPacket.Level,
+ HeroLevel = playerSubPacket.HeroLevel,
+ Morph = new Morph(playerSubPacket.MorphVNum, playerSubPacket.MorphUpgrade),
+ Family = playerSubPacket.FamilySubPacket.FamilyId is null
+ ? null
+ : new Family
+ (
+ playerSubPacket.FamilySubPacket.FamilyId,
+ playerSubPacket.FamilySubPacket.Title,
+ playerSubPacket.FamilyName,
+ playerSubPacket.Level,
+ playerSubPacket.FamilyIcons
+ ),
+ };
+ }
+
+ private async Task<Monster> CreateMonster
+ (InPacket packet, InNonPlayerSubPacket nonPlayerSubPacket, CancellationToken ct)
+ {
+ if (packet.VNum is null)
+ {
+ throw new Exception("The vnum from the in packet cannot be null for monsters.");
+ }
+
+ var monsterInfoResult = await _infoService.GetMonsterInfoAsync(packet.VNum.Value, ct);
+ if (!monsterInfoResult.IsDefined(out var monsterInfo))
+ {
+ _logger.LogWarning
+ (
+ "Could not obtain a monster info for vnum {vnum}: {error}",
+ packet.VNum.Value,
+ monsterInfoResult.ToFullString()
+ );
+ }
+
+ return new Monster
+ {
+ VNum = packet.VNum.Value,
+ MonsterInfo = monsterInfo,
+ Id = packet.EntityId,
+ Direction = packet.Direction,
+ Faction = nonPlayerSubPacket.Faction,
+ Hp = new Health { Percentage = nonPlayerSubPacket.HpPercentage },
+ Mp = new Health { Percentage = nonPlayerSubPacket.MpPercentage },
+ Name = nonPlayerSubPacket.Name?.Name,
+ Position = new Position(packet.PositionX, packet.PositionY),
+ IsInvisible = nonPlayerSubPacket.IsInvisible,
+ Level = monsterInfo?.Level ?? null,
+ IsSitting = nonPlayerSubPacket.IsSitting
+ };
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Map/MoveResponder.cs => Core/NosSmooth.Game/PacketHandlers/Map/MoveResponder.cs +53 -0
@@ 0,0 1,53 @@
+//
+// MoveResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Concurrent;
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Info;
+using NosSmooth.Packets.Server.Entities;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Map;
+
+/// <summary>
+/// Responds to move packet.
+/// </summary>
+public class MoveResponder : IPacketResponder<MovePacket>
+{
+ private readonly Game _game;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MoveResponder"/> class.
+ /// </summary>
+ /// <param name="game">The nostale game.</param>
+ public MoveResponder(Game game)
+ {
+ _game = game;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<MovePacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var map = _game.CurrentMap;
+
+ // TODO: store entities somewhere else so we can store them even if the map is still null?
+ if (map is null)
+ {
+ return Result.FromSuccess();
+ }
+
+ var entity = map.Entities.GetEntity<ILivingEntity>(packet.EntityId);
+ if (entity is not null && entity.Position is not null)
+ {
+ entity.Position = new Position(packet.MapX, packet.MapY);
+ entity.Speed = packet.Speed;
+ }
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/PacketHandlers/Map/OutResponder.cs => Core/NosSmooth.Game/PacketHandlers/Map/OutResponder.cs +67 -0
@@ 0,0 1,67 @@
+//
+// OutResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Core.Packets;
+using NosSmooth.Game.Data.Entities;
+using NosSmooth.Game.Data.Maps;
+using NosSmooth.Game.Events.Core;
+using NosSmooth.Game.Events.Entities;
+using NosSmooth.Game.Helpers;
+using NosSmooth.Packets.Enums;
+using NosSmooth.Packets.Server.Maps;
+using Remora.Results;
+
+namespace NosSmooth.Game.PacketHandlers.Map;
+
+/// <summary>
+/// Responds to out packet.
+/// </summary>
+public class OutResponder : IPacketResponder<OutPacket>
+{
+ private readonly Game _game;
+ private readonly EventDispatcher _eventDispatcher;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OutResponder"/> class.
+ /// </summary>
+ /// <param name="game">The game.</param>
+ /// <param name="eventDispatcher">The event dispatcher.</param>
+ public OutResponder(Game game, EventDispatcher eventDispatcher)
+ {
+ _game = game;
+ _eventDispatcher = eventDispatcher;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(PacketEventArgs<OutPacket> packetArgs, CancellationToken ct = default)
+ {
+ var packet = packetArgs.Packet;
+ var map = _game.CurrentMap;
+ if (map is null)
+ {
+ return Result.FromSuccess();
+ }
+ map.Entities.RemoveEntity(packet.EntityId);
+ Portal? portal = null;
+
+ IEntity entity = map.Entities.GetEntity(packet.EntityId) ?? EntityHelpers.CreateEntity
+ (packet.EntityType, packet.EntityId);
+
+ var position = entity.Position;
+ if (position is not null)
+ {
+ map.IsOnPortal(position.Value, out portal);
+ }
+
+ bool? died = null;
+ if (entity is ILivingEntity livingEntity)
+ {
+ died = (livingEntity.Hp?.Amount ?? -1) == 0 || (livingEntity.Hp?.Percentage ?? -1) == 0;
+ }
+
+ return await _eventDispatcher.DispatchEvent(new EntityLeftMapEvent(entity, portal, died), ct);
+ }
+}<
\ No newline at end of file