초기커밋

This commit is contained in:
2025-05-01 07:20:41 +09:00
commit 98bb2e3c5c
2747 changed files with 646947 additions and 0 deletions

View File

@@ -0,0 +1,848 @@
using System.Collections.Concurrent;
using Google.Protobuf.WellKnownTypes;
using Nettention.Proud;
using ServerCore;
using ServerBase;
using ServerCommon;
using ServerCommon.BusinessLogDomain;
using USER_GUID = System.String;
namespace GameServer;
public partial class InstanceRoom
{
CancellationTokenSource _cts = new CancellationTokenSource();
ConcurrentDictionary<string, Player> m_players = new();
//object memberLock = new object();
readonly string _roomId;
readonly int _instanceId;
Map _map = new();
EPlaceType _placeType;
ContentsType _contentsType;
bool _isFullMemberStart = false;
bool _concertStart = false;
bool _concertEnd = false;
DateTime _concertStartTime;
DateTime _concertEndTime;
int _concertLength;
int _startBuffID = 0;
int _concertEnterItemId = 0;
int _concertEnterItemCount = 0;
bool _isPartyInstance = false;
string _partyGuid = string.Empty;
int _screenPageNo = 0;
public bool isDestroy { get; private set; }
public int SessionCount { get { return m_players.Count; } }
public int Capacity { get; private set; }
public InstanceRoom(string roomId, int instanceId)
{
_roomId = roomId;
_instanceId = instanceId;
}
public async Task<bool> Init()
{
Log.getLogger().info($"InstanceRoom.Init() Start !!! - instanceRoomId:{_roomId}, InstanceMetaId:{_instanceId}");
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
if (MetaData.Instance._IndunTable.TryGetValue(_instanceId, out var instanceMetaData) == false)
{
Log.getLogger().error($"Failed to MetaData.TryGetValue() !!! : instanceMetaId:{_instanceId} - instanceRoomId:{_roomId}");
return false;
}
if (instanceMetaData.RoomFile.isNullOrWhiteSpace())
{
Log.getLogger().error($"Not Exist InstanceMetaData.RoomFile : instanceMetaId:{_instanceId} - instanceRoomId:{_roomId}");
return false;
}
_contentsType = instanceMetaData.ContentsType;
_placeType = instanceMetaData.placeType();
Capacity = instanceMetaData.LimitCount;
if (_placeType == EPlaceType.MyHome)
{
result = MyhomeHelper.getMyhomeOwnerUserGuidAndMyhomeGuidFromRoomId(_roomId, out var myhomeOwnerUserGuid, out var myhomeGuid);
if (result.isFail())
{
err_msg = $"Failed to getMyhomeOwnerUserGuidAndMyhomeGuidFromRoomId() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return false;
}
(result, var myhome_attrib) = await MyhomeHelper.getEnterMyhomeAttribFromDynamoDb(myhomeOwnerUserGuid, myhomeGuid);
if (result.isFail() || null == myhome_attrib)
{
err_msg = $"Failed to getEnterMyhomeAttribFromDynamoDb() !!! : {result.toBasicString()} - instanceRoomId:{_roomId}";
Log.getLogger().error(err_msg);
return false;
}
(result, var myhome_ugc_info) = await MyhomeHelper.getMyhomeUgcInfo(myhome_attrib);
if (result.isFail() || null == myhome_ugc_info)
{
err_msg = $"Failed to getMyhomeUgcInfo() !!! : {result.toBasicString()} - instanceRoomId:{_roomId}";
Log.getLogger().error(err_msg);
return false;
}
await _map.onInitFromMyhomeUgc(myhome_attrib.MyhomeGuid, myhome_ugc_info, _roomId);
}
else
{
await _map.onInit(instanceMetaData.RoomFile, _roomId, true);
if (true == InstanceRoomHandler.isOnlyOneInstanceRoomID(_roomId))
{
var result_farming = await FarmingHelper.tryFinalyzeUncompletedFarmingByMap(_map);
if (result_farming.isFail())
{
err_msg = $"Fail to tryFinalyzeUncompletedFarmingByMap() !!! : {_map.toBasicString()}, {result_farming.toBasicString()}";
Log.getLogger().error(err_msg);
}
}
}
if (_placeType == EPlaceType.Concert)
{
if (MetaData.Instance._ConcertTable.TryGetValue(instanceMetaData.MapId, out var concertInfo) == false)
{
Log.getLogger().error($"Failed to MetaData.TryGetValue() !!! : concertMetaId:{instanceMetaData.MapId} - instanceRoomId:{_roomId}");
return false;
}
_startBuffID = concertInfo.BuffID;
_concertEnterItemId = concertInfo.DeleteItem;
_concertEnterItemCount = concertInfo.DeleteItemCount;
_concertLength = concertInfo.ConcertLength;
if (concertInfo.StartRequiredType == "InstanceTimeTarget")
{
_isFullMemberStart = true;
_concertStartTime = DateTime.UtcNow.AddSeconds(concertInfo.ITRequiredDetail);
_concertEndTime = _concertStartTime.AddSeconds(_concertLength);
}
else if (concertInfo.StartRequiredType == "RealTimeTarget")
{
_concertStartTime = concertInfo.RTRequiredDetail.UtcDateTime;
_concertEndTime = _concertStartTime.AddSeconds(_concertLength);
}
var roomIdElemnet = _roomId.Split(":");
if (roomIdElemnet[0] == ServerCommon.Constant.PREFIX_PARTY_INSTANCE_ROOM_ID)
{
var party = server_logic.findGlobalEntity<GlobalParty>()?.getParty(roomIdElemnet[1]);
var party_instance_action = party?.getEntityAction<GlobalPartyDetailInstanceAction>();
if (null == party_instance_action || false == party_instance_action.isExist())
{
Log.getLogger().error($"Not Exist partyInstanceAction !!! - instanceRoomId:{_roomId}");
return false;
}
_isPartyInstance = true;
_partyGuid = roomIdElemnet[1];
Capacity = MetaHelper.GameConfigMeta.MaxPartyMembers;
_concertStartTime = party_instance_action.getStartTime().ToDateTime();
_concertEndTime = _concertStartTime.AddSeconds(_concertLength);
}
// instance 정보 추가 등록
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
result = await instance_room_storage.setInstanceRoomExtraInfo(_roomId, _placeType, _concertStartTime);
if (result.isFail())
{
Log.getLogger().error($"Failed to setInstanceRoomExtraInfo() !!! : {result.toBasicString()} - instanceRoomId:{_roomId}");
return false;
}
}
if (_placeType == EPlaceType.Movie)
{
if (MetaData.Instance._ConcertTable.TryGetValue(instanceMetaData.MapId, out var concertInfo) == false)
{
Log.getLogger().error($"Failed to MetaData.TryGetValue() !!! : concertMetaId:{instanceMetaData.MapId} - instanceRoomId:{_roomId}");
return false;
}
_startBuffID = concertInfo.BuffID;
}
//GameMode
(var gamemode_init_result, var gamemode_init_handler) = GameModeHelper.getGameModeInitHandler(this, _placeType);
if (gamemode_init_result.isSuccess())
{
var gamemode_validation_result = gamemode_init_handler.gamedModeInstanceInitValidate();
if (gamemode_validation_result.isFail()) return false;
var init_result = gamemode_init_handler.gamedModeInstanceInit();
if (init_result.isFail())
{
Log.getLogger().error($"battleInstanceInit error roomId : {_roomId}");
return false;
}
}
Log.getLogger().info($"InstanceRoom.Init() Finish !!! - instanceRoomId:{_roomId}, InstanceMetaId:{_instanceId}");
return true;
}
public bool Join(Player player)
{
var user_guid = player.getUserGuid();
Log.getLogger().info($"InstanceRoom.Join() Start !!! - userGuid:{user_guid}, instanceRoomId:{_roomId}, sessionCount:{SessionCount}");
int memberCount = 0;
{
m_players.AddOrUpdate(user_guid, player, (key, oldValue) => player);
memberCount = SessionCount;
}
var location_attribute = player.getEntityAttribute<LocationAttribute>();
NullReferenceCheckHelper.throwIfNull(location_attribute, () => $"location_attribute is null !!! - {player.toBasicString()}");
location_attribute.CurrentIndunLocation.InstanceRoomId = _roomId;
location_attribute.CurrentIndunLocation.InstanceMetaId = _instanceId;
location_attribute.EnterIndunLocation.clear();
var game_zone_action = player.getEntityAction<GameZoneAction>();
NullReferenceCheckHelper.throwIfNull(game_zone_action, () => $"game_zone_action is null !!! - {player.toBasicString()}");
var result = game_zone_action.tryEnterGameZone(_map);
if (result.isFail())
{
return false;
}
if (memberCount == 1)
{
_cts.Dispose();
// TODO: 위치수정 필요
_cts = new CancellationTokenSource();
_ = LogicThread.Factory.StartNew(() => KeepAlive(_cts.Token),
CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
if (_placeType == EPlaceType.Concert)
{
_ = LogicThread.Factory.StartNew(() => CheckConcertTime(_cts.Token),
CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
}
//_ = LogicThread.Factory.StartNew(() => CheckSession(_cts.Token),
// CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
}
//GameMode
(var get_mode_result, var gamemode_join_handler) = GameModeHelper.getGameModeJoinHandler(this, _placeType);
if (get_mode_result.isFail()) return true; //fail 이면 gamemode 인스턴스가 아니므로 그냥 정상 리턴 처리
if (get_mode_result.isSuccess())
{
get_mode_result = gamemode_join_handler.gamedModeInstanceJoinValidate(_placeType);
if (get_mode_result.isSuccess())
{
//successs 면 gamemod join 설정
var join_result = gamemode_join_handler.gamedModeInstanceJoin(player);
if (join_result.isFail())
{
Log.getLogger().error($"gamedModeInstanceJoin error!!! _placeType : {_placeType}, roomId : {_roomId}, player : {player.toBasicString()}");
return false;
}
}
}
Log.getLogger().info($"{player.getUserGuid()} JoinInstanceRoom {_roomId}");
// LogActionType.StageConcertStart 로그 작성은 인스턴룸이 실제로 시작 상태로 전환될 때 작성한다. 추후 LogActionType.StageConcertEnd 도 정의해야 한다.
//List<ILogInvoker> invokers = new List<ILogInvoker>();
//var stage_log_info = StageBusinessLogHelper.toStageLogInfo(_instanceId, _contentsType, _roomId, SessionCount, Capacity);
//invokers.Add(new StageLog(LogActionType.StageConcertStart, stage_log_info));
//BusinessLogger.collectLogs(LogActionType.StageEnter, session._selectedChar.LastPositionInfo.m_last_stage_exit_tran_guid, session._selectedChar, invokers);
//var invokers = new List<ILogInvoker>();
//var log_action = new LogAction(LogActionType.StageEnter, player._selectedChar.LastPositionInfo.m_last_stage_exit_tran_guid);
//var stage_log_info = StageBusinessLogHelper.toStageLogInfo(_instanceId, _contentsType, _roomId, SessionCount, Capacity);
//invokers.Add(new StageLog(stage_log_info));
//BusinessLogger.collectLogs(log_action, player._selectedChar, invokers);
Log.getLogger().info($"InstanceRoom.Join() Finish !!! - userGuid:{user_guid}, instanceRoomId:{_roomId}, sessionCount:{SessionCount}");
return true;
}
public async Task SendJoinSuccess(Player player)
{
// 입장 성공 패킷
{
ClientToGame clientToGame = new ClientToGame();
clientToGame.Response = new ClientToGameRes();
clientToGame.Response.ErrorCode = ServerErrorCode.Success;
clientToGame.Response.JoinInstanceRoomRes = new ClientToGameRes.Types.JoinInstanceRoomRes();
clientToGame.Response.JoinInstanceRoomRes.MeetingRoom = new MeetingRoomInfo();
clientToGame.Response.JoinInstanceRoomRes.MeetingRoom.ScreenPageNo = _screenPageNo;
GameServerApp.getServerLogic().onSendPacket(player, clientToGame);
player.send_S2C_NTF_SET_LOCATION();
}
// 기존 맴버 정보 패킷
{
ClientToGame clientToGame = new ClientToGame();
clientToGame.Message = new ClientToGameMessage();
clientToGame.Message.InstanceRoomMember = new ClientToGameMessage.Types.InstanceRoomMember();
var instanceRoomMembers = m_players.Values.Where(s => s.getUserGuid() != player.getUserGuid()).Select(s => s.getUserGuid());
clientToGame.Message.InstanceRoomMember.MemberGuid.Add(instanceRoomMembers);
GameServerApp.getServerLogic().onSendPacket(player, clientToGame);
}
// 입장 맴버 정보 패킷
{
ClientToGame clientToGame = new ClientToGame();
clientToGame.Message = new ClientToGameMessage();
clientToGame.Message.JoinInstanceRoomMember = new ClientToGameMessage.Types.JoinInstanceRoomMember();
clientToGame.Message.JoinInstanceRoomMember.MemberGuid = player.getUserGuid();
BroadcastExcept(clientToGame, player);
}
// Concert 정보
if (_placeType == EPlaceType.Concert)
{
if (!_concertStart)
{
// 콘서트 정보 패킷
{
ClientToGame clientToGame = new ClientToGame();
clientToGame.Message = new ClientToGameMessage();
clientToGame.Message.ConcertInfo = new ClientToGameMessage.Types.ConcertInfo();
clientToGame.Message.ConcertInfo.StartTime = _concertStartTime.ToTimestamp();
clientToGame.Message.ConcertInfo.RemainingSeat = Capacity - SessionCount;
Broadcast(clientToGame);
}
if (_isFullMemberStart)
{
if (SessionCount == Capacity)
{
_concertStart = true;
_concertStartTime = DateTime.UtcNow.AddSeconds(5);
_concertEndTime = _concertStartTime.AddSeconds(_concertLength);
// 콘서트 시작 패킷
await SendConcertStartPacket();
}
}
}
else // 콘서트 시작 후 입장
{
// 콘서트 시작 패킷
ClientToGame clientToGame = new ClientToGame();
clientToGame.Message = new ClientToGameMessage();
clientToGame.Message.ConcertStart = new ClientToGameMessage.Types.ConcertStart();
clientToGame.Message.ConcertStart.StartTime = _concertStartTime.ToTimestamp();
clientToGame.Message.ConcertStart.EndTime = _concertEndTime.ToTimestamp();
GameServerApp.getServerLogic().onSendPacket(player, clientToGame);
}
}
if (_isPartyInstance)
{
var party = GameServerApp.getServerLogic().findGlobalEntity<GlobalParty>()?.getParty(_partyGuid);
var party_instance_action = party?.getEntityAction<GlobalPartyDetailInstanceAction>();
NullReferenceCheckHelper.throwIfNull(party_instance_action, () => $"GlobalPartyDetailInstanceAction is null !!! - party:{party?.toBasicString()}");
if (true == party_instance_action.isExist())
{
await party_instance_action.changePartyInstance(_concertStartTime.ToTimestamp(),
_concertEndTime.ToTimestamp(), m_players.Count, false, true);
}
}
if (_startBuffID != 0)
{
var buff_action = player.getEntityAction<BuffAction>();
NullReferenceCheckHelper.throwIfNull(buff_action, () => $"buff_action is null !!! - {player.toBasicString()}");
var (result, add_buff_attribute, del_buff_attribute) = buff_action.AddBuffProcess((uint)_startBuffID);
BuffNotifyHelper.send_S2C_NTF_DELETE_BUFF(player, del_buff_attribute);
BuffNotifyHelper.send_S2C_NTF_START_BUFF(player, add_buff_attribute);
}
//GameMode
(var handler_result, var gamemode_join_success_handler) = GameModeHelper.getGameModeJoinSuccessHandler(player, this, _placeType);
if (handler_result.isFail() || gamemode_join_success_handler == null) return;
await gamemode_join_success_handler.joinSuccess();
}
async Task SendConcertStartPacket()
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
foreach (var player in m_players.Values)
{
var fn_transaction_runner = async delegate ()
{
var result = new Result();
ClientToGame clientToGame = new ClientToGame();
clientToGame.Message = new ClientToGameMessage();
clientToGame.Message.ConcertStart = new ClientToGameMessage.Types.ConcertStart();
//List<ILogInvoker> invokers = new List<ILogInvoker>();
//var log_action = new LogAction(LogActionType.StageConcertStart);
if (_concertEnterItemId != 0 && _concertEnterItemCount != 0)
{
var inventory_action = player.getEntityAction<InventoryActionBase>();
NullReferenceCheckHelper.throwIfNull(inventory_action, () => $"inventory_action is null !!! - {player.toBasicString()}");
(result, var deleted_items) = await inventory_action.tryDeleteItemByMetaId((uint)_concertEnterItemId, (ushort)_concertEnterItemCount);
if (result.isFail())
{
return result;
}
foreach (var deleted_item in deleted_items)
{
var deleted_item_attribute = deleted_item.getEntityAttribute<ItemAttributeBase>();
NullReferenceCheckHelper.throwIfNull(deleted_item_attribute, () => $"deleted_item_attribute is null !!! - {player.toBasicString()}");
ItemGuidCount item = new();
item.ItemGuid = deleted_item_attribute.ItemGuid;
item.ItemCount = deleted_item_attribute.ItemStackCount;
clientToGame.Message.ConcertStart.Items.Add(item);
}
//invokers.Add(new BusinessLogInvoker.ItemLog(itemlogData));
}
clientToGame.Message.ConcertStart.StartTime = _concertStartTime.ToTimestamp();
clientToGame.Message.ConcertStart.EndTime = _concertEndTime.ToTimestamp();
await QuestManager.It.QuestCheckWithoutTransaction(player, new QuestConcert(EQuestEventTargetType.CONCERT, EQuestEventNameType.STARTED, _instanceId));
var batch = new QueryBatchEx<QueryRunnerWithDocument>(player, LogActionType.None, server_logic.getDynamoDbClient());
{
batch.addQuery(new DBQWriteToAttributeAllWithTransactionRunner());
batch.addQuery(new QueryFinal());
}
result = await QueryHelper.sendQueryAndBusinessLog(batch);
if (result.isFail())
{
err_msg = $"Failed to sendQueryAndBusinessLog() !!! : {result.toBasicString()} - {player.toBasicString()}";
Log.getLogger().error(err_msg);
return result;
}
GameServerApp.getServerLogic().onSendPacket(player, clientToGame);
//var stage_log_info = StageBusinessLogHelper.toStageLogInfo(_instanceId, ContentsType.Concert, _roomId, SessionCount, Capacity);
//invokers.Add(new StageLog(stage_log_info));
//BusinessLogger.collectLogs(log_action, player._selectedChar, invokers);
return result;
};
result = await player.runTransactionRunnerSafely(TransactionIdType.PrivateContents, "ItemDelete", fn_transaction_runner);
if (result.isFail())
{
err_msg = $"Failed to runTransactionRunnerSafely() !!! : {result.toBasicString()} - {player.toBasicString()}";
Log.getLogger().error(err_msg);
continue;
}
}
}
public async Task Leave(Player player, bool disconnected)
{
var user_guid = player.getUserGuid();
Log.getLogger().info($"InstanceRoom.Leave() Start !!! - userGuid:{user_guid}, instanceRoomId:{_roomId}, sessionCount:{SessionCount}, addConnectedUser:{getMap().getCurrCountAsAddConnectedUser()}");
if (m_players.TryRemove(player.getUserGuid(), out _))
{
await InstanceRoomHandler.leaveInstanceRoom(user_guid, _roomId);
}
var location_attribute = player.getEntityAttribute<LocationAttribute>();
NullReferenceCheckHelper.throwIfNull(location_attribute, () => $"LocationAttribute is null !!! - player:{player.toBasicString()}");
location_attribute.CurrentIndunLocation.clear();
var game_zone_action = player.getEntityAction<GameZoneAction>();
await game_zone_action.tryLeaveGameZone();
if (SessionCount + getMap().getCurrCountAsAddConnectedUser() == 0)
{
if (!_isPartyInstance || _concertEnd)
{
await destroyRoom();
}
}
else
{
{
ClientToGame clientToGame = new ClientToGame();
clientToGame.Message = new ClientToGameMessage();
clientToGame.Message.LeaveInstanceRoomMember = new ClientToGameMessage.Types.LeaveInstanceRoomMember();
clientToGame.Message.LeaveInstanceRoomMember.MemberGuid = player.getUserGuid();
Broadcast(clientToGame);
}
if (_placeType == EPlaceType.Concert)
{
if (!_concertStart)
{
// 콘서트 정보 패킷
{
ClientToGame clientToGame = new ClientToGame();
clientToGame.Message = new ClientToGameMessage();
clientToGame.Message.ConcertInfo = new ClientToGameMessage.Types.ConcertInfo();
clientToGame.Message.ConcertInfo.StartTime = _concertStartTime.ToTimestamp();
clientToGame.Message.ConcertInfo.RemainingSeat = Capacity - SessionCount;
Broadcast(clientToGame);
}
}
}
}
if (_isPartyInstance)
{
var party = GameServerApp.getServerLogic().findGlobalEntity<GlobalParty>()?.getParty(_partyGuid);
var party_instance_action = party?.getEntityAction<GlobalPartyDetailInstanceAction>();
if (true == party_instance_action?.isExist())
{
NullReferenceCheckHelper.throwIfNull(party, () => $"GlobalPartyDetail is null !!! - partyGuid:{_partyGuid}");
var log_invokers = new List<ILogInvoker>(2);
if (SessionCount == 0 && _concertEnd)
{
// party Instance Business Log 기록
var party_instance_log_data = PartyBusinessLogHelper.toPartyInstanceLogData(party.PartyGuid, false);
var party_instance_business_log = new PartyInstanceBusinessLog(party_instance_log_data);
log_invokers.Add(party_instance_business_log);
await party_instance_action.finishPartyInstance();
}
else
{
await party_instance_action.changeJoinMemberCount(m_players.Count, true);
// party Instance Business Log 기록
var party_instance_log_data = PartyBusinessLogHelper.toPartyInstanceLogData(party.PartyGuid, false);
var party_instance_business_log = new PartyInstanceBusinessLog(party_instance_log_data);
log_invokers.Add(party_instance_business_log);
}
// party member Business Log 기록
var party_member_log_data = PartyBusinessLogHelper.toPartyMemberLogData(party.PartyGuid, player.getUserGuid(), PartyMemberActionType.PartyInstanceLeave);
var party_member_business_log = new PartyMemberBusinessLog(party_member_log_data);
log_invokers.Add(party_member_business_log);
BusinessLogger.collectLogs(new LogActionEx(LogActionType.LeavePartyInstance), player, log_invokers);
}
}
Log.getLogger().info($"InstanceRoom.Leave() Finish !!! - userGuid:{user_guid}, instanceRoomId:{_roomId}, sessionCount:{SessionCount}, addConnectedUser:{getMap().getCurrCountAsAddConnectedUser()}");
}
public async Task<ServerErrorCode> ExchangeMyHome(string myhomeGuid, MyHomeInfo myhomeInfo, string roomId)
{
Map new_myhome_map = new();
await new_myhome_map.onInitFromMyhomeUgc(myhomeGuid, myhomeInfo.MyhomeUgcInfo, roomId);
foreach (var player in m_players.Values)
{
var game_zone_action = player.getEntityAction<GameZoneAction>();
NullReferenceCheckHelper.throwIfNull(game_zone_action, () => $"game_zone_action is null !!! - {player.toBasicString()}");
await game_zone_action.tryLeaveGameZone();
MyhomeNotifyHelper.send_S2C_NTF_MYHOME_UGC_INFO(player, myhomeInfo);
game_zone_action.tryEnterGameZone(new_myhome_map);
}
_map = new_myhome_map;
return ServerErrorCode.Success;
}
public ServerErrorCode MyhomeHostEnterEditRoom(string exceptUserGuid)
{
foreach (var player in m_players.Values)
{
if (player.getUserGuid() == exceptUserGuid)
continue;
MyhomeNotifyHelper.send_GS2C_NTF_MYHOME_HOST_ENTER_EDIT_ROOM(player);
}
return ServerErrorCode.Success;
}
public ServerErrorCode ExchangeCraft(string anchorGuid, Timestamp finish_craft_time)
{
foreach (var player in m_players.Values)
{
var game_zone_action = player.getEntityAction<GameZoneAction>();
NullReferenceCheckHelper.throwIfNull(game_zone_action, () => $"game_zone_action is null !!! - {player.toBasicString()}");
var ntf_packet = CraftNotifyHelper.makeNtfCraftUpdatePacket(anchorGuid, finish_craft_time);
GameServerApp.getServerLogic().onSendPacket(player, ntf_packet);
}
return ServerErrorCode.Success;
}
async Task SendConcertEnd()
{
isDestroy = true;
if (_isPartyInstance)
{
var party = GameServerApp.getServerLogic().findGlobalEntity<GlobalParty>()?.getParty(_partyGuid);
var party_instance_action = party?.getEntityAction<GlobalPartyDetailInstanceAction>();
NullReferenceCheckHelper.throwIfNull(party_instance_action, () => $"party instance action is null !!! - party:{party?.toBasicString()}");
if (true == party_instance_action.isExist())
{
await party_instance_action.finishPartyInstance(m_players.Count);
}
}
ClientToGame clientToGame = new ClientToGame();
clientToGame.Message = new ClientToGameMessage();
clientToGame.Message.NtfConcertEnd = new ClientToGameMessage.Types.GS2C_NTF_CONCERT_END();
Broadcast(clientToGame);
}
public int changeScreenPage(bool isCustom, bool isNext, int customPage)
{
if (isCustom)
{
_screenPageNo = customPage;
return _screenPageNo;
}
_screenPageNo = isNext ? _screenPageNo + 1 : _screenPageNo - 1;
if (_screenPageNo <= 0) _screenPageNo = 0;
return _screenPageNo;
}
public void Broadcast(ClientToGame message)
{
HostID[] allClients = m_players.Values.Select(s => s.getHostId()).ToArray();
GameServerApp.getServerLogic().onSendPacketToHosts(allClients, ServerCore.ProudNetHelper.compressRmi(), message);
}
public void BroadcastExcept(ClientToGame message, Player player)
{
HostID[] allClients = m_players.Values.Where(s => s.getUserGuid() != player.getUserGuid()).Select(s => s.getHostId()).ToArray();
GameServerApp.getServerLogic().onSendPacketToHosts(allClients, ServerCore.ProudNetHelper.compressRmi(), message);
}
public void BroadcastExcept(ClientToGame message, USER_GUID userGuid)
{
HostID[] allClients = m_players.Values.Where(s => s.getUserGuid() != userGuid).Select(s => s.getHostId()).ToArray();
GameServerApp.getServerLogic().onSendPacketToHosts(allClients, ServerCore.ProudNetHelper.compressRmi(), message);
}
public async Task KeepAlive(CancellationToken cancelToken)
{
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
while (cancelToken.IsCancellationRequested == false)
{
try
{
await Task.Delay(ServerCommon.Constant.KEEP_INSTANCE_ROOM_TIME, cancelToken);
await instance_room_storage.keepInstanceRoom(_roomId);
if (!InstanceRoomHandler.isOnlyOneInstanceRoomID(_roomId))
{
var instance_room_id_base = InstanceRoomHandler.getInstanceRoomIdBaseFromInstanceRoomId(_roomId);
await instance_room_storage.keepInstanceRoomList(instance_room_id_base);
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Log.getLogger().error($"{ex}");
throw;
}
}
}
public async Task CheckConcertTime(CancellationToken cancelToken)
{
while (cancelToken.IsCancellationRequested == false)
{
try
{
if (_concertStartTime < DateTime.UtcNow && !_concertStart)
{
_concertStart = true;
await SendConcertStartPacket();
}
if (_concertEndTime < DateTime.UtcNow && !_concertEnd)
{
_concertEnd = true;
await SendConcertEnd();
}
if (_concertEnd && SessionCount == 0)
{
await destroyRoom();
InstanceRoomManager.Instance.DestroyRoom(_roomId);
}
await Task.Delay(1000, cancelToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Log.getLogger().error($"{ex}");
throw;
}
}
}
public Map getMap()
{
return _map;
}
public int getInstanceId()
{
return _instanceId;
}
public async Task destroyRoom()
{
isDestroy = true;
_cts.Cancel();
_map.DestroyP2PGroup();
_map.getCancellationToken().Cancel();
await InstanceRoomHandler.deleteInstanceRoom(_roomId);
Log.getLogger().info($"InstanceRoom.destroyRoom() Complete !!! - instanceRoomId:{_roomId}");
}
//public async Task CheckSession(CancellationToken cancelToken)
//{
// List<Player> errorPlayer = new();
// while (cancelToken.IsCancellationRequested == false || SessionCount == 0)
// {
// try
// {
// foreach (var (key, player) in m_players)
// {
// if (player.IsClosedProcessed)
// {
// Log.getLogger().error($"{player.getUserGuid()} Closed Session in InstanceRoom");
// errorPlayer.Add(player);
// continue;
// }
// }
// foreach (var player in errorPlayer)
// {
// if (!await InstanceRoomManager.Instance.LeaveRoom(player, _roomId))
// await Leave(player);
// }
// errorPlayer.Clear();
// await Task.Delay(1000, cancelToken);
// }
// catch (OperationCanceledException)
// {
// break;
// }
// catch (Exception ex)
// {
// Log.getLogger().error($"{ex}");
// throw;
// }
// }
//}
}

View File

@@ -0,0 +1,996 @@
using Amazon.DynamoDBv2.DocumentModel;
using GameServer;
using GameServer.Contents.Room;
using Google.Protobuf.WellKnownTypes;
using MetaAssets;
using ServerCommon;
using ServerCore; using ServerBase;
namespace GameServer
{
internal class InstanceRoomHandler
{
public static async Task<(Result, string)> joinInstance(string userGuid, int instanceMetaId, string? reJoinRoomId = null)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
if (!MetaData.Instance._IndunTable.TryGetValue(instanceMetaId, out var instance_meta_data))
{
err_msg = $"Failed to MetaData.TryGetValue() !!! : instanceMetaId:{instanceMetaId} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
if (!string.IsNullOrEmpty(reJoinRoomId))
{
var roomInfo = await instance_room_storage.GetInstanceRoomInfo(reJoinRoomId);
if (roomInfo == null)
{
await deleteInstanceRoom(reJoinRoomId);
}
else
{
var rejoin_instance_room_id_base = getInstanceRoomIdBaseFromInstanceRoomId(reJoinRoomId);
result = await joinInstanceRoomFromRedis(userGuid, reJoinRoomId, instanceMetaId, rejoin_instance_room_id_base, roomInfo.UgcNpcCount);
if (result.isSuccess())
return (result, reJoinRoomId);
}
}
var instance_room_id = string.Empty;
if (instance_meta_data.OverLimit == 1)
{
(result, instance_room_id) = await joinInstanceRoom(userGuid, instanceMetaId);
if (result.isFail())
{
err_msg = $"Failed to joinInstanceRoom() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
}
else
{
(result, instance_room_id) = await joinOnlyOneInstanceRoom(userGuid, instanceMetaId);
if (instance_room_id == string.Empty)
{
err_msg = $"Failed to joinOnlyOneInstanceRoom() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
}
return (result, instance_room_id);
}
static async Task<(Result, string)> joinInstanceRoom(string userGuid, int instanceMetaId)
{
var result = new Result();
string err_msg;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
if (!MetaData.Instance._IndunTable.TryGetValue(instanceMetaId, out var instance_meta_data))
{
err_msg = $"Failed to MetaData.TryGetValue() !!! : instanceMetaId:{instanceMetaId} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
if (instance_meta_data.OverLimit != 1)
{
err_msg = $"InstanceMetaData.OverLimit != 1 !!! : instanceMetaId:{instanceMetaId} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.InstanceMetaDataOverLimitWrong, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
var instance_room_id_base = makeInstanceRoomIdBase(instanceMetaId);
var instance_room_id = string.Empty;
var room_list = await instance_room_storage.GetInstanceRoomList(instance_room_id_base, instance_meta_data.LimitCount);
foreach (var room_id in room_list)
{
var instance_room_Info = await instance_room_storage.GetInstanceRoomInfo(room_id);
if (instance_room_Info == null)
{
await deleteInstanceRoom(room_id);
continue;
}
// 콘서트 입장 조건 체크
if (instance_room_Info.InstancePlaceType == EPlaceType.Concert)
{
// 콘서트 시작시간 Delta 전 까지만 입장 가능
if (DateTime.UtcNow.AddSeconds(MetaHelper.GameConfigMeta.ConcertEntranceLimitTime).ToTimestamp() >= instance_room_Info.InstanceStartTime)
continue;
}
result = await joinInstanceRoomFromRedis(userGuid, room_id, instanceMetaId, instance_room_id_base, instance_room_Info.UgcNpcCount);
if (result.isSuccess())
{
instance_room_id = room_id;
break;
}
}
if (instance_room_id == string.Empty)
{
(result, instance_room_id) = await createInstanceRoomFromRedis(instance_room_id_base, instanceMetaId);
if (result.isFail())
{
err_msg = $"Failed to createInstanceRoomFromRedis() !!! : {result.toBasicString()} - userGuid:{userGuid}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
result = await joinInstanceRoomFromRedis(userGuid, instance_room_id, instanceMetaId, instance_room_id_base, 0);
if (result.isFail())
{
err_msg = $"Failed to joinInstanceRoomFromRedis() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
}
return (result, instance_room_id);
}
/// <summary>
/// 방이 하나만 존재하는 인스턴스 룸 생성 함수 <br/>
/// <see cref="MetaAssets.InstanceMetaData.OverLimit"/> 값이 0 인 인스턴스의 룸 생성시 사용
/// </summary>
/// <param name="instanceMetaId"></param>
/// <param name="userGuid"></param>
/// <returns></returns>
static async Task<(Result, string)> joinOnlyOneInstanceRoom(string userGuid, int instanceMetaId)
{
var result = new Result();
string err_msg;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
if (!MetaData.Instance._IndunTable.TryGetValue(instanceMetaId, out var instance_meta_data))
{
err_msg = $"Failed to MetaData.TryGetValue() !!! : instanceMetaId:{instanceMetaId} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
if (instance_meta_data.OverLimit != 0)
{
err_msg = $"InstanceMetaData.OverLimit != 0 !!! : instanceMetaId:{instanceMetaId} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.InstanceMetaDataOverLimitWrong, err_msg);
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
var instance_room_id_base = makeInstanceRoomIdBase(instanceMetaId);
var instance_room_id = makeOnlyOneInstanceRoomId(instance_room_id_base);
var room_info = await instance_room_storage.GetInstanceRoomInfo(instance_room_id);
var room_ugc_npc_count = room_info?.UgcNpcCount ?? 0;
if (room_info == null)
{
(result, instance_room_id) = await createInstanceRoomFromRedis(instance_room_id_base, instanceMetaId);
if (result.isFail())
{
err_msg = $"Failed to createInstanceRoomFromRedis() !!! : {result.toBasicString()} - userGuid:{userGuid}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
}
result = await joinInstanceRoomFromRedis(userGuid, instance_room_id, instanceMetaId, instance_room_id_base, room_ugc_npc_count);
if (result.isFail())
{
err_msg = $"Failed to joinInstanceRoomFromRedis() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
return (result, instance_room_id);
}
public static async Task<(Result, string)> joinMyhomeInstance(string userGuid, string myhomeOwnerUserGuid, string myhomeGuid, int myhomeInstanceMetaId, int enterPlayerCount = 0)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
var instance_meta_id = myhomeInstanceMetaId;
if (!MetaData.Instance._IndunTable.TryGetValue(instance_meta_id, out var instance_meta_data))
{
err_msg = $"Failed to MetaData.TryGetValue() !!! : instanceMetaId:{instance_meta_id} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
if (enterPlayerCount < 1)
enterPlayerCount = instance_meta_data.LimitCount;
var instance_room_id_base = makeMyhomeInstanceRoomIdBase(myhomeOwnerUserGuid, myhomeGuid);
var instance_room_id = string.Empty;
var room_list = await instance_room_storage.GetInstanceRoomList(instance_room_id_base, enterPlayerCount);
foreach (var room_id in room_list)
{
var instance_room_Info = await instance_room_storage.GetInstanceRoomInfo(room_id);
if (instance_room_Info == null)
{
await deleteInstanceRoom(room_id);
continue;
}
result = await joinInstanceRoomFromRedis(userGuid, room_id, instance_meta_id, instance_room_id_base, instance_room_Info.UgcNpcCount);
if (result.isSuccess())
{
instance_room_id = room_id;
break;
}
}
if (instance_room_id == string.Empty)
{
(result, instance_room_id) = await createInstanceRoomFromRedis(instance_room_id_base, instance_meta_id);
if (result.isFail())
{
err_msg = $"Failed to createInstanceRoomFromRedis() !!! : {result.toBasicString()} - userGuid:{userGuid}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
result = await joinInstanceRoomFromRedis(userGuid, instance_room_id, instance_meta_id, instance_room_id_base, 0);
if (result.isFail())
{
err_msg = $"Failed to joinInstanceRoomFromRedis() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
}
return (result, instance_room_id);
}
public static async Task<(Result, string)> JoinOnlyOneMyhomeInstance(string userGuid, string myhomeOwnerUserGuid, string myhomeGuid, int myhomeInstanceMetaId)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
var instance_meta_id = myhomeInstanceMetaId;
if (!MetaData.Instance._IndunTable.TryGetValue(instance_meta_id, out var instance_meta_data))
{
err_msg = $"Failed to MetaData.TryGetValue() !!! : instanceMetaId:{instance_meta_id} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
var instance_room_id_base = makeMyhomeInstanceRoomIdBase(myhomeOwnerUserGuid, myhomeGuid);
var instance_room_id = makeOnlyOneInstanceRoomId(instance_room_id_base);
var room_info = await instance_room_storage.GetInstanceRoomInfo(instance_room_id);
var room_ugc_npc_count = room_info?.UgcNpcCount ?? 0;
if (room_info == null)
{
(result, instance_room_id) = await createInstanceRoomFromRedis(instance_room_id_base, instance_meta_id);
if (result.isFail())
{
err_msg = $"Failed to createInstanceRoomFromRedis() !!! : {result.toBasicString()} - userGuid:{userGuid}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
}
result = await joinInstanceRoomFromRedis(userGuid, instance_room_id, instance_meta_id, instance_room_id_base, room_ugc_npc_count);
if (result.isFail())
{
err_msg = $"Failed to joinInstanceRoomFromRedis() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
return (result, instance_room_id);
}
public static async Task<(Result, string)> joinDressRoomInstance(string userGuid)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
var instance_meta_id = ServerCommon.Constant.DRESS_ROOM_INSTANCE_META_ID;
if (!MetaData.Instance._IndunTable.TryGetValue(instance_meta_id, out var instance_meta_data))
{
err_msg = $"Failed to TryGetValue() !!! : instanceMetaId:{instance_meta_id}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
var instance_room_id_base = makeDressRoomInstanceRoomIdBase(userGuid);
var instance_room_id = makeOnlyOneInstanceRoomId(instance_room_id_base);
var room_info = await instance_room_storage.GetInstanceRoomInfo(instance_room_id);
var room_ugc_npc_count = room_info?.UgcNpcCount ?? 0;
if (room_info == null)
{
(result, instance_room_id) = await createInstanceRoomFromRedis(instance_room_id_base, instance_meta_id);
if (result.isFail())
{
err_msg = $"Failed to createInstance() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
}
result = await joinInstanceRoomFromRedis(userGuid, instance_room_id, instance_meta_id, instance_room_id_base, room_ugc_npc_count);
if (result.isFail())
{
err_msg = $"Failed to joinInstanceRoomFromRedis() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
return (result, instance_room_id);
}
public static async Task<(Result, string)> joinEditRoomInstance(string userGuid)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
var instanceMetaId = ServerCommon.Constant.EDIT_ROOM_INSTANCE_META_ID;
if (!MetaData.Instance._IndunTable.TryGetValue(instanceMetaId, out var instance_meta_data))
{
err_msg = $"Failed to MetaData.TryGetValue() !!! : instanceMetaId:{instanceMetaId} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
var instance_room_id_base = makeEditRoomInstanceRoomIdBase(userGuid);
var instance_room_id = makeOnlyOneInstanceRoomId(instance_room_id_base);
var room_info = await instance_room_storage.GetInstanceRoomInfo(instance_room_id);
var room_ugc_npc_count = room_info?.UgcNpcCount ?? 0;
if (room_info == null)
{
(result, instance_room_id) = await createInstanceRoomFromRedis(instance_room_id_base, instanceMetaId);
if (result.isFail())
{
err_msg = $"Failed to createInstanceRoomFromRedis() !!! : {result.toBasicString()} - userGuid:{userGuid}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
}
result = await joinInstanceRoomFromRedis(userGuid, instance_room_id, instanceMetaId, instance_room_id_base, room_ugc_npc_count);
if (result.isFail())
{
err_msg = $"Failed to joinInstanceRoomFromRedis() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
return (result, instance_room_id);
}
public static async Task<Result> joinTargetInstanceRoom(string userGuid, string instanceRoomId, int instanceMetaId, int enterPlayerCount = 0)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
if (!MetaData.Instance._IndunTable.TryGetValue(instanceMetaId, out var instance_meta_data))
{
err_msg = $"Failed to MetaData.TryGetValue() !!! : instanceMetaId:{instanceMetaId} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var instance_room_id_base = getInstanceRoomIdBaseFromInstanceRoomId(instanceRoomId);
var instance_room_Info = await instance_room_storage.GetInstanceRoomInfo(instanceRoomId);
if (instance_room_Info == null)
{
await deleteInstanceRoom(instanceRoomId);
err_msg = $"Not Found InstanceRoomInfo !!! : instanceRoomId:{instanceRoomId} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.NotExistRoomInfoForEnter, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
result = await joinInstanceRoomFromRedis(userGuid, instanceRoomId, instanceMetaId, instance_room_id_base, instance_room_Info.UgcNpcCount, enterPlayerCount);
if (result.isFail())
{
err_msg = $"Failed to joinInstanceRoomFromRedis() !!! : {result.toBasicString()} - userGuid:{userGuid}";
Log.getLogger().error(err_msg);
return result;
}
return result;
}
public static async Task<string> CreatePartyInstance(string partyGuid, int instanceMetaId)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
if (!MetaData.Instance._IndunTable.TryGetValue(instanceMetaId, out var instance_meta_data))
{
err_msg = $"Failed to TryGetValue() !!! : instanceMetaId:{instanceMetaId}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return string.Empty;
}
(result, var instance_server_info) = await getBestInstanceServerForCreateInstance(MetaHelper.GameConfigMeta.MaxPartyMembers);
if (result.isFail() || instance_server_info == null)
{
err_msg = $"Failed to getBestInstanceServerForCreateInstance() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return string.Empty;
}
if (instance_server_info.Address == string.Empty || instance_server_info.Port == 0)
{
err_msg = $"ServerInfo is invalid !!! Address:{instance_server_info.Address}, Port:{instance_server_info.Port}";
result.setFail(ServerErrorCode.ValidServerNotFound, err_msg);
Log.getLogger().error(err_msg);
return string.Empty;
}
var instance_room_id_base = makePartyInstanceRoomIdBase(partyGuid);
var instance_room_id = makeOnlyOneInstanceRoomId(instance_room_id_base);
(result, _) = await instance_room_storage.createInstanceRoom(instance_room_id, instance_server_info.Address, instance_server_info.Port, instanceMetaId);
if (result.isFail())
{
err_msg = $"Failed to createInstanceRoom() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return string.Empty;
}
return instance_room_id;
}
public static async Task<string> JoinPartyInstance(string userGuid, string instanceRoomId)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
var roomInfo = await instance_room_storage.GetInstanceRoomInfo(instanceRoomId);
if (roomInfo == null)
{
await deleteInstanceRoom(instanceRoomId);
err_msg = $"Not exist instanceroom:{instanceRoomId}";
Log.getLogger().error(err_msg);
return string.Empty;
}
result = await instance_room_storage.joinInstanceRoom(userGuid, instanceRoomId, MetaHelper.GameConfigMeta.MaxPartyMembers);
if (result.isFail())
{
err_msg = $"Failed to TryJoinInstanceRoom() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return string.Empty;
}
return instanceRoomId;
}
public static async Task<Result> leaveInstanceRoom(string userGuid, string instanceRoomId)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
await instance_room_storage.leaveInstanceRoom(userGuid, instanceRoomId);
if (!isOnlyOneInstanceRoomID(instanceRoomId))
{
var instance_room_id_base = getInstanceRoomIdBaseFromInstanceRoomId(instanceRoomId);
await instance_room_storage.decreaseInstanceRoomScore(instance_room_id_base, instanceRoomId);
}
return result;
}
public static async Task<Result> deleteInstanceRoom(string instanceRoomId)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
await instance_room_storage.deleteInstanceRoom(instanceRoomId);
if (!isOnlyOneInstanceRoomID(instanceRoomId))
{
var instance_room_id_base = getInstanceRoomIdBaseFromInstanceRoomId(instanceRoomId);
await instance_room_storage.removeInstanceRoomList(instance_room_id_base, instanceRoomId);
}
return result;
}
public static async Task<int> getInstanceUserCount(int instanceMetaId, string roomIdBase)
{
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
if (!MetaData.Instance._IndunTable.TryGetValue(instanceMetaId, out var instance_meta_data))
{
err_msg = $"Failed to TryGetValue() !!! : instanceMetaId:{instanceMetaId}";
Log.getLogger().error(err_msg);
return 0;
}
int user_count = 0;
if (instance_meta_data.OverLimit == 1)
{
user_count = await instance_room_storage.getInstanceRoomListTotalUserCount(roomIdBase);
}
else
{
var instance_room_id = makeOnlyOneInstanceRoomId(roomIdBase);
var instance_room = await instance_room_storage.GetInstanceRoomInfo(instance_room_id);
if (instance_room == null) return 0;
user_count = await instance_room_storage.getInstanceRoomUserCount(instance_room_id);
user_count += instance_room.UgcNpcCount;
}
return user_count;
}
public static string makeInstanceRoomIdBase(int instanceMetaId)
{
return $"{ServerCommon.Constant.PREFIX_INSTANCE_ROOM_ID}:{instanceMetaId}";
}
public static string makeMyhomeInstanceRoomIdBase(string userGuid, string myhomeGuid)
{
return $"{ServerCommon.Constant.PREFIX_MYHOME_INSTANCE_ROOM_ID}:{userGuid}:{myhomeGuid}";
}
static string makeDressRoomInstanceRoomIdBase(string userGuid)
{
return $"{ServerCommon.Constant.PREFIX_DRESS_ROOM_INSTANCE_ROOM_ID}:{userGuid}";
}
public static string makeEditRoomInstanceRoomIdBase(string userGuid)
{
return $"{ServerCommon.Constant.PREFIX_EDIT_ROOM_INSTANCE_ROOM_ID}:{userGuid}";
}
static string makePartyInstanceRoomIdBase(string partyGuid)
{
return $"{ServerCommon.Constant.PREFIX_PARTY_INSTANCE_ROOM_ID}:{partyGuid}";
}
public static async Task<string> makeInstanceRoomId(string instanceRoomIdBase, int overLimit)
{
if (overLimit == 1)
{
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
var room_id_seq = await instance_room_storage.getRoomIdSeq();
if (room_id_seq == 0)
return string.Empty;
return $"{instanceRoomIdBase}:{room_id_seq}";
}
else
{
return makeOnlyOneInstanceRoomId(instanceRoomIdBase);
}
}
static string makeOnlyOneInstanceRoomId(string instanceRoomIdBase)
{
return $"{instanceRoomIdBase}:{ServerCommon.Constant.POSTFIX_ONLY_ONE_INSTANCE_ROOM_ID}";
}
public static string getInstanceRoomIdBaseFromInstanceRoomId(string instanceRoomId)
{
var instance_room_Id_element = instanceRoomId.Split(":");
if (instance_room_Id_element.Length == ServerCommon.Constant.INSTANCE_ROOM_ID_ELEMENT_COUNT)
{
return $"{instance_room_Id_element[0]}:{instance_room_Id_element[1]}";
}
if (instance_room_Id_element.Length == ServerCommon.Constant.MYHOME_INSTANCE_ROOM_ID_ELEMENT_COUNT)
{
return $"{instance_room_Id_element[0]}:{instance_room_Id_element[1]}:{instance_room_Id_element[2]}";
}
return string.Empty;
}
public static bool isOnlyOneInstanceRoomID(string instanceRoomId)
{
var room_id_element = instanceRoomId.Split(":");
if (room_id_element.Length != ServerCommon.Constant.INSTANCE_ROOM_ID_ELEMENT_COUNT && room_id_element.Length != ServerCommon.Constant.MYHOME_INSTANCE_ROOM_ID_ELEMENT_COUNT)
return false;
if (room_id_element[room_id_element.Length - 1] == ServerCommon.Constant.POSTFIX_ONLY_ONE_INSTANCE_ROOM_ID)
return true;
return false;
}
static async Task<(Result, string)> createInstanceRoomFromRedis(string instanceRoomIdBase, int instanceMetaId)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
if (!MetaData.Instance._IndunTable.TryGetValue(instanceMetaId, out var instance_meta_data))
{
err_msg = $"Failed to MetaData.TryGetValue() !!! : instanceMetaId:{instanceMetaId}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
(result, var instance_server_info) = await getBestInstanceServerForCreateInstance(instance_meta_data.LimitCount);
if (result.isFail() || instance_server_info == null)
{
err_msg = $"Failed to getBestInstanceServerForCreateInstance() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
if (instance_server_info.Address == string.Empty || instance_server_info.Port == 0)
{
err_msg = $"ServerInfo is invalid !!! Address:{instance_server_info.Address}, Port:{instance_server_info.Port}";
result.setFail(ServerErrorCode.ValidServerNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
var instance_room_id = await makeInstanceRoomId(instanceRoomIdBase, instance_meta_data.OverLimit);
if (instance_room_id == string.Empty)
{
err_msg = $"Failed to makeInstanceRoomId() !!!";
result.setFail(ServerErrorCode.ValidServerNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, string.Empty);
}
(result, var is_created) = await instance_room_storage.createInstanceRoom(instance_room_id, instance_server_info.Address, instance_server_info.Port, instanceMetaId);
if (result.isFail())
{
err_msg = $"Failed to createInstanceRoom() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
if (instance_meta_data.OverLimit == 1 && is_created)
{
result = await instance_room_storage.addInstanceRoomList(instanceRoomIdBase, instance_room_id);
if (result.isFail())
{
err_msg = $"Failed to addInstanceRoomList() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return (result, string.Empty);
}
}
return (result, instance_room_id);
}
public static async Task<(Result, ServerInfo?)> getBestInstanceServerForCreateInstance(int roomCapacity)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
(result, var server_info_list) = await server_logic.getServerInfosByServerType(ServerType.Indun);
if (server_info_list.Count == 0)
{
err_msg = $"Not Exist Indun Server !!!";
result.setFail(ServerErrorCode.ValidServerNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, null);
}
int selected_server_index = LoadBalanceServerHelper.getBestInstanceServerIndexForCreate(server_info_list, roomCapacity);
if (selected_server_index == -1)
{
foreach (var server_info in server_info_list)
{
Log.getLogger().info($"IndunServer Capacity Info !!! : serverName:{server_info.Name}, serverCapacity:{server_info.Capacity}, serverRoomCapacity:{server_info.RoomCapacity}");
}
err_msg = $"Not Found Enough Capacity Indun Server !!! : tryCreateRoomCapacity:{roomCapacity}";
result.setFail(ServerErrorCode.ValidServerNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, null);
}
return (result, server_info_list[selected_server_index]);
}
public static async Task<Result> joinInstanceRoomFromRedis(string userGuid, string instanceRoomId, int instanceMetaId, string instanceRoomIdBase, int ugcNpcCount, int enterPlayerCount = 0)
{
var result = new Result();
var err_msg = string.Empty;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
if (!MetaData.Instance._IndunTable.TryGetValue(instanceMetaId, out var instance_meta_data))
{
err_msg = $"Failed to MetaData.TryGetValue() !!! : instanceMetaId:{instanceMetaId} - userGuid:{userGuid}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (enterPlayerCount < 1)
enterPlayerCount = instance_meta_data.LimitCount;
result = await instance_room_storage.joinInstanceRoom(userGuid, instanceRoomId, enterPlayerCount - ugcNpcCount);
if (result.isFail())
{
err_msg = $"Failed to TryJoinInstanceRoom() !!! : {result.toBasicString()}";
Log.getLogger().debug(err_msg);
return result;
}
if (instance_meta_data.OverLimit == 1)
{
result = await instance_room_storage.increaseInstanceRoomScore(instanceRoomIdBase, instanceRoomId);
if (result.isFail())
{
await instance_room_storage.leaveInstanceRoom(userGuid, instanceRoomId);
err_msg = $"Failed to increaseInstanceRoomScore() !!! : {result.toBasicString()} - userGuid:{userGuid}";
Log.getLogger().debug(err_msg);
return result;
}
}
return result;
}
public static async Task<Result> increaseInstanceRoomScoreForUgcNpc(string instanceRoomIdBase, string instanceRoomId)
{
var result = new Result();
string err_msg;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
var instance_room = await instance_room_storage.GetInstanceRoomInfo(instanceRoomId);
if (instance_room == null)
{
err_msg = $"Fail to increase instance room score for ugc npc: instance room id - {instanceRoomId}";
result.setFail(ServerErrorCode.InstanceRoomException, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
result = await instance_room_storage.increaseInstanceRoomUgcNpcScore(instanceRoomId);
if (!MetaData.Instance._IndunTable.TryGetValue(instance_room.InstanceId, out var instance_meta_data))
{
err_msg = $"Failed to TryGetValue() !!! : instanceMetaId:{instance_room.InstanceId}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (instance_meta_data.OverLimit == 1)
{
result = await instance_room_storage.increaseInstanceRoomScore(instanceRoomIdBase, instanceRoomId);
}
return result;
}
public static async Task<Result> decreaseInstanceRoomScoreForUgcNpc(string instanceRoomIdBase, string instanceRoomId)
{
var result = new Result();
string err_msg;
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
var instance_room = await instance_room_storage.GetInstanceRoomInfo(instanceRoomId);
if (instance_room == null)
{
err_msg = $"Fail to increase instance room score for ugc npc: instance room id - {instanceRoomId}";
result.setFail(ServerErrorCode.InstanceRoomException, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
result = await instance_room_storage.decreaseInstanceRoomUgcNpcScore(instanceRoomId);
if (!MetaData.Instance._IndunTable.TryGetValue(instance_room.InstanceId, out var instance_meta_data))
{
err_msg = $"Failed to TryGetValue() !!! : instanceMetaId:{instance_room.InstanceId}";
result.setFail(ServerErrorCode.InstanceMetaDataNotFound, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (instance_meta_data.OverLimit == 1)
{
result = await instance_room_storage.decreaseInstanceRoomScore(instanceRoomIdBase, instanceRoomId);
}
return result;
}
public static async Task<(Result, List<InstanceRoomInfo>)> tryGetMyhomeInstanceRoomInfos(string myhomeOwnerUserGuid, string myhomeGuid)
{
var result = new Result();
var err_msg = string.Empty;
var myhome_Instance_room_infos = new List<InstanceRoomInfo>();
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
var instance_room_id_base = makeMyhomeInstanceRoomIdBase(myhomeOwnerUserGuid, myhomeGuid);
var room_list = await instance_room_storage.GetInstanceRoomList(instance_room_id_base, int.MaxValue);
foreach (var room_id in room_list)
{
var instance_room_Info = await instance_room_storage.GetInstanceRoomInfo(room_id);
if (instance_room_Info == null)
{
await deleteInstanceRoom(room_id);
continue;
}
myhome_Instance_room_infos.Add(instance_room_Info);
}
return (result, myhome_Instance_room_infos);
}
}
}

View File

@@ -0,0 +1,270 @@

using System.Collections.Concurrent;
using Google.Protobuf.WellKnownTypes;
using ServerCore;
using ServerBase;
using ServerCommon;
using ServerCommon.BusinessLogDomain;
using MetaAssets;
namespace GameServer;
public class InstanceRoomManager
{
private static readonly InstanceRoomManager _instance = new InstanceRoomManager();
static InstanceRoomManager() { }
private InstanceRoomManager() { }
public static InstanceRoomManager Instance { get { return _instance; } }
ConcurrentDictionary<string, InstanceRoom> m_instance_rooms = new();
/// <summary>
/// 현재 생성된 모든 인스턴스룸들의 최대 인원수의 합
/// </summary>
public int Capacity { get { return _capacity; } }
int _capacity = 0;
/// <summary>
/// 인스턴스룸 입장<br/>
/// 입장 성공 후 <see cref="SendJoinRoomSuccess"/> 사용하여 클라로 패킷 전송
/// </summary>
/// <param name="player">인스턴스룸에 입장할 세션</param>
/// <param name="roomId">입장할 인스턴스룸 아이디</param>
/// <param name="instanceId">입장할 인스턴스룸의 인스턴스 아이디, 없는 경우 0</param>
/// <param name="joinedRoom">입장 성공 시 입장한 방, 실패 시 <see langword="null"/></param>
/// <returns><see langword="true"/> 입장 성공, <see langword="false"/> 입장 실패</returns>
public async Task<InstanceRoom?> JoinRoom(Player player, string roomId, int instanceId)
{
if (m_instance_rooms.TryGetValue(roomId, out var room) == false)
{
room = new InstanceRoom(roomId, instanceId);
if (false == await room.Init())
{
Log.getLogger().error($"Failed to InstanceRoom.init() !!! - instanceRoomId:{roomId}, {player.toBasicString()}");
return null;
}
if (false == m_instance_rooms.TryAdd(roomId, room))
{
if (!m_instance_rooms.TryGetValue(roomId, out room))
{
Log.getLogger().error($"Failed to Add & Get InstanceRoom !!! - instanceRoomId:{roomId}, {player.toBasicString()}");
return null;
}
}
else
{
Interlocked.Add(ref _capacity, room.Capacity);
NamedPipeMonitor.SetCurrentCapacity(_capacity);
ServerBase.Monitor.It.incRoomCounter();
}
}
if (room.Join(player) == false)
{
Log.getLogger().error($"Failed to InstanceRoom.Join() !!! - instanceRoomId:{roomId}, {player.toBasicString()}");
return null;
}
return room;
}
/// <summary>
/// 인스턴스룸 입장 관련 패킷 보내기<br/>
/// <see cref="JoinRoom"/> 성공 후 사용해야 함
/// </summary>
/// <param name="player">인스턴스룸에 입장 성공한 세션</param>
/// <param name="roomId">입장 성공한 인스턴스룸 아이디</param>
/// <returns>방이 존재하지 않을 경우 <see langword="false"/>, 그 외 <see langword="true"/></returns>
public async Task<bool> SendJoinRoomSuccess(Player player, string roomId)
{
if (!m_instance_rooms.TryGetValue(roomId, out var room))
{
Log.getLogger().error($"Failed to Get InstanceRoom !!! - instanceRoomId:{roomId}");
return false;
}
await room.SendJoinSuccess(player);
return true;
}
public async Task<bool> LeaveRoom(Player player, string roomId, bool disconnected = false)
{
if (m_instance_rooms.TryGetValue(roomId, out var room) == false)
{
Log.getLogger().error($"Failed to Get InstanceRoom !!! - instanceRoomId:{roomId}");
return false;
}
await room.Leave(player, disconnected);
if (room.isDestroy)
{
DestroyRoom(roomId);
}
return true;
}
public void DestroyRoom(string roomId)
{
if (!m_instance_rooms.TryRemove(roomId, out var room))
{
if (m_instance_rooms.ContainsKey(roomId))
{
Log.getLogger().fatal("Failed to Remove InstanceRoom !!! - instanceRoomId:{roomId}");
return;
}
Log.getLogger().info($"InstanceRoomManager.DestroyRoom() Not Exist !!! - instanceRoomId:{roomId}");
return;
}
Interlocked.Add(ref _capacity, -room.Capacity);
NamedPipeMonitor.SetCurrentCapacity(_capacity);
ServerBase.Monitor.It.decRoomCounter();
Log.getLogger().info($"InstanceRoomManager.DestroyRoom() Complete !!! - instanceRoomId:{roomId}");
}
public int getRoomCapacity(string roomId)
{
if (!m_instance_rooms.TryGetValue(roomId, out var room))
{
Log.getLogger().error($"InstanceRoomManager.getRoomCapacity() Not Exist !!! - instanceRoomId:{roomId}");
return 0;
}
return room.Capacity;
}
/*public async Task KickMyHomeNotiToFriendCauseHostLeave(ClientSession mySession, string instanceRoomId)
{
//내방이 아니면 리턴
if (!instanceRoomId.Contains(mySession.Id)) return;
Log.getLogger().debug($"KickMyHomeNotiToFriendCauseHostLeave Called Id : {mySession.Id}");
//실제 인던에 멤버로 있는 id리스트 조회 자기는 제외
var server_logic = GameServerApp.getServerLogic();
var instance_room_storage = new InstanceRoomStorage();
instance_room_storage.Init(server_logic.getRedisDb(), "");
List<string> roomMemberList = await instance_room_storage.GetInstanceRoomMemberList(instanceRoomId);
List<string> memberNotiList = new();
foreach (string id in roomMemberList)
{
if (id.Equals(mySession.Id)) continue;
memberNotiList.Add(id);
}
StringBuilder stringBuilder = new StringBuilder();
foreach (string memberId in memberNotiList)
{
FriendErrorMember errorMember = new();
errorMember.ErrorCode = ServerErrorCode.Success;
ClientSession? friendSession = ClientSessionManager.Instance.GetSessionByName(memberId);
if (friendSession == null)
{
continue;
}
ClientToGame clientToGameNoti = new ClientToGame();
clientToGameNoti.Message = new ClientToGameMessage();
clientToGameNoti.Message.NtfHostfriendLeavedHome = new();
friendSession.Send(RmiContext.ReliableSend, clientToGameNoti);
stringBuilder.Append(memberId).Append(":");
}
Log.getLogger().debug($"NotiToFriendCauseHostLeave Send friendsId : {stringBuilder.ToString()}");
}*/
public async Task<ServerErrorCode> ExchangeMyHome(string roomId, string myhomeGuid, MyHomeInfo myhomeInfo)
{
if (m_instance_rooms.TryGetValue(roomId, out var room) == false)
{
Log.getLogger().error($"not exist instance room : {roomId}");
return ServerErrorCode.NotExistInstanceRoom;
}
var error_code = await room.ExchangeMyHome(myhomeGuid, myhomeInfo, roomId);
if (error_code.isFail())
{
Log.getLogger().error("Fail ExchangeMyHome map");
return error_code;
}
return ServerErrorCode.Success;
}
public ServerErrorCode MyhomeHostEnterEditRoom(string roomId, string exceptUserGuid)
{
if (m_instance_rooms.TryGetValue(roomId, out var room) == false)
{
Log.getLogger().error($"not exist instance room : {roomId}");
return ServerErrorCode.NotExistInstanceRoom;
}
var error_code = room.MyhomeHostEnterEditRoom(exceptUserGuid);
if (error_code.isFail())
{
Log.getLogger().error($"Fail to MyhomeHostEnterEditRoom() !!! {error_code.toBasicString()}");
return error_code;
}
return ServerErrorCode.Success;
}
public ServerErrorCode ExchangeCraft(string roomId, string anchor_guid, Timestamp finish_craft_time)
{
if (m_instance_rooms.TryGetValue(roomId, out var room) == false)
{
Log.getLogger().error($"not exist instance room : {roomId}");
return ServerErrorCode.NotExistInstanceRoom;
}
var error_code = room.ExchangeCraft(anchor_guid, finish_craft_time);
if (error_code.isFail())
{
Log.getLogger().error("Fail ExchangeCraft map");
return error_code;
}
return ServerErrorCode.Success;
}
public int changeScreenPage(string roomId, bool isCustom, bool isNext, int customPage)
{
if (m_instance_rooms.TryGetValue(roomId, out var room) == false)
{
Log.getLogger().error($"not exist instance room : {roomId}");
return -1;
}
return room.changeScreenPage(isCustom, isNext, customPage);
}
public int getRoomId(string roomId)
{
if (m_instance_rooms.TryGetValue(roomId, out var room) == false)
{
return 0;
}
return room.getInstanceId();
}
public InstanceRoom? getInstanceRoomByRoomId(string roomId)
=> m_instance_rooms.GetValueOrDefault(roomId);
public ConcurrentDictionary<string, InstanceRoom> getInstanceRooms()
{
return m_instance_rooms;
}
}