%% ---------------------------------------------------------------------------
%% @doc lib_dungeon.erl

%% @author  hjh
%% @email  hejiahua@163.com
%% @since  2017-02-14
%% @deprecated 副本处理:玩家进程,基本函数
%% ---------------------------------------------------------------------------
-module(lib_dungeon).
-export([
        enter_dungeon/2                     %% 进入副本
        ,enter_dungeon/3
        ,pull_into_dungeon/2                %% 进入具体逻辑
        ,trans_to_dungeon_role/3
        ,dun_multi_member_to_dun_dummy/1
        ,reconnect/2                        %% 重连
        ,active_quit/1                      %% 主动退出副本
        ,immediate_quit_dungeon/2           %% 退出副本处理
        ,role_out_info_update/2             %% 退出副本更新信息
        ,update_status_dungeon/2            %% 更新人物副本信息

        ,get_revive_xy/1                    %% 获取副本复活点
        ,get_max_help_count/1               %% 获取副本最大助战次数

        ,update_dungeon_record/2            %% 更新副本历史记录
        ,load_dungeon_record/1              %% 加载副本历史记录
        ,save_dungeon_record/3              %% 保存副本历史记录

        ,pick_all/1                         %% 拾取掉落
        ,handle_task_trigger/2              %% 副本任务触发
        ,create_dungeon_infos/2             %% 获取副本信息
        ,send_dungeon_info/3                %% 推送副本信息
        ,daily_reset/2                      %% 副本日常重置
        ,send_dummy_reward/4                %% 发送假人镜像奖励
        ,init_dungeon_record/2
    ]).

-export([
        is_on_dungeon/1                     %% 是否在副本中
        ,is_common_revive_type/1            %% 是否是通用复活类型
        ,check_collect/2                    %% 是否可采集
        ,check_revive_on_dungeon/2          %% 检查复活
        ,check_revive_cost/1
        ,is_cluster_dungeon_by_dun_id/1
    ]).

-compile(export_all).

-include("server.hrl").
-include("figure.hrl").
-include("dungeon.hrl").
-include("team.hrl").
-include("scene.hrl").
-include("def_module.hrl").
-include("def_daily.hrl").
-include("common.hrl").
-include("predefine.hrl").
-include("attr.hrl").
-include("errcode.hrl").
-include("def_fun.hrl").
-include("def_event.hrl").
-include("rec_event.hrl").
-include("relationship.hrl").
-include("language.hrl").
-include("drop.hrl").
-include("goods.hrl").
-include("def_vip.hrl").
-include("buff.hrl").
-include("daily.hrl").
-include("dungeon_multi.hrl").
-include("custom_act.hrl").
-include("def_goods.hrl").
-include("task.hrl").
-include("dun_guard.hrl").
-include("guild.hrl").
-include("vip.hrl").

%% -------------------------------------------------------------
%% @副本触发相关
%% 1.进入触发  lib_dungeon:enter_dungeon_trigger/4
%% 2.完成触发  副本成功通关回调lib_dungeon_api:handle_event/2
%% 3.通用扫荡触发   lib_dungeon_sweep:sweep_dungeon_trigger/3
%% 4.特殊副本扫荡触发 宝宝副本走特殊逻辑,其他特殊副本触发都会调用
%% lib_dungeon_sweep:sweep_dungeon_trigger/3
%%
%% handle_task_trigger 执行的是副本类任务的触发,一般触发
%% 不建议放在这里
%% -------------------------------------------------------------

%% 玩家信息转化成副本相关信息
trans_to_dungeon_role(Player, Dun, OpType) when is_record(Player, player_status) ->
    #player_status{
        id = Id, figure = Figure, pid = Pid, sid = Sid, scene = Scene, server_id = ServerId,
        scene_pool_id = ScenePoolId, copy_id = CopyId, x = X, y = Y, server_num = ServerNum,
        combat_power = CombatPower, battle_attr = #battle_attr{hp_lim = HpLim, attr = Attr},
        dungeon = #status_dungeon{dun_data = DunDatas, out = Out = #dungeon_out{is_again = IsAgain} },
        dungeon_record = DungeonRecs } = Player,
    #dun_cfg{id = DunId, type = DunType, count_cond = CountConditions} = Dun,
    case IsAgain == 1 of
        true -> #dungeon_out{scene = OutScene, scene_pool_id = OutScenePoolId, copy_id = OutCopyId, x = OutX, y = OutY} = Out;
        false -> OutScene = Scene, OutScenePoolId = ScenePoolId, OutCopyId = CopyId, OutX = X, OutY = Y
    end,
    NewHpLim = HpLim,
    DunData  = case lists:keyfind(DunType, 1, DunDatas) of
        {_, DunData0} -> DunData0;
        _ -> []
    end,
    Node     = clusters_node:get_node(),
    AttrList = attr_conv:to_attr_tuple_list(Attr),
    BaseAttrList = lib_player_attr:get_list_base_attr_kv(AttrList),
    if DunType == ?DUNGEON_TYPE_EXGOD -> IsFirst = true;
        true -> IsFirst = lib_dungeon_api:check_ever_finish(Player, DunId)
    end,
    DungeonRec   = maps:get(DunId, DungeonRecs, []),
    TypicalData  = lib_dungeon_distribute:get_role_rec(DunType, DunId, Id, DungeonRec, DunData, #{}),
    %% 判断此次副本是常规次数还是消耗购买次数
    TypicalData1 = calc_dun_count_cost(Player, Dun, TypicalData),
    TypicalData2 =
    if DunType == ?DUNGEON_TYPE_MATERIAL ->
            case custom_unite_buff:check_buff(Player, dungeon_double) of
                {ok, Num} -> maps:put(?DUN_ROLE_MAT_REWARD_BUFF, Num, TypicalData1);
                _         -> maps:put(?DUN_ROLE_MAT_REWARD_BUFF, 0, TypicalData1)
            end;
        DunType == ?DUNGEON_TYPE_PET ->
            lib_dungeon_pet:before_enter_dungeon(Player, DunId, TypicalData1);
        DunType == ?DUNGEON_TYPE_PER_BOSS ->
            lib_dungeon_personal_boss:get_role_typical_data(Id, DunId, DunType, OpType, CountConditions, TypicalData1);
        true ->
            TypicalData1
    end,
    #dungeon_role{
        id = Id
        , node = Node
        , server_id = ServerId
        , server_num = ServerNum
        , figure = Figure
        , combat_power = CombatPower
        , hp = NewHpLim
        , hp_lim = NewHpLim
        , base_attr = BaseAttrList
        , pid = Pid
        , sid = Sid
        , scene = OutScene
        , scene_pool_id = OutScenePoolId
        , copy_id = OutCopyId
        , x = OutX
        , y = OutY
        , is_first = IsFirst
        , typical_data = TypicalData2
    };
trans_to_dungeon_role(_Player, _Dun, _) ->
    #dungeon_role{}.

%% 队伍假人转换为副本假人
dun_multi_member_to_dun_dummy(DummyList) ->
    lists:foldl(fun(DummyMember, Acc) ->
        #dun_multi_member{
            id = Id, figure = Figure, is_dummy = DummyType,
            server_id = ServId, server_num = ServerNum
        } = DummyMember,
        MirrorId = ?IF(DummyType == ?DUN_MIRROR, Id, 0),
        DungeonDummy = #dungeon_dummy{
            id = Id, mirror_id = MirrorId, dummy_type = DummyType,
            figure = Figure, server_id = ServId, server_num = ServerNum
        },
        [DungeonDummy|Acc]
    end, [], DummyList).


%% 协议进入副本
enter_dungeon(Player, DunId) ->
    enter_dungeon(Player, DunId, 0).

enter_dungeon(Player, DunId, OpType) ->
    enter_dungeon(Player, DunId, OpType, create).

%% 进入副本
enter_dungeon(Player, DunId, OpType, DunPid) ->
    % Player = lib_dungeon_team:init_help_type(Player0, DunId),
    #player_status{sid = Sid} = Player,
    Player1 = load_dungeon_record(Player),
    case data_dungeon:get_dun(DunId) of
        [] ->
            {ok, BinData} = pt_610:write(61001, [DunId, 0, ?ERRCODE(err610_dungeon_not_exist), [], OpType]),
            lib_server_send:send_to_sid(Sid, BinData),
            {ok, Player1};
        #dun_cfg{type = DunType, scene_id = SceneId} = Dun ->
            case data_scene:get(SceneId) of
                [] ->
                    {ok, BinData} = pt_610:write(61001, [DunId, SceneId, ?ERRCODE(err610_dungeon_scene_not_exist), [], OpType]),
                    lib_server_send:send_to_sid(Sid, BinData),
                    {ok, Player1};
                _Scene ->
                    case enter_dungeon2(Player1, Dun, OpType, DunPid) of
                        {false, ErrorCode} ->
                            {ErrorCodeInt, ErrorCodeArgs} = util:parse_error_code(ErrorCode),
                            {ok, BinData} = pt_610:write(61001, [DunId, SceneId, ErrorCodeInt, ErrorCodeArgs, OpType]),
                            lib_server_send:send_to_sid(Sid, BinData),
                            {ok, Player1};
                        {ok, NewPlayer} ->
                            {ok, LastPlayer} = lib_limit_goal_api:trigger(NewPlayer, {dungeon_times, DunType}),
                            %% 孩子任务
                            LastPlayer1  = lib_child_task:finish_dungeon(LastPlayer, DunType, DunId, 1),
                            {ok, LastPlayer1}
                    end
            end
    end.


%% @doc 检查进入副本条件
%% @return {ok, NewPlayer} | {false, ErrorCode}
%%  ErrorCode : integer() | {integer(), Args} 使用 util:parse_error_code/1 解析
enter_dungeon2(Player, Dun, OpType, DunPid) ->
    % NowScene = Player#player_status.scene,
    case check_enter_dungeon(Player, Dun, OpType) of
        {false, ErrorCode} ->
            {false, ErrorCode};
        true->
            case enter_dungeon_done(Player, Dun, OpType, DunPid) of
                {false, ErrorCode} ->
                    {false, ErrorCode};
                _ ->
                    {ok, Player}
            end
    end.

