%% -----------------------------
%% desc  :事件&操作分发默认处理
%% module:dungeon_default.erl
%% author:hh
%% time  :19/11/07
%% -----------------------------

-module(dungeon_default).


%% 通用默认接口
-export([
    get_login_args/2                        %%
    ,get_role_dun_born_xy/4                 %% 获取人物出生点默认操作
    ,get_send_reward_normal/2               %% 计算奖励默认操作
    ,send_dun_rewards_normal/1              %% 发奖励默认操作
    ,push_settlement_normal/2               %% 推送结算展示界面默认操作

    ,handle_wave_state_normal/2             %% 波数状态处理默认操作
    ,handle_kill_mon_do_normal/2            %% 怪物击杀处理默认操作
    ,make_grade_event_normal/1              %% 评分事件初始化默认操作
    ,dispatch_dungeon_score_event_normal/4  %% 评分事件触发默认操作
    ,make_grade_rating_normal/1             %% 通用评级事件默认操作
    ,dispatch_dungeon_rating_normal/3
    ,calc_rating_rewards_normal/3
    ,handle_role_out_normal/5               %% 人物退出副本默认操作

    ,create_dummy/2                         %% 创建假人
    ,enter_next_dun/4                       %% 副本内挑战下一关
    ,calc_rating_drop_normal/1              %% 通用评级掉落操作
    ]).

%% 事件分发
-export([
    dispatch_dun_event/2
    ]).

%% 人物进程
-export([
    do_log_single_dungeon_help/2
    ,role_update_dun_score_record/2
    ,role_update_max_rating/2
    ,role_calc_rating_drop_normal/5
    ]).

%% 常规接口
-export([
    notice_score_event_update/2
    ,calc_dun_score_event_id/1
    ,calc_dun_score/1
    ,calc_dun_rating/2
    ,calc_reward_with_rand/1
    ,create_dun_wave/5
    ,create_dun_wave_mon/3
    ,make_record/2
    ,calc_next_time/3
    ,push_settlement_delay/2
    ,send_dun_rewards_do/3
    ,send_role_dun_rewards/4
    ,check_all_dun_role_out/2
    ,filter_anger_skill/2
    ,check/1
    ]).

%% 假人回调
-export([
    dummy_init_callback/3
    ]).

-export([
    role_gm_get_mon_info/2
    ,role_proc_gm_get_mon_info/3
    ,gm_get_mon_info/2
    ]).

-include("dungeon.hrl").
-include("dungeon_multi.hrl").
-include("dungeon_pet.hrl").
-include("team.hrl").
-include("predefine.hrl").
-include("figure.hrl").
-include("relationship.hrl").
-include("common.hrl").
-include("def_fun.hrl").
-include("scene.hrl").
-include("rec_event.hrl").
-include("def_event.hrl").
-include("server.hrl").
-include("def_goods.hrl").
-include("drop.hrl").
-include("skill.hrl").
-include("def_module.hrl").
-include("errcode.hrl").
-include("attr.hrl").
-include("custom_act.hrl").

-ifdef(DEV_SERVER).
-define(PRINT_DUN_MSG, true).
-else.
-define(PRINT_DUN_MSG, false).
-endif.


%% ---------------------- 通用事件处理 --------------------------

%% 初始化波数
dispatch_dun_event({'init_dun_wave', Wave}, State) ->
    #dungeon_state{dun_id = DunId} = State,
    DunWaveCfg = data_dungeon_wave:get_dun_wave(DunId, Wave),
    if is_record(DunWaveCfg, base_dun_wave_cfg) ->
            NewState = init_dun_wave_do(DunWaveCfg, State),
            NewState;
        true ->
            State
    end;

%% 创怪
dispatch_dun_event({'create_mon', Wave, MonList}, State) ->
    NewState = create_dun_wave_mon(MonList, Wave, State),
    NewState;

