commit dabf1f3ba9731e634e88b84fbd347f2d671db02f
Author: tolelom <98kimsungmin@naver.com>
Date: Thu Feb 26 17:52:48 2026 +0900
first commit
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..48dbc6e
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,15 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(mkdir:*)",
+ "Bash(protoc:*)",
+ "Bash(go install:*)",
+ "Bash(winget install:*)",
+ "Bash(export:*)",
+ "Bash(go build:*)",
+ "Bash(go test:*)",
+ "Bash(go:*)",
+ "Bash(\"C:/Users/SSAFY/AppData/Local/Microsoft/WinGet/Packages/Google.Protobuf_Microsoft.Winget.Source_8wekyb3d8bbwe/bin/protoc.exe\":*)"
+ ]
+ }
+}
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..9879198
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# 디폴트 무시된 파일
+/shelf/
+/workspace.xml
+# 쿼리 파일을 포함한 무시된 디폴트 폴더
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# 에디터 기반 HTTP 클라이언트 요청
+/httpRequests/
diff --git a/.idea/a301_game_server.iml b/.idea/a301_game_server.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/a301_game_server.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml
new file mode 100644
index 0000000..d7202f0
--- /dev/null
+++ b/.idea/go.imports.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..649473c
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..91445dd
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,60 @@
+PROTOC := C:/Users/SSAFY/AppData/Local/Microsoft/WinGet/Packages/Google.Protobuf_Microsoft.Winget.Source_8wekyb3d8bbwe/bin/protoc.exe
+GOPATH_BIN := $(shell go env GOPATH)/bin
+
+.PHONY: build run proto proto-unity proto-all clean test testclient
+
+# Build the server binary.
+build:
+ go build -o bin/server.exe ./cmd/server
+
+# Run the server.
+run:
+ go run ./cmd/server -config config/config.yaml
+
+# Generate Go server code from protobuf.
+proto:
+ PATH="$(GOPATH_BIN):$(PATH)" $(PROTOC) \
+ --go_out=proto/gen/pb \
+ --go_opt=paths=source_relative \
+ --proto_path=proto \
+ proto/messages.proto
+
+# Generate C# Unity client code from protobuf.
+proto-unity:
+ $(PROTOC) \
+ --csharp_out=./unity/Assets/Scripts/Proto \
+ --proto_path=./proto \
+ proto/messages.proto
+
+# Generate both Go and C# at once.
+proto-all: proto proto-unity
+
+# Clean build artifacts.
+clean:
+ rm -rf bin/
+
+# Run tests.
+test:
+ go test ./... -v -race
+
+# Run with AOI disabled for comparison.
+run-no-aoi:
+ AOI_ENABLED=false go run ./cmd/server -config config/config.yaml
+
+# Run the WebSocket test client (server must be running).
+testclient:
+ go run ./cmd/testclient
+
+# Run test client with specific scenario (auth|move|combat|metrics|all).
+testclient-auth:
+ go run ./cmd/testclient -scenario auth
+
+testclient-combat:
+ go run ./cmd/testclient -scenario combat
+
+testclient-metrics:
+ go run ./cmd/testclient -scenario metrics
+
+# Load test: 10 clients for 15 seconds.
+loadtest:
+ go run ./cmd/testclient -clients 10 -duration 15s
diff --git a/UNITY_INTEGRATION.md b/UNITY_INTEGRATION.md
new file mode 100644
index 0000000..36823b8
--- /dev/null
+++ b/UNITY_INTEGRATION.md
@@ -0,0 +1,865 @@
+# Unity 클라이언트 연동 가이드
+
+## 목차
+
+1. [패키지 설치](#1-패키지-설치)
+2. [Protobuf C# 코드 생성](#2-protobuf-c-코드-생성)
+3. [네트워크 레이어 구현](#3-네트워크-레이어-구현)
+4. [메시지 타입 정의](#4-메시지-타입-정의)
+5. [인증 흐름](#5-인증-흐름)
+6. [이동 동기화](#6-이동-동기화)
+7. [엔티티 관리 (AOI)](#7-엔티티-관리-aoi)
+8. [전투 시스템](#8-전투-시스템)
+9. [존 전환](#9-존-전환)
+10. [디버그 도구](#10-디버그-도구)
+11. [전체 GameManager 예시](#11-전체-gamemanager-예시)
+
+---
+
+## 1. 패키지 설치
+
+### Unity Package Manager에서 설치
+
+**Window → Package Manager → Add package by name**
+
+```
+com.unity.nuget.newtonsoft-json
+```
+
+### NuGet 또는 DLL 직접 추가
+
+아래 두 패키지가 필요합니다.
+
+| 패키지 | 용도 |
+|--------|------|
+| `NativeWebSocket` | WebSocket 통신 |
+| `Google.Protobuf` | 메시지 직렬화 |
+
+**NativeWebSocket** (무료, WebGL 지원)
+- [https://github.com/endel/NativeWebSocket](https://github.com/endel/NativeWebSocket)
+- `Assets/Plugins/` 폴더에 복사
+
+**Google.Protobuf**
+- NuGet에서 `Google.Protobuf` 다운로드
+- `Google.Protobuf.dll`을 `Assets/Plugins/` 폴더에 복사
+
+---
+
+## 2. Protobuf C# 코드 생성
+
+서버의 `proto/messages.proto` 파일로부터 C# 코드를 생성합니다.
+
+### protoc 설치 (Windows)
+
+```
+winget install Google.Protobuf
+```
+
+### C# 코드 생성
+
+프로젝트 루트에서 실행:
+
+```bash
+protoc --csharp_out=./unity/Assets/Scripts/Proto \
+ --proto_path=./proto \
+ proto/messages.proto
+```
+
+생성된 `Messages.cs`를 Unity 프로젝트의 `Assets/Scripts/Proto/` 폴더에 배치합니다.
+
+---
+
+## 3. 네트워크 레이어 구현
+
+### 와이어 프로토콜
+
+서버와의 통신 형식은 다음과 같습니다.
+
+```
+[2바이트: 메시지 타입 ID (Big Endian)] [Protobuf 직렬화 페이로드]
+```
+
+### NetworkClient.cs
+
+```csharp
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using NativeWebSocket;
+using Google.Protobuf;
+
+public class NetworkClient : MonoBehaviour
+{
+ public static NetworkClient Instance { get; private set; }
+
+ [Header("Server")]
+ [SerializeField] private string serverUrl = "ws://localhost:8080/ws";
+
+ private WebSocket _ws;
+ private readonly Queue _mainThreadQueue = new();
+
+ // 메시지 핸들러 테이블
+ private readonly Dictionary> _handlers = new();
+
+ void Awake()
+ {
+ if (Instance != null) { Destroy(gameObject); return; }
+ Instance = this;
+ DontDestroyOnLoad(gameObject);
+ RegisterHandlers();
+ }
+
+ void Update()
+ {
+#if !UNITY_WEBGL || UNITY_EDITOR
+ _ws?.DispatchMessageQueue();
+#endif
+ // 메인 스레드에서 Unity API 호출
+ while (_mainThreadQueue.Count > 0)
+ _mainThreadQueue.Dequeue()?.Invoke();
+ }
+
+ public async void Connect()
+ {
+ _ws = new WebSocket(serverUrl);
+
+ _ws.OnOpen += () => Debug.Log("[Net] Connected");
+ _ws.OnClose += (e) => Debug.Log($"[Net] Disconnected: {e}");
+ _ws.OnError += (e) => Debug.LogError($"[Net] Error: {e}");
+ _ws.OnMessage += OnRawMessage;
+
+ await _ws.Connect();
+ }
+
+ public async void Disconnect()
+ {
+ if (_ws != null)
+ await _ws.Close();
+ }
+
+ // ─── 송신 ──────────────────────────────────────────────────
+
+ public async void Send(ushort msgType, IMessage payload)
+ {
+ if (_ws?.State != WebSocketState.Open) return;
+
+ byte[] body = payload.ToByteArray();
+ byte[] packet = new byte[2 + body.Length];
+
+ // Big Endian 2바이트 타입 ID
+ packet[0] = (byte)(msgType >> 8);
+ packet[1] = (byte)(msgType & 0xFF);
+ Buffer.BlockCopy(body, 0, packet, 2, body.Length);
+
+ await _ws.Send(packet);
+ }
+
+ // ─── 수신 ──────────────────────────────────────────────────
+
+ private void OnRawMessage(byte[] data)
+ {
+ if (data.Length < 2) return;
+
+ ushort msgType = (ushort)((data[0] << 8) | data[1]);
+ byte[] payload = new byte[data.Length - 2];
+ Buffer.BlockCopy(data, 2, payload, 0, payload.Length);
+
+ _mainThreadQueue.Enqueue(() =>
+ {
+ if (_handlers.TryGetValue(msgType, out var handler))
+ handler(payload);
+ else
+ Debug.LogWarning($"[Net] Unhandled message type: 0x{msgType:X4}");
+ });
+ }
+
+ public void Register(ushort msgType, Action handler)
+ {
+ _handlers[msgType] = handler;
+ }
+
+ // 핸들러 등록은 GameManager에서 수행 (아래 참조)
+ private void RegisterHandlers() { }
+}
+```
+
+---
+
+## 4. 메시지 타입 정의
+
+```csharp
+public static class MsgType
+{
+ // Auth
+ public const ushort LoginRequest = 0x0001;
+ public const ushort LoginResponse = 0x0002;
+ public const ushort EnterWorldRequest = 0x0003;
+ public const ushort EnterWorldResponse = 0x0004;
+
+ // Movement
+ public const ushort MoveRequest = 0x0010;
+ public const ushort StateUpdate = 0x0011;
+ public const ushort SpawnEntity = 0x0012;
+ public const ushort DespawnEntity = 0x0013;
+ public const ushort ZoneTransferNotify = 0x0014;
+
+ // System
+ public const ushort Ping = 0x0020;
+ public const ushort Pong = 0x0021;
+
+ // Combat
+ public const ushort UseSkillRequest = 0x0040;
+ public const ushort UseSkillResponse = 0x0041;
+ public const ushort CombatEvent = 0x0042;
+ public const ushort BuffApplied = 0x0043;
+ public const ushort BuffRemoved = 0x0044;
+ public const ushort RespawnRequest = 0x0045;
+ public const ushort RespawnResponse = 0x0046;
+
+ // Debug
+ public const ushort AOIToggleRequest = 0x0030;
+ public const ushort AOIToggleResponse = 0x0031;
+ public const ushort MetricsRequest = 0x0032;
+ public const ushort ServerMetrics = 0x0033;
+}
+```
+
+---
+
+## 5. 인증 흐름
+
+```
+클라이언트 서버
+ │ │
+ │── LoginRequest ───────────────→│ username + password
+ │← LoginResponse ────────────────│ success, session_token, player_id
+ │ │
+ │── EnterWorldRequest ──────────→│ session_token
+ │← EnterWorldResponse ───────────│ self(EntityState), zone_id
+ │ │
+ │ [게임 진행] │
+```
+
+### AuthManager.cs
+
+```csharp
+public class AuthManager : MonoBehaviour
+{
+ public static AuthManager Instance { get; private set; }
+
+ public string SessionToken { get; private set; }
+ public ulong PlayerId { get; private set; }
+
+ public event Action OnLoginResult;
+ public event Action OnEnterWorldResult;
+
+ void Awake()
+ {
+ Instance = this;
+ NetworkClient.Instance.Register(MsgType.LoginResponse, OnLoginResponse);
+ NetworkClient.Instance.Register(MsgType.EnterWorldResponse, OnEnterWorldResponse);
+ }
+
+ public void Login(string username, string password)
+ {
+ NetworkClient.Instance.Send(MsgType.LoginRequest, new LoginRequest
+ {
+ Username = username,
+ Password = password
+ });
+ }
+
+ public void EnterWorld()
+ {
+ NetworkClient.Instance.Send(MsgType.EnterWorldRequest, new EnterWorldRequest
+ {
+ SessionToken = SessionToken
+ });
+ }
+
+ private void OnLoginResponse(byte[] data)
+ {
+ var resp = LoginResponse.Parser.ParseFrom(data);
+ if (resp.Success)
+ {
+ SessionToken = resp.SessionToken;
+ PlayerId = resp.PlayerId;
+ }
+ OnLoginResult?.Invoke(resp.Success, resp.ErrorMessage);
+ }
+
+ private void OnEnterWorldResponse(byte[] data)
+ {
+ var resp = EnterWorldResponse.Parser.ParseFrom(data);
+ if (resp.Success)
+ OnEnterWorldResult?.Invoke(resp.Self);
+ }
+}
+```
+
+---
+
+## 6. 이동 동기화
+
+### 전략: 클라이언트 예측 + 서버 권위
+
+- 클라이언트는 **로컬에서 즉시 이동**을 적용합니다 (반응성 유지).
+- 매 50ms(서버 틱)마다 현재 위치/속도를 서버에 전송합니다.
+- 서버에서 받은 `StateUpdate`로 다른 엔티티의 위치를 보간합니다.
+
+### MovementSender.cs (로컬 플레이어)
+
+```csharp
+public class MovementSender : MonoBehaviour
+{
+ [SerializeField] private float sendInterval = 0.05f; // 50ms = 20 tick/s
+
+ private CharacterController _controller;
+ private float _timer;
+ private Vector3 _lastSentPos;
+
+ void Awake() => _controller = GetComponent();
+
+ void Update()
+ {
+ // 로컬 이동 입력 처리 (CharacterController 등)
+ HandleInput();
+
+ _timer += Time.deltaTime;
+ if (_timer >= sendInterval)
+ {
+ _timer = 0f;
+ SendMoveIfChanged();
+ }
+ }
+
+ void HandleInput()
+ {
+ float h = Input.GetAxis("Horizontal");
+ float v = Input.GetAxis("Vertical");
+ Vector3 dir = new Vector3(h, 0, v).normalized;
+ _controller.Move(dir * 5f * Time.deltaTime);
+ }
+
+ void SendMoveIfChanged()
+ {
+ if (Vector3.Distance(transform.position, _lastSentPos) < 0.01f) return;
+ _lastSentPos = transform.position;
+
+ var vel = _controller.velocity;
+ NetworkClient.Instance.Send(MsgType.MoveRequest, new MoveRequest
+ {
+ Position = ToProtoVec3(transform.position),
+ Rotation = transform.eulerAngles.y,
+ Velocity = ToProtoVec3(vel)
+ });
+ }
+
+ static Vector3Proto ToProtoVec3(Vector3 v) => new() { X = v.x, Y = v.y, Z = v.z };
+}
+```
+
+### RemoteEntityView.cs (다른 플레이어/몬스터)
+
+```csharp
+// 서버에서 받은 위치로 부드럽게 보간
+public class RemoteEntityView : MonoBehaviour
+{
+ private Vector3 _targetPos;
+ private float _targetRot;
+ private const float LerpSpeed = 15f;
+
+ public void ApplyState(EntityState state)
+ {
+ _targetPos = new Vector3(state.Position.X, state.Position.Y, state.Position.Z);
+ _targetRot = state.Rotation;
+ }
+
+ void Update()
+ {
+ transform.position = Vector3.Lerp(transform.position, _targetPos,
+ LerpSpeed * Time.deltaTime);
+ transform.rotation = Quaternion.Lerp(transform.rotation,
+ Quaternion.Euler(0, _targetRot, 0),
+ LerpSpeed * Time.deltaTime);
+ }
+}
+```
+
+---
+
+## 7. 엔티티 관리 (AOI)
+
+서버 AOI가 관리하는 엔티티 생성/삭제를 클라이언트에서 처리합니다.
+
+### EntityManager.cs
+
+```csharp
+public class EntityManager : MonoBehaviour
+{
+ public static EntityManager Instance { get; private set; }
+
+ [SerializeField] private GameObject playerPrefab;
+ [SerializeField] private GameObject mobPrefab;
+
+ private readonly Dictionary _entities = new();
+ public ulong LocalPlayerId { get; set; }
+
+ void Awake()
+ {
+ Instance = this;
+ NetworkClient.Instance.Register(MsgType.StateUpdate, OnStateUpdate);
+ NetworkClient.Instance.Register(MsgType.SpawnEntity, OnSpawnEntity);
+ NetworkClient.Instance.Register(MsgType.DespawnEntity, OnDespawnEntity);
+ }
+
+ // ─── 수신 핸들러 ───────────────────────────────────────────
+
+ private void OnStateUpdate(byte[] data)
+ {
+ var update = StateUpdate.Parser.ParseFrom(data);
+ foreach (var state in update.Entities)
+ UpdateEntity(state);
+ }
+
+ private void OnSpawnEntity(byte[] data)
+ {
+ var msg = SpawnEntity_.Parser.ParseFrom(data); // 네임스페이스 주의
+ SpawnEntity(msg.Entity);
+ }
+
+ private void OnDespawnEntity(byte[] data)
+ {
+ var msg = DespawnEntity_.Parser.ParseFrom(data);
+ DespawnEntity(msg.EntityId);
+ }
+
+ // ─── 엔티티 수명 관리 ─────────────────────────────────────
+
+ void SpawnEntity(EntityState state)
+ {
+ if (state.EntityId == LocalPlayerId) return;
+ if (_entities.ContainsKey(state.EntityId)) return;
+
+ var prefab = state.EntityType == EntityType.EntityTypeMob ? mobPrefab : playerPrefab;
+ var go = Instantiate(prefab,
+ new Vector3(state.Position.X, state.Position.Y, state.Position.Z),
+ Quaternion.identity);
+ go.name = $"{state.Name}_{state.EntityId}";
+ go.GetComponent()?.ApplyState(state);
+ go.GetComponent()?.Setup(state.Name, state.Hp, state.MaxHp);
+
+ _entities[state.EntityId] = go;
+ }
+
+ void UpdateEntity(EntityState state)
+ {
+ if (state.EntityId == LocalPlayerId) return;
+ if (!_entities.TryGetValue(state.EntityId, out var go))
+ {
+ SpawnEntity(state);
+ return;
+ }
+ go.GetComponent()?.ApplyState(state);
+ go.GetComponent()?.UpdateHP(state.Hp, state.MaxHp);
+ }
+
+ void DespawnEntity(ulong entityId)
+ {
+ if (_entities.TryGetValue(entityId, out var go))
+ {
+ Destroy(go);
+ _entities.Remove(entityId);
+ }
+ }
+
+ public void ClearAll()
+ {
+ foreach (var go in _entities.Values)
+ if (go != null) Destroy(go);
+ _entities.Clear();
+ }
+}
+```
+
+---
+
+## 8. 전투 시스템
+
+### CombatManager.cs
+
+```csharp
+public class CombatManager : MonoBehaviour
+{
+ public static CombatManager Instance { get; private set; }
+
+ public event Action OnCombatEvent;
+ public event Action OnBuffApplied;
+ public event Action OnBuffRemoved;
+
+ void Awake()
+ {
+ Instance = this;
+ NetworkClient.Instance.Register(MsgType.CombatEvent, OnCombatEventMsg);
+ NetworkClient.Instance.Register(MsgType.BuffApplied, OnBuffAppliedMsg);
+ NetworkClient.Instance.Register(MsgType.BuffRemoved, OnBuffRemovedMsg);
+ NetworkClient.Instance.Register(MsgType.UseSkillResponse, OnUseSkillResponse);
+ NetworkClient.Instance.Register(MsgType.RespawnResponse, OnRespawnResponse);
+ }
+
+ // ─── 스킬 사용 ────────────────────────────────────────────
+
+ // 단일 타겟 (Basic Attack, Fireball, Poison 등)
+ public void UseSkill(uint skillId, ulong targetEntityId)
+ {
+ NetworkClient.Instance.Send(MsgType.UseSkillRequest, new UseSkillRequest
+ {
+ SkillId = skillId,
+ TargetId = targetEntityId
+ });
+ }
+
+ // AoE 지면 타겟 (Flame Strike 등)
+ public void UseSkillAoE(uint skillId, Vector3 groundPos)
+ {
+ NetworkClient.Instance.Send(MsgType.UseSkillRequest, new UseSkillRequest
+ {
+ SkillId = skillId,
+ TargetPos = new Vector3Proto { X = groundPos.x, Y = groundPos.y, Z = groundPos.z }
+ });
+ }
+
+ // 셀프 타겟 (Heal, Power Up 등)
+ public void UseSkillSelf(uint skillId)
+ {
+ NetworkClient.Instance.Send(MsgType.UseSkillRequest, new UseSkillRequest
+ {
+ SkillId = skillId
+ });
+ }
+
+ // 리스폰 요청
+ public void RequestRespawn()
+ {
+ NetworkClient.Instance.Send(MsgType.RespawnRequest, new RespawnRequest());
+ }
+
+ // ─── 수신 핸들러 ──────────────────────────────────────────
+
+ private void OnCombatEventMsg(byte[] data)
+ {
+ var evt = CombatEvent.Parser.ParseFrom(data);
+ OnCombatEvent?.Invoke(evt);
+
+ switch (evt.EventType)
+ {
+ case CombatEventType.CombatEventDamage:
+ ShowDamageNumber(evt.TargetId, evt.Damage, evt.IsCritical);
+ UpdateEntityHP(evt.TargetId, evt.TargetHp, evt.TargetMaxHp);
+ if (evt.TargetDied)
+ OnEntityDied(evt.TargetId);
+ break;
+
+ case CombatEventType.CombatEventHeal:
+ ShowHealNumber(evt.TargetId, evt.Heal);
+ UpdateEntityHP(evt.TargetId, evt.TargetHp, evt.TargetMaxHp);
+ break;
+
+ case CombatEventType.CombatEventDeath:
+ OnEntityDied(evt.TargetId);
+ break;
+
+ case CombatEventType.CombatEventRespawn:
+ OnEntityRespawned(evt.TargetId, evt.TargetHp, evt.TargetMaxHp);
+ break;
+ }
+ }
+
+ private void OnBuffAppliedMsg(byte[] data)
+ {
+ var buff = BuffApplied.Parser.ParseFrom(data);
+ OnBuffApplied?.Invoke(buff);
+ // UI: 버프 아이콘 추가
+ BuffUI.Instance?.AddBuff(buff.TargetId, buff.BuffId, buff.BuffName,
+ buff.Duration, buff.IsDebuff);
+ }
+
+ private void OnBuffRemovedMsg(byte[] data)
+ {
+ var buff = BuffRemoved.Parser.ParseFrom(data);
+ OnBuffRemoved?.Invoke(buff);
+ BuffUI.Instance?.RemoveBuff(buff.TargetId, buff.BuffId);
+ }
+
+ private void OnUseSkillResponse(byte[] data)
+ {
+ var resp = UseSkillResponse.Parser.ParseFrom(data);
+ if (!resp.Success)
+ UIManager.Instance?.ShowError(resp.ErrorMessage); // "skill on cooldown" 등
+ }
+
+ private void OnRespawnResponse(byte[] data)
+ {
+ var resp = RespawnResponse.Parser.ParseFrom(data);
+ // 로컬 플레이어 상태 복구
+ LocalPlayer.Instance?.OnRespawned(resp.Self);
+ }
+
+ // ─── 헬퍼 ─────────────────────────────────────────────────
+
+ void ShowDamageNumber(ulong entityId, int damage, bool isCrit)
+ {
+ // 데미지 폰트 팝업
+ Debug.Log($"DMG {damage}{(isCrit ? " CRIT!" : "")} on {entityId}");
+ }
+
+ void ShowHealNumber(ulong entityId, int heal)
+ {
+ Debug.Log($"HEAL +{heal} on {entityId}");
+ }
+
+ void UpdateEntityHP(ulong entityId, int hp, int maxHp)
+ {
+ // EntityUI HP 바 업데이트
+ }
+
+ void OnEntityDied(ulong entityId)
+ {
+ if (entityId == AuthManager.Instance.PlayerId)
+ {
+ // 로컬 플레이어 사망 → 리스폰 UI 표시
+ UIManager.Instance?.ShowRespawnPanel();
+ }
+ // 사망 애니메이션 등
+ }
+
+ void OnEntityRespawned(ulong entityId, int hp, int maxHp) { }
+}
+```
+
+### 스킬 ID 참조
+
+| ID | 이름 | 타입 | 설명 |
+|----|------|------|------|
+| 1 | Basic Attack | 단일 적 | 근접 물리 공격 |
+| 2 | Fireball | 단일 적 | 원거리 마법 |
+| 3 | Heal | 자신 | HP 회복 |
+| 4 | Flame Strike | 지면 AoE | 범위 화염 공격 |
+| 5 | Poison | 단일 적 | 독 DoT (10초) |
+| 6 | Power Up | 자신 | STR 버프 (10초) |
+
+---
+
+## 9. 존 전환
+
+포탈 위치에 접근하면 서버가 `ZoneTransferNotify`를 보냅니다.
+
+```csharp
+public class ZoneManager : MonoBehaviour
+{
+ public static ZoneManager Instance { get; private set; }
+
+ public uint CurrentZoneId { get; private set; }
+
+ void Awake()
+ {
+ Instance = this;
+ NetworkClient.Instance.Register(MsgType.ZoneTransferNotify, OnZoneTransfer);
+ }
+
+ private void OnZoneTransfer(byte[] data)
+ {
+ var notify = ZoneTransferNotify.Parser.ParseFrom(data);
+
+ // 1. 현재 존의 모든 엔티티 제거
+ EntityManager.Instance.ClearAll();
+
+ // 2. 로딩 화면 표시 (선택)
+ UIManager.Instance?.ShowLoading(true);
+
+ // 3. 새 존 정보 적용
+ CurrentZoneId = notify.NewZoneId;
+
+ // 4. 로컬 플레이어 위치 이동
+ var pos = notify.Self.Position;
+ LocalPlayer.Instance?.Teleport(new Vector3(pos.X, pos.Y, pos.Z));
+
+ // 5. 주변 엔티티 스폰
+ foreach (var entity in notify.NearbyEntities)
+ EntityManager.Instance.SpawnEntityPublic(entity);
+
+ UIManager.Instance?.ShowLoading(false);
+ Debug.Log($"[Zone] Transferred to zone {notify.NewZoneId}");
+ }
+}
+```
+
+---
+
+## 10. 디버그 도구
+
+게임 중 AOI 토글과 서버 메트릭을 확인할 수 있습니다.
+
+```csharp
+public class DebugPanel : MonoBehaviour
+{
+ void Start()
+ {
+ NetworkClient.Instance.Register(MsgType.AOIToggleResponse, OnAOIToggle);
+ NetworkClient.Instance.Register(MsgType.ServerMetrics, OnMetrics);
+ }
+
+ // AOI 켜기/끄기 (성능 비교용)
+ public void ToggleAOI(bool enabled)
+ {
+ NetworkClient.Instance.Send(MsgType.AOIToggleRequest, new AOIToggleRequest
+ {
+ Enabled = enabled
+ });
+ }
+
+ // 서버 메트릭 요청
+ public void RequestMetrics()
+ {
+ NetworkClient.Instance.Send(MsgType.MetricsRequest, new MetricsRequest());
+ }
+
+ private void OnAOIToggle(byte[] data)
+ {
+ var resp = AOIToggleResponse.Parser.ParseFrom(data);
+ Debug.Log($"[Debug] AOI: {resp.Message}");
+ }
+
+ private void OnMetrics(byte[] data)
+ {
+ var m = ServerMetrics.Parser.ParseFrom(data);
+ Debug.Log($"[Metrics] Players={m.OnlinePlayers} " +
+ $"Entities={m.TotalEntities} " +
+ $"Tick={m.TickDurationUs}us " +
+ $"AOI={m.AoiEnabled}");
+ }
+}
+```
+
+---
+
+## 11. 전체 GameManager 예시
+
+모든 시스템을 하나의 씬에서 연결하는 예시입니다.
+
+```csharp
+public class GameManager : MonoBehaviour
+{
+ public static GameManager Instance { get; private set; }
+
+ [Header("UI")]
+ [SerializeField] private LoginPanel loginPanel;
+ [SerializeField] private HUDPanel hudPanel;
+
+ void Awake() => Instance = this;
+
+ void Start()
+ {
+ // 1. 서버 접속
+ NetworkClient.Instance.Connect();
+
+ // 2. 인증 이벤트 구독
+ AuthManager.Instance.OnLoginResult += HandleLoginResult;
+ AuthManager.Instance.OnEnterWorldResult += HandleEnterWorld;
+
+ // 3. 로그인 UI 표시
+ loginPanel.gameObject.SetActive(true);
+ }
+
+ // 로그인 버튼 → AuthManager.Login() 호출
+ public void OnLoginButtonClick(string user, string pass)
+ {
+ AuthManager.Instance.Login(user, pass);
+ }
+
+ private void HandleLoginResult(bool success, string error)
+ {
+ if (!success) { loginPanel.ShowError(error); return; }
+ loginPanel.gameObject.SetActive(false);
+ AuthManager.Instance.EnterWorld();
+ }
+
+ private void HandleEnterWorld(EntityState self)
+ {
+ // 로컬 플레이어 생성
+ EntityManager.Instance.LocalPlayerId = self.EntityId;
+ LocalPlayer.Instance?.Initialize(self);
+
+ // HUD 표시
+ hudPanel.gameObject.SetActive(true);
+ hudPanel.UpdateHP(self.Hp, self.MaxHp);
+ }
+
+ void OnApplicationQuit()
+ {
+ NetworkClient.Instance.Disconnect();
+ }
+}
+```
+
+---
+
+## 흐름 다이어그램
+
+```
+게임 시작
+ │
+ ▼
+NetworkClient.Connect() ← ws://서버IP:8080/ws
+ │
+ ▼
+AuthManager.Login() ← LoginRequest
+ │ LoginResponse (session_token)
+ ▼
+AuthManager.EnterWorld() ← EnterWorldRequest
+ │ EnterWorldResponse (self, zone_id)
+ ▼
+EntityManager 초기화 ← SpawnEntity (주변 플레이어/몬스터)
+ │
+ ▼
+게임 루프 (매 프레임)
+ ├─ MovementSender → MoveRequest (50ms마다)
+ ├─ CombatManager → UseSkillRequest (스킬 버튼)
+ │ ← StateUpdate (20tick/s, 주변 엔티티 위치)
+ │ ← CombatEvent (피해/힐/사망)
+ │ ← BuffApplied / BuffRemoved
+ │ ← SpawnEntity / DespawnEntity (AOI 진입/이탈)
+ └─ ZoneManager ← ZoneTransferNotify (포탈 진입 시)
+```
+
+---
+
+## 주의사항
+
+### Protobuf 네임스페이스
+생성된 C# 클래스 이름이 Unity 내장 타입과 충돌할 수 있습니다.
+
+```csharp
+// Vector3는 UnityEngine.Vector3와 충돌 → proto 타입 명시
+var v = new Proto.Vector3 { X = 1, Y = 0, Z = 0 };
+// 또는 별칭 사용
+using Vector3Proto = Proto.Vector3;
+```
+
+### WebGL 빌드
+WebGL에서는 `NativeWebSocket`의 JavaScript 백엔드가 사용됩니다.
+`_ws.DispatchMessageQueue()`를 `#if !UNITY_WEBGL || UNITY_EDITOR`로 감싸야 합니다.
+
+### 스레드 안전
+WebSocket 콜백은 백그라운드 스레드에서 호출됩니다.
+Unity API (`Instantiate`, `Destroy` 등)는 반드시 **메인 스레드**에서 호출해야 합니다.
+위 `NetworkClient`의 `_mainThreadQueue` 패턴을 사용하세요.
+
+### 핑 측정
+```csharp
+long sentTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+NetworkClient.Instance.Send(MsgType.Ping, new Ping { ClientTime = sentTime });
+
+// Pong 수신 시:
+// latency = (pong.ServerTime - pong.ClientTime) / 2 (단방향 추정)
+```
diff --git a/cmd/server/main.go b/cmd/server/main.go
new file mode 100644
index 0000000..e820a50
--- /dev/null
+++ b/cmd/server/main.go
@@ -0,0 +1,75 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "os"
+ "os/signal"
+ "syscall"
+
+ "a301_game_server/config"
+ "a301_game_server/internal/db"
+ "a301_game_server/internal/game"
+ "a301_game_server/internal/network"
+ "a301_game_server/pkg/logger"
+)
+
+func main() {
+ configPath := flag.String("config", "config/config.yaml", "path to configuration file")
+ flag.Parse()
+
+ cfg, err := config.Load(*configPath)
+ if err != nil {
+ panic("failed to load config: " + err.Error())
+ }
+
+ if err := logger.Init(cfg.Log.Level); err != nil {
+ panic("failed to init logger: " + err.Error())
+ }
+ defer logger.Sync()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ // Connect to PostgreSQL.
+ dbPool, err := db.NewPool(ctx, &cfg.Database)
+ if err != nil {
+ logger.Fatal("failed to connect to database", "error", err)
+ }
+ defer dbPool.Close()
+
+ // Run migrations.
+ if err := dbPool.RunMigrations(ctx); err != nil {
+ logger.Fatal("failed to run migrations", "error", err)
+ }
+
+ // Create game server.
+ gs := game.NewGameServer(cfg, dbPool)
+ gs.Start()
+ defer gs.Stop()
+
+ // Create and start network server.
+ srv := network.NewServer(cfg, gs)
+
+ // Graceful shutdown on SIGINT/SIGTERM.
+ sigCh := make(chan os.Signal, 1)
+ signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
+
+ go func() {
+ sig := <-sigCh
+ logger.Info("received signal, shutting down", "signal", sig)
+ cancel()
+ }()
+
+ logger.Info("game server starting",
+ "address", cfg.Server.Address(),
+ "tickRate", cfg.World.TickRate,
+ "aoiEnabled", cfg.World.AOI.Enabled,
+ )
+
+ if err := srv.Start(ctx); err != nil {
+ logger.Fatal("server error", "error", err)
+ }
+
+ logger.Info("server shutdown complete")
+}
diff --git a/cmd/testclient/main.go b/cmd/testclient/main.go
new file mode 100644
index 0000000..c434543
--- /dev/null
+++ b/cmd/testclient/main.go
@@ -0,0 +1,650 @@
+// testclient: 서버와 WebSocket 연결해서 전체 게임 흐름을 테스트하는 CLI 클라이언트.
+//
+// 사용법:
+//
+// go run ./cmd/testclient # 기본 (ws://localhost:8080/ws, user1/pass1)
+// go run ./cmd/testclient -url ws://host:8080/ws # 서버 주소 지정
+// go run ./cmd/testclient -user alice -pass secret
+// go run ./cmd/testclient -clients 5 # 5명 동시 접속 부하 테스트
+// go run ./cmd/testclient -scenario combat # 전투 시나리오만 실행
+package main
+
+import (
+ "encoding/binary"
+ "flag"
+ "fmt"
+ "log"
+ "math/rand"
+ "net/url"
+ "os"
+ "os/signal"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/gorilla/websocket"
+ pb "a301_game_server/proto/gen/pb"
+ "google.golang.org/protobuf/proto"
+)
+
+// ─── 메시지 타입 ID ────────────────────────────────────────────────
+const (
+ MsgLoginRequest uint16 = 0x0001
+ MsgLoginResponse uint16 = 0x0002
+ MsgEnterWorldRequest uint16 = 0x0003
+ MsgEnterWorldResponse uint16 = 0x0004
+
+ MsgMoveRequest uint16 = 0x0010
+ MsgStateUpdate uint16 = 0x0011
+ MsgSpawnEntity uint16 = 0x0012
+ MsgDespawnEntity uint16 = 0x0013
+ MsgZoneTransferNotify uint16 = 0x0014
+
+ MsgPing uint16 = 0x0020
+ MsgPong uint16 = 0x0021
+
+ MsgUseSkillRequest uint16 = 0x0040
+ MsgUseSkillResponse uint16 = 0x0041
+ MsgCombatEvent uint16 = 0x0042
+ MsgBuffApplied uint16 = 0x0043
+ MsgBuffRemoved uint16 = 0x0044
+ MsgRespawnRequest uint16 = 0x0045
+ MsgRespawnResponse uint16 = 0x0046
+
+ MsgAOIToggleRequest uint16 = 0x0030
+ MsgAOIToggleResponse uint16 = 0x0031
+ MsgMetricsRequest uint16 = 0x0032
+ MsgServerMetrics uint16 = 0x0033
+)
+
+var msgTypeNames = map[uint16]string{
+ MsgLoginRequest: "LoginRequest", MsgLoginResponse: "LoginResponse",
+ MsgEnterWorldRequest: "EnterWorldRequest", MsgEnterWorldResponse: "EnterWorldResponse",
+ MsgMoveRequest: "MoveRequest", MsgStateUpdate: "StateUpdate",
+ MsgSpawnEntity: "SpawnEntity", MsgDespawnEntity: "DespawnEntity",
+ MsgZoneTransferNotify: "ZoneTransferNotify",
+ MsgPing: "Ping", MsgPong: "Pong",
+ MsgUseSkillRequest: "UseSkillRequest", MsgUseSkillResponse: "UseSkillResponse",
+ MsgCombatEvent: "CombatEvent", MsgBuffApplied: "BuffApplied",
+ MsgBuffRemoved: "BuffRemoved",
+ MsgRespawnRequest: "RespawnRequest", MsgRespawnResponse: "RespawnResponse",
+ MsgAOIToggleRequest: "AOIToggleRequest", MsgAOIToggleResponse: "AOIToggleResponse",
+ MsgMetricsRequest: "MetricsRequest", MsgServerMetrics: "ServerMetrics",
+}
+
+// ─── 클라이언트 ────────────────────────────────────────────────────
+type Client struct {
+ id int
+ username string
+ password string
+ conn *websocket.Conn
+ logger *log.Logger
+
+ sessionToken string
+ playerID uint64
+ zoneID uint32
+
+ done chan struct{}
+ recvCh chan recvMsg
+
+ mu sync.Mutex
+ entities map[uint64]*pb.EntityState
+}
+
+type recvMsg struct {
+ msgType uint16
+ data []byte
+}
+
+func newClient(id int, username, password string) *Client {
+ return &Client{
+ id: id,
+ username: username,
+ password: password,
+ logger: log.New(os.Stdout, fmt.Sprintf("[client-%d] ", id), log.Ltime|log.Lmsgprefix),
+ done: make(chan struct{}),
+ recvCh: make(chan recvMsg, 64),
+ entities: make(map[uint64]*pb.EntityState),
+ }
+}
+
+func (c *Client) connect(serverURL string) error {
+ u, err := url.Parse(serverURL)
+ if err != nil {
+ return err
+ }
+
+ conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
+ if err != nil {
+ return fmt.Errorf("dial failed: %w", err)
+ }
+ c.conn = conn
+ c.logger.Printf("연결 성공: %s", serverURL)
+
+ // 수신 루프
+ go c.readLoop()
+ return nil
+}
+
+func (c *Client) readLoop() {
+ defer close(c.done)
+ for {
+ _, data, err := c.conn.ReadMessage()
+ if err != nil {
+ c.logger.Printf("연결 종료: %v", err)
+ return
+ }
+ if len(data) < 2 {
+ c.logger.Printf("패킷 너무 짧음 (%d bytes)", len(data))
+ continue
+ }
+ msgType := binary.BigEndian.Uint16(data[:2])
+ payload := data[2:]
+
+ // Ping에는 자동으로 Pong 응답
+ if msgType == MsgPing {
+ var ping pb.Ping
+ if err := proto.Unmarshal(payload, &ping); err == nil {
+ _ = c.send(MsgPong, &pb.Pong{ClientTime: ping.ClientTime})
+ }
+ continue
+ }
+
+ select {
+ case c.recvCh <- recvMsg{msgType, payload}:
+ default:
+ c.logger.Printf("recvCh 버퍼 풀 - 메시지 드롭: 0x%04X", msgType)
+ }
+ }
+}
+
+func (c *Client) send(msgType uint16, msg proto.Message) error {
+ body, err := proto.Marshal(msg)
+ if err != nil {
+ return err
+ }
+ packet := make([]byte, 2+len(body))
+ binary.BigEndian.PutUint16(packet[:2], msgType)
+ copy(packet[2:], body)
+ return c.conn.WriteMessage(websocket.BinaryMessage, packet)
+}
+
+// waitFor: 특정 메시지 타입이 올 때까지 대기 (timeout 적용)
+func (c *Client) waitFor(msgType uint16, timeout time.Duration) ([]byte, error) {
+ deadline := time.After(timeout)
+ for {
+ select {
+ case msg := <-c.recvCh:
+ name, ok := msgTypeNames[msg.msgType]
+ if !ok {
+ name = fmt.Sprintf("0x%04X", msg.msgType)
+ }
+ if msg.msgType == msgType {
+ c.logger.Printf(" <- %s (받음)", name)
+ return msg.data, nil
+ }
+ // 기다리는 타입이 아닌 것은 즉시 처리
+ c.handleAsync(msg)
+ case <-deadline:
+ return nil, fmt.Errorf("타임아웃: 0x%04X 메시지 대기 중 %v 초과", msgType, timeout)
+ case <-c.done:
+ return nil, fmt.Errorf("연결 끊김")
+ }
+ }
+}
+
+// handleAsync: waitFor 중 받은 다른 메시지를 처리
+func (c *Client) handleAsync(msg recvMsg) {
+ name, ok := msgTypeNames[msg.msgType]
+ if !ok {
+ name = fmt.Sprintf("0x%04X", msg.msgType)
+ }
+
+ switch msg.msgType {
+ case MsgStateUpdate:
+ var upd pb.StateUpdate
+ if err := proto.Unmarshal(msg.data, &upd); err == nil {
+ c.mu.Lock()
+ for _, e := range upd.Entities {
+ c.entities[e.EntityId] = e
+ }
+ c.mu.Unlock()
+ }
+ case MsgSpawnEntity:
+ var sp pb.SpawnEntity
+ if err := proto.Unmarshal(msg.data, &sp); err == nil {
+ c.mu.Lock()
+ c.entities[sp.Entity.EntityId] = sp.Entity
+ c.mu.Unlock()
+ c.logger.Printf(" <- SpawnEntity: [%d] %s (HP:%d/%d) @ (%.1f,%.1f)",
+ sp.Entity.EntityId, sp.Entity.Name, sp.Entity.Hp, sp.Entity.MaxHp,
+ sp.Entity.Position.GetX(), sp.Entity.Position.GetZ())
+ }
+ case MsgDespawnEntity:
+ var dp pb.DespawnEntity
+ if err := proto.Unmarshal(msg.data, &dp); err == nil {
+ c.mu.Lock()
+ delete(c.entities, dp.EntityId)
+ c.mu.Unlock()
+ c.logger.Printf(" <- DespawnEntity: [%d]", dp.EntityId)
+ }
+ case MsgCombatEvent:
+ var evt pb.CombatEvent
+ if err := proto.Unmarshal(msg.data, &evt); err == nil {
+ switch evt.EventType {
+ case pb.CombatEventType_COMBAT_EVENT_DAMAGE:
+ crit := ""
+ if evt.IsCritical {
+ crit = " [CRIT]"
+ }
+ c.logger.Printf(" <- CombatEvent: [%d] -> [%d] 데미지 %d%s (남은HP: %d/%d)",
+ evt.CasterId, evt.TargetId, evt.Damage, crit, evt.TargetHp, evt.TargetMaxHp)
+ case pb.CombatEventType_COMBAT_EVENT_HEAL:
+ c.logger.Printf(" <- CombatEvent: [%d] 힐 +%d (HP: %d/%d)",
+ evt.TargetId, evt.Heal, evt.TargetHp, evt.TargetMaxHp)
+ case pb.CombatEventType_COMBAT_EVENT_DEATH:
+ c.logger.Printf(" <- CombatEvent: [%d] 사망", evt.TargetId)
+ case pb.CombatEventType_COMBAT_EVENT_RESPAWN:
+ c.logger.Printf(" <- CombatEvent: [%d] 리스폰 (HP: %d/%d)",
+ evt.TargetId, evt.TargetHp, evt.TargetMaxHp)
+ }
+ }
+ case MsgBuffApplied:
+ var b pb.BuffApplied
+ if err := proto.Unmarshal(msg.data, &b); err == nil {
+ debuff := ""
+ if b.IsDebuff {
+ debuff = "[디버프]"
+ }
+ c.logger.Printf(" <- BuffApplied%s: [%d] %s (%.1fs)", debuff, b.TargetId, b.BuffName, b.Duration)
+ }
+ case MsgBuffRemoved:
+ var b pb.BuffRemoved
+ if err := proto.Unmarshal(msg.data, &b); err == nil {
+ c.logger.Printf(" <- BuffRemoved: [%d] buff#%d", b.TargetId, b.BuffId)
+ }
+ case MsgZoneTransferNotify:
+ var z pb.ZoneTransferNotify
+ if err := proto.Unmarshal(msg.data, &z); err == nil {
+ c.zoneID = z.NewZoneId
+ c.logger.Printf(" <- ZoneTransfer: 새 존 %d (주변 엔티티 %d개)", z.NewZoneId, len(z.NearbyEntities))
+ }
+ default:
+ c.logger.Printf(" <- %s (비동기 수신)", name)
+ }
+}
+
+// ─── 시나리오 ──────────────────────────────────────────────────────
+
+// stepAuth: 로그인 → 월드 입장
+func (c *Client) stepAuth() error {
+ // 1. 로그인
+ c.logger.Printf("→ LoginRequest (user=%s)", c.username)
+ if err := c.send(MsgLoginRequest, &pb.LoginRequest{
+ Username: c.username,
+ Password: c.password,
+ }); err != nil {
+ return err
+ }
+
+ data, err := c.waitFor(MsgLoginResponse, 5*time.Second)
+ if err != nil {
+ return err
+ }
+ var resp pb.LoginResponse
+ if err := proto.Unmarshal(data, &resp); err != nil {
+ return err
+ }
+ if !resp.Success {
+ return fmt.Errorf("로그인 실패: %s", resp.ErrorMessage)
+ }
+ c.sessionToken = resp.SessionToken
+ c.playerID = resp.PlayerId
+ c.logger.Printf(" 로그인 성공: playerID=%d, token=%s…", resp.PlayerId, resp.SessionToken[:8])
+
+ // 2. 월드 입장
+ c.logger.Printf("→ EnterWorldRequest")
+ if err := c.send(MsgEnterWorldRequest, &pb.EnterWorldRequest{
+ SessionToken: c.sessionToken,
+ }); err != nil {
+ return err
+ }
+
+ data, err = c.waitFor(MsgEnterWorldResponse, 5*time.Second)
+ if err != nil {
+ return err
+ }
+ var ewResp pb.EnterWorldResponse
+ if err := proto.Unmarshal(data, &ewResp); err != nil {
+ return err
+ }
+ if !ewResp.Success {
+ return fmt.Errorf("월드 입장 실패: %s", ewResp.ErrorMessage)
+ }
+ c.zoneID = ewResp.ZoneId
+ c.logger.Printf(" 월드 입장 성공: zone=%d, pos=(%.1f, %.1f, %.1f)",
+ ewResp.ZoneId,
+ ewResp.Self.Position.GetX(),
+ ewResp.Self.Position.GetY(),
+ ewResp.Self.Position.GetZ())
+ c.logger.Printf(" 플레이어 정보: name=%s, HP=%d/%d, level=%d",
+ ewResp.Self.Name, ewResp.Self.Hp, ewResp.Self.MaxHp, ewResp.Self.Level)
+ return nil
+}
+
+// stepMove: 위치 이동 전송 후 StateUpdate 수신 확인
+func (c *Client) stepMove() error {
+ c.logger.Printf("─── 이동 테스트 ───")
+ positions := [][2]float32{{5, 5}, {10, 10}, {15, 5}, {10, 0}}
+
+ for _, pos := range positions {
+ c.logger.Printf("→ MoveRequest (%.1f, 0, %.1f)", pos[0], pos[1])
+ if err := c.send(MsgMoveRequest, &pb.MoveRequest{
+ Position: &pb.Vector3{X: pos[0], Y: 0, Z: pos[1]},
+ Velocity: &pb.Vector3{X: 1, Y: 0, Z: 0},
+ Rotation: 0,
+ }); err != nil {
+ return err
+ }
+ time.Sleep(100 * time.Millisecond)
+ }
+
+ // StateUpdate 수신 대기 (이동 후 서버 틱 안에 옴)
+ c.logger.Printf("StateUpdate 대기 중…")
+ _, err := c.waitFor(MsgStateUpdate, 3*time.Second)
+ if err != nil {
+ c.logger.Printf(" 경고: StateUpdate 없음 (혼자 접속 중이면 정상)")
+ return nil
+ }
+ c.mu.Lock()
+ c.logger.Printf(" 현재 시야 내 엔티티 수: %d", len(c.entities))
+ c.mu.Unlock()
+ return nil
+}
+
+// stepCombat: 주변 몹 타겟팅 후 스킬 사용
+func (c *Client) stepCombat() error {
+ c.logger.Printf("─── 전투 테스트 ───")
+
+ // 주변 몹 찾기 (SpawnEntity로 받은 것들 중 mob 타입)
+ var mobID uint64
+ c.mu.Lock()
+ for id, e := range c.entities {
+ if e.EntityType == pb.EntityType_ENTITY_TYPE_MOB {
+ mobID = id
+ c.logger.Printf(" 타겟 몹 발견: [%d] %s (HP:%d/%d)", id, e.Name, e.Hp, e.MaxHp)
+ break
+ }
+ }
+ c.mu.Unlock()
+
+ if mobID == 0 {
+ c.logger.Printf(" 주변에 몹이 없음 - 스킬 테스트 스킵")
+ return nil
+ }
+
+ skills := []struct {
+ id uint32
+ name string
+ }{
+ {1, "Basic Attack"},
+ {2, "Fireball"},
+ }
+
+ for _, skill := range skills {
+ c.logger.Printf("→ UseSkillRequest: %s (skillID=%d) → 타겟 [%d]", skill.name, skill.id, mobID)
+ if err := c.send(MsgUseSkillRequest, &pb.UseSkillRequest{
+ SkillId: skill.id,
+ TargetId: mobID,
+ }); err != nil {
+ return err
+ }
+
+ data, err := c.waitFor(MsgUseSkillResponse, 3*time.Second)
+ if err != nil {
+ return err
+ }
+ var resp pb.UseSkillResponse
+ if err := proto.Unmarshal(data, &resp); err != nil {
+ return err
+ }
+ if resp.Success {
+ c.logger.Printf(" 스킬 성공")
+ } else {
+ c.logger.Printf(" 스킬 실패: %s", resp.ErrorMessage)
+ }
+ time.Sleep(200 * time.Millisecond) // 쿨다운 대기
+ }
+
+ return nil
+}
+
+// stepHeal: 자신 힐 스킬
+func (c *Client) stepHeal() error {
+ c.logger.Printf("─── 힐 테스트 ───")
+ c.logger.Printf("→ UseSkillRequest: Heal (skillID=3, 셀프)")
+ if err := c.send(MsgUseSkillRequest, &pb.UseSkillRequest{SkillId: 3}); err != nil {
+ return err
+ }
+ data, err := c.waitFor(MsgUseSkillResponse, 3*time.Second)
+ if err != nil {
+ return err
+ }
+ var resp pb.UseSkillResponse
+ if err := proto.Unmarshal(data, &resp); err != nil {
+ return err
+ }
+ if resp.Success {
+ c.logger.Printf(" 힐 성공")
+ } else {
+ c.logger.Printf(" 힐 실패: %s", resp.ErrorMessage)
+ }
+ return nil
+}
+
+// stepMetrics: 서버 메트릭 요청
+func (c *Client) stepMetrics() error {
+ c.logger.Printf("─── 서버 메트릭 ───")
+ c.logger.Printf("→ MetricsRequest")
+ if err := c.send(MsgMetricsRequest, &pb.MetricsRequest{}); err != nil {
+ return err
+ }
+ data, err := c.waitFor(MsgServerMetrics, 3*time.Second)
+ if err != nil {
+ return err
+ }
+ var m pb.ServerMetrics
+ if err := proto.Unmarshal(data, &m); err != nil {
+ return err
+ }
+ c.logger.Printf(" 온라인 플레이어: %d", m.OnlinePlayers)
+ c.logger.Printf(" 총 엔티티: %d", m.TotalEntities)
+ c.logger.Printf(" 틱 시간: %d µs", m.TickDurationUs)
+ c.logger.Printf(" AOI 활성화: %v", m.AoiEnabled)
+ return nil
+}
+
+// stepAOIToggle: AOI on/off 토글 테스트
+func (c *Client) stepAOIToggle(enabled bool) error {
+ c.logger.Printf("─── AOI 토글 (enabled=%v) ───", enabled)
+ if err := c.send(MsgAOIToggleRequest, &pb.AOIToggleRequest{Enabled: enabled}); err != nil {
+ return err
+ }
+ data, err := c.waitFor(MsgAOIToggleResponse, 3*time.Second)
+ if err != nil {
+ return err
+ }
+ var resp pb.AOIToggleResponse
+ if err := proto.Unmarshal(data, &resp); err != nil {
+ return err
+ }
+ c.logger.Printf(" AOI 토글 결과: %s", resp.Message)
+ return nil
+}
+
+// runScenario: 전체 또는 특정 시나리오 실행
+func (c *Client) runScenario(scenario string) {
+ // 공통: 인증
+ if err := c.stepAuth(); err != nil {
+ c.logger.Printf("[FAIL] 인증: %v", err)
+ return
+ }
+
+ switch scenario {
+ case "auth":
+ c.logger.Printf("[OK] 인증 시나리오 완료")
+
+ case "move":
+ if err := c.stepMove(); err != nil {
+ c.logger.Printf("[FAIL] 이동: %v", err)
+ return
+ }
+ c.logger.Printf("[OK] 이동 시나리오 완료")
+
+ case "combat":
+ // 이동해서 몹 시야 안으로
+ time.Sleep(500 * time.Millisecond) // SpawnEntity 수신 대기
+ if err := c.stepCombat(); err != nil {
+ c.logger.Printf("[FAIL] 전투: %v", err)
+ return
+ }
+ if err := c.stepHeal(); err != nil {
+ c.logger.Printf("[FAIL] 힐: %v", err)
+ return
+ }
+ c.logger.Printf("[OK] 전투 시나리오 완료")
+
+ case "metrics":
+ if err := c.stepMetrics(); err != nil {
+ c.logger.Printf("[FAIL] 메트릭: %v", err)
+ return
+ }
+ if err := c.stepAOIToggle(false); err != nil {
+ c.logger.Printf("[FAIL] AOI 토글: %v", err)
+ return
+ }
+ if err := c.stepAOIToggle(true); err != nil {
+ c.logger.Printf("[FAIL] AOI 토글: %v", err)
+ return
+ }
+ c.logger.Printf("[OK] 메트릭 시나리오 완료")
+
+ default: // "all"
+ time.Sleep(500 * time.Millisecond) // SpawnEntity 수신 대기
+
+ if err := c.stepMove(); err != nil {
+ c.logger.Printf("[FAIL] 이동: %v", err)
+ }
+ if err := c.stepCombat(); err != nil {
+ c.logger.Printf("[FAIL] 전투: %v", err)
+ }
+ if err := c.stepHeal(); err != nil {
+ c.logger.Printf("[FAIL] 힐: %v", err)
+ }
+ if err := c.stepMetrics(); err != nil {
+ c.logger.Printf("[FAIL] 메트릭: %v", err)
+ }
+ c.logger.Printf("[OK] 전체 시나리오 완료")
+ }
+}
+
+// ─── 부하 테스트 ──────────────────────────────────────────────────
+func loadTest(serverURL string, numClients int, duration time.Duration) {
+ fmt.Printf("부하 테스트: %d 클라이언트, %v 동안\n", numClients, duration)
+ var wg sync.WaitGroup
+ start := time.Now()
+
+ for i := 0; i < numClients; i++ {
+ wg.Add(1)
+ go func(id int) {
+ defer wg.Done()
+ username := fmt.Sprintf("loadtest_%d_%d", id, rand.Intn(9999))
+ c := newClient(id, username, "testpass")
+ if err := c.connect(serverURL); err != nil {
+ c.logger.Printf("[FAIL] 연결: %v", err)
+ return
+ }
+ defer c.conn.Close()
+
+ if err := c.stepAuth(); err != nil {
+ c.logger.Printf("[FAIL] 인증: %v", err)
+ return
+ }
+
+ // duration 동안 이동 패킷 전송
+ ticker := time.NewTicker(50 * time.Millisecond)
+ defer ticker.Stop()
+ timeout := time.After(duration)
+ x, z := float32(0), float32(0)
+
+ for {
+ select {
+ case <-ticker.C:
+ x += rand.Float32()*2 - 1
+ z += rand.Float32()*2 - 1
+ _ = c.send(MsgMoveRequest, &pb.MoveRequest{
+ Position: &pb.Vector3{X: x, Y: 0, Z: z},
+ Velocity: &pb.Vector3{X: 1, Y: 0, Z: 0},
+ })
+ case <-timeout:
+ c.logger.Printf("완료 (%.1fs)", time.Since(start).Seconds())
+ return
+ case <-c.done:
+ return
+ }
+ }
+ }(i)
+ time.Sleep(10 * time.Millisecond) // 동시 접속 분산
+ }
+
+ wg.Wait()
+ fmt.Printf("부하 테스트 완료: %.1fs\n", time.Since(start).Seconds())
+}
+
+// ─── main ─────────────────────────────────────────────────────────
+func main() {
+ serverURL := flag.String("url", "ws://localhost:8080/ws", "WebSocket 서버 주소")
+ username := flag.String("user", "testuser1", "사용자 이름")
+ password := flag.String("pass", "password123", "비밀번호")
+ scenario := flag.String("scenario", "all", "실행할 시나리오: all | auth | move | combat | metrics")
+ numClients := flag.Int("clients", 1, "동시 접속 클라이언트 수 (부하 테스트)")
+ loadDuration := flag.Duration("duration", 10*time.Second, "부하 테스트 지속 시간")
+ flag.Parse()
+
+ // 부하 테스트 모드
+ if *numClients > 1 {
+ loadTest(*serverURL, *numClients, *loadDuration)
+ return
+ }
+
+ // 단일 클라이언트 시나리오
+ c := newClient(1, *username, *password)
+ if err := c.connect(*serverURL); err != nil {
+ log.Fatalf("서버 연결 실패: %v\n서버가 실행 중인지 확인하세요: make run", err)
+ }
+ defer c.conn.Close()
+
+ c.runScenario(*scenario)
+
+ // Ctrl+C 대기 (실시간 메시지 관찰용)
+ if *scenario == "all" {
+ fmt.Println("\n[Ctrl+C로 종료] 서버 메시지 수신 대기 중...")
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
+
+ ticker := time.NewTicker(50 * time.Millisecond)
+ defer ticker.Stop()
+ loop:
+ for {
+ select {
+ case <-sig:
+ break loop
+ case <-c.done:
+ break loop
+ case msg := <-c.recvCh:
+ c.handleAsync(msg)
+ case <-ticker.C:
+ }
+ }
+ }
+}
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..3e766ed
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,124 @@
+package config
+
+import (
+ "fmt"
+ "os"
+ "time"
+
+ "gopkg.in/yaml.v3"
+)
+
+type Config struct {
+ Server ServerConfig `yaml:"server"`
+ Database DatabaseConfig `yaml:"database"`
+ World WorldConfig `yaml:"world"`
+ Network NetworkConfig `yaml:"network"`
+ Log LogConfig `yaml:"log"`
+}
+
+type DatabaseConfig struct {
+ Host string `yaml:"host"`
+ Port int `yaml:"port"`
+ User string `yaml:"user"`
+ Password string `yaml:"password"`
+ DBName string `yaml:"dbname"`
+ MaxConns int32 `yaml:"max_conns"`
+ MinConns int32 `yaml:"min_conns"`
+ SaveInterval time.Duration `yaml:"save_interval"`
+}
+
+func (d *DatabaseConfig) DSN() string {
+ return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable",
+ d.User, d.Password, d.Host, d.Port, d.DBName)
+}
+
+type ServerConfig struct {
+ Host string `yaml:"host"`
+ Port int `yaml:"port"`
+}
+
+type WorldConfig struct {
+ TickRate int `yaml:"tick_rate"`
+ AOI AOIConfig `yaml:"aoi"`
+ MaxPlayers int `yaml:"max_players"`
+}
+
+type AOIConfig struct {
+ Enabled bool `yaml:"enabled"`
+ CellSize float32 `yaml:"cell_size"`
+ ViewRange int `yaml:"view_range"`
+}
+
+type NetworkConfig struct {
+ WriteBufferSize int `yaml:"write_buffer_size"`
+ ReadBufferSize int `yaml:"read_buffer_size"`
+ SendChannelSize int `yaml:"send_channel_size"`
+ HeartbeatInterval time.Duration `yaml:"heartbeat_interval"`
+ HeartbeatTimeout time.Duration `yaml:"heartbeat_timeout"`
+ MaxMessageSize int64 `yaml:"max_message_size"`
+}
+
+type LogConfig struct {
+ Level string `yaml:"level"`
+}
+
+func (c *Config) TickInterval() time.Duration {
+ return time.Second / time.Duration(c.World.TickRate)
+}
+
+func (c *ServerConfig) Address() string {
+ return fmt.Sprintf("%s:%d", c.Host, c.Port)
+}
+
+func Load(path string) (*Config, error) {
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, fmt.Errorf("read config file: %w", err)
+ }
+
+ cfg := defaultConfig()
+ if err := yaml.Unmarshal(data, cfg); err != nil {
+ return nil, fmt.Errorf("parse config file: %w", err)
+ }
+
+ return cfg, nil
+}
+
+func defaultConfig() *Config {
+ return &Config{
+ Server: ServerConfig{
+ Host: "0.0.0.0",
+ Port: 8080,
+ },
+ World: WorldConfig{
+ TickRate: 20,
+ MaxPlayers: 5000,
+ AOI: AOIConfig{
+ Enabled: true,
+ CellSize: 50.0,
+ ViewRange: 2,
+ },
+ },
+ Database: DatabaseConfig{
+ Host: "localhost",
+ Port: 5432,
+ User: "postgres",
+ Password: "postgres",
+ DBName: "mmorpg",
+ MaxConns: 50,
+ MinConns: 5,
+ SaveInterval: 60 * time.Second,
+ },
+ Network: NetworkConfig{
+ WriteBufferSize: 4096,
+ ReadBufferSize: 4096,
+ SendChannelSize: 256,
+ HeartbeatInterval: 5 * time.Second,
+ HeartbeatTimeout: 15 * time.Second,
+ MaxMessageSize: 8192,
+ },
+ Log: LogConfig{
+ Level: "info",
+ },
+ }
+}
diff --git a/config/config.yaml b/config/config.yaml
new file mode 100644
index 0000000..386c417
--- /dev/null
+++ b/config/config.yaml
@@ -0,0 +1,32 @@
+server:
+ host: "0.0.0.0"
+ port: 8080
+
+database:
+ host: "localhost"
+ port: 5432
+ user: "postgres"
+ password: "postgres"
+ dbname: "mmorpg"
+ max_conns: 50
+ min_conns: 5
+ save_interval: 60s
+
+world:
+ tick_rate: 20
+ max_players: 5000
+ aoi:
+ enabled: true
+ cell_size: 50.0
+ view_range: 2
+
+network:
+ write_buffer_size: 4096
+ read_buffer_size: 4096
+ send_channel_size: 256
+ heartbeat_interval: 5s
+ heartbeat_timeout: 15s
+ max_message_size: 8192
+
+log:
+ level: "info"
diff --git a/gen_proto.ps1 b/gen_proto.ps1
new file mode 100644
index 0000000..2d362a6
--- /dev/null
+++ b/gen_proto.ps1
@@ -0,0 +1,42 @@
+# proto 코드 생성 스크립트 (PowerShell)
+# 사용법: .\gen_proto.ps1
+
+$PROTOC = "C:\Users\SSAFY\AppData\Local\Microsoft\WinGet\Packages\Google.Protobuf_Microsoft.Winget.Source_8wekyb3d8bbwe\bin\protoc.exe"
+$GOPATH_BIN = (go env GOPATH) + "\bin"
+$env:PATH = "$GOPATH_BIN;$env:PATH"
+
+$ROOT = $PSScriptRoot
+
+# Go 서버 코드 생성
+Write-Host "[1/2] Generating Go server code..." -ForegroundColor Cyan
+& $PROTOC `
+ --go_out="$ROOT\proto\gen\pb" `
+ --go_opt=paths=source_relative `
+ --proto_path="$ROOT\proto" `
+ "$ROOT\proto\messages.proto"
+
+if ($LASTEXITCODE -ne 0) {
+ Write-Host "Go proto generation failed." -ForegroundColor Red
+ exit 1
+}
+Write-Host " -> proto/gen/pb/messages.pb.go" -ForegroundColor Green
+
+# Unity C# 코드 생성
+Write-Host "[2/2] Generating C# Unity code..." -ForegroundColor Cyan
+$unityOutDir = "$ROOT\unity\Assets\Scripts\Proto"
+if (!(Test-Path $unityOutDir)) {
+ New-Item -ItemType Directory -Path $unityOutDir | Out-Null
+}
+
+& $PROTOC `
+ --csharp_out="$unityOutDir" `
+ --proto_path="$ROOT\proto" `
+ "$ROOT\proto\messages.proto"
+
+if ($LASTEXITCODE -ne 0) {
+ Write-Host "C# proto generation failed." -ForegroundColor Red
+ exit 1
+}
+Write-Host " -> unity/Assets/Scripts/Proto/Messages.cs" -ForegroundColor Green
+
+Write-Host "`nDone!" -ForegroundColor Green
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..26f2093
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,18 @@
+module a301_game_server
+
+go 1.25
+
+require (
+ github.com/gorilla/websocket v1.5.3 // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+ github.com/jackc/pgx/v5 v5.8.0 // indirect
+ github.com/jackc/puddle/v2 v2.2.2 // indirect
+ go.uber.org/multierr v1.10.0 // indirect
+ go.uber.org/zap v1.27.1 // indirect
+ golang.org/x/crypto v0.48.0 // indirect
+ golang.org/x/sync v0.19.0 // indirect
+ golang.org/x/text v0.34.0 // indirect
+ google.golang.org/protobuf v1.36.11 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..0bc5b72
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,31 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
+github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
+golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
+golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/ai/behavior.go b/internal/ai/behavior.go
new file mode 100644
index 0000000..900bc5e
--- /dev/null
+++ b/internal/ai/behavior.go
@@ -0,0 +1,184 @@
+package ai
+
+import (
+ "math"
+ "time"
+
+ "a301_game_server/internal/entity"
+ "a301_game_server/pkg/mathutil"
+)
+
+// AIState represents the mob's behavioral state.
+type AIState int
+
+const (
+ StateIdle AIState = iota // Standing at spawn, doing nothing
+ StatePatrol // Wandering near spawn
+ StateChase // Moving toward a target
+ StateAttack // In attack range, using skills
+ StateReturn // Walking back to spawn (leash)
+ StateDead // Dead, waiting for respawn
+)
+
+// EntityProvider gives the AI access to the game world.
+type EntityProvider interface {
+ GetEntity(id uint64) entity.Entity
+ GetPlayersInRange(center mathutil.Vec3, radius float32) []entity.Entity
+}
+
+// SkillUser allows the AI to use combat skills.
+type SkillUser interface {
+ UseSkill(casterID uint64, skillID uint32, targetID uint64, targetPos mathutil.Vec3) (bool, string)
+}
+
+// UpdateMob advances one mob's AI by one tick.
+func UpdateMob(m *Mob, dt time.Duration, provider EntityProvider, skills SkillUser) {
+ if !m.IsAlive() {
+ m.SetState(StateDead)
+ return
+ }
+
+ switch m.State() {
+ case StateIdle:
+ updateIdle(m, provider)
+ case StateChase:
+ updateChase(m, dt, provider)
+ case StateAttack:
+ updateAttack(m, provider, skills)
+ case StateReturn:
+ updateReturn(m, dt)
+ case StateDead:
+ // handled by spawner
+ }
+}
+
+func updateIdle(m *Mob, provider EntityProvider) {
+ // Scan for players in aggro range.
+ target := findNearestPlayer(m, provider, m.Def().AggroRange)
+ if target != nil {
+ m.SetTargetID(target.EntityID())
+ m.SetState(StateChase)
+ }
+}
+
+func updateChase(m *Mob, dt time.Duration, provider EntityProvider) {
+ target := provider.GetEntity(m.TargetID())
+ if target == nil || !isAlive(target) {
+ m.SetTargetID(0)
+ m.SetState(StateReturn)
+ return
+ }
+
+ // Leash check: too far from spawn?
+ if m.Position().DistanceXZ(m.SpawnPos()) > m.Def().LeashRange {
+ m.SetTargetID(0)
+ m.SetState(StateReturn)
+ return
+ }
+
+ dist := m.Position().DistanceXZ(target.Position())
+
+ // Close enough to attack?
+ if dist <= m.Def().AttackRange {
+ m.SetState(StateAttack)
+ return
+ }
+
+ // Move toward target.
+ moveToward(m, target.Position(), dt)
+}
+
+func updateAttack(m *Mob, provider EntityProvider, skills SkillUser) {
+ target := provider.GetEntity(m.TargetID())
+ if target == nil || !isAlive(target) {
+ m.SetTargetID(0)
+ m.SetState(StateReturn)
+ return
+ }
+
+ dist := m.Position().DistanceXZ(target.Position())
+
+ // Target moved out of attack range? Chase again.
+ if dist > m.Def().AttackRange*1.2 { // 20% buffer to prevent flickering
+ m.SetState(StateChase)
+ return
+ }
+
+ // Leash check.
+ if m.Position().DistanceXZ(m.SpawnPos()) > m.Def().LeashRange {
+ m.SetTargetID(0)
+ m.SetState(StateReturn)
+ return
+ }
+
+ // Face target.
+ dir := target.Position().Sub(m.Position())
+ m.SetRotation(float32(math.Atan2(float64(dir.X), float64(dir.Z))))
+
+ // Use attack skill.
+ skills.UseSkill(m.EntityID(), m.Def().AttackSkill, target.EntityID(), mathutil.Vec3{})
+}
+
+func updateReturn(m *Mob, dt time.Duration) {
+ dist := m.Position().DistanceXZ(m.SpawnPos())
+ if dist < 0.5 {
+ m.SetPosition(m.SpawnPos())
+ m.SetState(StateIdle)
+ // Heal to full when returning.
+ m.SetHP(m.MaxHP())
+ return
+ }
+ moveToward(m, m.SpawnPos(), dt)
+}
+
+// moveToward moves the mob toward a target position at its move speed.
+func moveToward(m *Mob, target mathutil.Vec3, dt time.Duration) {
+ dir := target.Sub(m.Position())
+ dir.Y = 0
+ dist := dir.Length()
+ if dist < 0.01 {
+ return
+ }
+
+ step := m.Def().MoveSpeed * float32(dt.Seconds())
+ if step > dist {
+ step = dist
+ }
+
+ move := dir.Normalize().Scale(step)
+ m.SetPosition(m.Position().Add(move))
+
+ // Face movement direction.
+ m.SetRotation(float32(math.Atan2(float64(dir.X), float64(dir.Z))))
+}
+
+func findNearestPlayer(m *Mob, provider EntityProvider, radius float32) entity.Entity {
+ players := provider.GetPlayersInRange(m.Position(), radius)
+ if len(players) == 0 {
+ return nil
+ }
+
+ var nearest entity.Entity
+ minDist := float32(math.MaxFloat32)
+ for _, p := range players {
+ if !isAlive(p) {
+ continue
+ }
+ d := m.Position().DistanceXZ(p.Position())
+ if d < minDist {
+ minDist = d
+ nearest = p
+ }
+ }
+ return nearest
+}
+
+func isAlive(e entity.Entity) bool {
+ type aliveChecker interface {
+ IsAlive() bool
+ }
+ if a, ok := e.(aliveChecker); ok {
+ return a.IsAlive()
+ }
+ return true
+}
diff --git a/internal/ai/behavior_test.go b/internal/ai/behavior_test.go
new file mode 100644
index 0000000..2c1a287
--- /dev/null
+++ b/internal/ai/behavior_test.go
@@ -0,0 +1,221 @@
+package ai
+
+import (
+ "testing"
+ "time"
+
+ "a301_game_server/internal/combat"
+ "a301_game_server/internal/entity"
+ "a301_game_server/pkg/mathutil"
+ pb "a301_game_server/proto/gen/pb"
+)
+
+type mockPlayer struct {
+ id uint64
+ pos mathutil.Vec3
+ hp int32
+ maxHP int32
+ alive bool
+}
+
+func (m *mockPlayer) EntityID() uint64 { return m.id }
+func (m *mockPlayer) EntityType() entity.Type { return entity.TypePlayer }
+func (m *mockPlayer) Position() mathutil.Vec3 { return m.pos }
+func (m *mockPlayer) SetPosition(p mathutil.Vec3) { m.pos = p }
+func (m *mockPlayer) Rotation() float32 { return 0 }
+func (m *mockPlayer) SetRotation(float32) {}
+func (m *mockPlayer) ToProto() *pb.EntityState { return &pb.EntityState{EntityId: m.id} }
+func (m *mockPlayer) HP() int32 { return m.hp }
+func (m *mockPlayer) MaxHP() int32 { return m.maxHP }
+func (m *mockPlayer) SetHP(hp int32) { m.hp = hp; m.alive = hp > 0 }
+func (m *mockPlayer) MP() int32 { return 100 }
+func (m *mockPlayer) SetMP(int32) {}
+func (m *mockPlayer) IsAlive() bool { return m.alive }
+func (m *mockPlayer) Stats() combat.CombatStats { return combat.CombatStats{Str: 10, Dex: 10, Int: 10, Level: 1} }
+
+type mockProvider struct {
+ entities map[uint64]entity.Entity
+}
+
+func (p *mockProvider) GetEntity(id uint64) entity.Entity {
+ return p.entities[id]
+}
+
+func (p *mockProvider) GetPlayersInRange(center mathutil.Vec3, radius float32) []entity.Entity {
+ radiusSq := radius * radius
+ var result []entity.Entity
+ for _, e := range p.entities {
+ if e.EntityType() == entity.TypePlayer {
+ if e.Position().DistanceSqTo(center) <= radiusSq {
+ result = append(result, e)
+ }
+ }
+ }
+ return result
+}
+
+type mockSkillUser struct {
+ called bool
+}
+
+func (s *mockSkillUser) UseSkill(casterID uint64, skillID uint32, targetID uint64, targetPos mathutil.Vec3) (bool, string) {
+ s.called = true
+ return true, ""
+}
+
+func newTestMob() *Mob {
+ def := &MobDef{
+ ID: 1,
+ Name: "TestMob",
+ Level: 1,
+ HP: 100,
+ MP: 0,
+ Str: 10,
+ Dex: 5,
+ Int: 3,
+ MoveSpeed: 5.0,
+ AggroRange: 10.0,
+ AttackRange: 2.5,
+ AttackSkill: 1,
+ LeashRange: 30.0,
+ }
+ return NewMob(1000, def, mathutil.NewVec3(50, 0, 50))
+}
+
+func TestIdleToChase(t *testing.T) {
+ mob := newTestMob()
+ player := &mockPlayer{id: 1, pos: mathutil.NewVec3(55, 0, 50), hp: 100, maxHP: 100, alive: true}
+
+ provider := &mockProvider{
+ entities: map[uint64]entity.Entity{1: player, 1000: mob},
+ }
+ skills := &mockSkillUser{}
+
+ // Player is within aggro range (5 units < 10 aggro range).
+ UpdateMob(mob, 50*time.Millisecond, provider, skills)
+
+ if mob.State() != StateChase {
+ t.Errorf("expected StateChase, got %d", mob.State())
+ }
+ if mob.TargetID() != 1 {
+ t.Errorf("expected target 1, got %d", mob.TargetID())
+ }
+}
+
+func TestChaseToAttack(t *testing.T) {
+ mob := newTestMob()
+ mob.SetState(StateChase)
+ mob.SetTargetID(1)
+
+ // Place player within attack range.
+ player := &mockPlayer{id: 1, pos: mathutil.NewVec3(52, 0, 50), hp: 100, maxHP: 100, alive: true}
+
+ provider := &mockProvider{
+ entities: map[uint64]entity.Entity{1: player, 1000: mob},
+ }
+ skills := &mockSkillUser{}
+
+ UpdateMob(mob, 50*time.Millisecond, provider, skills)
+
+ if mob.State() != StateAttack {
+ t.Errorf("expected StateAttack, got %d", mob.State())
+ }
+}
+
+func TestAttackUsesSkill(t *testing.T) {
+ mob := newTestMob()
+ mob.SetState(StateAttack)
+ mob.SetTargetID(1)
+
+ player := &mockPlayer{id: 1, pos: mathutil.NewVec3(52, 0, 50), hp: 100, maxHP: 100, alive: true}
+
+ provider := &mockProvider{
+ entities: map[uint64]entity.Entity{1: player, 1000: mob},
+ }
+ skills := &mockSkillUser{}
+
+ UpdateMob(mob, 50*time.Millisecond, provider, skills)
+
+ if !skills.called {
+ t.Error("expected skill to be used")
+ }
+}
+
+func TestLeashReturn(t *testing.T) {
+ mob := newTestMob()
+ mob.SetState(StateChase)
+ mob.SetTargetID(1)
+
+ // Move mob far from spawn.
+ mob.SetPosition(mathutil.NewVec3(100, 0, 100)) // >30 units from spawn(50,0,50)
+
+ player := &mockPlayer{id: 1, pos: mathutil.NewVec3(110, 0, 110), hp: 100, maxHP: 100, alive: true}
+
+ provider := &mockProvider{
+ entities: map[uint64]entity.Entity{1: player, 1000: mob},
+ }
+ skills := &mockSkillUser{}
+
+ UpdateMob(mob, 50*time.Millisecond, provider, skills)
+
+ if mob.State() != StateReturn {
+ t.Errorf("expected StateReturn (leash), got %d", mob.State())
+ }
+}
+
+func TestReturnToIdle(t *testing.T) {
+ mob := newTestMob()
+ mob.SetState(StateReturn)
+ mob.SetPosition(mathutil.NewVec3(50.1, 0, 50.1)) // very close to spawn
+
+ skills := &mockSkillUser{}
+ provider := &mockProvider{entities: map[uint64]entity.Entity{1000: mob}}
+
+ UpdateMob(mob, 50*time.Millisecond, provider, skills)
+
+ if mob.State() != StateIdle {
+ t.Errorf("expected StateIdle after return, got %d", mob.State())
+ }
+ if mob.HP() != mob.MaxHP() {
+ t.Error("mob should heal to full on return")
+ }
+}
+
+func TestTargetDiesReturnToSpawn(t *testing.T) {
+ mob := newTestMob()
+ mob.SetState(StateChase)
+ mob.SetTargetID(1)
+
+ // Target is dead.
+ player := &mockPlayer{id: 1, pos: mathutil.NewVec3(55, 0, 50), hp: 0, maxHP: 100, alive: false}
+
+ provider := &mockProvider{
+ entities: map[uint64]entity.Entity{1: player, 1000: mob},
+ }
+ skills := &mockSkillUser{}
+
+ UpdateMob(mob, 50*time.Millisecond, provider, skills)
+
+ if mob.State() != StateReturn {
+ t.Errorf("expected StateReturn when target dies, got %d", mob.State())
+ }
+}
+
+func TestMobReset(t *testing.T) {
+ mob := newTestMob()
+ mob.SetHP(0)
+ mob.SetPosition(mathutil.NewVec3(100, 0, 100))
+ mob.SetState(StateDead)
+
+ mob.Reset()
+
+ if mob.HP() != mob.MaxHP() {
+ t.Error("HP should be full after reset")
+ }
+ if mob.Position() != mob.SpawnPos() {
+ t.Error("position should be spawn pos after reset")
+ }
+ if mob.State() != StateIdle {
+ t.Error("state should be Idle after reset")
+ }
+}
diff --git a/internal/ai/mob.go b/internal/ai/mob.go
new file mode 100644
index 0000000..efc1c83
--- /dev/null
+++ b/internal/ai/mob.go
@@ -0,0 +1,137 @@
+package ai
+
+import (
+ "a301_game_server/internal/combat"
+ "a301_game_server/internal/entity"
+ "a301_game_server/pkg/mathutil"
+ pb "a301_game_server/proto/gen/pb"
+)
+
+// MobDef defines a mob template loaded from data.
+type MobDef struct {
+ ID uint32
+ Name string
+ Level int32
+ HP int32
+ MP int32
+ Str int32
+ Dex int32
+ Int int32
+ MoveSpeed float32 // units per second
+ AggroRange float32 // distance to start chasing
+ AttackRange float32
+ AttackSkill uint32 // skill ID used for auto-attack
+ LeashRange float32 // max distance from spawn before returning
+ ExpReward int64
+ LootTable []LootEntry
+}
+
+// LootEntry defines a possible drop.
+type LootEntry struct {
+ ItemID uint32
+ Quantity int32
+ Chance float32 // 0.0 - 1.0
+}
+
+// Mob is a server-controlled enemy entity.
+type Mob struct {
+ id uint64
+ def *MobDef
+ position mathutil.Vec3
+ rotation float32
+ hp int32
+ maxHP int32
+ mp int32
+ maxMP int32
+ spawnPos mathutil.Vec3
+ alive bool
+
+ // AI state
+ state AIState
+ targetID uint64 // entity being chased/attacked
+}
+
+// NewMob creates a mob from a definition at the given position.
+func NewMob(id uint64, def *MobDef, spawnPos mathutil.Vec3) *Mob {
+ return &Mob{
+ id: id,
+ def: def,
+ position: spawnPos,
+ spawnPos: spawnPos,
+ hp: def.HP,
+ maxHP: def.HP,
+ mp: def.MP,
+ maxMP: def.MP,
+ alive: true,
+ state: StateIdle,
+ }
+}
+
+// Entity interface
+func (m *Mob) EntityID() uint64 { return m.id }
+func (m *Mob) EntityType() entity.Type { return entity.TypeMob }
+
+func (m *Mob) Position() mathutil.Vec3 { return m.position }
+func (m *Mob) SetPosition(p mathutil.Vec3) { m.position = p }
+func (m *Mob) Rotation() float32 { return m.rotation }
+func (m *Mob) SetRotation(r float32) { m.rotation = r }
+
+// Combatant interface
+func (m *Mob) HP() int32 { return m.hp }
+func (m *Mob) MaxHP() int32 { return m.maxHP }
+func (m *Mob) SetHP(hp int32) {
+ if hp < 0 {
+ hp = 0
+ }
+ if hp > m.maxHP {
+ hp = m.maxHP
+ }
+ m.hp = hp
+ m.alive = hp > 0
+}
+func (m *Mob) MP() int32 { return m.mp }
+func (m *Mob) SetMP(mp int32) {
+ if mp < 0 {
+ mp = 0
+ }
+ m.mp = mp
+}
+func (m *Mob) IsAlive() bool { return m.alive }
+func (m *Mob) Stats() combat.CombatStats {
+ return combat.CombatStats{
+ Str: m.def.Str,
+ Dex: m.def.Dex,
+ Int: m.def.Int,
+ Level: m.def.Level,
+ }
+}
+
+func (m *Mob) Def() *MobDef { return m.def }
+func (m *Mob) SpawnPos() mathutil.Vec3 { return m.spawnPos }
+func (m *Mob) State() AIState { return m.state }
+func (m *Mob) SetState(s AIState) { m.state = s }
+func (m *Mob) TargetID() uint64 { return m.targetID }
+func (m *Mob) SetTargetID(id uint64) { m.targetID = id }
+
+func (m *Mob) ToProto() *pb.EntityState {
+ return &pb.EntityState{
+ EntityId: m.id,
+ Name: m.def.Name,
+ Position: &pb.Vector3{X: m.position.X, Y: m.position.Y, Z: m.position.Z},
+ Rotation: m.rotation,
+ Hp: m.hp,
+ MaxHp: m.maxHP,
+ Level: m.def.Level,
+ EntityType: pb.EntityType_ENTITY_TYPE_MOB,
+ }
+}
+
+// Reset restores the mob to full health at its spawn position.
+func (m *Mob) Reset() {
+ m.hp = m.maxHP
+ m.mp = m.maxMP
+ m.position = m.spawnPos
+ m.alive = true
+ m.state = StateIdle
+ m.targetID = 0
+}
diff --git a/internal/ai/registry.go b/internal/ai/registry.go
new file mode 100644
index 0000000..a0a020f
--- /dev/null
+++ b/internal/ai/registry.go
@@ -0,0 +1,130 @@
+package ai
+
+import "time"
+
+// MobRegistry holds all mob definitions.
+type MobRegistry struct {
+ defs map[uint32]*MobDef
+}
+
+// NewMobRegistry creates a registry with default mob definitions.
+func NewMobRegistry() *MobRegistry {
+ r := &MobRegistry{defs: make(map[uint32]*MobDef)}
+ r.registerDefaults()
+ return r
+}
+
+// Get returns a mob definition by ID.
+func (r *MobRegistry) Get(id uint32) *MobDef {
+ return r.defs[id]
+}
+
+func (r *MobRegistry) registerDefaults() {
+ r.defs[1] = &MobDef{
+ ID: 1,
+ Name: "Goblin",
+ Level: 1,
+ HP: 60,
+ MP: 0,
+ Str: 8,
+ Dex: 6,
+ Int: 3,
+ MoveSpeed: 4.0,
+ AggroRange: 10.0,
+ AttackRange: 2.5,
+ AttackSkill: 1, // basic attack
+ LeashRange: 30.0,
+ ExpReward: 20,
+ LootTable: []LootEntry{
+ {ItemID: 101, Quantity: 1, Chance: 0.5},
+ },
+ }
+
+ r.defs[2] = &MobDef{
+ ID: 2,
+ Name: "Wolf",
+ Level: 2,
+ HP: 80,
+ MP: 0,
+ Str: 12,
+ Dex: 10,
+ Int: 2,
+ MoveSpeed: 6.0,
+ AggroRange: 12.0,
+ AttackRange: 2.0,
+ AttackSkill: 1,
+ LeashRange: 35.0,
+ ExpReward: 35,
+ LootTable: []LootEntry{
+ {ItemID: 102, Quantity: 1, Chance: 0.4},
+ {ItemID: 103, Quantity: 1, Chance: 0.1},
+ },
+ }
+
+ r.defs[3] = &MobDef{
+ ID: 3,
+ Name: "Forest Troll",
+ Level: 5,
+ HP: 200,
+ MP: 30,
+ Str: 25,
+ Dex: 8,
+ Int: 5,
+ MoveSpeed: 3.0,
+ AggroRange: 8.0,
+ AttackRange: 3.0,
+ AttackSkill: 1,
+ LeashRange: 25.0,
+ ExpReward: 80,
+ LootTable: []LootEntry{
+ {ItemID: 104, Quantity: 1, Chance: 0.6},
+ {ItemID: 105, Quantity: 1, Chance: 0.15},
+ },
+ }
+
+ r.defs[4] = &MobDef{
+ ID: 4,
+ Name: "Fire Elemental",
+ Level: 8,
+ HP: 350,
+ MP: 100,
+ Str: 15,
+ Dex: 12,
+ Int: 30,
+ MoveSpeed: 3.5,
+ AggroRange: 15.0,
+ AttackRange: 10.0,
+ AttackSkill: 2, // fireball
+ LeashRange: 40.0,
+ ExpReward: 150,
+ LootTable: []LootEntry{
+ {ItemID: 106, Quantity: 1, Chance: 0.3},
+ {ItemID: 107, Quantity: 1, Chance: 0.05},
+ },
+ }
+
+ r.defs[5] = &MobDef{
+ ID: 5,
+ Name: "Dragon Whelp",
+ Level: 12,
+ HP: 800,
+ MP: 200,
+ Str: 35,
+ Dex: 20,
+ Int: 25,
+ MoveSpeed: 5.0,
+ AggroRange: 20.0,
+ AttackRange: 4.0,
+ AttackSkill: 2,
+ LeashRange: 50.0,
+ ExpReward: 350,
+ LootTable: []LootEntry{
+ {ItemID: 108, Quantity: 1, Chance: 0.4},
+ {ItemID: 109, Quantity: 1, Chance: 0.02},
+ },
+ }
+
+ // Adjust respawn-related values (these go in SpawnPoints, not MobDef, but
+ // set sensible AttackSkill cooldowns via the existing combat skill system)
+ _ = time.Second // reference for documentation
+}
diff --git a/internal/ai/spawner.go b/internal/ai/spawner.go
new file mode 100644
index 0000000..db2aa32
--- /dev/null
+++ b/internal/ai/spawner.go
@@ -0,0 +1,152 @@
+package ai
+
+import (
+ "sync/atomic"
+ "time"
+
+ "a301_game_server/pkg/logger"
+ "a301_game_server/pkg/mathutil"
+)
+
+// SpawnPoint defines where and what to spawn.
+type SpawnPoint struct {
+ MobDef *MobDef
+ Position mathutil.Vec3
+ RespawnDelay time.Duration
+ MaxCount int // max alive at this point
+}
+
+// spawnEntry tracks a single spawned mob.
+type spawnEntry struct {
+ mob *Mob
+ alive bool
+ diedAt time.Time
+}
+
+// Spawner manages mob spawning and respawning for a zone.
+type Spawner struct {
+ points []SpawnPoint
+ mobs map[uint64]*spawnEntry // mobID -> entry
+ pointMobs map[int][]*spawnEntry // spawnPointIndex -> entries
+ nextID *atomic.Uint64
+
+ // Callbacks
+ onSpawn func(m *Mob)
+ onRemove func(mobID uint64)
+}
+
+// NewSpawner creates a mob spawner.
+func NewSpawner(nextID *atomic.Uint64, onSpawn func(*Mob), onRemove func(uint64)) *Spawner {
+ return &Spawner{
+ mobs: make(map[uint64]*spawnEntry),
+ pointMobs: make(map[int][]*spawnEntry),
+ nextID: nextID,
+ onSpawn: onSpawn,
+ onRemove: onRemove,
+ }
+}
+
+// AddSpawnPoint registers a spawn point.
+func (s *Spawner) AddSpawnPoint(sp SpawnPoint) {
+ s.points = append(s.points, sp)
+}
+
+// InitialSpawn spawns all mobs at startup.
+func (s *Spawner) InitialSpawn() {
+ for i, sp := range s.points {
+ for j := 0; j < sp.MaxCount; j++ {
+ s.spawnMob(i, &sp)
+ }
+ }
+ logger.Info("initial spawn complete", "totalMobs", len(s.mobs))
+}
+
+// Update checks for mobs that need respawning.
+func (s *Spawner) Update(now time.Time) {
+ for i, sp := range s.points {
+ entries := s.pointMobs[i]
+ aliveCount := 0
+ for _, e := range entries {
+ if e.alive {
+ aliveCount++
+ continue
+ }
+ // Check if it's time to respawn.
+ if !e.diedAt.IsZero() && now.Sub(e.diedAt) >= sp.RespawnDelay {
+ s.respawnMob(e)
+ aliveCount++
+ }
+ }
+ // Spawn new if below max count.
+ for aliveCount < sp.MaxCount {
+ s.spawnMob(i, &sp)
+ aliveCount++
+ }
+ }
+}
+
+// NotifyDeath marks a mob as dead and starts the respawn timer.
+func (s *Spawner) NotifyDeath(mobID uint64) {
+ entry, ok := s.mobs[mobID]
+ if !ok {
+ return
+ }
+ entry.alive = false
+ entry.diedAt = time.Now()
+
+ // Remove from zone (despawn).
+ if s.onRemove != nil {
+ s.onRemove(mobID)
+ }
+}
+
+// GetMob returns a mob by ID.
+func (s *Spawner) GetMob(id uint64) *Mob {
+ if e, ok := s.mobs[id]; ok {
+ return e.mob
+ }
+ return nil
+}
+
+// AllMobs returns all tracked mobs.
+func (s *Spawner) AllMobs() []*Mob {
+ result := make([]*Mob, 0, len(s.mobs))
+ for _, e := range s.mobs {
+ result = append(result, e.mob)
+ }
+ return result
+}
+
+// AliveMobs returns all alive mobs.
+func (s *Spawner) AliveMobs() []*Mob {
+ var result []*Mob
+ for _, e := range s.mobs {
+ if e.alive {
+ result = append(result, e.mob)
+ }
+ }
+ return result
+}
+
+func (s *Spawner) spawnMob(pointIdx int, sp *SpawnPoint) {
+ id := s.nextID.Add(1) + 100000 // offset to avoid collision with player IDs
+ mob := NewMob(id, sp.MobDef, sp.Position)
+
+ entry := &spawnEntry{mob: mob, alive: true}
+ s.mobs[id] = entry
+ s.pointMobs[pointIdx] = append(s.pointMobs[pointIdx], entry)
+
+ if s.onSpawn != nil {
+ s.onSpawn(mob)
+ }
+}
+
+func (s *Spawner) respawnMob(entry *spawnEntry) {
+ entry.mob.Reset()
+ entry.alive = true
+ entry.diedAt = time.Time{}
+
+ if s.onSpawn != nil {
+ s.onSpawn(entry.mob)
+ }
+}
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
new file mode 100644
index 0000000..d423857
--- /dev/null
+++ b/internal/auth/auth.go
@@ -0,0 +1,68 @@
+package auth
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "golang.org/x/crypto/bcrypt"
+
+ "a301_game_server/internal/db"
+)
+
+var (
+ ErrInvalidCredentials = errors.New("invalid credentials")
+ ErrUsernameTaken = errors.New("username already taken")
+)
+
+// Service handles account registration and authentication.
+type Service struct {
+ pool *db.Pool
+}
+
+// NewService creates a new auth service.
+func NewService(pool *db.Pool) *Service {
+ return &Service{pool: pool}
+}
+
+// Register creates a new account. Returns the account ID.
+func (s *Service) Register(ctx context.Context, username, password string) (int64, error) {
+ hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ if err != nil {
+ return 0, fmt.Errorf("hash password: %w", err)
+ }
+
+ var id int64
+ err = s.pool.QueryRow(ctx,
+ `INSERT INTO accounts (username, password) VALUES ($1, $2)
+ ON CONFLICT (username) DO NOTHING
+ RETURNING id`,
+ username, string(hash),
+ ).Scan(&id)
+
+ if err != nil {
+ return 0, ErrUsernameTaken
+ }
+
+ return id, nil
+}
+
+// Login validates credentials and returns the account ID.
+func (s *Service) Login(ctx context.Context, username, password string) (int64, error) {
+ var id int64
+ var hash string
+
+ err := s.pool.QueryRow(ctx,
+ `SELECT id, password FROM accounts WHERE username = $1`, username,
+ ).Scan(&id, &hash)
+
+ if err != nil {
+ return 0, ErrInvalidCredentials
+ }
+
+ if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil {
+ return 0, ErrInvalidCredentials
+ }
+
+ return id, nil
+}
diff --git a/internal/combat/buff.go b/internal/combat/buff.go
new file mode 100644
index 0000000..25f86b8
--- /dev/null
+++ b/internal/combat/buff.go
@@ -0,0 +1,86 @@
+package combat
+
+import "time"
+
+// BuffDef defines a buff/debuff type.
+type BuffDef struct {
+ ID uint32
+ Name string
+ IsDebuff bool
+ DamagePerTick int32 // for DoTs (debuff); heal per tick for HoTs (buff)
+ StatModifier StatMod
+}
+
+// StatMod is a temporary stat modification from a buff.
+type StatMod struct {
+ StrBonus int32
+ DexBonus int32
+ IntBonus int32
+}
+
+// ActiveBuff is an active buff/debuff on an entity.
+type ActiveBuff struct {
+ Def *BuffDef
+ CasterID uint64
+ Remaining time.Duration
+ TickInterval time.Duration
+ NextTick time.Duration // time until next tick
+}
+
+// Tick advances the buff by dt. Returns damage/heal to apply this tick (0 if no tick).
+func (b *ActiveBuff) Tick(dt time.Duration) int32 {
+ b.Remaining -= dt
+ var tickValue int32
+
+ if b.TickInterval > 0 {
+ b.NextTick -= dt
+ if b.NextTick <= 0 {
+ tickValue = b.Def.DamagePerTick
+ b.NextTick += b.TickInterval
+ }
+ }
+
+ return tickValue
+}
+
+// IsExpired returns true if the buff has no remaining duration.
+func (b *ActiveBuff) IsExpired() bool {
+ return b.Remaining <= 0
+}
+
+// BuffRegistry holds all buff/debuff definitions.
+type BuffRegistry struct {
+ buffs map[uint32]*BuffDef
+}
+
+// NewBuffRegistry creates a registry with default buffs.
+func NewBuffRegistry() *BuffRegistry {
+ r := &BuffRegistry{buffs: make(map[uint32]*BuffDef)}
+ r.registerDefaults()
+ return r
+}
+
+// Get returns a buff definition.
+func (r *BuffRegistry) Get(id uint32) *BuffDef {
+ return r.buffs[id]
+}
+
+func (r *BuffRegistry) registerDefaults() {
+ // Poison DoT (referenced by skill ID 5, effect Value=1)
+ r.buffs[1] = &BuffDef{
+ ID: 1,
+ Name: "Poison",
+ IsDebuff: true,
+ DamagePerTick: 8,
+ }
+
+ // Power Up buff (referenced by skill ID 6, effect Value=2)
+ r.buffs[2] = &BuffDef{
+ ID: 2,
+ Name: "Power Up",
+ IsDebuff: false,
+ StatModifier: StatMod{
+ StrBonus: 20,
+ },
+ }
+}
diff --git a/internal/combat/combat_manager.go b/internal/combat/combat_manager.go
new file mode 100644
index 0000000..34acb80
--- /dev/null
+++ b/internal/combat/combat_manager.go
@@ -0,0 +1,368 @@
+package combat
+
+import (
+ "time"
+
+ "a301_game_server/internal/entity"
+ "a301_game_server/internal/network"
+ "a301_game_server/pkg/mathutil"
+ pb "a301_game_server/proto/gen/pb"
+)
+
+// Combatant is an entity that can participate in combat.
+type Combatant interface {
+ entity.Entity
+ HP() int32
+ SetHP(int32)
+ MaxHP() int32
+ MP() int32
+ SetMP(int32)
+ IsAlive() bool
+ Stats() CombatStats
+}
+
+// CombatStats provides stat access for damage calculation.
+type CombatStats struct {
+ Str int32
+ Dex int32
+ Int int32
+ Level int32
+}
+
+// CombatantWithConn is a combatant that can receive messages (player).
+type CombatantWithConn interface {
+ Combatant
+ Connection() *network.Connection
+}
+
+// Manager handles all combat logic for a zone.
+type Manager struct {
+ skills *SkillRegistry
+ buffs *BuffRegistry
+
+ // Per-entity state
+ cooldowns map[uint64]map[uint32]time.Time // entityID -> skillID -> ready time
+ activeBuffs map[uint64][]*ActiveBuff // entityID -> active buffs
+
+ // Broadcast function (set by zone to send to AOI)
+ broadcastToNearby func(ent entity.Entity, msgType uint16, msg interface{})
+ sendToEntity func(entityID uint64, msgType uint16, msg interface{})
+}
+
+// NewManager creates a combat manager.
+func NewManager() *Manager {
+ return &Manager{
+ skills: NewSkillRegistry(),
+ buffs: NewBuffRegistry(),
+ cooldowns: make(map[uint64]map[uint32]time.Time),
+ activeBuffs: make(map[uint64][]*ActiveBuff),
+ }
+}
+
+// Skills returns the skill registry.
+func (m *Manager) Skills() *SkillRegistry { return m.skills }
+
+// SetBroadcast configures the broadcast callback.
+func (m *Manager) SetBroadcast(
+ broadcast func(ent entity.Entity, msgType uint16, msg interface{}),
+ send func(entityID uint64, msgType uint16, msg interface{}),
+) {
+ m.broadcastToNearby = broadcast
+ m.sendToEntity = send
+}
+
+// UseSkill attempts to execute a skill.
+func (m *Manager) UseSkill(
+ caster Combatant,
+ skillID uint32,
+ targetID uint64,
+ targetPos mathutil.Vec3,
+ getEntity func(uint64) entity.Entity,
+ getEntitiesInRadius func(center mathutil.Vec3, radius float32) []entity.Entity,
+) (bool, string) {
+
+ if !caster.IsAlive() {
+ return false, "you are dead"
+ }
+
+ skill := m.skills.Get(skillID)
+ if skill == nil {
+ return false, "unknown skill"
+ }
+
+ // Cooldown check.
+ if cd, ok := m.cooldowns[caster.EntityID()]; ok {
+ if readyAt, ok := cd[skillID]; ok && time.Now().Before(readyAt) {
+ return false, "skill on cooldown"
+ }
+ }
+
+ // Mana check.
+ if caster.MP() < skill.ManaCost {
+ return false, "not enough mana"
+ }
+
+ // Resolve targets.
+ var targets []Combatant
+
+ switch skill.TargetType {
+ case TargetSelf:
+ targets = []Combatant{caster}
+
+ case TargetSingleEnemy:
+ ent := getEntity(targetID)
+ if ent == nil {
+ return false, "target not found"
+ }
+ target, ok := ent.(Combatant)
+ if !ok || !target.IsAlive() {
+ return false, "invalid target"
+ }
+ if caster.Position().DistanceXZ(target.Position()) > skill.Range {
+ return false, "target out of range"
+ }
+ targets = []Combatant{target}
+
+ case TargetSingleAlly:
+ ent := getEntity(targetID)
+ if ent == nil {
+ return false, "target not found"
+ }
+ target, ok := ent.(Combatant)
+ if !ok || !target.IsAlive() {
+ return false, "invalid target"
+ }
+ if caster.Position().DistanceXZ(target.Position()) > skill.Range {
+ return false, "target out of range"
+ }
+ targets = []Combatant{target}
+
+ case TargetAoEGround:
+ if caster.Position().DistanceXZ(targetPos) > skill.Range {
+ return false, "target position out of range"
+ }
+ entities := getEntitiesInRadius(targetPos, skill.AoERadius)
+ for _, e := range entities {
+ if c, ok := e.(Combatant); ok && c.IsAlive() && c.EntityID() != caster.EntityID() {
+ targets = append(targets, c)
+ }
+ }
+
+ case TargetAoETarget:
+ ent := getEntity(targetID)
+ if ent == nil {
+ return false, "target not found"
+ }
+ if caster.Position().DistanceXZ(ent.Position()) > skill.Range {
+ return false, "target out of range"
+ }
+ entities := getEntitiesInRadius(ent.Position(), skill.AoERadius)
+ for _, e := range entities {
+ if c, ok := e.(Combatant); ok && c.IsAlive() && c.EntityID() != caster.EntityID() {
+ targets = append(targets, c)
+ }
+ }
+ }
+
+ // Consume mana.
+ caster.SetMP(caster.MP() - skill.ManaCost)
+
+ // Set cooldown.
+ if m.cooldowns[caster.EntityID()] == nil {
+ m.cooldowns[caster.EntityID()] = make(map[uint32]time.Time)
+ }
+ m.cooldowns[caster.EntityID()][skillID] = time.Now().Add(skill.Cooldown)
+
+ // Calculate effective stats (base + buff modifiers).
+ casterStats := m.effectiveStats(caster)
+
+ // Apply effects to each target.
+ for _, target := range targets {
+ m.applyEffects(caster, target, skill, casterStats)
+ }
+
+ return true, ""
+}
+
+func (m *Manager) applyEffects(caster, target Combatant, skill *SkillDef, casterStats CombatStats) {
+ for _, effect := range skill.Effects {
+ switch effect.Type {
+ case EffectDamage:
+ targetStats := m.effectiveStats(target)
+ result := CalcDamage(effect.Value, casterStats.Str, targetStats.Dex)
+ target.SetHP(target.HP() - result.FinalDamage)
+
+ died := !target.IsAlive()
+
+ evt := &pb.CombatEvent{
+ CasterId: caster.EntityID(),
+ TargetId: target.EntityID(),
+ SkillId: skill.ID,
+ Damage: result.FinalDamage,
+ IsCritical: result.IsCritical,
+ TargetDied: died,
+ TargetHp: target.HP(),
+ TargetMaxHp: target.MaxHP(),
+ EventType: pb.CombatEventType_COMBAT_EVENT_DAMAGE,
+ }
+
+ if m.broadcastToNearby != nil {
+ m.broadcastToNearby(target, network.MsgCombatEvent, evt)
+ }
+
+ if died {
+ deathEvt := &pb.CombatEvent{
+ CasterId: caster.EntityID(),
+ TargetId: target.EntityID(),
+ EventType: pb.CombatEventType_COMBAT_EVENT_DEATH,
+ }
+ if m.broadcastToNearby != nil {
+ m.broadcastToNearby(target, network.MsgCombatEvent, deathEvt)
+ }
+ }
+
+ case EffectHeal:
+ heal := CalcHeal(effect.Value, casterStats.Int)
+ target.SetHP(target.HP() + heal)
+
+ evt := &pb.CombatEvent{
+ CasterId: caster.EntityID(),
+ TargetId: target.EntityID(),
+ SkillId: skill.ID,
+ Heal: heal,
+ TargetHp: target.HP(),
+ TargetMaxHp: target.MaxHP(),
+ EventType: pb.CombatEventType_COMBAT_EVENT_HEAL,
+ }
+ if m.broadcastToNearby != nil {
+ m.broadcastToNearby(target, network.MsgCombatEvent, evt)
+ }
+
+ case EffectBuff, EffectDebuff:
+ buffDef := m.buffs.Get(uint32(effect.Value))
+ if buffDef == nil {
+ continue
+ }
+ ab := &ActiveBuff{
+ Def: buffDef,
+ CasterID: caster.EntityID(),
+ Remaining: effect.Duration,
+ TickInterval: effect.TickInterval,
+ NextTick: effect.TickInterval,
+ }
+ m.activeBuffs[target.EntityID()] = append(m.activeBuffs[target.EntityID()], ab)
+
+ evt := &pb.BuffApplied{
+ TargetId: target.EntityID(),
+ BuffId: buffDef.ID,
+ BuffName: buffDef.Name,
+ Duration: float32(effect.Duration.Seconds()),
+ IsDebuff: buffDef.IsDebuff,
+ }
+ if m.broadcastToNearby != nil {
+ m.broadcastToNearby(target, network.MsgBuffApplied, evt)
+ }
+ }
+ }
+}
+
+// UpdateBuffs processes active buffs each tick. Call once per zone tick.
+func (m *Manager) UpdateBuffs(dt time.Duration, getEntity func(uint64) Combatant) {
+ for entityID, buffs := range m.activeBuffs {
+ target := getEntity(entityID)
+ if target == nil {
+ delete(m.activeBuffs, entityID)
+ continue
+ }
+
+ var remaining []*ActiveBuff
+ for _, b := range buffs {
+ tickValue := b.Tick(dt)
+
+ if tickValue != 0 && target.IsAlive() {
+ if b.Def.IsDebuff {
+ // DoT damage.
+ target.SetHP(target.HP() - tickValue)
+ evt := &pb.CombatEvent{
+ CasterId: b.CasterID,
+ TargetId: entityID,
+ Damage: tickValue,
+ TargetHp: target.HP(),
+ TargetMaxHp: target.MaxHP(),
+ EventType: pb.CombatEventType_COMBAT_EVENT_DAMAGE,
+ }
+ if m.broadcastToNearby != nil {
+ m.broadcastToNearby(target, network.MsgCombatEvent, evt)
+ }
+ } else {
+ // HoT heal.
+ target.SetHP(target.HP() + tickValue)
+ evt := &pb.CombatEvent{
+ CasterId: b.CasterID,
+ TargetId: entityID,
+ Heal: tickValue,
+ TargetHp: target.HP(),
+ TargetMaxHp: target.MaxHP(),
+ EventType: pb.CombatEventType_COMBAT_EVENT_HEAL,
+ }
+ if m.broadcastToNearby != nil {
+ m.broadcastToNearby(target, network.MsgCombatEvent, evt)
+ }
+ }
+ }
+
+ if !b.IsExpired() {
+ remaining = append(remaining, b)
+ } else {
+ // Notify buff removed.
+ evt := &pb.BuffRemoved{
+ TargetId: entityID,
+ BuffId: b.Def.ID,
+ }
+ if m.broadcastToNearby != nil {
+ m.broadcastToNearby(target, network.MsgBuffRemoved, evt)
+ }
+ }
+ }
+
+ if len(remaining) == 0 {
+ delete(m.activeBuffs, entityID)
+ } else {
+ m.activeBuffs[entityID] = remaining
+ }
+ }
+}
+
+// Respawn resets a dead entity to full HP at a spawn position.
+func (m *Manager) Respawn(ent Combatant, spawnPos mathutil.Vec3) {
+ ent.SetHP(ent.MaxHP())
+ ent.SetPosition(spawnPos)
+ m.clearCooldowns(ent.EntityID())
+ m.clearBuffs(ent.EntityID())
+}
+
+// RemoveEntity cleans up combat state for a removed entity.
+func (m *Manager) RemoveEntity(entityID uint64) {
+ m.clearCooldowns(entityID)
+ m.clearBuffs(entityID)
+}
+
+// effectiveStats returns stats with buff modifiers applied.
+func (m *Manager) effectiveStats(c Combatant) CombatStats {
+ base := c.Stats()
+ buffs := m.activeBuffs[c.EntityID()]
+ for _, b := range buffs {
+ base.Str += b.Def.StatModifier.StrBonus
+ base.Dex += b.Def.StatModifier.DexBonus
+ base.Int += b.Def.StatModifier.IntBonus
+ }
+ return base
+}
+
+func (m *Manager) clearCooldowns(entityID uint64) {
+ delete(m.cooldowns, entityID)
+}
+
+func (m *Manager) clearBuffs(entityID uint64) {
+ delete(m.activeBuffs, entityID)
+}
diff --git a/internal/combat/combat_test.go b/internal/combat/combat_test.go
new file mode 100644
index 0000000..8779447
--- /dev/null
+++ b/internal/combat/combat_test.go
@@ -0,0 +1,239 @@
+package combat
+
+import (
+ "testing"
+ "time"
+
+ "a301_game_server/internal/entity"
+ "a301_game_server/pkg/mathutil"
+ pb "a301_game_server/proto/gen/pb"
+)
+
+// mockCombatant implements Combatant for testing.
+type mockCombatant struct {
+ id uint64
+ pos mathutil.Vec3
+ hp, maxHP int32
+ mp, maxMP int32
+ str, dex, intStat int32
+}
+
+func (m *mockCombatant) EntityID() uint64 { return m.id }
+func (m *mockCombatant) EntityType() entity.Type { return entity.TypePlayer }
+func (m *mockCombatant) Position() mathutil.Vec3 { return m.pos }
+func (m *mockCombatant) SetPosition(p mathutil.Vec3) { m.pos = p }
+func (m *mockCombatant) Rotation() float32 { return 0 }
+func (m *mockCombatant) SetRotation(float32) {}
+func (m *mockCombatant) ToProto() *pb.EntityState { return &pb.EntityState{EntityId: m.id} }
+func (m *mockCombatant) HP() int32 { return m.hp }
+func (m *mockCombatant) SetHP(hp int32) {
+ if hp < 0 { hp = 0 }
+ if hp > m.maxHP { hp = m.maxHP }
+ m.hp = hp
+}
+func (m *mockCombatant) MaxHP() int32 { return m.maxHP }
+func (m *mockCombatant) MP() int32 { return m.mp }
+func (m *mockCombatant) SetMP(mp int32) {
+ if mp < 0 { mp = 0 }
+ m.mp = mp
+}
+func (m *mockCombatant) IsAlive() bool { return m.hp > 0 }
+func (m *mockCombatant) Stats() CombatStats {
+ return CombatStats{Str: m.str, Dex: m.dex, Int: m.intStat, Level: 1}
+}
+
+func newMock(id uint64, x, z float32) *mockCombatant {
+ return &mockCombatant{
+ id: id, pos: mathutil.NewVec3(x, 0, z),
+ hp: 100, maxHP: 100, mp: 100, maxMP: 100,
+ str: 10, dex: 10, intStat: 10,
+ }
+}
+
+func TestBasicAttack(t *testing.T) {
+ mgr := NewManager()
+ mgr.SetBroadcast(func(entity.Entity, uint16, interface{}) {}, func(uint64, uint16, interface{}) {})
+
+ attacker := newMock(1, 0, 0)
+ target := newMock(2, 2, 0) // within range (3.0)
+
+ entities := map[uint64]*mockCombatant{1: attacker, 2: target}
+
+ ok, errMsg := mgr.UseSkill(attacker, 1, 2, mathutil.Vec3{},
+ func(id uint64) entity.Entity {
+ if e, ok := entities[id]; ok { return e }
+ return nil
+ },
+ nil,
+ )
+
+ if !ok {
+ t.Fatalf("expected success, got error: %s", errMsg)
+ }
+
+ if target.HP() >= 100 {
+ t.Error("target should have taken damage")
+ }
+}
+
+func TestOutOfRange(t *testing.T) {
+ mgr := NewManager()
+ mgr.SetBroadcast(func(entity.Entity, uint16, interface{}) {}, func(uint64, uint16, interface{}) {})
+
+ attacker := newMock(1, 0, 0)
+ target := newMock(2, 100, 0) // far away
+
+ entities := map[uint64]*mockCombatant{1: attacker, 2: target}
+
+ ok, _ := mgr.UseSkill(attacker, 1, 2, mathutil.Vec3{},
+ func(id uint64) entity.Entity {
+ if e, ok := entities[id]; ok { return e }
+ return nil
+ },
+ nil,
+ )
+
+ if ok {
+ t.Error("expected failure due to range")
+ }
+}
+
+func TestCooldown(t *testing.T) {
+ mgr := NewManager()
+ mgr.SetBroadcast(func(entity.Entity, uint16, interface{}) {}, func(uint64, uint16, interface{}) {})
+
+ attacker := newMock(1, 0, 0)
+ target := newMock(2, 2, 0)
+
+ entities := map[uint64]*mockCombatant{1: attacker, 2: target}
+ getEnt := func(id uint64) entity.Entity {
+ if e, ok := entities[id]; ok { return e }
+ return nil
+ }
+
+ // First use should succeed.
+ ok, _ := mgr.UseSkill(attacker, 1, 2, mathutil.Vec3{}, getEnt, nil)
+ if !ok {
+ t.Fatal("first use should succeed")
+ }
+
+ // Immediate second use should fail (cooldown).
+ ok, errMsg := mgr.UseSkill(attacker, 1, 2, mathutil.Vec3{}, getEnt, nil)
+ if ok {
+ t.Error("expected cooldown failure")
+ }
+ if errMsg != "skill on cooldown" {
+ t.Errorf("expected 'skill on cooldown', got '%s'", errMsg)
+ }
+}
+
+func TestManaConsumption(t *testing.T) {
+ mgr := NewManager()
+ mgr.SetBroadcast(func(entity.Entity, uint16, interface{}) {}, func(uint64, uint16, interface{}) {})
+
+ caster := newMock(1, 0, 0)
+ target := newMock(2, 5, 0)
+ caster.mp = 10 // not enough for Fireball (20 mana)
+
+ entities := map[uint64]*mockCombatant{1: caster, 2: target}
+
+ ok, errMsg := mgr.UseSkill(caster, 2, 2, mathutil.Vec3{},
+ func(id uint64) entity.Entity {
+ if e, ok := entities[id]; ok { return e }
+ return nil
+ },
+ nil,
+ )
+
+ if ok {
+ t.Error("expected mana failure")
+ }
+ if errMsg != "not enough mana" {
+ t.Errorf("expected 'not enough mana', got '%s'", errMsg)
+ }
+}
+
+func TestHealSelf(t *testing.T) {
+ mgr := NewManager()
+ mgr.SetBroadcast(func(entity.Entity, uint16, interface{}) {}, func(uint64, uint16, interface{}) {})
+
+ caster := newMock(1, 0, 0)
+ caster.hp = 30
+
+ ok, _ := mgr.UseSkill(caster, 3, 0, mathutil.Vec3{},
+ func(id uint64) entity.Entity { return nil },
+ nil,
+ )
+
+ if !ok {
+ t.Fatal("heal should succeed")
+ }
+ if caster.HP() <= 30 {
+ t.Error("HP should have increased")
+ }
+}
+
+func TestPoisonDoT(t *testing.T) {
+ mgr := NewManager()
+ mgr.SetBroadcast(func(entity.Entity, uint16, interface{}) {}, func(uint64, uint16, interface{}) {})
+
+ attacker := newMock(1, 0, 0)
+ target := newMock(2, 5, 0)
+
+ entities := map[uint64]*mockCombatant{1: attacker, 2: target}
+
+ // Apply poison (skill 5).
+ ok, _ := mgr.UseSkill(attacker, 5, 2, mathutil.Vec3{},
+ func(id uint64) entity.Entity {
+ if e, ok := entities[id]; ok { return e }
+ return nil
+ },
+ nil,
+ )
+ if !ok {
+ t.Fatal("poison should succeed")
+ }
+
+ initialHP := target.HP()
+
+ // Simulate ticks until first DoT tick (2s at 50ms/tick = 40 ticks).
+ for i := 0; i < 40; i++ {
+ mgr.UpdateBuffs(50*time.Millisecond, func(id uint64) Combatant {
+ if e, ok := entities[id]; ok { return e }
+ return nil
+ })
+ }
+
+ if target.HP() >= initialHP {
+ t.Error("poison DoT should have dealt damage")
+ }
+}
+
+func TestDamageFormula(t *testing.T) {
+ // Base 15, attacker STR 10 (1.1x), defender DEX 10 (0.95x)
+ // Expected ~15.675 (without crit).
+ result := CalcDamage(15, 10, 10)
+ if result.FinalDamage < 1 {
+ t.Error("damage should be at least 1")
+ }
+}
+
+func TestRespawn(t *testing.T) {
+ mgr := NewManager()
+
+ ent := newMock(1, 50, 50)
+ ent.hp = 0
+
+ spawn := mathutil.NewVec3(0, 0, 0)
+ mgr.Respawn(ent, spawn)
+
+ if !ent.IsAlive() {
+ t.Error("should be alive after respawn")
+ }
+ if ent.HP() != ent.MaxHP() {
+ t.Error("should have full HP after respawn")
+ }
+ if ent.Position() != spawn {
+ t.Error("should be at spawn position")
+ }
+}
diff --git a/internal/combat/damage.go b/internal/combat/damage.go
new file mode 100644
index 0000000..71bf548
--- /dev/null
+++ b/internal/combat/damage.go
@@ -0,0 +1,51 @@
+package combat
+
+import (
+ "math/rand"
+)
+
+const (
+ critChance = 0.15 // 15% crit chance
+ critMultiplier = 1.5
+)
+
+// DamageResult holds the outcome of a damage calculation.
+type DamageResult struct {
+ FinalDamage int32
+ IsCritical bool
+}
+
+// CalcDamage computes final damage from base damage and attacker/defender stats.
+// Formula: base * (1 + attackerStr/100) * (1 - defenderDex/200)
+// Then roll for crit.
+func CalcDamage(baseDamage int32, attackerStr, defenderDex int32) DamageResult {
+ attack := float64(baseDamage) * (1.0 + float64(attackerStr)/100.0)
+ defense := 1.0 - float64(defenderDex)/200.0
+ if defense < 0.1 {
+ defense = 0.1 // minimum 10% damage
+ }
+
+ dmg := attack * defense
+ isCrit := rand.Float64() < critChance
+ if isCrit {
+ dmg *= critMultiplier
+ }
+
+ final := int32(dmg)
+ if final < 1 {
+ final = 1
+ }
+
+ return DamageResult{FinalDamage: final, IsCritical: isCrit}
+}
+
+// CalcHeal computes final healing.
+// Formula: base * (1 + casterInt/100)
+func CalcHeal(baseHeal int32, casterInt int32) int32 {
+ heal := float64(baseHeal) * (1.0 + float64(casterInt)/100.0)
+ result := int32(heal)
+ if result < 1 {
+ result = 1
+ }
+ return result
+}
diff --git a/internal/combat/skill.go b/internal/combat/skill.go
new file mode 100644
index 0000000..8ab5bdc
--- /dev/null
+++ b/internal/combat/skill.go
@@ -0,0 +1,148 @@
+package combat
+
+import "time"
+
+// TargetType determines how a skill selects its target.
+type TargetType int
+
+const (
+ TargetSelf TargetType = iota
+ TargetSingleEnemy // requires a valid enemy entity ID
+ TargetSingleAlly // requires a valid ally entity ID
+ TargetAoEGround // requires a ground position
+ TargetAoETarget // AoE centered on target entity
+)
+
+// EffectType determines what a skill effect does.
+type EffectType int
+
+const (
+ EffectDamage EffectType = iota
+ EffectHeal
+ EffectBuff
+ EffectDebuff
+)
+
+// Effect is a single outcome of a skill.
+type Effect struct {
+ Type EffectType
+ Value int32 // damage amount, heal amount, or buff/debuff ID
+ Duration time.Duration // 0 for instant effects
+ TickInterval time.Duration // for DoT/HoT; 0 means apply once
+}
+
+// SkillDef defines a skill's properties (loaded from data).
+type SkillDef struct {
+ ID uint32
+ Name string
+ Cooldown time.Duration
+ ManaCost int32
+ Range float32 // max distance to target (0 = self only)
+ TargetType TargetType
+ AoERadius float32 // for AoE skills
+ CastTime time.Duration
+ Effects []Effect
+}
+
+// SkillRegistry holds all skill definitions.
+type SkillRegistry struct {
+ skills map[uint32]*SkillDef
+}
+
+// NewSkillRegistry creates a registry with default skills.
+func NewSkillRegistry() *SkillRegistry {
+ r := &SkillRegistry{skills: make(map[uint32]*SkillDef)}
+ r.registerDefaults()
+ return r
+}
+
+// Get returns a skill definition by ID.
+func (r *SkillRegistry) Get(id uint32) *SkillDef {
+ return r.skills[id]
+}
+
+// Register adds a skill definition.
+func (r *SkillRegistry) Register(s *SkillDef) {
+ r.skills[s.ID] = s
+}
+
+func (r *SkillRegistry) registerDefaults() {
+ // Basic attack
+ r.Register(&SkillDef{
+ ID: 1,
+ Name: "Basic Attack",
+ Cooldown: 1 * time.Second,
+ ManaCost: 0,
+ Range: 3.0,
+ TargetType: TargetSingleEnemy,
+ Effects: []Effect{
+ {Type: EffectDamage, Value: 15},
+ },
+ })
+
+ // Fireball - ranged damage
+ r.Register(&SkillDef{
+ ID: 2,
+ Name: "Fireball",
+ Cooldown: 3 * time.Second,
+ ManaCost: 20,
+ Range: 15.0,
+ TargetType: TargetSingleEnemy,
+ Effects: []Effect{
+ {Type: EffectDamage, Value: 40},
+ },
+ })
+
+ // Heal
+ r.Register(&SkillDef{
+ ID: 3,
+ Name: "Heal",
+ Cooldown: 5 * time.Second,
+ ManaCost: 30,
+ Range: 0,
+ TargetType: TargetSelf,
+ Effects: []Effect{
+ {Type: EffectHeal, Value: 50},
+ },
+ })
+
+ // AoE - Flame Strike
+ r.Register(&SkillDef{
+ ID: 4,
+ Name: "Flame Strike",
+ Cooldown: 8 * time.Second,
+ ManaCost: 40,
+ Range: 12.0,
+ TargetType: TargetAoEGround,
+ AoERadius: 5.0,
+ Effects: []Effect{
+ {Type: EffectDamage, Value: 30},
+ },
+ })
+
+ // Poison (DoT debuff)
+ r.Register(&SkillDef{
+ ID: 5,
+ Name: "Poison",
+ Cooldown: 6 * time.Second,
+ ManaCost: 15,
+ Range: 8.0,
+ TargetType: TargetSingleEnemy,
+ Effects: []Effect{
+ {Type: EffectDebuff, Value: 1, Duration: 10 * time.Second, TickInterval: 2 * time.Second},
+ },
+ })
+
+ // Power Buff (self buff - increases damage)
+ r.Register(&SkillDef{
+ ID: 6,
+ Name: "Power Up",
+ Cooldown: 15 * time.Second,
+ ManaCost: 25,
+ Range: 0,
+ TargetType: TargetSelf,
+ Effects: []Effect{
+ {Type: EffectBuff, Value: 2, Duration: 10 * time.Second},
+ },
+ })
+}
diff --git a/internal/db/migrations.go b/internal/db/migrations.go
new file mode 100644
index 0000000..818648e
--- /dev/null
+++ b/internal/db/migrations.go
@@ -0,0 +1,47 @@
+package db
+
+var migrations = []string{
+ // 0: accounts table
+ `CREATE TABLE IF NOT EXISTS accounts (
+ id BIGSERIAL PRIMARY KEY,
+ username VARCHAR(32) UNIQUE NOT NULL,
+ password VARCHAR(128) NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+ )`,
+
+ // 1: characters table
+ `CREATE TABLE IF NOT EXISTS characters (
+ id BIGSERIAL PRIMARY KEY,
+ account_id BIGINT NOT NULL REFERENCES accounts(id),
+ name VARCHAR(32) UNIQUE NOT NULL,
+ level INT NOT NULL DEFAULT 1,
+ exp BIGINT NOT NULL DEFAULT 0,
+ hp INT NOT NULL DEFAULT 100,
+ max_hp INT NOT NULL DEFAULT 100,
+ mp INT NOT NULL DEFAULT 50,
+ max_mp INT NOT NULL DEFAULT 50,
+ str INT NOT NULL DEFAULT 10,
+ dex INT NOT NULL DEFAULT 10,
+ int_stat INT NOT NULL DEFAULT 10,
+ zone_id INT NOT NULL DEFAULT 1,
+ pos_x REAL NOT NULL DEFAULT 0,
+ pos_y REAL NOT NULL DEFAULT 0,
+ pos_z REAL NOT NULL DEFAULT 0,
+ rotation REAL NOT NULL DEFAULT 0,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+ )`,
+
+ // 2: inventory table
+ `CREATE TABLE IF NOT EXISTS inventory (
+ id BIGSERIAL PRIMARY KEY,
+ character_id BIGINT NOT NULL REFERENCES characters(id) ON DELETE CASCADE,
+ slot INT NOT NULL,
+ item_id INT NOT NULL,
+ quantity INT NOT NULL DEFAULT 1,
+ UNIQUE(character_id, slot)
+ )`,
+
+ // 3: index for character lookups by account
+ `CREATE INDEX IF NOT EXISTS idx_characters_account_id ON characters(account_id)`,
+}
diff --git a/internal/db/postgres.go b/internal/db/postgres.go
new file mode 100644
index 0000000..d8b3ef3
--- /dev/null
+++ b/internal/db/postgres.go
@@ -0,0 +1,51 @@
+package db
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/jackc/pgx/v5/pgxpool"
+
+ "a301_game_server/config"
+ "a301_game_server/pkg/logger"
+)
+
+// Pool wraps a pgx connection pool.
+type Pool struct {
+ *pgxpool.Pool
+}
+
+// NewPool creates a connection pool to PostgreSQL.
+func NewPool(ctx context.Context, cfg *config.DatabaseConfig) (*Pool, error) {
+ poolCfg, err := pgxpool.ParseConfig(cfg.DSN())
+ if err != nil {
+ return nil, fmt.Errorf("parse dsn: %w", err)
+ }
+
+ poolCfg.MaxConns = cfg.MaxConns
+ poolCfg.MinConns = cfg.MinConns
+
+ pool, err := pgxpool.NewWithConfig(ctx, poolCfg)
+ if err != nil {
+ return nil, fmt.Errorf("create pool: %w", err)
+ }
+
+ if err := pool.Ping(ctx); err != nil {
+ pool.Close()
+ return nil, fmt.Errorf("ping database: %w", err)
+ }
+
+ logger.Info("database connected", "host", cfg.Host, "db", cfg.DBName)
+ return &Pool{pool}, nil
+}
+
+// RunMigrations executes all schema migrations.
+func (p *Pool) RunMigrations(ctx context.Context) error {
+ for i, m := range migrations {
+ if _, err := p.Exec(ctx, m); err != nil {
+ return fmt.Errorf("migration %d failed: %w", i, err)
+ }
+ }
+ logger.Info("database migrations completed", "count", len(migrations))
+ return nil
+}
diff --git a/internal/db/repository/character.go b/internal/db/repository/character.go
new file mode 100644
index 0000000..33007d2
--- /dev/null
+++ b/internal/db/repository/character.go
@@ -0,0 +1,166 @@
+package repository
+
+import (
+ "context"
+ "fmt"
+
+ "a301_game_server/internal/db"
+)
+
+// CharacterData holds persisted character state.
+type CharacterData struct {
+ ID int64
+ AccountID int64
+ Name string
+ Level int32
+ Exp int64
+ HP int32
+ MaxHP int32
+ MP int32
+ MaxMP int32
+ Str int32
+ Dex int32
+ IntStat int32
+ ZoneID int32
+ PosX float32
+ PosY float32
+ PosZ float32
+ Rotation float32
+}
+
+// CharacterRepo handles character persistence.
+type CharacterRepo struct {
+ pool *db.Pool
+}
+
+// NewCharacterRepo creates a new character repository.
+func NewCharacterRepo(pool *db.Pool) *CharacterRepo {
+ return &CharacterRepo{pool: pool}
+}
+
+// Create inserts a new character.
+func (r *CharacterRepo) Create(ctx context.Context, accountID int64, name string) (*CharacterData, error) {
+ c := &CharacterData{
+ AccountID: accountID,
+ Name: name,
+ Level: 1,
+ HP: 100,
+ MaxHP: 100,
+ MP: 50,
+ MaxMP: 50,
+ Str: 10,
+ Dex: 10,
+ IntStat: 10,
+ ZoneID: 1,
+ }
+
+ err := r.pool.QueryRow(ctx,
+ `INSERT INTO characters (account_id, name, level, hp, max_hp, mp, max_mp, str, dex, int_stat, zone_id)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
+ RETURNING id`,
+ c.AccountID, c.Name, c.Level, c.HP, c.MaxHP, c.MP, c.MaxMP, c.Str, c.Dex, c.IntStat, c.ZoneID,
+ ).Scan(&c.ID)
+
+ if err != nil {
+ return nil, fmt.Errorf("create character: %w", err)
+ }
+ return c, nil
+}
+
+// GetByAccountID returns all characters for an account.
+func (r *CharacterRepo) GetByAccountID(ctx context.Context, accountID int64) ([]*CharacterData, error) {
+ rows, err := r.pool.Query(ctx,
+ `SELECT id, account_id, name, level, exp, hp, max_hp, mp, max_mp, str, dex, int_stat,
+ zone_id, pos_x, pos_y, pos_z, rotation
+ FROM characters WHERE account_id = $1`, accountID,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("query characters: %w", err)
+ }
+ defer rows.Close()
+
+ var chars []*CharacterData
+ for rows.Next() {
+ c := &CharacterData{}
+ if err := rows.Scan(
+ &c.ID, &c.AccountID, &c.Name, &c.Level, &c.Exp,
+ &c.HP, &c.MaxHP, &c.MP, &c.MaxMP,
+ &c.Str, &c.Dex, &c.IntStat,
+ &c.ZoneID, &c.PosX, &c.PosY, &c.PosZ, &c.Rotation,
+ ); err != nil {
+ return nil, fmt.Errorf("scan character: %w", err)
+ }
+ chars = append(chars, c)
+ }
+ return chars, nil
+}
+
+// GetByID loads a single character.
+func (r *CharacterRepo) GetByID(ctx context.Context, id int64) (*CharacterData, error) {
+ c := &CharacterData{}
+ err := r.pool.QueryRow(ctx,
+ `SELECT id, account_id, name, level, exp, hp, max_hp, mp, max_mp, str, dex, int_stat,
+ zone_id, pos_x, pos_y, pos_z, rotation
+ FROM characters WHERE id = $1`, id,
+ ).Scan(
+ &c.ID, &c.AccountID, &c.Name, &c.Level, &c.Exp,
+ &c.HP, &c.MaxHP, &c.MP, &c.MaxMP,
+ &c.Str, &c.Dex, &c.IntStat,
+ &c.ZoneID, &c.PosX, &c.PosY, &c.PosZ, &c.Rotation,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("get character %d: %w", id, err)
+ }
+ return c, nil
+}
+
+// Save persists the current character state.
+func (r *CharacterRepo) Save(ctx context.Context, c *CharacterData) error {
+ _, err := r.pool.Exec(ctx,
+ `UPDATE characters SET
+ level = $2, exp = $3, hp = $4, max_hp = $5, mp = $6, max_mp = $7,
+ str = $8, dex = $9, int_stat = $10,
+ zone_id = $11, pos_x = $12, pos_y = $13, pos_z = $14, rotation = $15,
+ updated_at = NOW()
+ WHERE id = $1`,
+ c.ID, c.Level, c.Exp, c.HP, c.MaxHP, c.MP, c.MaxMP,
+ c.Str, c.Dex, c.IntStat,
+ c.ZoneID, c.PosX, c.PosY, c.PosZ, c.Rotation,
+ )
+ if err != nil {
+ return fmt.Errorf("save character %d: %w", c.ID, err)
+ }
+ return nil
+}
+
+// SaveBatch saves multiple characters in a single transaction.
+func (r *CharacterRepo) SaveBatch(ctx context.Context, chars []*CharacterData) error {
+ if len(chars) == 0 {
+ return nil
+ }
+
+ tx, err := r.pool.Begin(ctx)
+ if err != nil {
+ return fmt.Errorf("begin tx: %w", err)
+ }
+ defer tx.Rollback(ctx)
+
+ for _, c := range chars {
+ _, err := tx.Exec(ctx,
+ `UPDATE characters SET
+ level = $2, exp = $3, hp = $4, max_hp = $5, mp = $6, max_mp = $7,
+ str = $8, dex = $9, int_stat = $10,
+ zone_id = $11, pos_x = $12, pos_y = $13, pos_z = $14, rotation = $15,
+ updated_at = NOW()
+ WHERE id = $1`,
+ c.ID, c.Level, c.Exp, c.HP, c.MaxHP, c.MP, c.MaxMP,
+ c.Str, c.Dex, c.IntStat,
+ c.ZoneID, c.PosX, c.PosY, c.PosZ, c.Rotation,
+ )
+ if err != nil {
+ return fmt.Errorf("save character %d in batch: %w", c.ID, err)
+ }
+ }
+
+ return tx.Commit(ctx)
+}
diff --git a/internal/entity/entity.go b/internal/entity/entity.go
new file mode 100644
index 0000000..dd07fa9
--- /dev/null
+++ b/internal/entity/entity.go
@@ -0,0 +1,26 @@
+package entity
+
+import (
+ pb "a301_game_server/proto/gen/pb"
+ "a301_game_server/pkg/mathutil"
+)
+
+// Type identifies the kind of entity.
+type Type int
+
+const (
+ TypePlayer Type = iota
+ TypeMob
+ TypeNPC
+)
+
+// Entity is anything that exists in the game world.
+type Entity interface {
+ EntityID() uint64
+ EntityType() Type
+ Position() mathutil.Vec3
+ SetPosition(pos mathutil.Vec3)
+ Rotation() float32
+ SetRotation(rot float32)
+ ToProto() *pb.EntityState
+}
diff --git a/internal/game/game_server.go b/internal/game/game_server.go
new file mode 100644
index 0000000..88c08c7
--- /dev/null
+++ b/internal/game/game_server.go
@@ -0,0 +1,462 @@
+package game
+
+import (
+ "context"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "a301_game_server/config"
+ "a301_game_server/internal/ai"
+ "a301_game_server/internal/auth"
+ "a301_game_server/internal/db"
+ "a301_game_server/internal/db/repository"
+ "a301_game_server/internal/network"
+ "a301_game_server/internal/player"
+ "a301_game_server/internal/world"
+ "a301_game_server/pkg/logger"
+ "a301_game_server/pkg/mathutil"
+ pb "a301_game_server/proto/gen/pb"
+)
+
+const defaultZoneID uint32 = 1
+
+// GameServer is the top-level orchestrator that connects networking with game logic.
+type GameServer struct {
+ cfg *config.Config
+ world *World
+ sessions *player.SessionManager
+ dbPool *db.Pool
+ authSvc *auth.Service
+ charRepo *repository.CharacterRepo
+
+ mu sync.RWMutex
+ connPlayer map[uint64]*player.Player // connID -> player
+ playerConn map[uint64]uint64 // playerID -> connID
+
+ nextPlayerID atomic.Uint64
+ cancelSave context.CancelFunc
+}
+
+// NewGameServer creates the game server.
+func NewGameServer(cfg *config.Config, dbPool *db.Pool) *GameServer {
+ gs := &GameServer{
+ cfg: cfg,
+ world: NewWorld(cfg),
+ sessions: player.NewSessionManager(),
+ dbPool: dbPool,
+ authSvc: auth.NewService(dbPool),
+ charRepo: repository.NewCharacterRepo(dbPool),
+ connPlayer: make(map[uint64]*player.Player),
+ playerConn: make(map[uint64]uint64),
+ }
+
+ // Create zones, portals, and mobs.
+ gs.setupWorld()
+
+ return gs
+}
+
+// World returns the game world.
+func (gs *GameServer) World() *World { return gs.world }
+
+// Start launches all zone game loops and periodic save.
+func (gs *GameServer) Start() {
+ gs.world.mu.RLock()
+ for _, zone := range gs.world.zones {
+ zone.SetMessageHandler(gs)
+ zone.SetZoneTransferCallback(gs.handleZoneTransfer)
+ }
+ gs.world.mu.RUnlock()
+
+ gs.world.StartAll()
+
+ // Start periodic character save.
+ ctx, cancel := context.WithCancel(context.Background())
+ gs.cancelSave = cancel
+ go gs.periodicSave(ctx)
+}
+
+// Stop shuts down all zone game loops and saves all players.
+func (gs *GameServer) Stop() {
+ if gs.cancelSave != nil {
+ gs.cancelSave()
+ }
+
+ // Final save of all online players.
+ gs.saveAllPlayers()
+
+ gs.world.StopAll()
+}
+
+// OnPacket handles incoming packets from a connection.
+func (gs *GameServer) OnPacket(conn *network.Connection, pkt *network.Packet) {
+ switch pkt.Type {
+ case network.MsgLoginRequest:
+ gs.handleLogin(conn, pkt)
+ case network.MsgEnterWorldRequest:
+ gs.handleEnterWorld(conn, pkt)
+ default:
+ gs.mu.RLock()
+ p, ok := gs.connPlayer[conn.ID()]
+ gs.mu.RUnlock()
+ if !ok {
+ return
+ }
+
+ zone, err := gs.world.GetZone(p.ZoneID())
+ if err != nil {
+ return
+ }
+ zone.EnqueueMessage(PlayerMessage{PlayerID: p.EntityID(), Packet: pkt})
+ }
+}
+
+// OnDisconnect handles a connection closing.
+func (gs *GameServer) OnDisconnect(conn *network.Connection) {
+ gs.mu.Lock()
+ p, ok := gs.connPlayer[conn.ID()]
+ if !ok {
+ gs.mu.Unlock()
+ return
+ }
+ delete(gs.connPlayer, conn.ID())
+ delete(gs.playerConn, p.EntityID())
+ gs.mu.Unlock()
+
+ // Save character to DB on disconnect.
+ if p.CharID() != 0 {
+ if err := gs.charRepo.Save(context.Background(), p.ToCharacterData()); err != nil {
+ logger.Error("failed to save player on disconnect", "playerID", p.EntityID(), "error", err)
+ }
+ }
+
+ zone, err := gs.world.GetZone(p.ZoneID())
+ if err == nil {
+ zone.EnqueueMessage(PlayerMessage{
+ PlayerID: p.EntityID(),
+ Packet: &network.Packet{Type: msgPlayerDisconnect},
+ })
+ }
+
+ logger.Info("player disconnected", "connID", conn.ID(), "playerID", p.EntityID())
+}
+
+// Internal message types.
+const (
+ msgPlayerDisconnect uint16 = 0xFFFF
+ msgPlayerEnterWorld uint16 = 0xFFFE
+)
+
+// HandleZoneMessage implements ZoneMessageHandler.
+func (gs *GameServer) HandleZoneMessage(zone *Zone, msg PlayerMessage) bool {
+ switch msg.Packet.Type {
+ case msgPlayerDisconnect:
+ zone.RemovePlayer(msg.PlayerID)
+ return true
+ case msgPlayerEnterWorld:
+ gs.mu.RLock()
+ var found *player.Player
+ for _, p := range gs.connPlayer {
+ if p.EntityID() == msg.PlayerID {
+ found = p
+ break
+ }
+ }
+ gs.mu.RUnlock()
+ if found != nil {
+ zone.AddPlayer(found)
+ }
+ return true
+ default:
+ return false
+ }
+}
+
+func (gs *GameServer) handleLogin(conn *network.Connection, pkt *network.Packet) {
+ req := pkt.Payload.(*pb.LoginRequest)
+ ctx := context.Background()
+
+ // Try login first.
+ accountID, err := gs.authSvc.Login(ctx, req.Username, req.Password)
+ if err != nil {
+ // Auto-register if account doesn't exist.
+ accountID, err = gs.authSvc.Register(ctx, req.Username, req.Password)
+ if err != nil {
+ conn.Send(network.MsgLoginResponse, &pb.LoginResponse{
+ Success: false,
+ ErrorMessage: "login failed: " + err.Error(),
+ })
+ return
+ }
+
+ // Create default character on first registration.
+ _, charErr := gs.charRepo.Create(ctx, accountID, req.Username)
+ if charErr != nil {
+ logger.Error("failed to create default character", "accountID", accountID, "error", charErr)
+ }
+ }
+
+ session := gs.sessions.Create(uint64(accountID), req.Username)
+
+ conn.Send(network.MsgLoginResponse, &pb.LoginResponse{
+ Success: true,
+ SessionToken: session.Token,
+ PlayerId: uint64(accountID),
+ })
+
+ logger.Info("player logged in", "username", req.Username, "accountID", accountID)
+}
+
+func (gs *GameServer) handleEnterWorld(conn *network.Connection, pkt *network.Packet) {
+ req := pkt.Payload.(*pb.EnterWorldRequest)
+
+ session := gs.sessions.Get(req.SessionToken)
+ if session == nil {
+ conn.Send(network.MsgEnterWorldResponse, &pb.EnterWorldResponse{
+ Success: false,
+ ErrorMessage: "invalid session",
+ })
+ return
+ }
+
+ ctx := context.Background()
+
+ // Load character from DB.
+ chars, err := gs.charRepo.GetByAccountID(ctx, int64(session.PlayerID))
+ if err != nil || len(chars) == 0 {
+ conn.Send(network.MsgEnterWorldResponse, &pb.EnterWorldResponse{
+ Success: false,
+ ErrorMessage: "no character found",
+ })
+ return
+ }
+
+ charData := chars[0] // Use first character for now.
+ p := player.NewPlayerFromDB(charData, conn)
+
+ // Register connection-player mapping.
+ gs.mu.Lock()
+ gs.connPlayer[conn.ID()] = p
+ gs.playerConn[p.EntityID()] = conn.ID()
+ gs.mu.Unlock()
+
+ zoneID := p.ZoneID()
+ zone, err := gs.world.GetZone(zoneID)
+ if err != nil {
+ // Fall back to default zone.
+ zoneID = defaultZoneID
+ p.SetZoneID(defaultZoneID)
+ zone, _ = gs.world.GetZone(defaultZoneID)
+ }
+
+ zone.EnqueueMessage(PlayerMessage{
+ PlayerID: p.EntityID(),
+ Packet: &network.Packet{Type: msgPlayerEnterWorld},
+ })
+
+ conn.Send(network.MsgEnterWorldResponse, &pb.EnterWorldResponse{
+ Success: true,
+ Self: p.ToProto(),
+ ZoneId: zoneID,
+ })
+
+ logger.Info("player entered world", "playerID", p.EntityID(), "charID", charData.ID, "zone", zoneID)
+}
+
+// periodicSave saves all dirty player data to DB at configured intervals.
+func (gs *GameServer) periodicSave(ctx context.Context) {
+ ticker := time.NewTicker(gs.cfg.Database.SaveInterval)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ticker.C:
+ gs.saveAllPlayers()
+ case <-ctx.Done():
+ return
+ }
+ }
+}
+
+func (gs *GameServer) saveAllPlayers() {
+ gs.mu.RLock()
+ var dirty []*player.Player
+ for _, p := range gs.connPlayer {
+ if p.IsDirty() && p.CharID() != 0 {
+ dirty = append(dirty, p)
+ }
+ }
+ gs.mu.RUnlock()
+
+ if len(dirty) == 0 {
+ return
+ }
+
+ chars := make([]*repository.CharacterData, 0, len(dirty))
+ for _, p := range dirty {
+ chars = append(chars, p.ToCharacterData())
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ if err := gs.charRepo.SaveBatch(ctx, chars); err != nil {
+ logger.Error("periodic save failed", "count", len(chars), "error", err)
+ return
+ }
+
+ for _, p := range dirty {
+ p.ClearDirty()
+ }
+
+ logger.Debug("periodic save completed", "count", len(chars))
+}
+
+// setupWorld creates all zones, portals, and mob spawn points.
+func (gs *GameServer) setupWorld() {
+ zone1 := gs.world.CreateZone(1) // Starting zone - plains
+ zone2 := gs.world.CreateZone(2) // Forest zone - medium difficulty
+ zone3 := gs.world.CreateZone(3) // Volcano zone - hard
+
+ // Set spawn positions.
+ zone1.spawnPos = mathutil.NewVec3(0, 0, 0)
+ zone2.spawnPos = mathutil.NewVec3(5, 0, 5)
+ zone3.spawnPos = mathutil.NewVec3(10, 0, 10)
+
+ // Portals: Zone 1 <-> Zone 2
+ zone1.AddPortal(world.ZonePortal{
+ SourceZoneID: 1,
+ TriggerPos: mathutil.NewVec3(300, 0, 150),
+ TriggerRadius: 5.0,
+ TargetZoneID: 2,
+ TargetPos: mathutil.NewVec3(5, 0, 5),
+ })
+ zone2.AddPortal(world.ZonePortal{
+ SourceZoneID: 2,
+ TriggerPos: mathutil.NewVec3(0, 0, 0),
+ TriggerRadius: 5.0,
+ TargetZoneID: 1,
+ TargetPos: mathutil.NewVec3(295, 0, 150),
+ })
+
+ // Portals: Zone 2 <-> Zone 3
+ zone2.AddPortal(world.ZonePortal{
+ SourceZoneID: 2,
+ TriggerPos: mathutil.NewVec3(300, 0, 300),
+ TriggerRadius: 5.0,
+ TargetZoneID: 3,
+ TargetPos: mathutil.NewVec3(10, 0, 10),
+ })
+ zone3.AddPortal(world.ZonePortal{
+ SourceZoneID: 3,
+ TriggerPos: mathutil.NewVec3(0, 0, 0),
+ TriggerRadius: 5.0,
+ TargetZoneID: 2,
+ TargetPos: mathutil.NewVec3(295, 0, 295),
+ })
+
+ // Populate zones with mobs.
+ gs.setupZoneMobs(zone1, []mobSpawnConfig{
+ {mobID: 1, count: 3, baseX: 20, baseZ: 30, spacing: 15}, // Goblins
+ {mobID: 2, count: 2, baseX: 80, baseZ: 80, spacing: 12}, // Wolves
+ })
+ gs.setupZoneMobs(zone2, []mobSpawnConfig{
+ {mobID: 2, count: 4, baseX: 50, baseZ: 50, spacing: 15}, // Wolves
+ {mobID: 3, count: 2, baseX: 150, baseZ: 150, spacing: 20}, // Trolls
+ {mobID: 4, count: 1, baseX: 200, baseZ: 50, spacing: 0}, // Fire Elemental
+ })
+ gs.setupZoneMobs(zone3, []mobSpawnConfig{
+ {mobID: 4, count: 3, baseX: 80, baseZ: 80, spacing: 25}, // Fire Elementals
+ {mobID: 5, count: 1, baseX: 200, baseZ: 200, spacing: 0}, // Dragon Whelp
+ })
+}
+
+type mobSpawnConfig struct {
+ mobID uint32
+ count int
+ baseX float32
+ baseZ float32
+ spacing float32
+}
+
+// setupZoneMobs configures mob spawn points for a zone.
+func (gs *GameServer) setupZoneMobs(zone *Zone, configs []mobSpawnConfig) {
+ registry := ai.NewMobRegistry()
+ spawner := zone.Spawner()
+
+ for _, cfg := range configs {
+ def := registry.Get(cfg.mobID)
+ if def == nil {
+ continue
+ }
+
+ respawn := time.Duration(15+def.Level*3) * time.Second
+
+ for i := 0; i < cfg.count; i++ {
+ spawner.AddSpawnPoint(ai.SpawnPoint{
+ MobDef: def,
+ Position: mathutil.NewVec3(cfg.baseX+float32(i)*cfg.spacing, 0, cfg.baseZ+float32(i)*cfg.spacing),
+ RespawnDelay: respawn,
+ MaxCount: 1,
+ })
+ }
+ }
+
+ spawner.InitialSpawn()
+}
+
+// handleZoneTransfer moves a player between zones.
+func (gs *GameServer) handleZoneTransfer(playerID uint64, targetZoneID uint32, targetPos mathutil.Vec3) {
+ gs.mu.RLock()
+ var p *player.Player
+ var connID uint64
+ for cid, pl := range gs.connPlayer {
+ if pl.EntityID() == playerID {
+ p = pl
+ connID = cid
+ break
+ }
+ }
+ gs.mu.RUnlock()
+
+ if p == nil {
+ return
+ }
+ _ = connID
+
+ sourceZone, err := gs.world.GetZone(p.ZoneID())
+ if err != nil {
+ return
+ }
+ targetZone, err := gs.world.GetZone(targetZoneID)
+ if err != nil {
+ logger.Warn("zone transfer target not found", "targetZone", targetZoneID)
+ return
+ }
+
+ // Remove from source zone.
+ sourceZone.RemovePlayer(playerID)
+
+ // Update player state.
+ p.SetPosition(targetPos)
+ p.SetZoneID(targetZoneID)
+
+ // Add to target zone via message queue.
+ targetZone.EnqueueMessage(PlayerMessage{
+ PlayerID: playerID,
+ Packet: &network.Packet{Type: msgPlayerEnterWorld},
+ })
+
+ // Notify client of zone change.
+ p.Connection().Send(network.MsgZoneTransferNotify, &pb.ZoneTransferNotify{
+ NewZoneId: targetZoneID,
+ Self: p.ToProto(),
+ })
+
+ logger.Info("zone transfer",
+ "playerID", playerID,
+ "from", sourceZone.ID(),
+ "to", targetZoneID,
+ )
+}
diff --git a/internal/game/world.go b/internal/game/world.go
new file mode 100644
index 0000000..d69d28b
--- /dev/null
+++ b/internal/game/world.go
@@ -0,0 +1,94 @@
+package game
+
+import (
+ "fmt"
+ "sync"
+
+ "a301_game_server/config"
+ "a301_game_server/pkg/logger"
+)
+
+// World manages all zones.
+type World struct {
+ mu sync.RWMutex
+ zones map[uint32]*Zone
+ cfg *config.Config
+}
+
+// NewWorld creates a new world.
+func NewWorld(cfg *config.Config) *World {
+ return &World{
+ zones: make(map[uint32]*Zone),
+ cfg: cfg,
+ }
+}
+
+// CreateZone creates and registers a new zone.
+func (w *World) CreateZone(id uint32) *Zone {
+ zone := NewZone(id, w.cfg)
+
+ w.mu.Lock()
+ w.zones[id] = zone
+ w.mu.Unlock()
+
+ logger.Info("zone created", "zoneID", id)
+ return zone
+}
+
+// GetZone returns a zone by ID.
+func (w *World) GetZone(id uint32) (*Zone, error) {
+ w.mu.RLock()
+ defer w.mu.RUnlock()
+
+ zone, ok := w.zones[id]
+ if !ok {
+ return nil, fmt.Errorf("zone %d not found", id)
+ }
+ return zone, nil
+}
+
+// StartAll launches all zone game loops.
+func (w *World) StartAll() {
+ w.mu.RLock()
+ defer w.mu.RUnlock()
+
+ for _, zone := range w.zones {
+ go zone.Run()
+ }
+ logger.Info("all zones started", "count", len(w.zones))
+}
+
+// StopAll stops all zone game loops.
+func (w *World) StopAll() {
+ w.mu.RLock()
+ defer w.mu.RUnlock()
+
+ for _, zone := range w.zones {
+ zone.Stop()
+ }
+ logger.Info("all zones stopped")
+}
+
+// TotalPlayers returns the total number of online players across all zones.
+func (w *World) TotalPlayers() int {
+ w.mu.RLock()
+ defer w.mu.RUnlock()
+
+ total := 0
+ for _, zone := range w.zones {
+ total += zone.PlayerCount()
+ }
+ return total
+}
+
+// TotalEntities returns the total number of entities across all zones.
+func (w *World) TotalEntities() int {
+ w.mu.RLock()
+ defer w.mu.RUnlock()
+
+ total := 0
+ for _, zone := range w.zones {
+ total += zone.EntityCount()
+ }
+ return total
+}
diff --git a/internal/game/zone.go b/internal/game/zone.go
new file mode 100644
index 0000000..cbdca62
--- /dev/null
+++ b/internal/game/zone.go
@@ -0,0 +1,665 @@
+package game
+
+import (
+ "sync/atomic"
+ "time"
+
+ "a301_game_server/config"
+ "a301_game_server/internal/ai"
+ "a301_game_server/internal/combat"
+ "a301_game_server/internal/entity"
+ "a301_game_server/internal/network"
+ "a301_game_server/internal/world"
+ "a301_game_server/pkg/logger"
+ "a301_game_server/pkg/mathutil"
+ pb "a301_game_server/proto/gen/pb"
+ "google.golang.org/protobuf/proto"
+)
+
+const (
+ maxMoveSpeed float32 = 10.0 // units per second
+)
+
+// PlayerMessage is a message from a player connection queued for zone processing.
+type PlayerMessage struct {
+ PlayerID uint64
+ Packet *network.Packet
+}
+
+// PlayerEntity wraps an entity.Entity that is also a connected player.
+type PlayerEntity interface {
+ entity.Entity
+ Connection() *network.Connection
+ Velocity() mathutil.Vec3
+ SetVelocity(vel mathutil.Vec3)
+}
+
+// ZoneMessageHandler provides an extension point for handling custom message types in a zone.
+type ZoneMessageHandler interface {
+ HandleZoneMessage(zone *Zone, msg PlayerMessage) bool // returns true if handled
+}
+
+// Zone is a self-contained game area with its own game loop.
+type Zone struct {
+ id uint32
+ cfg *config.Config
+ entities map[uint64]entity.Entity
+ players map[uint64]PlayerEntity
+ aoi world.AOIManager
+ incoming chan PlayerMessage
+ stopCh chan struct{}
+ tick int64
+
+ // Metrics
+ lastTickDuration atomic.Int64
+
+ // AOI toggle support
+ aoiEnabled bool
+ gridAOI *world.GridAOI
+ broadcastAOI *world.BroadcastAllAOI
+
+ // Combat
+ combatMgr *combat.Manager
+
+ // AI / Mobs
+ spawner *ai.Spawner
+ nextEntityID atomic.Uint64
+
+ // External message handler for custom/internal messages.
+ extHandler ZoneMessageHandler
+
+ // Respawn position
+ spawnPos mathutil.Vec3
+
+ // Zone portals
+ portals []world.ZonePortal
+
+ // Zone transfer callback (set by GameServer)
+ onZoneTransfer func(playerID uint64, targetZoneID uint32, targetPos mathutil.Vec3)
+}
+
+// NewZone creates a new zone with the given configuration.
+func NewZone(id uint32, cfg *config.Config) *Zone {
+ gridAOI := world.NewGridAOI(cfg.World.AOI.CellSize, cfg.World.AOI.ViewRange)
+ broadcastAOI := world.NewBroadcastAllAOI()
+
+ var activeAOI world.AOIManager
+ if cfg.World.AOI.Enabled {
+ activeAOI = gridAOI
+ } else {
+ activeAOI = broadcastAOI
+ }
+
+ cm := combat.NewManager()
+
+ z := &Zone{
+ id: id,
+ cfg: cfg,
+ entities: make(map[uint64]entity.Entity),
+ players: make(map[uint64]PlayerEntity),
+ aoi: activeAOI,
+ incoming: make(chan PlayerMessage, 4096),
+ stopCh: make(chan struct{}),
+ aoiEnabled: cfg.World.AOI.Enabled,
+ gridAOI: gridAOI,
+ broadcastAOI: broadcastAOI,
+ combatMgr: cm,
+ spawnPos: mathutil.NewVec3(0, 0, 0),
+ }
+
+ // Wire combat manager broadcast to zone AOI.
+ cm.SetBroadcast(z.broadcastCombatEvent, z.sendToEntity)
+
+ // Create mob spawner.
+ z.spawner = ai.NewSpawner(
+ &z.nextEntityID,
+ func(m *ai.Mob) { z.addMob(m) },
+ func(mobID uint64) { z.removeMob(mobID) },
+ )
+
+ return z
+}
+
+// ID returns the zone identifier.
+func (z *Zone) ID() uint32 { return z.id }
+
+// AddPortal registers a zone portal.
+func (z *Zone) AddPortal(portal world.ZonePortal) {
+ z.portals = append(z.portals, portal)
+}
+
+// SetZoneTransferCallback sets the function called when a player enters a portal.
+func (z *Zone) SetZoneTransferCallback(fn func(playerID uint64, targetZoneID uint32, targetPos mathutil.Vec3)) {
+ z.onZoneTransfer = fn
+}
+
+// PlayerCount returns the current number of players in this zone.
+func (z *Zone) PlayerCount() int { return len(z.players) }
+
+// EntityCount returns the current number of entities in this zone.
+func (z *Zone) EntityCount() int { return len(z.entities) }
+
+// LastTickDuration returns the duration of the last tick in microseconds.
+func (z *Zone) LastTickDuration() int64 { return z.lastTickDuration.Load() }
+
+// AOIEnabled returns whether grid-based AOI is currently active.
+func (z *Zone) AOIEnabled() bool { return z.aoiEnabled }
+
+// EnqueueMessage queues a player message for processing in the next tick.
+func (z *Zone) EnqueueMessage(msg PlayerMessage) {
+ select {
+ case z.incoming <- msg:
+ default:
+ logger.Warn("zone message queue full, dropping", "zoneID", z.id, "playerID", msg.PlayerID)
+ }
+}
+
+// AddPlayer adds a player to the zone.
+// Must be called from the zone's goroutine or before Run() starts.
+func (z *Zone) AddPlayer(p PlayerEntity) {
+ z.entities[p.EntityID()] = p
+ z.players[p.EntityID()] = p
+ z.aoi.Add(p)
+
+ // Notify existing nearby players about the new player.
+ spawnData, _ := network.Encode(network.MsgSpawnEntity, &pb.SpawnEntity{Entity: p.ToProto()})
+ for _, nearby := range z.aoi.GetNearby(p) {
+ if np, ok := z.players[nearby.EntityID()]; ok {
+ np.Connection().SendRaw(spawnData)
+ }
+ }
+
+ logger.Info("player added to zone", "zoneID", z.id, "playerID", p.EntityID(), "players", len(z.players))
+}
+
+// RemovePlayer removes a player from the zone.
+func (z *Zone) RemovePlayer(playerID uint64) {
+ entity, ok := z.entities[playerID]
+ if !ok {
+ return
+ }
+
+ events := z.aoi.Remove(entity)
+ z.handleAOIEvents(events)
+
+ z.combatMgr.RemoveEntity(playerID)
+ delete(z.entities, playerID)
+ delete(z.players, playerID)
+
+ logger.Info("player removed from zone", "zoneID", z.id, "playerID", playerID, "players", len(z.players))
+}
+
+// ToggleAOI switches between grid-based and broadcast-all AOI at runtime.
+func (z *Zone) ToggleAOI(enabled bool) {
+ if z.aoiEnabled == enabled {
+ return
+ }
+
+ z.aoiEnabled = enabled
+
+ // Rebuild the target AOI manager with current entities.
+ var newAOI world.AOIManager
+ if enabled {
+ g := world.NewGridAOI(z.cfg.World.AOI.CellSize, z.cfg.World.AOI.ViewRange)
+ for _, e := range z.entities {
+ g.Add(e)
+ }
+ z.gridAOI = g
+ newAOI = g
+ } else {
+ b := world.NewBroadcastAllAOI()
+ for _, e := range z.entities {
+ b.Add(e)
+ }
+ z.broadcastAOI = b
+ newAOI = b
+ }
+
+ z.aoi = newAOI
+
+ // After toggle, send full spawn list to all players so they see the correct set.
+ for _, p := range z.players {
+ z.sendNearbySnapshot(p)
+ }
+
+ logger.Info("AOI toggled", "zoneID", z.id, "enabled", enabled)
+}
+
+// Run starts the zone's game loop. Blocks until Stop() is called.
+func (z *Zone) Run() {
+ interval := z.cfg.TickInterval()
+ ticker := time.NewTicker(interval)
+ defer ticker.Stop()
+
+ logger.Info("zone started", "zoneID", z.id, "tickInterval", interval)
+
+ for {
+ select {
+ case <-ticker.C:
+ start := time.Now()
+ z.processTick()
+ z.lastTickDuration.Store(time.Since(start).Microseconds())
+ case <-z.stopCh:
+ logger.Info("zone stopped", "zoneID", z.id)
+ return
+ }
+ }
+}
+
+// Stop signals the zone's game loop to exit.
+func (z *Zone) Stop() {
+ close(z.stopCh)
+}
+
+func (z *Zone) processTick() {
+ z.tick++
+ z.processInputQueue()
+ z.updateMovement()
+ z.updateAI()
+ z.updateCombat()
+ z.checkDeaths()
+ z.spawner.Update(time.Now())
+ z.broadcastState()
+}
+
+func (z *Zone) updateCombat() {
+ dt := z.cfg.TickInterval()
+ z.combatMgr.UpdateBuffs(dt, func(id uint64) combat.Combatant {
+ if p, ok := z.players[id]; ok {
+ if c, ok := p.(combat.Combatant); ok {
+ return c
+ }
+ }
+ return nil
+ })
+}
+
+func (z *Zone) processInputQueue() {
+ for {
+ select {
+ case msg := <-z.incoming:
+ z.handleMessage(msg)
+ default:
+ return
+ }
+ }
+}
+
+// SetMessageHandler sets an external handler for custom message types.
+func (z *Zone) SetMessageHandler(h ZoneMessageHandler) {
+ z.extHandler = h
+}
+
+func (z *Zone) handleMessage(msg PlayerMessage) {
+ // Try external handler first (for internal messages like disconnect/enter).
+ if z.extHandler != nil && z.extHandler.HandleZoneMessage(z, msg) {
+ return
+ }
+
+ switch msg.Packet.Type {
+ case network.MsgMoveRequest:
+ z.handleMoveRequest(msg)
+ case network.MsgUseSkillRequest:
+ z.handleUseSkill(msg)
+ case network.MsgRespawnRequest:
+ z.handleRespawn(msg)
+ case network.MsgPing:
+ z.handlePing(msg)
+ case network.MsgAOIToggleRequest:
+ z.handleAOIToggle(msg)
+ case network.MsgMetricsRequest:
+ z.handleMetrics(msg)
+ }
+}
+
+func (z *Zone) handleMoveRequest(msg PlayerMessage) {
+ p, ok := z.players[msg.PlayerID]
+ if !ok {
+ return
+ }
+
+ req := msg.Packet.Payload.(*pb.MoveRequest)
+
+ newPos := mathutil.NewVec3(req.Position.X, req.Position.Y, req.Position.Z)
+ vel := mathutil.NewVec3(req.Velocity.X, req.Velocity.Y, req.Velocity.Z)
+
+ // Server-side speed validation.
+ if vel.Length() > maxMoveSpeed*1.1 { // 10% tolerance
+ vel = vel.Normalize().Scale(maxMoveSpeed)
+ }
+
+ oldPos := p.Position()
+ p.SetPosition(newPos)
+ p.SetRotation(req.Rotation)
+ p.SetVelocity(vel)
+
+ // Update AOI and handle events.
+ events := z.aoi.UpdatePosition(p, oldPos, newPos)
+ z.handleAOIEvents(events)
+
+ // Check portal triggers.
+ z.checkPortals(p, newPos)
+}
+
+func (z *Zone) handlePing(msg PlayerMessage) {
+ p, ok := z.players[msg.PlayerID]
+ if !ok {
+ return
+ }
+ ping := msg.Packet.Payload.(*pb.Ping)
+ p.Connection().Send(network.MsgPong, &pb.Pong{
+ ClientTime: ping.ClientTime,
+ ServerTime: time.Now().UnixMilli(),
+ })
+}
+
+func (z *Zone) handleAOIToggle(msg PlayerMessage) {
+ p, ok := z.players[msg.PlayerID]
+ if !ok {
+ return
+ }
+ req := msg.Packet.Payload.(*pb.AOIToggleRequest)
+ z.ToggleAOI(req.Enabled)
+
+ status := "disabled"
+ if req.Enabled {
+ status = "enabled"
+ }
+ p.Connection().Send(network.MsgAOIToggleResponse, &pb.AOIToggleResponse{
+ Enabled: req.Enabled,
+ Message: "AOI " + status,
+ })
+}
+
+func (z *Zone) handleMetrics(msg PlayerMessage) {
+ p, ok := z.players[msg.PlayerID]
+ if !ok {
+ return
+ }
+ p.Connection().Send(network.MsgServerMetrics, &pb.ServerMetrics{
+ OnlinePlayers: int32(len(z.players)),
+ TotalEntities: int32(len(z.entities)),
+ TickDurationUs: z.lastTickDuration.Load(),
+ AoiEnabled: z.aoiEnabled,
+ })
+}
+
+func (z *Zone) updateMovement() {
+ // Movement is applied immediately in handleMoveRequest (client-authoritative position
+ // with server validation). Future: add server-side physics/collision here.
+}
+
+func (z *Zone) broadcastState() {
+ if len(z.players) == 0 {
+ return
+ }
+
+ // For each player, send state updates of nearby entities.
+ for _, p := range z.players {
+ nearby := z.aoi.GetNearby(p)
+ if len(nearby) == 0 {
+ continue
+ }
+
+ states := make([]*pb.EntityState, 0, len(nearby))
+ for _, e := range nearby {
+ states = append(states, e.ToProto())
+ }
+
+ p.Connection().Send(network.MsgStateUpdate, &pb.StateUpdate{
+ Entities: states,
+ ServerTick: z.tick,
+ })
+ }
+}
+
+func (z *Zone) handleAOIEvents(events []world.AOIEvent) {
+ for _, evt := range events {
+ observerPlayer, ok := z.players[evt.Observer.EntityID()]
+ if !ok {
+ continue
+ }
+
+ switch evt.Type {
+ case world.AOIEnter:
+ observerPlayer.Connection().Send(network.MsgSpawnEntity, &pb.SpawnEntity{
+ Entity: evt.Target.ToProto(),
+ })
+ case world.AOILeave:
+ observerPlayer.Connection().Send(network.MsgDespawnEntity, &pb.DespawnEntity{
+ EntityId: evt.Target.EntityID(),
+ })
+ }
+ }
+}
+
+func (z *Zone) sendNearbySnapshot(p PlayerEntity) {
+ nearby := z.aoi.GetNearby(p)
+ states := make([]*pb.EntityState, 0, len(nearby))
+ for _, e := range nearby {
+ states = append(states, e.ToProto())
+ }
+ p.Connection().Send(network.MsgStateUpdate, &pb.StateUpdate{
+ Entities: states,
+ ServerTick: z.tick,
+ })
+}
+
+// ─── Combat Handlers ────────────────────────────────────────
+
+func (z *Zone) handleUseSkill(msg PlayerMessage) {
+ p, ok := z.players[msg.PlayerID]
+ if !ok {
+ return
+ }
+
+ req := msg.Packet.Payload.(*pb.UseSkillRequest)
+
+ var targetPos mathutil.Vec3
+ if req.TargetPos != nil {
+ targetPos = mathutil.NewVec3(req.TargetPos.X, req.TargetPos.Y, req.TargetPos.Z)
+ }
+
+ caster, ok := p.(combat.Combatant)
+ if !ok {
+ return
+ }
+
+ success, errMsg := z.combatMgr.UseSkill(
+ caster,
+ req.SkillId,
+ req.TargetId,
+ targetPos,
+ func(id uint64) entity.Entity { return z.entities[id] },
+ z.getEntitiesInRadius,
+ )
+
+ p.Connection().Send(network.MsgUseSkillResponse, &pb.UseSkillResponse{
+ Success: success,
+ ErrorMessage: errMsg,
+ })
+}
+
+func (z *Zone) handleRespawn(msg PlayerMessage) {
+ p, ok := z.players[msg.PlayerID]
+ if !ok {
+ return
+ }
+
+ c, ok := p.(combat.Combatant)
+ if !ok || c.IsAlive() {
+ return
+ }
+
+ oldPos := p.Position()
+ z.combatMgr.Respawn(c, z.spawnPos)
+
+ // Update AOI for the new position.
+ events := z.aoi.UpdatePosition(p, oldPos, z.spawnPos)
+ z.handleAOIEvents(events)
+
+ // Notify respawn.
+ respawnEvt := &pb.CombatEvent{
+ TargetId: p.EntityID(),
+ TargetHp: c.HP(),
+ TargetMaxHp: c.MaxHP(),
+ EventType: pb.CombatEventType_COMBAT_EVENT_RESPAWN,
+ }
+ z.broadcastCombatEvent(p, network.MsgCombatEvent, respawnEvt)
+
+ p.Connection().Send(network.MsgRespawnResponse, &pb.RespawnResponse{
+ Self: p.ToProto(),
+ })
+}
+
+// broadcastCombatEvent sends a combat event to all players who can see the entity.
+func (z *Zone) broadcastCombatEvent(ent entity.Entity, msgType uint16, msg interface{}) {
+ protoMsg, ok := msg.(proto.Message)
+ if !ok {
+ return
+ }
+ data, err := network.Encode(msgType, protoMsg)
+ if err != nil {
+ return
+ }
+
+ // Send to the entity itself if it's a player.
+ if p, ok := z.players[ent.EntityID()]; ok {
+ p.Connection().SendRaw(data)
+ }
+
+ // Send to nearby players.
+ for _, nearby := range z.aoi.GetNearby(ent) {
+ if p, ok := z.players[nearby.EntityID()]; ok {
+ p.Connection().SendRaw(data)
+ }
+ }
+}
+
+// sendToEntity sends a message to a specific entity (if it's a player).
+func (z *Zone) sendToEntity(entityID uint64, msgType uint16, msg interface{}) {
+ p, ok := z.players[entityID]
+ if !ok {
+ return
+ }
+ protoMsg, ok := msg.(proto.Message)
+ if !ok {
+ return
+ }
+ p.Connection().Send(msgType, protoMsg)
+}
+
+// getEntitiesInRadius returns all entities within a radius of a point.
+func (z *Zone) getEntitiesInRadius(center mathutil.Vec3, radius float32) []entity.Entity {
+ radiusSq := radius * radius
+ var result []entity.Entity
+ for _, e := range z.entities {
+ if e.Position().DistanceSqTo(center) <= radiusSq {
+ result = append(result, e)
+ }
+ }
+ return result
+}
+
+// ─── AI / Mob Management ────────────────────────────────────
+
+// Spawner returns the zone's mob spawner for external configuration.
+func (z *Zone) Spawner() *ai.Spawner { return z.spawner }
+
+func (z *Zone) addMob(m *ai.Mob) {
+ z.entities[m.EntityID()] = m
+ z.aoi.Add(m)
+
+ // Notify nearby players about the new mob.
+ spawnData, _ := network.Encode(network.MsgSpawnEntity, &pb.SpawnEntity{Entity: m.ToProto()})
+ for _, nearby := range z.aoi.GetNearby(m) {
+ if p, ok := z.players[nearby.EntityID()]; ok {
+ p.Connection().SendRaw(spawnData)
+ }
+ }
+}
+
+func (z *Zone) removeMob(mobID uint64) {
+ ent, ok := z.entities[mobID]
+ if !ok {
+ return
+ }
+
+ events := z.aoi.Remove(ent)
+ z.handleAOIEvents(events)
+ z.combatMgr.RemoveEntity(mobID)
+ delete(z.entities, mobID)
+}
+
+func (z *Zone) updateAI() {
+ dt := z.cfg.TickInterval()
+ for _, m := range z.spawner.AliveMobs() {
+ oldPos := m.Position()
+ ai.UpdateMob(m, dt, z, z)
+ newPos := m.Position()
+
+ // Update AOI if mob moved.
+ if oldPos != newPos {
+ events := z.aoi.UpdatePosition(m, oldPos, newPos)
+ z.handleAOIEvents(events)
+ }
+ }
+}
+
+func (z *Zone) checkDeaths() {
+ for _, m := range z.spawner.AliveMobs() {
+ if !m.IsAlive() {
+ z.spawner.NotifyDeath(m.EntityID())
+ }
+ }
+}
+
+// ─── ai.EntityProvider implementation ───────────────────────
+
+func (z *Zone) GetEntity(id uint64) entity.Entity {
+ return z.entities[id]
+}
+
+func (z *Zone) GetPlayersInRange(center mathutil.Vec3, radius float32) []entity.Entity {
+ radiusSq := radius * radius
+ var result []entity.Entity
+ for _, p := range z.players {
+ if p.Position().DistanceSqTo(center) <= radiusSq {
+ result = append(result, p)
+ }
+ }
+ return result
+}
+
+// ─── ai.SkillUser implementation ────────────────────────────
+
+func (z *Zone) UseSkill(casterID uint64, skillID uint32, targetID uint64, targetPos mathutil.Vec3) (bool, string) {
+ ent := z.entities[casterID]
+ if ent == nil {
+ return false, "caster not found"
+ }
+ caster, ok := ent.(combat.Combatant)
+ if !ok {
+ return false, "caster cannot fight"
+ }
+
+ return z.combatMgr.UseSkill(
+ caster, skillID, targetID, targetPos,
+ func(id uint64) entity.Entity { return z.entities[id] },
+ z.getEntitiesInRadius,
+ )
+}
+
+// ─── Zone Portals ───────────────────────────────────────────
+
+func (z *Zone) checkPortals(p PlayerEntity, pos mathutil.Vec3) {
+ if z.onZoneTransfer == nil || len(z.portals) == 0 {
+ return
+ }
+ for _, portal := range z.portals {
+ if portal.IsInRange(pos) {
+ z.onZoneTransfer(p.EntityID(), portal.TargetZoneID, portal.TargetPos)
+ return
+ }
+ }
+}
diff --git a/internal/network/connection.go b/internal/network/connection.go
new file mode 100644
index 0000000..19df558
--- /dev/null
+++ b/internal/network/connection.go
@@ -0,0 +1,174 @@
+package network
+
+import (
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/gorilla/websocket"
+ "google.golang.org/protobuf/proto"
+
+ "a301_game_server/pkg/logger"
+)
+
+// ConnState represents the lifecycle state of a connection.
+type ConnState int32
+
+const (
+ ConnStateActive ConnState = iota
+ ConnStateClosed
+)
+
+// Connection wraps a WebSocket connection with send buffering and lifecycle management.
+type Connection struct {
+ id uint64
+ ws *websocket.Conn
+ sendCh chan []byte
+ handler PacketHandler
+ state atomic.Int32
+ closeOnce sync.Once
+
+ maxMessageSize int64
+ heartbeatInterval time.Duration
+ heartbeatTimeout time.Duration
+}
+
+// PacketHandler processes incoming packets from a connection.
+type PacketHandler interface {
+ OnPacket(conn *Connection, pkt *Packet)
+ OnDisconnect(conn *Connection)
+}
+
+// NewConnection creates a new Connection wrapping the given WebSocket.
+func NewConnection(id uint64, ws *websocket.Conn, handler PacketHandler, sendChSize int, maxMsgSize int64, hbInterval, hbTimeout time.Duration) *Connection {
+ c := &Connection{
+ id: id,
+ ws: ws,
+ sendCh: make(chan []byte, sendChSize),
+ handler: handler,
+ maxMessageSize: maxMsgSize,
+ heartbeatInterval: hbInterval,
+ heartbeatTimeout: hbTimeout,
+ }
+ c.state.Store(int32(ConnStateActive))
+ return c
+}
+
+// ID returns the connection's unique identifier.
+func (c *Connection) ID() uint64 { return c.id }
+
+// Start launches the read and write goroutines.
+func (c *Connection) Start() {
+ go c.readLoop()
+ go c.writeLoop()
+}
+
+// Send encodes and queues a message for sending. Non-blocking: drops if buffer is full.
+func (c *Connection) Send(msgType uint16, msg proto.Message) {
+ if c.IsClosed() {
+ return
+ }
+
+ data, err := Encode(msgType, msg)
+ if err != nil {
+ logger.Error("encode failed", "connID", c.id, "msgType", msgType, "error", err)
+ return
+ }
+
+ select {
+ case c.sendCh <- data:
+ default:
+ logger.Warn("send buffer full, dropping message", "connID", c.id, "msgType", msgType)
+ }
+}
+
+// SendRaw queues pre-encoded data for sending. Non-blocking.
+func (c *Connection) SendRaw(data []byte) {
+ if c.IsClosed() {
+ return
+ }
+ select {
+ case c.sendCh <- data:
+ default:
+ logger.Warn("send buffer full, dropping raw message", "connID", c.id)
+ }
+}
+
+// Close terminates the connection.
+func (c *Connection) Close() {
+ c.closeOnce.Do(func() {
+ c.state.Store(int32(ConnStateClosed))
+ close(c.sendCh)
+ _ = c.ws.Close()
+ })
+}
+
+// IsClosed returns true if the connection has been closed.
+func (c *Connection) IsClosed() bool {
+ return ConnState(c.state.Load()) == ConnStateClosed
+}
+
+func (c *Connection) readLoop() {
+ defer func() {
+ c.handler.OnDisconnect(c)
+ c.Close()
+ }()
+
+ c.ws.SetReadLimit(c.maxMessageSize)
+ _ = c.ws.SetReadDeadline(time.Now().Add(c.heartbeatTimeout))
+
+ c.ws.SetPongHandler(func(string) error {
+ _ = c.ws.SetReadDeadline(time.Now().Add(c.heartbeatTimeout))
+ return nil
+ })
+
+ for {
+ msgType, data, err := c.ws.ReadMessage()
+ if err != nil {
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) {
+ logger.Debug("read error", "connID", c.id, "error", err)
+ }
+ return
+ }
+
+ if msgType != websocket.BinaryMessage {
+ continue
+ }
+
+ pkt, err := Decode(data)
+ if err != nil {
+ logger.Warn("decode error", "connID", c.id, "error", err)
+ continue
+ }
+
+ c.handler.OnPacket(c, pkt)
+ }
+}
+
+func (c *Connection) writeLoop() {
+ ticker := time.NewTicker(c.heartbeatInterval)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case data, ok := <-c.sendCh:
+ if !ok {
+ _ = c.ws.WriteMessage(websocket.CloseMessage,
+ websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+ return
+ }
+
+ _ = c.ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
+ if err := c.ws.WriteMessage(websocket.BinaryMessage, data); err != nil {
+ logger.Debug("write error", "connID", c.id, "error", err)
+ return
+ }
+
+ case <-ticker.C:
+ _ = c.ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
+ if err := c.ws.WriteMessage(websocket.PingMessage, nil); err != nil {
+ return
+ }
+ }
+ }
+}
diff --git a/internal/network/packet.go b/internal/network/packet.go
new file mode 100644
index 0000000..6e2ccb3
--- /dev/null
+++ b/internal/network/packet.go
@@ -0,0 +1,121 @@
+package network
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+
+ "google.golang.org/protobuf/proto"
+
+ pb "a301_game_server/proto/gen/pb"
+)
+
+// Message type IDs — the wire protocol uses 2-byte type prefixes.
+const (
+ // Auth
+ MsgLoginRequest uint16 = 0x0001
+ MsgLoginResponse uint16 = 0x0002
+ MsgEnterWorldRequest uint16 = 0x0003
+ MsgEnterWorldResponse uint16 = 0x0004
+
+ // Movement
+ MsgMoveRequest uint16 = 0x0010
+ MsgStateUpdate uint16 = 0x0011
+ MsgSpawnEntity uint16 = 0x0012
+ MsgDespawnEntity uint16 = 0x0013
+
+ // Zone Transfer
+ MsgZoneTransferNotify uint16 = 0x0014
+
+ // System
+ MsgPing uint16 = 0x0020
+ MsgPong uint16 = 0x0021
+
+ // Combat
+ MsgUseSkillRequest uint16 = 0x0040
+ MsgUseSkillResponse uint16 = 0x0041
+ MsgCombatEvent uint16 = 0x0042
+ MsgBuffApplied uint16 = 0x0043
+ MsgBuffRemoved uint16 = 0x0044
+ MsgRespawnRequest uint16 = 0x0045
+ MsgRespawnResponse uint16 = 0x0046
+
+ // Admin / Debug
+ MsgAOIToggleRequest uint16 = 0x0030
+ MsgAOIToggleResponse uint16 = 0x0031
+ MsgMetricsRequest uint16 = 0x0032
+ MsgServerMetrics uint16 = 0x0033
+)
+
+var (
+ ErrUnknownMessageType = errors.New("unknown message type")
+ ErrMessageTooShort = errors.New("message too short")
+)
+
+// Packet is a decoded network message.
+type Packet struct {
+ Type uint16
+ Payload proto.Message
+}
+
+// messageFactory maps type IDs to protobuf message constructors.
+var messageFactory = map[uint16]func() proto.Message{
+ MsgLoginRequest: func() proto.Message { return &pb.LoginRequest{} },
+ MsgLoginResponse: func() proto.Message { return &pb.LoginResponse{} },
+ MsgEnterWorldRequest: func() proto.Message { return &pb.EnterWorldRequest{} },
+ MsgEnterWorldResponse: func() proto.Message { return &pb.EnterWorldResponse{} },
+ MsgMoveRequest: func() proto.Message { return &pb.MoveRequest{} },
+ MsgStateUpdate: func() proto.Message { return &pb.StateUpdate{} },
+ MsgSpawnEntity: func() proto.Message { return &pb.SpawnEntity{} },
+ MsgDespawnEntity: func() proto.Message { return &pb.DespawnEntity{} },
+ MsgPing: func() proto.Message { return &pb.Ping{} },
+ MsgPong: func() proto.Message { return &pb.Pong{} },
+ MsgZoneTransferNotify: func() proto.Message { return &pb.ZoneTransferNotify{} },
+ MsgUseSkillRequest: func() proto.Message { return &pb.UseSkillRequest{} },
+ MsgUseSkillResponse: func() proto.Message { return &pb.UseSkillResponse{} },
+ MsgCombatEvent: func() proto.Message { return &pb.CombatEvent{} },
+ MsgBuffApplied: func() proto.Message { return &pb.BuffApplied{} },
+ MsgBuffRemoved: func() proto.Message { return &pb.BuffRemoved{} },
+ MsgRespawnRequest: func() proto.Message { return &pb.RespawnRequest{} },
+ MsgRespawnResponse: func() proto.Message { return &pb.RespawnResponse{} },
+ MsgAOIToggleRequest: func() proto.Message { return &pb.AOIToggleRequest{} },
+ MsgAOIToggleResponse: func() proto.Message { return &pb.AOIToggleResponse{} },
+ MsgMetricsRequest: func() proto.Message { return &pb.MetricsRequest{} },
+ MsgServerMetrics: func() proto.Message { return &pb.ServerMetrics{} },
+}
+
+// Encode serializes a packet into a wire-format byte slice: [2-byte type][protobuf payload].
+func Encode(msgType uint16, msg proto.Message) ([]byte, error) {
+ payload, err := proto.Marshal(msg)
+ if err != nil {
+ return nil, fmt.Errorf("marshal message 0x%04X: %w", msgType, err)
+ }
+
+ buf := make([]byte, 2+len(payload))
+ binary.BigEndian.PutUint16(buf[:2], msgType)
+ copy(buf[2:], payload)
+ return buf, nil
+}
+
+// Decode parses a wire-format byte slice into a Packet.
+func Decode(data []byte) (*Packet, error) {
+ if len(data) < 2 {
+ return nil, ErrMessageTooShort
+ }
+
+ msgType := binary.BigEndian.Uint16(data[:2])
+
+ factory, ok := messageFactory[msgType]
+ if !ok {
+ return nil, fmt.Errorf("%w: 0x%04X", ErrUnknownMessageType, msgType)
+ }
+
+ msg := factory()
+ if len(data) > 2 {
+ if err := proto.Unmarshal(data[2:], msg); err != nil {
+ return nil, fmt.Errorf("unmarshal message 0x%04X: %w", msgType, err)
+ }
+ }
+
+ return &Packet{Type: msgType, Payload: msg}, nil
+}
diff --git a/internal/network/server.go b/internal/network/server.go
new file mode 100644
index 0000000..8016a40
--- /dev/null
+++ b/internal/network/server.go
@@ -0,0 +1,89 @@
+package network
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "sync/atomic"
+
+ "github.com/gorilla/websocket"
+
+ "a301_game_server/config"
+ "a301_game_server/pkg/logger"
+)
+
+// Server listens for WebSocket connections and creates Connection objects.
+type Server struct {
+ cfg *config.Config
+ upgrader websocket.Upgrader
+ handler PacketHandler
+ nextID atomic.Uint64
+ srv *http.Server
+}
+
+// NewServer creates a new WebSocket server.
+func NewServer(cfg *config.Config, handler PacketHandler) *Server {
+ return &Server{
+ cfg: cfg,
+ handler: handler,
+ upgrader: websocket.Upgrader{
+ ReadBufferSize: cfg.Network.ReadBufferSize,
+ WriteBufferSize: cfg.Network.WriteBufferSize,
+ CheckOrigin: func(r *http.Request) bool { return true },
+ },
+ }
+}
+
+// Start begins listening for connections. Blocks until the context is cancelled.
+func (s *Server) Start(ctx context.Context) error {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/ws", s.handleWebSocket)
+ mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte("ok"))
+ })
+
+ addr := s.cfg.Server.Address()
+ s.srv = &http.Server{
+ Addr: addr,
+ Handler: mux,
+ }
+
+ errCh := make(chan error, 1)
+ go func() {
+ logger.Info("websocket server starting", "address", addr)
+ if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ errCh <- fmt.Errorf("listen: %w", err)
+ }
+ }()
+
+ select {
+ case err := <-errCh:
+ return err
+ case <-ctx.Done():
+ logger.Info("shutting down websocket server")
+ return s.srv.Shutdown(context.Background())
+ }
+}
+
+func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
+ ws, err := s.upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ logger.Error("websocket upgrade failed", "error", err, "remote", r.RemoteAddr)
+ return
+ }
+
+ connID := s.nextID.Add(1)
+ conn := NewConnection(
+ connID,
+ ws,
+ s.handler,
+ s.cfg.Network.SendChannelSize,
+ s.cfg.Network.MaxMessageSize,
+ s.cfg.Network.HeartbeatInterval,
+ s.cfg.Network.HeartbeatTimeout,
+ )
+
+ logger.Info("client connected", "connID", connID, "remote", r.RemoteAddr)
+ conn.Start()
+}
diff --git a/internal/player/player.go b/internal/player/player.go
new file mode 100644
index 0000000..0c6d3e5
--- /dev/null
+++ b/internal/player/player.go
@@ -0,0 +1,249 @@
+package player
+
+import (
+ "sync"
+
+ "a301_game_server/internal/combat"
+ "a301_game_server/internal/db/repository"
+ "a301_game_server/internal/entity"
+ "a301_game_server/internal/network"
+ "a301_game_server/pkg/mathutil"
+ pb "a301_game_server/proto/gen/pb"
+)
+
+// Player represents an online player in the game world.
+type Player struct {
+ mu sync.RWMutex
+
+ id uint64
+ charID int64 // database character ID
+ acctID int64 // database account ID
+ name string
+ position mathutil.Vec3
+ rotation float32
+ velocity mathutil.Vec3
+
+ stats Stats
+
+ conn *network.Connection
+ zoneID uint32
+ session *Session
+ dirty bool // true if state changed since last DB save
+}
+
+// NewPlayer creates a new player.
+func NewPlayer(id uint64, name string, conn *network.Connection) *Player {
+ return &Player{
+ id: id,
+ name: name,
+ conn: conn,
+ stats: Stats{
+ HP: 100, MaxHP: 100,
+ MP: 50, MaxMP: 50,
+ Str: 10, Dex: 10, Int: 10,
+ Level: 1,
+ },
+ }
+}
+
+// NewPlayerFromDB creates a player from persisted character data.
+func NewPlayerFromDB(data *repository.CharacterData, conn *network.Connection) *Player {
+ return &Player{
+ id: uint64(data.ID),
+ charID: data.ID,
+ acctID: data.AccountID,
+ name: data.Name,
+ position: mathutil.NewVec3(data.PosX, data.PosY, data.PosZ),
+ rotation: data.Rotation,
+ stats: Stats{
+ HP: data.HP, MaxHP: data.MaxHP,
+ MP: data.MP, MaxMP: data.MaxMP,
+ Str: data.Str, Dex: data.Dex, Int: data.IntStat,
+ Level: data.Level, Exp: data.Exp,
+ },
+ conn: conn,
+ zoneID: uint32(data.ZoneID),
+ }
+}
+
+// ToCharacterData converts current state to a persistable format.
+func (p *Player) ToCharacterData() *repository.CharacterData {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return &repository.CharacterData{
+ ID: p.charID,
+ AccountID: p.acctID,
+ Name: p.name,
+ Level: p.stats.Level,
+ Exp: p.stats.Exp,
+ HP: p.stats.HP,
+ MaxHP: p.stats.MaxHP,
+ MP: p.stats.MP,
+ MaxMP: p.stats.MaxMP,
+ Str: p.stats.Str,
+ Dex: p.stats.Dex,
+ IntStat: p.stats.Int,
+ ZoneID: int32(p.zoneID),
+ PosX: p.position.X,
+ PosY: p.position.Y,
+ PosZ: p.position.Z,
+ Rotation: p.rotation,
+ }
+}
+
+func (p *Player) EntityID() uint64 { return p.id }
+func (p *Player) EntityType() entity.Type { return entity.TypePlayer }
+
+func (p *Player) Position() mathutil.Vec3 {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return p.position
+}
+
+func (p *Player) SetPosition(pos mathutil.Vec3) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ p.position = pos
+ p.dirty = true
+}
+
+func (p *Player) Rotation() float32 {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return p.rotation
+}
+
+func (p *Player) SetRotation(rot float32) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ p.rotation = rot
+ p.dirty = true
+}
+
+func (p *Player) Velocity() mathutil.Vec3 {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return p.velocity
+}
+
+func (p *Player) SetVelocity(vel mathutil.Vec3) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ p.velocity = vel
+}
+
+func (p *Player) Name() string { return p.name }
+func (p *Player) CharID() int64 { return p.charID }
+func (p *Player) AccountID() int64 { return p.acctID }
+func (p *Player) Connection() *network.Connection { return p.conn }
+func (p *Player) ZoneID() uint32 { return p.zoneID }
+func (p *Player) SetZoneID(id uint32) { p.zoneID = id }
+
+func (p *Player) Stats() combat.CombatStats {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return combat.CombatStats{
+ Str: p.stats.Str,
+ Dex: p.stats.Dex,
+ Int: p.stats.Int,
+ Level: p.stats.Level,
+ }
+}
+
+func (p *Player) RawStats() Stats {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return p.stats
+}
+
+func (p *Player) SetStats(s Stats) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ p.stats = s
+ p.dirty = true
+}
+
+func (p *Player) HP() int32 {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return p.stats.HP
+}
+
+func (p *Player) SetHP(hp int32) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ if hp < 0 {
+ hp = 0
+ }
+ if hp > p.stats.MaxHP {
+ hp = p.stats.MaxHP
+ }
+ p.stats.HP = hp
+ p.dirty = true
+}
+
+func (p *Player) MaxHP() int32 {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return p.stats.MaxHP
+}
+
+func (p *Player) MP() int32 {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return p.stats.MP
+}
+
+func (p *Player) SetMP(mp int32) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ if mp < 0 {
+ mp = 0
+ }
+ if mp > p.stats.MaxMP {
+ mp = p.stats.MaxMP
+ }
+ p.stats.MP = mp
+ p.dirty = true
+}
+
+func (p *Player) Level() int32 {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return p.stats.Level
+}
+
+func (p *Player) IsAlive() bool {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return p.stats.HP > 0
+}
+
+// IsDirty returns true if state has changed since last save.
+func (p *Player) IsDirty() bool {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return p.dirty
+}
+
+// ClearDirty resets the dirty flag after a successful save.
+func (p *Player) ClearDirty() {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ p.dirty = false
+}
+
+func (p *Player) ToProto() *pb.EntityState {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+ return &pb.EntityState{
+ EntityId: p.id,
+ Name: p.name,
+ Position: &pb.Vector3{X: p.position.X, Y: p.position.Y, Z: p.position.Z},
+ Rotation: p.rotation,
+ Hp: p.stats.HP,
+ MaxHp: p.stats.MaxHP,
+ Level: p.stats.Level,
+ EntityType: pb.EntityType_ENTITY_TYPE_PLAYER,
+ }
+}
diff --git a/internal/player/session.go b/internal/player/session.go
new file mode 100644
index 0000000..9a80b17
--- /dev/null
+++ b/internal/player/session.go
@@ -0,0 +1,90 @@
+package player
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "sync"
+ "time"
+)
+
+// Session holds an authenticated player's session state.
+type Session struct {
+ Token string
+ PlayerID uint64
+ PlayerName string
+ CreatedAt time.Time
+ LastActive time.Time
+}
+
+// SessionManager manages active sessions.
+type SessionManager struct {
+ mu sync.RWMutex
+ sessions map[string]*Session // token -> session
+}
+
+// NewSessionManager creates a new session manager.
+func NewSessionManager() *SessionManager {
+ return &SessionManager{
+ sessions: make(map[string]*Session),
+ }
+}
+
+// Create generates a new session for the given player.
+func (sm *SessionManager) Create(playerID uint64, playerName string) *Session {
+ token := generateToken()
+ now := time.Now()
+
+ s := &Session{
+ Token: token,
+ PlayerID: playerID,
+ PlayerName: playerName,
+ CreatedAt: now,
+ LastActive: now,
+ }
+
+ sm.mu.Lock()
+ sm.sessions[token] = s
+ sm.mu.Unlock()
+
+ return s
+}
+
+// Get retrieves a session by token. Returns nil if not found.
+func (sm *SessionManager) Get(token string) *Session {
+ sm.mu.RLock()
+ defer sm.mu.RUnlock()
+ s := sm.sessions[token]
+ if s != nil {
+ s.LastActive = time.Now()
+ }
+ return s
+}
+
+// Remove deletes a session.
+func (sm *SessionManager) Remove(token string) {
+ sm.mu.Lock()
+ delete(sm.sessions, token)
+ sm.mu.Unlock()
+}
+
+// CleanupExpired removes sessions inactive for longer than the given duration.
+func (sm *SessionManager) CleanupExpired(maxIdle time.Duration) int {
+ sm.mu.Lock()
+ defer sm.mu.Unlock()
+
+ cutoff := time.Now().Add(-maxIdle)
+ removed := 0
+ for token, s := range sm.sessions {
+ if s.LastActive.Before(cutoff) {
+ delete(sm.sessions, token)
+ removed++
+ }
+ }
+ return removed
+}
+
+func generateToken() string {
+ b := make([]byte, 32)
+ _, _ = rand.Read(b)
+ return hex.EncodeToString(b)
+}
diff --git a/internal/player/stats.go b/internal/player/stats.go
new file mode 100644
index 0000000..fff41e0
--- /dev/null
+++ b/internal/player/stats.go
@@ -0,0 +1,14 @@
+package player
+
+// Stats holds combat-related attributes.
+type Stats struct {
+ HP int32
+ MaxHP int32
+ MP int32
+ MaxMP int32
+ Str int32
+ Dex int32
+ Int int32
+ Level int32
+ Exp int64
+}
diff --git a/internal/world/aoi.go b/internal/world/aoi.go
new file mode 100644
index 0000000..82d0132
--- /dev/null
+++ b/internal/world/aoi.go
@@ -0,0 +1,70 @@
+package world
+
+import (
+ "a301_game_server/internal/entity"
+ "a301_game_server/pkg/mathutil"
+)
+
+// AOIEvent represents an entity entering or leaving another entity's area of interest.
+type AOIEvent struct {
+ Observer entity.Entity
+ Target entity.Entity
+ Type AOIEventType
+}
+
+type AOIEventType int
+
+const (
+ AOIEnter AOIEventType = iota
+ AOILeave
+)
+
+// AOIManager determines which entities can see each other.
+type AOIManager interface {
+ Add(ent entity.Entity)
+ Remove(ent entity.Entity) []AOIEvent
+ UpdatePosition(ent entity.Entity, oldPos, newPos mathutil.Vec3) []AOIEvent
+ GetNearby(ent entity.Entity) []entity.Entity
+}
+
+// BroadcastAllAOI is a trivial AOI that treats all entities as visible to each other.
+// Used when AOI is disabled for debugging/comparison.
+type BroadcastAllAOI struct {
+ entities map[uint64]entity.Entity
+}
+
+func NewBroadcastAllAOI() *BroadcastAllAOI {
+ return &BroadcastAllAOI{
+ entities: make(map[uint64]entity.Entity),
+ }
+}
+
+func (b *BroadcastAllAOI) Add(ent entity.Entity) {
+ b.entities[ent.EntityID()] = ent
+}
+
+func (b *BroadcastAllAOI) Remove(ent entity.Entity) []AOIEvent {
+ delete(b.entities, ent.EntityID())
+ var events []AOIEvent
+ for _, other := range b.entities {
+ if other.EntityID() == ent.EntityID() {
+ continue
+ }
+ events = append(events, AOIEvent{Observer: other, Target: ent, Type: AOILeave})
+ }
+ return events
+}
+
+func (b *BroadcastAllAOI) UpdatePosition(_ entity.Entity, _, _ mathutil.Vec3) []AOIEvent {
+ return nil
+}
+
+func (b *BroadcastAllAOI) GetNearby(ent entity.Entity) []entity.Entity {
+ result := make([]entity.Entity, 0, len(b.entities)-1)
+ for _, e := range b.entities {
+ if e.EntityID() != ent.EntityID() {
+ result = append(result, e)
+ }
+ }
+ return result
+}
diff --git a/internal/world/aoi_test.go b/internal/world/aoi_test.go
new file mode 100644
index 0000000..f8287f0
--- /dev/null
+++ b/internal/world/aoi_test.go
@@ -0,0 +1,150 @@
+package world
+
+import (
+ "testing"
+
+ "a301_game_server/internal/entity"
+ "a301_game_server/pkg/mathutil"
+ pb "a301_game_server/proto/gen/pb"
+)
+
+// mockEntity is a minimal entity for testing.
+type mockEntity struct {
+ id uint64
+ pos mathutil.Vec3
+}
+
+func (m *mockEntity) EntityID() uint64 { return m.id }
+func (m *mockEntity) EntityType() entity.Type { return entity.TypePlayer }
+func (m *mockEntity) Position() mathutil.Vec3 { return m.pos }
+func (m *mockEntity) SetPosition(p mathutil.Vec3) { m.pos = p }
+func (m *mockEntity) Rotation() float32 { return 0 }
+func (m *mockEntity) SetRotation(float32) {}
+func (m *mockEntity) ToProto() *pb.EntityState { return &pb.EntityState{EntityId: m.id} }
+
+func TestBroadcastAllAOI_GetNearby(t *testing.T) {
+ aoi := NewBroadcastAllAOI()
+
+ e1 := &mockEntity{id: 1, pos: mathutil.NewVec3(0, 0, 0)}
+ e2 := &mockEntity{id: 2, pos: mathutil.NewVec3(100, 0, 100)}
+ e3 := &mockEntity{id: 3, pos: mathutil.NewVec3(999, 0, 999)}
+
+ aoi.Add(e1)
+ aoi.Add(e2)
+ aoi.Add(e3)
+
+ // With broadcast-all, everyone sees everyone.
+ nearby := aoi.GetNearby(e1)
+ if len(nearby) != 2 {
+ t.Errorf("expected 2 nearby, got %d", len(nearby))
+ }
+}
+
+func TestBroadcastAllAOI_Remove(t *testing.T) {
+ aoi := NewBroadcastAllAOI()
+
+ e1 := &mockEntity{id: 1}
+ e2 := &mockEntity{id: 2}
+ aoi.Add(e1)
+ aoi.Add(e2)
+
+ events := aoi.Remove(e1)
+ if len(events) != 1 {
+ t.Errorf("expected 1 leave event, got %d", len(events))
+ }
+ if events[0].Type != AOILeave {
+ t.Errorf("expected AOILeave event")
+ }
+
+ nearby := aoi.GetNearby(e2)
+ if len(nearby) != 0 {
+ t.Errorf("expected 0 nearby after removal, got %d", len(nearby))
+ }
+}
+
+func TestGridAOI_NearbyInSameCell(t *testing.T) {
+ aoi := NewGridAOI(50, 2)
+
+ e1 := &mockEntity{id: 1, pos: mathutil.NewVec3(10, 0, 10)}
+ e2 := &mockEntity{id: 2, pos: mathutil.NewVec3(20, 0, 20)}
+
+ aoi.Add(e1)
+ aoi.Add(e2)
+
+ nearby := aoi.GetNearby(e1)
+ if len(nearby) != 1 {
+ t.Errorf("expected 1 nearby, got %d", len(nearby))
+ }
+ if nearby[0].EntityID() != 2 {
+ t.Errorf("expected entity 2, got %d", nearby[0].EntityID())
+ }
+}
+
+func TestGridAOI_FarAwayNotVisible(t *testing.T) {
+ aoi := NewGridAOI(50, 1) // viewRange=1 means 3x3 grid = 150 units visibility
+
+ e1 := &mockEntity{id: 1, pos: mathutil.NewVec3(0, 0, 0)}
+ e2 := &mockEntity{id: 2, pos: mathutil.NewVec3(500, 0, 500)} // far away
+
+ aoi.Add(e1)
+ aoi.Add(e2)
+
+ nearby := aoi.GetNearby(e1)
+ if len(nearby) != 0 {
+ t.Errorf("expected 0 nearby for far entity, got %d", len(nearby))
+ }
+}
+
+func TestGridAOI_MoveGeneratesEvents(t *testing.T) {
+ aoi := NewGridAOI(50, 1)
+
+ e1 := &mockEntity{id: 1, pos: mathutil.NewVec3(0, 0, 0)}
+ e2 := &mockEntity{id: 2, pos: mathutil.NewVec3(200, 0, 200)}
+
+ aoi.Add(e1)
+ aoi.Add(e2)
+
+ // Initially not visible to each other.
+ nearby := aoi.GetNearby(e1)
+ if len(nearby) != 0 {
+ t.Fatalf("expected not visible initially, got %d", len(nearby))
+ }
+
+ // Move e2 close to e1.
+ oldPos := e2.pos
+ e2.pos = mathutil.NewVec3(10, 0, 10)
+ events := aoi.UpdatePosition(e2, oldPos, e2.pos)
+
+ // Should generate enter events (e1 sees e2, e2 sees e1).
+ enterCount := 0
+ for _, evt := range events {
+ if evt.Type == AOIEnter {
+ enterCount++
+ }
+ }
+ if enterCount != 2 {
+ t.Errorf("expected 2 enter events, got %d", enterCount)
+ }
+}
+
+func TestGridAOI_ToggleComparison(t *testing.T) {
+ // Demonstrates the difference between BroadcastAll and Grid AOI.
+ e1 := &mockEntity{id: 1, pos: mathutil.NewVec3(0, 0, 0)}
+ e2 := &mockEntity{id: 2, pos: mathutil.NewVec3(500, 0, 500)}
+
+ // BroadcastAll: both visible
+ broadcast := NewBroadcastAllAOI()
+ broadcast.Add(e1)
+ broadcast.Add(e2)
+ if len(broadcast.GetNearby(e1)) != 1 {
+ t.Error("broadcast-all should see all entities")
+ }
+
+ // Grid: e2 not visible from e1 (too far)
+ grid := NewGridAOI(50, 1)
+ grid.Add(e1)
+ grid.Add(e2)
+ if len(grid.GetNearby(e1)) != 0 {
+ t.Error("grid AOI should NOT see distant entities")
+ }
+}
diff --git a/internal/world/spatial_grid.go b/internal/world/spatial_grid.go
new file mode 100644
index 0000000..622806a
--- /dev/null
+++ b/internal/world/spatial_grid.go
@@ -0,0 +1,179 @@
+package world
+
+import (
+ "a301_game_server/internal/entity"
+ "a301_game_server/pkg/mathutil"
+)
+
+// cellKey uniquely identifies a grid cell.
+type cellKey struct {
+ cx, cz int
+}
+
+// GridAOI implements AOI using a spatial grid. Entities in nearby cells are considered visible.
+type GridAOI struct {
+ cellSize float32
+ viewRange int
+ cells map[cellKey]map[uint64]entity.Entity
+ entityCell map[uint64]cellKey
+}
+
+func NewGridAOI(cellSize float32, viewRange int) *GridAOI {
+ return &GridAOI{
+ cellSize: cellSize,
+ viewRange: viewRange,
+ cells: make(map[cellKey]map[uint64]entity.Entity),
+ entityCell: make(map[uint64]cellKey),
+ }
+}
+
+func (g *GridAOI) posToCell(pos mathutil.Vec3) cellKey {
+ cx := int(pos.X / g.cellSize)
+ cz := int(pos.Z / g.cellSize)
+ if pos.X < 0 {
+ cx--
+ }
+ if pos.Z < 0 {
+ cz--
+ }
+ return cellKey{cx, cz}
+}
+
+func (g *GridAOI) Add(ent entity.Entity) {
+ cell := g.posToCell(ent.Position())
+ g.addToCell(cell, ent)
+ g.entityCell[ent.EntityID()] = cell
+}
+
+func (g *GridAOI) Remove(ent entity.Entity) []AOIEvent {
+ eid := ent.EntityID()
+ cell, ok := g.entityCell[eid]
+ if !ok {
+ return nil
+ }
+
+ nearby := g.getNearbyFromCell(cell, eid)
+ events := make([]AOIEvent, 0, len(nearby))
+ for _, observer := range nearby {
+ events = append(events, AOIEvent{Observer: observer, Target: ent, Type: AOILeave})
+ }
+
+ g.removeFromCell(cell, eid)
+ delete(g.entityCell, eid)
+ return events
+}
+
+func (g *GridAOI) UpdatePosition(ent entity.Entity, oldPos, newPos mathutil.Vec3) []AOIEvent {
+ eid := ent.EntityID()
+ oldCell := g.posToCell(oldPos)
+ newCell := g.posToCell(newPos)
+
+ if oldCell == newCell {
+ return nil
+ }
+
+ oldVisible := g.visibleCells(oldCell)
+ newVisible := g.visibleCells(newCell)
+
+ leaving := cellDifference(oldVisible, newVisible)
+ entering := cellDifference(newVisible, oldVisible)
+
+ var events []AOIEvent
+
+ for _, c := range leaving {
+ if cellEntities, ok := g.cells[c]; ok {
+ for _, other := range cellEntities {
+ if other.EntityID() == eid {
+ continue
+ }
+ events = append(events,
+ AOIEvent{Observer: other, Target: ent, Type: AOILeave},
+ AOIEvent{Observer: ent, Target: other, Type: AOILeave},
+ )
+ }
+ }
+ }
+
+ for _, c := range entering {
+ if cellEntities, ok := g.cells[c]; ok {
+ for _, other := range cellEntities {
+ if other.EntityID() == eid {
+ continue
+ }
+ events = append(events,
+ AOIEvent{Observer: other, Target: ent, Type: AOIEnter},
+ AOIEvent{Observer: ent, Target: other, Type: AOIEnter},
+ )
+ }
+ }
+ }
+
+ g.removeFromCell(oldCell, eid)
+ g.addToCell(newCell, ent)
+ g.entityCell[eid] = newCell
+
+ return events
+}
+
+func (g *GridAOI) GetNearby(ent entity.Entity) []entity.Entity {
+ cell, ok := g.entityCell[ent.EntityID()]
+ if !ok {
+ return nil
+ }
+ return g.getNearbyFromCell(cell, ent.EntityID())
+}
+
+func (g *GridAOI) getNearbyFromCell(cell cellKey, excludeID uint64) []entity.Entity {
+ var result []entity.Entity
+ for _, c := range g.visibleCells(cell) {
+ if cellEntities, ok := g.cells[c]; ok {
+ for _, e := range cellEntities {
+ if e.EntityID() != excludeID {
+ result = append(result, e)
+ }
+ }
+ }
+ }
+ return result
+}
+
+func (g *GridAOI) visibleCells(center cellKey) []cellKey {
+ size := (2*g.viewRange + 1) * (2*g.viewRange + 1)
+ cells := make([]cellKey, 0, size)
+ for dx := -g.viewRange; dx <= g.viewRange; dx++ {
+ for dz := -g.viewRange; dz <= g.viewRange; dz++ {
+ cells = append(cells, cellKey{center.cx + dx, center.cz + dz})
+ }
+ }
+ return cells
+}
+
+func (g *GridAOI) addToCell(cell cellKey, ent entity.Entity) {
+ if g.cells[cell] == nil {
+ g.cells[cell] = make(map[uint64]entity.Entity)
+ }
+ g.cells[cell][ent.EntityID()] = ent
+}
+
+func (g *GridAOI) removeFromCell(cell cellKey, eid uint64) {
+ if m, ok := g.cells[cell]; ok {
+ delete(m, eid)
+ if len(m) == 0 {
+ delete(g.cells, cell)
+ }
+ }
+}
+
+func cellDifference(a, b []cellKey) []cellKey {
+ set := make(map[cellKey]struct{}, len(b))
+ for _, c := range b {
+ set[c] = struct{}{}
+ }
+ var diff []cellKey
+ for _, c := range a {
+ if _, ok := set[c]; !ok {
+ diff = append(diff, c)
+ }
+ }
+ return diff
+}
diff --git a/internal/world/zone_transfer.go b/internal/world/zone_transfer.go
new file mode 100644
index 0000000..b059ff1
--- /dev/null
+++ b/internal/world/zone_transfer.go
@@ -0,0 +1,20 @@
+package world
+
+import "a301_game_server/pkg/mathutil"
+
+// ZonePortal defines a connection between two zones.
+type ZonePortal struct {
+ // Trigger area in source zone.
+ SourceZoneID uint32
+ TriggerPos mathutil.Vec3
+ TriggerRadius float32
+
+ // Destination in target zone.
+ TargetZoneID uint32
+ TargetPos mathutil.Vec3
+}
+
+// IsInRange returns true if the given position is within the portal's trigger area.
+func (p *ZonePortal) IsInRange(pos mathutil.Vec3) bool {
+ return pos.DistanceXZ(p.TriggerPos) <= p.TriggerRadius
+}
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
new file mode 100644
index 0000000..2afbb69
--- /dev/null
+++ b/pkg/logger/logger.go
@@ -0,0 +1,43 @@
+package logger
+
+import (
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+var log *zap.SugaredLogger
+
+func Init(level string) error {
+ var zapLevel zapcore.Level
+ if err := zapLevel.UnmarshalText([]byte(level)); err != nil {
+ zapLevel = zapcore.InfoLevel
+ }
+
+ cfg := zap.Config{
+ Level: zap.NewAtomicLevelAt(zapLevel),
+ Encoding: "console",
+ EncoderConfig: zap.NewDevelopmentEncoderConfig(),
+ OutputPaths: []string{"stdout"},
+ ErrorOutputPaths: []string{"stderr"},
+ }
+
+ l, err := cfg.Build(zap.AddCallerSkip(1))
+ if err != nil {
+ return err
+ }
+
+ log = l.Sugar()
+ return nil
+}
+
+func Info(msg string, keysAndValues ...interface{}) { log.Infow(msg, keysAndValues...) }
+func Warn(msg string, keysAndValues ...interface{}) { log.Warnw(msg, keysAndValues...) }
+func Error(msg string, keysAndValues ...interface{}) { log.Errorw(msg, keysAndValues...) }
+func Debug(msg string, keysAndValues ...interface{}) { log.Debugw(msg, keysAndValues...) }
+func Fatal(msg string, keysAndValues ...interface{}) { log.Fatalw(msg, keysAndValues...) }
+
+func Sync() {
+ if log != nil {
+ _ = log.Sync()
+ }
+}
diff --git a/pkg/mathutil/vec3.go b/pkg/mathutil/vec3.go
new file mode 100644
index 0000000..1d97c88
--- /dev/null
+++ b/pkg/mathutil/vec3.go
@@ -0,0 +1,56 @@
+package mathutil
+
+import "math"
+
+type Vec3 struct {
+ X float32
+ Y float32
+ Z float32
+}
+
+func NewVec3(x, y, z float32) Vec3 {
+ return Vec3{X: x, Y: y, Z: z}
+}
+
+func (v Vec3) Add(other Vec3) Vec3 {
+ return Vec3{X: v.X + other.X, Y: v.Y + other.Y, Z: v.Z + other.Z}
+}
+
+func (v Vec3) Sub(other Vec3) Vec3 {
+ return Vec3{X: v.X - other.X, Y: v.Y - other.Y, Z: v.Z - other.Z}
+}
+
+func (v Vec3) Scale(s float32) Vec3 {
+ return Vec3{X: v.X * s, Y: v.Y * s, Z: v.Z * s}
+}
+
+func (v Vec3) Length() float32 {
+ return float32(math.Sqrt(float64(v.X*v.X + v.Y*v.Y + v.Z*v.Z)))
+}
+
+func (v Vec3) LengthSq() float32 {
+ return v.X*v.X + v.Y*v.Y + v.Z*v.Z
+}
+
+func (v Vec3) Normalize() Vec3 {
+ l := v.Length()
+ if l < 1e-8 {
+ return Vec3{}
+ }
+ return v.Scale(1.0 / l)
+}
+
+func (v Vec3) DistanceTo(other Vec3) float32 {
+ return v.Sub(other).Length()
+}
+
+func (v Vec3) DistanceSqTo(other Vec3) float32 {
+ return v.Sub(other).LengthSq()
+}
+
+// DistanceXZ returns distance ignoring Y axis (for ground-plane calculations).
+func (v Vec3) DistanceXZ(other Vec3) float32 {
+ dx := v.X - other.X
+ dz := v.Z - other.Z
+ return float32(math.Sqrt(float64(dx*dx + dz*dz)))
+}
diff --git a/proto/gen/pb/messages.pb.go b/proto/gen/pb/messages.pb.go
new file mode 100644
index 0000000..d1b03bb
--- /dev/null
+++ b/proto/gen/pb/messages.pb.go
@@ -0,0 +1,1728 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.36.11
+// protoc v7.34.0
+// source: messages.proto
+
+package pb
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+ unsafe "unsafe"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type EntityType int32
+
+const (
+ EntityType_ENTITY_TYPE_PLAYER EntityType = 0
+ EntityType_ENTITY_TYPE_MOB EntityType = 1
+ EntityType_ENTITY_TYPE_NPC EntityType = 2
+)
+
+// Enum value maps for EntityType.
+var (
+ EntityType_name = map[int32]string{
+ 0: "ENTITY_TYPE_PLAYER",
+ 1: "ENTITY_TYPE_MOB",
+ 2: "ENTITY_TYPE_NPC",
+ }
+ EntityType_value = map[string]int32{
+ "ENTITY_TYPE_PLAYER": 0,
+ "ENTITY_TYPE_MOB": 1,
+ "ENTITY_TYPE_NPC": 2,
+ }
+)
+
+func (x EntityType) Enum() *EntityType {
+ p := new(EntityType)
+ *p = x
+ return p
+}
+
+func (x EntityType) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (EntityType) Descriptor() protoreflect.EnumDescriptor {
+ return file_messages_proto_enumTypes[0].Descriptor()
+}
+
+func (EntityType) Type() protoreflect.EnumType {
+ return &file_messages_proto_enumTypes[0]
+}
+
+func (x EntityType) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use EntityType.Descriptor instead.
+func (EntityType) EnumDescriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{0}
+}
+
+type CombatEventType int32
+
+const (
+ CombatEventType_COMBAT_EVENT_DAMAGE CombatEventType = 0
+ CombatEventType_COMBAT_EVENT_HEAL CombatEventType = 1
+ CombatEventType_COMBAT_EVENT_BUFF CombatEventType = 2
+ CombatEventType_COMBAT_EVENT_DEBUFF CombatEventType = 3
+ CombatEventType_COMBAT_EVENT_DEATH CombatEventType = 4
+ CombatEventType_COMBAT_EVENT_RESPAWN CombatEventType = 5
+)
+
+// Enum value maps for CombatEventType.
+var (
+ CombatEventType_name = map[int32]string{
+ 0: "COMBAT_EVENT_DAMAGE",
+ 1: "COMBAT_EVENT_HEAL",
+ 2: "COMBAT_EVENT_BUFF",
+ 3: "COMBAT_EVENT_DEBUFF",
+ 4: "COMBAT_EVENT_DEATH",
+ 5: "COMBAT_EVENT_RESPAWN",
+ }
+ CombatEventType_value = map[string]int32{
+ "COMBAT_EVENT_DAMAGE": 0,
+ "COMBAT_EVENT_HEAL": 1,
+ "COMBAT_EVENT_BUFF": 2,
+ "COMBAT_EVENT_DEBUFF": 3,
+ "COMBAT_EVENT_DEATH": 4,
+ "COMBAT_EVENT_RESPAWN": 5,
+ }
+)
+
+func (x CombatEventType) Enum() *CombatEventType {
+ p := new(CombatEventType)
+ *p = x
+ return p
+}
+
+func (x CombatEventType) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (CombatEventType) Descriptor() protoreflect.EnumDescriptor {
+ return file_messages_proto_enumTypes[1].Descriptor()
+}
+
+func (CombatEventType) Type() protoreflect.EnumType {
+ return &file_messages_proto_enumTypes[1]
+}
+
+func (x CombatEventType) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use CombatEventType.Descriptor instead.
+func (CombatEventType) EnumDescriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{1}
+}
+
+type LoginRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
+ Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *LoginRequest) Reset() {
+ *x = LoginRequest{}
+ mi := &file_messages_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *LoginRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*LoginRequest) ProtoMessage() {}
+
+func (x *LoginRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead.
+func (*LoginRequest) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *LoginRequest) GetUsername() string {
+ if x != nil {
+ return x.Username
+ }
+ return ""
+}
+
+func (x *LoginRequest) GetPassword() string {
+ if x != nil {
+ return x.Password
+ }
+ return ""
+}
+
+type LoginResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
+ SessionToken string `protobuf:"bytes,2,opt,name=session_token,json=sessionToken,proto3" json:"session_token,omitempty"`
+ ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
+ PlayerId uint64 `protobuf:"varint,4,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *LoginResponse) Reset() {
+ *x = LoginResponse{}
+ mi := &file_messages_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *LoginResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*LoginResponse) ProtoMessage() {}
+
+func (x *LoginResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead.
+func (*LoginResponse) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *LoginResponse) GetSuccess() bool {
+ if x != nil {
+ return x.Success
+ }
+ return false
+}
+
+func (x *LoginResponse) GetSessionToken() string {
+ if x != nil {
+ return x.SessionToken
+ }
+ return ""
+}
+
+func (x *LoginResponse) GetErrorMessage() string {
+ if x != nil {
+ return x.ErrorMessage
+ }
+ return ""
+}
+
+func (x *LoginResponse) GetPlayerId() uint64 {
+ if x != nil {
+ return x.PlayerId
+ }
+ return 0
+}
+
+type EnterWorldRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ SessionToken string `protobuf:"bytes,1,opt,name=session_token,json=sessionToken,proto3" json:"session_token,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *EnterWorldRequest) Reset() {
+ *x = EnterWorldRequest{}
+ mi := &file_messages_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *EnterWorldRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*EnterWorldRequest) ProtoMessage() {}
+
+func (x *EnterWorldRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use EnterWorldRequest.ProtoReflect.Descriptor instead.
+func (*EnterWorldRequest) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *EnterWorldRequest) GetSessionToken() string {
+ if x != nil {
+ return x.SessionToken
+ }
+ return ""
+}
+
+type EnterWorldResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
+ ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
+ Self *EntityState `protobuf:"bytes,3,opt,name=self,proto3" json:"self,omitempty"`
+ NearbyEntities []*EntityState `protobuf:"bytes,4,rep,name=nearby_entities,json=nearbyEntities,proto3" json:"nearby_entities,omitempty"`
+ ZoneId uint32 `protobuf:"varint,5,opt,name=zone_id,json=zoneId,proto3" json:"zone_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *EnterWorldResponse) Reset() {
+ *x = EnterWorldResponse{}
+ mi := &file_messages_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *EnterWorldResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*EnterWorldResponse) ProtoMessage() {}
+
+func (x *EnterWorldResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use EnterWorldResponse.ProtoReflect.Descriptor instead.
+func (*EnterWorldResponse) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *EnterWorldResponse) GetSuccess() bool {
+ if x != nil {
+ return x.Success
+ }
+ return false
+}
+
+func (x *EnterWorldResponse) GetErrorMessage() string {
+ if x != nil {
+ return x.ErrorMessage
+ }
+ return ""
+}
+
+func (x *EnterWorldResponse) GetSelf() *EntityState {
+ if x != nil {
+ return x.Self
+ }
+ return nil
+}
+
+func (x *EnterWorldResponse) GetNearbyEntities() []*EntityState {
+ if x != nil {
+ return x.NearbyEntities
+ }
+ return nil
+}
+
+func (x *EnterWorldResponse) GetZoneId() uint32 {
+ if x != nil {
+ return x.ZoneId
+ }
+ return 0
+}
+
+type Vector3 struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ X float32 `protobuf:"fixed32,1,opt,name=x,proto3" json:"x,omitempty"`
+ Y float32 `protobuf:"fixed32,2,opt,name=y,proto3" json:"y,omitempty"`
+ Z float32 `protobuf:"fixed32,3,opt,name=z,proto3" json:"z,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Vector3) Reset() {
+ *x = Vector3{}
+ mi := &file_messages_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Vector3) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Vector3) ProtoMessage() {}
+
+func (x *Vector3) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Vector3.ProtoReflect.Descriptor instead.
+func (*Vector3) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *Vector3) GetX() float32 {
+ if x != nil {
+ return x.X
+ }
+ return 0
+}
+
+func (x *Vector3) GetY() float32 {
+ if x != nil {
+ return x.Y
+ }
+ return 0
+}
+
+func (x *Vector3) GetZ() float32 {
+ if x != nil {
+ return x.Z
+ }
+ return 0
+}
+
+type EntityState struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ EntityId uint64 `protobuf:"varint,1,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"`
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+ Position *Vector3 `protobuf:"bytes,3,opt,name=position,proto3" json:"position,omitempty"`
+ Rotation float32 `protobuf:"fixed32,4,opt,name=rotation,proto3" json:"rotation,omitempty"`
+ Hp int32 `protobuf:"varint,5,opt,name=hp,proto3" json:"hp,omitempty"`
+ MaxHp int32 `protobuf:"varint,6,opt,name=max_hp,json=maxHp,proto3" json:"max_hp,omitempty"`
+ Level int32 `protobuf:"varint,7,opt,name=level,proto3" json:"level,omitempty"`
+ EntityType EntityType `protobuf:"varint,8,opt,name=entity_type,json=entityType,proto3,enum=proto.EntityType" json:"entity_type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *EntityState) Reset() {
+ *x = EntityState{}
+ mi := &file_messages_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *EntityState) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*EntityState) ProtoMessage() {}
+
+func (x *EntityState) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use EntityState.ProtoReflect.Descriptor instead.
+func (*EntityState) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *EntityState) GetEntityId() uint64 {
+ if x != nil {
+ return x.EntityId
+ }
+ return 0
+}
+
+func (x *EntityState) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *EntityState) GetPosition() *Vector3 {
+ if x != nil {
+ return x.Position
+ }
+ return nil
+}
+
+func (x *EntityState) GetRotation() float32 {
+ if x != nil {
+ return x.Rotation
+ }
+ return 0
+}
+
+func (x *EntityState) GetHp() int32 {
+ if x != nil {
+ return x.Hp
+ }
+ return 0
+}
+
+func (x *EntityState) GetMaxHp() int32 {
+ if x != nil {
+ return x.MaxHp
+ }
+ return 0
+}
+
+func (x *EntityState) GetLevel() int32 {
+ if x != nil {
+ return x.Level
+ }
+ return 0
+}
+
+func (x *EntityState) GetEntityType() EntityType {
+ if x != nil {
+ return x.EntityType
+ }
+ return EntityType_ENTITY_TYPE_PLAYER
+}
+
+type MoveRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Position *Vector3 `protobuf:"bytes,1,opt,name=position,proto3" json:"position,omitempty"`
+ Rotation float32 `protobuf:"fixed32,2,opt,name=rotation,proto3" json:"rotation,omitempty"`
+ Velocity *Vector3 `protobuf:"bytes,3,opt,name=velocity,proto3" json:"velocity,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MoveRequest) Reset() {
+ *x = MoveRequest{}
+ mi := &file_messages_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MoveRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MoveRequest) ProtoMessage() {}
+
+func (x *MoveRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[6]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MoveRequest.ProtoReflect.Descriptor instead.
+func (*MoveRequest) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *MoveRequest) GetPosition() *Vector3 {
+ if x != nil {
+ return x.Position
+ }
+ return nil
+}
+
+func (x *MoveRequest) GetRotation() float32 {
+ if x != nil {
+ return x.Rotation
+ }
+ return 0
+}
+
+func (x *MoveRequest) GetVelocity() *Vector3 {
+ if x != nil {
+ return x.Velocity
+ }
+ return nil
+}
+
+type StateUpdate struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Entities []*EntityState `protobuf:"bytes,1,rep,name=entities,proto3" json:"entities,omitempty"`
+ ServerTick int64 `protobuf:"varint,2,opt,name=server_tick,json=serverTick,proto3" json:"server_tick,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *StateUpdate) Reset() {
+ *x = StateUpdate{}
+ mi := &file_messages_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *StateUpdate) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StateUpdate) ProtoMessage() {}
+
+func (x *StateUpdate) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[7]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use StateUpdate.ProtoReflect.Descriptor instead.
+func (*StateUpdate) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *StateUpdate) GetEntities() []*EntityState {
+ if x != nil {
+ return x.Entities
+ }
+ return nil
+}
+
+func (x *StateUpdate) GetServerTick() int64 {
+ if x != nil {
+ return x.ServerTick
+ }
+ return 0
+}
+
+type SpawnEntity struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Entity *EntityState `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *SpawnEntity) Reset() {
+ *x = SpawnEntity{}
+ mi := &file_messages_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *SpawnEntity) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SpawnEntity) ProtoMessage() {}
+
+func (x *SpawnEntity) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[8]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SpawnEntity.ProtoReflect.Descriptor instead.
+func (*SpawnEntity) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *SpawnEntity) GetEntity() *EntityState {
+ if x != nil {
+ return x.Entity
+ }
+ return nil
+}
+
+type DespawnEntity struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ EntityId uint64 `protobuf:"varint,1,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *DespawnEntity) Reset() {
+ *x = DespawnEntity{}
+ mi := &file_messages_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *DespawnEntity) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DespawnEntity) ProtoMessage() {}
+
+func (x *DespawnEntity) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[9]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DespawnEntity.ProtoReflect.Descriptor instead.
+func (*DespawnEntity) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *DespawnEntity) GetEntityId() uint64 {
+ if x != nil {
+ return x.EntityId
+ }
+ return 0
+}
+
+type Ping struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ClientTime int64 `protobuf:"varint,1,opt,name=client_time,json=clientTime,proto3" json:"client_time,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Ping) Reset() {
+ *x = Ping{}
+ mi := &file_messages_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Ping) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Ping) ProtoMessage() {}
+
+func (x *Ping) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[10]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Ping.ProtoReflect.Descriptor instead.
+func (*Ping) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *Ping) GetClientTime() int64 {
+ if x != nil {
+ return x.ClientTime
+ }
+ return 0
+}
+
+type Pong struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ClientTime int64 `protobuf:"varint,1,opt,name=client_time,json=clientTime,proto3" json:"client_time,omitempty"`
+ ServerTime int64 `protobuf:"varint,2,opt,name=server_time,json=serverTime,proto3" json:"server_time,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Pong) Reset() {
+ *x = Pong{}
+ mi := &file_messages_proto_msgTypes[11]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Pong) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Pong) ProtoMessage() {}
+
+func (x *Pong) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[11]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Pong.ProtoReflect.Descriptor instead.
+func (*Pong) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{11}
+}
+
+func (x *Pong) GetClientTime() int64 {
+ if x != nil {
+ return x.ClientTime
+ }
+ return 0
+}
+
+func (x *Pong) GetServerTime() int64 {
+ if x != nil {
+ return x.ServerTime
+ }
+ return 0
+}
+
+type ZoneTransferNotify struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ NewZoneId uint32 `protobuf:"varint,1,opt,name=new_zone_id,json=newZoneId,proto3" json:"new_zone_id,omitempty"`
+ Self *EntityState `protobuf:"bytes,2,opt,name=self,proto3" json:"self,omitempty"`
+ NearbyEntities []*EntityState `protobuf:"bytes,3,rep,name=nearby_entities,json=nearbyEntities,proto3" json:"nearby_entities,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ZoneTransferNotify) Reset() {
+ *x = ZoneTransferNotify{}
+ mi := &file_messages_proto_msgTypes[12]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ZoneTransferNotify) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ZoneTransferNotify) ProtoMessage() {}
+
+func (x *ZoneTransferNotify) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[12]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ZoneTransferNotify.ProtoReflect.Descriptor instead.
+func (*ZoneTransferNotify) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{12}
+}
+
+func (x *ZoneTransferNotify) GetNewZoneId() uint32 {
+ if x != nil {
+ return x.NewZoneId
+ }
+ return 0
+}
+
+func (x *ZoneTransferNotify) GetSelf() *EntityState {
+ if x != nil {
+ return x.Self
+ }
+ return nil
+}
+
+func (x *ZoneTransferNotify) GetNearbyEntities() []*EntityState {
+ if x != nil {
+ return x.NearbyEntities
+ }
+ return nil
+}
+
+type UseSkillRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ SkillId uint32 `protobuf:"varint,1,opt,name=skill_id,json=skillId,proto3" json:"skill_id,omitempty"`
+ TargetId uint64 `protobuf:"varint,2,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty"`
+ TargetPos *Vector3 `protobuf:"bytes,3,opt,name=target_pos,json=targetPos,proto3" json:"target_pos,omitempty"` // for ground-targeted AoE
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *UseSkillRequest) Reset() {
+ *x = UseSkillRequest{}
+ mi := &file_messages_proto_msgTypes[13]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *UseSkillRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UseSkillRequest) ProtoMessage() {}
+
+func (x *UseSkillRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[13]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UseSkillRequest.ProtoReflect.Descriptor instead.
+func (*UseSkillRequest) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{13}
+}
+
+func (x *UseSkillRequest) GetSkillId() uint32 {
+ if x != nil {
+ return x.SkillId
+ }
+ return 0
+}
+
+func (x *UseSkillRequest) GetTargetId() uint64 {
+ if x != nil {
+ return x.TargetId
+ }
+ return 0
+}
+
+func (x *UseSkillRequest) GetTargetPos() *Vector3 {
+ if x != nil {
+ return x.TargetPos
+ }
+ return nil
+}
+
+type UseSkillResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
+ ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *UseSkillResponse) Reset() {
+ *x = UseSkillResponse{}
+ mi := &file_messages_proto_msgTypes[14]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *UseSkillResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UseSkillResponse) ProtoMessage() {}
+
+func (x *UseSkillResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[14]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UseSkillResponse.ProtoReflect.Descriptor instead.
+func (*UseSkillResponse) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{14}
+}
+
+func (x *UseSkillResponse) GetSuccess() bool {
+ if x != nil {
+ return x.Success
+ }
+ return false
+}
+
+func (x *UseSkillResponse) GetErrorMessage() string {
+ if x != nil {
+ return x.ErrorMessage
+ }
+ return ""
+}
+
+type CombatEvent struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ CasterId uint64 `protobuf:"varint,1,opt,name=caster_id,json=casterId,proto3" json:"caster_id,omitempty"`
+ TargetId uint64 `protobuf:"varint,2,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty"`
+ SkillId uint32 `protobuf:"varint,3,opt,name=skill_id,json=skillId,proto3" json:"skill_id,omitempty"`
+ Damage int32 `protobuf:"varint,4,opt,name=damage,proto3" json:"damage,omitempty"`
+ Heal int32 `protobuf:"varint,5,opt,name=heal,proto3" json:"heal,omitempty"`
+ IsCritical bool `protobuf:"varint,6,opt,name=is_critical,json=isCritical,proto3" json:"is_critical,omitempty"`
+ TargetDied bool `protobuf:"varint,7,opt,name=target_died,json=targetDied,proto3" json:"target_died,omitempty"`
+ TargetHp int32 `protobuf:"varint,8,opt,name=target_hp,json=targetHp,proto3" json:"target_hp,omitempty"`
+ TargetMaxHp int32 `protobuf:"varint,9,opt,name=target_max_hp,json=targetMaxHp,proto3" json:"target_max_hp,omitempty"`
+ EventType CombatEventType `protobuf:"varint,10,opt,name=event_type,json=eventType,proto3,enum=proto.CombatEventType" json:"event_type,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CombatEvent) Reset() {
+ *x = CombatEvent{}
+ mi := &file_messages_proto_msgTypes[15]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CombatEvent) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CombatEvent) ProtoMessage() {}
+
+func (x *CombatEvent) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[15]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CombatEvent.ProtoReflect.Descriptor instead.
+func (*CombatEvent) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{15}
+}
+
+func (x *CombatEvent) GetCasterId() uint64 {
+ if x != nil {
+ return x.CasterId
+ }
+ return 0
+}
+
+func (x *CombatEvent) GetTargetId() uint64 {
+ if x != nil {
+ return x.TargetId
+ }
+ return 0
+}
+
+func (x *CombatEvent) GetSkillId() uint32 {
+ if x != nil {
+ return x.SkillId
+ }
+ return 0
+}
+
+func (x *CombatEvent) GetDamage() int32 {
+ if x != nil {
+ return x.Damage
+ }
+ return 0
+}
+
+func (x *CombatEvent) GetHeal() int32 {
+ if x != nil {
+ return x.Heal
+ }
+ return 0
+}
+
+func (x *CombatEvent) GetIsCritical() bool {
+ if x != nil {
+ return x.IsCritical
+ }
+ return false
+}
+
+func (x *CombatEvent) GetTargetDied() bool {
+ if x != nil {
+ return x.TargetDied
+ }
+ return false
+}
+
+func (x *CombatEvent) GetTargetHp() int32 {
+ if x != nil {
+ return x.TargetHp
+ }
+ return 0
+}
+
+func (x *CombatEvent) GetTargetMaxHp() int32 {
+ if x != nil {
+ return x.TargetMaxHp
+ }
+ return 0
+}
+
+func (x *CombatEvent) GetEventType() CombatEventType {
+ if x != nil {
+ return x.EventType
+ }
+ return CombatEventType_COMBAT_EVENT_DAMAGE
+}
+
+type BuffApplied struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ TargetId uint64 `protobuf:"varint,1,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty"`
+ BuffId uint32 `protobuf:"varint,2,opt,name=buff_id,json=buffId,proto3" json:"buff_id,omitempty"`
+ BuffName string `protobuf:"bytes,3,opt,name=buff_name,json=buffName,proto3" json:"buff_name,omitempty"`
+ Duration float32 `protobuf:"fixed32,4,opt,name=duration,proto3" json:"duration,omitempty"`
+ IsDebuff bool `protobuf:"varint,5,opt,name=is_debuff,json=isDebuff,proto3" json:"is_debuff,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *BuffApplied) Reset() {
+ *x = BuffApplied{}
+ mi := &file_messages_proto_msgTypes[16]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *BuffApplied) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuffApplied) ProtoMessage() {}
+
+func (x *BuffApplied) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[16]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuffApplied.ProtoReflect.Descriptor instead.
+func (*BuffApplied) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{16}
+}
+
+func (x *BuffApplied) GetTargetId() uint64 {
+ if x != nil {
+ return x.TargetId
+ }
+ return 0
+}
+
+func (x *BuffApplied) GetBuffId() uint32 {
+ if x != nil {
+ return x.BuffId
+ }
+ return 0
+}
+
+func (x *BuffApplied) GetBuffName() string {
+ if x != nil {
+ return x.BuffName
+ }
+ return ""
+}
+
+func (x *BuffApplied) GetDuration() float32 {
+ if x != nil {
+ return x.Duration
+ }
+ return 0
+}
+
+func (x *BuffApplied) GetIsDebuff() bool {
+ if x != nil {
+ return x.IsDebuff
+ }
+ return false
+}
+
+type BuffRemoved struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ TargetId uint64 `protobuf:"varint,1,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty"`
+ BuffId uint32 `protobuf:"varint,2,opt,name=buff_id,json=buffId,proto3" json:"buff_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *BuffRemoved) Reset() {
+ *x = BuffRemoved{}
+ mi := &file_messages_proto_msgTypes[17]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *BuffRemoved) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuffRemoved) ProtoMessage() {}
+
+func (x *BuffRemoved) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[17]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuffRemoved.ProtoReflect.Descriptor instead.
+func (*BuffRemoved) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{17}
+}
+
+func (x *BuffRemoved) GetTargetId() uint64 {
+ if x != nil {
+ return x.TargetId
+ }
+ return 0
+}
+
+func (x *BuffRemoved) GetBuffId() uint32 {
+ if x != nil {
+ return x.BuffId
+ }
+ return 0
+}
+
+type RespawnRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *RespawnRequest) Reset() {
+ *x = RespawnRequest{}
+ mi := &file_messages_proto_msgTypes[18]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *RespawnRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RespawnRequest) ProtoMessage() {}
+
+func (x *RespawnRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[18]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RespawnRequest.ProtoReflect.Descriptor instead.
+func (*RespawnRequest) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{18}
+}
+
+type RespawnResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Self *EntityState `protobuf:"bytes,1,opt,name=self,proto3" json:"self,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *RespawnResponse) Reset() {
+ *x = RespawnResponse{}
+ mi := &file_messages_proto_msgTypes[19]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *RespawnResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RespawnResponse) ProtoMessage() {}
+
+func (x *RespawnResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[19]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RespawnResponse.ProtoReflect.Descriptor instead.
+func (*RespawnResponse) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{19}
+}
+
+func (x *RespawnResponse) GetSelf() *EntityState {
+ if x != nil {
+ return x.Self
+ }
+ return nil
+}
+
+type AOIToggleRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *AOIToggleRequest) Reset() {
+ *x = AOIToggleRequest{}
+ mi := &file_messages_proto_msgTypes[20]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *AOIToggleRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AOIToggleRequest) ProtoMessage() {}
+
+func (x *AOIToggleRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[20]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AOIToggleRequest.ProtoReflect.Descriptor instead.
+func (*AOIToggleRequest) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{20}
+}
+
+func (x *AOIToggleRequest) GetEnabled() bool {
+ if x != nil {
+ return x.Enabled
+ }
+ return false
+}
+
+type AOIToggleResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
+ Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *AOIToggleResponse) Reset() {
+ *x = AOIToggleResponse{}
+ mi := &file_messages_proto_msgTypes[21]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *AOIToggleResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AOIToggleResponse) ProtoMessage() {}
+
+func (x *AOIToggleResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[21]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AOIToggleResponse.ProtoReflect.Descriptor instead.
+func (*AOIToggleResponse) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{21}
+}
+
+func (x *AOIToggleResponse) GetEnabled() bool {
+ if x != nil {
+ return x.Enabled
+ }
+ return false
+}
+
+func (x *AOIToggleResponse) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+type ServerMetrics struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ OnlinePlayers int32 `protobuf:"varint,1,opt,name=online_players,json=onlinePlayers,proto3" json:"online_players,omitempty"`
+ TotalEntities int32 `protobuf:"varint,2,opt,name=total_entities,json=totalEntities,proto3" json:"total_entities,omitempty"`
+ TickDurationUs int64 `protobuf:"varint,3,opt,name=tick_duration_us,json=tickDurationUs,proto3" json:"tick_duration_us,omitempty"`
+ AoiEnabled bool `protobuf:"varint,4,opt,name=aoi_enabled,json=aoiEnabled,proto3" json:"aoi_enabled,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ServerMetrics) Reset() {
+ *x = ServerMetrics{}
+ mi := &file_messages_proto_msgTypes[22]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ServerMetrics) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ServerMetrics) ProtoMessage() {}
+
+func (x *ServerMetrics) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[22]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ServerMetrics.ProtoReflect.Descriptor instead.
+func (*ServerMetrics) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{22}
+}
+
+func (x *ServerMetrics) GetOnlinePlayers() int32 {
+ if x != nil {
+ return x.OnlinePlayers
+ }
+ return 0
+}
+
+func (x *ServerMetrics) GetTotalEntities() int32 {
+ if x != nil {
+ return x.TotalEntities
+ }
+ return 0
+}
+
+func (x *ServerMetrics) GetTickDurationUs() int64 {
+ if x != nil {
+ return x.TickDurationUs
+ }
+ return 0
+}
+
+func (x *ServerMetrics) GetAoiEnabled() bool {
+ if x != nil {
+ return x.AoiEnabled
+ }
+ return false
+}
+
+type MetricsRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MetricsRequest) Reset() {
+ *x = MetricsRequest{}
+ mi := &file_messages_proto_msgTypes[23]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MetricsRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MetricsRequest) ProtoMessage() {}
+
+func (x *MetricsRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[23]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MetricsRequest.ProtoReflect.Descriptor instead.
+func (*MetricsRequest) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{23}
+}
+
+var File_messages_proto protoreflect.FileDescriptor
+
+const file_messages_proto_rawDesc = "" +
+ "\n" +
+ "\x0emessages.proto\x12\x05proto\"F\n" +
+ "\fLoginRequest\x12\x1a\n" +
+ "\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" +
+ "\bpassword\x18\x02 \x01(\tR\bpassword\"\x90\x01\n" +
+ "\rLoginResponse\x12\x18\n" +
+ "\asuccess\x18\x01 \x01(\bR\asuccess\x12#\n" +
+ "\rsession_token\x18\x02 \x01(\tR\fsessionToken\x12#\n" +
+ "\rerror_message\x18\x03 \x01(\tR\ferrorMessage\x12\x1b\n" +
+ "\tplayer_id\x18\x04 \x01(\x04R\bplayerId\"8\n" +
+ "\x11EnterWorldRequest\x12#\n" +
+ "\rsession_token\x18\x01 \x01(\tR\fsessionToken\"\xd1\x01\n" +
+ "\x12EnterWorldResponse\x12\x18\n" +
+ "\asuccess\x18\x01 \x01(\bR\asuccess\x12#\n" +
+ "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x12&\n" +
+ "\x04self\x18\x03 \x01(\v2\x12.proto.EntityStateR\x04self\x12;\n" +
+ "\x0fnearby_entities\x18\x04 \x03(\v2\x12.proto.EntityStateR\x0enearbyEntities\x12\x17\n" +
+ "\azone_id\x18\x05 \x01(\rR\x06zoneId\"3\n" +
+ "\aVector3\x12\f\n" +
+ "\x01x\x18\x01 \x01(\x02R\x01x\x12\f\n" +
+ "\x01y\x18\x02 \x01(\x02R\x01y\x12\f\n" +
+ "\x01z\x18\x03 \x01(\x02R\x01z\"\xf7\x01\n" +
+ "\vEntityState\x12\x1b\n" +
+ "\tentity_id\x18\x01 \x01(\x04R\bentityId\x12\x12\n" +
+ "\x04name\x18\x02 \x01(\tR\x04name\x12*\n" +
+ "\bposition\x18\x03 \x01(\v2\x0e.proto.Vector3R\bposition\x12\x1a\n" +
+ "\brotation\x18\x04 \x01(\x02R\brotation\x12\x0e\n" +
+ "\x02hp\x18\x05 \x01(\x05R\x02hp\x12\x15\n" +
+ "\x06max_hp\x18\x06 \x01(\x05R\x05maxHp\x12\x14\n" +
+ "\x05level\x18\a \x01(\x05R\x05level\x122\n" +
+ "\ventity_type\x18\b \x01(\x0e2\x11.proto.EntityTypeR\n" +
+ "entityType\"\x81\x01\n" +
+ "\vMoveRequest\x12*\n" +
+ "\bposition\x18\x01 \x01(\v2\x0e.proto.Vector3R\bposition\x12\x1a\n" +
+ "\brotation\x18\x02 \x01(\x02R\brotation\x12*\n" +
+ "\bvelocity\x18\x03 \x01(\v2\x0e.proto.Vector3R\bvelocity\"^\n" +
+ "\vStateUpdate\x12.\n" +
+ "\bentities\x18\x01 \x03(\v2\x12.proto.EntityStateR\bentities\x12\x1f\n" +
+ "\vserver_tick\x18\x02 \x01(\x03R\n" +
+ "serverTick\"9\n" +
+ "\vSpawnEntity\x12*\n" +
+ "\x06entity\x18\x01 \x01(\v2\x12.proto.EntityStateR\x06entity\",\n" +
+ "\rDespawnEntity\x12\x1b\n" +
+ "\tentity_id\x18\x01 \x01(\x04R\bentityId\"'\n" +
+ "\x04Ping\x12\x1f\n" +
+ "\vclient_time\x18\x01 \x01(\x03R\n" +
+ "clientTime\"H\n" +
+ "\x04Pong\x12\x1f\n" +
+ "\vclient_time\x18\x01 \x01(\x03R\n" +
+ "clientTime\x12\x1f\n" +
+ "\vserver_time\x18\x02 \x01(\x03R\n" +
+ "serverTime\"\x99\x01\n" +
+ "\x12ZoneTransferNotify\x12\x1e\n" +
+ "\vnew_zone_id\x18\x01 \x01(\rR\tnewZoneId\x12&\n" +
+ "\x04self\x18\x02 \x01(\v2\x12.proto.EntityStateR\x04self\x12;\n" +
+ "\x0fnearby_entities\x18\x03 \x03(\v2\x12.proto.EntityStateR\x0enearbyEntities\"x\n" +
+ "\x0fUseSkillRequest\x12\x19\n" +
+ "\bskill_id\x18\x01 \x01(\rR\askillId\x12\x1b\n" +
+ "\ttarget_id\x18\x02 \x01(\x04R\btargetId\x12-\n" +
+ "\n" +
+ "target_pos\x18\x03 \x01(\v2\x0e.proto.Vector3R\ttargetPos\"Q\n" +
+ "\x10UseSkillResponse\x12\x18\n" +
+ "\asuccess\x18\x01 \x01(\bR\asuccess\x12#\n" +
+ "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\"\xc8\x02\n" +
+ "\vCombatEvent\x12\x1b\n" +
+ "\tcaster_id\x18\x01 \x01(\x04R\bcasterId\x12\x1b\n" +
+ "\ttarget_id\x18\x02 \x01(\x04R\btargetId\x12\x19\n" +
+ "\bskill_id\x18\x03 \x01(\rR\askillId\x12\x16\n" +
+ "\x06damage\x18\x04 \x01(\x05R\x06damage\x12\x12\n" +
+ "\x04heal\x18\x05 \x01(\x05R\x04heal\x12\x1f\n" +
+ "\vis_critical\x18\x06 \x01(\bR\n" +
+ "isCritical\x12\x1f\n" +
+ "\vtarget_died\x18\a \x01(\bR\n" +
+ "targetDied\x12\x1b\n" +
+ "\ttarget_hp\x18\b \x01(\x05R\btargetHp\x12\"\n" +
+ "\rtarget_max_hp\x18\t \x01(\x05R\vtargetMaxHp\x125\n" +
+ "\n" +
+ "event_type\x18\n" +
+ " \x01(\x0e2\x16.proto.CombatEventTypeR\teventType\"\x99\x01\n" +
+ "\vBuffApplied\x12\x1b\n" +
+ "\ttarget_id\x18\x01 \x01(\x04R\btargetId\x12\x17\n" +
+ "\abuff_id\x18\x02 \x01(\rR\x06buffId\x12\x1b\n" +
+ "\tbuff_name\x18\x03 \x01(\tR\bbuffName\x12\x1a\n" +
+ "\bduration\x18\x04 \x01(\x02R\bduration\x12\x1b\n" +
+ "\tis_debuff\x18\x05 \x01(\bR\bisDebuff\"C\n" +
+ "\vBuffRemoved\x12\x1b\n" +
+ "\ttarget_id\x18\x01 \x01(\x04R\btargetId\x12\x17\n" +
+ "\abuff_id\x18\x02 \x01(\rR\x06buffId\"\x10\n" +
+ "\x0eRespawnRequest\"9\n" +
+ "\x0fRespawnResponse\x12&\n" +
+ "\x04self\x18\x01 \x01(\v2\x12.proto.EntityStateR\x04self\",\n" +
+ "\x10AOIToggleRequest\x12\x18\n" +
+ "\aenabled\x18\x01 \x01(\bR\aenabled\"G\n" +
+ "\x11AOIToggleResponse\x12\x18\n" +
+ "\aenabled\x18\x01 \x01(\bR\aenabled\x12\x18\n" +
+ "\amessage\x18\x02 \x01(\tR\amessage\"\xa8\x01\n" +
+ "\rServerMetrics\x12%\n" +
+ "\x0eonline_players\x18\x01 \x01(\x05R\ronlinePlayers\x12%\n" +
+ "\x0etotal_entities\x18\x02 \x01(\x05R\rtotalEntities\x12(\n" +
+ "\x10tick_duration_us\x18\x03 \x01(\x03R\x0etickDurationUs\x12\x1f\n" +
+ "\vaoi_enabled\x18\x04 \x01(\bR\n" +
+ "aoiEnabled\"\x10\n" +
+ "\x0eMetricsRequest*N\n" +
+ "\n" +
+ "EntityType\x12\x16\n" +
+ "\x12ENTITY_TYPE_PLAYER\x10\x00\x12\x13\n" +
+ "\x0fENTITY_TYPE_MOB\x10\x01\x12\x13\n" +
+ "\x0fENTITY_TYPE_NPC\x10\x02*\xa3\x01\n" +
+ "\x0fCombatEventType\x12\x17\n" +
+ "\x13COMBAT_EVENT_DAMAGE\x10\x00\x12\x15\n" +
+ "\x11COMBAT_EVENT_HEAL\x10\x01\x12\x15\n" +
+ "\x11COMBAT_EVENT_BUFF\x10\x02\x12\x17\n" +
+ "\x13COMBAT_EVENT_DEBUFF\x10\x03\x12\x16\n" +
+ "\x12COMBAT_EVENT_DEATH\x10\x04\x12\x18\n" +
+ "\x14COMBAT_EVENT_RESPAWN\x10\x05B\x1fZ\x1da301_game_server/proto/gen/pbb\x06proto3"
+
+var (
+ file_messages_proto_rawDescOnce sync.Once
+ file_messages_proto_rawDescData []byte
+)
+
+func file_messages_proto_rawDescGZIP() []byte {
+ file_messages_proto_rawDescOnce.Do(func() {
+ file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)))
+ })
+ return file_messages_proto_rawDescData
+}
+
+var file_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
+var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 24)
+var file_messages_proto_goTypes = []any{
+ (EntityType)(0), // 0: proto.EntityType
+ (CombatEventType)(0), // 1: proto.CombatEventType
+ (*LoginRequest)(nil), // 2: proto.LoginRequest
+ (*LoginResponse)(nil), // 3: proto.LoginResponse
+ (*EnterWorldRequest)(nil), // 4: proto.EnterWorldRequest
+ (*EnterWorldResponse)(nil), // 5: proto.EnterWorldResponse
+ (*Vector3)(nil), // 6: proto.Vector3
+ (*EntityState)(nil), // 7: proto.EntityState
+ (*MoveRequest)(nil), // 8: proto.MoveRequest
+ (*StateUpdate)(nil), // 9: proto.StateUpdate
+ (*SpawnEntity)(nil), // 10: proto.SpawnEntity
+ (*DespawnEntity)(nil), // 11: proto.DespawnEntity
+ (*Ping)(nil), // 12: proto.Ping
+ (*Pong)(nil), // 13: proto.Pong
+ (*ZoneTransferNotify)(nil), // 14: proto.ZoneTransferNotify
+ (*UseSkillRequest)(nil), // 15: proto.UseSkillRequest
+ (*UseSkillResponse)(nil), // 16: proto.UseSkillResponse
+ (*CombatEvent)(nil), // 17: proto.CombatEvent
+ (*BuffApplied)(nil), // 18: proto.BuffApplied
+ (*BuffRemoved)(nil), // 19: proto.BuffRemoved
+ (*RespawnRequest)(nil), // 20: proto.RespawnRequest
+ (*RespawnResponse)(nil), // 21: proto.RespawnResponse
+ (*AOIToggleRequest)(nil), // 22: proto.AOIToggleRequest
+ (*AOIToggleResponse)(nil), // 23: proto.AOIToggleResponse
+ (*ServerMetrics)(nil), // 24: proto.ServerMetrics
+ (*MetricsRequest)(nil), // 25: proto.MetricsRequest
+}
+var file_messages_proto_depIdxs = []int32{
+ 7, // 0: proto.EnterWorldResponse.self:type_name -> proto.EntityState
+ 7, // 1: proto.EnterWorldResponse.nearby_entities:type_name -> proto.EntityState
+ 6, // 2: proto.EntityState.position:type_name -> proto.Vector3
+ 0, // 3: proto.EntityState.entity_type:type_name -> proto.EntityType
+ 6, // 4: proto.MoveRequest.position:type_name -> proto.Vector3
+ 6, // 5: proto.MoveRequest.velocity:type_name -> proto.Vector3
+ 7, // 6: proto.StateUpdate.entities:type_name -> proto.EntityState
+ 7, // 7: proto.SpawnEntity.entity:type_name -> proto.EntityState
+ 7, // 8: proto.ZoneTransferNotify.self:type_name -> proto.EntityState
+ 7, // 9: proto.ZoneTransferNotify.nearby_entities:type_name -> proto.EntityState
+ 6, // 10: proto.UseSkillRequest.target_pos:type_name -> proto.Vector3
+ 1, // 11: proto.CombatEvent.event_type:type_name -> proto.CombatEventType
+ 7, // 12: proto.RespawnResponse.self:type_name -> proto.EntityState
+ 13, // [13:13] is the sub-list for method output_type
+ 13, // [13:13] is the sub-list for method input_type
+ 13, // [13:13] is the sub-list for extension type_name
+ 13, // [13:13] is the sub-list for extension extendee
+ 0, // [0:13] is the sub-list for field type_name
+}
+
+func init() { file_messages_proto_init() }
+func file_messages_proto_init() {
+ if File_messages_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)),
+ NumEnums: 2,
+ NumMessages: 24,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_messages_proto_goTypes,
+ DependencyIndexes: file_messages_proto_depIdxs,
+ EnumInfos: file_messages_proto_enumTypes,
+ MessageInfos: file_messages_proto_msgTypes,
+ }.Build()
+ File_messages_proto = out.File
+ file_messages_proto_goTypes = nil
+ file_messages_proto_depIdxs = nil
+}
diff --git a/proto/messages.proto b/proto/messages.proto
new file mode 100644
index 0000000..83ede73
--- /dev/null
+++ b/proto/messages.proto
@@ -0,0 +1,170 @@
+syntax = "proto3";
+
+package proto;
+
+option go_package = "a301_game_server/proto/gen/pb";
+
+// ─── Authentication ────────────────────────────────────────
+
+message LoginRequest {
+ string username = 1;
+ string password = 2;
+}
+
+message LoginResponse {
+ bool success = 1;
+ string session_token = 2;
+ string error_message = 3;
+ uint64 player_id = 4;
+}
+
+message EnterWorldRequest {
+ string session_token = 1;
+}
+
+message EnterWorldResponse {
+ bool success = 1;
+ string error_message = 2;
+ EntityState self = 3;
+ repeated EntityState nearby_entities = 4;
+ uint32 zone_id = 5;
+}
+
+// ─── Entity State ──────────────────────────────────────────
+
+message Vector3 {
+ float x = 1;
+ float y = 2;
+ float z = 3;
+}
+
+message EntityState {
+ uint64 entity_id = 1;
+ string name = 2;
+ Vector3 position = 3;
+ float rotation = 4;
+ int32 hp = 5;
+ int32 max_hp = 6;
+ int32 level = 7;
+ EntityType entity_type = 8;
+}
+
+enum EntityType {
+ ENTITY_TYPE_PLAYER = 0;
+ ENTITY_TYPE_MOB = 1;
+ ENTITY_TYPE_NPC = 2;
+}
+
+// ─── Movement ──────────────────────────────────────────────
+
+message MoveRequest {
+ Vector3 position = 1;
+ float rotation = 2;
+ Vector3 velocity = 3;
+}
+
+message StateUpdate {
+ repeated EntityState entities = 1;
+ int64 server_tick = 2;
+}
+
+message SpawnEntity {
+ EntityState entity = 1;
+}
+
+message DespawnEntity {
+ uint64 entity_id = 1;
+}
+
+// ─── System ────────────────────────────────────────────────
+
+message Ping {
+ int64 client_time = 1;
+}
+
+message Pong {
+ int64 client_time = 1;
+ int64 server_time = 2;
+}
+
+// ─── Zone Transfer ─────────────────────────────────────────
+
+message ZoneTransferNotify {
+ uint32 new_zone_id = 1;
+ EntityState self = 2;
+ repeated EntityState nearby_entities = 3;
+}
+
+// ─── Combat ────────────────────────────────────────────────
+
+message UseSkillRequest {
+ uint32 skill_id = 1;
+ uint64 target_id = 2;
+ Vector3 target_pos = 3; // for ground-targeted AoE
+}
+
+message UseSkillResponse {
+ bool success = 1;
+ string error_message = 2;
+}
+
+message CombatEvent {
+ uint64 caster_id = 1;
+ uint64 target_id = 2;
+ uint32 skill_id = 3;
+ int32 damage = 4;
+ int32 heal = 5;
+ bool is_critical = 6;
+ bool target_died = 7;
+ int32 target_hp = 8;
+ int32 target_max_hp = 9;
+ CombatEventType event_type = 10;
+}
+
+enum CombatEventType {
+ COMBAT_EVENT_DAMAGE = 0;
+ COMBAT_EVENT_HEAL = 1;
+ COMBAT_EVENT_BUFF = 2;
+ COMBAT_EVENT_DEBUFF = 3;
+ COMBAT_EVENT_DEATH = 4;
+ COMBAT_EVENT_RESPAWN = 5;
+}
+
+message BuffApplied {
+ uint64 target_id = 1;
+ uint32 buff_id = 2;
+ string buff_name = 3;
+ float duration = 4;
+ bool is_debuff = 5;
+}
+
+message BuffRemoved {
+ uint64 target_id = 1;
+ uint32 buff_id = 2;
+}
+
+message RespawnRequest {}
+
+message RespawnResponse {
+ EntityState self = 1;
+}
+
+// ─── Admin / Debug ─────────────────────────────────────────
+
+message AOIToggleRequest {
+ bool enabled = 1;
+}
+
+message AOIToggleResponse {
+ bool enabled = 1;
+ string message = 2;
+}
+
+message ServerMetrics {
+ int32 online_players = 1;
+ int32 total_entities = 2;
+ int64 tick_duration_us = 3;
+ bool aoi_enabled = 4;
+}
+
+message MetricsRequest {}
diff --git a/testclient.exe b/testclient.exe
new file mode 100644
index 0000000..11860db
Binary files /dev/null and b/testclient.exe differ
diff --git a/unity/Assets/Scripts/Proto/Messages.cs b/unity/Assets/Scripts/Proto/Messages.cs
new file mode 100644
index 0000000..63e78df
--- /dev/null
+++ b/unity/Assets/Scripts/Proto/Messages.cs
@@ -0,0 +1,6479 @@
+//
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: messages.proto
+//
+#pragma warning disable 1591, 0612, 3021, 8981
+#region Designer generated code
+
+using pb = global::Google.Protobuf;
+using pbc = global::Google.Protobuf.Collections;
+using pbr = global::Google.Protobuf.Reflection;
+using scg = global::System.Collections.Generic;
+namespace Proto {
+
+ /// Holder for reflection information generated from messages.proto
+ public static partial class MessagesReflection {
+
+ #region Descriptor
+ /// File descriptor for messages.proto
+ public static pbr::FileDescriptor Descriptor {
+ get { return descriptor; }
+ }
+ private static pbr::FileDescriptor descriptor;
+
+ static MessagesReflection() {
+ byte[] descriptorData = global::System.Convert.FromBase64String(
+ string.Concat(
+ "Cg5tZXNzYWdlcy5wcm90bxIFcHJvdG8iMgoMTG9naW5SZXF1ZXN0EhAKCHVz",
+ "ZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJImEKDUxvZ2luUmVzcG9u",
+ "c2USDwoHc3VjY2VzcxgBIAEoCBIVCg1zZXNzaW9uX3Rva2VuGAIgASgJEhUK",
+ "DWVycm9yX21lc3NhZ2UYAyABKAkSEQoJcGxheWVyX2lkGAQgASgEIioKEUVu",
+ "dGVyV29ybGRSZXF1ZXN0EhUKDXNlc3Npb25fdG9rZW4YASABKAkinAEKEkVu",
+ "dGVyV29ybGRSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEhUKDWVycm9yX21l",
+ "c3NhZ2UYAiABKAkSIAoEc2VsZhgDIAEoCzISLnByb3RvLkVudGl0eVN0YXRl",
+ "EisKD25lYXJieV9lbnRpdGllcxgEIAMoCzISLnByb3RvLkVudGl0eVN0YXRl",
+ "Eg8KB3pvbmVfaWQYBSABKA0iKgoHVmVjdG9yMxIJCgF4GAEgASgCEgkKAXkY",
+ "AiABKAISCQoBehgDIAEoAiK1AQoLRW50aXR5U3RhdGUSEQoJZW50aXR5X2lk",
+ "GAEgASgEEgwKBG5hbWUYAiABKAkSIAoIcG9zaXRpb24YAyABKAsyDi5wcm90",
+ "by5WZWN0b3IzEhAKCHJvdGF0aW9uGAQgASgCEgoKAmhwGAUgASgFEg4KBm1h",
+ "eF9ocBgGIAEoBRINCgVsZXZlbBgHIAEoBRImCgtlbnRpdHlfdHlwZRgIIAEo",
+ "DjIRLnByb3RvLkVudGl0eVR5cGUiYwoLTW92ZVJlcXVlc3QSIAoIcG9zaXRp",
+ "b24YASABKAsyDi5wcm90by5WZWN0b3IzEhAKCHJvdGF0aW9uGAIgASgCEiAK",
+ "CHZlbG9jaXR5GAMgASgLMg4ucHJvdG8uVmVjdG9yMyJICgtTdGF0ZVVwZGF0",
+ "ZRIkCghlbnRpdGllcxgBIAMoCzISLnByb3RvLkVudGl0eVN0YXRlEhMKC3Nl",
+ "cnZlcl90aWNrGAIgASgDIjEKC1NwYXduRW50aXR5EiIKBmVudGl0eRgBIAEo",
+ "CzISLnByb3RvLkVudGl0eVN0YXRlIiIKDURlc3Bhd25FbnRpdHkSEQoJZW50",
+ "aXR5X2lkGAEgASgEIhsKBFBpbmcSEwoLY2xpZW50X3RpbWUYASABKAMiMAoE",
+ "UG9uZxITCgtjbGllbnRfdGltZRgBIAEoAxITCgtzZXJ2ZXJfdGltZRgCIAEo",
+ "AyJ4ChJab25lVHJhbnNmZXJOb3RpZnkSEwoLbmV3X3pvbmVfaWQYASABKA0S",
+ "IAoEc2VsZhgCIAEoCzISLnByb3RvLkVudGl0eVN0YXRlEisKD25lYXJieV9l",
+ "bnRpdGllcxgDIAMoCzISLnByb3RvLkVudGl0eVN0YXRlIloKD1VzZVNraWxs",
+ "UmVxdWVzdBIQCghza2lsbF9pZBgBIAEoDRIRCgl0YXJnZXRfaWQYAiABKAQS",
+ "IgoKdGFyZ2V0X3BvcxgDIAEoCzIOLnByb3RvLlZlY3RvcjMiOgoQVXNlU2tp",
+ "bGxSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEhUKDWVycm9yX21lc3NhZ2UY",
+ "AiABKAki4wEKC0NvbWJhdEV2ZW50EhEKCWNhc3Rlcl9pZBgBIAEoBBIRCgl0",
+ "YXJnZXRfaWQYAiABKAQSEAoIc2tpbGxfaWQYAyABKA0SDgoGZGFtYWdlGAQg",
+ "ASgFEgwKBGhlYWwYBSABKAUSEwoLaXNfY3JpdGljYWwYBiABKAgSEwoLdGFy",
+ "Z2V0X2RpZWQYByABKAgSEQoJdGFyZ2V0X2hwGAggASgFEhUKDXRhcmdldF9t",
+ "YXhfaHAYCSABKAUSKgoKZXZlbnRfdHlwZRgKIAEoDjIWLnByb3RvLkNvbWJh",
+ "dEV2ZW50VHlwZSJpCgtCdWZmQXBwbGllZBIRCgl0YXJnZXRfaWQYASABKAQS",
+ "DwoHYnVmZl9pZBgCIAEoDRIRCglidWZmX25hbWUYAyABKAkSEAoIZHVyYXRp",
+ "b24YBCABKAISEQoJaXNfZGVidWZmGAUgASgIIjEKC0J1ZmZSZW1vdmVkEhEK",
+ "CXRhcmdldF9pZBgBIAEoBBIPCgdidWZmX2lkGAIgASgNIhAKDlJlc3Bhd25S",
+ "ZXF1ZXN0IjMKD1Jlc3Bhd25SZXNwb25zZRIgCgRzZWxmGAEgASgLMhIucHJv",
+ "dG8uRW50aXR5U3RhdGUiIwoQQU9JVG9nZ2xlUmVxdWVzdBIPCgdlbmFibGVk",
+ "GAEgASgIIjUKEUFPSVRvZ2dsZVJlc3BvbnNlEg8KB2VuYWJsZWQYASABKAgS",
+ "DwoHbWVzc2FnZRgCIAEoCSJuCg1TZXJ2ZXJNZXRyaWNzEhYKDm9ubGluZV9w",
+ "bGF5ZXJzGAEgASgFEhYKDnRvdGFsX2VudGl0aWVzGAIgASgFEhgKEHRpY2tf",
+ "ZHVyYXRpb25fdXMYAyABKAMSEwoLYW9pX2VuYWJsZWQYBCABKAgiEAoOTWV0",
+ "cmljc1JlcXVlc3QqTgoKRW50aXR5VHlwZRIWChJFTlRJVFlfVFlQRV9QTEFZ",
+ "RVIQABITCg9FTlRJVFlfVFlQRV9NT0IQARITCg9FTlRJVFlfVFlQRV9OUEMQ",
+ "AiqjAQoPQ29tYmF0RXZlbnRUeXBlEhcKE0NPTUJBVF9FVkVOVF9EQU1BR0UQ",
+ "ABIVChFDT01CQVRfRVZFTlRfSEVBTBABEhUKEUNPTUJBVF9FVkVOVF9CVUZG",
+ "EAISFwoTQ09NQkFUX0VWRU5UX0RFQlVGRhADEhYKEkNPTUJBVF9FVkVOVF9E",
+ "RUFUSBAEEhgKFENPTUJBVF9FVkVOVF9SRVNQQVdOEAVCH1odYTMwMV9nYW1l",
+ "X3NlcnZlci9wcm90by9nZW4vcGJiBnByb3RvMw=="));
+ descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
+ new pbr::FileDescriptor[] { },
+ new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Proto.EntityType), typeof(global::Proto.CombatEventType), }, null, new pbr::GeneratedClrTypeInfo[] {
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.LoginRequest), global::Proto.LoginRequest.Parser, new[]{ "Username", "Password" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.LoginResponse), global::Proto.LoginResponse.Parser, new[]{ "Success", "SessionToken", "ErrorMessage", "PlayerId" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.EnterWorldRequest), global::Proto.EnterWorldRequest.Parser, new[]{ "SessionToken" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.EnterWorldResponse), global::Proto.EnterWorldResponse.Parser, new[]{ "Success", "ErrorMessage", "Self", "NearbyEntities", "ZoneId" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.Vector3), global::Proto.Vector3.Parser, new[]{ "X", "Y", "Z" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.EntityState), global::Proto.EntityState.Parser, new[]{ "EntityId", "Name", "Position", "Rotation", "Hp", "MaxHp", "Level", "EntityType" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.MoveRequest), global::Proto.MoveRequest.Parser, new[]{ "Position", "Rotation", "Velocity" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.StateUpdate), global::Proto.StateUpdate.Parser, new[]{ "Entities", "ServerTick" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.SpawnEntity), global::Proto.SpawnEntity.Parser, new[]{ "Entity" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.DespawnEntity), global::Proto.DespawnEntity.Parser, new[]{ "EntityId" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.Ping), global::Proto.Ping.Parser, new[]{ "ClientTime" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.Pong), global::Proto.Pong.Parser, new[]{ "ClientTime", "ServerTime" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.ZoneTransferNotify), global::Proto.ZoneTransferNotify.Parser, new[]{ "NewZoneId", "Self", "NearbyEntities" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.UseSkillRequest), global::Proto.UseSkillRequest.Parser, new[]{ "SkillId", "TargetId", "TargetPos" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.UseSkillResponse), global::Proto.UseSkillResponse.Parser, new[]{ "Success", "ErrorMessage" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.CombatEvent), global::Proto.CombatEvent.Parser, new[]{ "CasterId", "TargetId", "SkillId", "Damage", "Heal", "IsCritical", "TargetDied", "TargetHp", "TargetMaxHp", "EventType" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.BuffApplied), global::Proto.BuffApplied.Parser, new[]{ "TargetId", "BuffId", "BuffName", "Duration", "IsDebuff" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.BuffRemoved), global::Proto.BuffRemoved.Parser, new[]{ "TargetId", "BuffId" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.RespawnRequest), global::Proto.RespawnRequest.Parser, null, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.RespawnResponse), global::Proto.RespawnResponse.Parser, new[]{ "Self" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.AOIToggleRequest), global::Proto.AOIToggleRequest.Parser, new[]{ "Enabled" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.AOIToggleResponse), global::Proto.AOIToggleResponse.Parser, new[]{ "Enabled", "Message" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.ServerMetrics), global::Proto.ServerMetrics.Parser, new[]{ "OnlinePlayers", "TotalEntities", "TickDurationUs", "AoiEnabled" }, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Proto.MetricsRequest), global::Proto.MetricsRequest.Parser, null, null, null, null, null)
+ }));
+ }
+ #endregion
+
+ }
+ #region Enums
+ public enum EntityType {
+ [pbr::OriginalName("ENTITY_TYPE_PLAYER")] Player = 0,
+ [pbr::OriginalName("ENTITY_TYPE_MOB")] Mob = 1,
+ [pbr::OriginalName("ENTITY_TYPE_NPC")] Npc = 2,
+ }
+
+ public enum CombatEventType {
+ [pbr::OriginalName("COMBAT_EVENT_DAMAGE")] CombatEventDamage = 0,
+ [pbr::OriginalName("COMBAT_EVENT_HEAL")] CombatEventHeal = 1,
+ [pbr::OriginalName("COMBAT_EVENT_BUFF")] CombatEventBuff = 2,
+ [pbr::OriginalName("COMBAT_EVENT_DEBUFF")] CombatEventDebuff = 3,
+ [pbr::OriginalName("COMBAT_EVENT_DEATH")] CombatEventDeath = 4,
+ [pbr::OriginalName("COMBAT_EVENT_RESPAWN")] CombatEventRespawn = 5,
+ }
+
+ #endregion
+
+ #region Messages
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class LoginRequest : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new LoginRequest());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[0]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public LoginRequest() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public LoginRequest(LoginRequest other) : this() {
+ username_ = other.username_;
+ password_ = other.password_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public LoginRequest Clone() {
+ return new LoginRequest(this);
+ }
+
+ /// Field number for the "username" field.
+ public const int UsernameFieldNumber = 1;
+ private string username_ = "";
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string Username {
+ get { return username_; }
+ set {
+ username_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "password" field.
+ public const int PasswordFieldNumber = 2;
+ private string password_ = "";
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string Password {
+ get { return password_; }
+ set {
+ password_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as LoginRequest);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(LoginRequest other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Username != other.Username) return false;
+ if (Password != other.Password) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Username.Length != 0) hash ^= Username.GetHashCode();
+ if (Password.Length != 0) hash ^= Password.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (Username.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(Username);
+ }
+ if (Password.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(Password);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (Username.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(Username);
+ }
+ if (Password.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(Password);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (Username.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Username);
+ }
+ if (Password.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Password);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(LoginRequest other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Username.Length != 0) {
+ Username = other.Username;
+ }
+ if (other.Password.Length != 0) {
+ Password = other.Password;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 10: {
+ Username = input.ReadString();
+ break;
+ }
+ case 18: {
+ Password = input.ReadString();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 10: {
+ Username = input.ReadString();
+ break;
+ }
+ case 18: {
+ Password = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class LoginResponse : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new LoginResponse());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[1]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public LoginResponse() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public LoginResponse(LoginResponse other) : this() {
+ success_ = other.success_;
+ sessionToken_ = other.sessionToken_;
+ errorMessage_ = other.errorMessage_;
+ playerId_ = other.playerId_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public LoginResponse Clone() {
+ return new LoginResponse(this);
+ }
+
+ /// Field number for the "success" field.
+ public const int SuccessFieldNumber = 1;
+ private bool success_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Success {
+ get { return success_; }
+ set {
+ success_ = value;
+ }
+ }
+
+ /// Field number for the "session_token" field.
+ public const int SessionTokenFieldNumber = 2;
+ private string sessionToken_ = "";
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string SessionToken {
+ get { return sessionToken_; }
+ set {
+ sessionToken_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "error_message" field.
+ public const int ErrorMessageFieldNumber = 3;
+ private string errorMessage_ = "";
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string ErrorMessage {
+ get { return errorMessage_; }
+ set {
+ errorMessage_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "player_id" field.
+ public const int PlayerIdFieldNumber = 4;
+ private ulong playerId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ulong PlayerId {
+ get { return playerId_; }
+ set {
+ playerId_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as LoginResponse);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(LoginResponse other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Success != other.Success) return false;
+ if (SessionToken != other.SessionToken) return false;
+ if (ErrorMessage != other.ErrorMessage) return false;
+ if (PlayerId != other.PlayerId) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Success != false) hash ^= Success.GetHashCode();
+ if (SessionToken.Length != 0) hash ^= SessionToken.GetHashCode();
+ if (ErrorMessage.Length != 0) hash ^= ErrorMessage.GetHashCode();
+ if (PlayerId != 0UL) hash ^= PlayerId.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (Success != false) {
+ output.WriteRawTag(8);
+ output.WriteBool(Success);
+ }
+ if (SessionToken.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(SessionToken);
+ }
+ if (ErrorMessage.Length != 0) {
+ output.WriteRawTag(26);
+ output.WriteString(ErrorMessage);
+ }
+ if (PlayerId != 0UL) {
+ output.WriteRawTag(32);
+ output.WriteUInt64(PlayerId);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (Success != false) {
+ output.WriteRawTag(8);
+ output.WriteBool(Success);
+ }
+ if (SessionToken.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(SessionToken);
+ }
+ if (ErrorMessage.Length != 0) {
+ output.WriteRawTag(26);
+ output.WriteString(ErrorMessage);
+ }
+ if (PlayerId != 0UL) {
+ output.WriteRawTag(32);
+ output.WriteUInt64(PlayerId);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (Success != false) {
+ size += 1 + 1;
+ }
+ if (SessionToken.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(SessionToken);
+ }
+ if (ErrorMessage.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(ErrorMessage);
+ }
+ if (PlayerId != 0UL) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt64Size(PlayerId);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(LoginResponse other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Success != false) {
+ Success = other.Success;
+ }
+ if (other.SessionToken.Length != 0) {
+ SessionToken = other.SessionToken;
+ }
+ if (other.ErrorMessage.Length != 0) {
+ ErrorMessage = other.ErrorMessage;
+ }
+ if (other.PlayerId != 0UL) {
+ PlayerId = other.PlayerId;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ Success = input.ReadBool();
+ break;
+ }
+ case 18: {
+ SessionToken = input.ReadString();
+ break;
+ }
+ case 26: {
+ ErrorMessage = input.ReadString();
+ break;
+ }
+ case 32: {
+ PlayerId = input.ReadUInt64();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ Success = input.ReadBool();
+ break;
+ }
+ case 18: {
+ SessionToken = input.ReadString();
+ break;
+ }
+ case 26: {
+ ErrorMessage = input.ReadString();
+ break;
+ }
+ case 32: {
+ PlayerId = input.ReadUInt64();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class EnterWorldRequest : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EnterWorldRequest());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[2]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public EnterWorldRequest() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public EnterWorldRequest(EnterWorldRequest other) : this() {
+ sessionToken_ = other.sessionToken_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public EnterWorldRequest Clone() {
+ return new EnterWorldRequest(this);
+ }
+
+ /// Field number for the "session_token" field.
+ public const int SessionTokenFieldNumber = 1;
+ private string sessionToken_ = "";
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string SessionToken {
+ get { return sessionToken_; }
+ set {
+ sessionToken_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as EnterWorldRequest);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(EnterWorldRequest other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (SessionToken != other.SessionToken) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (SessionToken.Length != 0) hash ^= SessionToken.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (SessionToken.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(SessionToken);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (SessionToken.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(SessionToken);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (SessionToken.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(SessionToken);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(EnterWorldRequest other) {
+ if (other == null) {
+ return;
+ }
+ if (other.SessionToken.Length != 0) {
+ SessionToken = other.SessionToken;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 10: {
+ SessionToken = input.ReadString();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 10: {
+ SessionToken = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class EnterWorldResponse : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EnterWorldResponse());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[3]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public EnterWorldResponse() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public EnterWorldResponse(EnterWorldResponse other) : this() {
+ success_ = other.success_;
+ errorMessage_ = other.errorMessage_;
+ self_ = other.self_ != null ? other.self_.Clone() : null;
+ nearbyEntities_ = other.nearbyEntities_.Clone();
+ zoneId_ = other.zoneId_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public EnterWorldResponse Clone() {
+ return new EnterWorldResponse(this);
+ }
+
+ /// Field number for the "success" field.
+ public const int SuccessFieldNumber = 1;
+ private bool success_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Success {
+ get { return success_; }
+ set {
+ success_ = value;
+ }
+ }
+
+ /// Field number for the "error_message" field.
+ public const int ErrorMessageFieldNumber = 2;
+ private string errorMessage_ = "";
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string ErrorMessage {
+ get { return errorMessage_; }
+ set {
+ errorMessage_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "self" field.
+ public const int SelfFieldNumber = 3;
+ private global::Proto.EntityState self_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public global::Proto.EntityState Self {
+ get { return self_; }
+ set {
+ self_ = value;
+ }
+ }
+
+ /// Field number for the "nearby_entities" field.
+ public const int NearbyEntitiesFieldNumber = 4;
+ private static readonly pb::FieldCodec _repeated_nearbyEntities_codec
+ = pb::FieldCodec.ForMessage(34, global::Proto.EntityState.Parser);
+ private readonly pbc::RepeatedField nearbyEntities_ = new pbc::RepeatedField();
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public pbc::RepeatedField NearbyEntities {
+ get { return nearbyEntities_; }
+ }
+
+ /// Field number for the "zone_id" field.
+ public const int ZoneIdFieldNumber = 5;
+ private uint zoneId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public uint ZoneId {
+ get { return zoneId_; }
+ set {
+ zoneId_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as EnterWorldResponse);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(EnterWorldResponse other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Success != other.Success) return false;
+ if (ErrorMessage != other.ErrorMessage) return false;
+ if (!object.Equals(Self, other.Self)) return false;
+ if(!nearbyEntities_.Equals(other.nearbyEntities_)) return false;
+ if (ZoneId != other.ZoneId) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Success != false) hash ^= Success.GetHashCode();
+ if (ErrorMessage.Length != 0) hash ^= ErrorMessage.GetHashCode();
+ if (self_ != null) hash ^= Self.GetHashCode();
+ hash ^= nearbyEntities_.GetHashCode();
+ if (ZoneId != 0) hash ^= ZoneId.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (Success != false) {
+ output.WriteRawTag(8);
+ output.WriteBool(Success);
+ }
+ if (ErrorMessage.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(ErrorMessage);
+ }
+ if (self_ != null) {
+ output.WriteRawTag(26);
+ output.WriteMessage(Self);
+ }
+ nearbyEntities_.WriteTo(output, _repeated_nearbyEntities_codec);
+ if (ZoneId != 0) {
+ output.WriteRawTag(40);
+ output.WriteUInt32(ZoneId);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (Success != false) {
+ output.WriteRawTag(8);
+ output.WriteBool(Success);
+ }
+ if (ErrorMessage.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(ErrorMessage);
+ }
+ if (self_ != null) {
+ output.WriteRawTag(26);
+ output.WriteMessage(Self);
+ }
+ nearbyEntities_.WriteTo(ref output, _repeated_nearbyEntities_codec);
+ if (ZoneId != 0) {
+ output.WriteRawTag(40);
+ output.WriteUInt32(ZoneId);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (Success != false) {
+ size += 1 + 1;
+ }
+ if (ErrorMessage.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(ErrorMessage);
+ }
+ if (self_ != null) {
+ size += 1 + pb::CodedOutputStream.ComputeMessageSize(Self);
+ }
+ size += nearbyEntities_.CalculateSize(_repeated_nearbyEntities_codec);
+ if (ZoneId != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt32Size(ZoneId);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(EnterWorldResponse other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Success != false) {
+ Success = other.Success;
+ }
+ if (other.ErrorMessage.Length != 0) {
+ ErrorMessage = other.ErrorMessage;
+ }
+ if (other.self_ != null) {
+ if (self_ == null) {
+ Self = new global::Proto.EntityState();
+ }
+ Self.MergeFrom(other.Self);
+ }
+ nearbyEntities_.Add(other.nearbyEntities_);
+ if (other.ZoneId != 0) {
+ ZoneId = other.ZoneId;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ Success = input.ReadBool();
+ break;
+ }
+ case 18: {
+ ErrorMessage = input.ReadString();
+ break;
+ }
+ case 26: {
+ if (self_ == null) {
+ Self = new global::Proto.EntityState();
+ }
+ input.ReadMessage(Self);
+ break;
+ }
+ case 34: {
+ nearbyEntities_.AddEntriesFrom(input, _repeated_nearbyEntities_codec);
+ break;
+ }
+ case 40: {
+ ZoneId = input.ReadUInt32();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ Success = input.ReadBool();
+ break;
+ }
+ case 18: {
+ ErrorMessage = input.ReadString();
+ break;
+ }
+ case 26: {
+ if (self_ == null) {
+ Self = new global::Proto.EntityState();
+ }
+ input.ReadMessage(Self);
+ break;
+ }
+ case 34: {
+ nearbyEntities_.AddEntriesFrom(ref input, _repeated_nearbyEntities_codec);
+ break;
+ }
+ case 40: {
+ ZoneId = input.ReadUInt32();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class Vector3 : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Vector3());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[4]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public Vector3() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public Vector3(Vector3 other) : this() {
+ x_ = other.x_;
+ y_ = other.y_;
+ z_ = other.z_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public Vector3 Clone() {
+ return new Vector3(this);
+ }
+
+ /// Field number for the "x" field.
+ public const int XFieldNumber = 1;
+ private float x_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public float X {
+ get { return x_; }
+ set {
+ x_ = value;
+ }
+ }
+
+ /// Field number for the "y" field.
+ public const int YFieldNumber = 2;
+ private float y_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public float Y {
+ get { return y_; }
+ set {
+ y_ = value;
+ }
+ }
+
+ /// Field number for the "z" field.
+ public const int ZFieldNumber = 3;
+ private float z_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public float Z {
+ get { return z_; }
+ set {
+ z_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as Vector3);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(Vector3 other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(X, other.X)) return false;
+ if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(Y, other.Y)) return false;
+ if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(Z, other.Z)) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (X != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(X);
+ if (Y != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(Y);
+ if (Z != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(Z);
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (X != 0F) {
+ output.WriteRawTag(13);
+ output.WriteFloat(X);
+ }
+ if (Y != 0F) {
+ output.WriteRawTag(21);
+ output.WriteFloat(Y);
+ }
+ if (Z != 0F) {
+ output.WriteRawTag(29);
+ output.WriteFloat(Z);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (X != 0F) {
+ output.WriteRawTag(13);
+ output.WriteFloat(X);
+ }
+ if (Y != 0F) {
+ output.WriteRawTag(21);
+ output.WriteFloat(Y);
+ }
+ if (Z != 0F) {
+ output.WriteRawTag(29);
+ output.WriteFloat(Z);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (X != 0F) {
+ size += 1 + 4;
+ }
+ if (Y != 0F) {
+ size += 1 + 4;
+ }
+ if (Z != 0F) {
+ size += 1 + 4;
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(Vector3 other) {
+ if (other == null) {
+ return;
+ }
+ if (other.X != 0F) {
+ X = other.X;
+ }
+ if (other.Y != 0F) {
+ Y = other.Y;
+ }
+ if (other.Z != 0F) {
+ Z = other.Z;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 13: {
+ X = input.ReadFloat();
+ break;
+ }
+ case 21: {
+ Y = input.ReadFloat();
+ break;
+ }
+ case 29: {
+ Z = input.ReadFloat();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 13: {
+ X = input.ReadFloat();
+ break;
+ }
+ case 21: {
+ Y = input.ReadFloat();
+ break;
+ }
+ case 29: {
+ Z = input.ReadFloat();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class EntityState : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EntityState());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[5]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public EntityState() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public EntityState(EntityState other) : this() {
+ entityId_ = other.entityId_;
+ name_ = other.name_;
+ position_ = other.position_ != null ? other.position_.Clone() : null;
+ rotation_ = other.rotation_;
+ hp_ = other.hp_;
+ maxHp_ = other.maxHp_;
+ level_ = other.level_;
+ entityType_ = other.entityType_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public EntityState Clone() {
+ return new EntityState(this);
+ }
+
+ /// Field number for the "entity_id" field.
+ public const int EntityIdFieldNumber = 1;
+ private ulong entityId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ulong EntityId {
+ get { return entityId_; }
+ set {
+ entityId_ = value;
+ }
+ }
+
+ /// Field number for the "name" field.
+ public const int NameFieldNumber = 2;
+ private string name_ = "";
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string Name {
+ get { return name_; }
+ set {
+ name_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "position" field.
+ public const int PositionFieldNumber = 3;
+ private global::Proto.Vector3 position_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public global::Proto.Vector3 Position {
+ get { return position_; }
+ set {
+ position_ = value;
+ }
+ }
+
+ /// Field number for the "rotation" field.
+ public const int RotationFieldNumber = 4;
+ private float rotation_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public float Rotation {
+ get { return rotation_; }
+ set {
+ rotation_ = value;
+ }
+ }
+
+ /// Field number for the "hp" field.
+ public const int HpFieldNumber = 5;
+ private int hp_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int Hp {
+ get { return hp_; }
+ set {
+ hp_ = value;
+ }
+ }
+
+ /// Field number for the "max_hp" field.
+ public const int MaxHpFieldNumber = 6;
+ private int maxHp_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int MaxHp {
+ get { return maxHp_; }
+ set {
+ maxHp_ = value;
+ }
+ }
+
+ /// Field number for the "level" field.
+ public const int LevelFieldNumber = 7;
+ private int level_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int Level {
+ get { return level_; }
+ set {
+ level_ = value;
+ }
+ }
+
+ /// Field number for the "entity_type" field.
+ public const int EntityTypeFieldNumber = 8;
+ private global::Proto.EntityType entityType_ = global::Proto.EntityType.Player;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public global::Proto.EntityType EntityType {
+ get { return entityType_; }
+ set {
+ entityType_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as EntityState);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(EntityState other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (EntityId != other.EntityId) return false;
+ if (Name != other.Name) return false;
+ if (!object.Equals(Position, other.Position)) return false;
+ if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(Rotation, other.Rotation)) return false;
+ if (Hp != other.Hp) return false;
+ if (MaxHp != other.MaxHp) return false;
+ if (Level != other.Level) return false;
+ if (EntityType != other.EntityType) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (EntityId != 0UL) hash ^= EntityId.GetHashCode();
+ if (Name.Length != 0) hash ^= Name.GetHashCode();
+ if (position_ != null) hash ^= Position.GetHashCode();
+ if (Rotation != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(Rotation);
+ if (Hp != 0) hash ^= Hp.GetHashCode();
+ if (MaxHp != 0) hash ^= MaxHp.GetHashCode();
+ if (Level != 0) hash ^= Level.GetHashCode();
+ if (EntityType != global::Proto.EntityType.Player) hash ^= EntityType.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (EntityId != 0UL) {
+ output.WriteRawTag(8);
+ output.WriteUInt64(EntityId);
+ }
+ if (Name.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(Name);
+ }
+ if (position_ != null) {
+ output.WriteRawTag(26);
+ output.WriteMessage(Position);
+ }
+ if (Rotation != 0F) {
+ output.WriteRawTag(37);
+ output.WriteFloat(Rotation);
+ }
+ if (Hp != 0) {
+ output.WriteRawTag(40);
+ output.WriteInt32(Hp);
+ }
+ if (MaxHp != 0) {
+ output.WriteRawTag(48);
+ output.WriteInt32(MaxHp);
+ }
+ if (Level != 0) {
+ output.WriteRawTag(56);
+ output.WriteInt32(Level);
+ }
+ if (EntityType != global::Proto.EntityType.Player) {
+ output.WriteRawTag(64);
+ output.WriteEnum((int) EntityType);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (EntityId != 0UL) {
+ output.WriteRawTag(8);
+ output.WriteUInt64(EntityId);
+ }
+ if (Name.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(Name);
+ }
+ if (position_ != null) {
+ output.WriteRawTag(26);
+ output.WriteMessage(Position);
+ }
+ if (Rotation != 0F) {
+ output.WriteRawTag(37);
+ output.WriteFloat(Rotation);
+ }
+ if (Hp != 0) {
+ output.WriteRawTag(40);
+ output.WriteInt32(Hp);
+ }
+ if (MaxHp != 0) {
+ output.WriteRawTag(48);
+ output.WriteInt32(MaxHp);
+ }
+ if (Level != 0) {
+ output.WriteRawTag(56);
+ output.WriteInt32(Level);
+ }
+ if (EntityType != global::Proto.EntityType.Player) {
+ output.WriteRawTag(64);
+ output.WriteEnum((int) EntityType);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (EntityId != 0UL) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt64Size(EntityId);
+ }
+ if (Name.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
+ }
+ if (position_ != null) {
+ size += 1 + pb::CodedOutputStream.ComputeMessageSize(Position);
+ }
+ if (Rotation != 0F) {
+ size += 1 + 4;
+ }
+ if (Hp != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(Hp);
+ }
+ if (MaxHp != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(MaxHp);
+ }
+ if (Level != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(Level);
+ }
+ if (EntityType != global::Proto.EntityType.Player) {
+ size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) EntityType);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(EntityState other) {
+ if (other == null) {
+ return;
+ }
+ if (other.EntityId != 0UL) {
+ EntityId = other.EntityId;
+ }
+ if (other.Name.Length != 0) {
+ Name = other.Name;
+ }
+ if (other.position_ != null) {
+ if (position_ == null) {
+ Position = new global::Proto.Vector3();
+ }
+ Position.MergeFrom(other.Position);
+ }
+ if (other.Rotation != 0F) {
+ Rotation = other.Rotation;
+ }
+ if (other.Hp != 0) {
+ Hp = other.Hp;
+ }
+ if (other.MaxHp != 0) {
+ MaxHp = other.MaxHp;
+ }
+ if (other.Level != 0) {
+ Level = other.Level;
+ }
+ if (other.EntityType != global::Proto.EntityType.Player) {
+ EntityType = other.EntityType;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ EntityId = input.ReadUInt64();
+ break;
+ }
+ case 18: {
+ Name = input.ReadString();
+ break;
+ }
+ case 26: {
+ if (position_ == null) {
+ Position = new global::Proto.Vector3();
+ }
+ input.ReadMessage(Position);
+ break;
+ }
+ case 37: {
+ Rotation = input.ReadFloat();
+ break;
+ }
+ case 40: {
+ Hp = input.ReadInt32();
+ break;
+ }
+ case 48: {
+ MaxHp = input.ReadInt32();
+ break;
+ }
+ case 56: {
+ Level = input.ReadInt32();
+ break;
+ }
+ case 64: {
+ EntityType = (global::Proto.EntityType) input.ReadEnum();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ EntityId = input.ReadUInt64();
+ break;
+ }
+ case 18: {
+ Name = input.ReadString();
+ break;
+ }
+ case 26: {
+ if (position_ == null) {
+ Position = new global::Proto.Vector3();
+ }
+ input.ReadMessage(Position);
+ break;
+ }
+ case 37: {
+ Rotation = input.ReadFloat();
+ break;
+ }
+ case 40: {
+ Hp = input.ReadInt32();
+ break;
+ }
+ case 48: {
+ MaxHp = input.ReadInt32();
+ break;
+ }
+ case 56: {
+ Level = input.ReadInt32();
+ break;
+ }
+ case 64: {
+ EntityType = (global::Proto.EntityType) input.ReadEnum();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class MoveRequest : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new MoveRequest());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[6]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public MoveRequest() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public MoveRequest(MoveRequest other) : this() {
+ position_ = other.position_ != null ? other.position_.Clone() : null;
+ rotation_ = other.rotation_;
+ velocity_ = other.velocity_ != null ? other.velocity_.Clone() : null;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public MoveRequest Clone() {
+ return new MoveRequest(this);
+ }
+
+ /// Field number for the "position" field.
+ public const int PositionFieldNumber = 1;
+ private global::Proto.Vector3 position_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public global::Proto.Vector3 Position {
+ get { return position_; }
+ set {
+ position_ = value;
+ }
+ }
+
+ /// Field number for the "rotation" field.
+ public const int RotationFieldNumber = 2;
+ private float rotation_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public float Rotation {
+ get { return rotation_; }
+ set {
+ rotation_ = value;
+ }
+ }
+
+ /// Field number for the "velocity" field.
+ public const int VelocityFieldNumber = 3;
+ private global::Proto.Vector3 velocity_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public global::Proto.Vector3 Velocity {
+ get { return velocity_; }
+ set {
+ velocity_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as MoveRequest);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(MoveRequest other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (!object.Equals(Position, other.Position)) return false;
+ if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(Rotation, other.Rotation)) return false;
+ if (!object.Equals(Velocity, other.Velocity)) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (position_ != null) hash ^= Position.GetHashCode();
+ if (Rotation != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(Rotation);
+ if (velocity_ != null) hash ^= Velocity.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (position_ != null) {
+ output.WriteRawTag(10);
+ output.WriteMessage(Position);
+ }
+ if (Rotation != 0F) {
+ output.WriteRawTag(21);
+ output.WriteFloat(Rotation);
+ }
+ if (velocity_ != null) {
+ output.WriteRawTag(26);
+ output.WriteMessage(Velocity);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (position_ != null) {
+ output.WriteRawTag(10);
+ output.WriteMessage(Position);
+ }
+ if (Rotation != 0F) {
+ output.WriteRawTag(21);
+ output.WriteFloat(Rotation);
+ }
+ if (velocity_ != null) {
+ output.WriteRawTag(26);
+ output.WriteMessage(Velocity);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (position_ != null) {
+ size += 1 + pb::CodedOutputStream.ComputeMessageSize(Position);
+ }
+ if (Rotation != 0F) {
+ size += 1 + 4;
+ }
+ if (velocity_ != null) {
+ size += 1 + pb::CodedOutputStream.ComputeMessageSize(Velocity);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(MoveRequest other) {
+ if (other == null) {
+ return;
+ }
+ if (other.position_ != null) {
+ if (position_ == null) {
+ Position = new global::Proto.Vector3();
+ }
+ Position.MergeFrom(other.Position);
+ }
+ if (other.Rotation != 0F) {
+ Rotation = other.Rotation;
+ }
+ if (other.velocity_ != null) {
+ if (velocity_ == null) {
+ Velocity = new global::Proto.Vector3();
+ }
+ Velocity.MergeFrom(other.Velocity);
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 10: {
+ if (position_ == null) {
+ Position = new global::Proto.Vector3();
+ }
+ input.ReadMessage(Position);
+ break;
+ }
+ case 21: {
+ Rotation = input.ReadFloat();
+ break;
+ }
+ case 26: {
+ if (velocity_ == null) {
+ Velocity = new global::Proto.Vector3();
+ }
+ input.ReadMessage(Velocity);
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 10: {
+ if (position_ == null) {
+ Position = new global::Proto.Vector3();
+ }
+ input.ReadMessage(Position);
+ break;
+ }
+ case 21: {
+ Rotation = input.ReadFloat();
+ break;
+ }
+ case 26: {
+ if (velocity_ == null) {
+ Velocity = new global::Proto.Vector3();
+ }
+ input.ReadMessage(Velocity);
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class StateUpdate : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new StateUpdate());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[7]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public StateUpdate() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public StateUpdate(StateUpdate other) : this() {
+ entities_ = other.entities_.Clone();
+ serverTick_ = other.serverTick_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public StateUpdate Clone() {
+ return new StateUpdate(this);
+ }
+
+ /// Field number for the "entities" field.
+ public const int EntitiesFieldNumber = 1;
+ private static readonly pb::FieldCodec _repeated_entities_codec
+ = pb::FieldCodec.ForMessage(10, global::Proto.EntityState.Parser);
+ private readonly pbc::RepeatedField entities_ = new pbc::RepeatedField();
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public pbc::RepeatedField Entities {
+ get { return entities_; }
+ }
+
+ /// Field number for the "server_tick" field.
+ public const int ServerTickFieldNumber = 2;
+ private long serverTick_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public long ServerTick {
+ get { return serverTick_; }
+ set {
+ serverTick_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as StateUpdate);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(StateUpdate other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if(!entities_.Equals(other.entities_)) return false;
+ if (ServerTick != other.ServerTick) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ hash ^= entities_.GetHashCode();
+ if (ServerTick != 0L) hash ^= ServerTick.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ entities_.WriteTo(output, _repeated_entities_codec);
+ if (ServerTick != 0L) {
+ output.WriteRawTag(16);
+ output.WriteInt64(ServerTick);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ entities_.WriteTo(ref output, _repeated_entities_codec);
+ if (ServerTick != 0L) {
+ output.WriteRawTag(16);
+ output.WriteInt64(ServerTick);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ size += entities_.CalculateSize(_repeated_entities_codec);
+ if (ServerTick != 0L) {
+ size += 1 + pb::CodedOutputStream.ComputeInt64Size(ServerTick);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(StateUpdate other) {
+ if (other == null) {
+ return;
+ }
+ entities_.Add(other.entities_);
+ if (other.ServerTick != 0L) {
+ ServerTick = other.ServerTick;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 10: {
+ entities_.AddEntriesFrom(input, _repeated_entities_codec);
+ break;
+ }
+ case 16: {
+ ServerTick = input.ReadInt64();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 10: {
+ entities_.AddEntriesFrom(ref input, _repeated_entities_codec);
+ break;
+ }
+ case 16: {
+ ServerTick = input.ReadInt64();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class SpawnEntity : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SpawnEntity());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[8]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public SpawnEntity() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public SpawnEntity(SpawnEntity other) : this() {
+ entity_ = other.entity_ != null ? other.entity_.Clone() : null;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public SpawnEntity Clone() {
+ return new SpawnEntity(this);
+ }
+
+ /// Field number for the "entity" field.
+ public const int EntityFieldNumber = 1;
+ private global::Proto.EntityState entity_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public global::Proto.EntityState Entity {
+ get { return entity_; }
+ set {
+ entity_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as SpawnEntity);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(SpawnEntity other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (!object.Equals(Entity, other.Entity)) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (entity_ != null) hash ^= Entity.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (entity_ != null) {
+ output.WriteRawTag(10);
+ output.WriteMessage(Entity);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (entity_ != null) {
+ output.WriteRawTag(10);
+ output.WriteMessage(Entity);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (entity_ != null) {
+ size += 1 + pb::CodedOutputStream.ComputeMessageSize(Entity);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(SpawnEntity other) {
+ if (other == null) {
+ return;
+ }
+ if (other.entity_ != null) {
+ if (entity_ == null) {
+ Entity = new global::Proto.EntityState();
+ }
+ Entity.MergeFrom(other.Entity);
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 10: {
+ if (entity_ == null) {
+ Entity = new global::Proto.EntityState();
+ }
+ input.ReadMessage(Entity);
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 10: {
+ if (entity_ == null) {
+ Entity = new global::Proto.EntityState();
+ }
+ input.ReadMessage(Entity);
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class DespawnEntity : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new DespawnEntity());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[9]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public DespawnEntity() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public DespawnEntity(DespawnEntity other) : this() {
+ entityId_ = other.entityId_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public DespawnEntity Clone() {
+ return new DespawnEntity(this);
+ }
+
+ /// Field number for the "entity_id" field.
+ public const int EntityIdFieldNumber = 1;
+ private ulong entityId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ulong EntityId {
+ get { return entityId_; }
+ set {
+ entityId_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as DespawnEntity);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(DespawnEntity other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (EntityId != other.EntityId) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (EntityId != 0UL) hash ^= EntityId.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (EntityId != 0UL) {
+ output.WriteRawTag(8);
+ output.WriteUInt64(EntityId);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (EntityId != 0UL) {
+ output.WriteRawTag(8);
+ output.WriteUInt64(EntityId);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (EntityId != 0UL) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt64Size(EntityId);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(DespawnEntity other) {
+ if (other == null) {
+ return;
+ }
+ if (other.EntityId != 0UL) {
+ EntityId = other.EntityId;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ EntityId = input.ReadUInt64();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ EntityId = input.ReadUInt64();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class Ping : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Ping());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[10]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public Ping() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public Ping(Ping other) : this() {
+ clientTime_ = other.clientTime_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public Ping Clone() {
+ return new Ping(this);
+ }
+
+ /// Field number for the "client_time" field.
+ public const int ClientTimeFieldNumber = 1;
+ private long clientTime_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public long ClientTime {
+ get { return clientTime_; }
+ set {
+ clientTime_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as Ping);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(Ping other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (ClientTime != other.ClientTime) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (ClientTime != 0L) hash ^= ClientTime.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (ClientTime != 0L) {
+ output.WriteRawTag(8);
+ output.WriteInt64(ClientTime);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (ClientTime != 0L) {
+ output.WriteRawTag(8);
+ output.WriteInt64(ClientTime);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (ClientTime != 0L) {
+ size += 1 + pb::CodedOutputStream.ComputeInt64Size(ClientTime);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(Ping other) {
+ if (other == null) {
+ return;
+ }
+ if (other.ClientTime != 0L) {
+ ClientTime = other.ClientTime;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ ClientTime = input.ReadInt64();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ ClientTime = input.ReadInt64();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class Pong : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Pong());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[11]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public Pong() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public Pong(Pong other) : this() {
+ clientTime_ = other.clientTime_;
+ serverTime_ = other.serverTime_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public Pong Clone() {
+ return new Pong(this);
+ }
+
+ /// Field number for the "client_time" field.
+ public const int ClientTimeFieldNumber = 1;
+ private long clientTime_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public long ClientTime {
+ get { return clientTime_; }
+ set {
+ clientTime_ = value;
+ }
+ }
+
+ /// Field number for the "server_time" field.
+ public const int ServerTimeFieldNumber = 2;
+ private long serverTime_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public long ServerTime {
+ get { return serverTime_; }
+ set {
+ serverTime_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as Pong);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(Pong other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (ClientTime != other.ClientTime) return false;
+ if (ServerTime != other.ServerTime) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (ClientTime != 0L) hash ^= ClientTime.GetHashCode();
+ if (ServerTime != 0L) hash ^= ServerTime.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (ClientTime != 0L) {
+ output.WriteRawTag(8);
+ output.WriteInt64(ClientTime);
+ }
+ if (ServerTime != 0L) {
+ output.WriteRawTag(16);
+ output.WriteInt64(ServerTime);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (ClientTime != 0L) {
+ output.WriteRawTag(8);
+ output.WriteInt64(ClientTime);
+ }
+ if (ServerTime != 0L) {
+ output.WriteRawTag(16);
+ output.WriteInt64(ServerTime);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (ClientTime != 0L) {
+ size += 1 + pb::CodedOutputStream.ComputeInt64Size(ClientTime);
+ }
+ if (ServerTime != 0L) {
+ size += 1 + pb::CodedOutputStream.ComputeInt64Size(ServerTime);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(Pong other) {
+ if (other == null) {
+ return;
+ }
+ if (other.ClientTime != 0L) {
+ ClientTime = other.ClientTime;
+ }
+ if (other.ServerTime != 0L) {
+ ServerTime = other.ServerTime;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ ClientTime = input.ReadInt64();
+ break;
+ }
+ case 16: {
+ ServerTime = input.ReadInt64();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ ClientTime = input.ReadInt64();
+ break;
+ }
+ case 16: {
+ ServerTime = input.ReadInt64();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class ZoneTransferNotify : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ZoneTransferNotify());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[12]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ZoneTransferNotify() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ZoneTransferNotify(ZoneTransferNotify other) : this() {
+ newZoneId_ = other.newZoneId_;
+ self_ = other.self_ != null ? other.self_.Clone() : null;
+ nearbyEntities_ = other.nearbyEntities_.Clone();
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ZoneTransferNotify Clone() {
+ return new ZoneTransferNotify(this);
+ }
+
+ /// Field number for the "new_zone_id" field.
+ public const int NewZoneIdFieldNumber = 1;
+ private uint newZoneId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public uint NewZoneId {
+ get { return newZoneId_; }
+ set {
+ newZoneId_ = value;
+ }
+ }
+
+ /// Field number for the "self" field.
+ public const int SelfFieldNumber = 2;
+ private global::Proto.EntityState self_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public global::Proto.EntityState Self {
+ get { return self_; }
+ set {
+ self_ = value;
+ }
+ }
+
+ /// Field number for the "nearby_entities" field.
+ public const int NearbyEntitiesFieldNumber = 3;
+ private static readonly pb::FieldCodec _repeated_nearbyEntities_codec
+ = pb::FieldCodec.ForMessage(26, global::Proto.EntityState.Parser);
+ private readonly pbc::RepeatedField nearbyEntities_ = new pbc::RepeatedField();
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public pbc::RepeatedField NearbyEntities {
+ get { return nearbyEntities_; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as ZoneTransferNotify);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(ZoneTransferNotify other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (NewZoneId != other.NewZoneId) return false;
+ if (!object.Equals(Self, other.Self)) return false;
+ if(!nearbyEntities_.Equals(other.nearbyEntities_)) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (NewZoneId != 0) hash ^= NewZoneId.GetHashCode();
+ if (self_ != null) hash ^= Self.GetHashCode();
+ hash ^= nearbyEntities_.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (NewZoneId != 0) {
+ output.WriteRawTag(8);
+ output.WriteUInt32(NewZoneId);
+ }
+ if (self_ != null) {
+ output.WriteRawTag(18);
+ output.WriteMessage(Self);
+ }
+ nearbyEntities_.WriteTo(output, _repeated_nearbyEntities_codec);
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (NewZoneId != 0) {
+ output.WriteRawTag(8);
+ output.WriteUInt32(NewZoneId);
+ }
+ if (self_ != null) {
+ output.WriteRawTag(18);
+ output.WriteMessage(Self);
+ }
+ nearbyEntities_.WriteTo(ref output, _repeated_nearbyEntities_codec);
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (NewZoneId != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt32Size(NewZoneId);
+ }
+ if (self_ != null) {
+ size += 1 + pb::CodedOutputStream.ComputeMessageSize(Self);
+ }
+ size += nearbyEntities_.CalculateSize(_repeated_nearbyEntities_codec);
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(ZoneTransferNotify other) {
+ if (other == null) {
+ return;
+ }
+ if (other.NewZoneId != 0) {
+ NewZoneId = other.NewZoneId;
+ }
+ if (other.self_ != null) {
+ if (self_ == null) {
+ Self = new global::Proto.EntityState();
+ }
+ Self.MergeFrom(other.Self);
+ }
+ nearbyEntities_.Add(other.nearbyEntities_);
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ NewZoneId = input.ReadUInt32();
+ break;
+ }
+ case 18: {
+ if (self_ == null) {
+ Self = new global::Proto.EntityState();
+ }
+ input.ReadMessage(Self);
+ break;
+ }
+ case 26: {
+ nearbyEntities_.AddEntriesFrom(input, _repeated_nearbyEntities_codec);
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ NewZoneId = input.ReadUInt32();
+ break;
+ }
+ case 18: {
+ if (self_ == null) {
+ Self = new global::Proto.EntityState();
+ }
+ input.ReadMessage(Self);
+ break;
+ }
+ case 26: {
+ nearbyEntities_.AddEntriesFrom(ref input, _repeated_nearbyEntities_codec);
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class UseSkillRequest : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UseSkillRequest());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[13]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public UseSkillRequest() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public UseSkillRequest(UseSkillRequest other) : this() {
+ skillId_ = other.skillId_;
+ targetId_ = other.targetId_;
+ targetPos_ = other.targetPos_ != null ? other.targetPos_.Clone() : null;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public UseSkillRequest Clone() {
+ return new UseSkillRequest(this);
+ }
+
+ /// Field number for the "skill_id" field.
+ public const int SkillIdFieldNumber = 1;
+ private uint skillId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public uint SkillId {
+ get { return skillId_; }
+ set {
+ skillId_ = value;
+ }
+ }
+
+ /// Field number for the "target_id" field.
+ public const int TargetIdFieldNumber = 2;
+ private ulong targetId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ulong TargetId {
+ get { return targetId_; }
+ set {
+ targetId_ = value;
+ }
+ }
+
+ /// Field number for the "target_pos" field.
+ public const int TargetPosFieldNumber = 3;
+ private global::Proto.Vector3 targetPos_;
+ ///
+ /// for ground-targeted AoE
+ ///
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public global::Proto.Vector3 TargetPos {
+ get { return targetPos_; }
+ set {
+ targetPos_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as UseSkillRequest);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(UseSkillRequest other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (SkillId != other.SkillId) return false;
+ if (TargetId != other.TargetId) return false;
+ if (!object.Equals(TargetPos, other.TargetPos)) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (SkillId != 0) hash ^= SkillId.GetHashCode();
+ if (TargetId != 0UL) hash ^= TargetId.GetHashCode();
+ if (targetPos_ != null) hash ^= TargetPos.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (SkillId != 0) {
+ output.WriteRawTag(8);
+ output.WriteUInt32(SkillId);
+ }
+ if (TargetId != 0UL) {
+ output.WriteRawTag(16);
+ output.WriteUInt64(TargetId);
+ }
+ if (targetPos_ != null) {
+ output.WriteRawTag(26);
+ output.WriteMessage(TargetPos);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (SkillId != 0) {
+ output.WriteRawTag(8);
+ output.WriteUInt32(SkillId);
+ }
+ if (TargetId != 0UL) {
+ output.WriteRawTag(16);
+ output.WriteUInt64(TargetId);
+ }
+ if (targetPos_ != null) {
+ output.WriteRawTag(26);
+ output.WriteMessage(TargetPos);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (SkillId != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt32Size(SkillId);
+ }
+ if (TargetId != 0UL) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt64Size(TargetId);
+ }
+ if (targetPos_ != null) {
+ size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetPos);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(UseSkillRequest other) {
+ if (other == null) {
+ return;
+ }
+ if (other.SkillId != 0) {
+ SkillId = other.SkillId;
+ }
+ if (other.TargetId != 0UL) {
+ TargetId = other.TargetId;
+ }
+ if (other.targetPos_ != null) {
+ if (targetPos_ == null) {
+ TargetPos = new global::Proto.Vector3();
+ }
+ TargetPos.MergeFrom(other.TargetPos);
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ SkillId = input.ReadUInt32();
+ break;
+ }
+ case 16: {
+ TargetId = input.ReadUInt64();
+ break;
+ }
+ case 26: {
+ if (targetPos_ == null) {
+ TargetPos = new global::Proto.Vector3();
+ }
+ input.ReadMessage(TargetPos);
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ SkillId = input.ReadUInt32();
+ break;
+ }
+ case 16: {
+ TargetId = input.ReadUInt64();
+ break;
+ }
+ case 26: {
+ if (targetPos_ == null) {
+ TargetPos = new global::Proto.Vector3();
+ }
+ input.ReadMessage(TargetPos);
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class UseSkillResponse : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UseSkillResponse());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[14]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public UseSkillResponse() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public UseSkillResponse(UseSkillResponse other) : this() {
+ success_ = other.success_;
+ errorMessage_ = other.errorMessage_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public UseSkillResponse Clone() {
+ return new UseSkillResponse(this);
+ }
+
+ /// Field number for the "success" field.
+ public const int SuccessFieldNumber = 1;
+ private bool success_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Success {
+ get { return success_; }
+ set {
+ success_ = value;
+ }
+ }
+
+ /// Field number for the "error_message" field.
+ public const int ErrorMessageFieldNumber = 2;
+ private string errorMessage_ = "";
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string ErrorMessage {
+ get { return errorMessage_; }
+ set {
+ errorMessage_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as UseSkillResponse);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(UseSkillResponse other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Success != other.Success) return false;
+ if (ErrorMessage != other.ErrorMessage) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Success != false) hash ^= Success.GetHashCode();
+ if (ErrorMessage.Length != 0) hash ^= ErrorMessage.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (Success != false) {
+ output.WriteRawTag(8);
+ output.WriteBool(Success);
+ }
+ if (ErrorMessage.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(ErrorMessage);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (Success != false) {
+ output.WriteRawTag(8);
+ output.WriteBool(Success);
+ }
+ if (ErrorMessage.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(ErrorMessage);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (Success != false) {
+ size += 1 + 1;
+ }
+ if (ErrorMessage.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(ErrorMessage);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(UseSkillResponse other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Success != false) {
+ Success = other.Success;
+ }
+ if (other.ErrorMessage.Length != 0) {
+ ErrorMessage = other.ErrorMessage;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ Success = input.ReadBool();
+ break;
+ }
+ case 18: {
+ ErrorMessage = input.ReadString();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ Success = input.ReadBool();
+ break;
+ }
+ case 18: {
+ ErrorMessage = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class CombatEvent : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CombatEvent());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[15]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public CombatEvent() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public CombatEvent(CombatEvent other) : this() {
+ casterId_ = other.casterId_;
+ targetId_ = other.targetId_;
+ skillId_ = other.skillId_;
+ damage_ = other.damage_;
+ heal_ = other.heal_;
+ isCritical_ = other.isCritical_;
+ targetDied_ = other.targetDied_;
+ targetHp_ = other.targetHp_;
+ targetMaxHp_ = other.targetMaxHp_;
+ eventType_ = other.eventType_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public CombatEvent Clone() {
+ return new CombatEvent(this);
+ }
+
+ /// Field number for the "caster_id" field.
+ public const int CasterIdFieldNumber = 1;
+ private ulong casterId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ulong CasterId {
+ get { return casterId_; }
+ set {
+ casterId_ = value;
+ }
+ }
+
+ /// Field number for the "target_id" field.
+ public const int TargetIdFieldNumber = 2;
+ private ulong targetId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ulong TargetId {
+ get { return targetId_; }
+ set {
+ targetId_ = value;
+ }
+ }
+
+ /// Field number for the "skill_id" field.
+ public const int SkillIdFieldNumber = 3;
+ private uint skillId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public uint SkillId {
+ get { return skillId_; }
+ set {
+ skillId_ = value;
+ }
+ }
+
+ /// Field number for the "damage" field.
+ public const int DamageFieldNumber = 4;
+ private int damage_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int Damage {
+ get { return damage_; }
+ set {
+ damage_ = value;
+ }
+ }
+
+ /// Field number for the "heal" field.
+ public const int HealFieldNumber = 5;
+ private int heal_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int Heal {
+ get { return heal_; }
+ set {
+ heal_ = value;
+ }
+ }
+
+ /// Field number for the "is_critical" field.
+ public const int IsCriticalFieldNumber = 6;
+ private bool isCritical_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool IsCritical {
+ get { return isCritical_; }
+ set {
+ isCritical_ = value;
+ }
+ }
+
+ /// Field number for the "target_died" field.
+ public const int TargetDiedFieldNumber = 7;
+ private bool targetDied_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool TargetDied {
+ get { return targetDied_; }
+ set {
+ targetDied_ = value;
+ }
+ }
+
+ /// Field number for the "target_hp" field.
+ public const int TargetHpFieldNumber = 8;
+ private int targetHp_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int TargetHp {
+ get { return targetHp_; }
+ set {
+ targetHp_ = value;
+ }
+ }
+
+ /// Field number for the "target_max_hp" field.
+ public const int TargetMaxHpFieldNumber = 9;
+ private int targetMaxHp_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int TargetMaxHp {
+ get { return targetMaxHp_; }
+ set {
+ targetMaxHp_ = value;
+ }
+ }
+
+ /// Field number for the "event_type" field.
+ public const int EventTypeFieldNumber = 10;
+ private global::Proto.CombatEventType eventType_ = global::Proto.CombatEventType.CombatEventDamage;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public global::Proto.CombatEventType EventType {
+ get { return eventType_; }
+ set {
+ eventType_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as CombatEvent);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(CombatEvent other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (CasterId != other.CasterId) return false;
+ if (TargetId != other.TargetId) return false;
+ if (SkillId != other.SkillId) return false;
+ if (Damage != other.Damage) return false;
+ if (Heal != other.Heal) return false;
+ if (IsCritical != other.IsCritical) return false;
+ if (TargetDied != other.TargetDied) return false;
+ if (TargetHp != other.TargetHp) return false;
+ if (TargetMaxHp != other.TargetMaxHp) return false;
+ if (EventType != other.EventType) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (CasterId != 0UL) hash ^= CasterId.GetHashCode();
+ if (TargetId != 0UL) hash ^= TargetId.GetHashCode();
+ if (SkillId != 0) hash ^= SkillId.GetHashCode();
+ if (Damage != 0) hash ^= Damage.GetHashCode();
+ if (Heal != 0) hash ^= Heal.GetHashCode();
+ if (IsCritical != false) hash ^= IsCritical.GetHashCode();
+ if (TargetDied != false) hash ^= TargetDied.GetHashCode();
+ if (TargetHp != 0) hash ^= TargetHp.GetHashCode();
+ if (TargetMaxHp != 0) hash ^= TargetMaxHp.GetHashCode();
+ if (EventType != global::Proto.CombatEventType.CombatEventDamage) hash ^= EventType.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (CasterId != 0UL) {
+ output.WriteRawTag(8);
+ output.WriteUInt64(CasterId);
+ }
+ if (TargetId != 0UL) {
+ output.WriteRawTag(16);
+ output.WriteUInt64(TargetId);
+ }
+ if (SkillId != 0) {
+ output.WriteRawTag(24);
+ output.WriteUInt32(SkillId);
+ }
+ if (Damage != 0) {
+ output.WriteRawTag(32);
+ output.WriteInt32(Damage);
+ }
+ if (Heal != 0) {
+ output.WriteRawTag(40);
+ output.WriteInt32(Heal);
+ }
+ if (IsCritical != false) {
+ output.WriteRawTag(48);
+ output.WriteBool(IsCritical);
+ }
+ if (TargetDied != false) {
+ output.WriteRawTag(56);
+ output.WriteBool(TargetDied);
+ }
+ if (TargetHp != 0) {
+ output.WriteRawTag(64);
+ output.WriteInt32(TargetHp);
+ }
+ if (TargetMaxHp != 0) {
+ output.WriteRawTag(72);
+ output.WriteInt32(TargetMaxHp);
+ }
+ if (EventType != global::Proto.CombatEventType.CombatEventDamage) {
+ output.WriteRawTag(80);
+ output.WriteEnum((int) EventType);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (CasterId != 0UL) {
+ output.WriteRawTag(8);
+ output.WriteUInt64(CasterId);
+ }
+ if (TargetId != 0UL) {
+ output.WriteRawTag(16);
+ output.WriteUInt64(TargetId);
+ }
+ if (SkillId != 0) {
+ output.WriteRawTag(24);
+ output.WriteUInt32(SkillId);
+ }
+ if (Damage != 0) {
+ output.WriteRawTag(32);
+ output.WriteInt32(Damage);
+ }
+ if (Heal != 0) {
+ output.WriteRawTag(40);
+ output.WriteInt32(Heal);
+ }
+ if (IsCritical != false) {
+ output.WriteRawTag(48);
+ output.WriteBool(IsCritical);
+ }
+ if (TargetDied != false) {
+ output.WriteRawTag(56);
+ output.WriteBool(TargetDied);
+ }
+ if (TargetHp != 0) {
+ output.WriteRawTag(64);
+ output.WriteInt32(TargetHp);
+ }
+ if (TargetMaxHp != 0) {
+ output.WriteRawTag(72);
+ output.WriteInt32(TargetMaxHp);
+ }
+ if (EventType != global::Proto.CombatEventType.CombatEventDamage) {
+ output.WriteRawTag(80);
+ output.WriteEnum((int) EventType);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (CasterId != 0UL) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt64Size(CasterId);
+ }
+ if (TargetId != 0UL) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt64Size(TargetId);
+ }
+ if (SkillId != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt32Size(SkillId);
+ }
+ if (Damage != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(Damage);
+ }
+ if (Heal != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(Heal);
+ }
+ if (IsCritical != false) {
+ size += 1 + 1;
+ }
+ if (TargetDied != false) {
+ size += 1 + 1;
+ }
+ if (TargetHp != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(TargetHp);
+ }
+ if (TargetMaxHp != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(TargetMaxHp);
+ }
+ if (EventType != global::Proto.CombatEventType.CombatEventDamage) {
+ size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) EventType);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(CombatEvent other) {
+ if (other == null) {
+ return;
+ }
+ if (other.CasterId != 0UL) {
+ CasterId = other.CasterId;
+ }
+ if (other.TargetId != 0UL) {
+ TargetId = other.TargetId;
+ }
+ if (other.SkillId != 0) {
+ SkillId = other.SkillId;
+ }
+ if (other.Damage != 0) {
+ Damage = other.Damage;
+ }
+ if (other.Heal != 0) {
+ Heal = other.Heal;
+ }
+ if (other.IsCritical != false) {
+ IsCritical = other.IsCritical;
+ }
+ if (other.TargetDied != false) {
+ TargetDied = other.TargetDied;
+ }
+ if (other.TargetHp != 0) {
+ TargetHp = other.TargetHp;
+ }
+ if (other.TargetMaxHp != 0) {
+ TargetMaxHp = other.TargetMaxHp;
+ }
+ if (other.EventType != global::Proto.CombatEventType.CombatEventDamage) {
+ EventType = other.EventType;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ CasterId = input.ReadUInt64();
+ break;
+ }
+ case 16: {
+ TargetId = input.ReadUInt64();
+ break;
+ }
+ case 24: {
+ SkillId = input.ReadUInt32();
+ break;
+ }
+ case 32: {
+ Damage = input.ReadInt32();
+ break;
+ }
+ case 40: {
+ Heal = input.ReadInt32();
+ break;
+ }
+ case 48: {
+ IsCritical = input.ReadBool();
+ break;
+ }
+ case 56: {
+ TargetDied = input.ReadBool();
+ break;
+ }
+ case 64: {
+ TargetHp = input.ReadInt32();
+ break;
+ }
+ case 72: {
+ TargetMaxHp = input.ReadInt32();
+ break;
+ }
+ case 80: {
+ EventType = (global::Proto.CombatEventType) input.ReadEnum();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ CasterId = input.ReadUInt64();
+ break;
+ }
+ case 16: {
+ TargetId = input.ReadUInt64();
+ break;
+ }
+ case 24: {
+ SkillId = input.ReadUInt32();
+ break;
+ }
+ case 32: {
+ Damage = input.ReadInt32();
+ break;
+ }
+ case 40: {
+ Heal = input.ReadInt32();
+ break;
+ }
+ case 48: {
+ IsCritical = input.ReadBool();
+ break;
+ }
+ case 56: {
+ TargetDied = input.ReadBool();
+ break;
+ }
+ case 64: {
+ TargetHp = input.ReadInt32();
+ break;
+ }
+ case 72: {
+ TargetMaxHp = input.ReadInt32();
+ break;
+ }
+ case 80: {
+ EventType = (global::Proto.CombatEventType) input.ReadEnum();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class BuffApplied : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BuffApplied());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[16]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public BuffApplied() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public BuffApplied(BuffApplied other) : this() {
+ targetId_ = other.targetId_;
+ buffId_ = other.buffId_;
+ buffName_ = other.buffName_;
+ duration_ = other.duration_;
+ isDebuff_ = other.isDebuff_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public BuffApplied Clone() {
+ return new BuffApplied(this);
+ }
+
+ /// Field number for the "target_id" field.
+ public const int TargetIdFieldNumber = 1;
+ private ulong targetId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ulong TargetId {
+ get { return targetId_; }
+ set {
+ targetId_ = value;
+ }
+ }
+
+ /// Field number for the "buff_id" field.
+ public const int BuffIdFieldNumber = 2;
+ private uint buffId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public uint BuffId {
+ get { return buffId_; }
+ set {
+ buffId_ = value;
+ }
+ }
+
+ /// Field number for the "buff_name" field.
+ public const int BuffNameFieldNumber = 3;
+ private string buffName_ = "";
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string BuffName {
+ get { return buffName_; }
+ set {
+ buffName_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ /// Field number for the "duration" field.
+ public const int DurationFieldNumber = 4;
+ private float duration_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public float Duration {
+ get { return duration_; }
+ set {
+ duration_ = value;
+ }
+ }
+
+ /// Field number for the "is_debuff" field.
+ public const int IsDebuffFieldNumber = 5;
+ private bool isDebuff_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool IsDebuff {
+ get { return isDebuff_; }
+ set {
+ isDebuff_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as BuffApplied);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(BuffApplied other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (TargetId != other.TargetId) return false;
+ if (BuffId != other.BuffId) return false;
+ if (BuffName != other.BuffName) return false;
+ if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(Duration, other.Duration)) return false;
+ if (IsDebuff != other.IsDebuff) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (TargetId != 0UL) hash ^= TargetId.GetHashCode();
+ if (BuffId != 0) hash ^= BuffId.GetHashCode();
+ if (BuffName.Length != 0) hash ^= BuffName.GetHashCode();
+ if (Duration != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(Duration);
+ if (IsDebuff != false) hash ^= IsDebuff.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (TargetId != 0UL) {
+ output.WriteRawTag(8);
+ output.WriteUInt64(TargetId);
+ }
+ if (BuffId != 0) {
+ output.WriteRawTag(16);
+ output.WriteUInt32(BuffId);
+ }
+ if (BuffName.Length != 0) {
+ output.WriteRawTag(26);
+ output.WriteString(BuffName);
+ }
+ if (Duration != 0F) {
+ output.WriteRawTag(37);
+ output.WriteFloat(Duration);
+ }
+ if (IsDebuff != false) {
+ output.WriteRawTag(40);
+ output.WriteBool(IsDebuff);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (TargetId != 0UL) {
+ output.WriteRawTag(8);
+ output.WriteUInt64(TargetId);
+ }
+ if (BuffId != 0) {
+ output.WriteRawTag(16);
+ output.WriteUInt32(BuffId);
+ }
+ if (BuffName.Length != 0) {
+ output.WriteRawTag(26);
+ output.WriteString(BuffName);
+ }
+ if (Duration != 0F) {
+ output.WriteRawTag(37);
+ output.WriteFloat(Duration);
+ }
+ if (IsDebuff != false) {
+ output.WriteRawTag(40);
+ output.WriteBool(IsDebuff);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (TargetId != 0UL) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt64Size(TargetId);
+ }
+ if (BuffId != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt32Size(BuffId);
+ }
+ if (BuffName.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(BuffName);
+ }
+ if (Duration != 0F) {
+ size += 1 + 4;
+ }
+ if (IsDebuff != false) {
+ size += 1 + 1;
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(BuffApplied other) {
+ if (other == null) {
+ return;
+ }
+ if (other.TargetId != 0UL) {
+ TargetId = other.TargetId;
+ }
+ if (other.BuffId != 0) {
+ BuffId = other.BuffId;
+ }
+ if (other.BuffName.Length != 0) {
+ BuffName = other.BuffName;
+ }
+ if (other.Duration != 0F) {
+ Duration = other.Duration;
+ }
+ if (other.IsDebuff != false) {
+ IsDebuff = other.IsDebuff;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ TargetId = input.ReadUInt64();
+ break;
+ }
+ case 16: {
+ BuffId = input.ReadUInt32();
+ break;
+ }
+ case 26: {
+ BuffName = input.ReadString();
+ break;
+ }
+ case 37: {
+ Duration = input.ReadFloat();
+ break;
+ }
+ case 40: {
+ IsDebuff = input.ReadBool();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ TargetId = input.ReadUInt64();
+ break;
+ }
+ case 16: {
+ BuffId = input.ReadUInt32();
+ break;
+ }
+ case 26: {
+ BuffName = input.ReadString();
+ break;
+ }
+ case 37: {
+ Duration = input.ReadFloat();
+ break;
+ }
+ case 40: {
+ IsDebuff = input.ReadBool();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class BuffRemoved : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BuffRemoved());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[17]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public BuffRemoved() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public BuffRemoved(BuffRemoved other) : this() {
+ targetId_ = other.targetId_;
+ buffId_ = other.buffId_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public BuffRemoved Clone() {
+ return new BuffRemoved(this);
+ }
+
+ /// Field number for the "target_id" field.
+ public const int TargetIdFieldNumber = 1;
+ private ulong targetId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ulong TargetId {
+ get { return targetId_; }
+ set {
+ targetId_ = value;
+ }
+ }
+
+ /// Field number for the "buff_id" field.
+ public const int BuffIdFieldNumber = 2;
+ private uint buffId_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public uint BuffId {
+ get { return buffId_; }
+ set {
+ buffId_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as BuffRemoved);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(BuffRemoved other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (TargetId != other.TargetId) return false;
+ if (BuffId != other.BuffId) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (TargetId != 0UL) hash ^= TargetId.GetHashCode();
+ if (BuffId != 0) hash ^= BuffId.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (TargetId != 0UL) {
+ output.WriteRawTag(8);
+ output.WriteUInt64(TargetId);
+ }
+ if (BuffId != 0) {
+ output.WriteRawTag(16);
+ output.WriteUInt32(BuffId);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (TargetId != 0UL) {
+ output.WriteRawTag(8);
+ output.WriteUInt64(TargetId);
+ }
+ if (BuffId != 0) {
+ output.WriteRawTag(16);
+ output.WriteUInt32(BuffId);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (TargetId != 0UL) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt64Size(TargetId);
+ }
+ if (BuffId != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeUInt32Size(BuffId);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(BuffRemoved other) {
+ if (other == null) {
+ return;
+ }
+ if (other.TargetId != 0UL) {
+ TargetId = other.TargetId;
+ }
+ if (other.BuffId != 0) {
+ BuffId = other.BuffId;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ TargetId = input.ReadUInt64();
+ break;
+ }
+ case 16: {
+ BuffId = input.ReadUInt32();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ TargetId = input.ReadUInt64();
+ break;
+ }
+ case 16: {
+ BuffId = input.ReadUInt32();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class RespawnRequest : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RespawnRequest());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[18]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public RespawnRequest() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public RespawnRequest(RespawnRequest other) : this() {
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public RespawnRequest Clone() {
+ return new RespawnRequest(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as RespawnRequest);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(RespawnRequest other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(RespawnRequest other) {
+ if (other == null) {
+ return;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class RespawnResponse : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RespawnResponse());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[19]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public RespawnResponse() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public RespawnResponse(RespawnResponse other) : this() {
+ self_ = other.self_ != null ? other.self_.Clone() : null;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public RespawnResponse Clone() {
+ return new RespawnResponse(this);
+ }
+
+ /// Field number for the "self" field.
+ public const int SelfFieldNumber = 1;
+ private global::Proto.EntityState self_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public global::Proto.EntityState Self {
+ get { return self_; }
+ set {
+ self_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as RespawnResponse);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(RespawnResponse other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (!object.Equals(Self, other.Self)) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (self_ != null) hash ^= Self.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (self_ != null) {
+ output.WriteRawTag(10);
+ output.WriteMessage(Self);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (self_ != null) {
+ output.WriteRawTag(10);
+ output.WriteMessage(Self);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (self_ != null) {
+ size += 1 + pb::CodedOutputStream.ComputeMessageSize(Self);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(RespawnResponse other) {
+ if (other == null) {
+ return;
+ }
+ if (other.self_ != null) {
+ if (self_ == null) {
+ Self = new global::Proto.EntityState();
+ }
+ Self.MergeFrom(other.Self);
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 10: {
+ if (self_ == null) {
+ Self = new global::Proto.EntityState();
+ }
+ input.ReadMessage(Self);
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 10: {
+ if (self_ == null) {
+ Self = new global::Proto.EntityState();
+ }
+ input.ReadMessage(Self);
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class AOIToggleRequest : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AOIToggleRequest());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[20]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public AOIToggleRequest() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public AOIToggleRequest(AOIToggleRequest other) : this() {
+ enabled_ = other.enabled_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public AOIToggleRequest Clone() {
+ return new AOIToggleRequest(this);
+ }
+
+ /// Field number for the "enabled" field.
+ public const int EnabledFieldNumber = 1;
+ private bool enabled_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Enabled {
+ get { return enabled_; }
+ set {
+ enabled_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as AOIToggleRequest);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(AOIToggleRequest other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Enabled != other.Enabled) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Enabled != false) hash ^= Enabled.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (Enabled != false) {
+ output.WriteRawTag(8);
+ output.WriteBool(Enabled);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (Enabled != false) {
+ output.WriteRawTag(8);
+ output.WriteBool(Enabled);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (Enabled != false) {
+ size += 1 + 1;
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(AOIToggleRequest other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Enabled != false) {
+ Enabled = other.Enabled;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ Enabled = input.ReadBool();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ Enabled = input.ReadBool();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class AOIToggleResponse : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AOIToggleResponse());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[21]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public AOIToggleResponse() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public AOIToggleResponse(AOIToggleResponse other) : this() {
+ enabled_ = other.enabled_;
+ message_ = other.message_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public AOIToggleResponse Clone() {
+ return new AOIToggleResponse(this);
+ }
+
+ /// Field number for the "enabled" field.
+ public const int EnabledFieldNumber = 1;
+ private bool enabled_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Enabled {
+ get { return enabled_; }
+ set {
+ enabled_ = value;
+ }
+ }
+
+ /// Field number for the "message" field.
+ public const int MessageFieldNumber = 2;
+ private string message_ = "";
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public string Message {
+ get { return message_; }
+ set {
+ message_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as AOIToggleResponse);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(AOIToggleResponse other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Enabled != other.Enabled) return false;
+ if (Message != other.Message) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Enabled != false) hash ^= Enabled.GetHashCode();
+ if (Message.Length != 0) hash ^= Message.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (Enabled != false) {
+ output.WriteRawTag(8);
+ output.WriteBool(Enabled);
+ }
+ if (Message.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(Message);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (Enabled != false) {
+ output.WriteRawTag(8);
+ output.WriteBool(Enabled);
+ }
+ if (Message.Length != 0) {
+ output.WriteRawTag(18);
+ output.WriteString(Message);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (Enabled != false) {
+ size += 1 + 1;
+ }
+ if (Message.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Message);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(AOIToggleResponse other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Enabled != false) {
+ Enabled = other.Enabled;
+ }
+ if (other.Message.Length != 0) {
+ Message = other.Message;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ Enabled = input.ReadBool();
+ break;
+ }
+ case 18: {
+ Message = input.ReadString();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ Enabled = input.ReadBool();
+ break;
+ }
+ case 18: {
+ Message = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class ServerMetrics : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ServerMetrics());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[22]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ServerMetrics() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ServerMetrics(ServerMetrics other) : this() {
+ onlinePlayers_ = other.onlinePlayers_;
+ totalEntities_ = other.totalEntities_;
+ tickDurationUs_ = other.tickDurationUs_;
+ aoiEnabled_ = other.aoiEnabled_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public ServerMetrics Clone() {
+ return new ServerMetrics(this);
+ }
+
+ /// Field number for the "online_players" field.
+ public const int OnlinePlayersFieldNumber = 1;
+ private int onlinePlayers_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int OnlinePlayers {
+ get { return onlinePlayers_; }
+ set {
+ onlinePlayers_ = value;
+ }
+ }
+
+ /// Field number for the "total_entities" field.
+ public const int TotalEntitiesFieldNumber = 2;
+ private int totalEntities_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int TotalEntities {
+ get { return totalEntities_; }
+ set {
+ totalEntities_ = value;
+ }
+ }
+
+ /// Field number for the "tick_duration_us" field.
+ public const int TickDurationUsFieldNumber = 3;
+ private long tickDurationUs_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public long TickDurationUs {
+ get { return tickDurationUs_; }
+ set {
+ tickDurationUs_ = value;
+ }
+ }
+
+ /// Field number for the "aoi_enabled" field.
+ public const int AoiEnabledFieldNumber = 4;
+ private bool aoiEnabled_;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool AoiEnabled {
+ get { return aoiEnabled_; }
+ set {
+ aoiEnabled_ = value;
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as ServerMetrics);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(ServerMetrics other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (OnlinePlayers != other.OnlinePlayers) return false;
+ if (TotalEntities != other.TotalEntities) return false;
+ if (TickDurationUs != other.TickDurationUs) return false;
+ if (AoiEnabled != other.AoiEnabled) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (OnlinePlayers != 0) hash ^= OnlinePlayers.GetHashCode();
+ if (TotalEntities != 0) hash ^= TotalEntities.GetHashCode();
+ if (TickDurationUs != 0L) hash ^= TickDurationUs.GetHashCode();
+ if (AoiEnabled != false) hash ^= AoiEnabled.GetHashCode();
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (OnlinePlayers != 0) {
+ output.WriteRawTag(8);
+ output.WriteInt32(OnlinePlayers);
+ }
+ if (TotalEntities != 0) {
+ output.WriteRawTag(16);
+ output.WriteInt32(TotalEntities);
+ }
+ if (TickDurationUs != 0L) {
+ output.WriteRawTag(24);
+ output.WriteInt64(TickDurationUs);
+ }
+ if (AoiEnabled != false) {
+ output.WriteRawTag(32);
+ output.WriteBool(AoiEnabled);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (OnlinePlayers != 0) {
+ output.WriteRawTag(8);
+ output.WriteInt32(OnlinePlayers);
+ }
+ if (TotalEntities != 0) {
+ output.WriteRawTag(16);
+ output.WriteInt32(TotalEntities);
+ }
+ if (TickDurationUs != 0L) {
+ output.WriteRawTag(24);
+ output.WriteInt64(TickDurationUs);
+ }
+ if (AoiEnabled != false) {
+ output.WriteRawTag(32);
+ output.WriteBool(AoiEnabled);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (OnlinePlayers != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(OnlinePlayers);
+ }
+ if (TotalEntities != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeInt32Size(TotalEntities);
+ }
+ if (TickDurationUs != 0L) {
+ size += 1 + pb::CodedOutputStream.ComputeInt64Size(TickDurationUs);
+ }
+ if (AoiEnabled != false) {
+ size += 1 + 1;
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(ServerMetrics other) {
+ if (other == null) {
+ return;
+ }
+ if (other.OnlinePlayers != 0) {
+ OnlinePlayers = other.OnlinePlayers;
+ }
+ if (other.TotalEntities != 0) {
+ TotalEntities = other.TotalEntities;
+ }
+ if (other.TickDurationUs != 0L) {
+ TickDurationUs = other.TickDurationUs;
+ }
+ if (other.AoiEnabled != false) {
+ AoiEnabled = other.AoiEnabled;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ case 8: {
+ OnlinePlayers = input.ReadInt32();
+ break;
+ }
+ case 16: {
+ TotalEntities = input.ReadInt32();
+ break;
+ }
+ case 24: {
+ TickDurationUs = input.ReadInt64();
+ break;
+ }
+ case 32: {
+ AoiEnabled = input.ReadBool();
+ break;
+ }
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ case 8: {
+ OnlinePlayers = input.ReadInt32();
+ break;
+ }
+ case 16: {
+ TotalEntities = input.ReadInt32();
+ break;
+ }
+ case 24: {
+ TickDurationUs = input.ReadInt64();
+ break;
+ }
+ case 32: {
+ AoiEnabled = input.ReadBool();
+ break;
+ }
+ }
+ }
+ }
+ #endif
+
+ }
+
+ [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
+ public sealed partial class MetricsRequest : pb::IMessage
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ , pb::IBufferMessage
+ #endif
+ {
+ private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new MetricsRequest());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pb::MessageParser Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Proto.MessagesReflection.Descriptor.MessageTypes[23]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public MetricsRequest() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public MetricsRequest(MetricsRequest other) : this() {
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public MetricsRequest Clone() {
+ return new MetricsRequest(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override bool Equals(object other) {
+ return Equals(other as MetricsRequest);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public bool Equals(MetricsRequest other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (_unknownFields != null) {
+ hash ^= _unknownFields.GetHashCode();
+ }
+ return hash;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public override string ToString() {
+ return pb::JsonFormatter.ToDiagnosticString(this);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void WriteTo(pb::CodedOutputStream output) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ output.WriteRawMessage(this);
+ #else
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(ref output);
+ }
+ }
+ #endif
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public int CalculateSize() {
+ int size = 0;
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(MetricsRequest other) {
+ if (other == null) {
+ return;
+ }
+ _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ public void MergeFrom(pb::CodedInputStream input) {
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ input.ReadRawMessage(this);
+ #else
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+ break;
+ }
+ }
+ #endif
+ }
+
+ #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
+ void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
+ uint tag;
+ while ((tag = input.ReadTag()) != 0) {
+ if ((tag & 7) == 4) {
+ // Abort on any end group tag.
+ return;
+ }
+ switch(tag) {
+ default:
+ _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
+ break;
+ }
+ }
+ }
+ #endif
+
+ }
+
+ #endregion
+
+}
+
+#endregion Designer generated code