check_enter_dungeon(Player, Dun, OpType) ->
    #player_status{scene = SceneId} = Player,
    IsOnDungeon = is_on_dungeon(Player),
    IsGuildScene = lib_guild:is_guild_scene(SceneId),
    if
        IsOnDungeon andalso (Dun#dun_cfg.type =/= ?DUNGEON_TYPE_TOWER andalso Dun#dun_cfg.type =/= ?DUNGEON_TYPE_VISUS
            andalso Dun#dun_cfg.type =/= ?DUNGEON_TYPE_PET andalso Dun#dun_cfg.type =/= ?DUNGEON_TYPE_GALAXY) ->
            {false, ?ERRCODE(err610_had_on_dungeon)};
        IsGuildScene ->
            {false, ?ERRCODE(err400_pls_exit_guild_scene)};
        true ->
            execute_check([
                {fun check_other_conditions/3, [Player, Dun, OpType]},
                {fun check_dungeon_time_open/1, [Dun]},
                {fun check_dungeon_condition/3, [Dun#dun_cfg.condition, Dun, Player]},
                {fun check_dungeon_count_cond/3, [Dun#dun_cfg.count_cond, Dun, Player]},
                {fun check_cost/2, [Player, Dun]},
                {fun check_extra/2, [Player, Dun]},
                {fun check_max_wave/2, [Player, Dun]}
            ])
    end.

execute_check([{Fun, Args}|T]) ->
    case erlang:apply(Fun, Args) of
        true ->
            execute_check(T);
    Error ->
            % ?ERR("~p ~p ~p", [erlang:fun_info(Fun, name), Error, length(T)]),
            Error
    end;
execute_check([]) -> true.

check_cost(Player, Dun) ->
    case lib_dungeon_distribute:calc_cost(Player, Dun) of
        [_|_] = Cost->
            Result = lib_goods_api:check_object_list(Player, Cost),
            case Result of
                true ->
                    true;
                _ ->
                    CostName = lib_goods_api:get_first_object_name(Cost),
                    case Dun#dun_cfg.type of
                        ?DUNGEON_TYPE_EXP ->
                            MyErrorCode = ?ERRCODE(err240_my_cost_error_exp);
                        _ ->
                            MyErrorCode = {?ERRCODE(what_not_enough), [CostName]}
                    end,
                    {false, MyErrorCode}
            end;
        {false, ErrorCode} ->
            {false, ErrorCode};
        _ ->
            true
    end.

check_other_conditions(Player, Dun, OpType) ->
    #player_status{dungeon = StatusDun, scene = OldScene} = Player,
    #status_dungeon{dun_data = DunData} = StatusDun,
    #dun_cfg{id = DunId, type = DunType, scene_id = SceneId, born_type = BornType} = Dun,
    if BornType == 1 ->
            CheckList = [alive, outside, action_free, dun_multi_match];
        true ->
            CheckList = [alive, safe_scene, action_free, dun_multi_match]
    end,
    case DunType of
        ?DUNGEON_TYPE_TOWER ->
            case lib_player_check:check_list(Player, CheckList) of
                {false, 1014} -> %% ?ERRCODE(cannot_transferable_scene)
                    if SceneId == OldScene -> true;
                        true ->
                            case data_scene:get(OldScene) of
                                #ets_scene{type = ?SCENE_TYPE_DUNGEON} ->
                                    case data_tower_dun:get_tower_prev_dun(DunId) of
                                        [PrevDunId|_] ->
                                            case data_dungeon:get_dun(PrevDunId) of
                                                #dun_cfg{scene_id = OldScene} -> true;
                                                _ -> {false, ?ERRCODE(cannot_transferable_scene)}
                                            end;
                                        _ -> {false, ?ERRCODE(cannot_transferable_scene)}
                                    end;
                                _ -> {false, ?ERRCODE(cannot_transferable_scene)}
                            end
                    end;
                Result2 -> Result2
            end;
        ?DUNGEON_TYPE_VISUS ->
            case lib_player_check:check_list(Player, CheckList) of
                {false, 1014} -> %% ?ERRCODE(cannot_transferable_scene)
                    case data_dungeon:get_type_by_scene(OldScene) of
                        [DunType|_] -> true;
                        _ -> {false, ?ERRCODE(cannot_transferable_scene)}
                    end;
                Result2 -> Result2
            end;
        ?DUNGEON_TYPE_PET ->
            case lib_player_check:check_list(Player, CheckList) of
                {false, 1014} -> %% ?ERRCODE(cannot_transferable_scene)
                    if SceneId == OldScene ->
                            true;
                        true ->
                            %% 必须要上一关顺利通关
                            case lists:keyfind(?DUNGEON_TYPE_PET, 1, DunData) of
                                {?DUNGEON_TYPE_PET, [NextDunId]} ->
                                    case NextDunId >= DunId of
                                        true -> true;
                                        _ ->  {false, ?ERRCODE(err610_dun_pet_pre_must)}
                                    end;
                                _ ->
                                    FirstDunId = hd(data_dungeon_pet:get_all_dun_pet_cfg()),
                                    case DunId == FirstDunId of
                                        true ->
                                            true;
                                        _ ->
                                            {false, ?ERRCODE(cannot_transferable_scene)}
                                    end
                            end
                    end;
                _ ->
                    %% 必须要上一关顺利通关
                    case lists:keyfind(?DUNGEON_TYPE_PET, 1, DunData) of
                        {?DUNGEON_TYPE_PET, [NextDunId]} ->
                            case NextDunId >= DunId of
                                true -> true;
                                _    -> {false, ?ERRCODE(err610_dun_pet_pre_must)}
                            end;
                        _ ->
                            FirstDunId = hd(data_dungeon_pet:get_all_dun_pet_cfg()),
                            case DunId == FirstDunId of
                                true ->
                                    true;
                                _ ->
                                    {false, ?ERRCODE(err610_dun_pet_pre_must)}
                            end
                    end
            end;
        ?DUNGEON_TYPE_PER_BOSS ->
            case lib_player_check:check_list(Player, CheckList) of
                {false, Err} when Err == 1014 orelse Err == 4600000 ->
                    case local_boss_api:is_deserted_boss_scene(OldScene) of
                        false ->
                            lib_dungeon_personal_boss:check_merge_count_condition(OpType, DunType, DunId, Player);
                        _ ->
                            {false, ?ERRCODE(err461_pls_quit_scene)}
                    end;
                Result2 ->
                    Result2
            end;
        ?DUNGEON_TYPE_GALAXY ->
            case lib_player_check:check_list(Player, CheckList) of
                {false, 1014} -> %% ?ERRCODE(cannot_transferable_scene)
                    if SceneId == OldScene -> true;
                        true ->
                            case data_galaxy_dun:get_galaxy_prev_dun(DunId) of
                                PrevDunId when is_integer(PrevDunId) ->
                                    case data_dungeon:get_dun(PrevDunId) of
                                        #dun_cfg{scene_id = OldScene} -> true;
                                        _ -> {false, ?ERRCODE(cannot_transferable_scene)}
                                    end;
                                [] -> {false, ?ERRCODE(cannot_transferable_scene)}
                            end
                    end;
                Result2 -> Result2
            end;
        ?DUNGEON_TYPE_ASSESS ->
            lib_dungeon_elite_assess:check_can_enter(Player, DunId);
        _ ->
            lib_player_check:check_list(Player, CheckList)
    end.

%% 检查次数
%% CheckType :: default 正常检查|create_team 创队检查|join_team 入队检查
check_dungeon_count_cond(CountCond, Dun, Player) ->
    HelpType  = ?DUN_TEAM_HELP_NO,
    CheckType = default,
    check_dungeon_count_cond(CountCond, Dun, HelpType, CheckType, Player).

-spec check_dungeon_count_cond(CountCond, Dun, HelpType, CheckType, Player) -> ReturnType when
    CountCond :: list(),
    Dun :: #dun_cfg{},
    HelpType :: ?DUN_TEAM_HELP_NO | ?DUN_TEAM_HELP_YES,
    CheckType :: default | create_team | join_team | atom(),
    Player :: #player_status{},
    ReturnType :: boolean() | {boolean(), integer()}.
check_dungeon_count_cond(CountCond, Dun, HelpType, CheckType, Player) ->
    %% 首先检查是否首次不扣次数
    case is_first_free(Dun, Player#player_status.id) of
        true  ->
            true;
        false ->
            do_check_dungeon_count_cond(CountCond, Dun, HelpType, CheckType, Player)
    end.

do_check_dungeon_count_cond([], _Dun, _HelpType, _CheckType, _Player) -> true;
do_check_dungeon_count_cond([H|T], #dun_cfg{id = DunId, type = DunType} = Dun, _HelpType, CheckType, Player) ->
    #player_status{id = RoleId} = Player,
    case H of
        {?DUN_COUNT_COND_DAILY, MaxNum} ->
            MaxNum1 = 
            if DunType == ?DUNGEON_TYPE_CRUSADE ->
                    lib_dun_multi:calc_crusade_max_num(MaxNum, Player#player_status.figure#figure.lv);
                true -> MaxNum
            end,
            if
                CheckType == default orelse CheckType == create_team orelse
                 (DunType == ?DUNGEON_TYPE_CRUSADE andalso CheckType == join_team) ->
                    case check_daily_count(Player, Dun, MaxNum1) of
                        {false, AllCountLimit} ->
                            {false, {?ERRCODE(err610_dungeon_count_daily), [AllCountLimit]}};
                        true ->
                            do_check_dungeon_count_cond(T, Dun, _HelpType, CheckType, Player)
                    end;
                true ->
                    do_check_dungeon_count_cond(T, Dun, _HelpType, CheckType, Player)
            end;
        {?DUN_COUNT_COND_WEEK, MaxNum} ->
            Num = lib_week:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunId),
            case Num < MaxNum of
                true -> do_check_dungeon_count_cond(T, Dun, _HelpType, CheckType, Player);
                false -> {false, {?ERRCODE(err610_dungeon_count_week), [MaxNum]}}
            end;
        {?DUN_COUNT_COND_PERMANENT, MaxNum} ->
            Num = lib_counter:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunId),
            case Num < MaxNum of
                true -> do_check_dungeon_count_cond(T, Dun, _HelpType, CheckType, Player);
                false -> {false, {?ERRCODE(err610_dungeon_count_permanent), [MaxNum]}}
            end;
        {?DUN_COUNT_COND_DAILY_HELP, MaxNum} ->
            if
                CheckType == default orelse CheckType == join_team ->
                    CountType = lib_dungeon_api:get_daily_help_type(DunType, DunId),
                    Num = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_HELP, CountType),
                    if
                        Num >= MaxNum ->
                            {false, {?ERRCODE(err610_dungeon_count_daily_help), [MaxNum]}};
                        true ->
                            do_check_dungeon_count_cond(T, Dun, _HelpType, CheckType, Player)
                    end;
                true ->
                    do_check_dungeon_count_cond(T, Dun, _HelpType, CheckType, Player)
            end;
        {?DUN_COUNT_COND_DAILY_REWARD, _MaxNum} ->
            do_check_dungeon_count_cond(T, Dun, _HelpType, CheckType, Player); %% 可以进入但是不一定能领奖
        _ ->
            {false, ?ERRCODE(err610_dungeon_count_cfg_error)}
    end.

check_daily_count(Player, Dun, MaxNum) ->
    #player_status{id = RoleId, figure = #figure{vip = VipLv}, vip_status = VipStatus} = Player,
    #dun_cfg{id = DunId, type = DunType, buy_count_cost = BuyCost} = Dun,
    CountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
    SupVipType = lib_sup_vip:sup_vip_type(VipStatus),
    CountList = [
        {?MOD_DUNGEON, ?MOD_DUNGEON_BUY, CountType},
        {?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, CountType},
        {?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, CountType},
        {?MOD_DUNGEON, ?MOD_DUNGEON_GOLD_BUY, CountType}
    ],
    case lib_daily:get_count(RoleId, CountList) of
        [{_, BuyCount}, {_, Num}, {_, BuyUseCount}, {_, GoldBuy}] -> ok;
        _ -> BuyCount = Num = BuyUseCount = GoldBuy = 0
    end,
    UniteNum = custom_unite_buff:get_unite_num(Player,DunType),
    AllCountLimit = MaxNum + UniteNum,
    if
        Num >= AllCountLimit -> %% 正常次数已用完,接着判断购买次数
            if BuyUseCount < BuyCount -> %% 还有购买次数未使用
                    true;
                true -> %% 购买次数已用尽
                    Ret =
                    if DunType == ?DUNGEON_TYPE_MATERIAL andalso GoldBuy >= BuyCount ->
                            calc_buy_count_cost(BuyCost, 0, 1, VipLv);
                        DunType == ?DUNGEON_TYPE_MATERIAL ->
                            calc_buy_count_cost(BuyCost, BuyCount, 1, VipLv);
                        true ->
                            false
                    end,
                    case Ret of
                        [] -> %% 已达最大购买次数或配置有问题
                            {false, AllCountLimit};
                        false -> %% VIP不足
                            {false, AllCountLimit};
                        Cost -> %% 检测需要购买的次数的消耗是否满足
                            if
                                DunType == ?DUNGEON_TYPE_MATERIAL andalso SupVipType > ?NO_SUP_VIP andalso
                                 GoldBuy >= BuyCount -> %% 贵族第一次免费
                                    true;
                                SupVipType > ?NO_SUP_VIP andalso BuyCount == 0 -> %% 贵族第一次免费
                                    true;
                                true ->
                                    case lib_goods_api:check_object_list(Player, Cost) of
                                        true ->
                                            true;
                                        {false, ErrorCode} ->
                                            {false, ErrorCode}
                                    end
                            end
                    end
            end;
        true ->
            true
    end.


%% 检查副本条件
check_dungeon_condition([], _Dun, _Player) -> true;
check_dungeon_condition([T|L], Dun, Player) ->
    #player_status{id = RoleId, figure = #figure{lv = Lv,vip=Vip}, dungeon_record = DungeonRecs} = Player,
    #dun_cfg{id = DunId, type = DunType, count_deduct = CountDeduct} = Dun,
    case T of
        {num, 1, 1} -> check_dungeon_condition(L, Dun, Player);
        {num, _Min, _Max} ->
            check_dungeon_condition(L, Dun, Player);
        {lv, NeedLv} ->
            case Lv >= NeedLv of
                true -> check_dungeon_condition(L, Dun, Player);
                false -> {false, {?ERRCODE(err610_lv_limit), [NeedLv]}}
            end;
        {taskid, TaskId} ->
            case lib_role_task:is_finish_task_id(TaskId) of
                true ->
                    check_dungeon_condition(L, Dun, Player);
                _ ->
                    case data_task:get(TaskId) of
                        #task{level = Level} -> ok;
                        _ ->
                            case lists:keyfind(lv, 1, Dun#dun_cfg.condition) of
                                {_, Level} -> Level;
                                _          -> Level = 0
                            end
                    end,
                    {false, {?ERRCODE(err610_lv_task_limit), [Level]}}
            end;
        {vip,NeedVip}->
            case Vip >= NeedVip of
                true -> check_dungeon_condition(L, Dun, Player);
                false -> {false, ?ERRCODE(err610_not_enough_vip_to_enter_dungeon)}
            end;
        {finish_dun_id, FinishDunId} ->
            if DunType == ?DUNGEON_TYPE_GUARD ->
                    DunRec = maps:get(FinishDunId, DungeonRecs, []),
                    case lists:keyfind(?DUNGEON_REC_MAX_SCORE, 1, DunRec) of
                        {_, MaxWave} ->
                            CfgMaxWave = lists:max(data_dungeon_wave:get_dun_waves(FinishDunId)),
                            if CfgMaxWave =< MaxWave ->
                                    check_dungeon_condition(L, Dun, Player);
                                true ->
                                    {false, ?ERRCODE(err610_need_finish_dun_id)}
                            end;
                        _ ->
                            {false, ?ERRCODE(err610_need_finish_dun_id)}
                    end;
                true ->
                    Count = lib_counter:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_SUCCESS, FinishDunId),
                    case Count > 0 of
                        true    -> check_dungeon_condition(L, Dun, Player);
                        false   -> {false, ?ERRCODE(err610_need_finish_dun_id)}
                    end
            end;
        {just_once_finish} ->
            SubModuleId = if CountDeduct =:= ?DUN_COUNT_DEDUCT_SUCCESS -> ?MOD_DUNGEON_ENTER; true -> ?MOD_DUNGEON_SUCCESS end,
            Count = lib_counter:get_count(RoleId, ?MOD_DUNGEON, SubModuleId, DunId),
            case Count == 0 of
                true -> check_dungeon_condition(L, Dun, Player);
                false -> {false, ?ERRCODE(err610_just_once_finish)}
            end;
        {today_finish_dun, FinishDunId} ->
            SubModuleId = if CountDeduct =:= ?DUN_COUNT_DEDUCT_SUCCESS -> ?MOD_DUNGEON_ENTER; true -> ?MOD_DUNGEON_SUCCESS end,
            Count = lib_daily:get_count(RoleId, ?MOD_DUNGEON, SubModuleId, FinishDunId),
            case Count > 0 of
                true -> check_dungeon_condition(L, Dun, Player);
                false -> {false, ?ERRCODE(err610_need_finish_dun_id)}
            end;
        % {safe_scene}、{enter_scene, _SceneIdList}、{enter_scene2, _SceneXyList} 任意满足就可以
        {safe_scene} ->
            case check_scene(Player, Dun) of
                true -> check_dungeon_condition(L, Dun, Player);
                {false, ErrorCode} -> {false, ErrorCode}
            end;
        {enter_scene, _SceneIdList} ->
            case check_scene(Player, Dun) of
                true -> check_dungeon_condition(L, Dun, Player);
                {false, ErrorCode} -> {false, ErrorCode}
            end;
        {enter_scene2, _SceneXyList} ->
            case check_scene(Player, Dun) of
                true -> check_dungeon_condition(L, Dun, Player);
                {false, ErrorCode} -> {false, ErrorCode}
            end;
        % 不判断
        {success_out_scene, _SceneId, _X, _Y} -> check_dungeon_condition(L, Dun, Player);
        {any_out_scene, _SceneId, _X, _Y} -> check_dungeon_condition(L, Dun, Player);
        {relationship, _} -> check_dungeon_condition(L, Dun, Player);
        {legend_lv, NeedLv} ->
            LegendLv = lib_player:get_legend_lv(Player),
            case LegendLv>= NeedLv of
                true-> check_dungeon_condition(L, Dun, Player);
                false-> {false, ?ERRCODE(err610_legend_lv_not_enough)}
            end;
        {couple_intimacy, _} ->
            check_dungeon_condition(L, Dun, Player);
        {floor, _, _} ->
            check_dungeon_condition(L, Dun, Player);
        {open_day, CfgOpenDay} ->
            OpenDay = util:get_open_day(),
            if  
                OpenDay >= CfgOpenDay ->
                    check_dungeon_condition(L, Dun, Player);
                true ->
                    {false, ?ERRCODE(err610_open_day_error)}
            end;
        _A ->
            {false, ?ERRCODE(err610_dungeon_count_cfg_error)}
    end.

%% 开启时间检查
%% 检查副本是否开启
check_dungeon_time_open(Dun) ->
    case Dun of
        #dun_cfg{type = ?DUNGEON_TYPE_EXGOD} ->
            [GodBegin] = data_annihilate:get_annihilate_kv(god_begin),
            [GodEnd]   = data_annihilate:get_annihilate_kv(god_end),
            OpenDay = util:get_open_day(),
            if OpenDay >= GodBegin andalso OpenDay =< GodEnd ->
                    true;
                true -> {false, ?ERRCODE(err610_open_day_error)}
            end;
        _ -> true
    end.

%% 根据副本类型进行特殊处理
check_extra(Player, Dun) ->
    lib_dungeon_distribute:check_extra(Player, Dun).

%% 是否达到最大波数
check_max_wave(Player, Dun) ->
    #player_status{dungeon_record = DungeonRecs} = Player,
    #dun_cfg{type = DunType, id = DunId} = Dun,
    CfgWaveList   = case data_dungeon_wave:get_dun_waves(DunId) of [] -> [1]; WaveL -> WaveL end,
    CfgDunMaxWave = lists:max(CfgWaveList),
    CheckTypeList = [?DUNGEON_TYPE_CHILD],
    case lists:member(DunType, CheckTypeList) of
        true ->
            DunRecord = maps:get(DunId, DungeonRecs, []),
            case lists:keyfind(?DUNGEON_REC_MAX_SCORE, 1, DunRecord) of
                %% 波数小于原先波数不修改
                {_, MaxWave} when MaxWave >= CfgDunMaxWave ->
                    {false, ?ERRCODE(err610_max_wave)};
                _ ->
                    true
            end;
        _ ->
            true
    end.

%% 检查场景
%% @return {false, ErrorCode} | true
check_scene(Player, Dun) ->
    #player_status{scene = SceneId, x = X, y = Y} = Player,
    #dun_cfg{condition = Condition} = Dun,
    CheckSafeScene = case lists:keyfind(safe_scene, 1, Condition) of
        false -> false;
        {safe_scene} ->
            case skip_check_safe_scene(Player, Dun) of
                true -> true;
                false ->
                    case lib_scene:is_transferable(Player) of
                        {true, _Success} -> true;
                        {false, ErrorCode} -> {false, ErrorCode}
                    end
            end
    end,
    CheckEnterScene = case lists:keyfind(enter_scene, 1, Condition) of
        false -> false;
        {enter_scene, SceneIdList} ->
            case lists:member(SceneId, SceneIdList) of
                true -> true;
                false -> {false, ?ERRCODE(err610_not_on_enter_scene)}
            end
    end,
    CheckEnterScene2 = case lists:keyfind(enter_scene2, 1, Condition) of
        false -> false;
        {enter_scene2, SceneXyList} ->
            F = fun({TmpSceneId, TmpX, TmpY, TmpXrange, TmpYrange}) ->
                IsRightX = (X >= TmpX - TmpXrange) andalso (X =< TmpX + TmpXrange),
                IsRightY = (Y >= TmpY - TmpYrange) andalso (Y =< TmpY + TmpYrange),
                SceneId == TmpSceneId andalso IsRightX andalso IsRightY
            end,
            case lists:any(F, SceneXyList) of
                true -> true;
                false -> {false, ?ERRCODE(err610_not_on_enter_scene)}
            end
    end,
    SceneOfBattle = ?ERRCODE(err120_cannot_transfer_on_battle),
    if
        % 战斗状态中不能传送
        CheckSafeScene == {false, SceneOfBattle} -> CheckSafeScene;
        CheckSafeScene == true orelse CheckEnterScene == true orelse CheckEnterScene2 == true -> true;
        CheckSafeScene =/= false -> CheckSafeScene;
        CheckEnterScene =/= false -> CheckEnterScene;
        CheckEnterScene2 =/= false -> CheckEnterScene2;
        % 必须要填进入条件,容错
        true -> {false, ?ERRCODE(err610_not_on_safe_scene_to_enter_dungeon)}
    end.

% 转换错误码
trans_error_code(ErrorCode, Player) ->
    #player_status{figure = #figure{name = Name}} = Player,
    SceneOfTransfer = ?ERRCODE(err120_cannot_transfer_scene),
    SceneOfBattle = ?ERRCODE(err120_cannot_transfer_on_battle),
    DunOfEnter = ?ERRCODE(err610_not_on_enter_scene),
    DunOfSafe = ?ERRCODE(err610_not_on_safe_scene_to_enter_dungeon),
    case ErrorCode of
        SceneOfTransfer ->
            {
                {?ERRCODE(err610_not_on_safe_scene_to_enter_dungeon_by_other), [Name]}
                , ?ERRCODE(err610_not_on_safe_scene_to_enter_dungeon)
            };
        SceneOfBattle ->
            {
                {?ERRCODE(err610_cannot_join_act_on_battle_with_name), [Name]}
                , ?ERRCODE(err610_cannot_join_act_on_battle)
            };
        DunOfEnter ->
            {
                {?ERRCODE(err610_not_on_enter_scene_by_other), [Name]}
                , ?ERRCODE(err610_not_on_enter_scene)
            };
        DunOfSafe ->
            {
                {?ERRCODE(err610_not_on_safe_scene_to_enter_dungeon_by_other), [Name]}
                , ?ERRCODE(err610_not_on_safe_scene_to_enter_dungeon)
            };
        _ ->
            {
                ErrorCode
                , ErrorCode
            }
    end.

%% 跳过安全区域检查
%% 玩家不在副本中,玩家处于副本场景中
skip_check_safe_scene(Player, _Dun) ->
    case is_on_dungeon(Player) of
        true -> false;
        false ->
            #player_status{scene = SceneId} = Player,
            case data_scene:get(SceneId) of
                #ets_scene{type = ?SCENE_TYPE_DUNGEON} -> true;
                _ -> false
            end
    end.

%% 进入单人副本处理
enter_dungeon_done(Player, #dun_cfg{id = DunId, type = DunType} = Dun, OptionType, DunPid) ->
    % 单人副本或者队伍进程不存在
    IsDungeonSingle = is_dungeon_single(Dun#dun_cfg.condition),
    if
        IsDungeonSingle ->
            case misc:is_process_alive(DunPid) of
                false when DunPid =:= create->
                    DungeonRole = trans_to_dungeon_role(Player, Dun, OptionType),
                    case is_on_dungeon(Player) of
                        true when (DunType == ?DUNGEON_TYPE_TOWER orelse DunType == ?DUNGEON_TYPE_VISUS orelse DunType == ?DUNGEON_TYPE_PET
                            orelse DunType == ?DUNGEON_TYPE_GALAXY)
                            andalso OptionType == 1 ->
                            mod_dungeon:enter_next_dun(Player#player_status.copy_id, [DungeonRole, Dun#dun_cfg.type, DunId]);
                        _ ->
                            StartDunArgs = lib_dungeon_distribute:get_start_dun_args(Player, DungeonRole, Dun, OptionType),
                            _Pid = mod_dungeon:start(0, self(), DunId, [DungeonRole], StartDunArgs)
                    end;
                _ ->
                    {false, ?FAIL}
            end;
        true ->
                {false, ?FAIL}
    end.

%% 检查扣除待购买的次数消耗
dungeon_cost(Player, DunId, _CountCostType, CostList) ->
    if
        CostList =/= [] ->
            case lib_goods_api:cost_object_list_with_check(Player, CostList, dungeon_count_buy_cost, integer_to_list(DunId)) of
                {true, Player1, _} ->
                    {true, Player1};
                {false, _ErrorCode, _} ->
                    ?ERR("dun cost error ~p", [_ErrorCode]),
                    {true, Player}
            end;
        true ->
            {true, Player}
    end.

is_dungeon_single(DunId) when is_integer(DunId) ->
    case data_dungeon:get_dun(DunId) of
        #dun_cfg{condition = Condition} ->
            is_dungeon_single(Condition);
        _ ->
            false
    end;
%% 是否单人副本
is_dungeon_single(Condition) ->
    case lists:keyfind(num, 1, Condition) of
        {num, 1, 1} -> true;
        _ -> false
    end.

is_first_free(#dun_cfg{type = Type, id = DunId}, RoleId) ->
    %% 进入副本后马上记录首次进入,即不管是否成功通关
    case lists:member(Type, [?DUNGEON_TYPE_PER_BOSS]) of
        true ->
            lib_counter:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_FIRTS_ENTER, DunId) =:= 0;
        _ ->
            false
    end.

%% 扣除次数并扣除消耗道具
count_increment_and_cost_after_result(Dun, RoleId, HelpType, DeductTypeList, CostType, CostList, {PlusEnter, PlusBuy}) ->
    lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, count_increment_and_cost_after_result, [[Dun, HelpType, DeductTypeList, CostType, CostList, {PlusEnter, PlusBuy}]]).

count_increment_and_cost_after_result(Player, [Dun, _HelpType, DeductTypeList, CostType, CostList, {PlusEnter, PlusBuy}|_]) ->
    #player_status{id = RoleId} = Player,
    #dun_cfg{id = DunId, type = DunType, count_cond = CountConditions, count_deduct = CountDeduct} = Dun,
    CountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
    CountDeductCheck = lists:member(CountDeduct, DeductTypeList),
    if
        CostType == ?MOD_DUNGEON_ENTER andalso CountDeductCheck -> %% 消耗的是正常的挑战次数
            dungeon_count_plus(CountConditions, DunId, DunType, RoleId, PlusEnter);
            % [dungeon_count_increment(DunId, RoleId, HelpType, CountDeductX) || CountDeductX <- DeductTypeList];
        CostType == ?MOD_DUNGEON_BUY andalso CountDeductCheck -> %% 消耗的是待购买的次数
            gold_buy_count_increment(DunType, CountType, RoleId, PlusBuy),
            lib_daily:plus_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY, CountType, PlusBuy),
            lib_daily:plus_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, CountType, PlusBuy);
        CostType == ?MOD_DUNGEON_BUY_USE andalso CountDeductCheck -> %% 消耗的是已购买的次数
            lib_daily:plus_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, PlusBuy);
        true ->
            skip
    end,
    if
        CountDeductCheck andalso CostList =/= [] ->
        % CountDeduct == ?DUN_COUNT_DEDUCT_SUCCESS andalso CostType == ?MOD_DUNGEON_BUY_USE andalso CostList =/= [] ->
            case lib_goods_api:cost_object_list_with_check(Player, CostList, dungeon_count_buy_cost, integer_to_list(DunId)) of
                {true, Player1, _} ->
                    {ok, Player1};
                {false, _ErrorCode, _} ->
                    ok
            end;
        true ->
            ok
    end.

gold_buy_count_increment(DunType, CountType, RoleId) ->
    gold_buy_count_increment(DunType, CountType, RoleId, 1).

gold_buy_count_increment(?DUNGEON_TYPE_MATERIAL, CountType, RoleId, PlusCount) ->
    CountList = [
        {?MOD_DUNGEON, ?MOD_DUNGEON_BUY, CountType},
        {?MOD_DUNGEON, ?MOD_DUNGEON_GOLD_BUY, CountType}
    ],
    [{_, BuyCount}, {_, GoldBuy}|_] = lib_daily:get_count(RoleId, CountList),
    if GoldBuy >= BuyCount -> %% 消耗的是红钻次数
            skip;
        true -> %% 彩钻次数要额外记录
            lib_daily:plus_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_GOLD_BUY, CountType, PlusCount)
    end;
gold_buy_count_increment(_, _, _, _) ->
    skip.

%% 合并次数扣除
dungeon_merge_count_plus(DunId, RoleId, _HelpType, CountDeduct, {MergeEnterCount, MergeBuyCount}) ->
    #dun_cfg{type = DunType, count_deduct = DunCountDeduct} = Dun = data_dungeon:get_dun(DunId),
    if DunCountDeduct == CountDeduct ->
            CountType1 = lib_dungeon_api:get_daily_count_type(DunType, DunId),
            if
                MergeEnterCount > 0 andalso MergeBuyCount > 0 ->
                    lib_daily:plus_count_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, CountType1, MergeEnterCount),
                    lib_daily:plus_count_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, CountType1, MergeBuyCount);
                MergeBuyCount > 0 ->
                    lib_daily:plus_count_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, CountType1, MergeBuyCount);
                MergeEnterCount > 0 ->
                    case is_first_free(Dun, RoleId) of
                        true -> ignore;
                        _    ->
                            lib_daily:plus_count_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, CountType1, MergeEnterCount)
                    end;
                true ->
                    skip
            end;
        true ->
            ok
    end.

