WIP: Dynamic entity spawn (only monsters rn)
This commit is contained in:
parent
5d58821be4
commit
a1736317a1
7 changed files with 102 additions and 20 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
GameServer/Extensions/Logic/MathExtensions.cs
Normal file
13
GameServer/Extensions/Logic/MathExtensions.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue