%%%-----------------------------------
%%% @Module  : mod_login
%%% @Author  : xyao
%%% @Email   : jiexiaowen@gmail.com
%%% @Created : 2011.06.14
%%% @Description: 登陆模块
%%%-----------------------------------
-module(mod_login).
-export([
        login/1,
        logout/1,
        logout/2,
        stop_player/1,
        stop_all/0,
        server_stop_all/0,
        save_online/1,
        delay_logout/2,
        server_login/1,
        re_login/6
    ]).

-include("server.hrl").
-include("unite.hrl").
-include("goods.hrl").
-include("scene.hrl").
-include("predefine.hrl").
-include("figure.hrl").
-include("attr.hrl").
-include("common.hrl").
-include("login.hrl").
-include("task.hrl").
-include("team.hrl").
-include("guild.hrl").
-include("skill.hrl").
-include("dungeon.hrl").
-include("def_event.hrl").
-include("vip.hrl").
-include("rec_recharge.hrl").
-include("rec_offline.hrl").
-include("activitycalen.hrl").
-include("def_fun.hrl").
-include("welfare.hrl").
-include("rec_auction.hrl").
-include("def_goods.hrl").
-include("dungeon_multi.hrl").
-include("transform.hrl").
-include ("guild_run_assist.hrl").

%% 验证登录
%% Data:登陆验证数据
%% Arg:tcp的Socket进程,socket ID
login(LoginParams = #login_params{id = Id, accname = Accname}) ->
    case lib_login:get_player_login_by_id(Id) of
        [] ->
            {error, 0};
        [Aname, Status] ->
            case Status of
                0 -> %%正常
                    case binary_to_list(Aname) =:= Accname of
                        true ->
                            do_login(LoginParams);
                        false ->
                            {error, 0}
                    end;
                1 -> %% 封号
                    {error, 6};
                2 -> %% 买卖元宝封号
                    {error, 7};
                3 -> %% 不正当竞争封号
                    {error, 8};
                _ -> %% 状态不正常
                    {error, 9}
            end
    end.

%% 角色登陆
do_login(LoginParams = #login_params{id = Id, accid = AccId, accname = Accname, ip = Ip,
        socket = Socket, ids = Ids,
        login_source = LoginSource, ta_guest_id = TaGuestId}) ->
    %% 检查用户登陆和状态已经登陆的踢出出去
    case check_player(Id, Ids, Socket, Ip, LoginSource, TaGuestId) of
        {ok, Pid} ->
            {ok, Pid};
        false ->
            case catch mod_server:start_link([self(), ?ONLINE_ON, LoginParams]) of
                {ok, Pid} ->
                    {ok, Pid};
                Other ->
                    ?ERR("login error AccId(~w), AccName(~p), id(~w)~nReason:~p~n", [AccId, Accname, Id, Other]),
                    {error, 0}
            end
    end.

%% 检查用户是否登陆了
check_player(Id, Ids, Socket, Ip, LoginSource, TaGuestId) ->
    Fun = fun(TmpId, Result) ->
        case misc:get_player_process(TmpId) of
            Pid when is_pid(Pid), TmpId == Id ->
                case mod_server:re_login(Pid, Socket, Ip, self(), LoginSource, TaGuestId) of
                    true -> %% 将socket进程与server关联
                        link(Pid),
                        {ok, Pid};
                    _ ->
                        login_outside(Id, Pid),
                        timer:sleep(500),
                        Result
                end;
            Pid when is_pid(Pid) -> %% 允许托管的号共存
                case catch mod_server:online_flag(Pid) of
                    ?ONLINE_OFF ->
                        Result;
                    ?ONLINE_ON ->
                        login_outside(Id, Pid),
                        timer:sleep(500),
                        Result;
                    ?ONLINE_OFF_HOSTING ->
                        Result;
                    _ErrorR ->
                        ?ERR("get_online_flag err:~p~n", [[TmpId, Pid, _ErrorR]]),
                        login_outside(Id, Pid),
                        timer:sleep(500),
                        Result
                end;
            _ ->
                Result
        end
          end,
    lists:foldl(Fun, false, Ids).

login_outside(Id, Pid) ->
    %% 通知客户端账户在别处登陆
    {ok, BinData} = pt_590:write(59004, 5),
    lib_server_send:send_to_uid(Id, BinData),
    logout(Pid),
    ok.