dungeon_count_increment(DunId, RoleId, HelpType, CountDeduct) when is_integer(DunId) ->
    dungeon_count_increment(data_dungeon:get_dun(DunId), RoleId, HelpType, CountDeduct);
dungeon_count_increment(Dun = #dun_cfg{}, RoleId, HelpType, CountDeduct) ->
    #dun_cfg{id = DunId, type = DunType, count_deduct = DunCountDeduct, count_cond = CountConditions} = Dun,
    if
        DunCountDeduct =:= CountDeduct ->
            case is_first_free(Dun, RoleId) of
                true  ->
                    ignore;
                false ->
                    dungeon_count_increment_do(CountConditions, DunId, DunType, RoleId, HelpType)
            end;
        true ->
            ok
    end;
dungeon_count_increment(_, _, _, _) ->
    skip.


%% 副本次数增加
dungeon_count_increment_do([T|L], DunId, DunType, RoleId, HelpType) ->
    % ?INFO("dungeon_count_increment DunId:~p RoleId:~p T:~p HelpType:~p CountDeduct:~p, DunCountDeduct:~p Trace:~w ~n",
    %     [DunId, RoleId, T, HelpType, CountDeduct, DunCountDeduct, tool:back_trace()]),
    case T of
        % 每日
        {?DUN_COUNT_COND_DAILY, _MaxNum} when HelpType == ?HELP_TYPE_NO ->
            CountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
            lib_daily:increment_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, CountType);
        {?DUN_COUNT_COND_DAILY_REWARD, _MaxNum} when HelpType == ?HELP_TYPE_NO ->
            CountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
            lib_daily:increment_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, CountType);
        {?DUN_COUNT_COND_WEEK, _MaxNum} ->
            lib_week:increment_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunId);
        {?DUN_COUNT_COND_PERMANENT, _MaxNum} ->
            lib_counter:increment_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunId);
        {?DUN_COUNT_COND_DAILY_HELP, _MaxNum} when HelpType == ?HELP_TYPE_YES ->
            CountType = lib_dungeon_api:get_daily_help_type(DunType, DunId),
            lib_daily:increment_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_HELP, CountType);
        _ -> skip
    end,
    dungeon_count_increment_do(L, DunId, DunType, RoleId, HelpType);
