WIP: Dynamic entity spawn (only monsters rn)

This commit is contained in:
xeon 2024-02-25 23:29:35 +03:00
parent 5d58821be4
commit a1736317a1
7 changed files with 102 additions and 20 deletions

View file

@ -14,6 +14,9 @@ using Protocol;
namespace GameServer.Controllers; namespace GameServer.Controllers;
internal class CreatureController : Controller internal class CreatureController : Controller
{ {
private const float DynamicSpawnRadius = 5000;
private const float DynamicSpawnPositionDelta = 2500;
private readonly EntitySystem _entitySystem; private readonly EntitySystem _entitySystem;
private readonly EntityFactory _entityFactory; private readonly EntityFactory _entityFactory;
private readonly ModelManager _modelManager; private readonly ModelManager _modelManager;
@ -21,6 +24,7 @@ internal class CreatureController : Controller
private readonly IGameActionListener _listener; private readonly IGameActionListener _listener;
private readonly GameplayFeatureSettings _gameplayFeatures; private readonly GameplayFeatureSettings _gameplayFeatures;
private readonly Vector _lastDynamicSpawnPos;
public CreatureController(PlayerSession session, EntitySystem entitySystem, EntityFactory entityFactory, ModelManager modelManager, ConfigManager configManager, IOptions<GameplayFeatureSettings> gameplayFeatures, IGameActionListener listener) : base(session) public CreatureController(PlayerSession session, EntitySystem entitySystem, EntityFactory entityFactory, ModelManager modelManager, ConfigManager configManager, IOptions<GameplayFeatureSettings> gameplayFeatures, IGameActionListener listener) : base(session)
{ {
@ -30,6 +34,8 @@ internal class CreatureController : Controller
_configManager = configManager; _configManager = configManager;
_listener = listener; _listener = listener;
_gameplayFeatures = gameplayFeatures.Value; _gameplayFeatures = gameplayFeatures.Value;
_lastDynamicSpawnPos = new();
} }
public async Task JoinScene(int instanceId) public async Task JoinScene(int instanceId)
@ -92,6 +98,14 @@ internal class CreatureController : Controller
public void OnPlayerPositionChanged() public void OnPlayerPositionChanged()
{ {
_modelManager.Player.Position.MergeFrom(GetPlayerEntity()!.Pos); _modelManager.Player.Position.MergeFrom(GetPlayerEntity()!.Pos);
if (_lastDynamicSpawnPos.GetDistance(_modelManager.Player.Position) >= DynamicSpawnPositionDelta)
{
_lastDynamicSpawnPos.MergeFrom(_modelManager.Player.Position);
ClearInactiveEntities();
SpawnDynamicEntities();
}
} }
[GameEvent(GameEventType.VisionSkillChanged)] [GameEvent(GameEventType.VisionSkillChanged)]
@ -276,19 +290,44 @@ internal class CreatureController : Controller
} }
private void CreateWorldEntities() private void CreateWorldEntities()
{
_lastDynamicSpawnPos.MergeFrom(_modelManager.Player.Position.Clone());
SpawnDynamicEntities();
}
private void ClearInactiveEntities()
{
_entitySystem.Destroy(_entitySystem.EnumerateEntities()
.Where(e => e is MonsterEntity && e.DynamicId != 0 &&
e.Pos.GetDistance(_modelManager.Player.Position) > DynamicSpawnRadius).ToArray());
}
private void SpawnDynamicEntities()
{ {
Vector playerPos = _modelManager.Player.Position; Vector playerPos = _modelManager.Player.Position;
// Test monster // Currently only monsters
MonsterEntity monster = _entityFactory.CreateMonster(106003002); // Turtle. IEnumerable<LevelEntityConfig> entitiesToSpawn = _configManager.Enumerate<LevelEntityConfig>()
monster.Pos = new() .Where(config => config.MapId == 8 && Math.Abs(config.Transform[0].X / 100 - playerPos.X) < DynamicSpawnRadius && Math.Abs(config.Transform[0].Y / 100 - playerPos.Y) < DynamicSpawnRadius &&
{ config.BlueprintType.StartsWith("Monster"));
X = playerPos.X + 250,
Y = playerPos.Y + 250,
Z = playerPos.Z
};
_entitySystem.Add([monster]); List<MonsterEntity> spawnMonsters = [];
monster.InitProps(_configManager.GetConfig<BasePropertyConfig>(600000100)!); foreach (LevelEntityConfig levelEntity in entitiesToSpawn)
{
if (_entitySystem.HasDynamicEntity(levelEntity.EntityId)) continue;
MonsterEntity monster = _entityFactory.CreateMonster(levelEntity.EntityId);
monster.Pos = new()
{
X = levelEntity.Transform[0].X / 100,
Y = levelEntity.Transform[0].Y / 100,
Z = levelEntity.Transform[0].Z / 100
};
monster.InitProps(_configManager.GetConfig<BasePropertyConfig>(600000100)!);
spawnMonsters.Add(monster);
}
_entitySystem.Add(spawnMonsters);
} }
} }

View file

@ -0,0 +1,13 @@
using Protocol;
namespace GameServer.Extensions.Logic;
internal static class MathExtensions
{
public static float GetDistance(this Vector self, Vector other)
{
float x = self.X - other.X;
float y = self.Y - other.Y;
return (float)Math.Sqrt(x * x + y * y);
}
}

View file

