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
11 changes: 11 additions & 0 deletions src/NosCore.GameObject/Ecs/Components/MateStateComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// __ _ __ __ ___ __ ___ ___
// | \| |/__\ /' _/ / _//__\| _ \ __|
// | | ' | \/ |`._`.| \_| \/ | v / _|
// |_|\__|\__/ |___/ \__/\__/|_|_\___|
//

using NosCore.GameObject.Services.MateService;

namespace NosCore.GameObject.Ecs.Components;

public record struct MateStateComponent(Mate Mate, long OwnerId);
12 changes: 12 additions & 0 deletions src/NosCore.GameObject/Ecs/Components/PlayerMatesComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// __ _ __ __ ___ __ ___ ___
// | \| |/__\ /' _/ / _//__\| _ \ __|
// | | ' | \/ |`._`.| \_| \/ | v / _|
// |_|\__|\__/ |___/ \__/\__/|_|_\___|
//

using NosCore.GameObject.Services.MateService;
using System.Collections.Concurrent;

namespace NosCore.GameObject.Ecs.Components;

public record struct PlayerMatesComponent(ConcurrentDictionary<long, Mate> Mates);
200 changes: 200 additions & 0 deletions src/NosCore.GameObject/Ecs/Extensions/MateExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// __ _ __ __ ___ __ ___ ___
// | \| |/__\ /' _/ / _//__\| _ \ __|
// | | ' | \/ |`._`.| \_| \/ | v / _|
// |_|\__|\__/ |___/ \__/\__/|_|_\___|
//

using NosCore.Data.Enumerations.Character;
using NosCore.GameObject.Services.MateService;
using NosCore.Packets.Enumerations;
using NosCore.Packets.ServerPackets.Entities;
using NosCore.Packets.ServerPackets.Mates;
using NosCore.Packets.ServerPackets.Parcel;
using NosCore.Packets.ServerPackets.Player;
using NosCore.Packets.ServerPackets.Visibility;
using NosCore.Shared.Enumerations;
using System.Globalization;