dungeon_count_increment_do(_, _DunId, _DunType, _RoleId, _HelpType) -> ok.

dungeon_count_plus([T|L], DunId, DunType, RoleId, Count) ->
    case T of
        % 每日
        {?DUN_COUNT_COND_DAILY, _MaxNum} ->
            CountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
            lib_daily:plus_count_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, CountType, Count);
        {?DUN_COUNT_COND_DAILY_REWARD, _MaxNum} ->
            CountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
            lib_daily:plus_count_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, CountType, Count);
        {?DUN_COUNT_COND_WEEK, _MaxNum} ->
            lib_week:plus_count_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunId, Count);
        {?DUN_COUNT_COND_PERMANENT, _MaxNum} ->
            lib_counter:plus_count_offline(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunId, Count);
        _ -> skip
    end,
    dungeon_count_plus(L, DunId, DunType, RoleId, Count);

dungeon_count_plus([], _, _, _, _) -> ok.

%% 付费扫荡批量设置次数
dungeon_buy_count_plus(_CountCondition, GoodsType, DunId, DunType, RoleId, Count) ->
    CountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
    if GoodsType == ?TYPE_GOLD -> %% 彩钻购买次数
            CountList = [
                {{?MOD_DUNGEON, ?MOD_DUNGEON_BUY, CountType}, Count},
                {{?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, CountType}, Count},
                {{?MOD_DUNGEON, ?MOD_DUNGEON_SUCCESS, DunId}, Count},
                {{?MOD_DUNGEON, ?MOD_DUNGEON_GOLD_BUY, CountType}, Count}
            ];
        true -> %% 红钻购买次数
            CountList = [
                {{?MOD_DUNGEON, ?MOD_DUNGEON_BUY, CountType}, Count},
                {{?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, CountType}, Count},
                {{?MOD_DUNGEON, ?MOD_DUNGEON_SUCCESS, DunId}, Count}
            ]
    end,
    lib_daily:plus_count(RoleId, CountList),
    lib_counter:plus_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_SUCCESS, DunId, Count).

%% 批量设置次数
dungeon_count_plus(RoleId, DunIds, Num) ->
    F = fun
        (DunId, {Daily, Weekly, Permanent} = Acc) ->
            case data_dungeon:get_dun(DunId) of
                #dun_cfg{count_cond = CountConditions, type = DunType} ->
                    DailyCountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
                    plus_count_by_count_cond(CountConditions, DailyCountType, DunId, Num, Daily, Weekly, Permanent);
                _ ->
                    Acc
            end
    end,
    {DailyList, WeeklyList, PermanentList} =  lists:foldl(F, {[], [], []}, DunIds),
    if
        DailyList =/= [] ->
            lib_daily:plus_count(RoleId, DailyList);
        true ->
            ok
    end,
    if
        WeeklyList =/= [] ->
            lib_week:plus_count(RoleId, WeeklyList);
        true ->
            ok
    end,
    if
        PermanentList =/= [] ->
            lib_counter:plus_count(RoleId, PermanentList);
        true ->
            ok
    end.

plus_count_by_count_cond([H|CountConditions], DailyCountType, DunId, Num, Daily, Weekly, Permanent) ->
    case H of
        % 每日
        {?DUN_COUNT_COND_DAILY, _MaxNum} ->
            case lists:keyfind({?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DailyCountType}, 1, Daily) of
                false ->
                    Daily1 = [{{?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DailyCountType}, Num}|Daily],
                    plus_count_by_count_cond(CountConditions, DailyCountType, DunId, Num, Daily1, Weekly, Permanent);
                _ ->
                    plus_count_by_count_cond(CountConditions, DailyCountType, DunId, Num, Daily, Weekly, Permanent)
            end;
        {?DUN_COUNT_COND_WEEK, _MaxNum} ->
            Weekly1 = lists:keystore({?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunId}, 1, Weekly, {{?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunId}, Num}),
            plus_count_by_count_cond(CountConditions, DailyCountType, DunId, Num, Daily, Weekly1, Permanent);
        {?DUN_COUNT_COND_PERMANENT, _MaxNum} ->
            Permanent1 = lists:keystore({?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunId}, 1, Permanent, {{?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunId}, Num}),
            plus_count_by_count_cond(CountConditions, DailyCountType, DunId, Num, Daily, Weekly, Permanent1);
        _ -> plus_count_by_count_cond(CountConditions, DailyCountType, DunId, Num, Daily, Weekly, Permanent)
    end;

plus_count_by_count_cond([], _, _, _, Daily, Weekly, Permanent) ->
    {Daily, Weekly, Permanent}.

%% 主动退出副本
active_quit(Player) ->
    #player_status{id = RoleId, copy_id = CopyId} = Player,
    case is_on_dungeon(Player) of
        true -> mod_dungeon:active_quit(CopyId, RoleId);
        false -> immediate_quit_dungeon(Player, ?DUN_RESULT_TYPE_FAIL)
    end.


