Files
caliverse_server/GameServer/GameServerLogic.cs
2025-11-28 16:54:56 +09:00

730 lines
26 KiB
C#

using GameServer.Contents.WorldEvent;
using Nettention.Proud;
using Google.Protobuf;
using Microsoft.Extensions.Configuration;
using ServerCore;
using ServerBase;
using ServerCommon;
using MODULE_ID = System.UInt32;
using WORLD_META_ID = System.UInt32;
using CHANNEL_NO = System.UInt32;
namespace GameServer;
public partial class GameServerLogic : ServerLogicBase, IWithPacketNamespaceVerifier, IWithConfiguration,
IWithServerMetrics, IWithLogActor
{
private readonly PlayerManager m_player_manager = new();
private readonly SystemMetaMailManager m_system_mail_manager = new();
private readonly ChannelManager m_channel_manager = new();
private readonly NoticeChatManager m_notice_chat_manager = new();
private readonly SeasonPassManager m_season_pass_manager = new();
private readonly UgqApiManager m_ugq_api_manager = new();
private readonly LandManager m_land_manager = new();
private readonly BuildingManager m_building_manager = new();
private readonly BannerManager m_banner_manager = new();
private readonly RankingScheduleManager m_ranking_schedule_manager = new();
private readonly RankingManager m_ranking_manager = new();
private readonly WorldEventScheduleManager _worldEventScheduleManager = new();
private ReservationManager? m_reservation_manager { get; set; }
private ReturnManager? m_return_manager { get; set; }
public ChatCommand ChatCommand { get; private set; } = new ChatCommand();
private Map m_map = new();
private IConfiguration? m_key_options;
public GameServerLogic(ServerConfig config)
: base(config)
{
}
public Result mergeConfiguration(IConfiguration configuration)
{
var result = new Result();
var key_options = configuration;
m_key_options = key_options;
var port = ushort.Parse(key_options["port"] ?? "0");
var config_file = key_options["config"];
var server_config = getServerConfig();
server_config.setAppParamPort(port);
server_config.setConfigFilePath($"{"./" + ServerConfig.ConfigDirectory + "/" + config_file}");
return result;
}
protected override string onGetDumpFilename()
{
return getServerName() + "_" + getServerConfig().getAppParamPort();
}
public override async Task<Result> onInit()
{
var result = new Result();
var err_msg = string.Empty;
result = await base.onInit();
if (result.isFail())
{
err_msg = $"Failed to onInit() !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return result;
}
await onSetAccountLoginBlock(getServerConfig().AccountLoginBlockEnable);
MapDataTable.Instance.Load();
MapManager.Instance.Load();
m_reservation_manager = new ReservationManager();
m_return_manager = new ReturnManager();
return result;
}
protected override Result onCreateServerName()
{
var result = new Result();
var err_msg = string.Empty;
var server_type_name = getServerType();
var server_type = server_type_name.toServerType();
if (false == server_type.isValidServerType())
{
err_msg = $"ServerType invalid !!! : {server_type.ToString()}";
result.setFail(ServerErrorCode.ServerTypeInvalid, err_msg);
return result;
}
var server_config = getServerConfig();
var listen_port = server_config.toClientListenPort();
if (listen_port <= 0)
{
err_msg =
$"Client listen port invalid !!! : appParamPort:{listen_port}, configListenPort:{server_config.ClientListenPort} - {server_type.ToString()}";
result.setFail(ServerErrorCode.ClientListenPortInvalid, err_msg);
return result;
}
base.setServerName(server_type.toServerName(server_config.toClientListenIP(), listen_port, (int)getWorldId(),
(int)getChannelNo()));
return result;
}
protected override Result onLoadMetaDatas()
{
return ServerCommon.ServerLogicHelper.loadMetaDatas(this);
}
protected override async Task<Result> onRegisterModuleAll()
{
var result = new Result();
var config = getServerConfig();
var load_config_info = config.getLoadedConfig();
NullReferenceCheckHelper.throwIfNull(load_config_info,
() => $"load_config_info is null !!! - {toBasicString()}");
{
result = await tryCreateAndRegisterModule(() =>
{
var config_param = new DynamoDbClient.ConfigParam();
result = config_param.tryReadFromJsonOrDefault(load_config_info);
if (result.isFail())
{
return (null, result);
}
var module_context = new ModuleContext((MODULE_ID)ModuleId.DynamoDbConnector
, 0, 0, config_param);
var created_module = new DynamoDbClient(module_context);
return (created_module, result);
});
}
{
result = await tryCreateAndRegisterModule(() =>
{
var config_param = new RedisConnector.ConfigParam();
result = config_param.tryReadFromJsonOrDefault(load_config_info);
if (result.isFail())
{
return (null, result);
}
var module_context = new ModuleContext((MODULE_ID)ModuleId.RedisConnector
, 0, 0, config_param);
var created_module = new RedisConnector(module_context);
return (created_module, result);
});
}
{
result = await tryCreateAndRegisterModule(() =>
{
var found_redis_connector = getModule<RedisConnector>((MODULE_ID)ModuleId.RedisConnector);
if (null == found_redis_connector)
{
return (
null
, new Result() { ErrorCode = ServerErrorCode.ModuleNotFound, ResultString = $"RedisConnector not found !!! : moduleId:{ModuleId.RedisConnector}" }
);
}
var config_param = new RedisWithLuaScriptExecutor.ConfigParam();
result = config_param.tryReadFromJsonOrDefault(load_config_info);
if (result.isFail())
{
return (null, result);
}
var module_context = new ModuleContext((MODULE_ID)ModuleId.RedisWithLuaScriptExecutor
, 0, 0, config_param);
var created_module = new RedisWithLuaScriptExecutor(found_redis_connector, module_context);
return (created_module, result);
});
}
{
result = await tryCreateAndRegisterModule(() =>
{
var config_param = new RabbitMqConnector.ConfigParam();
result = config_param.tryReadFromJsonOrDefault(load_config_info);
if (result.isFail())
{
return (null, result);
}
config_param.ServiceName = getServerName();
var module_context = new ModuleContext((MODULE_ID)ModuleId.RabbitMqConnector
, 0, 0, config_param);
var created_module = new RabbitMQ4Game(module_context);
config_param.fnServerMessageRecvFromConsumer = created_module.onRecvProtocol;
return (created_module, result);
});
}
{
result = await tryCreateAndRegisterModule(() =>
{
var config_param = new MongoDbConnector.ConfigParam();
result = config_param.tryReadFromJsonOrDefault(load_config_info);
if (result.isFail())
{
return (null, result);
}
var module_context = new ModuleContext((MODULE_ID)ModuleId.MongoDbConnector
, 0, 0, config_param);
var created_module = new MongoDbConnector(module_context);
return (created_module, result);
});
}
{
result = await tryCreateAndRegisterModule(() =>
{
var config_param = new S3Connector.ConfigParam();
result = config_param.tryReadFromJsonOrDefault(load_config_info);
if (result.isFail())
{
return (null, result);
}
var module_context = new ModuleContext((MODULE_ID)ModuleId.S3Connector
, 0, 0, config_param);
var created_module = new S3Connector(module_context);
return (created_module, result);
});
}
{
result = await tryCreateAndRegisterModule(() =>
{
var config_param = new GameLoginListener.ConfigParam();
result = config_param.tryReadFromJsonOrDefault(load_config_info);
if (result.isFail())
{
return (null, result);
}
config_param.ListenIp = config.toClientListenIP();
config_param.ListenPort = config.toClientListenPort();
var module_context = new ModuleContext((MODULE_ID)ModuleId.ProudNetListener
, 0, 0, config_param);
var created_module = new GameLoginListener(this, module_context);
created_module.FnSendPacketToHost = onSendPacketToHost;
created_module.FnSendPacketToHosts = onSendPacketToHosts;
return (created_module, result);
});
}
return result;
}
public override async Task onTaskServerInfoSyncTick()
{
var result = await syncServerInfoToCache();
if (result.isFail())
{
Log.getLogger()
.error($"Failed to syncServerInfoToCache() !!! : {result.toBasicString()} - {toBasicString()}");
}
}
protected override List<IRule> onAppendRuleAll()
{
return new List<IRule>() { new InventoryRule(), };
}
public override async Task<Result> onCreateTickerAll()
{
var result = new Result();
result = await this.createTimeEventForMinuteTicker();
if (result.isFail())
{
Log.getLogger()
.error(
$"Failed to createTimeEventForMinuteTicker() !!! : {result.toBasicString()} - {toBasicString()}");
return result;
}
var server_type = getServerType().toServerType();
var entity_ticker_initializers = new Initializers();
if (ServerType.Channel == server_type)
{
entity_ticker_initializers.appendInitializer(
new ChannelUpdateTicker(ServerCommon.Constant.CHANNEL_UPDATE_TIME, null));
entity_ticker_initializers.appendInitializer(
new ShopProductCheckTicker(ServerCommon.Constant.SHOP_PRODUCT_CHECK_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new CaliumEventTicker(ServerCommon.Constant.CALIUM_EVENT_CHECK_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new CaliumStorageRetryTicker(ServerCommon.Constant.CALIUM_RETRY_CHECK_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new BuildingUpdateTicker(ServerCommon.Constant.BUILDING_UPDATE_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new LandAuctionReservationConfigureTicker(
ServerCommon.Constant.LAND_AUCTION_RESERVATION_CONFIGURE_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new LandAuctionCheckTicker(ServerCommon.Constant.LAND_AUCTION_CHECK_INTERVAL_MSEC, null));
}
entity_ticker_initializers.appendInitializer(
new EntityUpdateTicker(ServerCommon.Constant.ENTITY_UPDATE_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new EventUpdateTicker(ServerCommon.Constant.EVENT_UPDATE_INTERVAL_3_SEC, null));
entity_ticker_initializers.appendInitializer(
new UserLoginCacheRefreshTicker(ServerCommon.Constant.KEEP_LOGIN_UPDATE_TIME, null));
entity_ticker_initializers.appendInitializer(new NoticeChatTicker(ServerCommon.Constant.NOTICE_CHAT_UPDATE_TIME,
null));
entity_ticker_initializers.appendInitializer(
new PartyCacheRefreshTicker(ServerCommon.Constant.KEEP_PARTY_TIME / 2, null));
entity_ticker_initializers.appendInitializer(
new ReservationCheckTicker(ServerCommon.Constant.RESERVATION_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new NormalQuestCheckTicker(ServerCommon.Constant.NORMAL_QUEST_CHECK_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new SystemMailCheckTicker(ServerCommon.Constant.SYSTEM_MAIL_CHECK_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new RankingScheduleUpdateTicker(ServerCommon.Constant.RANKING_SCHESULE_CHECK_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new RankingUpdateTicker(ServerCommon.Constant.RANKING_CHECK_INTERVAL_MSEC, null));
// 월드 이벤트
entity_ticker_initializers.appendInitializer(
new WorldEventScheduleUpdateTicker(ServerCommon.Constant.GAME_EVENT_CHECK_INTERVAL));
// Match 서버 목록 갱신
entity_ticker_initializers.appendInitializer(
new MatchServerListTicker(ServerCommon.Constant.MATCH_SERVER_LIST_INTERVAL_MSEC, null));
entity_ticker_initializers.appendInitializer(
new GameEventCheckTicker(ServerCommon.Constant.GAME_EVENT_CHECK_INTERVAL));
result = await entity_ticker_initializers.init("EntityTickers");
if (result.isFail())
{
Log.getLogger().error($"Failed to init() !!! : {result.toBasicString()} - {toBasicString()}");
return result;
}
foreach (var initializer in entity_ticker_initializers.getInitializers())
{
var entity_ticker = initializer as EntityTicker;
NullReferenceCheckHelper.throwIfNull(entity_ticker, () => $"entity_ticker is null !!! - {toBasicString()}");
result = registerEntityTicker(entity_ticker);
if (result.isFail())
{
return result;
}
}
result = await onInitServerKeyToCache((int)getWorldId());
if (result.isFail())
{
return result;
}
return result;
}
public async Task<Result> setupServerMetrics()
{
var result =
await ServerBase.ServerMetricsHelper.setupDefaultServerMetrics<RedisConnector>(this,
(MODULE_ID)ModuleId.RedisConnector);
if (result.isFail())
{
return result;
}
return result;
}
protected override async Task<Result> onStartServer()
{
var result = new Result();
var err_msg = string.Empty;
var server_name = getServerName();
var config = getServerConfig();
if (getServerType().toServerType() == ServerType.Channel)
{
config.setChannelNo(getChannelNo());
config.setWorldId((ushort)getWorldId());
}
MetaTextStringFuncHelper.registerFuncAll();
var proud_net_listener = this.getProudNetListener();
proud_net_listener.changeMaxConnectionCount(config.DefaultMaxUser);
if (config.NftRule.NftDBAccess)
{
result = await MySqlConnectorHelper.simpleTryConnectToDb(config.AccountNftDb);
if (result.isFail())
{
err_msg = $"Failed to connect NFT Db !!! : {result.toBasicString()}";
Log.getLogger().error(err_msg);
return result;
}
}
Log.getLogger().info($"tryLoadGlobalContent start !!! : {server_name} - {toBasicString()}");
result = await tryLoadGlobalContent();
if (result.isFail())
{
Log.getLogger().error(result.toBasicString());
return result;
}
Log.getLogger().info($"tryLoadGlobalContent finish !!! : {server_name} - {toBasicString()}");
result = await base.onStartServer();
if (result.isFail())
{
return result;
}
result = await proud_net_listener.startListen();
if (result.isFail())
{
return result;
}
if (getServerType().toServerType() == ServerType.Channel)
{
if (ServerCommon.MetaData.Instance._WorldMetaTable.TryGetValue((int)getWorldId(), out var worldMetaData) ==
false)
{
err_msg = $"Failed to onInit() !!! : Failed to read WorldMetaData !!! worldMetaId:{getWorldId()}";
result.setFail(ServerErrorCode.NotFoundTable, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (false == await m_map.onInit(worldMetaData.MapPath, getChannelNo().ToString()))
{
err_msg = $"Failed to onInit() !!!";
result.setFail(ServerErrorCode.NotFoundTable, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
await FarmingHelper.tryFinalyzeUncompletedFarmingByMap(m_map);
}
return result;
}
private async Task<Result> tryLoadGlobalContent()
{
var result = new Result();
result = await m_system_mail_manager.LoadDB();
{
if (result.isFail()) return result;
}
Log.getLogger().Info("tryLoadGlobalContent - m_system_mail_manager.LoadDB() OK");
result = await m_notice_chat_manager.LoadDB();
{
if (result.isFail()) return result;
}
Log.getLogger().Info("tryLoadGlobalContent - m_notice_chat_manager.LoadDB()OK");
result = await m_land_manager.loadLands();
{
if (result.isFail()) return result;
}
Log.getLogger().Info("tryLoadGlobalContent - m_land_manager.loadLands() OK");
var isDebugMode = this.isDebugMode();
if (isDebugMode == false)
{
result = await m_building_manager.loadBuildings();
{
if (result.isFail()) return result;
}
Log.getLogger().Info("tryLoadGlobalContent - m_building_manager.loadBuildings() OK");
}
if (isDebugMode == false)
{
result = await LandAuctionManager.It.tryLoadLandAuctionAll();
{
if (result.isFail()) return result;
}
Log.getLogger().Info("tryLoadGlobalContent - LandAuctionManager.It.tryLoadLandAuctionAll() OK");
}
result = await m_banner_manager.loadBanners();
{
if (result.isFail()) return result;
}
Log.getLogger().Info("tryLoadGlobalContent - m_banner_manager.loadBanners() OK");
result = await m_ranking_schedule_manager.loadRankingSchedules();
{
if (result.isFail()) return result;
}
Log.getLogger().Info("tryLoadGlobalContent - m_ranking_schedule_manager.loadRankingSchedules() OK");
result = await m_ranking_manager.loadRankings();
{
if (result.isFail()) return result;
}
Log.getLogger().Info("tryLoadGlobalContent - m_ranking_manager.loadRankings() OK");
result = await _worldEventScheduleManager.LoadSchedulesAsync(this.getDynamoDbClient());
{
if (result.isFail()) return result;
}
Log.getLogger().Info("tryLoadGlobalContent - m_world_event_scheduler.LoadEventsFromDatabaseAsync() OK");
m_season_pass_manager.Init();
if (getServerConfig().OfflineMode == false)
{
await AIChatServerConnector.onInit();
}
Log.getLogger().Info("tryLoadGlobalContent - m_season_pass_manager.Init() OK");
await m_ugq_api_manager.onInit();
Log.getLogger().Info("tryLoadGlobalContent - m_ugq_api_manager.Init() OK");
// calium converter entity
var calium_storage_entity = findGlobalEntity<CaliumStorageEntity>();
NullReferenceCheckHelper.throwIfNull(calium_storage_entity, () => $"CaliumStorageEntity is null !!!");
var calium_storage_action = calium_storage_entity.getEntityAction<CaliumStorageAction>();
NullReferenceCheckHelper.throwIfNull(calium_storage_action, () => $"CaliumStorageAction is null !!!");
result = await calium_storage_action.initStorageAttribute();
{
if (result.isFail()) return result;
}
// ugcnpcrank entity
var ugc_npc_rank_entity = findGlobalEntity<UgcNpcRankEntity>();
NullReferenceCheckHelper.throwIfNull(ugc_npc_rank_entity, () => $"UgcNpcRankEntity is null !!!");
var ugc_npc_rank_action = ugc_npc_rank_entity.getEntityAction<UgcNpcRankManageAction>();
NullReferenceCheckHelper.throwIfNull(ugc_npc_rank_action, () => $"UgcNpcRankManageAction is null !!!");
result = await ugc_npc_rank_action.initRank();
{
if (result.isFail()) return result;
}
//Battle 처리
var server_logic = GameServerApp.getServerLogic();
var server_config = server_logic.getServerConfig();
bool is_battle_system_active = server_config.BattleSystemEnable;
if (is_battle_system_active)
{
await BattleInstanceManager.It.onInit();
}
return result;
}
protected override List<EntityBase> onAppendGlobalEntityAll()
{
var list = new List<EntityBase>();
list.Add(new GlobalParty());
list.Add(new UgcNpcRankEntity());
list.Add(new CaliumStorageEntity());
return list;
}
public bool onSendPacketToHost(Nettention.Proud.HostID hostId, RmiContext rmiContext, IMessage msg)
{
var client_to_game = msg as ClientToGame;
NullReferenceCheckHelper.throwIfNull(client_to_game, () => $"client_to_game is null !!! - {toBasicString()}");
var proud_net_listener = this.getProudNetListener();
var found_proxy = proud_net_listener.findProxy<PClientToGame.Proxy>();
;
NullReferenceCheckHelper.throwIfNull(found_proxy, () => $"found_proxy is null !!! - {toBasicString()}");
return found_proxy.Message(hostId, RmiContext.ReliableSend, client_to_game);
}
public bool onSendPacketToHosts(Nettention.Proud.HostID[] hostIds, RmiContext rmiContext, IMessage msg)
{
var client_to_game = msg as ClientToGame;
NullReferenceCheckHelper.throwIfNull(client_to_game, () => $"client_to_game is null !!! - {toBasicString()}");
var proud_net_listener = this.getProudNetListener();
var found_proxy = proud_net_listener.findProxy<PClientToGame.Proxy>();
;
NullReferenceCheckHelper.throwIfNull(found_proxy, () => $"found_proxy is null !!! - {toBasicString()}");
return found_proxy.Message(hostIds, rmiContext, client_to_game);
}
public bool isSetupCompleted()
{
return ServerBase.ServerMetricsHelper.isSetupCompleted(this);
}
public async Task<Result> syncServerInfoToCache()
{
var result = new Result();
var proud_net_listener = this.getProudNetListener();
var server_name = getServerName();
var server_config = getServerConfig();
var listen_ip = server_config.toClientListenIP();
var listen_port = server_config.toClientListenPort();
var user_count = proud_net_listener.getEntityWithSessions().Count;
var reservation_count = 0;
var ugc_npc_count = 0;
var return_count = 0;
var awsInstanceId = getInstanceId();
int room_capacity = 0;
if (getServerType().toServerType() == ServerType.Indun)
{
room_capacity = InstanceRoomManager.Instance.Capacity;
}
result = await ServerBase.ServerMetricsHelper.syncServerInfoToCache(this
, server_name
, listen_ip, listen_port
, user_count, proud_net_listener.getMaxConnectionCount()
, reservation_count, return_count
, ugc_npc_count
, awsInstanceId
, (Int32)getWorldId()
, (Int32)getChannelNo()
, room_capacity);
if (result.isFail())
{
return result;
}
return result;
}
public bool isValidPacketNamespace(string? toCheckNamespace, IPacketCommand packetCommand)
{
var packet_namespace = "GameServer.PacketHandler";
if (null != toCheckNamespace
&& true == toCheckNamespace.Contains(packet_namespace))
{
return true;
}
else
{
ServerCore.Log.getLogger()
.error(
$"Invalid GameServer PacketNamespace !!!, not included Namespace : {packet_namespace} ⊆ {toCheckNamespace}, packetCommnad:{packetCommand.toBasicString()}");
}
return false;
}
public WORLD_META_ID getWorldId()
{
var config = getConfiguration();
return config.getWorldId();
}
public CHANNEL_NO getChannelNo()
{
var config = getConfiguration();
return config.getChannelNo();
}
public ILogActor toLogActor()
{
return this.toLogActor();
}
// 디버깅을 위한 코드 추가
private bool isDebugMode()
{
if (getServerConfig().ServiceType != Enum.GetName(ServiceType.Dev))
{
return false;
}
try
{
bool.TryParse(m_key_options?["debug"], out bool isDebugMode);
return isDebugMode;
}
catch (Exception)
{
return false;
}
}
protected override Task? onRunning()
{
return base.onRunning();
}
}