namespace NosCore.GameObject.Ecs.Extensions
{
public static class MateExtensions
{
public static ScpPacket GenerateScp(this Mate mate, RegionType language)
{
return new ScpPacket
{
PetId = mate.PetSlot,
NpcMonsterVNum = mate.VNum,
TransportId = mate.MateTransportId,
Level = mate.Level,
Loyalty = mate.Loyalty,
Experience = mate.Experience,
Unknow1 = 0,
AttackUpgrade = mate.NpcMonster.AttackUpgrade,
DamageMinimum = mate.NpcMonster.DamageMinimum,
DamageMaximum = mate.NpcMonster.DamageMaximum,
Concentrate = mate.NpcMonster.Concentrate,
CriticalChance = mate.NpcMonster.CriticalChance,
CriticalRate = mate.NpcMonster.CriticalRate,
DefenceUpgrade = mate.NpcMonster.DefenceUpgrade,
CloseDefence = mate.NpcMonster.CloseDefence,
DefenceDodge = mate.NpcMonster.DefenceDodge,
DistanceDefence = mate.NpcMonster.DistanceDefence,
DistanceDefenceDodge = mate.NpcMonster.DistanceDefenceDodge,
MagicDefence = mate.NpcMonster.MagicDefence,
Element = mate.NpcMonster.Element,
FireResistance = mate.NpcMonster.FireResistance,
WaterResistance = mate.NpcMonster.WaterResistance,
LightResistance = mate.NpcMonster.LightResistance,
DarkResistance = mate.NpcMonster.DarkResistance,
Hp = mate.Hp,
MaxHp = mate.MaxHp,
Mp = mate.Mp,
MaxMp = mate.MaxMp,
IsTeamMember = mate.IsTeamMember,
XpLoad = mate.XpLoad,
CanPickUp = mate.CanPickUp,
Name = mate.DisplayName(language),
IsSummonable = mate.IsSummonable
};
}

public static ScnPacket GenerateScn(this Mate mate, RegionType language)
{
return new ScnPacket
{
PetId = mate.PetSlot,
NpcMonsterVNum = mate.VNum,
TransportId = mate.MateTransportId,
Level = mate.Level,
Loyalty = mate.Loyalty,
Experience = mate.Experience,
WeaponInstanceDetails = EmptySlot,
ArmorInstanceDetails = EmptySlot,
GauntletInstanceDetails = EmptySlot,
BootsInstanceDetails = EmptySlot,
AttackUpgrade = mate.NpcMonster.AttackUpgrade,
MinimumAttack = mate.NpcMonster.DamageMinimum,
MaximumAttack = mate.NpcMonster.DamageMaximum,
Precision = mate.NpcMonster.Concentrate,
CriticalRate = mate.NpcMonster.CriticalChance,
CriticalDamageRate = mate.NpcMonster.CriticalRate,
DefenceUpgrade = mate.NpcMonster.DefenceUpgrade,
Defence = mate.NpcMonster.CloseDefence,
DefenceDodge = mate.NpcMonster.DefenceDodge,
DistanceDefence = mate.NpcMonster.DistanceDefence,
DistanceDodge = mate.NpcMonster.DistanceDefenceDodge,
DodgeRate = mate.NpcMonster.MagicDefence,
ElementRate = mate.NpcMonster.Element,
FireResistance = mate.NpcMonster.FireResistance,
WaterResistance = mate.NpcMonster.WaterResistance,
LightResistance = mate.NpcMonster.LightResistance,
DarkResistance = mate.NpcMonster.DarkResistance,
Hp = mate.Hp,
HpMax = mate.MaxHp,
Mp = mate.Mp,
MpMax = mate.MaxMp,
IsTeamMember = mate.IsTeamMember,
LevelXp = (int)mate.XpLoad,
Name = mate.DisplayName(language),
MorphId = mate.Skin != 0 ? mate.Skin : -1,
IsSummonable = mate.IsSummonable,
SpDetails = null,
Skill1Details = null,
Skill2Details = null,
Skill3Details = null
};
}

public static InPacket GenerateIn(this Mate mate, RegionType language)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just checked the other are also like this so most likely it's for another PR but ideally we only use the visualEntity one so we need to merge them

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and I counted them so the follow-up has a shape: there are six GenerateIn extensions today.

INonPlayableEntity visualEntity          VisualEntityExtension.cs   <- the general one
MapItemComponentBundle item              MapItemBundleExtensions.cs
Mate mate, RegionType language           MateExtensions.cs
MonsterComponentBundle monster           MonsterBundleExtensions.cs
NpcComponentBundle npc, short? dialog    NpcBundleExtensions.cs
PlayerComponentBundle player, string prefix   PlayerBundleExtensions.cs

The general one already does most of it, and it already special-cases two of the others inline — visualEntity is INamedEntity for the name and visualEntity is NpcComponentBundle for the dialog. So the merge is mostly a matter of moving the remaining per-type bits behind the same kind of test, or better, behind something the bundles expose themselves.

Three of the five carry an extra argument the general one has no place for yet: the mate's RegionType (it feeds DisplayName), the npc's dialog override, and the player's prefix. Those are the part that needs a decision, not just a move.

Happy to take it as its own PR — it touches five files and none of this one's behaviour, so it would only make this one harder to read. Say the word and I'll open it.

{
return new InPacket
{
VisualType = VisualType.Npc,
VNum = mate.VNum.ToString(CultureInfo.InvariantCulture),
VisualId = mate.MateTransportId,
PositionX = mate.PositionX,
PositionY = mate.PositionY,
Direction = mate.Direction,
InNonPlayerSubPacket = new InNonPlayerSubPacket
{
InAliveSubPacket = new InAliveSubPacket
{
Hp = Percent(mate.Hp, mate.MaxHp, 100),
Mp = Percent(mate.Mp, mate.MaxMp, 100)
},
Dialog = 0,
Faction = 0,
GroupEffect = 3,
Owner = mate.CharacterId,
SpawnEffect = SpawnEffectType.NoEffect,
IsSitting = false,
Morph = (short?)(mate.Skin != 0 ? mate.Skin : -1),
Name = mate.DisplayName(language),
Unknow1 = (byte)(mate.MateType == MateType.Partner ? 1 : 0)
}
};
}

public static OutPacket GenerateOut(this Mate mate)
{
return new OutPacket
{
VisualType = VisualType.Npc,
VisualId = mate.MateTransportId
};
}

public static PstPacket GeneratePst(this Mate mate)
{
return new PstPacket
{
Type = VisualType.Npc,
VisualId = mate.MateTransportId,
GroupOrder = (int)mate.MateType,
HpLeft = Percent(mate.Hp, mate.MaxHp, 0),
MpLeft = Percent(mate.Mp, mate.MaxMp, 0),
HpLoad = mate.MaxHp,
MpLoad = mate.MaxMp,
Race = 0,
Gender = GenderType.Male,
Morph = 0,
BuffIds = null
};
}

public static CondPacket GenerateCond(this Mate mate)
{
return new CondPacket
{
VisualType = VisualType.Npc,
VisualId = mate.MateTransportId,
NoAttack = false,
NoMove = false,
Speed = mate.NpcMonster.Speed
};
}

private static string DisplayName(this Mate mate, RegionType language)
{
if (!string.IsNullOrEmpty(mate.Name))
{
return mate.Name;
}

return mate.NpcMonster.Name.TryGetValue(language, out var localized)
? localized
: mate.NpcMonster.Name[RegionType.EN];
}

private static int Percent(int current, int maximum, int whenUnknown) =>
maximum > 0 ? (int)(current / (float)maximum * 100) : whenUnknown;

private static ScnPacket.ScEquipmentDetails EmptySlot => new()
{
ItemId = -1,
ItemRare = 0,
ItemUpgrade = 0
};
}
}
34 changes: 32 additions & 2 deletions src/NosCore.GameObject/Ecs/MapWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,35 @@ public Entity CreateNpc(
return entity;
}