@ -8,6 +8,7 @@ internal class KcpConnection : IConnection
{ {
private readonly byte[] _recvBuffer; private readonly byte[] _recvBuffer;
private readonly KcpConversation _conv; private readonly KcpConversation _conv;
private readonly ManualResetEvent _sendEvent;
private uint _upStreamSeqNo; private uint _upStreamSeqNo;
private uint _downStreamSeqNo; private uint _downStreamSeqNo;
@ -15,6 +16,7 @@ internal class KcpConnection : IConnection
{ {
_conv = conv; _conv = conv;
_recvBuffer = GC.AllocateUninitializedArray<byte>(8192); _recvBuffer = GC.AllocateUninitializedArray<byte>(8192);
_sendEvent = new ManualResetEvent(true);
} }
public bool Active => !_conv.TransportClosed; public bool Active => !_conv.TransportClosed;
@ -51,7 +53,16 @@ internal class KcpConnection : IConnection
MessageManager.EncodeMessage(memory[BaseMessage.LengthFieldSize..], message); MessageManager.EncodeMessage(memory[BaseMessage.LengthFieldSize..], message);
if (_conv == null) throw new InvalidOperationException("Trying to send message when conv is null"); if (_conv == null) throw new InvalidOperationException("Trying to send message when conv is null");
if (!_sendEvent.WaitOne(0))
{
await Task.Yield();
_ = _sendEvent.WaitOne();
}
_sendEvent.Reset();
await _conv.SendAsync(memoryOwner.Memory[..networkSize]); await _conv.SendAsync(memoryOwner.Memory[..networkSize]);
_sendEvent.Set();
} }
private uint NextUpStreamSeqNo() private uint NextUpStreamSeqNo()

View file

@ -13,6 +13,7 @@ internal abstract class EntityBase
public Rotator Rot { get; set; } public Rotator Rot { get; set; }
public bool Active { get; set; } public bool Active { get; set; }
public int DynamicId { get; protected set; }
public EntityState State { get; protected set; } public EntityState State { get; protected set; }

View file

@ -5,11 +5,14 @@ namespace GameServer.Systems.Entity;
internal class EntitySystem internal class EntitySystem
{ {
private readonly List<EntityBase> _entities; private readonly List<EntityBase> _entities;
private readonly List<int> _dynamicEntityIds;
private readonly IGameActionListener _listener; private readonly IGameActionListener _listener;
public EntitySystem(IGameActionListener listener) public EntitySystem(IGameActionListener listener)
{ {
_entities = []; _entities = [];
_dynamicEntityIds = [];
_listener = listener; _listener = listener;
} }
@ -26,15 +29,26 @@ internal class EntitySystem
throw new InvalidOperationException($"EntitySystem::Create - entity with id {entity.Id} already exists"); throw new InvalidOperationException($"EntitySystem::Create - entity with id {entity.Id} already exists");
_entities.Add(entity); _entities.Add(entity);
if (entity.DynamicId != 0)
_dynamicEntityIds.Add(entity.DynamicId);
} }
_ = _listener.OnEntitiesAdded(entities); _ = _listener.OnEntitiesAdded(entities);
} }
public bool HasDynamicEntity(int dynamicId)
{
return _dynamicEntityIds.Contains(dynamicId);
}
public void Destroy(IEnumerable<EntityBase> entities) public void Destroy(IEnumerable<EntityBase> entities)
{ {
foreach (EntityBase entity in entities) foreach (EntityBase entity in entities)
{
_ = _entities.Remove(entity); _ = _entities.Remove(entity);
_ = _dynamicEntityIds.Remove(entity.DynamicId);
}
_ = _listener.OnEntitiesRemoved(entities); _ = _listener.OnEntitiesRemoved(entities);
} }

View file

@ -8,6 +8,7 @@ internal class MonsterEntity : EntityBase
public MonsterEntity(long id, int configId, IGameActionListener listener) : base(id, listener) public MonsterEntity(long id, int configId, IGameActionListener listener) : base(id, listener)
{ {
ConfigId = configId; ConfigId = configId;
DynamicId = configId;
} }
public int ConfigId { get; } public int ConfigId { get; }
@ -33,17 +34,20 @@ internal class MonsterEntity : EntityBase
fsm.Fsms.Add(new DFsm fsm.Fsms.Add(new DFsm
{ {
FsmId = 10007, // Main State Machine FsmId = 10007, // Main State Machine
CurrentState = 10013, // Battle Branching CurrentState = 10013 // Battle Branching
Status = 1, // ??
Flag = (int)EFsmStateFlag.Confirmed
}); });
fsm.Fsms.Add(new DFsm fsm.Fsms.Add(new DFsm
{ {
FsmId = 10007, // Main State Machine FsmId = 10007, // Main State Machine
CurrentState = 10015, // Moving Combat CurrentState = 10015 // Moving Combat
Status = 1, // ?? });
Flag = (int)EFsmStateFlag.Confirmed
// Some monsters need weapon
fsm.Fsms.Add(new DFsm
{
FsmId = 100,
CurrentState = 9 // [9 - Empty hand, 10 - Crowbar, 11 - flamethrower, 12 - chainsaw, 13 - electric blade, 14 - sniper rifle]
}); });
} }

View file

@ -6,13 +6,13 @@
"HeadFrame": 80060009, "HeadFrame": 80060009,
"Characters": [ 1402, 1302, 1203 ], "Characters": [ 1402, 1302, 1203 ],
"Position": { "Position": {
"X": -35823, "X": -45000,
"Y": 67132, "Y": 67800,
"Z": 4067 "Z": 2600
} }
}, },
"Features": { "Features": {
"TeleportByMapMark": true, "TeleportByMapMark": true,
"UnlimitedEnergy": true "UnlimitedEnergy": false
} }
} }