%% 直接退出副本
immediate_quit_dungeon(Player, _EndType) ->
    #player_status{scene = SceneId, figure = #figure{lv = Lv, guild_id = _GuildId}, dungeon = #status_dungeon{out = Out, dun_id = DunId},
        team = #status_team{team_id = TeamId}, id = RoleId, dun_multi = RoleDunMulti, action_lock = ActionLock} = Player,
    #dungeon_out{scene = OldSceneId, scene_pool_id = ScenePoolId, copy_id = OldCopyId, x = X, y = Y, dun_id = OutDunId, reply_pid = ReplyPid, data = DunData} = Out,
    IsDunLock = lists:member(ActionLock, [?ERRCODE(err215_on_multi_team), ?ERRCODE(err610_had_on_dungeon)]),
    case data_scene:get(SceneId) of
        #ets_scene{type = ?SCENE_TYPE_DUNGEON} when IsDunLock ->
            %% 拾取未拾取的掉落
            mod_drop:pick_all(node(), RoleId, SceneId, Player#player_status.scene_pool_id, Player#player_status.copy_id, undefined),
            Player1 = handle_quit_dungeon(Player, DunId),
            NewOut = #dungeon_out{dun_id = OutDunId, reply_pid = ReplyPid, data = DunData},
            StatusDunAttrList = [{dun_id, 0}, {is_end, ?DUN_IS_END_NO}, {dead_time, 0}, {revive_count, 0}, {cost_revive_count, 0}, {out, NewOut},
            {revive_map, #{}}, {dun_score, 0}],
            AttrList = [{dun_multi_pid, 0}, {status_dungeon, StatusDunAttrList}, {ghost, 0}, {group, 0}, {change_scene_hp_lim, 1}, {pk, {?PK_PEACE, true}}],
            {ChangeX, ChangeY} = {X, Y},
            % 1.退出去的场景为0;2.公会id等于0和玩家退出的场景为公会场景
            IsClusterType = is_cluster_dungeon_by_dun_id(DunId),
            OldIsBossScene = local_boss_api:is_local_boss_scene(OldSceneId),
            if
                OldSceneId == 0 orelse OldIsBossScene ->
                    [NewSceneId, NewX, NewY] = lib_scene:repair_to_default(Lv),
                    NewPlayer = lib_scene:change_scene(Player1, NewSceneId, 0, 0, NewX, NewY, true, AttrList);
                true ->
                    NewPlayer = lib_scene:change_scene(Player1, OldSceneId, ScenePoolId, OldCopyId, ChangeX, ChangeY, true, AttrList)
            end,
            #player_status{team = #status_team{team_id = TeamId}, dun_multi = #role_dun_multi{team_id = MulTeamId}} = NewPlayer,
            NewPlayer1 =
            if
                TeamId > 0 orelse MulTeamId > 0 ->
                    %% 退队
                    TeamId > 0 andalso mod_team:cast_to_team(TeamId, {'quit_dungeon', RoleId}),
                    MulTeamId > 0 andalso IsClusterType andalso begin
                        Msg = {leave_team, {NewPlayer#player_status.id}, MulTeamId},
                        % ?ERR("out dungeon leave team ~p", [MulTeamId]),
                        clusters_node:apply_cast(lib_dun_multi_cluster, cast_dun_multi_hall_cluster, [DunId, Msg])
                    end,
                    MultTeamPlayer =
                        if MulTeamId > 0 andalso IsClusterType ->
                            case RoleDunMulti of
                                #role_dun_multi{merge_type = OldMergeType} -> ok;
                                _ -> OldMergeType = 0
                            end,
                            NewDunMulti = #role_dun_multi{team_id = 0, dun_id = 0, merge_type = OldMergeType},
                            mod_scene_agent:update(NewPlayer, [{dun_multi, {0, 0}}]),
                            NewPlayer#player_status{dun_multi = NewDunMulti};
                        true ->
                            NewPlayer
                    end,
                    MultTeamPlayer;
                true ->
                    NewPlayer
            end,
            ActionLock = ?IF(IsClusterType, ?ERRCODE(err215_on_multi_team), ?ERRCODE(err610_had_on_dungeon)),
            NewPlayer2 = lib_player:break_action_lock(NewPlayer1, ActionLock),
            NewPlayer3 = lib_dungeon_vip:reset_vip_tmp_skill(NewPlayer2, DunId),
            {ok, NewPlayer3};
        #ets_scene{type = ?SCENE_TYPE_DUNGEON} ->
            ?ERR("dungeon quit error lock ~p scene ~p", [ActionLock, SceneId]),
            {ok, Player};
        _ ->
            {ok, Player}
    end.

%% 副本关闭
close_dungeon(Player, _SceneList, CopyId) ->
    #player_status{scene = _SceneId, copy_id = RoleCopyId} = Player,
    case RoleCopyId == CopyId of
        true -> immediate_quit_dungeon(Player, ?DUN_RESULT_TYPE_FAIL);
        false -> {ok, Player}
    end.

pull_into_dungeon(RoleId, Data) ->
    case lib_player:is_online_global(RoleId) of
        true ->
            lib_player:apply_cast(RoleId, ?APPLY_CAST_SAVE, ?MODULE, pull_player_into_dungeon, [Data]);
        false ->
            pull_player_into_dungeon(RoleId, Data)
    end.

pull_player_into_dungeon(RoleId, Data) when is_integer(RoleId) -> %%离线玩家
    case Data of
        #{dun_id := DunId, team_id := TeamId, scene_info := {_, _, CopyId}} -> ok;
        _ -> DunId = 0, TeamId = 0, CopyId = undefined
    end,
    mod_dungeon:handle_enter_fail(CopyId, RoleId, 0),
    case data_dungeon:get_dun(DunId) of
        #dun_cfg{type = DunType} ->
            IsClusterType = is_cluster_dungeon_by_dun_id(DunId),
            case lists:member(DunType, ?TEAM_DUNGEON) of
                true when IsClusterType == true ->
                    Msg = {leave_team, {RoleId}, TeamId},
                    clusters_node:apply_cast(lib_dun_multi_cluster, cast_dun_multi_hall_cluster, [DunId, Msg]);
                false ->
                    ok
            end;
        _ -> ok
    end;
pull_player_into_dungeon(Player, Data) ->
    #{dun_id := DunId, team_id := TeamId, scene_info := {ChangeSceneId, ChangeScenePoolId, CopyId},
        pos := {ChangeSceneX, ChangeSceneY}, hp := {Hp, HpLim}, dead_time := DeadTime,
        revive_count := ReviveCount, out_info := Out, revive_map := ReviveMap, enter_count := PlusCount,
        is_end := IsEnd, help_type := HelpType1, cost_type := CostType} = Data,
    #player_status{id = RoleId, dungeon = OldStatusDungeon, battle_attr = #battle_attr{hp = _RoleHp, hp_lim = RoleHpLim}} = Player,
    case check_pull_into(Player, Data) of
        {true, CostPlayer} ->
            IsClusterType = is_cluster_dungeon_by_dun_id(DunId),
            #dun_cfg{type = DunType, count_deduct = CountDeduct} = Dun = data_dungeon:get_dun(DunId),
            HelpType = HelpType1,
            CountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
            %% 扣除次数
            if
                CostType == ?MOD_DUNGEON_ENTER -> %% 正常次数
                    dungeon_count_increment(Dun, Player#player_status.id, HelpType, ?DUN_COUNT_DEDUCT_ENTER);
                CostType == ?MOD_DUNGEON_BUY andalso CountDeduct == ?DUN_COUNT_DEDUCT_ENTER -> %% 待购买的次数
                    gold_buy_count_increment(DunType, CountType, RoleId),
                    lib_daily:increment(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY, CountType),
                    lib_daily:increment(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, CountType);
                CostType == ?MOD_DUNGEON_BUY_USE andalso CountDeduct == ?DUN_COUNT_DEDUCT_ENTER -> %% 已购买的次数
                    lib_daily:increment(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, CountType);
                true -> skip
            end,
            Dun#dun_cfg.type == ?DUNGEON_TYPE_TOWER andalso
             begin
                case lib_dungeon_tower:is_diffcult_dun(DunId) of
                    true ->
                        lib_counter:increment(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, Dun#dun_cfg.type),
                        lib_dungeon_tower:refresh_dun_count_state(RoleId);
                    _ -> skip
                end
            end,
            %% 构造status_dungeon新结构
            NewStatusDungeon0 = #status_dungeon{
                dun_id = DunId, dead_time = DeadTime, revive_count = ReviveCount,
                out = Out, revive_map = ReviveMap,
                is_end = IsEnd, help_type = HelpType
            },
            %% 继承需保留的数据
            NewStatusDungeon = NewStatusDungeon0#status_dungeon{
                flop     = OldStatusDungeon#status_dungeon.flop,
                dun_data = OldStatusDungeon#status_dungeon.dun_data
            },
            NewPlayer = CostPlayer#player_status{dungeon = NewStatusDungeon},
            %% 加锁
            NewPlayer1 = ?IF(IsClusterType,
                lib_player:setup_action_lock(NewPlayer, ?ERRCODE(err215_on_multi_team)),
             begin case maps:get(?DUN_ROLE_DUN_ENTER_NEXT, Out#dungeon_out.data, 0) of
                    1 -> NewPlayer; %% 如果正处于连续通关副本中,不重复设置人物锁
                    _ -> lib_player:setup_action_lock(NewPlayer#player_status{action_lock = free}, ?ERRCODE(err610_had_on_dungeon))
             end end),
            Ghost = ?IF(Hp == 0, 1, 0),
            case IsClusterType of
                true ->
                    AttrList = [{dun_multi_pid, CopyId}, {hp, RoleHpLim}, {hp_lim, RoleHpLim}, {ghost, Ghost}, {group, ?DUN_DEF_GROUP}];
                false ->
                    AttrList = [{hp, Hp}, {hp_lim, HpLim}, {ghost, Ghost}, {group, ?DUN_DEF_GROUP}]
            end,
            NewPlayer2 = lib_scene:change_scene(NewPlayer1, ChangeSceneId, ChangeScenePoolId, CopyId, ChangeSceneX, ChangeSceneY, _AutoSelect = false, AttrList),
            NewPlayer3 = handle_enter_dungeon(NewPlayer2, DunId),
            %% 进入副本触发
            NewPlayer4 = enter_dungeon_trigger(NewPlayer3, DunId, Dun, TeamId, PlusCount),
            NewPlayer5 = lib_dungeon_help:enter_dun_scene(NewPlayer4, Dun#dun_cfg.type),
            %% 通知
            {ok, BinData} = pt_610:write(61001, [DunId, ChangeSceneId, ?SUCCESS, "", 0]),
            lib_server_send:send_to_sid(Player#player_status.sid, BinData),
            {ok, NewPlayer5};
        {false, Code, _} ->
            {CodeInt, CodeArgs} = util:parse_error_code(Code),
            {ok, BinData} = pt_610:write(61001, [DunId, 0, CodeInt, CodeArgs, 0]),
            lib_server_send:send_to_sid(Player#player_status.sid, BinData),
            mod_dungeon:handle_enter_fail(CopyId, Player#player_status.id, Code),
            {ok, Player}
    end.

check_pull_into(Player, Data) ->
    #player_status{id = RoleId, dun_multi = #role_dun_multi{team_id = MulTeamId}, sid = Sid} = Player,
    #{dun_id := DunId, team_id := DunTeamId, cost_type := CountCostType, cost_list := CostList} = Data,
    IsClusterType = is_cluster_dungeon_by_dun_id(DunId),
    Dun = data_dungeon:get_dun(DunId),
    if
        not is_record(Dun, dun_cfg) -> ?ERR("record error", []),
            {false, ?FAIL, Player};
        IsClusterType andalso MulTeamId == 0 andalso DunTeamId > 0-> %%异常，退掉队伍, 不让进
            {ok, BinData} = pt_215:write(21506, [1]),
            lib_server_send:send_to_sid(Sid, BinData),
            Msg = {leave_team, {RoleId}, DunTeamId},
            clusters_node:apply_cast(lib_dun_multi_cluster, cast_dun_multi_hall_cluster, [DunId, Msg]),
            {false, ?ERRCODE(err215_not_in_team), Player};
        IsClusterType andalso MulTeamId > 0 ->
            case MulTeamId =/= DunTeamId of
                true ->
                    ?ERR("pull_player_into_dungeon_after TEAM not match ~p~n",[{RoleId, MulTeamId, DunTeamId}]),
                    Msg = {leave_team, {RoleId}, DunTeamId},
                    clusters_node:apply_cast(lib_dun_multi_cluster, cast_dun_multi_hall_cluster, [DunId, Msg]),
                    {false, ?ERRCODE(err215_not_in_team), Player};
                false ->
                    #dun_cfg{count_deduct = CountDeduct} = Dun,
                    if
                        CountDeduct == ?DUN_COUNT_DEDUCT_ENTER ->
                            dungeon_cost(Player, DunId, CountCostType, CostList);
                        true ->
                            {true, Player}
                    end
            end;
        true ->
            #dun_cfg{count_deduct = CountDeduct} = Dun,
            if
                CountDeduct == ?DUN_COUNT_DEDUCT_ENTER ->
                    dungeon_cost(Player, DunId, CountCostType, CostList);
                true ->
                    {true, Player}
            end
    end.

enter_dungeon_trigger(Player, DunId, Dun, _DunTeamId, PlusCount) ->
    #player_status{id = RoleId} = Player,
    #dun_cfg{type = DunType} = Dun,
    lib_dun_multi_cluster:finish_first(Player, DunId),
    %%完成日常
    case DunType == ?DUNGEON_TYPE_PER_BOSS of
        true -> ignore;
        _ ->
            lib_dungeon_distribute:role_success_end_activity(DunType, DunId, RoleId, PlusCount)
    end,
    %% 嗨点活动
    HiPointIsEnterTrigger = lists:member(DunType, ?HI_POINT_DUNGEON_SCORE_TRIGGER),
    DunType == ?DUNGEON_TYPE_EXP andalso contract_task_api:exp_dun_task_trigger(RoleId, 1),
    if
        HiPointIsEnterTrigger -> %% 副本完成后才计分
            skip;
        true ->
            mod_hi_point:success_end(Player#player_status.id, ?MOD_DUNGEON, DunType),
            custom_act_adventure:do_trigger_adventure_task(Player#player_status.id, ?MOD_DUNGEON, DunType)
    end,
    SevenDayIsEnterTrigger = lists:member(DunType, ?SEVEN_DAY_GOAL_DUNGEON_TRIGGER),
    if
        SevenDayIsEnterTrigger -> NewPlayer = Player;
        true ->
            {ok, NewPlayer} = lib_seven_days_goal:do_finish_task(Player, ?MOD_DUNGEON, DunType)
    end,
    DunType == ?DUNGEON_TYPE_VISUS andalso lib_role_legion:trigger_task(NewPlayer, ?MOD_DUNGEON, 7),
    DunType == ?DUNGEON_TYPE_EXP andalso lib_role_legion:trigger_task(NewPlayer, ?MOD_DUNGEON, 2),
    NewPlayer.

%% 退出副本时更新人物的退出副本信息
role_out_info_update(Player, OutInfo) ->
    NewPlayer = update_status_dungeon([{out, OutInfo}, {is_end, ?DUN_IS_END_YES}, {dead_count, 0}], Player),
    {ok, NewPlayer}.

%% 副本重连
reconnect(#player_status{id = RoleId, scene = SceneId, x = X, y = Y, sid = Sid} = Player, Type) ->
    IsHosting = offline_hosting_api:is_offline_hosting(Player),
    if IsHosting -> %% 托管中不处理
            {next, Player};
        true ->
            DunType = lib_dungeon_api:get_dungeon_type_by_scene(SceneId),
            case lists:member(DunType, ?TEAM_DUNGEON) of
                true when DunType =/= ?DUNGEON_TYPE_GUARD ->
                    [DunId|_] = data_dungeon:get_ids_by_type(DunType),
                    case is_cluster_dungeon_by_dun_id(DunId) of
                        true ->
                            case clusters_api:is_connected_center() of
                                false ->
                                    lib_scene:player_change_default_scene(RoleId, [{change_scene_hp_lim, 0}]);
                                _ ->
                                    clusters_node:apply_cast(mod_dungeon_agent, reconnect, [node(), RoleId, Sid, {X, Y}])
                            end,
                            {ok, Player};
                        _ ->
                            do_reconnect(Player, Type)
                    end;
                _ -> do_reconnect(Player, Type)
            end
    end.

do_reconnect(#player_status{id = RoleId, figure = Figure, pid = Pid, sid = Sid, scene = SceneId, x = X, y = Y} = Player, ?NORMAL_LOGIN) ->
    case mod_dungeon_agent:get_dungeon_record(RoleId) of
        [] -> %% 找不到备份
            case lib_scene:is_dungeon_scene(SceneId) of
                true ->
                    case data_dungeon:get_type_by_scene(SceneId) of
                        [?DUNGEON_TYPE_TASK|_] ->
                            [RepairSceneId|_] = lib_scene:repair_to_default(Figure#figure.lv),
                            NewPlayer = Player#player_status{scene = RepairSceneId},
                            NewPlayer1 = lib_player_reconnect:repair_pk_status(NewPlayer),
                            {ok, NewPlayer1};
                        _ ->
                            NewPlayer = lib_player_reconnect:repair_to_default(Player),
                            {ok, NewPlayer}
                    end;
                false ->
                    {next, Player}
            end;
        [#dungeon_record{dun_pid = DunPid, dun_id = DunId}|_] ->
            case misc:is_process_alive(DunPid) of
                true ->
                    case data_dungeon:get_dun(DunId) of
                        #dun_cfg{} ->
                            mod_dungeon:reconnect(DunPid, RoleId, [{figure, Figure}, {pid, Pid}, {sid, Sid}], {X, Y}),
                            {ok, Player};
                        _ ->
                            {next, Player}
                    end;
                false ->
                   {next, Player}
            end
    end;
do_reconnect(#player_status{scene = SceneId} = Player, ?RE_LOGIN) ->
    case lib_dungeon:is_on_dungeon(Player) of
        true ->
            NewPlayer = lib_scene:change_relogin_scene(Player, [{action_lock, ?ERRCODE(err610_had_on_dungeon)}]),
            {ok, NewPlayer};
        false ->
            case lib_scene:is_dungeon_scene(SceneId) of
                true ->
                    NewPlayer = lib_scene:change_default_scene(Player, [{change_scene_hp_lim, 0}]),
                    {ok, NewPlayer};
                false ->
                    {next, Player}
            end
    end.

%% 延迟登出
delay_logout(Player) ->
    case lib_dungeon:is_on_dungeon(Player) of
        true ->
            #player_status{id = RoleId, copy_id = CopyId, battle_attr = #battle_attr{hp = Hp, hp_lim = HpLim} } = Player,
            mod_dungeon:delay_logout(CopyId, RoleId, Hp, HpLim);
        false ->
            skip
    end.

%% 登出
logout(Player) ->
    case lib_dungeon:is_on_dungeon(Player) of
        true ->
            #player_status{id = RoleId, copy_id = CopyId, battle_attr = #battle_attr{hp = Hp, hp_lim = HpLim}} = Player,
            mod_dungeon:logout(CopyId, RoleId, Hp, HpLim);
        false ->
            skip
    end.

%% 发送副本复活信息
send_reveive_info(Player) ->
    case lib_dungeon:is_on_dungeon(Player) of
        true ->
            case get_revive_info(Player) of
                {false, _ErrorCode} -> IsRevive = 0, NextReviveTime = 0;
                {true, NextReviveTime} -> IsRevive = 1
            end,
            {ok, BinData} = pt_200:write(20009, [IsRevive, NextReviveTime]),
            lib_server_send:send_to_sid(Player#player_status.sid, BinData);
        false ->
            skip
    end.

%% -----------------------------------------------------------------
%% 通用函数
%% -----------------------------------------------------------------

%% 是否在副本中
is_on_dungeon(Player) ->
    #player_status{copy_id = CopyId, dungeon = StatusDun} = Player,
    #status_dungeon{dun_id = DunId} = StatusDun,
    misc:is_process_alive(CopyId) andalso DunId > 0.

%% 是否可采集怪
check_collect(Player, _CheckArgs) ->
    case is_on_dungeon(Player) of
        true ->
            #player_status{dungeon = StatusDun} = Player,
            #status_dungeon{dun_id = DunId, help_type = HelpType} = StatusDun,
            case data_dungeon:get_dun_type(DunId) of
                ?DUNGEON_TYPE_GUARD when HelpType == ?DUN_TEAM_HELP_YES ->
                    {false, ?ERRCODE(err200_help_cant_collect)};
                _ ->
                    true
            end;
        _ ->
            true
    end.

%% 是否在副本中(副本场景类型)
is_on_dungeon_by_scene(Scene) ->
    [SceneType|_] = lib_scene:get_scene_type(Scene),
    SceneType == ?SCENE_TYPE_DUNGEON.

%% 注意:目前副本复活是类型9(通用副本复活)
%% @return true | {false, ErrorCode}
check_revive_on_dungeon(Player, Type) ->
    IsOnDungeon = is_on_dungeon(Player),
    if
        not IsOnDungeon -> true;
        Type == 1 orelse Type == 2 orelse Type == 16 -> true;
        Type == 9 ->
            case get_revive_info(Player) of
                {false, ErrorCode} -> {false, ErrorCode};
                {true, NextReiveTime} ->
                    % 客户端有误差
                    case utime:unixtime() >= NextReiveTime-1 of
                        true  -> true;
                        false -> {false, ?ERRCODE(err200_revive_time_cd)}
                    end
            end;
        true -> {false, ?ERRCODE(err200_revive_type_error)}
    end.

%% 获取复活信息[错误码跟复活协议挂钩]
get_revive_info(Player) ->
    #player_status{dungeon = StatusDun} = Player,
    #status_dungeon{dun_id = DunId, dead_time = DeadTime, revive_count = ReviveCount} = StatusDun,
    get_revive_time(DunId, DeadTime, ReviveCount).

%% 获得复活信息
get_revive_time(DunId, DeadTime, ReviveCount) ->
    Dun = data_dungeon:get_dun(DunId),
    if
        not is_record(Dun, dun_cfg) ->
            {false, ?ERRCODE(err200_revive_count_error)};
        Dun#dun_cfg.type == ?DUNGEON_TYPE_GUARD ->
            NewReviveCount = ReviveCount+1,
            MaxCount = data_dun_guard:get_max_die_count(),
            MinCount = data_dun_guard:get_min_die_count(),
            if NewReviveCount < MinCount ->
                    {false, ?ERRCODE(err200_revive_count_error)};
                true ->
                    DunReviveCfg = if NewReviveCount < MaxCount ->
                            data_dun_guard:get_revive(NewReviveCount);
                        true ->
                            data_dun_guard:get_revive(MaxCount)
                    end,
                    NewReviveTime = DeadTime + DunReviveCfg#base_dun_guard_revive.cd,
                    {true, NewReviveTime}
            end;
        % 本副本不允许复活
        Dun#dun_cfg.revive_count == 0 ->
            {false, ?ERRCODE(err200_dun_not_allow_revive)};
        % 复活次数已满
        ReviveCount >= Dun#dun_cfg.revive_count andalso Dun#dun_cfg.revive_count =/= 0 ->
            {false, ?ERRCODE(err200_revive_count_error)};
        true ->
            #dun_cfg{revive_time = ReviveTime} = Dun,
            NextReiveTime = DeadTime + ReviveTime,
            {true, NextReiveTime}
    end.

%% 付费复活判断副本情况
%% 守卫信标副本特殊处理
check_revive_cost(Player) ->
    #player_status{copy_id = CopyId, dungeon = Dungeon} = Player,
    #status_dungeon{cost_revive_count = ReviveCount, dun_id = DunId} = Dungeon,
    IsDunAlive = misc:is_process_alive(CopyId) andalso DunId > 0,
    IsGuardDun = ?IF(data_dungeon:get_dun_type(DunId) == ?DUNGEON_TYPE_GUARD, true, false),
    if IsDunAlive andalso IsGuardDun ->
            MaxCount = data_dun_guard:get_max_die_count(),
            MinCount = data_dun_guard:get_min_die_count(),
            NewReviveCount = ReviveCount+1,
            % ?ERR("~p ~p ~p", [NewReviveCount, MinCount, MaxCount]),
            if NewReviveCount < MinCount -> false;
                true ->
                    if NewReviveCount < MaxCount ->
                            #base_dun_guard_revive{cost = Cost} = data_dun_guard:get_revive(NewReviveCount),
                            {true, {true, bgold_and_gold, Cost, revive}};
                        true ->
                            #base_dun_guard_revive{cost = Cost} = data_dun_guard:get_revive(MaxCount),
                            {true, {true, bgold_and_gold, Cost, revive}}
                    end
            end;
        true ->
            false
    end.

%% 是否是通用的复活类型
is_common_revive_type(Player) when is_record(Player, player_status) ->
    case is_on_dungeon(Player) of
        true  -> true;
        false -> false
    end.

%% 更新副本状态
update_status_dungeon([], Player) -> Player;
update_status_dungeon([H|T], Player) ->
    #player_status{dungeon = #status_dungeon{flop = RoleFlop} = StatusDun} = Player,
    case H of
        {dun_id, DunId} -> NewStatusDun = StatusDun#status_dungeon{dun_id = DunId};
        {is_end, IsEnd} -> NewStatusDun = StatusDun#status_dungeon{is_end = IsEnd};
        {dead_time, DeadTime} -> NewStatusDun = StatusDun#status_dungeon{dead_time = DeadTime};
        {revive_count, ReviveCount} -> NewStatusDun = StatusDun#status_dungeon{revive_count = ReviveCount};
        {dead_count, DeadCount} -> NewStatusDun = StatusDun#status_dungeon{dead_count = DeadCount};
        {cost_revive_count, ReviveCount} -> NewStatusDun = StatusDun#status_dungeon{cost_revive_count = ReviveCount};
        {out, DungeonOut} -> NewStatusDun = StatusDun#status_dungeon{out = DungeonOut};
        {revive_map, ReviveMap} -> NewStatusDun = StatusDun#status_dungeon{revive_map = ReviveMap};
        {dun_score, Score} -> NewStatusDun = StatusDun#status_dungeon{dun_score = Score};
        {flop, Flop} -> NewStatusDun = StatusDun#status_dungeon{flop = RoleFlop#role_dun_flop{tmp_flop = Flop}}
    end,
    NewPlayer = Player#player_status{dungeon = NewStatusDun},
    update_status_dungeon(T, NewPlayer).

%% 获取复活点
get_revive_xy(Player) ->
    #player_status{scene = SceneId, dungeon = #status_dungeon{revive_map = ReviveMap} } = Player,
    case maps:get(SceneId, ReviveMap, []) of
        [] ->
            [NewX, NewY] = lib_scene:get_born_xy(SceneId),
            {NewX, NewY, 1};
        {NewX, NewY} ->
            {NewX, NewY, 1}
    end.

%% 获得最大助战次数
get_max_help_count(Dun) ->
    case Dun of
        #dun_cfg{count_cond = CountCond, type = _Type, id = _DunId} ->
            if
                true -> %% 普通的助战次数
                    case lists:keyfind(?DUN_COUNT_COND_DAILY_HELP, 1, CountCond) of
                        {?DUN_COUNT_COND_DAILY_HELP, MaxNum} -> MaxNum;
                        _ -> 0
                    end
            end;
        _ ->
            0
    end.

update_dungeon_record(#player_status{dungeon_record = undefined} = PS, Data) ->
    NewPS = load_dungeon_record(PS),
    update_dungeon_record(NewPS, Data);

update_dungeon_record(PS, #callback_dungeon_succ{dun_type = DunType} = Data) ->
    lib_dungeon_distribute:update_dungeon_record(DunType, PS, Data).

%% 加载副本记录数据
-spec load_dungeon_record(PlayerData) -> Ret when
    PlayerData :: #player_status{} | integer() | any(),
    Ret :: #player_status{} | map() | any().
load_dungeon_record(PlayerData) when is_record(PlayerData, player_status) ->
    case PlayerData of
        #player_status{id = RoleId, dungeon_record = undefined} ->
            SQL = io_lib:format(?SQL_DUN_BEST_REC_SELECT, [RoleId]),
            All = db:get_all(SQL),
            Record = init_dungeon_record(All, #{}),
            PlayerData#player_status{dungeon_record = Record};
        _ ->
            PlayerData
    end;

load_dungeon_record(PlayerData) when is_integer(PlayerData) ->
    SQL = io_lib:format(?SQL_DUN_BEST_REC_SELECT, [PlayerData]),
    All = db:get_all(SQL),
    Record = init_dungeon_record(All, #{}),
    Record;

load_dungeon_record(_PS) -> _PS.

init_dungeon_record([[DunId, Data]|T], Rec) ->
    case data_dungeon:get_dun(DunId) of
        #dun_cfg{} ->
            Value = util:bitstring_to_term(Data),
            init_dungeon_record(T, Rec#{DunId => Value});
        _ -> init_dungeon_record(T, Rec)
    end;
init_dungeon_record([], Rec) -> Rec.

save_dungeon_record(RoleId, DunId, Value) ->
    SQL = io_lib:format(?SQL_DUN_BEST_REC_REPLACE, [RoleId, DunId, util:term_to_string(Value)]),
    % ?PRINT("~s~n", [SQL]),
    db:execute(SQL).

handle_quit_dungeon(Player, DunId) ->
    case data_dungeon:get_dun(DunId) of
        #dun_cfg{type = ?DUNGEON_TYPE_EXGOD} ->
            god_api:refresh_exper_dun_god_state(Player);
        #dun_cfg{type = _DunType} = Dun ->
            Player1 = lib_dungeon_distribute:handle_quit_dungeon(Player, Dun),
            Player2 = lib_goods_buff:remove_goods_temp_buff(Player1, ?BUFF_EXP_DUN_ACT),
            Player2;
        _ ->
            Player
    end.

%%通用出场景移除鼓舞技能
handle_quit_remove_skill_buff(Player, DunId) ->
    case data_dungeon_special:get(DunId, encourage_data) of
        {SkillIdGold, SkillIdCoin, _, _, _, _} ->
            lib_player_event:add_listener(?EVENT_FIN_CHANGE_SCENE, ?MODULE, clean_buff, {SkillIdGold,SkillIdCoin}),
            Player;
        _ ->
            Player
    end.

clean_buff(Player, #event_callback{param = {SkillIdGold, SkillIdCoin}}) ->
    lib_player_event:remove_listener(?EVENT_FIN_CHANGE_SCENE, ?MODULE, clean_buff),
    case lib_skill_buff:clean_buff(Player, SkillIdGold) of
        {ok, NewPlayer} ->
            lib_skill_buff:clean_buff(NewPlayer, SkillIdCoin);
        _ ->
            {ok, Player}
    end;
clean_buff(Player, _) ->
    {ok, Player}.

handle_enter_dungeon(Player, DunId) ->
    #player_status{figure = #figure{lv = Lv}} = Player,
    case data_dungeon:get_dun(DunId) of
        #dun_cfg{type = DunType, time = Time} = Dun ->
            Player1 = lib_dungeon_distribute:handle_enter_dungeon(Player, Dun),
            ActOpenLv = lib_custom_act_util:get_act_open_lv(?CUSTOM_ACT_TYPE_DUN_MUL_EXP),
            Player2 = case lib_dungeon_api:get_custom_act_exp_ratio(DunType) of
                Ratio when Lv >= ActOpenLv ->
                    lib_goods_buff:add_goods_temp_buff(Player1, ?BUFF_EXP_DUN_ACT, [{attr, [{?EXP_ADD_RATIO, Ratio}]}], utime:unixtime() + Time);
                _ ->
                    Player1
            end,
            Player2;
        _ ->
            Player
    end.

%%断线重连
login_back_to_scene(Player, SceneId, ScenePoolId, CopyId, X, Y, AttrList, DunType, Args) ->
    #player_status{figure = #figure{lv = Lv}} = Player,
    ActOpenLv = lib_custom_act_util:get_act_open_lv(?CUSTOM_ACT_TYPE_DUN_MUL_EXP),
    Player1 = case lib_dungeon_api:get_custom_act_exp_ratio(DunType) of
        Ratio when Lv >= ActOpenLv ->
            lib_goods_buff:add_goods_temp_buff(Player, ?BUFF_EXP_DUN_ACT, [{attr, [{?EXP_ADD_RATIO, Ratio}]}], utime:unixtime() + 600);
        _ -> Player
    end,
    lib_dungeon_distribute:login_back_to_scene(DunType, Player1, SceneId, ScenePoolId, CopyId, X, Y, AttrList, Args).

login_back_to_scene_normal(Player, SceneId, ScenePoolId, CopyId, X, Y, AttrList, Args) ->
    NewPlayer = lib_scene:change_scene(Player, SceneId, ScenePoolId, CopyId, X, Y, false, AttrList),
    hanlde_login_back(NewPlayer, Args).

%%断线重连数据处理
hanlde_login_back(Player, []) -> Player;
hanlde_login_back(Player, [{send_bin, BinData}| T]) ->
    lib_server_send:send_to_sid(Player#player_status.sid, BinData),
    hanlde_login_back(Player, T);
hanlde_login_back(Player, [{skill, SkillList}| T]) ->
    NewPlayer = login_back_skill(Player, SkillList),
    hanlde_login_back(NewPlayer, T);
hanlde_login_back(Player, [{anger, Anger} | T]) ->
    #player_status{id = RoleId, battle_attr = BattleAttr} = Player,
    NewPlayer = Player#player_status{battle_attr = BattleAttr#battle_attr{anger = Anger}},
    mod_scene_agent:update(NewPlayer, [{anger, Anger}]),
    {ok, Bin} = pt_130:write(13086, [Anger, ?ANGER_MAX]),
    lib_server_send:send_to_uid(RoleId, Bin),
    hanlde_login_back(NewPlayer, T).

login_back_skill(Player, []) -> Player;
login_back_skill(Player, [{SkillId, Count}| T]) ->
    case lib_skill_buff:add_buff(Player, SkillId, 1) of
        {ok, NewPlayer} ->
            ok;
        _ ->
            NewPlayer = Player
    end,
    LeftCount = max(0, Count-1),
    case LeftCount =< 0 of
        true-> login_back_skill(NewPlayer, T);
        false-> login_back_skill(NewPlayer, [{SkillId, LeftCount} | T])
    end.

%% 日常挑战次数
get_daily_count(DunType, DunId, DailyCountList) ->
    CountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
    case lists:keyfind({?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, CountType}, 1, DailyCountList) of
        {_, Count} ->
            Count;
        _ ->
            0
    end.

pick_all(Drops) ->
    NowTime = utime:unixtime(),
    F = fun
        (DropInfo, Acc) ->
            if
                DropInfo#ets_drop.expire_time + 15 > NowTime ->
                    drop_logic:drop_to_goods_list([DropInfo]) ++ Acc;
                true ->
                    Acc
            end
    end,
    lists:foldl(F, [], Drops).

calc_base_reward_list(DungeonRole) ->
    case DungeonRole#dungeon_role.calc_reward_list of
        {msg, _Code, Rewards} ->
            ok;
        [{base, Rewards}|_] ->
            ok;
        Rewards -> ok
    end,
    %% 显示用，此时并未捡起来
    DropGoods = pick_all(DungeonRole#dungeon_role.drop_list),
    lib_goods_api:make_reward_unique(DungeonRole#dungeon_role.drop_reward_list ++ DropGoods ++ Rewards).


%% 获取进入等级
%% 用于生怪:进入平均等级
calc_enter_lv([], _) -> 99;
calc_enter_lv(RoleList, DunType) ->
    Lvs = [Lv||#dungeon_role{figure = #figure{lv = Lv}}<-RoleList],
    SumLv = lists:sum([Lv||#dungeon_role{figure = #figure{lv = Lv}}<-RoleList]),
    case DunType of
        ?DUNGEON_TYPE_EXP ->
            max(trunc(SumLv/length(RoleList)), trunc(0.8*lists:max(Lvs)));
        _ ->
            trunc(SumLv/length(RoleList))
    end.

handle_achievement(Player, DunId) ->
    handle_achievement(Player, DunId, [1]).

handle_achievement(Player, DunId, [PlusCount|_]) ->
    case data_dungeon:get_dun(DunId) of
        #dun_cfg{type = DunType} ->
            case DunType of
                ?DUNGEON_TYPE_CRUSADE ->
                    mod_hi_point:success_end(Player#player_status.id, ?MOD_DUN_MULTI, 1, PlusCount),
                    custom_act_adventure:do_trigger_adventure_task(Player#player_status.id, ?MOD_DUN_MULTI, 1, PlusCount),
                    lists:map(fun(_) -> lib_custom_act_slices:task_event(Player, ?MOD_DUN_MULTI, 1) end, lists:duplicate(PlusCount, 1)),
                    {ok, NewPlayer} = lib_achievement_api:dungeon_crusade_event(Player, PlusCount);
                ?DUNGEON_TYPE_MATERIAL ->
                    {ok, NewPlayer} = lib_achievement_api:dungeon_material_event(Player, PlusCount);
                ?DUNGEON_TYPE_ASSESS ->
                    {ok, NewPlayer} = lib_elite_assess_api:assess_dun_event(Player, DunId);
                 _ ->
                     NewPlayer = Player
            end;
        _ ->
            NewPlayer = Player
    end,
    NewPlayer.

%% 副本通关通知任务系统刷新(只涉及任务相关)
%% 执行的是副本类任务的触发,一般触发不建议放在这里
handle_task_trigger(PS, #callback_dungeon_succ{dun_type=DunType} = CallBack) ->
    lib_dungeon_distribute:handle_task_trigger(DunType, PS, CallBack).

handle_task_trigger_normal(#player_status{id = RoleId}=Player, #callback_dungeon_succ{distribute=?DUN_TASK_PASS_TRIGGER,dun_type=DunType, dun_id = DunId,dun_wave=DunWave}) ->
    custom_act_days_plan:finish_dungeon(RoleId,DunType),
    case lists:member(DunType,[?DUNGEON_TYPE_EXP,?DUNGEON_TYPE_VISUS]) of
        true->
            lib_task_api:fin_dun_wave(RoleId, DunId, DunWave);
        _->
            skip
    end,
    lib_task_api:fin_dun_type(Player, DunType),
    lib_task_api:fin_dun_type_enter(RoleId,DunType),
    lib_task_api:fin_dun_enter(RoleId,DunId),
    lib_task_api:fin_dun(RoleId, DunId);
handle_task_trigger_normal(#player_status{id = RoleId}, #callback_dungeon_succ{distribute=?DUN_TASK_ENTER_TRIGGER,dun_id = DunId,dun_type=DunType,dun_wave=DunWave}) ->
    case lists:member(DunType,[?DUNGEON_TYPE_EXP,?DUNGEON_TYPE_VISUS]) of
        true->
            lib_task_api:fin_dun_wave(RoleId, DunId, DunWave);
        _->
            skip
    end,
    lib_task_api:fin_dun_enter(RoleId,DunId),
    lib_task_api:fin_dun_type_enter(RoleId,DunType).


%% 根据VIP次数获取购买消耗
calc_buy_count_cost(BuyCost, VipBuyCount, Count, Vip) ->
    calc_buy_count_cost(BuyCost, VipBuyCount, Count, Vip, []).
calc_buy_count_cost(BuyCost, VipBuyCount, Count, Vip, Acc) when Count > 0 ->
    NewCount = VipBuyCount + 1,
    case lists:keyfind(NewCount, 1, BuyCost) of
        {_, VipNeed, Cost} ->
            case Vip >= VipNeed of
                true  ->
                    calc_buy_count_cost(BuyCost, NewCount, Count - 1, Vip, lib_goods_util:merge_same_goods([Cost|Acc]));
                false ->
                    false
            end;
        _ ->
            calc_buy_count_cost(BuyCost, NewCount, Count - 1, Vip, Acc)
    end;

calc_buy_count_cost(_, _, _, _, Acc) -> Acc.

%% 判断当前副本消耗正常次数还是已购买的次数或待购买的次数
calc_dun_count_cost(Player, Dun, TypicalData) ->
    #player_status{id = RoleId, figure = #figure{vip = VipLv}} = Player,
    #dun_cfg{id = DunId, type = DunType, count_cond = CountCondition, buy_count_cost = BuyCost} = Dun,
    CountType = lib_dungeon_api:get_daily_count_type(DunType, DunId),
    CountList = [
        {?MOD_DUNGEON, ?MOD_DUNGEON_BUY, CountType},
        {?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, CountType},
        {?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, CountType},
        {?MOD_DUNGEON, ?MOD_DUNGEON_GOLD_BUY, CountType}
    ],
    case lib_daily:get_count(RoleId, CountList) of
        [{_, BuyCount}, {_, EnterCount}, {_, BuyUseCount}, {_, GoldBuy}] -> ok;
        _ -> BuyCount = EnterCount = BuyUseCount = GoldBuy = 0
    end,
    case lists:keyfind(?DUN_COUNT_COND_DAILY, 1, CountCondition) of
        {_, MaxNum} -> ok;
        _ -> MaxNum = 0
    end,
    UniteNum = custom_unite_buff:get_unite_num(Player, DunType),
    AllCountLimit = MaxNum + UniteNum,
    if
        EnterCount >= AllCountLimit -> %% 正常次数已用完
            IsSupVipType = lib_sup_vip:is_sup_vip(Player),
            if
                (DunType == ?DUNGEON_TYPE_MATERIAL andalso IsSupVipType andalso GoldBuy >= BuyCount) orelse
                (IsSupVipType andalso BuyCount == 0) -> %% 贵族第一次免费
                    TypicalData1 = maps:put(?DUN_ROLE_COUNT_COST_TYPE, ?MOD_DUNGEON_BUY, TypicalData),
                    maps:put(?DUN_ROLE_COST_LIST, [], TypicalData1);
                BuyUseCount < BuyCount -> %% 使用已购买的VIP次数
                    TypicalData1 = maps:put(?DUN_ROLE_COUNT_COST_TYPE, ?MOD_DUNGEON_BUY_USE, TypicalData),
                    maps:put(?DUN_ROLE_COST_LIST, [], TypicalData1);
                true -> %% 使用待购买的VIP次数
                    TypicalData1 = maps:put(?DUN_ROLE_COUNT_COST_TYPE, ?MOD_DUNGEON_BUY, TypicalData),
                    CostList = calc_buy_count_cost(BuyCost, BuyCount, 1, VipLv),
                    maps:put(?DUN_ROLE_COST_LIST, CostList, TypicalData1)
            end;
        true ->
            maps:put(?DUN_ROLE_COUNT_COST_TYPE, ?MOD_DUNGEON_ENTER, TypicalData)
    end.

create_dungeon_infos(Rec, DunType) ->
    Ids = data_dungeon:get_ids_by_type(DunType),
    F = fun
        (DunId) ->
            %% 副本记录数据
            Data = maps:get(DunId, Rec, []),
            #dun_cfg{count_cond = CountCondition} = data_dungeon:get_dun(DunId),
            %% 日次数
            DailyCount =
             case lists:keyfind(?DUN_COUNT_COND_DAILY, 1, CountCondition) of
                false ->
                    case lists:keyfind(?DUN_COUNT_COND_DAILY_REWARD, 1, CountCondition) of
                        false -> 0;
                        _ -> unknown
                    end;
                _ -> unknown
            end,
            %% 经验副本-日常已通关等级(层数)
            if DunType == ?DUNGEON_TYPE_EXP -> DailyLevel = unknown;
                true -> DailyLevel = 0
            end,
            %% 爬塔副本-每日奖励领取
            if DunType == ?DUNGEON_TYPE_TOWER -> DailyPass = unknown;
                true -> DailyPass = 0
            end,
            %% 周次数
            WeeklyCount =
             case lists:keyfind(?DUN_COUNT_COND_WEEK, 1, CountCondition) of
                false -> 0;
                _ -> unknown
            end,
            %% 永久次数
            PermanentCount =
             case lists:keyfind(?DUN_COUNT_COND_PERMANENT, 1, CountCondition) of
                false -> 0;
                _ -> unknown
            end,
            PermanentPass = unknown,
            #dungeon_info{id = DunId, data = Data, daily_count = DailyCount, weekly_count = WeeklyCount,
             permanent_count = PermanentCount, permanent_pass = PermanentPass, daily_level = DailyLevel,
             daily_pass = DailyPass}
    end,
    [F(DunId) || DunId <- Ids].

%% 根据副本类型获取副本数据
get_dungeon_data(RoleId, DataList, DunType, MFA) ->
    DailyDataList  = get_daily_data(RoleId, [{DunType, DataList}]),
    WeeklyDataList = get_weekly_data(RoleId, DailyDataList),
    get_permanent_data(RoleId, WeeklyDataList, MFA).

check_need_get_daily_data(?DUNGEON_TYPE_TOWER, DataList) ->
    Res = lists:keyfind(unknown, #dungeon_info.daily_pass, DataList),
    if Res == false ->
            false;
        true ->
            true
    end;
check_need_get_daily_data(_DunType, DataList) ->
    Res = lists:keyfind(unknown, #dungeon_info.daily_count, DataList),
    Res1 = lists:keyfind(unknown, #dungeon_info.daily_level, DataList),
    if Res == false andalso Res1 == false ->
            false;
        true ->
            true
    end.

%% 获取日常数据
get_daily_data(RoleId, List) ->
    DailyDataList =
     lists:foldl(fun({DunType, DataList}, Acc) ->
        case check_need_get_daily_data(DunType, DataList) of
            true ->
                [{DunType, do_get_daily_data(RoleId, DataList, DunType)}|Acc];
            _ ->
                [{DunType, DataList}|Acc]
        end
    end, [], List),
    DailyDataList.

do_get_daily_data(RoleId, DataList, DunType) ->
    DataListWithDailyCount =
     case lib_dungeon_api:get_daily_count_type(DunType, -1) of
        DunType ->
            DailyCount = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunType),
            [Data#dungeon_info{daily_count = DailyCount} || Data <- DataList];
        _ ->
            [begin
                DailyCount = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, Data#dungeon_info.id),
                Data#dungeon_info{daily_count = DailyCount}
            end || Data <- DataList]
    end,
    DataListWithBuyCount =
     case lib_dungeon_api:get_daily_buy_type(DunType, -1) of
        DunType ->
            BuyCount = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY, DunType),
            BuyUseCount = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, DunType),
            [Data#dungeon_info{buy_count = BuyCount, buy_use = BuyUseCount} || Data <- DataListWithDailyCount];
        _OtherRet when DunType == ?DUNGEON_TYPE_MATERIAL ->
            [begin
                #dungeon_info{id = DunId} = Data,
                BuyCount = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY, DunId),
                GoldBuy  = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_GOLD_BUY, DunId),
                BuyUseCount = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, DunId),
                Data#dungeon_info{buy_count = BuyCount, gold_buy = GoldBuy, buy_use = BuyUseCount}
            end || Data <- DataListWithDailyCount];
        _ ->
            [begin
                #dungeon_info{id = DunId} = Data,
                BuyCount = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY, DunId),
                BuyUseCount = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_BUY_USE, DunId),
                Data#dungeon_info{buy_count = BuyCount, buy_use = BuyUseCount}
            end || Data <- DataListWithDailyCount]
    end,
    DataListWithPassCount =
     case lib_dungeon_api:get_daily_pass_type(DunType, -1) of
        DunType ->
            PassCount = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_SUCCESS, DunType),
            [Data#dungeon_info{daily_pass = PassCount} || Data <- DataListWithBuyCount];
        _ ->
            DailyType = if DunType == ?DUNGEON_TYPE_TOWER -> ?MOD_DUN_SWEEP_SIGN;
                true -> ?MOD_DUNGEON_SUCCESS
            end,
             [begin
                 PassCount = lib_daily:get_count(RoleId, ?MOD_DUNGEON, DailyType, Data#dungeon_info.id),
                 Data#dungeon_info{daily_pass = PassCount}
                end || Data <- DataListWithBuyCount]
    end,
    DataListWithDailyLevel =
     case lib_dungeon_api:get_daily_level_type(DunType, -1) of
        DunType ->
            DailyLevel = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_PASS_LV, DunType),
            [Data#dungeon_info{daily_level = DailyLevel} || Data <- DataListWithPassCount];
        _ ->
            [begin
                DailyLevel = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_PASS_LV, Data#dungeon_info.id),
                Data#dungeon_info{daily_level = DailyLevel}
            end || Data <- DataListWithPassCount]
    end,
    if DunType == ?DUNGEON_TYPE_EXP ->
            [begin
                case lib_dungeon_exp:get_dun_sweep_sign(RoleId, DunData) of
                    Sign when is_integer(Sign) ->
                        DunData1 = lists:keystore(?MOD_DUN_SWEEP_SIGN, 1, DunData, {?MOD_DUN_SWEEP_SIGN, Sign}),
                        Data#dungeon_info{data = DunData1};
                    _ ->
                        Data
                end
            end || #dungeon_info{data = DunData} = Data <- DataListWithDailyLevel];
        true ->
            DataListWithDailyLevel
    end.

%% 获取周常数据
get_weekly_data(RoleId, List) ->
    WeeklyDataList =lists:foldl(fun({DunType, DataList}, Acc) ->
        case lists:keyfind(unknown, #dungeon_info.weekly_count, DataList) of
            #dungeon_info{} ->
                [{DunType, do_get_weekly_data(RoleId, DataList)}|Acc];
            _  ->
                [{DunType, DataList}|Acc]
        end
    end, [], List),
    WeeklyDataList.

do_get_weekly_data(RoleId, DataList) ->
    [begin
        WeeklyCount = lib_week:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, Data#dungeon_info.id),
        Data#dungeon_info{weekly_count = WeeklyCount}
    end || Data <- DataList].

get_permanent_data(RoleId, List, {M,F,A}) ->
    case lists:any(fun
        ({_, DataList}) ->
            lists:keyfind(unknown, #dungeon_info.permanent_count, DataList) =/= false orelse
            lists:keyfind(unknown, #dungeon_info.permanent_pass, DataList) =/= false
    end, List) of
        false ->
            M:F(RoleId, List, A);
        _ ->
            PermanentDataList = [{DunType, do_get_permanent_data(RoleId, DunType, DataList)} || {DunType, DataList} <- List],
            M:F(RoleId, PermanentDataList, A)
    end.

do_get_permanent_data(RoleId, DunType, DataList) ->
    [begin
        PermanentCount = lib_counter:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, Data#dungeon_info.id),
        if
            DunType =:= ?DUNGEON_TYPE_PER_BOSS ->
                PermanentPass = lib_counter:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_FIRTS_ENTER, Data#dungeon_info.id);
            DunType =/= ?DUNGEON_TYPE_GUARD ->
                PermanentPass = lib_counter:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_SUCCESS, Data#dungeon_info.id);
            true ->
                #dungeon_info{id = DunId, data = DunInfo} = Data,
                CfgMaxWave = lists:max(data_dungeon_wave:get_dun_waves(DunId)),
                PermanentPass =
                case lists:keyfind(?DUNGEON_REC_MAX_SCORE, 1, DunInfo) of
                    {_, MaxWave} ->
                        ?IF(MaxWave >= CfgMaxWave, 1, 0);
                    _ ->
                        0
                end
        end,
        Data#dungeon_info{permanent_count = PermanentCount, permanent_pass = PermanentPass}
    end || Data <- DataList].

send_dungeon_info(RoleId, [{DunType, DataList}], _) ->
    FormatList = [begin
        case lists:keyfind(?DUNGEON_REC_MAX_STARS, 1, Data) of
            {_, MaxStarEvents} when is_list(MaxStarEvents) ->
                MaxStar = length(MaxStarEvents),
                NewData = lists:keydelete(?DUNGEON_REC_MAX_STARS, 1, Data);
            _  ->
                NewData = Data,
                MaxStar = 0
        end,
        %% BuyCount为VIP购买总次数
        %% BuyUseCount为购买已使用次数,只有VIP购买和VIP购买已使用次数不一致的副本使用,一般两种次数都是同时增加,不会需要用到
        %% (除少量副本如宠物副本、专属幻魔等之外)
        %% GoldBuy为VIP彩钻购买次数,包含于VIP购买总次数中
        %% 后期为了尽量不改协议字段,决定将某些字段放在KV形式的数组中
        NewData1 = lists:keystore(?MOD_DUNGEON_BUY_USE, 1, NewData, {?MOD_DUNGEON_BUY_USE, BuyUseCount}),
        NewData2 = lists:keystore(10, 1, NewData1, {10, GoldBuy}),
        IsPass      = ?IF(PermanentPass == 0, 0, 1),
        IsDailyPass = ?IF(DailyPass == 0, 0, 1),
        {DunId, BuyCount, DailyCount, IsPass, IsDailyPass, MaxStar, DailyLevel, NewData2} end ||
        #dungeon_info{
            id = DunId, daily_count = DailyCount, weekly_count = _WeeklyCount, daily_pass = DailyPass, permanent_pass = PermanentPass,
            buy_count = BuyCount, gold_buy = GoldBuy, buy_use = BuyUseCount, daily_level = DailyLevel, data = Data
        } <- DataList
    ],
    {ok, BinData} = pt_610:write(61020, [DunType, FormatList]),
    lib_server_send:send_to_uid(RoleId, BinData).

daily_reset(Player, ?FOUR) ->
    lib_dungeon_api:role_daily_reset(Player);

daily_reset(Player, _) ->
    Player.

get_mul_drop_times(#dun_cfg{type = DunType}, #player_status{figure = Figure}) ->
    #figure{lv = Lv} = Figure,
    OpenLv = lib_custom_act_util:get_act_open_lv(?CUSTOM_ACT_TYPE_DUN_MUL_DROP),
    case Lv >= OpenLv of
        true ->
            case lib_dungeon_api:get_custom_act_drop_times(DunType) of
                N when N > 1 ->
                    N;
                _ ->
                    1
            end;
        false -> 1
    end.

%% 根据副本id判断是否跨服副本
is_cluster_dungeon_by_dun_id(DunId) ->
    case data_dungeon:get_dun(DunId) of
        #dun_cfg{scene_id = SceneId} ->
            case data_scene:get(SceneId) of
                #ets_scene{cls_type = ?SCENE_CLS_TYPE_CENTER} ->
                    true;
                _ ->
                    false
            end;
        _ ->
            false
    end.

%% 小飞鞋是否可以使用
is_allow_fly_shoes(_) -> false.

%% 是否通过某个类型的副本
is_pass_dun_type(DunType, DungeonRecord) ->
    DunIds = data_dungeon:get_ids_by_type(DunType),
    check_is_pass_dun_type(DunIds, DungeonRecord, false).

check_is_pass_dun_type([], _DungeonRecord, Result) -> Result;
check_is_pass_dun_type([DunId | T], DungeonRecord, Result) ->
    case maps:get(DunId, DungeonRecord, false) of
        false -> check_is_pass_dun_type(T, DungeonRecord, Result);
        _ -> true
    end.

%% 获某个副本类型的历史最高纪录
get_pass_dun_top_score(DunType, DungeonRecord) ->
    DunIds = data_dungeon:get_ids_by_type(DunType),
    ScoreList = do_get_pass_dun_top_score(DunIds, DungeonRecord, []),
    lists:max([0 | ScoreList]).

do_get_pass_dun_top_score([], _DungeonRecord, Result) -> Result;
do_get_pass_dun_top_score([DunId | T], DungeonRecord, Result) ->
    case maps:get(DunId, DungeonRecord, false) of
        DataList when is_list(DataList) ->
            case lists:keyfind(?DUNGEON_REC_MAX_SCORE, 1, DataList) of
                {_, Score} when is_integer(Score) -> do_get_pass_dun_top_score(T, DungeonRecord, [Score| Result]);
                {_, ScoreL} when is_list(ScoreL)  -> do_get_pass_dun_top_score(T, DungeonRecord, [length(ScoreL)|Result]);
                _ -> do_get_pass_dun_top_score(T, DungeonRecord, Result)
            end;
        _ -> do_get_pass_dun_top_score(T, DungeonRecord, Result)
    end.

%% 假人奖励
send_dummy_reward(DunType, DunId, MirrorId, RoleName) ->
    Pid = misc:get_player_process(MirrorId),
    case is_pid(Pid) andalso misc:is_process_alive(Pid) of
        true ->
            lib_player:apply_cast(MirrorId, ?APPLY_CAST_SAVE, ?MODULE, role_send_dummy_reward, [DunType, DunId, RoleName]);
        _ ->
            DailyRewardCount = lib_daily:get_count_offline(MirrorId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_DUMMY_MIRROR_REWARD, DunType),
            CfgMaxCount = data_dungeon_special:get(6, crusade_dun_assist_award_num),
            if DailyRewardCount >= CfgMaxCount ->
                    skip;
                true ->
                    Title       = ?LAN_MSG(6100014),
                    Content     = uio:format(?LAN_MSG(6100015), [RoleName, DailyRewardCount+1]),
                    CmAttachment = data_dungeon_special:get(6, crusade_dun_assist_award),
                    lib_mail_api:send_sys_mail([MirrorId], Title, Content, CmAttachment),
                    lib_daily:increment_offline(MirrorId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_DUMMY_MIRROR_REWARD, DunType)
            end
    end.

role_send_dummy_reward(Player, DunType, _DunId, RoleName) ->
    #player_status{id = RoleId} = Player,
    DailyRewardCount = lib_daily:get_count_offline(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_DUMMY_MIRROR_REWARD, DunType),
    CfgMaxCount = data_dungeon_special:get(6, crusade_dun_assist_award_num),
    if DailyRewardCount >= CfgMaxCount ->
            skip;
        true ->
            Title       = ?LAN_MSG(6100014),
            Content     = uio:format(?LAN_MSG(6100015), [RoleName, DailyRewardCount+1]),
            CmAttachment = data_dungeon_special:get(6, crusade_dun_assist_award),
            lib_mail_api:send_sys_mail([RoleId], Title, Content, CmAttachment),
            lib_daily:increment_offline(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_DUMMY_MIRROR_REWARD, DunType)
    end,
    {ok, Player}.

%% -----------------------------------------------------------------
%% 登录相关处理
%% -----------------------------------------------------------------
login(Id) ->
    StatusDun = case db_select_dun_data(Id) of
        []-> #status_dungeon{};
        List ->
            DunData = [make_dun_data(One) || One <- List],
            #status_dungeon{dun_data = DunData}
    end,
    lib_dungeon_flop:login(Id, StatusDun).

make_dun_data([DunType, Data]) ->
    TermData = util:bitstring_to_term(Data),
    {DunType, TermData}.

%%获取通过最高层
get_dungeon_level(#player_status{dungeon_record = undefined} = Player, DunType) ->
    NewPlayer = lib_dungeon:load_dungeon_record(Player),
    lib_player:apply_cast(Player#player_status.id, ?APPLY_CAST_SAVE, lib_dungeon, load_dungeon_record, []),
    get_dungeon_level(NewPlayer, DunType);
get_dungeon_level(Player, DunType) ->
    #player_status{dungeon_record = Rec} = Player,
    Ids = data_dungeon:get_ids_by_type(DunType),
    lists:foldl(fun
        (Id, Acc) ->
            case maps:is_key(Id, Rec) of
                true ->
                    Acc + 1;
                _ ->
                    Acc
            end
    end, 0, Ids).

send_to_uid(Node, RoleId, Bin) ->
    case node() of
        Node -> lib_server_send:send_to_uid(RoleId, Bin);
        _ -> clusters_center:apply_cast(Node, lib_server_send, send_to_uid, [RoleId, Bin])
    end.

%% 使用次数卡增加挑战次数
use_goods_add_daily_times(Player, GoodsNum) ->
    #player_status{id = RoleId, figure = #figure{name = RoleName}} = Player,
    DailyTimes = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, ?DUNGEON_TYPE_PER_BOSS),
    if
        DailyTimes =< 0 ->
            {fail, ?ERRCODE(err610_has_max_times)};
        GoodsNum > DailyTimes ->
            {fail, ?FAIL};
        true ->
            lib_goods_api:cost_object_list(Player, [{0, ?BOSS_TIMES_CARD_2, GoodsNum}], goods_use, ""),
            lib_daily:cut_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, ?DUNGEON_TYPE_PER_BOSS, GoodsNum),
            lib_log_api:log_boss_change([RoleId, RoleName, 6, GoodsNum, DailyTimes, 3, 0, utime:unixtime()]),
            pp_dungeon:handle(61020, Player, [?DUNGEON_TYPE_PER_BOSS]),
            {ok, Player, GoodsNum}
    end.

%% -----------------------------------------------------------------
%% 副本相关data信息db处理
%% -----------------------------------------------------------------
%% select
db_select_dun_data(RoleId) ->
    Sql = io_lib:format(?SQL_DUN_DATA_SELECT, [RoleId]),
    db:get_all(Sql).

%% replace
db_replace_dun_data(RoleId, DunType, Data)->
    BitData = util:term_to_bitstring(Data),
    Sql = io_lib:format(?SQL_DUN_DATA_REPLACE, [RoleId, DunType, BitData]),
    db:execute(Sql).

%% -----------------------------------------------------------------
%% 秘籍相关
%% -----------------------------------------------------------------
clear_dun_enter_count(PlayerId, DunId) ->
    case data_dungeon:get_dun(DunId) of
        #dun_cfg{type = DunType} ->
            lib_daily:set_count_offline(PlayerId, ?MOD_DUNGEON, ?MOD_DUNGEON_ENTER, DunType, 0),
            ok;
        _ ->
            error
    end.