using System.Diagnostics; using ClientTester.Packet; using LiteNetLib; using LiteNetLib.Utils; using Serilog; namespace ClientTester.DummyService; public class DummyClients { private NetManager manager; private EventBasedNetListener listener; private NetDataWriter? writer; public NetPeer? peer; public int clientId; // 일단 이게 hashKey가 됨 => 0 ~ 1000번까지는 더미용응로 뺴둠 // info private Vector3 position = new Vector3(); private int rotY = 0; private float moveSpeed = 3.5f; private float distance = 0.0f; private float preTime = 0.0f; // 이동 계산용 private readonly Stopwatch moveClock = Stopwatch.StartNew(); private float posX = 0f; private float posZ = 0f; private float dirX = 0f; private float dirZ = 0f; // 맵 경계 public MapBounds Map { get; set; } = new MapBounds(-95f, -25f, -30f, 10f); // 통계 public int SentCount { set; get; } public int ReceivedCount { set; get; } public int RttCount { set; get; } public DummyClients(int clientId, string ip, int port, string key) { this.clientId = clientId; listener = new EventBasedNetListener(); manager = new NetManager(listener); writer = new NetDataWriter(); listener.PeerConnectedEvent += netPeer => { peer = netPeer; Log.Information("[Client {ClientId:00}] 연결됨", this.clientId); // clientID가 토큰의 hashKey라고 가정함 DummyAccTokenPacket recvTokenPacket = new DummyAccTokenPacket(); recvTokenPacket.Token = clientId; byte[] data = PacketSerializer.Serialize((ushort)PacketCode.DUMMY_ACC_TOKEN, recvTokenPacket); writer.Put(data); peer.Send(writer, DeliveryMethod.ReliableOrdered); writer.Reset(); }; listener.NetworkReceiveEvent += (peer, reader, channel, deliveryMethod) => { ReceivedCount++; reader.Recycle(); }; listener.PeerDisconnectedEvent += (peer, info) => { Log.Warning("[Client {ClientId:00}] 연결 끊김: {Reason}", this.clientId, info.Reason); this.peer = null; }; manager.Start(); manager.Connect(ip, port, key); position.X = -60; position.Z = 25; } public void UpdateDummy() { // 델타 타임 계산 (초 단위) float now = (float)moveClock.Elapsed.TotalSeconds; float delta = preTime > 0f ? now - preTime : 0.1f; preTime = now; // 남은 거리가 없으면 새 방향·목표 거리 설정 if (distance <= 0f) { // 벽에 붙어있으면 반대 방향 강제, 아니면 ±30도 회전 int wallRotY = Map.GetRotYAwayFromWall(posX, posZ); rotY = wallRotY >= 0 ? (wallRotY + Random.Shared.Next(-30, 31) + 360) % 360 : (rotY + Random.Shared.Next(-30, 31) + 360) % 360; float rad = rotY * MathF.PI / 180f; dirX = MathF.Sin(rad); dirZ = MathF.Cos(rad); // 3초~12초에 도달할 수 있는 거리 = moveSpeed × 랜덤 초 float seconds = 3f + (float)Random.Shared.NextDouble() * 9f; distance = moveSpeed * seconds; } // 이번 틱 이동량 (남은 거리 초과 방지) float step = MathF.Min(moveSpeed * delta, distance); float nextX = posX + dirX * step; float nextZ = posZ + dirZ * step; // 벽 충돌 시 clamp + 다음 틱에 방향 재설정 bool hitWall = Map.Clamp(ref nextX, ref nextZ); posX = nextX; posZ = nextZ; distance = hitWall ? 0f : distance - step; // 정수 Vector3 갱신 position.X = posX; position.Z = posZ; } public void SendTransform() { if (peer == null || writer == null) { return; } UpdateDummy(); TransformPlayerPacket transformPlayerPacket = new TransformPlayerPacket { PlayerId = clientId, RotY = rotY, Position = new Packet.Position { X = position.X, Y = -3.7f, // 높이는 버린다. Z = position.Z } }; // Protobuf 직렬화 + 헤더 조립 byte[] data = PacketSerializer.Serialize((ushort)PacketCode.TRANSFORM_PLAYER, transformPlayerPacket); writer.Put(data); // 이동은 손실 감수함 peer.Send(writer, DeliveryMethod.Unreliable); SentCount++; writer.Reset(); } public void PollEvents() { manager.PollEvents(); } public void Stop() { manager.Stop(); } }