%% 把玩家踢出去
stop_player(PlayerId) ->
    case misc:get_player_process(PlayerId) of
        false -> skip;
        Pid -> logout(Pid)
    end.

%% 把所有在线玩家踢出去
stop_all() ->
    mod_ban:ban_all(),
    do_stop_all(ets:tab2list(?ETS_ONLINE)).

%% 让所有玩家自动退出
do_stop_all([]) ->
    ok;
do_stop_all([H | T]) ->
    {ok, BinData} = pt_590:write(59004, 11),
    lib_server_send:send_to_sid(H#ets_online.sid, BinData),
    %% 关闭socket
    lib_server_send:send_to_sid(H#ets_online.sid, close),
    logout(H#ets_online.pid),
    timer:sleep(10),
    do_stop_all(T).

%% 离线
delay_logout(Pid, _Socket) when is_pid(Pid) ->
    case misc:is_process_alive(Pid) of
        true -> mod_server:stop(Pid);
        false -> skip
    end.

%% 退出登陆
logout(Pid) when is_pid(Pid) ->
    case misc:is_process_alive(Pid) of
        true -> mod_server:stop(Pid);
        false -> skip
    end;
logout(_) -> skip.

%% 服务器停止调用
server_stop_all()->
    do_server_stop_all(ets:tab2list(?ETS_ONLINE)).

%% 让所有玩家自动退出
do_server_stop_all([]) ->
    ok;
do_server_stop_all([H | T]) ->
    {ok, BinData} = pt_590:write(59004, 11),
    lib_server_send:send_to_sid(H#ets_online.sid, BinData),
    %% 关闭socket
    lib_server_send:send_to_sid(H#ets_online.sid, close),
    case misc:is_process_alive(H#ets_online.pid) of
        true -> mod_server:server_stop(H#ets_online.pid);
        false -> skip
    end,
    timer:sleep(10),
    do_server_stop_all(T).

%% 退出游戏系统
%% LogNorlOrErr
%% -define(LOGOUT_LOG_NORMAL,      1).   %% 正常退出日志
%% -define(LOGOUT_LOG_ERROR,       2).   %% 异常退出日志
%% -define(LOGOUT_LOG_SERVER_STOP, 3).   %% 服务器停服玩家退出操作
logout(PS, LogNorlOrErr) when is_record(PS, player_status)->
    #player_status{id = RoleId, accid = AccId, accname = AccName,
        scene = Scene,
        ip = Ip, network_type = NetworkType,
        combat_power = Power, hightest_combat_power = HightPower,
        figure = #figure{lv = RoleLv}
    } = PS,
    %% 更新离线时间
    NowTime = utime:unixtime(),
    if
        PS#player_status.online =:= ?ONLINE_OFF_HOSTING ->
            %% 托管玩家不记录
            %% 只记录离线状态
            ?TRY(lib_player:update_online_flag(RoleId, ?ONLINE_OFF)),
            ok;
        true ->
            %% 保存在线时长日志
            ?TRY(lib_login:log_all_online(PS, NowTime)),
            %% 保存在线状态
            ?TRY(lib_player:update_player_login_offline(PS, NowTime)),
            %% 游戏服活跃管理
            case LogNorlOrErr of
                ?LOGOUT_LOG_SERVER_STOP -> ignore;
                _ -> svr_vitality_local_mgr:online_update(RoleId, HightPower, NowTime)
            end,
            ta_agent_fire:role_logout(PS, NowTime, LogNorlOrErr),
            ok
    end,
    ?TRY(
        lib_log_api:log_login_logout(
            RoleId, LogNorlOrErr,
            RoleLv, Power, Scene,
            Ip, NetworkType,
            AccId, AccName
        )
    ),
    lib_role_task:logout(),

    %% 下线把祈愿数据更新到数据库
    ?TRY(lib_currencywish:logout(PS)),
    %% 下线把竞技场冷却写入数据库
    ?TRY(lib_arena:replace_db_arena_role_cd_byid(RoleId)),

    %% 删除在线ETS记录
    ets:delete(?ETS_ONLINE, RoleId),

    %% 玩家离开场景
    ?TRY(lib_scene:leave_scene(PS)),

    %% 更新排行榜
    ?TRY(lib_common_rank_api:logout(PS)),

    %% 更新经验海数据
    ?TRY(exp_sea_logic:logout(PS)),

    %% 清理离开场景时所要的操作
    ?TRY(lib_scene:change_scene_handler(PS, 0, PS#player_status.scene, [])),

    %% 保存玩家state数据
    ?TRY(lib_player:update_player_state(PS)),

    %% 保存玩家经验数据
    ?TRY(lib_player:update_player_exp_offline(PS)),


    %% 玩家下线，移除ets_buff信息
    ?TRY(lib_goods_buff:logout(PS)),

    %% 下线删除玩家
    ?TRY(mod_chat_agent:delete(RoleId)),

    %% 派发下线事件
    ?TRY(lib_player_event:dispatch(PS, ?EVENT_ONLINE_FLAG, 0)),

    %% 退出队伍
    ?TRY(lib_team_api:quit_team(PS, 1)),

    %% 下线清理公会支援数据
    ?TRY(lib_guild_support_mod:logout(PS)),

    %% 消费数据保存
    ?TRY(lib_consume_data:save_advance_payments(RoleId)),
    ?TRY(lib_consume_data:logout_check(RoleId)),
    ?TRY(lib_marble:logout(PS)),

    ?TRY(lib_lattice_shop:logout(PS)),
    ?TRY(offline_hosting_api:logout(PS, NowTime)),
    ?TRY(lib_role_god_dun:logout(PS)),

    ?TRY(lib_role_galaxy:logout(PS)),
    ?TRY(custom_act_seckill:logout(PS)),

    %% 精英考核在线数据保存
    ?TRY(lib_elite_assessment:logout(RoleId, RoleLv)),
    %% 成就在线数据保存
    ?TRY(lib_achievement:logout(RoleId)),
    %% 嗨点在线数据保存
    ?TRY(mod_hi_point:logout(RoleId)),
    %% 玩家其他功能下线处理
    ?TRY(lib_player_logout:logout(PS, ?NORMAL_LOGOUT)),

    {ok, LastPS} = ?TRY(lib_player_event:dispatch(PS, ?EVENT_DISCONNECT, ?NORMAL_LOGOUT)),

    %% TA上报
    ?TRY(ta_agent_fire:role_daily_active(PS)),
    ?TRY(ta_agent_fire:role_daily_attr(PS)),
    %% 关闭玩家的定时器
    #player_status{battle_attr = BA} = LastPS,
    #pk{pk_value_ref = PkValueRef} = BA#battle_attr.pk,
    util:cancel_timer(PkValueRef),
    %% 玩家下线，更新下线时间
    ?TRY(mate_api:logout(PS)),

    PS#player_status.online == ?ONLINE_ON andalso
     ?TRY(exp_dun_drop_mgr:role_logout(PS#player_status.id, NowTime)),

    %% 关闭消息发送进程(下线处理均放在上面)
    exit(LastPS#player_status.sid, kill),
    ok;
logout(_PS, LogType)->
    ?ERR("logout type error:~p~n", [[_PS, LogType]]),
    ok.

%% 游戏服务器登陆
%% 此函数已经改为玩家进程执行
server_login([ConnPid, OnlineFlag, LoginParams]) ->
    #login_params{
        id = Id, accid = AccId, accname = AccName, ip = Ip,
        socket = Socket, ids = _AccRoleIds, network_type = NetworkType,
        login_source = LoginSource, ta_guest_id = TaGuestId
    } = LoginParams,
    Pid = self(),
    %% 注册玩家pid
    case misc:register(global, misc:player_process_name(Id), Pid) of
        yes ->
            ok;
        _ ->
            %% 该玩家已经注册，可能是并发登陆导致
            throw({already_started, Id})
    end,
    NowTime = utime:unixtime(),
    %% 玩家玩家数据
    [GM, TalkLim, TalkLimType, TalkLimTime, LastLogoutTime, _TalkLimRight, RegTime, Source, LastLoginTime, ComeBackState, LastBehavior|_]
    = lib_player:get_player_login_data(Id),
    %% 修正上一次退出游戏时间
    NewLastLogoutTime = if
        LastLoginTime > LastLogoutTime -> LastLoginTime;
        true -> LastLogoutTime
    end,
    [NickName, Sex, Lv, Career, Realm, Picture, PictureLim, PictureVer, ProfilePhotoId,CReName, CReNameTime, _CReCareerTime|_]
    = lib_player:get_player_low_data(Id),
    [Gold, BGold, _FGold, Coin, GCoin,GFame,Honour, Exp, ExpList, HightestCombatPower|_] = lib_player:get_player_high_data(Id),
    LastExpList = case util:bitstring_to_term(ExpList) of
        undefined -> [];
        NewExpList -> NewExpList
    end,
    [DbScene, DbX, DbY, Hp, QuickbarBin, PkValue, PkStatus, PkStatusChangeTime, SkillPoint,
        PkValueChangeTime, LastTaskId, BitLastBeKill|_] = lib_player:get_player_state_data(Id),
    [InitCellNum, InitStorageNum|_] = lib_player:get_player_attr_data(Id),
    %% 背包数量
    CellNum = max(data_goods:get_location_size(?GOODS_LOC_BAG), InitCellNum),
    StorageNum = max(data_goods:get_location_size(?GOODS_LOC_STORAGE), InitStorageNum),
    TransformData = transform:login(Id),
    LvModel = lib_goods_util:get_lv_model(Id, Career, Sex, TransformData#transform_info.transform_id, Lv),
    {FashionModelList, WearFashion} = lib_fashion:get_equip_fashion_list(Id, Career),
    StatusGuild = lib_guild:load_status_guild(Id,NowTime,NewLastLogoutTime),
    VipStatus = lib_vip:login(Id),
    #vip_status{type = VipType, lv = VipLv, trial_lv = TrialLv} = VipStatus,
    %% 物品初始化
    GoodsStatus = lib_goods_do:login(Id, Lv, CellNum, StorageNum),
    lib_goods_do:init_data(GoodsStatus),
    %% 玩家活跃度形象
    RoleLives = lib_liveness:login(Id, Lv),
    %% 特殊货币
    CurrencyMap = lib_goods_util:load_currency(Id),
    %% 玩家形象
    Figure = #figure{id = Id, name = NickName, sex=Sex, lv=Lv, career=Career, realm=Realm, gm=GM,
        vip= VipLv, lv_model = LvModel, fashion_model = FashionModelList, wear_fashion = WearFashion,
        picture = Picture, picture_ver = PictureVer,profile_photo_id = ProfilePhotoId,
        guild_id = StatusGuild#status_guild.id, accname = AccName,
        guild_name = StatusGuild#status_guild.name,
        flag = StatusGuild#status_guild.flag,
        position = StatusGuild#status_guild.position,
        position_name = StatusGuild#status_guild.position_name,
        turn = TransformData#transform_info.transform_id,
        turn_stage = TransformData#transform_info.stage
    },
    %% pk状态
    Pk = lib_player_record:pk_login(NowTime, PkStatus, PkStatusChangeTime, PkValue, PkValueChangeTime,NewLastLogoutTime),
    %% 需要进行复活操作
    BA = #battle_attr{hp=Hp, pk=Pk},
    SendMsgPid = ConnPid,
    TaskDaily = lib_role_task:login(#task_args{id=Id,gs_dict=GoodsStatus#goods_status.dict,sid=SendMsgPid, figure=Figure}),
    %% 加载日常计数
    lib_daily:online(Id),

    %%加载周计数
    lib_week:online(Id),

    %%加载终生次数记录器
    lib_counter:online(Id),

    %% 技能初始化
    Skill = lib_skill:skill_online(Id, Career, Lv, SkillPoint, {VipType, VipLv, TrialLv}),
    %% npc信息
    NpcInfo = lib_npc:load_role_npc_info(Id),
    %% 加载技能快捷栏
    Quickbar = quickbar_api:load_check_quickbar(QuickbarBin, Career, Sex),
    %% 上一次最后被谁击杀
    RealBeKill = case util:bitstring_to_term(BitLastBeKill) of
        undefined -> [];
        LastBeKill -> LastBeKill
    end,
    Status3V3 = lib_3v3_local:login([Id]),
    RelaCoupon = relation_coupon:login(Id, NowTime),
    ProfilePhotoInfo = lib_player:load_role_profile_photo_info(Id),
    RoleMarble = lib_marble:login(Id),
    Dance = lib_dance:login(Id),
    {RoleLegion, Figure2} = lib_role_legion:login(Id, Figure),
    PlayerStatus = #player_status{
        id = Id,
        accid = AccId,
        accname = AccName,
        platform = config:get_platform(),
        server_num = config:get_server_num(),
        server_name = util:get_server_name(),
        reg_server_id = config:get_reg_server_id(),
        server_id = config:get_server_id(),
        source = Source,
        last_login_time = NowTime,
        login_time_before_last = LastLoginTime,
        socket = Socket,
        %% 上次登出时间肯定比上次登录时间晚,如果异常直接取上一次登录时间
        last_logout_time = NewLastLogoutTime,
        ip = util:ip2bin(Ip),
        network_type = NetworkType,
        figure = Figure2,
        pid = Pid,
        sid = SendMsgPid,
        scene = DbScene,
        x = DbX,
        y = DbY,
        gold = Gold,
        bgold = BGold,
        coin = Coin,
        gcoin = GCoin,
        gfame = GFame,
        honour = Honour,
        exp = Exp,
        exp_lim = data_exp:get(Lv),
        exp_list = LastExpList,
        hightest_combat_power = HightestCombatPower,
        talk_lim = TalkLim,
        talk_lim_type = TalkLimType,
        talk_lim_time = TalkLimTime,
        team = #status_team{},
        cell_num = CellNum,
        storage_num = StorageNum,
        battle_attr = BA,
        skill = Skill,
        npc_info = NpcInfo,
        guild = StatusGuild,
        picture_lim = PictureLim,
        dungeon = lib_dungeon:login(Id),
        revive_status = #revive_status{},
        vip_status = VipStatus,
        recharge_status = #recharge_status{},
        off = #status_off{},
        reconnect = ?NORMAL_LOGIN,
        role_liveness = RoleLives,
        flower = lib_flower:login(Id, NowTime),
        transform_data = TransformData,
        quickbar = Quickbar,
        guild_skill = lib_guild_skill:login(Id),
        help_type_setting = #{},
        reg_time = RegTime,
        currency_map = CurrencyMap,
        c_rename = CReName,
        c_rename_time = CReNameTime,
        last_task_id = LastTaskId,
        last_be_kill = RealBeKill,
        role_auction = #status_auction{}
        ,role_3v3 = Status3V3
        ,role_drum = lib_role_drum:login(Id)
        ,kf_1vn = lib_kf_1vn:login(Id)
        ,fishing = lib_fishing:login(Id)
        ,comeback_state = ComeBackState
        ,limit_goal = lib_limit_goal:login(Id, Lv)
        ,supplication = lib_supplication:login(Id, Lv)
        ,module_advance = lib_module_advance:login(Id, Lv)
        ,draw_record = lib_draw_record:login(Id)
        ,joy_wish = lib_joy_wish_act:login(Id)
        ,vip_bag_onsale = lib_vip_bag_onsale:login(Id)
        ,lucky_flop = lib_lucky_flop:login(Id)
        ,chat_limit_new = lib_chat_limit:login(Id)
        ,role_coupon = RelaCoupon
        ,guild_task = lib_guild_labor:login(Id)
        ,guild_shop = lib_guild_shop_mod:login(Id,NowTime)
        ,profile_photo_info = ProfilePhotoInfo
        ,mate = mate_api:login(Id)
        ,role_marble = RoleMarble
        ,exp_dun_achieve = lib_dungeon_exp:login(Id)
        ,guild_collect = lib_guild_collect:login(Id)
        ,role_fashion = lib_fashion:fashion_login(Id, NowTime)
        ,welfare = welfare_api:login(Id)
        ,role_achievement = lib_achievement:login(Id)
        ,goods_devour = goods_devour:login(Id)
        ,role_motion = motion_energy:login_init()
        ,last_behavior = LastBehavior
        ,role_lattice = lib_lattice:login(Id)
        ,role_lattice_shop = lib_lattice_shop:login(Id)
        ,investment = lib_investment:login(Id)
        ,role_dun_pet = lib_dungeon_pet:login(Id)
        ,role_dun_guard = lib_dungeon_guard:login_init(Id)
        ,race_rank = race_rank:load_role_race_rank_data(Id)
        ,role_god = god_api:login(Id, GoodsStatus)
        ,online = OnlineFlag
        ,offline_hosting = offline_hosting_api:login(Id, OnlineFlag)
        ,cross_guild_war = cross_guild_war_db:load_role_cgw(Id)
        ,role_god_dun = lib_role_god_dun:login(Id)
        ,role_galaxy = lib_role_galaxy:login(Id, Lv)
        ,beauty_contest = beauty_contest_api:login(Id)
        ,role_dun_galaxy_chapter = lib_dungeon_galaxy:login(Id)
        ,role_first_top = lib_common_rank:db_get_role_first_top_reward(Id)
        ,role_rank_praise = lib_common_rank_api:login_praise(Id)
        ,role_legion = RoleLegion
        ,role_cross_point = cross_point_fight_logic:login(Id)
        ,role_treasure_map = lib_treasure_map:init(Id)
        ,equipment_task = equipment_task:login(Id)
        ,task_daily = TaskDaily
        ,dance = Dance
        ,role_guild_run_assist = #role_guild_run_assist{}
        ,boss_first_blood = boss_first_blood_logic:login(Id)
        ,mobilize_combat_power = custom_act_mobilize:login(Id)
        ,role_act_reserve = lib_activity_reserve:login(Id, Lv, NowTime)
        % % ----------------------------------------------------------
        ,role_currencywish = lib_currencywish:login(Id)    %货币祈愿初始化数据
    },
    %% 玩家初始加载竞技场冷却,加载到进程字典
    lib_arena:get_db_arena_role_cd_to_processmap(Id),
    %% 注册玩家信息
    lib_player:register_player_info_dict(#{id => Id}),
    %% 成就列表初始化
    lib_achievement:login_achv(Id),
    %% 精英考核初始化
    lib_elite_assessment:login(Id, Lv),
    %% 活跃度初始化
    lib_activitycalen:login(PlayerStatus),
    %% 充值数据
    RechargePS = lib_recharge:login(PlayerStatus, NowTime),
    %% 称号列表初始化
    DsgtPS = lib_designation_util:login(RechargePS),
    %% 境界学位初始化
    % DegreePS = lib_degree:login(DsgtPS),
    %% 物品BUFF初始化
    BuffPS = lib_goods_buff:login(DsgtPS),
    %% 登陆移除离线玩家信息
    lib_offline_api:login(Id),
    %% 查看玩家是否有自动学习技能
    {ok, SkillPS} = lib_skill:auto_learn_skill(BuffPS, login),
    FillQBarPS = quickbar_api:check_fill_quickbar(SkillPS),
    %% 设置初始化
    SettingPS = lib_game:login_setting(FillQBarPS),
    %% 邮件推送
    PushmailPS = lib_pushmail:login(SettingPS),
    %% 充值
    RechargeReturnPS = lib_recharge_act:login(PushmailPS),
    %% 累充活动
    RechargeActPs = lib_recharge_cumulation:login(RechargeReturnPS),
    %% 资源找回
    ResourceBackPS = lib_resource_back:login(RechargeActPs),
    %% 个性装扮
    DressUpPS = lib_dress_up:login(ResourceBackPS),
    %% 宠物
    PetPs = lib_pet_api:login(DressUpPS),
    %% 孩子
    ChildPs = lib_child_api:login(PetPs),
    %% 限时目标星魂
    LimitGoalSoulPS = lib_limit_goal:count_limit_goal_attr(ChildPs),
    %%进阶系统
    FosterPs = lib_role_foster:login(LimitGoalSoulPS),
    %%进阶珍宝
    FosterSkinPs = lib_role_foster_skin:login(FosterPs),
    %%幻光
    VisusPs  = lib_role_visus:login(FosterSkinPs),
    %%灵能
    NucleonPs = lib_role_nucleon:login(VisusPs),
    %% 七天登录
    LoginRewardPs = lib_login_reward:login_reward_login(NucleonPs),
    %% 生活技能
    LiveSkillPS = lib_live_skill:login(LoginRewardPs),
    %% 跨服组队
    DunMulTiPS = lib_dun_multi:login(LiveSkillPS),
    %% 巅峰竞技
    TopPkPS = lib_top_pk:login(DunMulTiPS),
    %% 幸运鉴宝
    TEPS = lib_treasure_evaluation:login_te(TopPkPS),
    %% 精品特卖
    SpecSellPS = lib_spec_sell_act:login(TEPS),
    %% 巨折献礼
    OverFlowPS = lib_overflow_gift:login(SpecSellPS),
    %% 龙神扭蛋
    CapsuleEggPS = lib_capsule_egg:login(OverFlowPS),
    %% 交易市场
    MarketRole = market:login(CapsuleEggPS),
    %% 装备系统
    EquipmentPS = equipment_api:login(MarketRole),
    %% 七日任务
    SevenDaysGoalPS = lib_seven_days_goal:login_load_data(EquipmentPS),
    %% 聊天机器人
    ChatSimuPS = chat_simulation:login(SevenDaysGoalPS),
    %% 本服BOSS
    LocalBossPS = local_boss_api:login(ChatSimuPS),
    PelletPS = custom_act_pellet:login(LocalBossPS),
    THousePS = treasure_house:login(PelletPS),
    %%跨服国战霸主
    CgwPS = cross_guild_war_api:king_idn(THousePS),
    %% 羁绊
    MatePS = mate_api:login(CgwPS),
    %% 战魂
    WarSoulPS = warsoul_logic:login(MatePS),
    %% 魂力
    UraniumPs = lib_role_uranium:login(WarSoulPS),
    %% 护送
    ConvoyPs  = convoy_api:login(UraniumPs, NowTime),
    %% 经验海
    ExpSeaPs = exp_sea_logic:login(ConvoyPs),
    %% 定制活动初始化
    CustomActPS = lib_custom_act_api:login(ExpSeaPs),
    ActDataPS = custom_act_sellswords_resupply:login_load_data(CustomActPS),
    WorkroomPs = lib_workroom:login(ActDataPS),
    %% 衣橱,放在最后,需要等所有外观系统数据初始化完毕
    WardrobePs = lib_wardrobe:login(WorkroomPs),
    %% 社团协助感谢信 和 协助记录
    guild_thank:login_load_data(Id),
    lib_role_newer:login(Id),
    mod_hi_point:login(Id),
    custom_act_journey:login(Id),
    exp_dun_wave_rank:role_login(Id, NowTime),
    %% 拼图上线检测,放在定制活动数据拉取后处理

    %% ---------------------------------------------------------
    %% 涉及属性相关或需要修改PlayerStatus的代码都放在这之前
    %% ---------------------------------------------------------
    %% 属性计算
    CountAttrPS = lib_player:count_player_attribute(WardrobePs),
    %% 开服冲榜
    lib_rush_rank_api:login(CountAttrPS),
    %% 邮件初始化
    lib_mail:init_mail_dict(Id),
    %% 排行榜 login
    lib_common_rank_api:login(CountAttrPS),
    %% 装备重新计算评分
    %equipment_api:update_equipment_score(CountAttrPS),
    %% 派发上线延后事件
    lib_player_event:async_dispatch(Id, ?EVENT_LOGIN_CAST),
    %% 游戏服活跃管理
    svr_vitality_local_mgr:online_update(Id, CountAttrPS#player_status.hightest_combat_power, NowTime),
    %% 保存在线信息
    save_online(CountAttrPS),
    if
        OnlineFlag =:= ?ONLINE_OFF_HOSTING ->
            %% 更新后台在线标识
            lib_player:update_online_flag(Id, ?ONLINE_OFF_HOSTING),
            ok;
        true ->
            %% 托管玩家不记录的数据
            real_online_do(CountAttrPS, Ip, NowTime, ConnPid)
    end,
    LoginLogType =
        case OnlineFlag of
            ?ONLINE_ON ->
                ?LOGIN_LOG_NORMAL;
            ?ONLINE_OFF_HOSTING ->
                ?LOGIN_LOG_HOSTING
        end,
    %% 上线日志
    log_online(CountAttrPS, LoginLogType),
    %% ----------------------------------------------
    %% after_login/2放在最后，登录时一般性的处理放在此注释上方
    %% 这里做一些特殊的事后处理，重登也会调用after_login/2
    %% ----------------------------------------------
    after_login(CountAttrPS, LoginSource, TaGuestId).

%% @doc
%% 登录之后处理，包括两种情形：登录和重连
%% （原设计是由前端在登录时发送12005协议来驱动reconnect逻辑的处理，
%% 没必要，且理论上存在外挂作弊不发12005直接发12002，
%% 导致服务端未处理reconnect等一系列问题）
%% 注意！！！
%%   登录时如果处于玩法中，reconnect可能会被置为?RE_LOGIN，
%%   此字段在lib_player_reconnect:reconnect/2处理过程中可能会被修改，谨慎使用
%% 注意！！！
%%   因为上线和切换场景有共同的表现，所以客户端没有分开两套逻辑来做，而是统一
%%   依靠12005的协议返回来驱动发送12002加载场景的，
%%   这里只是改的服务端的处理逻辑，正常的客户端上线还是会发12005再发12002。
%% @end
after_login(Status = #player_status{reconnect = Reconnect}, LoginSource, TaGuestId) ->
    %% 这里是正常登录和重连都会执行的区域
    ta_agent_fire:role_login(Status, LoginSource, TaGuestId),

    %% -----------------------------------------------
    %% 其他状态修改动作通常应放置于EndStatus之前
    EndStatus = Status,
    %% -----------------------------------------------
    case lib_player_reconnect:reconnect(EndStatus, Reconnect) of
        {ok, NewStatus} ->
            NewStatus;
        {no, NewStatus} ->
            NewStatus#player_status{reconnect = 0}
    end.

%% 玩家真实上线才做的事情
real_online_do(CountAttrPS, Ip, NowTime, ConnPid) ->
    #player_status{id = Id} = CountAttrPS,
    %% 角色辅助信息初始化
    lib_role:login(CountAttrPS),
    misc:register(global, misc:player_send_process_name(Id), ConnPid),
    %% 插入聊天
    sync_mod_chat_agent(CountAttrPS),
    %% 更新后台在线标识
    lib_login:update_login_data(Id, Ip, NowTime, ?ONLINE_ON),
    lib_player_event:dispatch(CountAttrPS, ?EVENT_ONLINE_FLAG, ?ONLINE_ON).

%% 已经上线了，重新登陆
re_login(Player, Socket, Ip, ConnPid, LoginSource, TaGuestId) ->
    #player_status{
        sid = OldSid
    } = Player,
    Now = utime:unixtime(),
    %% 通知客户端账户在别处登陆
    {ok, BinData} = pt_590:write(59004, 5),
    lib_server_send:send_to_sid(OldSid, BinData),
    lib_server_send:send_to_sid(OldSid, close),
    NewPlayer = Player#player_status{
        reconnect = ?RE_LOGIN,
        socket = Socket
    },
    ReLoginStatus = offline_hosting_api:re_login(NewPlayer),
    DunMultiStatus = lib_dun_multi:re_login(ReLoginStatus),
	lib_treasure_map:relogin(DunMultiStatus),
    lib_exp_dun_drop:re_login(DunMultiStatus, Now),
    %% 如果是正常在线被顶号，重新统计一下在线时间
    case DunMultiStatus#player_status.online of
        ?ONLINE_ON ->
            welfare_api:re_calc_online_time(DunMultiStatus);
        _ ->
            ignore
    end,
    NewStatus = DunMultiStatus#player_status{
        online = ?ONLINE_ON,
        ip = util:ip2bin(Ip),
        last_login_time = Now,
        sid = ConnPid
        },
    save_online(NewStatus),
    real_online_do(NewStatus, Ip, Now, ConnPid),
    log_online(NewStatus, ?LOGIN_LOG_RE),
    AfterStatus = after_login(NewStatus, LoginSource, TaGuestId),
    %% RE_LOGIN后面上线检测12005切换场景还要用
    AfterStatus#player_status{reconnect = ?RE_LOGIN}.

%% 同步更新ETS中的角色数据
save_online(PlayerStatus) ->
    ets:insert(?ETS_ONLINE,
        #ets_online{
            id  = PlayerStatus#player_status.id,
            sid = PlayerStatus#player_status.sid,
            pid = PlayerStatus#player_status.pid
        }).

%% 同步游戏性信息到mod_chat_agent服务器
sync_mod_chat_agent(PlayerStatus) ->
    mod_chat_agent:insert(
        #ets_unite{
            id = PlayerStatus#player_status.id,
            server_id = PlayerStatus#player_status.server_id,
            pid = PlayerStatus#player_status.pid,
            figure = PlayerStatus#player_status.figure,
            online = PlayerStatus#player_status.online,
            scene = PlayerStatus#player_status.scene,
            copy_id = PlayerStatus#player_status.copy_id,
            sid = PlayerStatus#player_status.sid,
            last_login_time = PlayerStatus#player_status.last_login_time,
            talk_lim = PlayerStatus#player_status.talk_lim,
            talk_lim_time = PlayerStatus#player_status.talk_lim_time,
            combat_power = PlayerStatus#player_status.combat_power
        }
    ).

log_online(CountAttrPS, LoginLogType) ->
    lib_log_api:log_login_logout(CountAttrPS#player_status.id, LoginLogType,
        CountAttrPS#player_status.figure#figure.lv,
        CountAttrPS#player_status.combat_power, CountAttrPS#player_status.scene,
        CountAttrPS#player_status.ip, CountAttrPS#player_status.network_type,
        CountAttrPS#player_status.accid, CountAttrPS#player_status.accname).