%% 结算
dispatch_dun_event({'dungeon_result', _ResultType, _ResultSubtype},
     State = #dungeon_state{is_end = ?DUN_IS_END_YES}) ->
    State;
dispatch_dun_event({'dungeon_result', ResultType, ResultSubtype}, State) ->
    #dungeon_state{
        dun_id = DunId, dun_type = DunType, role_list = RoleList,
        scene_id = SceneId, scene_pool_id = ScenePoolId
    } = State,
    %% 人物副本次数更新
    NewRoleList = dun_increment(RoleList, DunId, ResultType),

    %% 评分事件相关
    NewState0 = dun_grade_event_update_after_result(State#dungeon_state{role_list = NewRoleList}, ResultType),
    NewState1 = dun_rating_update_after_result(NewState0, ResultType),

    ArgsMap = #{dun_id => DunId, dun_type => DunType, result_type => ResultType, out => #dungeon_out{}},
    Now = utime:unixtime(),
    NewState2 = dungeon_end(NewState1, ResultType, ResultSubtype, ArgsMap, Now),

    %% 计算奖励
    NewState3 = calc_role_rewards(NewState2),
    %% 发奖励
    NewState4 = send_dun_rewards(NewState3),

    %% 结算后处理
    NewState5 = lib_dungeon_distribute:handle_dungeon_result(NewState4),

    %% 推送结算信息
    push_dun_result(DunType, NewState5),

    %% 日志
    log_dungeon(NewState5, ?DUN_LOG_TYPE_RESULT, []),

    %% TA数据上报
    ta_multi_dun_ret(NewState5, Now),

    case dungeon_logic:nobody_in_dungeon(NewState5) of
        true -> self() ! stop;
        _    -> ok
    end,

    % 清理场景怪物
    case lib_dungeon_distribute:dungeon_end_clear_mon(NewState5, ResultType) of
        true ->
            lib_mon:clear_scene_mon(SceneId, ScenePoolId, self(), 1);
        _ -> ok
    end,

    FinalState = NewState5,
    FinalState;

dispatch_dun_event({'init_friendly_objects'}, State) ->
    State;

dispatch_dun_event(_Msg, State) ->
    ?PRINT_DUN_MSG andalso ?ERR("final default dispatch msg ~n ~w", [_Msg]),
    State.


%% -------------------- 默认操作处理 -------------------------

push_dun_result(DunType, State) ->
    #dungeon_state{role_list = RoleList, result_type = ResultType} = State,
    case lists:member(DunType, ?DUN_RESULT_PUSH_EXCLUSIVE) of
        false ->
            if 
                DunType == ?DUNGEON_TYPE_PER_BOSS andalso ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
                    erlang:send_after(5000, self(), {apply, ?MODULE, push_settlement_delay, []});
                DunType == ?DUNGEON_TYPE_VIP andalso ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
                    erlang:send_after(1000, self(), {apply, ?MODULE, push_settlement_delay, []});
                DunType == ?DUNGEON_TYPE_GUIDE_BOSS andalso ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
                    erlang:send_after(2000, self(), {apply, ?MODULE, push_settlement_delay, []});
                true ->
                    [lib_dungeon_distribute:push_settlement(DunType, State, DungeonRole) || DungeonRole <- RoleList]
            end;
        _ ->
            skip
    end.

push_settlement_delay(State, _) ->
    #dungeon_state{dun_type = DunType, role_list = RoleList} = State,
    [lib_dungeon_distribute:push_settlement(DunType, State, Role) || Role <- RoleList].

%%断线重连获取参数
get_login_args(_State, _RoleId) -> [].

%% 副本波数操作
init_dun_wave_do(DunWaveCfg, State) ->
    #dungeon_state{role_list = RoleList, dun_type = DunType, dun_id = DunId, wave_ref = WaveRef, wave_num = WaveNum} = State,
    %% 某些副本波数是由超时定时器控制,在定时器触发前副本可能已经结算(失败或者其他情况),此处需要做容错
    case WaveRef of
        [OldWaveStartRef, OldWaveEndRef|_] -> ok;
        _ -> OldWaveStartRef = [], OldWaveEndRef = []
    end,
    #base_dun_wave_cfg{limit_time = LimitTime, create_time = CreateTime, mon_list = MonList, wave = Wave, next_wave = NextWave} = DunWaveCfg,
    %% 由于有连环波数的存在,需要对CreateTime重新设定
    CreateTime1 = new_create_time(DunType, RoleList, Wave, CreateTime),
    %% 宠物副本定时(上一波超时即创建)创建波数怪,上一波完成触发下一波时可能已经创建过了,因此需要过滤
    case Wave =< WaveNum of
        true -> State;
        _ ->
            Now = utime:unixtime(),
            WaveStartTime = Now + CreateTime1,
            TrueMonNum = get_mon_num(DunType, MonList),
            NewState0  = create_dun_wave(Wave, NextWave, TrueMonNum, WaveStartTime, State),
            {WaveCreateRef, NewState1} = check_role_load_sign(OldWaveStartRef, MonList, Wave, CreateTime1, NewState0),
            if  
                LimitTime == 0 ->
                    util:cancel_timer(OldWaveEndRef),
                    WaveEndRef = undefined;
                true ->
                    WaveEndRef = util:send_after(OldWaveEndRef, (LimitTime+CreateTime1)*1000, self(), {'wave_timeout', DunId, Wave, NewState1#dungeon_state.index_of_wave})
            end,
            NewState2 = NewState1#dungeon_state{wave_ref = [WaveCreateRef, WaveEndRef]},
            NewState2
    end.

%% 检查玩家是否已经加载完场景
%% 还没加载场景则等待一定时间后再初始化
check_role_load_sign(OldWaveStartRef, MonList, Wave, CreateTime, State) ->
    #dungeon_state{init_wave_sign = InitWaveSign} = State,
    if
        InitWaveSign =/= ?INIT_WAVE_LOAD_SIGN -> %% 默认初始波数需前端加载驱动
            WaveCreateRef = util:send_after(OldWaveStartRef, ?INIT_WAVE_CREATE_TIME, self(), {'create_init_dun_wave_mon', ?INIT_WAVE_LOAD_SIGN, Wave, MonList}),
            State1 = State;
        true ->
            if
                CreateTime == 0 ->
                    WaveCreateRef = undefined,
                    State1 = create_dun_wave_mon(MonList, Wave, State);
                true ->
                    WaveCreateRef = util:send_after(OldWaveStartRef, CreateTime*1000, self(), {'create_dun_wave_mon', Wave, MonList}),
                    State1 = State
            end
    end,
    {WaveCreateRef, State1}.

%% 重新计算波数怪预创建时间
new_create_time(DunType, RoleList, Wave, CreateTime) ->
    case lists:member(DunType, [?DUNGEON_TYPE_CHILD]) of
        true ->
            case RoleList of
                [#dungeon_role{typical_data = TypicalData}|_] ->
                    InitWave = maps:get(?DUN_ROLE_CHILD_INIT_WAVE, TypicalData, 1),
                    case Wave > InitWave of
                        true -> 0;
                        _ -> CreateTime
                    end;
                _ -> CreateTime
            end;
        _ ->
            CreateTime
    end.

%% 重新计算波数里面的怪物数据
get_mon_num(DunType, MonList) ->
    case lists:member(DunType, [?DUNGEON_TYPE_CHILD]) of
        true ->
            MonList1 = [MonId ||{MonId, _X, _Y, _Ratio, _Args} <- MonList, MonId =/= 2300051],
            erlang:length(MonList1);
        _ ->
            erlang:length(MonList)
    end.


%% 副本默认奖励
%% 1)基础奖励+波数+随机 或 首通奖励+波数+随机
%% 2)星级奖励视条件而定
get_send_reward_normal(State, DungeonRole) ->
    #dungeon_state{dun_type = DunType, dun_id = DunId, result_type = ResultType, wave_info = WaveInfos} = State,
    #dungeon_role{is_first  = FirstFinished, help_type = HelpType, figure = Figure, typical_data = TypicalData} = DungeonRole,
    if
        ResultType =:= ?DUN_RESULT_TYPE_SUCCESS andalso HelpType =:= ?DUN_TEAM_HELP_NO ->
            case data_dungeon:get_dun(DunId) of
                #dun_cfg{base_reward = BaseRewards, first_reward = FirstRewards, rand_reward = RandRewards} -> ok;
                _ -> BaseRewards = [], FirstRewards = [], RandRewards = []
            end,
            PassRewards = if not FirstFinished -> FirstRewards;
                true   -> BaseRewards
            end,
            PassRewards1 = calc_reward_with_rand(PassRewards),
            RandRewards1 = calc_reward_with_rand(RandRewards),
            %% 波数奖励
            WaveRewards = calc_wave_rewards(DunId, FirstFinished, WaveInfos),
            {ExtraArgs, ScoreRewards} = lib_dungeon_distribute:calc_score_rewards(DunType, DunId, DungeonRole),
            lib_dungeon_distribute:handle_score_reward_rec(DunType, DungeonRole, DunId, ExtraArgs),
            RatingRewards = lib_dungeon_distribute:calc_rating_rewards(DunType, DunId, DungeonRole),
            FinalRewards  = lists:append([PassRewards1, ScoreRewards, RatingRewards, RandRewards1, WaveRewards]),
            FinalRewards1 = lib_goods_api:make_reward_unique(FinalRewards),
            %% 副本多倍活动
            %% 以下算法只能部分副本通用,其他副本由于首通奖励拿的不是first_reward/有自己的结算逻辑(幻光，宠物，宝宝，守卫，星辰)
            %% 副本：材料副本，vip副本
            CustomAddReward =
            case FirstFinished of
                false ->
                    case lib_dungeon_api:get_custom_act_reward_times(DunType, DunId, Figure#figure.lv) of
                        Times when Times > 1 ->
                            CustomBaseReward  = lists:append([ScoreRewards, RatingRewards, RandRewards1, WaveRewards]),
                            CustomBaseReward1 = lib_goods_api:make_reward_unique(CustomBaseReward),
                            lib_dungeon_api:calc_multi_times_goods(CustomBaseReward1, Times);
                        _ ->
                            []
                    end;
                true ->
                    case lib_dungeon_api:get_custom_act_reward_times(DunType, DunId, Figure#figure.lv) of
                        Times when Times > 1 ->
                            lib_dungeon_api:calc_multi_times_goods(FinalRewards1, Times);
                        _ ->
                            []
                    end
            end,
            %% 联合buff/材料提交 多倍奖励活动
            UnitedBuffNum = maps:get(?DUN_ROLE_MAT_REWARD_BUFF, TypicalData, 0),
            CustomAddReward1 = calc_united_buff_reward(DunType, FinalRewards1, UnitedBuffNum),
            CustomAddReward2 = lib_goods_api:make_reward_unique(lists:append([CustomAddReward, CustomAddReward1])),
            DungeonRole#dungeon_role{calc_reward_list = FinalRewards1, custom_reward_list = CustomAddReward2};
        true -> % 助战没有奖励
            DungeonRole
    end.

calc_wave_rewards(DunId, IsFirst, WaveInfos) ->
    lists:foldl(fun(#dun_wave_info{key = {WaveId, _}, status = Status}, TmpRewards) ->
        if Status == 1 ->
                case data_dungeon_wave:get_dun_wave(DunId, WaveId) of
                    #base_dun_wave_cfg{normal_reward = NormalRewards, first_reward = FirstRewards} ->
                        if not IsFirst -> lists:append([FirstRewards, TmpRewards]);
                            true ->
                                lists:append([NormalRewards, TmpRewards])
                        end;
                    _ ->
                        TmpRewards
                end;
            true ->
                TmpRewards
        end
     end, [], WaveInfos).

calc_reward_with_rand(RandRewards = []) -> RandRewards;
calc_reward_with_rand(RandRewards) ->
    ResultReward = case RandRewards of
        [{_GoodsType, _GoodsId, _GoodsNum}|_] ->
            RandRewards;
        [RandNum, RandGoodsList|_] ->
            GoodsList = urand:list_rand_by_weight(RandGoodsList, RandNum),
            lists:append(GoodsList);
        _ -> %% 格式有问题
            ?ERR("error format rewardlist ~p", [RandRewards]),
            []
    end,
    ResultReward1 = lists:filter(fun({GoodsType, _GoodsId, GoodsNum}) ->
        GoodsType =/= 0 orelse GoodsNum =/= 0
     end, ResultReward),
    ResultReward1.

calc_rating_rewards_normal(_DunType, DunId, DungeonRole) ->
    #dungeon_role{grade_rating = GradeRating} = DungeonRole,
    FilterRating = lists:foldl(fun({Rating, _, _, Status}, Acc) ->
        if Status == ?DUN_SCORE_FINISH ->
                [Rating|Acc];
            true ->
                Acc
        end
     end, [], GradeRating),
    case FilterRating of
        [_|_] ->
            MaxRating = lists:max(FilterRating),
            case data_dungeon_grade:get_dun_rating(DunId, MaxRating) of
                #base_dun_grade_rating{rewards = Rewards = [_|_]} ->
                    case Rewards of
                        [{_GoodsType, _GoodsId, _GoodsNum}|_] -> Rewards;
                        _ -> []
                    end;
                _ ->
                    []
            end;
        _ ->
            case GradeRating of
                [_|_] ->
                    case data_dungeon_grade:get_dun_grades(DunId) of
                        Ratings = [_|_] ->
                            MinRating = lists:min(Ratings),
                            case data_dungeon_grade:get_dun_rating(DunId, MinRating) of
                                #base_dun_grade_rating{rewards = Rewards = [_|_]} ->
                                    case Rewards of
                                        [{_GoodsType, _GoodsId, _GoodsNum}|_] -> Rewards;
                                        _ -> []
                                    end;
                                _ -> []
                            end;
                        _ -> []
                    end;
                _ -> []
            end
    end.

%% 联合buff/材料提交活动的额外多倍奖励
calc_united_buff_reward(?DUNGEON_TYPE_MATERIAL, Rewards, UnitedBuffNum)
 when UnitedBuffNum =/= 0 ->
    Num = round(UnitedBuffNum/100),
    if Num > 0 -> lib_dungeon_api:calc_multi_times_goods(Rewards, Num);
        true   -> []
    end;
calc_united_buff_reward(_DunType, _Rewards, _) ->
    [].

%% 结算界面推送
%% 结算推送默认会检查翻牌逻辑
push_settlement_normal(State, DungeonRole) ->
    #dungeon_state{
        result_type = ResultType,
        dun_id = DunId,
        dun_type = DunType,
        scene_id = SceneId,
        wave_info = WaveInfos,
        wave_num       = WaveNum,
        result_subtype = ResultSubtype
    } = State,
    #dungeon_role{node = Node, id = RoleId, custom_reward_list = CustomReward} = DungeonRole,
    %%评分
    % Score = lib_dungeon:calc_score(State, DungeonRole#dungeon_role.id),
    case data_dungeon:get_dun(DunId) of
        #dun_cfg{flop_pools = [_|_]} -> %% 有翻牌配置即要求翻牌
            ResultType == ?DUN_RESULT_TYPE_SUCCESS andalso lib_player:apply_cast(Node, RoleId, ?APPLY_CAST_SAVE, ?NOT_HAND_OFFLINE, lib_dungeon_flop, start_dun_flop, [DunType, DunId]);
        _ -> ok
    end,
    %%奖励
    RewardList = lib_dungeon:calc_base_reward_list(DungeonRole),
    KvData = lib_dungeon_distribute:pack_account_kv_data(State, RoleId),
    case DunType of
        %% 宠物副本如果有奖励,推送成功界面
        ?DUNGEON_TYPE_PET when RewardList =/= [] ->
            ResultType1 = ?DUN_RESULT_TYPE_SUCCESS;
        ?DUNGEON_TYPE_PER_BOSS when WaveNum > 1 ->
            ResultType1 = ?DUN_RESULT_TYPE_SUCCESS;
        _ ->
            ResultType1 = ResultType
    end,
    case ResultType1 of
        ?DUN_RESULT_TYPE_SUCCESS ->
            case lists:foldl(fun(WaveInfo = #dun_wave_info{key = {Wave, _}}, Acc) -> 
                if Wave == WaveNum -> [WaveInfo|Acc];
                    true -> Acc
                end
            end, [], WaveInfos) of
                [#dun_wave_info{status = Status}|_] ->
                    if Status == ?DUN_WAVE_FIN -> NewWaveNum = WaveNum;
                        true -> NewWaveNum = max(0, WaveNum-1)
                    end;
                _ ->
                    NewWaveNum = WaveNum
            end;
        _ ->
            NewWaveNum = WaveNum
    end,
    %% 宠物副本完整击杀的波数不一定是当前波数(需要统计通关波数)
    case DunType of
        ?DUNGEON_TYPE_PET ->
            case lists:foldl(fun(#dun_wave_info{key = {Wave, _}, status = Status}, Acc) ->
                if Status == ?DUN_WAVE_FIN -> [Wave|Acc];
                    true -> Acc
                end end, [], WaveInfos) of
                [_Wave|_] = WaveList ->
                    NewWaveNum1 = erlang:length(WaveList);
                _ ->
                    NewWaveNum1 = 0
            end;
        _ ->
            NewWaveNum1 = NewWaveNum
    end,
    {ok, BinData} = pt_610:write(61003, [ResultType1, ResultSubtype, DunId, SceneId, NewWaveNum1, RewardList, CustomReward, KvData]),
    dungeon_logic:send_to_uid(Node, RoleId, BinData).


%% 根据波数状态处理默认逻辑
handle_wave_state_normal(State, [?WAVE_NEXT|_]) ->
    #dungeon_state{wave_num = WaveNum} = State,
    lib_dungeon_distribute:init_dun_wave(State, _NewWave = WaveNum + 1);
handle_wave_state_normal(State, [?WAVE_CYCLE|_]) ->
    #dungeon_state{wave_num = WaveNum} = State,
    lib_dungeon_distribute:init_dun_wave(State, _NewWave = WaveNum);
handle_wave_state_normal(State, [?DUN_PASS|_]) ->
    ResultType    = ?DUN_RESULT_TYPE_SUCCESS,
    ResultSubType = ?DUN_RESULT_SUBTYPE_NO,
    lib_dungeon_distribute:dungeon_result(State, ResultType, ResultSubType);
handle_wave_state_normal(State, _) ->
    State.


handle_kill_mon_do_normal(State, [Mid, _AtterId, _MonXys|_]) ->
    #dungeon_state{typical_data = TypicalData} = State,
    KillMonList = maps:get(?DUN_STATE_SPECIAL_KEY_MON_KILL_CALC, TypicalData, []),
    NewKillMonList = case lists:keyfind(Mid, 1, KillMonList) of
        {_, OldNum} ->
            lists:keystore(Mid, 1, KillMonList, {Mid, OldNum+1});
        _ ->
            lists:keystore(Mid, 1, KillMonList, {Mid, 1})
    end,
    NewTypicalData = maps:put(?DUN_STATE_SPECIAL_KEY_MON_KILL_CALC, NewKillMonList, TypicalData),
    State#dungeon_state{typical_data = NewTypicalData}.


%% 评分事件初始化默认处理
make_grade_event_normal(DungeonState) ->
    #dungeon_state{dun_id = DunId, role_list = RoleList, grade_ref = GradeRefList, start_time = StartTime} = DungeonState,
    {NewGradeRefList, NewRoleList} = role_make_grade_event(DunId, StartTime, RoleList, GradeRefList),
    DungeonState#dungeon_state{grade_ref = NewGradeRefList, role_list = NewRoleList}.

%% 副本通用评级默认处理
make_grade_rating_normal(DungeonState) ->
    #dungeon_state{dun_id = DunId, rating_ref = RatingRef, start_time = StartTime, role_list = RoleList} = DungeonState,
    {NewRatingRef, NewRoleList} = role_make_rating_event(DunId, RoleList, StartTime, RatingRef),
    DungeonState#dungeon_state{rating_ref = NewRatingRef, role_list = NewRoleList}.


%% 评级掉落默认处理
calc_rating_drop_normal(DungeonState) ->
    #dungeon_state{dun_id = DunId, role_list = RoleList} = DungeonState,
    lists:map(fun(DungeonRole) ->
        #dungeon_role{id = RoleId, node = Node, typical_data = TypicalData} = DungeonRole,
        MergeType = maps:get(?DUN_ROLE_COUNT_MERGE, TypicalData, 0),
        MergeCount = maps:get(?DUN_ROLE_DAILY_ENTER_COUNT, TypicalData, 1),
        case dungeon_default:calc_dun_rating(DunId, DungeonRole) of
            Rating when is_integer(Rating) andalso Rating =/= 0 ->
                case data_dungeon_grade:get_dun_rating(DunId, Rating) of
                    #base_dun_grade_rating{rewards = [MonId|_]} ->
                        lib_player:apply_cast(Node, RoleId, ?APPLY_CAST_STATUS, ?NOT_HAND_OFFLINE, ?MODULE, role_calc_rating_drop_normal, [MonId, MergeType, MergeCount, DunId]);
                    #base_dun_grade_rating{rewards = []} ->
                        skip;
                    _ ->
                        ?ERR("no cfg:~p~n", [{DunId, Rating}])
                end;
            _ ->
                skip
        end
    end, RoleList),
    DungeonState.

%% 玩家退出默认处理
handle_role_out_normal(RoleId, _LogType, ResultType, _ResultSubType,
    #dungeon_state{dun_id = DunId, dun_type = DunType, is_end = ?DUN_IS_END_YES, role_list = RoleList, force_quit_ref = ForceRef} = State) ->
    case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
        #dungeon_role{id = RoleId, is_end_out = ?DUN_IS_END_OUT_NO, online = OL, node = RoleNode, drop_list = _DropList} = DungeonRole
         when OL =:= ?DUN_ONLINE_YES orelse OL =:= ?DUN_ONLINE_DELAY ->
            %% 检测奖励发放
            NewDungeonRole0 = send_dun_rewards_do(DungeonRole, DunType, DunId),
            lists:member(DunType, ?DUN_RESULT_PUSH_EXCLUSIVE) == true
             andalso lib_dungeon_distribute:push_settlement(DunType, State, NewDungeonRole0),
            % 清理记录
            mod_dungeon_agent:remove_dungeon_record(RoleId, self()),
            % 更新退出状态&在线状态
            NewDungeonRole = NewDungeonRole0#dungeon_role{is_end_out = ?DUN_IS_END_OUT_YES, online = ?DUN_ONLINE_NO},

            NewRoleList = lists:keystore(RoleId, #dungeon_role.id, RoleList, NewDungeonRole),
            NewState2 = State#dungeon_state{role_list = NewRoleList},

            lib_player:apply_cast(RoleNode, RoleId, ?APPLY_CAST_SAVE, ?NOT_HAND_OFFLINE, lib_dungeon, immediate_quit_dungeon, [ResultType]),
            check_all_dun_role_out(NewRoleList, ForceRef),
            NewState2;
        #dungeon_role{id = RoleId, is_end_out = ?DUN_IS_END_OUT_YES, online = OL, node = RoleNode, drop_list = _DropList} = DungeonRole ->
            case OL =:= ?DUN_ONLINE_YES orelse OL =:= ?DUN_ONLINE_DELAY of
                true ->
                    lists:member(DunType, ?DUN_RESULT_PUSH_EXCLUSIVE) == true
                     andalso lib_dungeon_distribute:push_settlement(DunType, State, DungeonRole),
                    % 清理记录
                    mod_dungeon_agent:remove_dungeon_record(RoleId, self()),

                    lib_player:apply_cast(RoleNode, RoleId, ?APPLY_CAST_SAVE, ?NOT_HAND_OFFLINE, lib_dungeon, immediate_quit_dungeon, [ResultType]),
                    NewDungeonRole = DungeonRole#dungeon_role{online = ?DUN_ONLINE_NO},
                    NewRoleList = lists:keystore(RoleId, #dungeon_role.id, RoleList, NewDungeonRole),
                    NewState = State#dungeon_state{role_list = NewRoleList};
                false ->
                    NewRoleList = RoleList,
                    NewState = State
            end,
            check_all_dun_role_out(NewRoleList, ForceRef),
            NewState;
        DungeonRole = #dungeon_role{} -> %% 已经离线的
            mod_dungeon_agent:remove_dungeon_record(RoleId, self()),
            NewRoleList = lists:keydelete(RoleId, #dungeon_role.id, RoleList),
            lib_dungeon_distribute:handle_role_out_offline(DungeonRole, State),
            NewState2 = State#dungeon_state{role_list = NewRoleList},
            check_all_dun_role_out(NewRoleList, ForceRef),
            NewState2;
        _ ->
            State
    end;
%% 未结算就退出
%% 默认无奖励
handle_role_out_normal(RoleId, LogType, ResultType, ResultSubtype, State) ->
    #dungeon_state{dun_id = DunId, wave_num = DunWave, start_time = StartTime, role_list = RoleList, dun_type = DunType, typical_data = TypicalData} = State,
    case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
        DungeonRole = #dungeon_role{node = RoleNode} ->
            %% 清理记录
            mod_dungeon_agent:remove_dungeon_record(RoleId, self()),
            NewRoleList = lists:keydelete(RoleId, #dungeon_role.id, RoleList),
            NewState1 = State#dungeon_state{role_list = NewRoleList},

            lists:member(DunType, ?DUN_RESULT_PUSH_EXCLUSIVE) == true
             andalso lib_dungeon_distribute:push_settlement(DunType, NewState1, DungeonRole),
            %% log
            role_out_log(DungeonRole, NewState1, LogType, [{result_type, ResultType}, {result_subtype, ResultSubtype}]),

            %% TA数据上报
            PassTime = utime:unixtime() - StartTime,
            role_ta_multi_dun_ret(DungeonRole, [?DUN_RESULT_TYPE_NO, ?DUN_RESULT_SUBTYPE_ACTIVE_QUIT, DunType, DunId, PassTime, TypicalData, DunWave]),

            lib_player:apply_cast(RoleNode, RoleId, ?APPLY_CAST_SAVE, ?NOT_HAND_OFFLINE, lib_dungeon, immediate_quit_dungeon, [ResultType]),
            NewState1;
        _ ->
            State
    end.

%% 没有人处于未领奖未退出且在线的情况,就结束副本
%% 若已有退出定时器则忽略
check_all_dun_role_out(RoleList, ForceRef) ->
    F = fun(#dungeon_role{is_end_out = IsEndOut, online = Online}) ->
        IsEndOut == ?DUN_IS_END_OUT_NO andalso Online =/= ?DUN_ONLINE_NO
    end,
    case lists:any(F, RoleList) of
        false ->
            if is_reference(ForceRef) ->
                    RestTime = erlang:read_timer(ForceRef),
                    if is_integer(RestTime) andalso RestTime > 0 ->
                            skip;
                        true ->
                            util:send_after(undefined, 2000, self(), stop)
                    end;
                true ->
                    util:send_after(undefined, 2000, self(), stop)
            end;
        _ ->
            skip
    end.

%% 初始化星级评分条件
role_make_grade_event(DunId, StartTime, RoleList, GradeRefList) ->
    role_make_grade_event_do(DunId, StartTime, RoleList, GradeRefList, []).

%% 初始化副本通用评级
role_make_rating_event(DunId, RoleList, StartTime, RatingRef) ->
    role_make_rating_event_do(DunId, RoleList, StartTime, RatingRef, []).

role_make_rating_event_do(_DunId, [], _, RatingRef, RoleList) -> {RatingRef, RoleList};
role_make_rating_event_do(DunId, [Role|Rest], StartTime, RatingRef, NewRoleList) ->
    #dungeon_role{id = RoleId, node = Node, grade_rating = RatingList} = Role,
    case data_dungeon_grade:get_dun_grades(DunId) of
        Ratings = [_|_] ->
            {NewRatingRef, NewRatingList} =
             lists:foldl(fun(Rating, {TmpRatingRef, TmpRatingList}) ->
                case data_dungeon_grade:get_dun_rating(DunId, Rating) of
                    #base_dun_grade_rating{last_time = LastTime} ->
                        Now = utime:unixtime(),
                        ExtraTime = max(0, StartTime - Now),
                        Ref = erlang:send_after((LastTime+ExtraTime)*1000, self(), {trigger_time_pass_check, dun_rating, RoleId, Rating, LastTime}),
                        NewTmpRatingRef = update_grade_ref_list(Role#dungeon_role.id, Ref, TmpRatingRef),
                        NewTmpRatingList = lists:keystore(Rating, 1, TmpRatingList, {Rating, 0, LastTime, ?DUN_SCORE_UNFIN});
                    _ ->
                        NewTmpRatingRef = TmpRatingRef,
                        NewTmpRatingList = TmpRatingList
                end,
                {NewTmpRatingRef, NewTmpRatingList}
             end, {RatingRef, RatingList}, Ratings),
            %% 通知
            notice_dun_rating_update(RoleId, Node, DunId, StartTime, NewRatingList),
            NewRole = Role#dungeon_role{grade_rating = NewRatingList},
            role_make_rating_event_do(DunId, Rest, StartTime, NewRatingRef, [NewRole|NewRoleList]);
        _ ->
            role_make_rating_event_do(DunId, Rest, StartTime, RatingRef, [Role|NewRoleList])
    end.

role_make_grade_event_do(_DunId, _, [], GradeRefList, RoleList) -> {GradeRefList, RoleList};
role_make_grade_event_do(DunId, StartTime, [Role|Rest], GradeRefList, NewRoleList) ->
    #dungeon_role{id = RoleId, figure = #figure{lv = Lv}} = Role,
    case data_dungeon_grade:get_dungeon_grade(DunId, Lv) of
        #dungeon_grade{condition = Condition = [_|_]} ->
            {NewGradeRefList, RoleGradeEvent} = role_make_grade_event_do_by_config(Condition, RoleId, GradeRefList, StartTime, #dungeon_role_grade{}),
            NewRole = Role#dungeon_role{grade_progress = RoleGradeEvent},
            notice_score_event_update(DunId, NewRole),
            NewRoleList1 = lists:keystore(RoleId, #dungeon_role.id, NewRoleList, NewRole),
            role_make_grade_event_do(DunId, StartTime, Rest, NewGradeRefList, NewRoleList1);
        _ ->
            case lists:keyfind(RoleId, #dungeon_role.id, NewRoleList) of
                #dungeon_role{} ->
                    role_make_grade_event_do(DunId, StartTime, Rest, GradeRefList, NewRoleList);
                _ ->
                    role_make_grade_event_do(DunId, StartTime, Rest, GradeRefList, [Role|NewRoleList])
            end
    end.

role_make_grade_event_do_by_config([], _RoleId, GradeRefList, _, DungeonRoleGrade) -> {GradeRefList, DungeonRoleGrade};
role_make_grade_event_do_by_config([{ConfigIndex, EventType, Condition}|L], RoleId, GradeRefList, StartTime, #dungeon_role_grade{map = Maps} = DungeonRoleGrade) ->
    EventTypeId = get_event_type(EventType),
    if EventTypeId =/= 0 andalso EventTypeId =/= ?DUN_SCORE_HP andalso EventTypeId =/= ?DUN_SCORE_WAVE ->
            EventList = maps:get(EventTypeId, Maps, []),
            NewEventList = lists:keystore(ConfigIndex, 1, EventList, {ConfigIndex, 0, Condition, ?DUN_SCORE_UNFIN}),
            NewMaps = maps:put(EventTypeId, NewEventList, Maps);
        EventTypeId == ?DUN_SCORE_HP ->
            EventList = maps:get(EventTypeId, Maps, []),
            NewEventList = lists:keystore(ConfigIndex, 1, EventList, {ConfigIndex, 100, Condition, ?DUN_SCORE_UNFIN}),
            NewMaps = maps:put(EventTypeId, NewEventList, Maps); 
        EventTypeId == ?DUN_SCORE_WAVE ->
            EventList = maps:get(EventTypeId, Maps, []),
            NewEventList = lists:keystore(ConfigIndex, 1, EventList, {ConfigIndex, [], Condition, ?DUN_SCORE_UNFIN}),
            NewMaps = maps:put(EventTypeId, NewEventList, Maps);
        true ->
            NewMaps = Maps
    end,
    NewDungeonRoleGrade = DungeonRoleGrade#dungeon_role_grade{map = NewMaps},
    if EventTypeId == ?DUN_SCORE_PASS_TIME ->
            Now = utime:unixtime(),
            ExtraTime = max(0, StartTime - Now),
            NewRef = erlang:send_after((Condition+ExtraTime)*1000, self(), {trigger_time_pass_check, dun_grade, RoleId, ConfigIndex, Condition});
        true ->
            NewRef = undefined
    end,
    NewGradeRefList = update_grade_ref_list(RoleId, NewRef, GradeRefList),
    role_make_grade_event_do_by_config(L, RoleId, NewGradeRefList, StartTime, NewDungeonRoleGrade);
role_make_grade_event_do_by_config([_|L], RoleId, GradeRefList, StartTime, DungeonRoleGrade) ->
    role_make_grade_event_do_by_config(L, RoleId, GradeRefList, StartTime, DungeonRoleGrade).

%% 评分关键字映射事件id
get_event_type(?DUN_GRADE_EVENT_DIE_COUNT) -> ?DUN_SCORE_DIE_COUNT;
get_event_type(?DUN_GRADE_EVENT_KILL_NUM)  -> ?DUN_SCORE_DIE;
get_event_type(?DUN_GRADE_EVENT_KILL_ALL)  -> ?DUN_SCORE_DIE_ALL;
get_event_type(?DUN_GRADE_EVENT_TIME)      -> ?DUN_SCORE_PASS_TIME;
get_event_type(?DUN_GRADE_EVENT_WAVE)      -> ?DUN_SCORE_WAVE;
get_event_type(?DUN_GRADE_EVENT_REST_HP)   -> ?DUN_SCORE_HP;
get_event_type(_)                          -> 0.

update_grade_ref_list(RoleId, NewRef, GradeRefList) when is_reference(NewRef) ->
    case lists:keyfind(RoleId, 1, GradeRefList) of
        {_, RoleGradeList} ->
            lists:keystore(RoleId, 1, GradeRefList, {RoleId, [NewRef|RoleGradeList]});
        _ ->
            lists:keystore(RoleId, 1, GradeRefList, {RoleId, [NewRef]})
    end;
update_grade_ref_list(_RoleId, _, GradeRefList) -> GradeRefList.


%% 评分事件触发默认操作
%% 暂时只支持死亡次数&击杀怪物&通关时间事件
dispatch_dungeon_score_event_normal(DungeonState = #dungeon_state{is_end = ?DUN_IS_END_YES}, _, _, _) ->
    DungeonState;
dispatch_dungeon_score_event_normal(State, ScoreEventIds, RoleId, EventArgs) ->
    lists:foldl(fun(ScoreEventId, TmpState) ->
        dispatch_dungeon_score_event(ScoreEventId, RoleId, TmpState, EventArgs)
    end, State, ScoreEventIds).

dispatch_dungeon_rating_normal(DungeonState = #dungeon_state{is_end = ?DUN_IS_END_YES}, _, _) ->
    DungeonState;
dispatch_dungeon_rating_normal(DungeonState, RoleId, EventArgs) ->
    #dungeon_state{dun_id = DunId, start_time = StartTime, role_list = RoleList} = DungeonState,
    case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
        DunRole = #dungeon_role{node = Node, grade_rating = GradeRating} ->
            NewGradeRating = update_pass_time_status(GradeRating, EventArgs),
            %% 变更通知
            notice_dun_rating_update(RoleId, Node, DunId, StartTime, NewGradeRating),
            NewDungeonRole = DunRole#dungeon_role{grade_rating = NewGradeRating},
            NewRoleList = lists:keystore(RoleId, #dungeon_role.id, RoleList, NewDungeonRole),
            DungeonState#dungeon_state{role_list = NewRoleList};
        _ ->
            DungeonState
    end.

dispatch_dungeon_score_event(EventType, RoleId, State, EventArgs) ->
    case lists:member(EventType, ?SCORE_EVENT_TYPE_IDS) of
        false -> State;
        _ ->
            #dungeon_state{dun_id = DunId, dun_type = DunType, role_list = RoleList} = State,
            case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
                DungeonRole = #dungeon_role{online = Online, grade_progress = GradeProgress}
                 when (Online == ?DUN_ONLINE_YES orelse Online == ?DUN_ONLINE_DELAY)
                  andalso is_record(GradeProgress, dungeon_role_grade) ->
                    NewDungeonRole = update_role_score_event(EventType, DungeonRole, DunId, EventArgs),
                    %% 通知前端评分事件进度更新
                    notice_score_event_update(DunId, NewDungeonRole),
                    NewRoleList = lists:keystore(RoleId, #dungeon_role.id, RoleList, NewDungeonRole),
                    State#dungeon_state{role_list = NewRoleList};
                _ ->
                    if DunType == ?DUNGEON_TYPE_PET ->
                            NewRoleList = lists:foldl(fun(Role, TempRoles) -> 
                                NewRole = update_role_score_event(EventType, Role, DunId, EventArgs),
                                %% 通知前端评分事件进度更新
                                notice_score_event_update(DunId, NewRole),
                                [NewRole|TempRoles]                      
                            end, [], RoleList),
                            State#dungeon_state{role_list = NewRoleList};
                        true ->
                            State
                    end
            end
    end.

update_role_score_event(?DUN_SCORE_DIE_ALL, DungeonRole, DunId, [_Wave, WaveStatus|_]) ->
    #dungeon_role{id = RoleId, node = Node, grade_progress = GradeProgress} = DungeonRole,
    #dungeon_role_grade{map = Maps} = GradeProgress,
    case maps:get(?DUN_SCORE_DIE_ALL, Maps, false) of
        [{ConfigIndex, _Val, _ConfigVal, Status}|_] ->
            if WaveStatus == ?DUN_PASS andalso Status =/= ?DUN_SCORE_FINISH ->
                    NewMaps = maps:put(?DUN_SCORE_DIE_ALL, [{ConfigIndex, _Val, _ConfigVal, ?DUN_SCORE_FINISH}], Maps),
                    update_role_score_highest_record(DunId, Node, RoleId, NewMaps),
                    DungeonRole#dungeon_role{grade_progress = GradeProgress#dungeon_role_grade{map = NewMaps}};
                true ->
                    DungeonRole
            end;
        _ ->
            DungeonRole
    end;

update_role_score_event(EventTypeId, DungeonRole, DunId, EventArgs) ->
    #dungeon_role{id = RoleId, node = Node, grade_progress = GradeProgress} = DungeonRole,
    #dungeon_role_grade{map = Maps} = GradeProgress,
    case maps:get(EventTypeId, Maps, false) of
        DataList = [_|_] ->
            NewDataList = case EventTypeId of
                ?DUN_SCORE_DIE_COUNT -> update_role_die_count_status(DataList, EventArgs);
                ?DUN_SCORE_PASS_TIME -> update_pass_time_status(DataList, EventArgs);
                ?DUN_SCORE_DIE       -> update_die_status(DataList);
                ?DUN_SCORE_HP        -> upate_hp_status(DataList, EventArgs);
                ?DUN_SCORE_WAVE      ->
                    [Wave, WaveStatus|_] = EventArgs,
                    if WaveStatus == ?WAVE_NEXT orelse WaveStatus == ?DUN_PASS ->
                            update_pass_wave_status(Wave, DataList);
                        true ->
                            DataList
                    end;
                _ ->
                    DataList
            end,
            % (WaveStatus == ?WAVE_NEXT orelse WaveStatus == ?DUN_PASS) andalso ?ERR("~p ~p ~p", [WaveStatus, DataList, NewDataList]),
            NewMaps = maps:put(EventTypeId, NewDataList, Maps),
            update_role_score_highest_record(DunId, Node, RoleId, NewMaps),
            DungeonRole#dungeon_role{grade_progress = GradeProgress#dungeon_role_grade{map = NewMaps}};
        _ ->
            DungeonRole
    end.

%% 玩家死亡次数判断
update_role_die_count_status(DieCountList, [NewVal|_]) ->
    lists:foldl(fun({ConfigIndex, _Val, ConfigVal, Status}, TmpList) ->
        % NewVal = Val + 1,
        NewStatus = ?IF(NewVal >= ConfigVal, ?DUN_SCORE_UNFIN, Status),
        lists:keystore(ConfigIndex, 1, TmpList, {ConfigIndex, NewVal, ConfigVal, NewStatus})
     end, [], DieCountList).

%% 击杀怪物数量判断
update_die_status(DieList) ->
    lists:foldl(fun({ConfigIndex, Val, ConfigVal, Status}, TmpDieList) ->
        NewVal = Val + 1,
        NewStatus = ?IF(NewVal >= ConfigVal, ?DUN_SCORE_FINISH, Status),
        lists:keystore(ConfigIndex, 1, TmpDieList, {ConfigIndex, NewVal, ConfigVal, NewStatus})
      end, [], DieList).

%% 通关时间判断
update_pass_time_status(PassTimeList, [ConfigIndex, RealCostTime|_]) ->
    case lists:keyfind(ConfigIndex, 1, PassTimeList) of
        {_, 0, PassTime, _OldStatus} ->
            if RealCostTime < PassTime ->
                    lists:keystore(ConfigIndex, 1, PassTimeList, {ConfigIndex, RealCostTime, PassTime, ?DUN_SCORE_FINISH});
                true ->
                    lists:keystore(ConfigIndex, 1, PassTimeList, {ConfigIndex, RealCostTime, PassTime, _OldStatus})
                    % lists:keystore(ConfigIndex, 1, PassTimeList, {ConfigIndex, RealCostTime, PassTime, ?DUN_SCORE_UNFIN})
            end;
        _ ->
            PassTimeList
    end.

%% 通关波数判断
update_pass_wave_status(Wave, PassWaveList) ->
    lists:foldl(fun({ConfigIndex, Val, ConfigVal, Status}, TmpPassList) ->
        NewVal = case lists:member(Wave, Val) of
            true -> Val;
            _    -> [Wave|Val]
        end,
        PassWaveNum = length(NewVal),
        NewStatus = ?IF(PassWaveNum >= ConfigVal, ?DUN_SCORE_FINISH, Status),
        lists:keystore(ConfigIndex, 1, TmpPassList, {ConfigIndex, NewVal, ConfigVal, NewStatus})
    end, [], PassWaveList).

%% 剩余血量判断
upate_hp_status(DataList, [RestHp|_]) ->
    lists:foldl(fun({ConfigIndex, _Val, ConfigVal, _Status}, TmpDataList) -> 
        NewStatus = ?IF(RestHp >= ConfigVal, ?DUN_SCORE_FINISH, ?DUN_SCORE_UNFIN),
        lists:keystore(ConfigIndex, 1, TmpDataList, {ConfigIndex, RestHp, ConfigVal, NewStatus})
    end, [], DataList).

%% 结算时检查并更新相关事件
dun_grade_event_update_after_result(State, ResultType) ->
    #dungeon_state{dun_id = DunId, grade_ref = GradeRefList, role_list = RoleList} = State,
    %% 清除评分事件定时器
    GradeRefList =/= [] andalso [lists:map(fun(Ref) -> util:cancel_timer(Ref) end, GradeRefs) || {_RoleIdX, GradeRefs} <- GradeRefList],
    if ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
            %% 通关时间事件&死亡次数事件&通关波数事件&剩余血量事件
            NewState = lists:foldl(fun(#dungeon_role{id = RoleId}, TmpState) ->
                CalcEvents = [
                    ?DUN_SCORE_PASS_TIME, ?DUN_SCORE_DIE_COUNT
                    ,?DUN_SCORE_WAVE, ?DUN_SCORE_HP
                ],
                calc_dun_role_score_event_result(TmpState, RoleId, CalcEvents, ResultType)
             end, State, RoleList),
            NewState#dungeon_state{grade_ref = []};
        true ->
            lists:map(fun(#dungeon_role{node = Node, id = RoleId, grade_progress = GradeProgress}) ->
                case GradeProgress of
                    #dungeon_role_grade{map = Maps} ->
                        ScoreList = lists:foldl(fun({_, ScoreEvents}, Acc) ->
                            EventList = [{ConfigIndex, ?DUN_SCORE_UNFIN} || {ConfigIndex, _, _, _} <- ScoreEvents],
                            lists:append(EventList, Acc)
                         end, [], maps:to_list(Maps)),
                        dungeon_logic:send_to_uid(Node, RoleId, pt_610, 61065, [DunId, ScoreList]);
                    _ ->
                        skip
                end
            end, RoleList),
            State#dungeon_state{grade_ref = []}
    end.

dun_rating_update_after_result(State, ResultType) ->
    #dungeon_state{dun_id = DunId, rating_ref = RatingRefList, start_time = StartTime, end_time = EndTime} = State,
    [lists:map(fun(Ref) -> util:cancel_timer(Ref) end, RatingRef) || {_RoleIdX, RatingRef} <- RatingRefList],
    #dungeon_state{role_list = RoleList} = State,
    if ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
            LastTime = utime:unixtime() - StartTime,
            NewRoleList = lists:foldl(fun(DunRole = #dungeon_role{node = Node, id = RoleId, grade_rating = GradeRating}, Acc) ->
                NewGradeRating = lists:map(fun({Rating, _Progress, ConfigLastTime, Status}) ->
                    if LastTime =< ConfigLastTime -> {Rating, LastTime, ConfigLastTime, ?DUN_SCORE_FINISH};
                        true -> {Rating, round(EndTime-StartTime), ConfigLastTime, Status}
                    end
                 end, GradeRating),
                NewDunRole = DunRole#dungeon_role{grade_rating = NewGradeRating},
                RoleMaxRating = calc_dun_rating(DunId, NewDunRole),
                update_role_max_rating(Node, RoleId, DunId, RoleMaxRating),
                [NewDunRole|Acc]
             end, [], RoleList),
            State#dungeon_state{rating_ref = [], role_list = NewRoleList};
        true ->
            State#dungeon_state{rating_ref = []}
    end.

calc_dun_role_score_event_result(State, RoleId, Events, ResultType) ->
    #dungeon_state{dun_id = DunId, role_list = RoleList, start_time = StartTime} = State,
    case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
        Role = #dungeon_role{id = RoleId, node = Node, grade_progress = GradeProgress, hp = Hp, hp_lim = HpLim} ->
            case GradeProgress of
                #dungeon_role_grade{map = Maps} ->
                    Now = utime:unixtime(),
                    EventArgs = [{?DUN_SCORE_PASS_TIME, abs(Now - StartTime)}, {?DUN_SCORE_HP, Hp/HpLim*100}],
                    NewMaps   = calc_dun_role_score_event_result_do(Maps, Events, EventArgs),
                    update_role_score_highest_record(DunId, Node, RoleId, NewMaps, ResultType),
                    NewRole = Role#dungeon_role{grade_progress = GradeProgress#dungeon_role_grade{map = NewMaps}},
                    notice_score_event_update(DunId, NewRole),
                    NewRoleList = lists:keystore(RoleId, #dungeon_role.id, RoleList, NewRole),
                    State#dungeon_state{role_list = NewRoleList};
                _ -> State
            end;
        _ -> State
    end.

calc_dun_role_score_event_result_do(Maps, [], _EventArgs) ->
    Maps;
calc_dun_role_score_event_result_do(Maps, [EventType|Rest], EventArgs) ->
    case maps:get(EventType, Maps, false) of
        EventList = [_|_] ->
            NewEventList = update_score_event_status(EventType, EventList, EventArgs),
            NewMaps = maps:put(EventType, NewEventList, Maps),
            calc_dun_role_score_event_result_do(NewMaps, Rest, EventArgs);
        _ ->
            calc_dun_role_score_event_result_do(Maps, Rest, EventArgs)
    end.

%% 更新副本评分事件最高星级数据
update_role_score_highest_record(DunId, Node, RoleId, NewMaps) ->
    update_role_score_highest_record(DunId, Node, RoleId, NewMaps, ?DUN_RESULT_TYPE_NO).

update_role_score_highest_record(DunId, Node, RoleId, NewMaps, ResultType) ->
    {_StarNum, ScoreEventIds} = calc_dun_score_event_id(NewMaps),
    lib_player:apply_cast(Node, RoleId, ?APPLY_CAST_SAVE, ?NOT_HAND_OFFLINE, ?MODULE, role_update_dun_score_record, [[DunId, ScoreEventIds, ResultType]]).

%% 更新副本最高评级数据
update_role_max_rating(Node, RoleId, DunId, RoleMaxRating) ->
    lib_player:apply_cast(Node, RoleId, ?APPLY_CAST_SAVE, ?NOT_HAND_OFFLINE, ?MODULE, role_update_max_rating, [[DunId, RoleMaxRating]]).

calc_dun_score_event_id(Maps) ->
    lists:foldl(fun({_, EventList}, {Acc1, Acc2}) ->
        lists:foldl(fun({Index, _, _, Status}, {TmpAcc1, TmpAcc2}) ->
            if Status == ?DUN_SCORE_FINISH -> {TmpAcc1+1, [Index|TmpAcc2]};
                true -> {TmpAcc1, TmpAcc2}
        end end, {Acc1, Acc2}, EventList)
     end, {0, []}, maps:to_list(Maps)).

%% 计算玩家副本评分
calc_dun_score(NewMaps) ->
    lists:foldl(fun({_, EventList}, Acc) ->
        lists:foldl(fun({_, _, _, Status}, Acc1) ->
            if Status == ?DUN_SCORE_FINISH -> Acc1+1;
                true -> Acc1
            end
         end, Acc, EventList)
     end, 0, maps:to_list(NewMaps)).

%% 计算玩家副本评级
calc_dun_rating(DunId, DungeonRole) ->
    #dungeon_role{grade_rating = GradeRating} = DungeonRole,
    FilterRating = lists:foldl(fun({Rating, _, _, Status}, Acc) ->
        if Status == ?DUN_SCORE_FINISH ->
                [Rating|Acc];
            true ->
                Acc
        end
     end, [], GradeRating),
    case FilterRating of
        [_|_] ->
            lists:max(FilterRating);
        _ ->
            case GradeRating of
                [_|_] ->
                    case data_dungeon_grade:get_dun_grades(DunId) of
                        ConfigRatings = [_|_] ->
                            lists:min(ConfigRatings);
                        _ ->
                            0
                    end;
                _ ->
                    0
            end
    end.

role_update_dun_score_record(PS, [DunId, ScoreEventIds, ResultType]) ->
    #player_status{id = RoleId, dungeon_record = AllDunRec} = PS,
    DunRec = maps:get(DunId, AllDunRec, []),
    StarNum = length(ScoreEventIds),
    {UpdateSign, NewDunRec} =
     case lists:keyfind(?DUNGEON_REC_MAX_STARS, 1, DunRec) of
        {_, MaxStarScoreEventIds} ->
            MaxStar = length(MaxStarScoreEventIds),
            if StarNum > MaxStar ->
                    {true, lists:keystore(?DUNGEON_REC_MAX_STARS, 1, DunRec, {?DUNGEON_REC_MAX_STARS, ScoreEventIds})};
                true ->
                    {false, DunRec}
            end;
        _ ->
            {true, lists:keystore(?DUNGEON_REC_MAX_STARS, 1, DunRec, {?DUNGEON_REC_MAX_STARS, ScoreEventIds})}
    end,
    {ok, PS1} =
    case UpdateSign of
        true ->
            lib_dungeon:save_dungeon_record(RoleId, DunId, NewDunRec),
            NewAllDunRec = maps:put(DunId, NewDunRec, AllDunRec),
            NewPS = PS#player_status{dungeon_record = NewAllDunRec},
            {ok, NewPS};
        _ ->
            {ok, PS}
    end,
    PS2 = custom_act_sellswords_resupply:update_personal_act_data(PS1, DunId, StarNum, ResultType),
    {ok, PS2}.

role_update_max_rating(Player, [DunId, RoleMaxRating]) ->
    #player_status{id = RoleId, dungeon_record = AllDunRec} = Player,
    DunRec = maps:get(DunId, AllDunRec, []),
    case lists:keyfind(?DUNGEON_REC_RATING, 1, DunRec) of
        {_, OldMaxRating} -> ok;
        _ -> OldMaxRating = 0
    end,
    if RoleMaxRating > OldMaxRating ->
            NewDunRec = lists:keystore(?DUNGEON_REC_RATING, 1, DunRec, {?DUNGEON_REC_RATING, RoleMaxRating}),
            lib_dungeon:save_dungeon_record(RoleId, DunId, NewDunRec),
            NewAllDunRec = maps:put(DunId, NewDunRec, AllDunRec),
            NewPlayer = Player#player_status{dungeon_record = NewAllDunRec},
            {ok, NewPlayer};
        true ->
            {ok, Player}
    end.

%% 掉落取人物当前所处位置信息
role_calc_rating_drop_normal(Player, MonId, _MergeType, PlusCount, DunId) ->
    #player_status{scene = Scene, scene_pool_id = PoolId, copy_id = CopyId, x = X, y = Y, dungeon = Dungeon} = Player,
    MonCfg = data_mon:get(MonId),
    MonInfo = mon_state_logic:base_to_cache([MonId, Scene, PoolId, X, Y, 0, CopyId, none], MonCfg),
    #scene_object{mon = Mon, figure = Figure} = MonInfo,
    Origin =
        case data_dungeon:get_dun(DunId) of
            #dun_cfg{type = ?DUNGEON_TYPE_CRUSADE} ->
                ?DROP_ORIGIN_CRUSADE_BOSS;
            _ ->
                0
        end,
    MonArgs = #mon_args{mid = Mon#scene_mon.mid, kind = Mon#scene_mon.kind, lv = Figure#figure.lv,
        ctime = Mon#scene_mon.ctime, boss = Mon#scene_mon.boss, scene = Scene,
        scene_pool_id = PoolId, copy_id = CopyId, x = X, y = Y, name = Figure#figure.name,
        exp = Mon#scene_mon.exp, origin = Origin
    },
    NewMonArgs = lib_dungeon_distribute:get_drop_arg(Dungeon, MonArgs),
    AllocList = data_drop:get_rule_list(Mon#scene_mon.mid),
    [dungeon_logic:handle_dun_drop(Player, NewMonArgs, Alloc, PlusCount, []) || Alloc <- AllocList],
    {ok, Player}.

%% 通知前端评分事件信息
notice_score_event_update(DunId, DungeonRole) ->
    #dungeon_role{node = Node, id = RoleId, grade_progress = GradeProgress} = DungeonRole,
    case GradeProgress of
        #dungeon_role_grade{map = Maps} ->
            ScoreList = pack_score_list(Maps),
            dungeon_logic:send_to_uid(Node, RoleId, pt_610, 61065, [DunId, ScoreList]);
        _ ->
            dungeon_logic:send_to_uid(Node, RoleId, pt_610, 61065, [DunId, []])
    end.

%% 通知前端副本通用评级信息
notice_dun_rating_update(RoleId, Node, DunId, StartTime, RatingList) ->
    {RatingList1, RatingIds} = lists:foldl(fun({Rating, Progress, ConfigLastTime, Status}, {Acc1, Acc2}) ->
        if Progress < ConfigLastTime -> {[{Rating, Progress, ConfigLastTime, Status}|Acc1], [Rating|Acc2]};
            true -> {Acc1, Acc2}
        end
     end, {[], []}, RatingList),
    case RatingIds of
        [_|_] ->
            % ?ERR("~p", [RatingIds]),
            MaxRating = lists:max(RatingIds),
            Now = utime:unixtime(),
            NextEndTime = calc_next_time(Now, StartTime, lists:keysort(3, RatingList1)),
            % ?ERR("~p ~p", [MaxRating, NextEndTime]),
            dungeon_logic:send_to_uid(Node, RoleId, pt_610, 61070, [DunId, MaxRating, NextEndTime]);
        _ ->
            case RatingList of
                [_|_] ->
                    case data_dungeon_grade:get_dun_grades(DunId) of
                        Ratings = [_|_] ->
                            MinRating = lists:min(Ratings),
                            case data_dungeon:get_dun(DunId) of
                                #dun_cfg{time = DunTime} ->
                                    NextEndTime = StartTime + DunTime;
                                _ ->
                                    NextEndTime = 0
                            end,
                            dungeon_logic:send_to_uid(Node, RoleId, pt_610, 61070, [DunId, MinRating, NextEndTime]);
                        _ ->
                            dungeon_logic:send_to_uid(Node, RoleId, pt_610, 61070, [DunId, 0, 0])
                    end;
                _ ->
                    dungeon_logic:send_to_uid(Node, RoleId, pt_610, 61070, [DunId, 0, 0])
            end
    end.

calc_next_time(_, _, []) -> 0;
calc_next_time(Now, StartTime, [{_Rating, _, ConfigLastTime, _}|Rest]) ->
    LastTime = Now - StartTime,
    if LastTime >= ConfigLastTime ->
            calc_next_time(Now, StartTime, Rest);
        true -> %?ERR("~p ~p ~p", [_Rating, LastTime, ConfigLastTime]),
            StartTime + ConfigLastTime
    end.

pack_score_list(Maps) ->
    ScoreList = maps:to_list(Maps),
    lists:foldl(fun({EventType, ScoreEvents}, Acc) ->
        if EventType == ?DUN_SCORE_DIE_COUNT orelse EventType == ?DUN_SCORE_PASS_TIME ->
                EventList = [begin NewScoreStatus = ?IF(Progress < ConfigVal, ?DUN_SCORE_FINISH, ?DUN_SCORE_UNFIN),
                    {ConfigIndex, NewScoreStatus}
                end || {ConfigIndex, Progress, ConfigVal, _ScoreStatus} <- ScoreEvents],
                lists:append(EventList, Acc);
            EventType == ?DUN_SCORE_HP ->
                EventList = [begin NewScoreStatus = ?IF(Progress >= ConfigVal, ?DUN_SCORE_FINISH, ?DUN_SCORE_UNFIN),
                    {ConfigIndex, NewScoreStatus}
                end || {ConfigIndex, Progress, ConfigVal, _} <- ScoreEvents],
                lists:append(EventList, Acc);
            true ->
                lists:append([{ConfigIndex, ScoreStatus} || {ConfigIndex, _, _, ScoreStatus} <- ScoreEvents], Acc)
        end
     end, [], ScoreList).

%% 规定时间内通关,达标
update_score_event_status(EventType = ?DUN_SCORE_PASS_TIME, EventList, EventArgs) ->
    case lists:keyfind(EventType, 1, EventArgs) of
        {_, RealCostTime} -> ok;
        _ -> RealCostTime = 0
    end,
    lists:foldl(fun(H = {ConfigIndex, _Progress, ConfigPassTime, _Status}, Acc) ->
        if RealCostTime =< ConfigPassTime ->
                [{ConfigIndex, RealCostTime, ConfigPassTime, ?DUN_SCORE_FINISH}|Acc];
            true ->
                [H|Acc]
        end
     end, [], EventList);
%% 死亡次数少于配置次数,达标
update_score_event_status(?DUN_SCORE_DIE_COUNT, EventList, _) ->
    lists:foldl(fun(H = {ConfigIndex, Progress, ConfigMaxDieCount, _Status}, Acc) ->
        if Progress < ConfigMaxDieCount ->
                [{ConfigIndex, Progress, ConfigMaxDieCount, ?DUN_SCORE_FINISH}|Acc];
            true ->
                [H|Acc]
        end
     end, [], EventList);
%% 通关波数大于配置次数,达标
update_score_event_status(?DUN_SCORE_WAVE, EventList, _) ->
    lists:foldl(fun(H = {ConfigIndex, Progress, ConfigMaxWave, _Status}, Acc) ->
        ProgressLen = length(Progress),
        if ProgressLen >= ConfigMaxWave ->
            [{ConfigIndex, Progress, ConfigMaxWave, ?DUN_SCORE_FINISH}|Acc];
            true ->
                [H|Acc]
        end
                end, [], EventList);
%% 通关时剩余血量比例大于配置数值,达标
update_score_event_status(EventType = ?DUN_SCORE_HP, EventList, EventArgs) ->
    case lists:keyfind(EventType, 1, EventArgs) of
        {_, RestHp} -> ok;
        _           -> RestHp = 0
    end,
    upate_hp_status(EventList, [RestHp]);
update_score_event_status(_, EventList, _) ->
    EventList.


%% 创建波数信息
create_dun_wave(Wave, NextWave, MonNum, StartTime, State) ->
    #dungeon_state{
        dun_id = DunId, index_of_wave = WaveIndex, dun_type = DunType,
        wave_info = WaveInfos, role_list = RoleList} = State,
    WaveKey = {Wave, WaveIndex},
    WaveInfo = make_record(dun_wave_info, [WaveKey, 0, MonNum, ?DUN_WAVE_UNFIN, #{start_time => StartTime}]),
    NewWaveInfos = lists:keystore(WaveKey, #dun_wave_info.key, WaveInfos, WaveInfo),
    % ?ERR("~p", [NewWaveInfos]),
    if Wave =/= NextWave ->
            NewWaveIndex = WaveIndex;
        true ->
            NewWaveIndex = WaveIndex+1
    end,
    %% 通知前端波数刷新
    dungeon_logic:notice_wave_mon_info(DunId, Wave, NewWaveIndex, NewWaveInfos, RoleList),
    NewState = State#dungeon_state{wave_num = Wave, index_of_wave = NewWaveIndex, wave_info = NewWaveInfos},
    %% 宠物副本上一波超时,会直接生成下一波,此处需要推送最新波数
    (DunType == ?DUNGEON_TYPE_PET orelse DunType == ?DUNGEON_TYPE_CHILD) andalso dungeon_logic:notice_wave_info(NewState),
    NewState.


%% 副本结算相关操作
dungeon_end(State, ResultType, ResultSubtype, ArgsMap, ResultTime) ->
    #dungeon_state{
        dun_id = DunId, start_time = StartTime, team_id = _TeamId,
        dun_type = DunType, role_list = RoleList, close_ref = CloseRef,
        force_quit_ref = OldForceRef, wave_num = WaveNum, wave_info = WaveInfos,
        wave_ref = [WaveStartRef, WaveEndRef|_], create_gap_ref = GapRefL
    } = State,

    PassTime = ResultTime - StartTime,

    case lists:foldl(fun(WaveInfo = #dun_wave_info{key = {Wave, _}}, Acc) -> 
        if Wave == WaveNum -> [WaveInfo|Acc];
            true -> Acc
        end
    end, [], WaveInfos) of
        [#dun_wave_info{status = Status}|_] ->
            if Status == ?DUN_WAVE_FIN ->
                    NewWaveNum = WaveNum;
                true ->
                    NewWaveNum = max(0, WaveNum-1)
            end;
        _ ->
            NewWaveNum = WaveNum
    end,
    % 副本通关触发任务
    if
        ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
            dungeon_result_trigger(ResultType, ResultSubtype, DunId, WaveNum, NewWaveNum, DunType, StartTime, PassTime, RoleList);
        ResultType == ?DUN_RESULT_TYPE_FAIL ->
            dungeon_result_trigger(ResultType, ResultSubtype, DunId, WaveNum, NewWaveNum, DunType, StartTime, ResultTime, RoleList);
        true ->
            skip
    end,

    %% 更新玩家退出信息
    update_role_out_info(State, ArgsMap),

    %% 清除超时定时器
    util:cancel_timer(CloseRef),
    util:cancel_timer(WaveStartRef),
    util:cancel_timer(WaveEndRef),
    lists:map(fun(TGapRef) -> util:cancel_timer(TGapRef) end, GapRefL),

    %% 强制退出倒计时通知
    RestTime = get_force_quit_time(DunId, DunType, ResultType, ResultSubtype),
    [dungeon_logic:send_to_uid(Nodex, RoleIdX, pt_610, 61018, [1, round(ResultTime + RestTime)]) || #dungeon_role{id = RoleIdX, node = Nodex} <- RoleList],
    ForceRef = util:send_after(OldForceRef, RestTime*1000, self(), {'force_quit_ref'}),
    % 副本数据更新
    NewState = State#dungeon_state{
        is_end = ?DUN_IS_END_YES, result_type = ResultType,
        result_subtype = ResultSubtype, result_time = ResultTime,
        force_quit_ref = ForceRef
    },
    NewState.

get_force_quit_time(DunId, _DunType, _ResultType, _ResultSubtype) ->
    ?FORCE_QUIT_TIME(DunId).

update_role_out_info(State, ArgsMap) ->
    #{result_type := ResultType} = ArgsMap,
    #dungeon_state{dun_id = DunId, role_list = RoleList} = State,
    F = fun(DungeonRole) ->
        #dungeon_role{node = RoleNode, id = RoleId, scene = SceneId, scene_pool_id = ScenePoolId, copy_id = CopyId, x = X, y = Y} = DungeonRole,
        OldOut = maps:get(out, ArgsMap, #dungeon_out{}),
        NewOut = OldOut#dungeon_out{scene  = SceneId, scene_pool_id = ScenePoolId, copy_id = CopyId, x = X, y = Y},
        NewOut2 = get_dungeon_out(ResultType, DunId, NewOut),
        lib_player:apply_cast(RoleNode, RoleId, ?APPLY_CAST_SAVE, ?NOT_HAND_OFFLINE, lib_dungeon, role_out_info_update, [NewOut2])
    end,
    lists:foreach(F, RoleList).

%% 重新获取副本结束
get_dungeon_out(ResultType, DunId, Out) ->
    #dun_cfg{condition = Condition} = data_dungeon:get_dun(DunId),
    case traverse_condition_for_out(Condition, ResultType) of
        [] -> Out;
        {SceneId, X, Y} -> Out#dungeon_out{scene  = SceneId, scene_pool_id = 0, copy_id = 0, x = X, y = Y}
    end.

%% 遍历条件获得退出场景和坐标
traverse_condition_for_out([], _ResultType) -> [];
traverse_condition_for_out([{any_out_scene, SceneId, X, Y}|_T], _ResultType) -> {SceneId, X, Y};
traverse_condition_for_out([{success_out_scene, SceneId, X, Y}|_T], ?DUN_RESULT_TYPE_SUCCESS) -> {SceneId, X, Y};
traverse_condition_for_out([_H|T], ResultType) -> traverse_condition_for_out(T, ResultType).


%% 计算副本累积奖励
calc_role_rewards(NewState) ->
    #dungeon_state{role_list = RoleList} = NewState,
    NewRoleList = [calc_role_rewards_do(DungeonRole, NewState) ||  DungeonRole <- RoleList, is_record(DungeonRole, dungeon_role)],
    NewState#dungeon_state{role_list = NewRoleList}.

calc_role_rewards_do(DungeonRole, State) ->
    case lib_dungeon_distribute:get_send_reward(State, DungeonRole) of
	    DungeonRole1 when is_record(DungeonRole1, dungeon_role) -> DungeonRole1;
		Reward -> DungeonRole#dungeon_role{calc_reward_list = Reward}
    end.

send_dun_rewards(State) ->
    lib_dungeon_distribute:send_dun_rewards(State).


%% 发奖励默认接口
send_dun_rewards_normal(State) ->
    #dungeon_state{
        dun_type = DunType, dun_id = DunId, cluster = Cluster, 
        role_list = TmpRoleList, is_end = IsEnd, typical_data = TypicalData,
        result_type = ResultType
    } = State,
    %% 真人
    Fun = fun(DungeonRole, TmpState)->
        #dungeon_state{role_list = RoleList} = TmpState,
        case DungeonRole of
            #dungeon_role{id = RoleId, is_reward = ?DUN_IS_REWARD_NO, online = OL} = DungeonRole
             when OL =:= ?DUN_ONLINE_YES orelse OL =:= ?DUN_ONLINE_DELAY ->
                NewDungeonRole = send_dun_rewards_do(DungeonRole, DunType, DunId),
                case IsEnd of
                    ?DUN_IS_END_YES ->
                        NewDungeonRole1 = NewDungeonRole#dungeon_role{is_end_out = ?DUN_IS_END_OUT_YES};
                    _ ->
                        NewDungeonRole1 = NewDungeonRole
                end,
                NewRoleList = lists:keystore(RoleId, #dungeon_role.id, RoleList, NewDungeonRole1),
                State#dungeon_state{role_list = NewRoleList};
            _ -> TmpState
        end
    end,
    NewState = lists:foldl(Fun, State, TmpRoleList),
    RoleName = case lists:keyfind(1, #dungeon_role.team_position, TmpRoleList) of
        #dungeon_role{figure = #figure{name = Name}} ->
            Name;
        _ ->
            <<>>
    end,
    %% 假人镜像
    DunDummys = maps:get(?DUN_STATE_SPECIAL_KEY_DUMMY, TypicalData, []),
    NewState1 = lists:foldl(fun(DunDummy, TempState1) -> 
        #dungeon_dummy{dummy_type = DummyType, mirror_id = MirrorId, server_id = ServerId} = DunDummy,
        if DummyType == ?DUN_MIRROR andalso ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
                if DunType == ?DUNGEON_TYPE_CRUSADE ->
                        if  Cluster == ?DUN_CLUSTER_YES ->
                                clusters_center:apply_cast(ServerId, lib_dungeon, send_dummy_reward, [DunType, DunId, MirrorId, RoleName]);
                            true ->
                                lib_dungeon:send_dummy_reward(DunType, DunId, MirrorId, RoleName)
                        end;
                    true ->
                        skip
                end,
                TempState1;
            true ->
                TempState1
        end
    end, NewState, DunDummys),
    NewState1.

%% 单人发奖励
send_dun_rewards_do(DungeonRole, DunType, DunId) ->
    #dungeon_role{id = RoleId, node = RoleNode, is_reward = IsReward, help_type = HelpType,
        online = Online, calc_reward_list = CalcRewards, custom_reward_list = CustomRewards} = DungeonRole,
    AllRewards = CalcRewards ++ CustomRewards,
    if IsReward =/= ?DUN_IS_REWARD_YES ->
        AllRewards =/= [] andalso send_reward_do(RoleNode, RoleId, HelpType, DunType, DunId, Online, AllRewards),
            DungeonRole#dungeon_role{is_reward = ?DUN_IS_REWARD_YES};
        true ->
            DungeonRole
    end.

%% 发送奖励
send_reward_do(Node, RoleId, HelpType, DunType, DunId, Online, Rewards) ->
    unode:apply(Node, lib_dungeon, dungeon_count_increment, [DunId, RoleId, HelpType, ?DUN_COUNT_DEDUCT_REWARD]),
    unode:apply(Node, ?MODULE, send_role_dun_rewards, [RoleId, Online, Rewards, DunType]),
    ok.

send_role_dun_rewards(RoleId, Online, Rewards, DunType) ->
    Pid = misc:get_player_process(RoleId),
    case is_pid(Pid) andalso misc:is_process_alive(Pid) of
        true ->
            if Online =/= ?DUN_ONLINE_YES ->
                    {Title, Content} = dungeon_mail(DunType),
                    lib_mail_api:send_sys_mail([RoleId], ?MOD_DUNGEON, Title, Content, Rewards);
                true ->
                    lib_goods_api:send_reward_by_id(Rewards, dungeon_pass, RoleId)
            end;
        _ ->
            {Title, Content} = dungeon_mail(DunType),
            lib_mail_api:send_sys_mail([RoleId], ?MOD_DUNGEON, Title, Content, Rewards)
    end.

dungeon_mail(DunType) ->
    case DunType of
        ?DUNGEON_TYPE_CRUSADE ->
            Title = utext:get(6100001), Content = utext:get(6100002);
	    ?DUNGEON_TYPE_CHILD ->
            Title = utext:get(6101101), Content = utext:get(6101102);
        _ ->
            Title = utext:get(13), Content = utext:get(14)
    end,
    {Title, Content}.

%% 副本通关触发
dungeon_result_trigger(?DUN_RESULT_TYPE_SUCCESS, ResultSubtype, DunId, LastWave, WaveNum, DunType, StartTime, PassTime, RoleList) ->
    [begin
        #dungeon_role{id = RoleId, help_type = HelpType, node = Node} = Role,
        Other = lib_dungeon_distribute:get_dun_result_succ_args(DunType, Role, [ResultSubtype, WaveNum, LastWave]),
        Data = #callback_dungeon_succ{
            dun_id     = DunId,
            dun_type   = DunType,
            dun_wave   = WaveNum,
            help_type  = HelpType,
            start_time = StartTime,
            pass_time  = PassTime,
            other      = Other
        },
        unode:apply(Node, lib_player_event, async_dispatch, [RoleId, ?EVENT_DUNGEON_SUCCESS, Data])
        end || Role <- RoleList
    ];
dungeon_result_trigger(_, ResultSubtype, DunId, LastWave, WaveNum, DunType, _StartTime, _ResultTime, RoleList) ->
    [begin
        #dungeon_role{id = RoleId, node = Node} = Role,
        Other = lib_dungeon_distribute:get_dun_result_succ_args(DunType, Role, [ResultSubtype, WaveNum, LastWave]),
        Data = #callback_dungeon_succ{
            dun_id = DunId, dun_type = DunType,
            dun_wave = WaveNum, other = Other
        },
        unode:apply(Node, lib_player_event, async_dispatch, [RoleId, ?EVENT_DUNGEON_FAIL, Data])
        end || Role <- RoleList
    ].

%% 副本结束后副本次数处理
dun_increment(RoleList, DunId, ResultType) ->
    #dun_cfg{type = DunType} = Dun = data_dungeon:get_dun(DunId),
    if
        ResultType == ?DUN_RESULT_TYPE_SUCCESS -> DeductTypeList = [?DUN_COUNT_DEDUCT_SUCCESS, ?DUN_COUNT_DEDUCT_END];
        ResultType == ?DUN_RESULT_TYPE_FAIL -> DeductTypeList = [?DUN_COUNT_DEDUCT_FAIL, ?DUN_COUNT_DEDUCT_END];
        true -> DeductTypeList = []
    end,
    F = fun(#dungeon_role{id = RoleId, help_type = HelpType, node = RoleNode, typical_data = TypicalData}= DunRole, TmpRoleList) ->
            case maps:get(?DUN_ROLE_DUN_COUNT_COST, TypicalData, 0) of
                0 ->
                    CostType = maps:get(?DUN_ROLE_COUNT_COST_TYPE, TypicalData, ?MOD_DUNGEON_ENTER),
                    CostList = maps:get(?DUN_ROLE_COST_LIST, TypicalData, []),
                    NewDunRole = DunRole#dungeon_role{typical_data = maps:put(?DUN_ROLE_DUN_COUNT_COST, 1, TypicalData)},
                    % [unode:apply(RoleNode, lib_dungeon, dungeon_count_increment_and_cost, [Dun, RoleId, HelpType, DeductType, CostList]) || DeductType <- DeductTypeList],
                    if 
                        DunType == ?DUNGEON_TYPE_CRUSADE ->
                            PlusCount = maps:get(?DUN_ROLE_DAILY_ENTER_COUNT, TypicalData, 1),
                            unode:apply(RoleNode, lib_dungeon, count_increment_and_cost_after_result, [Dun, RoleId, HelpType, DeductTypeList, CostType, CostList, {PlusCount, 0}]);
                        true ->
                            unode:apply(RoleNode, lib_dungeon, count_increment_and_cost_after_result, [Dun, RoleId, HelpType, DeductTypeList, CostType, CostList, {1, 0}])
                    end,
                    [NewDunRole|TmpRoleList];
                _A ->
                    [DunRole|TmpRoleList]
            end
    end,
    lists:foldl(F, [], RoleList).


%%创建假人
create_dummy(State, [_DunId, DummyList]) ->
    #dungeon_state{dun_id = DunId, dun_type = DunType, scene_id = SceneId, team_id = TeamId, scene_pool_id = ScenePoolId, typical_data = TypicalData} = State,
    % SumPower = lists:sum([P || #dungeon_role{combat_power = P} <- RoleList ]),
    % AveragePower = round(SumPower/length(RoleList)),
    Pid = self(),
    Fun = fun({Index, DummyMember}) ->
        #dun_multi_member{
            figure = Figure, attr_list = BattleAttr, server_num = ServNum, hp_lim = HpLim} = DummyMember,
        {SceneId1, BornX, BornY} = get_dummy_born_pos(DunType, DunId, SceneId, Index),
        SkillArgs = get_dun_dummy_skill_args(DummyMember),
        DummyArgs = [{dummy, #scene_dummy{team_id = TeamId, server_num = ServNum}}],
        Args = [{group, ?DUN_DEF_GROUP}, {find_target, 4000}, {find_type, 1}, {born_handler, {?MODULE, dummy_init_callback, [Pid, Figure#figure.id]}}],
        DunDummyArgs = lib_dungeon_distribute:get_dun_dummy_args(State, DummyMember),
        BattleAttr1 = lib_player_attr:set_battle_attr(#battle_attr{hp_lim = HpLim, attr = #attr{}}, [{?SPEED, ?SPEED_VALUE}] ++ BattleAttr),
        % BattleAttr1 = lib_fake_role:get_dungeon_faker_role_attr(Lv, AveragePower)
        % NewBattelAttr = lib_dummy_api:change_battle_attr(BattleAttr, [{hp_r, 10}]),
        lib_scene_object:async_create_a_dummy(SceneId1, ScenePoolId, BornX, BornY, self(), 1, Figure, BattleAttr1, DummyArgs ++ DunDummyArgs ++ SkillArgs ++ Args)
    end,
    lists:map(Fun, DummyList),
    DunDummys = lib_dungeon:dun_multi_member_to_dun_dummy([Dummy || {_, Dummy} <- DummyList]),
    NewTypicalData = maps:put(?DUN_STATE_SPECIAL_KEY_DUMMY, DunDummys, TypicalData),
    State#dungeon_state{typical_data = NewTypicalData}.

get_dummy_born_pos(?DUNGEON_TYPE_GUARD, _DunId, SceneId, Index) ->
    BornPos = data_dun_guard:get_kv(born_pos),
    case lists:keyfind(Index, 1, BornPos) of
        {_, X, Y} -> 
            {SceneId, X, Y};
        _ ->
            get_dummy_born_pos(0, _DunId, SceneId, Index)
    end;
get_dummy_born_pos(_, DunId, _, _Index) ->
    case data_dungeon:get_dun(DunId) of
        #dun_cfg{scene_id = Scene} ->
            #ets_scene{x = X, y = Y} = data_scene:get(Scene),
            {Scene, X, Y};
        _ -> {0, 0, 0}
    end.

dummy_init_callback(init, #ob_act{object = Dummy}, [FromPid, OldDummyId]) ->
    #scene_object{id = Id} = Dummy,
    mod_dungeon:get_dummy_id(FromPid, Id, OldDummyId),
    ok.

%% 排除怒气技能，普攻技能只要第一个普攻技能
filter_anger_skill([], SKillList) ->
    urand:list_shuffle(SKillList);
filter_anger_skill([{SkillId, SkillLev} | T], SKillList) ->
    SKillList2 = case data_skill:get(SkillId, SkillLev) of
        #skill{is_anger = 1} -> %% 排除怒气技能
            SKillList;
        #skill{is_normal = 1, skill_link = [NormalSkillId | _LinkT]} -> %% 普攻技能
            lists:keystore(NormalSkillId, 1, SKillList, {NormalSkillId, SkillLev});
        _ ->
            [{SkillId, SkillLev} | SKillList]
    end,
    filter_anger_skill(T, SKillList2).

merge_skill([], Skills) -> Skills;
merge_skill([{SkillId, _Lv}|Rest], Skills) ->
    case lists:keyfind(SkillId, 1, Skills) of
        {_, _} ->
            merge_skill(Rest, Skills);
        _ ->
            [{SkillId, _Lv}|Skills]
    end.

get_dun_dummy_skill_args(DummyMember) ->
    #dun_multi_member{figure = Figure, is_dummy = IsDummy, skills = Skills, skills_passive = SkillsPassive} = DummyMember,
    #figure{career = Career, sex = Sex} = Figure,
    SkillArgs = 
    if IsDummy == ?DUN_MIRROR ->
            Skill  = lib_skill_api:get_career_active_skill_ids(Career, Sex),
            Skills1 = filter_anger_skill(Skill, []),
            Skills2 = merge_skill(Skills1, Skills),
            [{skill, Skills2}, {skill_passive, SkillsPassive}];
        true ->
            Skill  = lib_skill_api:get_career_active_skill_ids(Career, Sex),
            Skill1 = filter_anger_skill(Skill, []),
            [{skill, Skill1}]
    end,
    SkillArgs.

%% 副本内挑战下一关默认操作
enter_next_dun(State, NewDungeonRole, DunType, DunId) ->
    #dungeon_state{dun_id = OldDunId} = State,
    case check_enter_next_dun(OldDunId, DunId, State) of
        true ->
            handle_enter_next_dun(State, NewDungeonRole, DunType, DunId);
        _A ->
            State
    end.

handle_enter_next_dun(State, DungeonRole, DunType, DunId) ->
    #dun_cfg{scene_id = SceneId, type = DunType, time = Time, born_type = BornType} = data_dungeon:get_dun(DunId),
    #dungeon_state{
        scene_id = OldSceneId, scene_pool_id = OldScenePool, 
        force_quit_ref = ForceQuitRef, close_ref = OldCloseRef
    } = State,
    Now = utime:unixtime(),
    EndTime = Now + Time,
    InitWave = 1,
    if SceneId =/= OldSceneId ->
            {NewSceneId, NewScenePoolId} = dungeon_logic:get_dun_scene_pool_id(DunType, BornType, SceneId, [DungeonRole]);
        true ->
            NewScenePoolId = OldScenePool,
            NewSceneId = SceneId
    end,
    util:cancel_timer(ForceQuitRef),
    NewDungeonRole = get_first_enter_out_scene_info(State#dungeon_state.role_list, DungeonRole),
    NewState = State#dungeon_state{
        dun_id = DunId,
        scene_id = NewSceneId,
        scene_pool_id = NewScenePoolId,
        start_time = Now,
        end_time = EndTime,
        role_list = [NewDungeonRole],
        wave_num = 0,
        wave_info = [],
        init_wave_sign = 0,
        mon_auto_id = 1,
        is_end = 0,
        result_type = 0,
        result_subtype = 0,
        result_time = 0,
        next_wave_time = [],
        grade_ref = []
    },
    StateAfScore  = lib_dungeon_distribute:make_grade_event(NewState),
    StateAfRating = lib_dungeon_distribute:make_grade_rating(StateAfScore),
    StateAfWave   = lib_dungeon_distribute:init_dun_wave(StateAfRating, InitWave),
    CloseRef      = util:send_after(OldCloseRef, (Time+2)*1000, self(), {'dungeon_timeout'}),
    dungeon_logic:pull_player_into_dungeon(self(), StateAfWave, NewDungeonRole),
    StateAfWave#dungeon_state{close_ref = CloseRef}.

%% 获取第一次进去时的回原场景信息
get_first_enter_out_scene_info([OldDungeonRole|_], DungeonRole) ->
    #dungeon_role{
        scene = OldOutSceneId, scene_pool_id = OldOutScenePoolId,
        copy_id = OldOutCopyId, x = OldOutX, y = OldOutY
    } = OldDungeonRole,
    #dungeon_role{typical_data = TypicalData} = DungeonRole,
    DungeonRole#dungeon_role{
        scene = OldOutSceneId, scene_pool_id = OldOutScenePoolId,
        copy_id = OldOutCopyId, x = OldOutX, y = OldOutY,
        typical_data = maps:put(?DUN_ROLE_DUN_ENTER_NEXT, 1, TypicalData)
    }.

check_enter_next_dun(OldDunId, DunId, State) ->
    #dungeon_state{dun_type = DunType, result_type = ResultType, end_time = EndTime} = State,
    CheckList = [
        {not_same_dun, OldDunId, DunId},
        {next_dun_check, OldDunId, DunId, DunType},
        {old_dun_result, ResultType},
        {dun_time_end, EndTime}],
    case util:check_lists(?MODULE, CheckList) of
        true ->
            true;
        _A ->
            false
    end.

check({not_same_dun, OldDunId, DunId}) ->
    if OldDunId == DunId ->
            {false, ?FAIL};
        true ->
            true
    end;
check({next_dun_check, OldDunId, DunId, DunType}) ->
    if DunType == ?DUNGEON_TYPE_TOWER ->
            case data_tower_dun:get_tower_next_dun(OldDunId) of
                DunId ->
                    true;
                _ ->
                    {false, ?FAIL}
            end;
        DunType == ?DUNGEON_TYPE_VISUS ->
            DunIds = data_dungeon:get_ids_by_type(DunType),
            IsVisusDun = lists:member(DunId, DunIds),
            IsVisusDun1 = lists:member(OldDunId, DunIds),
            NextDunId = OldDunId + 1,
            MaxDunId = lists:max(DunIds),
            if (not IsVisusDun) orelse (not IsVisusDun1) orelse
                 MaxDunId == OldDunId orelse NextDunId =/= DunId ->
                    {false, ?FAIL};
                true ->
                    true
            end;
        DunType == ?DUNGEON_TYPE_PET ->
            case data_dungeon_pet:get_dun_pet_next_cfg(OldDunId) of
                DunId ->
                    true;
                _ ->
                    {false, ?FAIL}
            end;
        DunType == ?DUNGEON_TYPE_GALAXY ->
            case data_galaxy_dun:get_galaxy_next_dun(OldDunId) of
                DunId ->
                    true;
                _ ->
                    {false, ?FAIL}
            end;
        true ->
            {false, ?FAIL}
    end;
check({old_dun_result, ResultType}) ->
    if ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
            true;
        true ->
            {false, ?FAIL}
    end;
check({dun_time_end, EndTime}) ->
    Now = utime:unixtime(),
    if Now >= EndTime ->
            {false, ?FAIL};
        true ->
            true
    end;
%% 是否已拿到奖励,拿到奖励才进行下一关
check(_) ->
    {false, ?FAIL}.

%% ---------------------------------------------------------------------------------------

make_record(dun_wave_info, [WaveKey, Progress, MonNum, WaveStatus, TypicalData]) ->
    #dun_wave_info{
        key = WaveKey,
        progress = Progress,
        require = MonNum,
        status = WaveStatus,
        typical_data = TypicalData
    }.

%% 副本日志
%% @param LogType 记录类型
%% @param Args 参数 [{result_type, ResultType}, {result_subtype, }]
log_dungeon(State, LogType, Args) ->
    #dungeon_state{dun_id = DunId} = State,
    Dun = data_dungeon:get_dun(DunId),
    case lib_dungeon:is_dungeon_single(Dun#dun_cfg.condition) of
        true -> log_single_dungeon(State, LogType, Args);
        false -> log_multi_dungeon(State, LogType, Args)
    end.

role_out_log(DungeonRole, State, LogType, Args) ->
    #dungeon_state{dun_id = DunId} = State,
    Dun = data_dungeon:get_dun(DunId),
    case lib_dungeon:is_dungeon_single(Dun#dun_cfg.condition) of
        true -> log_single_dungeon(State, DungeonRole, LogType, Args);
        _    -> log_multi_dungeon(State, DungeonRole, LogType, Args)
    end.

%% 单人副本
log_single_dungeon(State, LogType, Args) ->
    #dungeon_state{role_list = RoleList} = State,
    F = fun(DungeonRole) -> log_single_dungeon(State, DungeonRole, LogType, Args) end,
    lists:foreach(F, RoleList).

%% 单人BOSS、跨服据点副本不记录日志
log_single_dungeon(#dungeon_state{dun_type = DunType} = State, DungeonRole, LogType, Args)
    when DunType =/= ?DUNGEON_TYPE_PER_BOSS
    orelse DunType =/= ?DUNGEON_TYPE_POINT
    ->
    #dungeon_state{dun_id = DunId, result_type = OldResultType, result_subtype = OldResultSubtype} = State,
    [ResultType, ResultSubtype] = calc_log_result(Args, [OldResultType, OldResultSubtype]),
    #dungeon_role{id = RoleId, figure = #figure{lv = Lv}, combat_power = CombatPower, drop_reward_list = RewardList} = DungeonRole,
    do_log_single_dungeon(State, DungeonRole, [RoleId, Lv, CombatPower, DunId, DunType, util:term_to_string(RewardList), ResultType, ResultSubtype, LogType]),
    ok;
log_single_dungeon(_, _, _, _) -> ok.

do_log_single_dungeon(State, DungeonRole, [RoleId, Lv, CombatPower, DunId, ?DUNGEON_TYPE_EXP, MakeRewardList, ResultType, ResultSubtype, LogType]) ->
    #dungeon_state{cluster = Cluster, wave_num = WaveNum} = State,
    #dungeon_role{typical_data = TypicalData} = DungeonRole,
    MaxWave = maps:get(?DUNGEON_REC_MAX_SCORE, TypicalData, 1),
    ReMark = lists:concat(["max wave:", MaxWave, " now wave:", WaveNum]),
    case Cluster == ?DUN_CLUSTER_NO andalso lib_player:is_online_global(RoleId) of
        true ->
            Args = [RoleId, Lv, CombatPower, DunId, ?DUNGEON_TYPE_EXP, MakeRewardList, ResultType, ResultSubtype, LogType, ReMark, utime:unixtime()],
            lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, do_log_single_dungeon_help, [Args]);
        false ->
            mod_log:add_log(log_single_dungeon, [RoleId, Lv, CombatPower, DunId, ?DUNGEON_TYPE_EXP, MakeRewardList, ResultType, ResultSubtype, LogType, ReMark, utime:unixtime()])
    end;
do_log_single_dungeon(State, _DungeonRole, [RoleId, Lv, CombatPower, DunId, DunType, MakeRewardList, ResultType, ResultSubtype, LogType]) ->
    if DunType == ?DUNGEON_TYPE_PER_BOSS ->
            ReMarkStr = lists:concat(["boss num:", State#dungeon_state.wave_num]);
        true ->
            ReMarkStr = util:term_to_string([])
    end,
    mod_log:add_log(log_single_dungeon, [RoleId, Lv, CombatPower, DunId, DunType, MakeRewardList, ResultType, ResultSubtype, LogType, ReMarkStr, utime:unixtime()]);
do_log_single_dungeon(_, _, _) ->
    ok.

do_log_single_dungeon_help(_PS, [RoleId, Lv, CombatPower, DunId, DunType = ?DUNGEON_TYPE_EXP, MakeRewardList, ResultType, ResultSubType, LogType, ReMark, Time]) ->
    mod_log:add_log(log_single_dungeon, [RoleId, Lv, CombatPower, DunId, DunType, MakeRewardList, ResultType, ResultSubType, LogType, ReMark, Time]);
do_log_single_dungeon_help(PS, [RoleId, Lv, CombatPower, DunId, DunType, MakeRewardList, ResultType, ResultSubtype, LogType, ReMark, Time]) ->
    #player_status{goods_buff_exp_ratio = BuffExpRatio} = PS,
    Ratio = round(BuffExpRatio * 100),
    ReMarkList = lists:concat([ReMark, " buff_add:", Ratio, "%"]),
    mod_log:add_log(log_single_dungeon, [RoleId, Lv, CombatPower, DunId, DunType, MakeRewardList, ResultType, ResultSubtype, LogType, ReMarkList, Time]);
do_log_single_dungeon_help(PS, _) ->
    PS.

%% 多人副本
log_multi_dungeon(State, LogType, Args) ->
    #dungeon_state{role_list = RoleList} = State,
    F = fun(DungeonRole) -> log_multi_dungeon(State, DungeonRole, LogType, Args) end,
    lists:foreach(F, RoleList).

log_multi_dungeon(State, DungeonRole, LogType, Args) ->
    #dungeon_state{dun_id = DunId, dun_type = DunType, team_id = TeamId, role_list = RoleList, result_type = OldResultType, result_subtype = OldResultSubtype} = State,
    [ResultType, ResultSubtype] = calc_log_result(Args, [OldResultType, OldResultSubtype]),
    % #dungeon{diff_lv = DiffLv} = data_dungeon:get(DunId),
    #dungeon_role{node = Node, id = RoleId, figure = #figure{lv = Lv, guild_id = GuildId}, combat_power = CombatPower, drop_reward_list = RewardList, help_type = HelpType} = DungeonRole,
    LeftRoleIdList = [{TmpRoleId, TmpNode}||#dungeon_role{id = TmpRoleId, node = TmpNode}<-RoleList, RoleId=/=TmpRoleId],
    case LeftRoleIdList of
        [] -> RoleId1 = 0, Node1 = "undefined", RoleId2 = 0, Node2 = "undefined", RoleId3 = 0, Node3 = "undefined";
        [{RoleId1, Node1}] -> RoleId2 = 0, Node2 = "undefined", RoleId3 = 0, Node3 = "undefined";
        [{RoleId1, Node1}, {RoleId2, Node2}] -> RoleId3 = 0, Node3 = "undefined";
        [{RoleId1, Node1}, {RoleId2, Node2}, {RoleId3, Node3}|_] -> ok
    end,
    Score = 0, Grade = 0,
    mod_log:add_log(log_multi_dungeon, [RoleId, Node, Lv, CombatPower, GuildId, DunId, DunType, _DiffLv = 0, TeamId, HelpType, RoleId1, Node1, RoleId2, Node2, RoleId3, Node3, util:term_to_string(RewardList),
        Grade, Score, "", "", "", "", "", _MonScore = 0, ResultType, ResultSubtype, LogType, utime:unixtime()]),
    ok.

%% 日志更新
calc_log_result([], Result) -> Result;
calc_log_result([H|T], [OldResultType, OldResultSubtype]) ->
    case H of
        {result_type, ResultType} -> calc_log_result(T, [ResultType, OldResultSubtype]);
        {result_subtype, ResultSubtype} -> calc_log_result(T, [OldResultType, ResultSubtype])
    end.

%% TA数据上报
ta_multi_dun_ret(State = #dungeon_state{dun_type = DunType}, Now) ->
    #dungeon_state{dun_id = DunId, result_type = ResultType, result_subtype = ResultSubtype, 
        start_time = StartTime, role_list = RoleList, typical_data = TypicalData, wave_num = DunWave} = State,
    PassTime = Now - StartTime,
    lists:map(fun(DungeonRole) ->
        role_ta_multi_dun_ret(DungeonRole, [ResultType, ResultSubtype, DunType, DunId, PassTime, TypicalData, DunWave])
    end, RoleList);
ta_multi_dun_ret(_, _) -> ok.

role_ta_multi_dun_ret(#dungeon_role{node = RoleNode, id = RoleId} = DungeonRole, [ResultType, ResultSubtype, 
 DunType = ?DUNGEON_TYPE_CRUSADE, DunId, PassTime, TypicalData|_]) ->
    TAFormatArgs = [is_single_mode, player_num, robot_num, assist_num],
    TAFormatArgsRet = lists:map(fun(Key) -> maps:get(Key, TypicalData, 0) end, TAFormatArgs),
    MaxRating = if ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
            calc_dun_rating(DunId, DungeonRole);
        true -> 0
    end,
    TAFormatArgsRet1 = [MaxRating, DunId, ResultSubtype, PassTime] ++ TAFormatArgsRet,
    spawn(fun() -> timer:sleep(1000),
    clusters_center:apply_cast(RoleNode, lib_player, apply_cast,
        [RoleId, ?APPLY_CAST_STATUS, ?HAND_OFFLINE, lib_dungeon_api, ta_multi_dun_ret, [DunType, TAFormatArgsRet1]])
    end);

role_ta_multi_dun_ret(#dungeon_role{node = RoleNode, id = RoleId, team_position = TeamPos, cost_revive_count = CostRevive, revive_count = ReviveCount}, [_ResultType, ResultSubtype,
 DunType = ?DUNGEON_TYPE_GUARD, DunId, PassTime, TypicalData, DunWave]) ->
    TAFormatArgs = [player_num, robot_num, power_need],
    TAFormatArgsRet = lists:map(fun(Key) -> maps:get(Key, TypicalData, 0) end, TAFormatArgs),
    {Role2Power, Role3Power} = calc_other_power(RoleId, TypicalData),
    {HpTop, HpMid, HpBot}    = calc_tower_hp(DunId, TypicalData),
    IsLeader = ?IF(TeamPos == 1, true, false),
    TAFormatArgsRet1 = [ReviveCount+CostRevive, IsLeader] ++ TAFormatArgsRet ++ [DunId, Role2Power, Role3Power, ResultSubtype, PassTime, DunWave, HpTop, HpMid, HpBot],
    spawn(fun() -> timer:sleep(1000),
    clusters_center:apply_cast(RoleNode, lib_player, apply_cast, 
        [RoleId, ?APPLY_CAST_STATUS, ?HAND_OFFLINE, lib_dungeon_api, ta_multi_dun_ret, [DunType, TAFormatArgsRet1]])
    end);

role_ta_multi_dun_ret(_DungeonRole, _) -> ok.

calc_other_power(RoleId, TypicalData) ->
    RolePowers = maps:get(rec_role_power, TypicalData, []),
    NewRolePowers = lists:keydelete(RoleId, 1, RolePowers),
    case NewRolePowers of
        [{_RoleId2, Power2}, {_RoleId3, Power3}] -> ok;
        [{_RoleId2, Power2}] -> Power3 = 0;
        _ -> Power2 = 0, Power3 = 0
    end,
    {Power2, Power3}.

calc_tower_hp(DunId, TypicalData) ->
    Towers = [MonIdX || {MonIdX, _X, _Y} <- data_dun_guard:get_tower_pos(DunId)], 
    TowerHp = maps:get(?DUN_STATE_SPECIAL_KEY_TOWER_HP, TypicalData, []),
    lists:foldl(fun({MonId, Hp}, {HpTop, HpMid, HpBot}) ->
        case data_mon:get(MonId) of
            #mon{hp_lim = HpLim} -> 
                Rest = round(Hp/HpLim*100), 
                case ulists:index_of(MonId, Towers) of
                    1 -> {Rest, HpMid, HpBot};
                    2 -> {HpTop, Rest, HpBot};
                    3 -> {HpTop, HpMid, Rest}
                end;
            _ -> {HpTop, HpMid, HpBot}
        end
    end, {0, 0, 0}, TowerHp).

%% 创建波数怪
create_dun_wave_mon(MonList, Wave, State) ->
    #dungeon_state{
        dun_id = DunId, dun_type = DunType, role_list = RoleList, mon_auto_id = MonAutoId,
        scene_id = NowSceneId, scene_pool_id = ScenePoolId, start_time = StartTime,
        index_of_wave = WaveIndex, create_gap_ref = GapRef
    } = State,
    #dun_cfg{
        is_use_power_crush = IsUsePowerCrush, is_use_attr_crush = IsUseAttrCrush,
        recommend_power = DunRecommendPower, recommend_attr = DunRecommendAttr
    } = data_dungeon:get_dun(DunId),
    #base_dun_wave_cfg{
        recommend_power = WaveRecommendPower,
        recommend_attr = WaveRecommendAttr
    } = data_dungeon_wave:get_dun_wave(DunId, Wave),
    
    DunType = data_dungeon:get_dun_type(DunId),
    if (DunType == ?DUNGEON_TYPE_PET orelse DunType == ?DUNGEON_TYPE_CHILD) andalso MonList =/= [] ->
            [CreateMon|RestMonList] = MonList,
            CreateMonList = [CreateMon],
            if RestMonList =/= [] ->
                    TGapRef = erlang:send_after(?CREATE_MON_GAP, self(), {'create_dun_wave_mon', Wave, RestMonList}),
                    NewGapRef = [TGapRef|GapRef];
                true ->
                    NewGapRef = GapRef
            end;
        DunType == ?DUNGEON_TYPE_GUARD andalso MonList =/= [] ->
            {CreateMonList, NewGapRef} = lib_dungeon_guard:get_create_mon(Wave, MonList, GapRef);
        true ->
            {CreateMonList, NewGapRef} = calc_create_mon_list(StartTime, Wave, MonList, GapRef)
    end,
    
    [#dungeon_role{x = RoleX, y = RoleY, pid = RolePid}|_] = RoleList,
    
    %% 其他参数
    ExtraAttrs = lib_dungeon_distribute:get_create_mon_args(State),
    %% 战力碾压或属性碾压属性
    {NewRecommendPower, NewRecommendAttr} = get_power_crush_val(DunType, DunRecommendPower, DunRecommendAttr, WaveRecommendPower, WaveRecommendAttr),
    CrushRAttr = get_crush_args(DunType, IsUsePowerCrush, IsUseAttrCrush, RoleList, NewRecommendPower, NewRecommendAttr),

    %% 创怪 Num:所在这一波的创建顺序,MonAutoIdX:自增Id
    MonFun = fun
        ({MonId, X, Y, _RandWeight, ConfigAttrs}, {Num, MonAutoIdX}) ->
            MonType    = mon_type(MonId),
            MonKind    = mon_kind(MonId),
            {MonKey, ConfigAttrs1} = mon_attrs(ConfigAttrs, MonAutoIdX, Wave, WaveIndex),
            MonAttrs   = [{create_key, MonKey}, {type, MonType}],
            case MonKind of
                ?MON_KIND_WALK_AUTO -> {BornX, BornY} = lib_dungeon_child:get_born_pos(MonAutoIdX);
                _ -> {BornX, BornY} = {X, Y}
            end,
            SpecialAttrs = special_attrs(DunType, Wave, Num+1, MonId, MonAutoIdX, {BornX, BornY}),
            FinalAttrs = lists:append([ConfigAttrs1, MonAttrs, CrushRAttr, ExtraAttrs, SpecialAttrs]),
            create_dun_mon_do(DunType, DunId, RolePid, NowSceneId, ScenePoolId, RoleX, RoleY, BornX, BornY, MonId, MonType, FinalAttrs),
            {Num+1, MonAutoIdX+1};
        ({MonId, X, Y}, {Num, MonAutoIdX}) ->
            MonType    = mon_type(MonId),
            {MonKey, _} = mon_attrs([], MonAutoIdX, Wave, WaveIndex),
            MonAttrs   = [{create_key, MonKey}, {type, MonType}],
            SpecialAttrs = special_attrs(DunType, Wave, Num+1, MonId, MonAutoIdX, {X, Y}),
            FinalAttrs = lists:append([MonAttrs, CrushRAttr, ExtraAttrs, SpecialAttrs]),
            create_dun_mon_do(DunType, DunId, RolePid, NowSceneId, ScenePoolId, RoleX, RoleY, X, Y, MonId, MonType, FinalAttrs),
            {Num+1, MonAutoIdX+1};
        (_, {Num, MonAutoIdX}) ->
            {Num, MonAutoIdX}
    end,
    {_, MonAutoId1} = lists:foldl(MonFun, {0, MonAutoId}, CreateMonList),
    State#dungeon_state{mon_auto_id = MonAutoId1, create_gap_ref = NewGapRef}.

create_dun_mon_do(_DunType, _DunId, _RolePid, NowSceneId, ScenePoolId, _RoleX, _RoleY, X, Y, MonId, MonType, FinalAttrs) ->
    MonX = X, MonY = Y,
    lib_mon:async_create_mon(MonId, NowSceneId, ScenePoolId, MonX, MonY, MonType, self(), 1, FinalAttrs).

%% 给怪物传进特殊属性
special_attrs(DunType, Wave, MonIndex, MonId, MonAutoIdX, {X, Y}) ->
    case DunType of
        %% 宝宝副本怪物逃跑时间
        ?DUNGEON_TYPE_CHILD ->
            Path = lib_dungeon_child:create_mon_path({X, Y}, MonId),
            BornArgs = [{path, Path},{state, walk}],
            BornHandler = {lib_dungeon_child, when_walk_mon_born, [self(), Wave, MonIndex, utime:unixtime()]},
            HpChangeHandler = {lib_dungeon_child, when_walk_mon_hp_change, []},
            case MonId == ?PET_DUN_BOX_MON of
                true -> DieArgs = [{die_handler, {lib_dungeon_child, when_box_mon_die, [self(), Wave]}}];
                _ -> DieArgs = []
            end,
            Pos = lib_dungeon_child:get_born_spot(MonAutoIdX),
            ExtraArgs = [{born_handler, BornHandler}, {hp_change_handler, HpChangeHandler}, {pos, Pos}],
            BornArgs ++ ExtraArgs ++ DieArgs;
        ?DUNGEON_TYPE_PET ->
            PathList = data_dungeon_pet:get_dun_pet_kv_cfg(mon_road),
            case PathList of
                [] ->
                    Path = lib_scene_object_ai:dest_path(X, Y, 3672, 3569, 200),
                    [{path, Path}, {state, walk}
                        ,{walk_end_handler, {lib_dungeon_pet, walk_end_handle, []}}
                    ];
                _ ->
                    case lists:keyfind({X, Y}, 1, PathList) of
                        {_, {TX, TY}} ->
                            Path = lib_scene_object_ai:dest_path(X, Y, TX, TY, 200),
                            [{path, Path}, {state, walk}
                                ,{walk_end_handler, {lib_dungeon_pet, walk_end_handle, []}}
                            ];
                        _ ->
                            []
                    end
            end;
        ?DUNGEON_TYPE_GALAXY ->
            #mon{hp_lim = HpLim} = data_mon:get(MonId),
            [{hp_change_handler, {lib_dungeon_galaxy, hp_change_handle, [HpLim]}}];
        ?DUNGEON_TYPE_TOWER -> [{angle, 250}];
        ?DUNGEON_TYPE_GUIDE_BOSS ->
            #mon{hp_lim = HpLim} = data_mon:get(MonId),
            TriggerLoss = round(HpLim*0.1),
            [{hp_change_handler, {lib_dungeon_guide_boss, hp_change_handle, [HpLim, 0, TriggerLoss, 0]}}];
        _ -> []
    end.

mon_type(MonId) ->
    case data_mon:get(MonId) of
        #mon{type = MonType} ->
            MonType;
        _ ->
            1
    end.

mon_kind(MonId) ->
    case data_mon:get(MonId) of
        #mon{kind = MonKind} ->
            MonKind;
        _ ->
            0
    end.

mon_attrs(ConfigAttrs, MonAutoId, Wave, WaveIndex) ->
    case lists:keyfind(wave, 1, ConfigAttrs) of
        {_, ConfigWave} = WaveConfig ->
            MonKey     = {?DUN_WAVE_MON_KEY, MonAutoId, ConfigWave, WaveIndex},
            ConfigAttrs1 = ConfigAttrs -- [WaveConfig];
        _ ->
            MonKey     = {?DUN_WAVE_MON_KEY, MonAutoId, Wave, WaveIndex},
            ConfigAttrs1 = ConfigAttrs
    end,
    {MonKey, ConfigAttrs1}.

%% 获得战力碾压和属性碾压的值
get_power_crush_val(DunType, DunRecommendPower, DunRecommendAttr, WaveRecommendPower, WaveRecommendAttr) ->
    CheckRes = lists:member(DunType, wave_recommend_args_dun_types()),
    if
        CheckRes == true ->
            {WaveRecommendPower, WaveRecommendAttr};
        true ->
            {DunRecommendPower, DunRecommendAttr}
    end.

wave_recommend_args_dun_types() ->
    [
        ?DUNGEON_TYPE_EXP,
        ?DUNGEON_TYPE_PET,
        ?DUNGEON_TYPE_VISUS,
        ?DUNGEON_TYPE_PER_BOSS
    ].

%% 获得战力碾压或者属性碾压
get_crush_args(DunType, IsUsePowerCrush, IsUseAttrCrush, RoleList, RecommendPower, RecommendAttr) ->
    if
        IsUsePowerCrush == 1 -> %% 如果配了两种会默认优先判断战力碾压
            get_power_crush_attr(DunType, IsUsePowerCrush, RoleList, RecommendPower);
        IsUseAttrCrush == 1 ->
            get_attr_crush_attr(RoleList, RecommendAttr);
        true ->
            get_power_crush_attr(DunType, IsUsePowerCrush, RoleList, RecommendPower)
    end.

%% 获得战力碾压
get_power_crush_attr(_DunType, 0, _RoleList, _RecommendPower) -> [];
get_power_crush_attr(_DunType, _IsUsePowerCrush, [], _RecommendPower) -> [];
get_power_crush_attr(DunType, _IsUsePowerCrush, RoleList, RecommendPower) when RecommendPower =/= 0 ->
    CombatPowerSum = lists:sum([CombatPower||#dungeon_role{combat_power = CombatPower}<-RoleList]),
    CombatPower = CombatPowerSum/length(RoleList),
    DiffRatio = (RecommendPower - CombatPower) / RecommendPower,
    case data_dungeon:get_power_crush_r(DiffRatio) of
        [] -> [];
        CrushR ->
            LastCrushR = lib_dungeon_distribute:get_power_crush_calc(DunType, CrushR),
            [{dun_crush_r, LastCrushR}]
    end;
get_power_crush_attr(_, _, _, _) -> [].

%% 获得属性碾压
get_attr_crush_attr([], _RecommendAttr) -> [];
get_attr_crush_attr(_RoleList, []) -> [];
get_attr_crush_attr(RoleList, RecommendAttr) ->
    [{AttrId, AttrVal}|_] = RecommendAttr,
    Fun = fun(#dungeon_role{base_attr = BaseAttrList}, AttrList) ->
        lib_player_attr:add_attr(list, [BaseAttrList, AttrList])
    end,
    SumAttrList = lists:foldl(Fun, [], RoleList),
    Fun1 = fun({Key, Val}, CalcList)->
        case lists:keyfind(Key, 1, SumAttrList) of
            {_, SumVal} when SumVal < Val ->
                DiffRatio = (Val - SumVal) / Val,
                [{Key, DiffRatio} | CalcList];
            _ -> CalcList
        end
    end,
    case lists:foldl(Fun1, [], [{AttrId, AttrVal}]) of
        [] -> [];
        List ->
            Ratio = get_attr_crush_r(List, 0),
            CrushR = 1 + Ratio,
            [{dun_crush_r, CrushR}]
    end.

get_attr_crush_r([], Ratio) -> Ratio;
get_attr_crush_r([{Key, DiffRatio} | T], Ratio) ->
    case data_dungeon:get_attr_crush_r(Key, DiffRatio) of
        [] -> get_attr_crush_r(T, Ratio);
        CrushR -> get_attr_crush_r(T, Ratio + CrushR)
    end.

calc_create_mon_list(StartTime, Wave, MonList, GapRef) ->
    NowTime = utime:unixtime(),
    {RestMonList, CreateMonList, CreateMonDelay} = calc_this_time_create(StartTime, NowTime, MonList),
    if RestMonList == [] orelse CreateMonDelay == 0 ->
            {CreateMonList, GapRef};
        true ->
            Ref = erlang:send_after(CreateMonDelay, self(), {'create_dun_wave_mon', Wave, RestMonList}),
            {CreateMonList, [Ref|GapRef]}
    end.

calc_this_time_create(StartTime, NowTime, MonList) ->
    LastTime = round(NowTime - StartTime)*1000,
    {NewDelayMons, NewNormalMons, CreateMonDelay} =
    lists:foldl(fun
        (Mon = {MonId, X, Y, RandWeight, ConfigAttrs}, {DelayMons, NormalMons, MinTime}) -> 
            case lists:keyfind(del_c, 1, ConfigAttrs) of
                {_, CreateTime} when LastTime < CreateTime ->
                    RestTime   = round(CreateTime - LastTime),
                    if MinTime == 0 andalso RestTime =/= 0 ->
                            NewMinTime = RestTime;
                        true ->
                            NewMinTime = min(RestTime, MinTime)
                    end,
                    {[{MonId, X, Y, RandWeight, ConfigAttrs}|DelayMons], NormalMons, NewMinTime};
                {_, _C} ->
                    {DelayMons, [{MonId, X, Y, RandWeight, lists:keydelete(del_c, 1, ConfigAttrs)}|NormalMons], MinTime};
                _ ->
                    {DelayMons, [Mon|NormalMons], MinTime}
            end;
        (Mon = {_MonId, _X, _Y}, {DelayMons, NormalMons, MinTime}) ->
            {DelayMons, [Mon|NormalMons], MinTime}
    end, {[], [], 0}, MonList),
    {NewDelayMons, NewNormalMons, CreateMonDelay}.

get_role_dun_born_xy(State, _RoleId, X, Y) ->
    #dungeon_state{dun_id = DunId, wave_num = Wave} = State,
    case data_dungeon_wave:get_dun_wave(DunId, Wave) of
        #base_dun_wave_cfg{born_xy = [{CfgX, CfgY}|_]} ->
            {CfgX, CfgY};
        _ ->
            {X, Y}
    end.

role_gm_get_mon_info(RoleId, Args) ->
    lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, role_proc_gm_get_mon_info, [RoleId, Args]).

role_proc_gm_get_mon_info(Player, _, Args) ->
    #player_status{copy_id = CopyId, dungeon = StatusDungeon} = Player,
    case lib_dungeon:is_on_dungeon(Player) of
        true ->
            #status_dungeon{dun_id = DunId} = StatusDungeon,
            case lib_dungeon:is_cluster_dungeon_by_dun_id(DunId) of
                false ->
                    CopyId ! {apply, ?MODULE, gm_get_mon_info, Args};
                _ ->
                    clusters_node:apply_cast(erlang, send, [CopyId, {apply, ?MODULE, gm_get_mon_info, Args}])
            end;
        _ ->
            skip
    end,
    {ok, Player}.

gm_get_mon_info(State, Args) ->
    #dungeon_state{scene_id = SceneId, scene_pool_id = ScenePoolId} = State,
    CopyId = self(),
    MonInfos = mon_mgr:gm_get_mon_info(SceneId, ScenePoolId, CopyId, Args),
    ?ERR("dun mon info ~p", [MonInfos]),
    ok.