feat : 존 기능 초기 커밋
This commit is contained in:
@@ -13,14 +13,11 @@ namespace MMOserver.Game.Channel;
|
||||
* - 인스턴스 던전 관리
|
||||
* - 동적 맵 id 생성
|
||||
*/
|
||||
public class Channel
|
||||
public class Channel : UserContainer
|
||||
{
|
||||
// 채널 내 유저 NetPeer (hashKey → NetPeer) — BroadcastToChannel 교차 조회 제거용
|
||||
private readonly Dictionary<int, NetPeer> connectPeers = new();
|
||||
|
||||
// 채널 내 유저 상태 (hashKey → Player)
|
||||
private readonly Dictionary<int, Player> connectUsers = new();
|
||||
|
||||
// 채널 맵 관리
|
||||
private readonly Dictionary<int, AMap> maps = new();
|
||||
|
||||
@@ -37,17 +34,32 @@ public class Channel
|
||||
{
|
||||
ChannelId = channelId;
|
||||
|
||||
// 일단 하드코딩으로 맵 생성
|
||||
foreach (MapConfigData config in MapLoader.Data.Maps)
|
||||
{
|
||||
// 로비
|
||||
maps.Add(1, new Robby(1));
|
||||
|
||||
// 인던
|
||||
int defaultValue = 1000;
|
||||
for (int i = 1; i <= 10; i++)
|
||||
AMap? map = MapLoader.ParseMapType(config.MapType) switch
|
||||
{
|
||||
maps.Add(i + defaultValue, new BossInstance(i + defaultValue));
|
||||
EnumMap.ROBBY => new Robby(config.MapId),
|
||||
EnumMap.DUNGEON => new BossInstance(config.MapId),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 맵정보
|
||||
map.SetZoneConfig(config.Rows, config.Cols, config.ZoneSize);
|
||||
// 맵내에 존 생성
|
||||
map.CreateZones(config.Zones);
|
||||
maps.Add(config.MapId, map);
|
||||
}
|
||||
|
||||
// 인던은 동적 생성 유지
|
||||
int defaultValue = 1000;
|
||||
for (int i = 1; i <= 10; i++)
|
||||
{
|
||||
maps.Add(i + defaultValue, new BossInstance(i + defaultValue));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +68,6 @@ public class Channel
|
||||
get;
|
||||
}
|
||||
|
||||
public int UserCount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public int UserCountMax
|
||||
{
|
||||
get;
|
||||
@@ -70,28 +76,23 @@ public class Channel
|
||||
|
||||
public void AddUser(int userId, Player player, NetPeer peer)
|
||||
{
|
||||
connectUsers[userId] = player;
|
||||
base.AddUser(userId, player);
|
||||
users[userId] = player;
|
||||
connectPeers[userId] = peer;
|
||||
UserCount++;
|
||||
|
||||
// 처음 접속 시 1번 맵(로비)으로 입장
|
||||
ChangeMap(userId, player, 1);
|
||||
}
|
||||
|
||||
public void RemoveUser(int userId)
|
||||
public override void RemoveUser(int userId)
|
||||
{
|
||||
base.RemoveUser(userId);
|
||||
// 현재 맵에서도 제거
|
||||
if (connectUsers.TryGetValue(userId, out Player? player) &&
|
||||
maps.TryGetValue(player.CurrentMapId, out AMap? currentMap))
|
||||
if (maps.TryGetValue(userId, out AMap? currentMap))
|
||||
{
|
||||
currentMap.RemoveUser(userId);
|
||||
}
|
||||
|
||||
if (connectUsers.Remove(userId))
|
||||
{
|
||||
UserCount--;
|
||||
}
|
||||
|
||||
connectPeers.Remove(userId);
|
||||
}
|
||||
|
||||
@@ -111,6 +112,10 @@ public class Channel
|
||||
|
||||
newMap.AddUser(userId, player);
|
||||
player.CurrentMapId = mapId;
|
||||
player.CurrentZoneId = 0;
|
||||
|
||||
// 현재 맵에 0번 존에 추가
|
||||
GetMap(mapId)?.GetZone(0)?.AddUser(player.PlayerId, player);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -120,31 +125,12 @@ public class Channel
|
||||
connectPeers[userId] = peer;
|
||||
}
|
||||
|
||||
// 채널 내 모든 유저의 hashKey 반환 (채널 입장 시 기존 플레이어 목록 조회용)
|
||||
public IEnumerable<int> GetConnectUsers()
|
||||
{
|
||||
return connectUsers.Keys;
|
||||
}
|
||||
|
||||
// 채널 내 모든 Player 반환
|
||||
public IEnumerable<Player> GetPlayers()
|
||||
{
|
||||
return connectUsers.Values;
|
||||
}
|
||||
|
||||
// 채널 내 모든 NetPeer 반환 — BroadcastToChannel 전용 (sessions 교차 조회 불필요)
|
||||
public IEnumerable<NetPeer> GetConnectPeers()
|
||||
{
|
||||
return connectPeers.Values;
|
||||
}
|
||||
|
||||
// 특정 유저의 Player 반환
|
||||
public Player? GetPlayer(int userId)
|
||||
{
|
||||
connectUsers.TryGetValue(userId, out Player? player);
|
||||
return player;
|
||||
}
|
||||
|
||||
// 특정 유저의 NetPeer 반환
|
||||
public NetPeer? GetPeer(int userId)
|
||||
{
|
||||
@@ -152,9 +138,10 @@ public class Channel
|
||||
return peer;
|
||||
}
|
||||
|
||||
public int HasUser(int userId)
|
||||
// 유저가 존재하면 채널id를 넘긴다
|
||||
public override int HasUser(int userId)
|
||||
{
|
||||
if (connectUsers.ContainsKey(userId))
|
||||
if (users.ContainsKey(userId))
|
||||
{
|
||||
return ChannelId;
|
||||
}
|
||||
@@ -217,6 +204,4 @@ public class Channel
|
||||
{
|
||||
return partyManager;
|
||||
}
|
||||
|
||||
// TODO : 채널 가져오기
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using MMOserver.Game.Channel.Maps.ZoneData;
|
||||
using MMOserver.Game.Engine;
|
||||
|
||||
namespace MMOserver.Game.Channel.Maps;
|
||||
|
||||
/*
|
||||
@@ -5,25 +8,83 @@ namespace MMOserver.Game.Channel.Maps;
|
||||
* - 각 존 관리
|
||||
* - 맵의 유저 관리
|
||||
*/
|
||||
public abstract class AMap
|
||||
public abstract class AMap : UserContainer
|
||||
{
|
||||
private Dictionary<int, Player> users = new Dictionary<int, Player>();
|
||||
|
||||
public abstract EnumMap GetMapType();
|
||||
public abstract int GetMapId();
|
||||
|
||||
public void AddUser(int userId, Player player)
|
||||
// 존 그리드 설정 (MapLoader에서 주입)
|
||||
public int Rows { get; private set; }
|
||||
public int Cols { get; private set; }
|
||||
public int ZoneSize { get; private set; }
|
||||
|
||||
// 존 관리
|
||||
private readonly Dictionary<int, Zone> zones = new();
|
||||
|
||||
public void SetZoneConfig(int rows, int cols, int zoneSize)
|
||||
{
|
||||
users[userId] = player;
|
||||
Rows = rows;
|
||||
Cols = cols;
|
||||
ZoneSize = zoneSize;
|
||||
}
|
||||
|
||||
public void RemoveUser(int userId)
|
||||
// 존 생성
|
||||
public void CreateZones(List<ZoneConfigData> zoneConfigs)
|
||||
{
|
||||
users.Remove(userId);
|
||||
foreach (ZoneConfigData config in zoneConfigs)
|
||||
{
|
||||
Vector3 position = new Vector3(config.CenterX, 0, config.CenterZ);
|
||||
Vector3 size = new Vector3(ZoneSize, 0, ZoneSize);
|
||||
Zone zone = new Zone(this, config.ZoneId, config.Row, config.Col, position, size);
|
||||
AddZone(zone);
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<int, Player> GetUsers()
|
||||
public void AddZone(Zone zone)
|
||||
{
|
||||
return users;
|
||||
zones[zone.ZoneId] = zone;
|
||||
}
|
||||
|
||||
public Zone? GetZone(int zoneId)
|
||||
{
|
||||
zones.TryGetValue(zoneId, out Zone? zone);
|
||||
return zone;
|
||||
}
|
||||
|
||||
// 현재 맵의 근처 존 가져오기
|
||||
public virtual (int, int, int, int) GetNearZone(int zoneId, Vector3? position)
|
||||
{
|
||||
return (zoneId, -1, -1, -1);
|
||||
}
|
||||
|
||||
// 좌상단/우상단/우하단/좌하단 판정
|
||||
public int ZoneAt(int row, int col)
|
||||
{
|
||||
return (row >= 0 && row < Rows && col >= 0 && col < Cols) ? row * Cols + col : -1;
|
||||
}
|
||||
|
||||
// 이동시 zone업데이트
|
||||
public bool UpdatePlayerZone(int userId, Player player)
|
||||
{
|
||||
// 플레이어의 위치는 이동한 상태
|
||||
int col = (int)(player.Position.X / ZoneSize);
|
||||
int row = (int)(player.Position.Z / ZoneSize);
|
||||
int newZoneId = ZoneAt(row, col);
|
||||
|
||||
if (newZoneId == player.CurrentZoneId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 범위 밖으로 이동한 상태 일단 존은 그대로 둔다.
|
||||
if (newZoneId < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GetZone(player.CurrentZoneId)?.RemoveUser(userId);
|
||||
GetZone(newZoneId)!.AddUser(userId, player);
|
||||
player.CurrentZoneId = newZoneId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
60
MMOTestServer/MMOserver/Game/Channel/Maps/MapLoader.cs
Normal file
60
MMOTestServer/MMOserver/Game/Channel/Maps/MapLoader.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Text.Json;
|
||||
using MMOserver.Game.Channel.Maps.InstanceDungeun;
|
||||
|
||||
namespace MMOserver.Game.Channel.Maps;
|
||||
|
||||
// Zone 데이터 구조
|
||||
public record ZoneConfigData(
|
||||
int ZoneId,
|
||||
int Row,
|
||||
int Col,
|
||||
int CenterX,
|
||||
int CenterZ
|
||||
);
|
||||
|
||||
// Map 데이터 구조
|
||||
public record MapConfigData(
|
||||
int MapId,
|
||||
string MapType,
|
||||
int Rows,
|
||||
int Cols,
|
||||
int ZoneSize,
|
||||
List<ZoneConfigData> Zones
|
||||
);
|
||||
|
||||
// 한 채널의 Map정보 List
|
||||
public record MapFileData(
|
||||
List<MapConfigData> Maps
|
||||
);
|
||||
|
||||
/*
|
||||
* maps.json 로더
|
||||
* - JSON 파싱만 담당
|
||||
* - Program.cs에서 Initialize() 1회 호출
|
||||
*/
|
||||
public static class MapLoader
|
||||
{
|
||||
private static readonly JsonSerializerOptions Options = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public static MapFileData Data { get; private set; } = null!;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
string json = File.ReadAllText("maps.json");
|
||||
Data = JsonSerializer.Deserialize<MapFileData>(json, Options) ?? throw new InvalidOperationException("maps.json 파싱 실패");
|
||||
}
|
||||
|
||||
public static EnumMap ParseMapType(string mapType)
|
||||
{
|
||||
return mapType.ToUpper() switch
|
||||
{
|
||||
"ROBBY" => EnumMap.ROBBY,
|
||||
"DUNGEON" => EnumMap.DUNGEON,
|
||||
"INSTANCE" => EnumMap.INSTANCE,
|
||||
_ => EnumMap.NONE
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using MMOserver.Game.Engine;
|
||||
using MMOserver.Game.Channel.Maps.ZoneData;
|
||||
using MMOserver.Game.Engine;
|
||||
|
||||
namespace MMOserver.Game.Channel.Maps;
|
||||
|
||||
@@ -7,10 +8,10 @@ namespace MMOserver.Game.Channel.Maps;
|
||||
*/
|
||||
public class Robby : AMap
|
||||
{
|
||||
private EnumMap enumMap;
|
||||
private int mapId;
|
||||
private readonly EnumMap enumMap;
|
||||
private readonly int mapId;
|
||||
|
||||
public Robby(int mapId, EnumMap enumMap = EnumMap.ROBBY)
|
||||
public Robby(int mapId, EnumMap enumMap = EnumMap.ROBBY)
|
||||
{
|
||||
this.enumMap = enumMap;
|
||||
this.mapId = mapId;
|
||||
@@ -25,4 +26,31 @@ public class Robby : AMap
|
||||
{
|
||||
return mapId;
|
||||
}
|
||||
|
||||
public override (int, int, int, int) GetNearZone(int zoneId, Vector3? position)
|
||||
{
|
||||
if (position == null || zoneId < 0)
|
||||
{
|
||||
return base.GetNearZone(zoneId, position);
|
||||
}
|
||||
|
||||
Zone? zone = GetZone(zoneId);
|
||||
if (zone == null)
|
||||
{
|
||||
return base.GetNearZone(zoneId, position);
|
||||
}
|
||||
|
||||
int quadrant = zone.GetFourquadrant(position);
|
||||
int row = zone.Row;
|
||||
int col = zone.Col;
|
||||
|
||||
return quadrant switch
|
||||
{
|
||||
1 => (zoneId, ZoneAt(row - 1, col), ZoneAt(row, col - 1), ZoneAt(row - 1, col - 1)), // 위, 좌, 좌상단
|
||||
2 => (zoneId, ZoneAt(row - 1, col), ZoneAt(row, col + 1), ZoneAt(row - 1, col + 1)), // 위, 우, 우상단
|
||||
3 => (zoneId, ZoneAt(row + 1, col), ZoneAt(row, col + 1), ZoneAt(row + 1, col + 1)), // 아래, 우, 우하단
|
||||
4 => (zoneId, ZoneAt(row + 1, col), ZoneAt(row, col - 1), ZoneAt(row + 1, col - 1)), // 아래, 좌, 좌하단
|
||||
_ => base.GetNearZone(zoneId, position) // 0 = 중앙, 현재 존만
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
89
MMOTestServer/MMOserver/Game/Channel/Maps/ZoneData/Zone.cs
Normal file
89
MMOTestServer/MMOserver/Game/Channel/Maps/ZoneData/Zone.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using MMOserver.Game.Engine;
|
||||
|
||||
namespace MMOserver.Game.Channel.Maps.ZoneData;
|
||||
|
||||
/*
|
||||
* 존 단위로 변경시킨다.
|
||||
* 기존 맵 단위로 유저관리를 존 단위로 나눈다.
|
||||
* 마을 존 / 전투 필드 존 타입을 분기 시킨다.
|
||||
*/
|
||||
public class Zone : UserContainer
|
||||
{
|
||||
public Zone(AMap map, int zoneId, int row, int col, Vector3 position, Vector3 size)
|
||||
{
|
||||
Map = new WeakReference<AMap>(map);
|
||||
ZoneId = zoneId;
|
||||
Row = row;
|
||||
Col = col;
|
||||
Position = position;
|
||||
ZoneSize = size;
|
||||
}
|
||||
|
||||
// Map은 약한참조를 들고 있는다 순환참조 방지
|
||||
public WeakReference<AMap> Map
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
// 그리드 인덱스 (zoneId = Row * Cols + Col)
|
||||
public int ZoneId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public int Row
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public int Col
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
// 존 중심 좌표
|
||||
public Vector3 Position
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
// 존 사이즈
|
||||
public Vector3 ZoneSize
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
// 플레이어 위치 기준 현재 존 내 분면 반환
|
||||
public int GetFourquadrant(Vector3 position)
|
||||
{
|
||||
float dx = position.X - Position.X;
|
||||
float dz = position.Z - Position.Z;
|
||||
|
||||
// 중앙 영역 (존 크기의 1/4 이내) → 현재 존만 브로드캐스트
|
||||
float threshold = ZoneSize.X / 4;
|
||||
if (Math.Abs(dx) <= threshold && Math.Abs(dz) <= threshold)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (dx <= 0 && dz <= 0)
|
||||
{
|
||||
// 좌상단
|
||||
return 1;
|
||||
}
|
||||
if (dx > 0 && dz <= 0)
|
||||
{
|
||||
// 우상단
|
||||
return 2;
|
||||
}
|
||||
if (dx > 0 && dz > 0)
|
||||
{
|
||||
// 우하단
|
||||
return 3;
|
||||
}
|
||||
// 좌하단
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
47
MMOTestServer/MMOserver/Game/Channel/UserContainer.cs
Normal file
47
MMOTestServer/MMOserver/Game/Channel/UserContainer.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using MMOserver.Game.Service;
|
||||
using ServerLib.Service;
|
||||
|
||||
namespace MMOserver.Game.Channel;
|
||||
|
||||
public abstract class UserContainer
|
||||
{
|
||||
protected readonly Dictionary<int, Player> users = new();
|
||||
|
||||
public virtual void AddUser(int userId, Player player)
|
||||
{
|
||||
users.Add(userId, player);
|
||||
}
|
||||
|
||||
public virtual void RemoveUser(int userId)
|
||||
{
|
||||
users.Remove(userId);
|
||||
}
|
||||
|
||||
public virtual int HasUser(int userId)
|
||||
{
|
||||
return users.ContainsKey(userId) ? userId : -1;
|
||||
}
|
||||
|
||||
public virtual Player? GetPlayer(int userId)
|
||||
{
|
||||
users.TryGetValue(userId, out Player? player);
|
||||
return player;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Player> GetPlayers()
|
||||
{
|
||||
return users.Values;
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetConnectUsers()
|
||||
{
|
||||
return users.Keys;
|
||||
}
|
||||
|
||||
public Dictionary<int, Player> GetUsers()
|
||||
{
|
||||
return users;
|
||||
}
|
||||
|
||||
public int UserCount => users.Count;
|
||||
}
|
||||
Reference in New Issue
Block a user