public Entity CreateMate(
int visualId,
Services.MateService.Mate mate,
MapInstance mapInstance,
short positionX,
short positionY,
byte direction)
{
var now = SystemClock.Instance.GetCurrentInstant();
return World.Create(
new EntityIdentityComponent(visualId, VisualType.Npc, mate.CharacterId),
new HealthComponent(mate.Hp, mate.MaxHp, true),
new ManaComponent(mate.Mp, mate.MaxMp),
new PositionComponent(positionX, positionY, direction, mapInstance.MapInstanceId),
new VisualComponent(0, 0, 0, 0, false, false, false),
new NpcDataComponent(mate.VNum, mate.NpcMonster.Race, mate.Level, 0, mate.NpcMonster.Speed, 10),
new SpawnComponent(positionX, positionY, false, false),
new EffectComponent(0, 0),
new TimingComponent(now, now),
new NpcStateComponent(mate.NpcMonster, mapInstance, new SemaphoreSlim(1, 1),
new ConcurrentDictionary<Entity, int>(), null, null, null,
new Dictionary<Type, Subject<RequestData>>(), null, false),
new BuffStateComponent(new ConcurrentDictionary<short, BuffInstance>()),
new AggroComponent(VisualType.Object, 0, 0, Instant.MinValue),
new SkillCooldownComponent(new ConcurrentDictionary<short, Instant>()),
new MateStateComponent(mate, mate.CharacterId)
);
}

