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

using NosCore.GameObject.Services.MateService;

namespace NosCore.GameObject.Ecs.Components;

/// <summary>
/// What makes an entity somebody's mate rather than a monster: the stored row it came from and
/// the character it belongs to.
/// </summary>
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);
206 changes: 206 additions & 0 deletions src/NosCore.GameObject/Ecs/Extensions/MateExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// __ _ __ __ ___ __ ___ ___
// | \| |/__\ /' _/ / _//__\| _ \ __|
// | | ' | \/ |`._`.| \_| \/ | 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
};
}

/// <summary>
/// in 2 1506 445562 26 26 2 100 100 0 0 3 626114 1 0 -1 Ratufu^pirate^(Feu)
/// </summary>
public static InPacket GenerateIn(this Mate mate, RegionType language)
{
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
};
}

/// <summary>
/// GroupOrder carries the mate type, not a party position: pst 2 22687 0 100 100 ...
/// </summary>
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
34 changes: 34 additions & 0 deletions src/NosCore.GameObject/Ecs/MateComponentBundle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using NosCore.GameObject.Ecs.Attributes;
using NosCore.GameObject.Ecs.Components;
using NosCore.GameObject.Ecs.Interfaces;

namespace NosCore.GameObject.Ecs;

// A mate on the map is a monster that belongs to somebody: it stands, takes hits, carries buffs
// and cooldowns, and dies. Giving it the monster's component set rather than a set of its own
// is what lets the battle service treat it as a combatant without a second notion of one.
[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;

// A monster answers with the square it spawned on; a mate follows its owner, so the live
// position is the only meaningful one.
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
Loading
Loading