public Entity CreateMapItem(
long visualId,
short vNum,
Expand Down Expand Up @@ -236,11 +265,12 @@ public Entity ClonePlayer(
PlayerContextComponent context,
PlayerInventoryComponent inventory,
PlayerSocialComponent social,
PlayerRequestsComponent requests)
PlayerRequestsComponent requests,
PlayerMatesComponent mates)
{
return World.Create(identity, health, mana, position, visual, appearance, experience, gold,
reputation, sp, name, combat, buffs, player, playerFlags, timing, speed, state, network,
context, inventory, social, requests);
context, inventory, social, requests, mates);
}

public void DestroyEntity(Entity entity)
Expand Down
29 changes: 29 additions & 0 deletions src/NosCore.GameObject/Ecs/MateComponentBundle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using NosCore.GameObject.Ecs.Attributes;
using NosCore.GameObject.Ecs.Components;
using NosCore.GameObject.Ecs.Interfaces;

namespace NosCore.GameObject.Ecs;

[ComponentBundle(
typeof(EntityIdentityComponent),
typeof(HealthComponent),
typeof(ManaComponent),
typeof(PositionComponent),
typeof(VisualComponent),
typeof(NpcDataComponent),
typeof(SpawnComponent),
typeof(EffectComponent),
typeof(TimingComponent),
typeof(NpcStateComponent),
typeof(BuffStateComponent),
typeof(AggroComponent),
typeof(SkillCooldownComponent),
typeof(MateStateComponent)
)]
public readonly partial struct MateComponentBundle : INonPlayableEntity
{
public Arch.Core.Entity Handle => Entity;

public short MapX => PositionX;
public short MapY => PositionY;
}
3 changes: 2 additions & 1 deletion src/NosCore.GameObject/Ecs/PlayerComponentBundle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ namespace NosCore.GameObject.Ecs;
typeof(PlayerContextComponent),
typeof(PlayerInventoryComponent),
typeof(PlayerSocialComponent),
typeof(PlayerRequestsComponent)
typeof(PlayerRequestsComponent),
typeof(PlayerMatesComponent)
)]
public readonly partial struct PlayerComponentBundle : ICharacterEntity
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// __ _ __ __ ___ __ ___ ___
// | \| |/__\ /' _/ / _//__\| _ \ __|
// | | ' | \/ |`._`.| \_| \/ | v / _|
// |_|\__|\__/ |___/ \__/\__/|_|_\___|
//

using JetBrains.Annotations;
using NosCore.GameObject.Ecs;
using NosCore.GameObject.Ecs.Extensions;
using NosCore.GameObject.Messaging.Events;
using NosCore.GameObject.Services.MateService;
using NosCore.Networking;
using NosCore.Packets.ServerPackets.Entities;
using NosCore.Shared.Enumerations;
using System.Linq;
using System.Threading.Tasks;

namespace NosCore.GameObject.Messaging.Handlers.Mate
{
// Mates follow on the same event the owner's own step publishes.
[UsedImplicitly]
public sealed class MateFollowHandler
{
[UsedImplicitly]
public async Task Handle(CharacterMovedEvent evt)
{
if (evt.Character is not PlayerComponentBundle character)
{
return;
}

var mates = character.Mates.Values.Where(s => s.IsTeamMember).ToList();
if (mates.Count == 0)
{
return;
}

var map = character.MapInstance;
MatePlacement.Arrange(character.PositionX, character.PositionY, map.Map, mates);

foreach (var mate in mates)
{
// The entity carries the position aggro and range checks read, not the packet.
if (mate.Entity is { } handle)
{
handle.PositionX = mate.PositionX;
handle.PositionY = mate.PositionY;
}

var move = new MovePacket
{
VisualType = VisualType.Npc,
VisualEntityId = mate.MateTransportId,
MapX = mate.PositionX,
MapY = mate.PositionY,
Speed = mate.NpcMonster.Speed
};

// Broadcasting a hidden owner's mates would put the owner back on screen.
if (character.Invisible)
{
await character.SendPacketAsync(move).ConfigureAwait(false);
continue;
}

await map.SendPacketAsync(move).ConfigureAwait(false);
}
}
}
}
Loading
Loading