%% ---------------------------------------
%% module:lib_dungeon_guard.erl
%% desc  :守卫信标(三人守塔)
%% time  :20/07/04
%% author:hh
%% ---------------------------------------

-module(lib_dungeon_guard).

-export([
    login_init/1
    ]).

-export([
    dispatch_dun_event/2
    ,get_role_rec/6
    ,get_create_mon_args/1
    ,get_create_mon/3
    ,get_role_dun_born_xy/4
    ,get_dun_dummy_args/2
    ,handle_wave_state/2
    ,handle_kill_mon_do/2
    ,check_quit_state/2
    ,get_send_reward/2
    ,handle_disconnect/4
    ,handle_role_out/5
    ,push_settlement/2
    ,dungeon_end_clear_mon/2
    
    ,handle_tower_killed/2
    ,handle_dungeon_result/1
    ,dungeon_update_tower_hp/2
    ,dungeon_born_wave_notice/2
    ,fin_enter_dun/2
    ]).

-export([
    hp_change_handle/3
    ,mon_born_callback/3
    ,dummy_die_callback/5]).

-export([
    role_update_guard_dun_wave_rec/4
    ,refresh_guard_dun_pass_reward/2
    ,role_refresh_guard_dun_pass_reward/2
    ,update_dun_count/2
    ,local_update_guard_assist_count/2
    ,local_increment_guard_assist_count/2
    ,role_update_guard_assist_count/2
    ,role_increment_guard_assist_count/2
    ,calc_buff_hurt/2
    ,calc_guard_buff/2
    ,get_guard_buff_time/1
    ,calc_guard_dun_pass/1
    ]).

-export([
    send_weekly_account_mail/4
    ]).

-export([
    gm_add_mon/3
    ,role_gm_add_mon/3
    % ,calc_finish_max_wave/2
    ]).

-define(QUICK_PASS_CD,      5).      %% 快速通关CD
-define(QUICK_PASS_WAVE,    5).      %% 快速通关可同时召唤波数

-include("def_module.hrl").
-include("predefine.hrl").
-include("scene.hrl").
-include("attr.hrl").
-include("dungeon.hrl").
-include("server.hrl").
-include("scene.hrl").
-include("dun_guard.hrl").
-include("common.hrl").
-include("def_fun.hrl").
-include("figure.hrl").
-include("errcode.hrl").
-include("dungeon_multi.hrl").
-include("language.hrl").
-include("rec_event.hrl").
-include("def_event.hrl").

login_init(RoleId) ->
    case db:get_all(?SELECT_ROLE_DUN_GUARD_ACHIEVE, [RoleId]) of
        DataList = [_|_] ->
            change_data_format(DataList, []);
        _ ->
            []
    end.

change_data_format([], Acc) -> Acc;
change_data_format([[DunId, Wave, State|_]|Rest], Acc) ->
    change_data_format(Rest, [{{DunId, Wave}, State}|Acc]).


%% 拦截初始波数事件
dispatch_dun_event(Msg = {'init_dun_wave', Wave}, State) ->
    %% 塔怪处理
    NewState = create_tower_mon(Wave, State),
    #dungeon_state{typical_data = TypicalData} = NewState,
    Waves = maps:get(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, TypicalData, []),
    case lists:member(Wave, Waves) of
        true ->
            NewState;
        _ ->
            %% 波数怪默认处理
            dungeon_default:dispatch_dun_event(Msg, NewState)
    end;

%% 快速通关(创建下一波怪物)
dispatch_dun_event({'quick_pass_next_dun', Node, Sid}, State) ->
    #dungeon_state{
        wave_num = WaveNum, role_list = RoleList, dun_id = DunId,
        typical_data = TypicalData, is_end = IsEnd} = State,
    if IsEnd == ?DUN_IS_END_YES ->
            NewState1 = State;
        true ->
            Now = utime:unixtime(),
            LastTime = maps:get(?DUN_STATE_SPECIAL_KEY_DUN_QUICK_CD, TypicalData, 0),
            Waves = maps:get(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, TypicalData, []),
            AllWaveNum = length(Waves),
            Pass = Now - LastTime,
            if WaveNum == 1 andalso Waves == [] ->
                    clusters_center:apply_cast(Node, lib_server_send, send_to_sid, [Sid, pt_611, 61103, [?ERRCODE(err610_have_not_start), 0]]),
                    NewState1 = State;
                true -> 
                    if (LastTime =/= 0 andalso Pass >= ?QUICK_PASS_CD) orelse LastTime == 0 ->
                            LeastMaxWave = calc_least_pass_wave(RoleList),
                            if WaveNum < LeastMaxWave ->
                                    if AllWaveNum < ?QUICK_PASS_WAVE ->
                                            NewTypicalData = maps:put(?DUN_STATE_SPECIAL_KEY_DUN_QUICK_CD, Now, TypicalData),
                                            DunWaveCfg = data_dungeon_wave:get_dun_wave(DunId, WaveNum+1),
                                            NewState = quick_start_wave(DunWaveCfg, State),
                                            clusters_center:apply_cast(Node, lib_server_send, send_to_sid, [Sid, pt_611, 61103, [?SUCCESS, 0]]),
                                            NewState1 = NewState#dungeon_state{typical_data = NewTypicalData};
                                        true ->
                                            clusters_center:apply_cast(Node, lib_server_send, send_to_sid, [Sid, pt_611, 61103, [?ERRCODE(err610_guard_pass_max_wave), 0]]),
                                            NewState1 = State
                                    end;
                                true ->
                                    clusters_center:apply_cast(Node, lib_server_send, send_to_sid, [Sid, pt_611, 61103, [?ERRCODE(err610_guard_pass_max_wave), 0]]),
                                    NewState1 = State
                            end;
                        true ->
                            Rest = round(?QUICK_PASS_CD - Pass),
                            clusters_center:apply_cast(Node, lib_server_send, send_to_sid, [Sid, pt_611, 61103, [?ERRCODE(err610_quick_pass_cd), Rest]]),
                            NewState1 = State
                    end
            end
    end,
    NewState1;

%% 队伍聊天
dispatch_dun_event({'team_chat', Sid, RoleId, Node, Msg}, State) ->
    #dungeon_state{role_list = RoleList} = State,
    case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
        #dungeon_role{figure = Figure} ->
            lists:map(fun(#dungeon_role{node = RoleNode, id = Role}) ->
                {ok, BinDataX} = pt_611:write(61109, [?SUCCESS, Figure, Msg]),
                lib_server_send:send_to_uid(RoleNode, Role, BinDataX) 
            end, RoleList);
        _ ->
            {ok, BinData} = pt_611:write(61109, [?FAIL, #figure{}, Msg]),
            lib_server_send:send_to_sid(Node, Sid, BinData)
    end,
    State;

%% 其他信息默认处理
dispatch_dun_event(Msg, State) ->
    dungeon_default:dispatch_dun_event(Msg, State).

%% 进入副本时获取人物副本记录
get_role_rec(_DunId, _DunType, _RoleId, DungeonRec, _DunData, TypicalData) ->
    case lists:keyfind(?DUNGEON_REC_MAX_SCORE, 1, DungeonRec) of
        {_, MaxDunWave} -> ok;
        _ -> MaxDunWave = 0
    end,
    NewTypicalData = maps:put(?DUNGEON_REC_MAX_SCORE, MaxDunWave, TypicalData),
    NewTypicalData1 = maps:put(?DUN_ROLE_GUARD_INIT_MAX_WAVE, MaxDunWave, NewTypicalData),
    NewTypicalData1.

%% 获取副本创怪参数
get_create_mon_args(State) ->
    #dungeon_state{wave_num = WaveNum, typical_data = TypicalData} = State,
    TowerStatus = maps:get(?DUN_STATE_SPECIL_KEY_TOWER_STATUS, TypicalData, []),
    Towers = [MonId || {{MonId, _MonAutoId}, _} <- TowerStatus],
    [{walk_auto, {_AiOption = 1, Towers}}, {born_handler, {?MODULE, mon_born_callback, [self(), WaveNum]}}].

%% 获取本次将要创建的怪物
get_create_mon(Wave, MonList, GapRef) ->
    {CreateMonList, RestMonList} = %{MonList, []},
    lists:foldl(fun(MonInfo = {MonId, _X, _Y, _Rand, _Attrs}, {CreateMons, RestMons}) ->
        case lists:keyfind(MonId, 1, CreateMons) of
            {_, _, _, _, _} ->
                {CreateMons, [MonInfo|RestMons]};
            _ ->
                {[MonInfo|CreateMons], RestMons}
        end
    end, {[], []}, MonList),
    if RestMonList =/= [] ->
            TGapRef = erlang:send_after(800, self(), {'create_dun_wave_mon', Wave, RestMonList}),
            NewGapRef = [TGapRef|GapRef];
        true ->
            NewGapRef = GapRef
    end,
    {CreateMonList, NewGapRef}.

%% 获取副本创假人参数
get_dun_dummy_args(_State, DummyMember) ->
    #dun_multi_member{id = DummyId} = DummyMember,
    Mfa = {?MODULE, dummy_die_callback, [self(), DummyId]},
    [{warning_range, 2000}, {tracing_distance, 400}, {die_handler, Mfa}, {stay_dead, 1}].

get_role_dun_born_xy(State, RoleId, ChangeX, ChangeY) ->
    #dungeon_state{role_list = RoleList} = State,
    case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
        #dungeon_role{team_position = Index} ->
            BornPos = data_dun_guard:get_kv(born_pos),
            case lists:keyfind(Index, 1, BornPos) of
                {_, X, Y} ->
                    {X, Y};
                _ ->
                    {ChangeX, ChangeY}
            end;
        _ ->
            {ChangeX, ChangeY}
    end.

%% 处理波数状态
handle_wave_state(State, [WaveStatus, Wave|_] = Args) ->
    #dungeon_state{
        dun_id = DunId, wave_num = WaveNum, team_id = TeamId, 
        role_list = RoleList, typical_data = TypicalData, wave_info = WaveInfos
    } = State,
    UnFinishWaves = maps:get(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, TypicalData, []),
    FinishMaxWave = calc_finish_max_wave(WaveNum, UnFinishWaves, WaveInfos),
    % ?ERR("wave ~p fin max wave ~p", [Wave, FinishMaxWave]),
    %% 更新历史波数记录
    if WaveStatus == ?WAVE_NEXT orelse WaveStatus == ?DUN_PASS ->
            NewRoleList = update_guard_dun_wave_rec(DunId, FinishMaxWave, TeamId, RoleList),
            NewState = State#dungeon_state{role_list = NewRoleList};
        true ->
            NewState = State
    end,
    %% 处理波数状态
    NewState1 =
    if WaveStatus == ?WAVE_NEXT andalso UnFinishWaves == [] ->
            MaxWave = lists:max(data_dungeon_wave:get_dun_waves(DunId)),
            if WaveNum >= MaxWave ->
                    dungeon_default:handle_wave_state_normal(NewState, [?DUN_PASS, Wave]);
                true ->
                    dungeon_default:handle_wave_state_normal(NewState, Args)
            end;
        WaveStatus == ?WAVE_NEXT ->
            NewState;
        true ->
            dungeon_default:handle_wave_state_normal(NewState, Args)
    end,
    NewState1.

%% 处理怪物击杀
handle_kill_mon_do(State, [Mid, _Killer, _MonXys, Wave|_]) ->
    #dungeon_state{wave_info = WaveInfos, wave_num = WaveNum, typical_data = TypicalData, role_list = RoleList} = State,
    NewState = 
    case lists:keyfind({Wave, 1}, #dun_wave_info.key, WaveInfos) of
        #dun_wave_info{status = ?DUN_WAVE_FIN} ->
            Waves = maps:get(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, TypicalData, []),
            MaxWave = calc_least_pass_wave(RoleList),
            case lists:member(Wave, Waves) of
                true ->
                    NewWaves = lists:delete(Wave, Waves),
                    AccessMax = calc_access_max_wave(NewWaves, WaveNum, MaxWave),
                    {ok, BinData} = pt_611:write(61107, [NewWaves, AccessMax]),
                    dungeon_logic:notice_dun_role_info(RoleList, BinData),
                    NewTypicalData = maps:put(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, NewWaves, TypicalData),
                    State#dungeon_state{typical_data = NewTypicalData};
                _ ->
                    State
            end;
        _ ->
            State
    end,
    #dungeon_state{typical_data = NewTypicalData1} = NewState,
    KillMonList = maps:get(?DUN_STATE_SPECIAL_KEY_MON_KILL_CALC, NewTypicalData1, []),
    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,
    NewTypicalData2 = maps:put(?DUN_STATE_SPECIAL_KEY_MON_KILL_CALC, NewKillMonList, NewTypicalData1),
    NewState#dungeon_state{typical_data = NewTypicalData2}.

%% fixed    :波数奖励 or 助战奖励
%% optional :全服首通(邮件发放)
get_send_reward(DungeonState, DungeonRole) ->
    #dungeon_state{dun_id = DunId, result_type = ResultType, wave_info = WaveInfos} = DungeonState,
     if 
        ResultType =/= ?DUN_RESULT_TYPE_SUCCESS ->
            [];
        DungeonRole#dungeon_role.help_type =:= ?DUN_TEAM_HELP_NO ->
            case data_dungeon:get_dun(DunId) of
                #dun_cfg{base_reward = BaseRewards, rand_reward = RandRewards} -> ok;
                _ -> BaseRewards = [], RandRewards = []
            end,
            PassRewards1  = dungeon_default:calc_reward_with_rand(BaseRewards),
            RandRewards1  = dungeon_default:calc_reward_with_rand(RandRewards),
            WaveRewards   = calc_wave_rewards(DunId, WaveInfos),
            FinalRewards  = lists:append([PassRewards1, RandRewards1, WaveRewards]),
            FinalRewards1 = lib_goods_api:make_reward_unique(FinalRewards),
            DungeonRole#dungeon_role{calc_reward_list = FinalRewards1};
        true ->
            DungeonRole
     end.

%% 退出检查结果类型
check_quit_state(State, RoleId) ->
    #dungeon_state{is_end = IsEnd, role_list = RoleList, wave_info = WaveInfos, result_type = ResultType, result_subtype = ResultSubType} = State,
    RoleLen = length(RoleList),
    if 
        RoleLen > 1 ->
            {ResultType, ResultSubType};
        WaveInfos == [] ->
            {?DUN_RESULT_TYPE_FAIL, ?DUN_RESULT_SUBTYPE_ACTIVE_QUIT};
        IsEnd =/= ?DUN_IS_END_YES ->
            case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
                #dungeon_role{online = Online} when Online == ?DUN_ONLINE_YES orelse Online == ?DUN_ONLINE_DELAY ->
                    NewResultType = calc_dun_guard_result(WaveInfos),
                    {NewResultType, ?DUN_RESULT_SUBTYPE_ACTIVE_QUIT};
                _ ->
                    {?DUN_RESULT_TYPE_FAIL, ?DUN_RESULT_SUBTYPE_LOGOUT}
            end;
        true ->
            {ResultType, ResultSubType}
    end.

%% 中途退出处理(不走正常退出流程的操作)
handle_disconnect(RoleId, _Hp, _HpLim, State) ->
    #dungeon_state{dun_id = DunId, team_id = MulTeamId, role_list = RoleList, is_end = IsEnd} = State,
    if IsEnd == ?DUN_IS_END_YES ->
            State;
        true ->
            DunRole = lists:keyfind(RoleId, #dungeon_role.id, RoleList),
            if not is_record(DunRole, dungeon_role) ->
                    State;
                true ->
                        RoleLen = length(RoleList),
                        ResultSubType = ?DUN_RESULT_SUBTYPE_ROLE_DISCONNECT,
                        %% 没人了先结算
                        if RoleLen > 1 -> 
                                ResultType = ?DUN_RESULT_TYPE_NO,
                                NewState0 = State;
                            true ->
                                {ResultType, _} = check_quit_state(State, RoleId),
                                NewState0 = lib_dungeon_distribute:dungeon_result(State, ResultType, ResultSubType)
                        end,
                        #dungeon_state{role_list = NewRoleList, wave_info = WaveInfos} = NewState0,
                        #dungeon_role{node = Node, help_type = HelpType, typical_data = TypicalData} = NewDungeonRole = lists:keyfind(RoleId, #dungeon_role.id, NewRoleList),
                        % ResultType = calc_dun_guard_result(WaveInfos),
                        
                        %% 发奖
                        Rewards = if HelpType == ?DUN_TEAM_HELP_NO ->
                                calc_wave_rewards(DunId, WaveInfos);
                            true ->
                                []
                        end,
                        Rewards =/= [] andalso unode:apply(Node, lib_mail_api, send_sys_mail,
                         [[RoleId], ?MOD_DUNGEON, <<"副本中途离开"/utf8>>, <<"由于你的异常操作,导致在守卫信标时掉线,发此奖励,烦请查收"/utf8>>, Rewards]),
                        %% 扣次数
                        CountCost = maps:get(?DUN_ROLE_DUN_COUNT_COST, TypicalData, 0),
                        if CountCost == 0 ->
                                DunRole1 = cost_role_dun_count(ResultType, NewDungeonRole, DunId);
                            true ->
                                DunRole1 = NewDungeonRole
                        end,

                        NewDunRole  = DunRole1#dungeon_role{online = ?DUN_ONLINE_NO, is_reward = ?DUN_IS_REWARD_NO},
                        NewRoleList1 = lists:keystore(RoleId, #dungeon_role.id, RoleList, NewDunRole),
                        NewState    = NewState0#dungeon_state{role_list = NewRoleList1},
                        LogType     = ?DUN_LOG_TYPE_DISCONNECT, 
                        %% 退队
                        Msg = {leave_team, {RoleId}, MulTeamId},
                        lib_dun_multi_cluster:cast_dun_multi_hall_cluster(DunId, Msg),
                        dungeon_default:handle_role_out_normal(RoleId, LogType, ResultType, ResultSubType, NewState)
            end
    end.

%% 正常退出处理
handle_role_out(RoleId, LogType, ResultType, ResultSubType, State) ->
    #dungeon_state{dun_id = DunId, dun_type = DunType, role_list = RoleList, wave_num = WaveNum, wave_info = WaveInfos, is_end = IsEnd, typical_data = DunTypicalData} = State,
    case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
        DunRole = #dungeon_role{id = RoleId, node = Node, figure = #figure{power = Power}, typical_data = TypicalData, is_reward = IsReward, help_type = HelpType} ->
            CountCost = maps:get(?DUN_ROLE_DUN_COUNT_COST, TypicalData, 0),
            DungeonDummys = maps:get(?DUN_STATE_SPECIAL_KEY_DUMMY, DunTypicalData, []),
            if ResultType == ?DUN_RESULT_TYPE_NO ->
                    %% 检查支援值奖励
                    AssistRoles = calc_access_assist_role(RoleList, DungeonDummys),
                    Ret = lists:filter(fun(DungeonMate) -> 
                        case DungeonMate of
                            #dungeon_role{id = RoleIdX} -> RoleIdX == RoleId;
                            #dungeon_dummy{mirror_id = RoleIdX} -> RoleIdX == RoleId;
                            _ -> false
                        end
                    end, AssistRoles),
                    if Ret =/= [] ->
                            Waves = maps:get(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, DunTypicalData, []),
                            FinishWave = calc_finish_max_wave(WaveNum, Waves, WaveInfos),
                            AssistVal = data_dun_guard:get_wave_assist_val(DunId, FinishWave),
                            %?ERR("finish wave ~p ~p ~p", [FinishWave, AssistVal, Node]),
                            AssistVal =/= 0 andalso Node =/= undefined andalso
                             clusters_center:apply_cast(Node, guard_dun_assist_local, update_guard_assist, [RoleId, Power, AssistVal]);
                        true ->
                            skip
                    end,
                    NewResultType = calc_dun_guard_result(WaveInfos);
                true ->
                    NewResultType = ResultType
            end,
            %% 普通通关奖励
            if IsReward == ?DUN_IS_REWARD_NO ->
                    if IsEnd == ?DUN_IS_END_YES -> NewDunRole1 = DunRole;
                        true ->
                            WaveRewards = calc_wave_rewards(DunId, WaveInfos),
                            PassRewards = 
                            if 
                                HelpType == ?DUN_TEAM_HELP_NO -> WaveRewards;
                                true -> []
                            end,
                            NewDunRole = DunRole#dungeon_role{calc_reward_list = PassRewards},
                            if PassRewards =/= [] -> NewDunRole1 = dungeon_default:send_dun_rewards_do(NewDunRole, DunType, DunId);
                                true -> NewDunRole1 = NewDunRole
                            end
                    end;
                true -> NewDunRole1 = DunRole
            end,
            %% 次数扣除
            if CountCost == 0 ->
                    NewDunRole2 = cost_role_dun_count(NewResultType, NewDunRole1, DunId),
                    NewRoleList = lists:keystore(RoleId, #dungeon_role.id, RoleList, NewDunRole2);
                true ->
                    NewRoleList = lists:keystore(RoleId, #dungeon_role.id, RoleList, NewDunRole1)
            end,
            spawn(fun() -> timer:sleep(1000), lib_player:apply_cast(Node, RoleId, ?APPLY_CAST_STATUS, ?NOT_HAND_OFFLINE, ?MODULE, update_dun_count, [?DUNGEON_TYPE_GUARD]) end),
            NewState = State#dungeon_state{role_list = NewRoleList},
            dungeon_default:handle_role_out_normal(RoleId, LogType, ResultType, ResultSubType, NewState);
        _ ->
            dungeon_default:handle_role_out_normal(RoleId, LogType, ResultType, ResultSubType, State)
    end.

%% 结算
push_settlement(State, DungeonRole) ->
    #dungeon_state{
        result_type = ResultType, result_subtype = ResultSubType, wave_info = WaveInfos, 
        dun_id = DunId, scene_id = SceneId, role_list = RoleList, wave_num = WaveNum, index_of_wave = WaveIndex} = State,
    #dungeon_role{sid = Sid, node = Node, typical_data = RoleTypicalData} = DungeonRole,
    %% 真人伤害
    PackData = lists:foldl(fun(#dungeon_role{server_num = ServIdX, figure = Figure, typical_data = TypicalDataX}, Acc) ->
            MonHurt = maps:get(?DUN_ROLE_CRUSADE_DUN_MON_HURT, TypicalDataX, 0),
            [{ServIdX, Figure, MonHurt}|Acc]
         end, [], RoleList),
    %% 奖励
    RewardList = lib_dungeon:calc_base_reward_list(DungeonRole),
    InitRec = maps:get(?DUN_ROLE_GUARD_INIT_MAX_WAVE, RoleTypicalData, 0),
    NowRec = maps:get(?DUNGEON_REC_MAX_SCORE, RoleTypicalData, 0),
    IsNewRec = ?IF(NowRec > InitRec, 1, 0),
    PassWave = 
    if ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
            case lists:keyfind({WaveNum, WaveIndex}, #dun_wave_info.key, WaveInfos) of
                #dun_wave_info{status = ?DUN_WAVE_FIN} -> WaveNum;
                _ -> max(0, WaveNum-1)
            end;
        true -> max(0, WaveNum-1) 
    end,
    {ok, BinData} = pt_610:write(61030, [ResultType, ResultSubType, DunId, SceneId, RewardList, IsNewRec, NowRec, PassWave, PackData]),
    lib_server_send:send_to_sid(Node, Sid, BinData).

dungeon_end_clear_mon(State, _ResultType) ->
    #dungeon_state{dun_id = DunId, typical_data = TypicalData, scene_id = SceneId, scene_pool_id = ScenePoolId} = State,
    Waves = maps:get(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, TypicalData, []),
    TowerStatus = maps:get(?DUN_STATE_SPECIL_KEY_TOWER_STATUS, TypicalData, []),
    Mids = lists:foldl(fun(Wave, Acc) ->
        case data_dungeon_wave:get_dun_wave(DunId, Wave) of
            #base_dun_wave_cfg{mon_list = MonList} ->
                lists:foldl(fun({MonId, _, _, _, _}, TmpAcc) -> 
                    case lists:member(MonId, TmpAcc) of
                        true -> TmpAcc;
                        _    -> [MonId|TmpAcc]
                    end
                 end, Acc, MonList);
            _ -> Acc
        end
    end, [], Waves),
    TowerMids = lists:map(fun({{TowerMid, _}, _}) -> TowerMid end, TowerStatus),
    lib_mon:clear_scene_mon_by_mids(SceneId, ScenePoolId, self(), 1, Mids ++ TowerMids),
    true.

cost_role_dun_count(ResultType, DunRole, DunId) ->
    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,
    #dungeon_role{id = RoleId, help_type = HelpType, node = RoleNode, typical_data = TypicalData} = DunRole,
    case maps:get(?DUN_ROLE_DUN_COUNT_COST, TypicalData, 0) of
        0 ->
            NewDunRole = DunRole#dungeon_role{typical_data = maps:put(?DUN_ROLE_DUN_COUNT_COST, 1, TypicalData)},
            [unode:apply(RoleNode, lib_dungeon, dungeon_count_increment, [Dun, RoleId, HelpType, DeductType]) || DeductType <- DeductTypeList],
            NewDunRole;
        _ ->
            DunRole
    end.

update_dun_count(Player, DunType) ->
    pp_dungeon:handle(61020, Player, [DunType]),
    ok.

calc_dun_guard_result(WaveInfos) ->
    case lists:any(fun(#dun_wave_info{status = WaveStatus}) ->
        WaveStatus == ?DUN_WAVE_FIN
     end, WaveInfos) of
        true ->
            ?DUN_RESULT_TYPE_SUCCESS;
        _ ->
            ?DUN_RESULT_TYPE_FAIL
    end.

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

update_guard_dun_wave_rec(DunId, WaveNum, _TeamId, RoleList) ->
    NewRoleList = lists:foldl(fun(DunRole, TmpRoleList) ->
        NewDunRole = do_update_guard_dun_wave_rec(DunId, WaveNum, DunRole),
        [NewDunRole|TmpRoleList]
     end, [], RoleList),
    NewRoleList.

do_update_guard_dun_wave_rec(DunId, WaveNum, DunRole) ->
    #dungeon_role{node = Node, id = RoleId, help_type = HelpType, typical_data = TypicalData} = DunRole,
    OldMaxWave = maps:get(?DUNGEON_REC_MAX_SCORE, TypicalData, 0),
    if WaveNum > OldMaxWave andalso HelpType == ?DUN_TEAM_HELP_NO ->
            NewTypicalData = maps:put(?DUNGEON_REC_MAX_SCORE, WaveNum, TypicalData),
            lib_player:apply_cast(Node, RoleId, ?APPLY_CAST_SAVE, ?NOT_HAND_OFFLINE, ?MODULE, role_update_guard_dun_wave_rec, [DunId, ?DUNGEON_REC_MAX_SCORE, WaveNum]),
            DunRole#dungeon_role{typical_data = NewTypicalData};
        true ->
            DunRole
    end.

%% 更新玩家塔防副本历史最佳波数记录
role_update_guard_dun_wave_rec(Player, DunId, Type, WaveNum) ->
    #player_status{id = RoleId, dungeon_record = DunRecs, role_dun_guard = RoleDunGuard} = Player,
    %% 副本记录更新
    DunRec = maps:get(DunId, DunRecs, []),
    NewDunRec = lists:keystore(Type, 1, DunRec, {Type, WaveNum}),
    NewDunRecs = maps:put(DunId, NewDunRec, DunRecs),
    %% 里程碑状态更新
    AllAchieveWaves = data_dun_guard:get_dun_all_achieve_wave(DunId),
    IsAchieve = lists:member(WaveNum, AllAchieveWaves),
    if IsAchieve == true ->
            case lists:keyfind({DunId, WaveNum}, 1, RoleDunGuard) of
                {_, _} ->
                    NewRoleDunGuard = RoleDunGuard,
                    DbUpdate = false;
                _ ->
                    NewRoleDunGuard = lists:keystore({DunId, WaveNum}, 1, RoleDunGuard, {{DunId, WaveNum}, ?ACHIEVING}),
                    DbUpdate = true
            end;
        true ->
            NewRoleDunGuard = RoleDunGuard,
            DbUpdate = false
    end,
    DbUpdate == true andalso db:execute(?REPLACE_INTO_ROLE_DUN_ACHIEVE, [RoleId, DunId, WaveNum, ?ACHIEVING]),
    NewPlayer = Player#player_status{dungeon_record = NewDunRecs, role_dun_guard = NewRoleDunGuard},
    lib_dungeon:save_dungeon_record(RoleId, DunId, NewDunRec),
    {ok, NewPlayer}.

refresh_guard_dun_pass_reward(RoleId, DunId) ->
    lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, role_refresh_guard_dun_pass_reward, [DunId]).

role_refresh_guard_dun_pass_reward(Player, DunId) ->
    pp_dungeon:handle(61108, Player, [DunId]),
    {ok, Player}.

%% 更新守卫信标支援次数和恢复时间
local_update_guard_assist_count(RoleId, DunType) ->
    Pid = misc:get_player_process(RoleId),
    case is_pid(Pid) andalso misc:is_process_alive(Pid) of
        true ->
            lib_player:apply_cast(RoleId, ?APPLY_CAST, ?MODULE, role_update_guard_assist_count, [RoleId, DunType]);
        _ ->
            Count = lib_counter:get_count_offline(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_REWARD, DunType),
            RecoverTime = lib_counter:get_count_offline(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_RECOVER, DunType),
            lib_counter:set_count_offline(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_REWARD, DunType, max(Count-1, 0)),
            RecoverTime == 0 andalso lib_counter:set_count_offline(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_RECOVER, DunType, utime:unixtime())
    end.

role_update_guard_assist_count(RoleId, DunType) ->
    lib_counter:cut_count(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_REWARD, DunType, 1),
    RecoverTime = lib_counter:get_count(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_RECOVER, DunType),
    RecoverTime == 0 andalso lib_counter:set_count(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_RECOVER, DunType, utime:unixtime()),
    ok.

%% 增加玩家次数
local_increment_guard_assist_count(RoleId, DunType) ->
    Pid = misc:get_player_process(RoleId),
    case is_pid(Pid) andalso misc:is_process_alive(Pid) of
        true ->
            lib_player:apply_cast(RoleId, ?APPLY_CAST, ?MODULE, role_increment_guard_assist_count, [RoleId, DunType]);
        _ ->
            lib_counter:increment_offline(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_REWARD, DunType)
    end.

role_increment_guard_assist_count(RoleId, DunType) ->
    AssistCount = lib_counter:get_count(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_REWARD, DunType),
    NewAssistCount = AssistCount + 1,
    lib_counter:increment(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_REWARD, DunType),
    AssistLimit = data_dungeon_special:get(9, guard_dun_assist_limit),
    if NewAssistCount >= AssistLimit ->
            lib_counter:set_count(RoleId, ?MOD_DUN_MULTI, ?MOD_DUN_MULTI_ASSIST_RECOVER, DunType, 0);
        true ->
            skip
    end,
    ok.

% handle_event(Player, #event_callback{type_id = ?EVENT_ONLINE_FLAG, data = OnlineFlag}) ->
%     #player_status{id = RoleId, online = OldOnlineFlag} = Player,
%     if OnlineFlag == ?ONLINE_ON ->
%             clusters_node:apply_cast(guard_dun_assist_mgr, update_online_sign, [RoleId, ?ONLINE_ON]);
%         OldOnlineFlag == ?ONLINE_OFF andalso OnlineFlag == ?ONLINE_ON ->
%             clusters_node:apply_cast(guard_dun_assist_mgr, update_online_sign, [RoleId, ?ONLINE_OFF]);
%         true ->
%             skip
%     end,
%     {ok, Player}.

%% 塔怪处理
create_tower_mon(_Wave = 1, State) ->
    #dungeon_state{
        scene_id = SceneId, dun_id = DunId, scene_pool_id = ScenePool, mon_auto_id = MonAutoId, typical_data = TypicalData, role_list = RoleList} = State,
    Towers = data_dun_guard:get_tower_pos(DunId),
    % Towers = data_dun_guard:get_kv(tower_pos),
    TowerStatus = maps:get(?DUN_STATE_SPECIL_KEY_TOWER_STATUS, TypicalData, []),
    DunPid  = self(),
    {MonAutoId1, NewTowerStatus, NewTowerHp} = lists:foldl(fun({MonId, X, Y}, {TmpAutoId, TmpTowerStatus, TmpTowerHp}) ->
        #mon{type = MonType, hp_lim= InitHp} = data_mon:get(MonId),
        MonKey   = {?DUN_WAVE_MON_KEY, ?DUN_GUARD_TOWER, TmpAutoId},
        MonAttrs = [{create_key, MonKey}, {type, MonType}, {group, ?DUN_DEF_GROUP}, {hp_change_handler, {?MODULE, hp_change_handle, [DunPid]}}],
        lib_mon:async_create_mon(MonId, SceneId, ScenePool, X, Y, MonType, self(), 1, MonAttrs),
        NewTmpTowerStatus = lists:keystore({MonId, TmpAutoId}, 1, TmpTowerStatus, {{MonId, TmpAutoId}, ?TOWER_ALIVE}),
        NewTmpTowerHp = lists:keystore(MonId, 1, TmpTowerHp, {MonId, InitHp}),
        {TmpAutoId+1, NewTmpTowerStatus, NewTmpTowerHp}
     end, {MonAutoId, TowerStatus, _TowerHp = []}, Towers),
    NewTypicalData0 = maps:put(?DUN_STATE_SPECIL_KEY_TOWER_STATUS, NewTowerStatus, TypicalData),
    NewTypicalData  = maps:put(?DUN_STATE_SPECIAL_KEY_TOWER_HP, NewTowerHp, NewTypicalData0),
    NewState = State#dungeon_state{mon_auto_id = MonAutoId1, typical_data = NewTypicalData},
    notice_tower_hp(RoleList, NewTowerHp),
    NewState;
create_tower_mon(_, State) ->
    State.

%% 快速通关处理
quick_start_wave(DunWaveCfg, State) ->
    #dungeon_state{dun_id = DunId, wave_ref = [OldWaveStartRef, OldWaveEndRef|_]} = State,
    #base_dun_wave_cfg{limit_time = LimitTime, mon_list = MonList, wave = Wave, next_wave = NextWave} = DunWaveCfg,
    Now = utime:unixtime(),
    WaveStartTime = Now,
    NewState0 = dungeon_default:create_dun_wave(Wave, NextWave, length(MonList), WaveStartTime, State),
    NewState1 = dungeon_default:create_dun_wave_mon(MonList, Wave, NewState0),
    if  LimitTime == 0 ->
            util:cancel_timer(OldWaveEndRef),
            WaveEndRef = undefined;
        true ->
            WaveEndRef = util:send_after(OldWaveEndRef, (LimitTime+2)*1000, self(), {'wave_timeout', DunId, Wave, NewState1#dungeon_state.index_of_wave})
    end,
    NewState1#dungeon_state{wave_ref = [OldWaveStartRef, WaveEndRef]}.


%% 塔怪被击杀处理
handle_tower_killed(Args, State) ->
    #dungeon_state{typical_data = TypicalData} = State,
    [_, Mid, _, CreateKey|_] = Args,
    {?DUN_WAVE_MON_KEY, ?DUN_GUARD_TOWER, MonAutoId} = CreateKey,
    TowerStatus = maps:get(?DUN_STATE_SPECIL_KEY_TOWER_STATUS, TypicalData, []),
    case lists:keyfind({Mid, MonAutoId}, 1, TowerStatus) of
        {_, _} ->
            NewTowerStatus = lists:keystore({Mid, MonAutoId}, 1, TowerStatus, {{Mid, MonAutoId}, ?TOWER_KILLED}),
            TowerState = calc_tower_status(NewTowerStatus),
            NewTypicalData = maps:put(?DUN_STATE_SPECIL_KEY_TOWER_STATUS, NewTowerStatus, TypicalData),
            %% TODO 通知前端信息
            NewState = State#dungeon_state{typical_data = NewTypicalData},
            handle_tower_status(TowerState, NewState);
        _ ->
            State
    end.

calc_tower_status(Towers) ->
    case lists:all(fun({_, TowerStatus}) -> TowerStatus == ?TOWER_KILLED end, Towers) of
        true ->
            ?ALL_TOWER_DIE;
        _ ->
            ?TOWER_EXIST
    end.

handle_dungeon_result(State) ->
    #dungeon_state{
        dun_id = DunId, result_type = ResultType, start_time = StartTime, 
        role_list = RoleList, typical_data = TypicalData, wave_num = WaveNum, wave_info = WaveInfos} = State,
    Now = utime:unixtime(),
    PassTime = Now - StartTime,
    DungeonDummys = maps:get(?DUN_STATE_SPECIAL_KEY_DUMMY, TypicalData, []),
    UnFinishWaves = maps:get(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, TypicalData, []),
    MaxWaveNum = lists:max(data_dungeon_wave:get_dun_waves(DunId)),
    if ResultType == ?DUN_RESULT_TYPE_SUCCESS ->
            RoleList1 = [{RoleId, Figure, ServerId, ServerNum} || #dungeon_role{id = RoleId, figure = Figure, server_id = ServerId, server_num = ServerNum} <- RoleList],
            UnFinishWaves == [] andalso WaveNum >= MaxWaveNum andalso guard_dun_assist_mgr:update_guard_dun_pass_log(DunId, PassTime, RoleList1),
            AssistRoles = calc_access_assist_role(RoleList, DungeonDummys),
            Waves = maps:get(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, TypicalData, []),
            FinishWave = calc_finish_max_wave(WaveNum, Waves, WaveInfos),
            lists:map(fun({RoleIdX, _, SvrId, _}) -> 
                clusters_center:apply_cast(SvrId, ?MODULE, refresh_guard_dun_pass_reward, [RoleIdX, DunId])
            end, RoleList1),
            [begin
                case DungeonMate of
                    #dungeon_role{id = AssistRoleId, server_id = ServerIdX, figure = FigureX} -> ok;
                    #dungeon_dummy{mirror_id = AssistRoleId, server_id = ServerIdX, figure = FigureX} -> 
                        ok; % IsMirror = AssistRoleId =/= 0;
                    _ -> 
                        AssistRoleId = 0, ServerIdX = 0, FigureX = undefined
                end,
                if ServerIdX =/= 0 ->
                        %% 更新玩家支援值榜单
                        AssistVal = data_dun_guard:get_wave_assist_val(DunId, FinishWave),
                        AssistVal =/= 0 andalso clusters_center:apply_cast(ServerIdX, guard_dun_assist_local, update_guard_assist, [AssistRoleId, FigureX#figure.power, AssistVal]);
                    true ->
                        skip
                end
            end || DungeonMate <- AssistRoles],
            ok;
        true ->
            skip
    end,
    State.

handle_tower_status(?ALL_TOWER_DIE, State) ->
    % ResultType    = ?DUN_RESULT_TYPE_FAIL,
    ResultSubType = ?DUN_RESULT_SUBTYPE_TOWER_DIE,
    ResultType = calc_dun_guard_result(State#dungeon_state.wave_info),
    lib_dungeon_distribute:dungeon_result(State, ResultType, ResultSubType);
handle_tower_status(_, State) ->
    State.

%% 怪物进程回调-血量变化通知
hp_change_handle(MonState, _MonAtter, [DunPid]) ->
    #ob_act{object = #scene_object{config_id = MonId, battle_attr = #battle_attr{hp = Hp}}} = MonState,
    gen_server:cast(DunPid, {apply, ?MODULE, dungeon_update_tower_hp, [MonId, Hp]}),
    ok.

%% 怪物进程回调-出生通知
mon_born_callback(init, _Minfo, [DunPid, WaveNum]) ->
    gen_server:cast(DunPid, {apply, ?MODULE, dungeon_born_wave_notice, [WaveNum]}).

%% 假人死亡回调
dummy_die_callback(#ob_act{object = #scene_object{id = ObjId, x = X, y = Y, battle_attr = #battle_attr{hp_lim = HpLim}}} = State, _KList, _Atter, _AtterSign, [DunPid, DieId]) ->
    gen_server:cast(DunPid, {dummy_die, self(), DieId, ObjId, [X, Y, HpLim]}),
    State.

%% 副本进程回调-血量更新
dungeon_update_tower_hp(State = #dungeon_state{is_end = ?DUN_IS_END_NO}, [MonId, Hp]) ->
    #dungeon_state{role_list = RoleList, typical_data = TypicalData} = State,
    TowerHp = maps:get(?DUN_STATE_SPECIAL_KEY_TOWER_HP, TypicalData, []),
    NewTowerHp = lists:keystore(MonId, 1, TowerHp, {MonId, Hp}),
    NewTypicalData = maps:put(?DUN_STATE_SPECIAL_KEY_TOWER_HP, NewTowerHp, TypicalData),
    notice_tower_hp(RoleList, NewTowerHp),
    NewState = State#dungeon_state{typical_data = NewTypicalData},
    {noreply, NewState};
dungeon_update_tower_hp(State, _) ->
    {noreply, State}.

dungeon_born_wave_notice(State, [WaveNum]) ->
    #dungeon_state{typical_data = TypicalData, wave_num = NowWave, role_list = RoleList} = State,
    Waves = maps:get(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, TypicalData, []),
    NewState =
    case lists:member(WaveNum, Waves) of
        false ->
            NewWaves = [WaveNum|Waves],
            MaxWave = calc_least_pass_wave(RoleList),
            AccessMax = calc_access_max_wave(NewWaves, NowWave, MaxWave),
            NewTypicalData = maps:put(?DUN_STATE_SPECIAL_KEY_GUARD_WAVES, NewWaves, TypicalData),
            {ok, BinData} = pt_611:write(61107, [NewWaves, AccessMax]),
            dungeon_logic:notice_dun_role_info(RoleList, BinData),
            State#dungeon_state{typical_data = NewTypicalData};
        _ ->
            State
    end,
    {noreply, NewState}.

notice_tower_hp(RoleList, TowerHp) ->
    {ok, BinData} = pt_610:write(61080, [TowerHp]),
    dungeon_logic:notice_dun_role_info(RoleList, BinData).

fin_enter_dun(State, RoleId) ->
    #dungeon_state{role_list = RoleList} = State,
    MaxWave = calc_least_pass_wave(RoleList),
    case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
        #dungeon_role{node = Node} -> ok;
        _   -> Node = undefined
    end,
    if MaxWave > ?QUICK_PASS_WAVE ->
            AccessMax = ?QUICK_PASS_WAVE;
        true ->
            AccessMax = MaxWave
    end,
    {ok, BinData} = pt_611:write(61107, [[], AccessMax]),
    lib_server_send:send_to_uid(Node, RoleId, BinData),
    State.

calc_least_pass_wave(RoleList) ->
    [MaxWave|_] = lists:foldl(fun(#dungeon_role{typical_data = TypicalData}, Acc) -> 
        RoleMaxWave = maps:get(?DUN_ROLE_GUARD_INIT_MAX_WAVE, TypicalData, 0),
        case Acc of
            [] -> [RoleMaxWave];
            [OldMaxWave|_] ->
                if RoleMaxWave < OldMaxWave -> [RoleMaxWave];
                    true -> Acc
                end
        end
    end, [], RoleList),
    MaxWave.


calc_access_assist_role(RoleList, DungeonDummys) ->
    %% 过滤出协助的真实玩家或镜像
    {RoleId, RoleMinPower, HelpRoles} = calc_min_power_member(RoleList),
    {MirrorId, MirrorPower, MirrorHelpRoles} = calc_min_power_member(DungeonDummys),
    %% 排除战力最低的协助者或镜像
    AssistFilterRoleId =
    if RoleMinPower < MirrorPower -> RoleId;
        true -> MirrorId
    end,
    %% 排除战力最低的真实协助玩家或者镜像协助玩家
    AccessAssistRoles =
    lists:foldl(fun(DungeonMember, Acc) -> 
        case DungeonMember of
            #dungeon_role{id = RoleIdX2} -> ok;
            #dungeon_dummy{mirror_id = RoleIdX2} -> ok;
            _ -> RoleIdX2 = 0
        end,
        if RoleIdX2 =/= AssistFilterRoleId ->
                [DungeonMember|Acc];
            true ->
                Acc
        end
     end, [], HelpRoles ++ MirrorHelpRoles),
    AccessAssistRoles.

calc_min_power_member(RoleList) ->
    %% 过滤出协助玩家
    {MinPowerRoles, HelpRoles} = 
    lists:foldl(fun(DungeonMember, {Acc1, Acc2}) ->
        Ret =
        case DungeonMember of
            #dungeon_role{id = RoleIdX, combat_power = Power, help_type = ?DUN_TEAM_HELP_YES} ->
                {true, RoleIdX, Power};
            #dungeon_dummy{dummy_type = ?DUN_MIRROR, mirror_id = MirrorId, figure = #figure{power = Power}} ->
                {true, MirrorId, Power};
            #dungeon_role{id = RoleIdX, combat_power = Power} -> 
                {false, RoleIdX, Power};
            #dungeon_dummy{id = RoleIdX, figure = #figure{power = Power}} ->
                {false, RoleIdX, Power}
        end,
        {IsHelp, IdX, PowerX} = Ret,
        NewAcc2 = 
        case IsHelp of
            true  -> [DungeonMember|Acc2];
            false -> Acc2
        end,
        NewAcc1 =
        case Acc1 of
            [{_, MinPower}|_] ->
                if PowerX < MinPower -> [{IdX, PowerX}];
                    true -> Acc1
                end;
            _ -> [{IdX, PowerX}]
        end,
        {NewAcc1, NewAcc2}
    end, {[], []}, RoleList),
    case MinPowerRoles of
        [{RoleId, RoleMinPower}|_] -> ok;
        _ -> RoleId = 0, RoleMinPower = 0
    end,
    {RoleId, RoleMinPower, HelpRoles}.

%% 周支援榜结算
send_weekly_account_mail(RoleId, Rank, Val, Rewards) ->
    Title       = ?LAN_MSG(6100016),
    Content     = uio:format(?LAN_MSG(6100017), [Val, Rank]),
    lib_mail_api:send_sys_mail([RoleId], Title, Content, Rewards),
    ok.

calc_finish_max_wave(WaveNum, UnFinishWaves, WaveInfos) ->
    MaxWaveNum = calc_now_max_wave(WaveNum, WaveInfos),
    AllWaves = lists:seq(1, MaxWaveNum),
    FinishWaves = AllWaves -- UnFinishWaves,
    % ?ERR("finish ~p unfin ~p", [FinishWaves, UnFinishWaves]),
    case lists:sort(FinishWaves) of
        SortWaves = [_|_] ->
            calc_finish_max_wave_do(SortWaves, 1, []);
        _ ->
            0
    end.

calc_now_max_wave(_WaveNum, WaveInfos) ->
    lists:foldl(fun(#dun_wave_info{key = {Wave, _}}, TempAcc) -> 
        max(Wave, TempAcc)
    end, 0, WaveInfos).

%  全部波数 [1, 2, 3, 4, 5]
%% 只完成了 [5] 则 不算完成波数
%% 只完成了 [1, 5] 则 算完成波数1
%% 只完成了 [1, 2, 4] 则 算完成波数2
calc_finish_max_wave_do([], _, FinishWaves) -> 
    case FinishWaves of
        [_|_] ->
            lists:max(FinishWaves);
        _ ->
            0
    end;
calc_finish_max_wave_do([Wave|Rest], TempWave, TempFinishWaves) ->
    if TempWave == Wave ->
            calc_finish_max_wave_do(Rest, TempWave+1, [Wave|TempFinishWaves]);
        true ->
            case TempFinishWaves of
                [_|_] ->
                    lists:max(TempFinishWaves);
                _ ->
                    0
            end
    end.

gm_add_mon(RoleId, MonId, Num) ->
    lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, role_gm_add_mon, [MonId, Num]).

role_gm_add_mon(Player, MonId, Num) ->
    #player_status{scene = SceneId, scene_pool_id = ScenePool, copy_id = CopyId, x = X, y = Y} = Player,
    case data_mon:get(MonId) of
        #mon{type = Type} -> ok;
        _   -> Type = 1
    end,
    gm_create_mon(MonId, {SceneId, ScenePool, X, Y, Type, CopyId, _BroadCast = 1, []}, Num),
    {ok, Player}.

gm_create_mon(_MonId, _, 0) -> ok;
gm_create_mon(MonId, {SceneId, ScenePool, X, Y, Type, CopyId, BroadCast, Attrs}, Num) ->
    Rand = urand:rand(-10, 10),
    lib_mon:async_create_mon(MonId, SceneId, ScenePool, X+Rand, Y+Rand, Type, CopyId, BroadCast, Attrs),
    gm_create_mon(MonId, {SceneId, ScenePool, X, Y, Type, CopyId, BroadCast, Attrs}, Num-1).

calc_access_max_wave(ExistWaves, WaveNum, MaxWave) ->
    Len = length(ExistWaves),
    if Len >= ?QUICK_PASS_WAVE ->
            lists:max(ExistWaves);
        true ->
            CanAccess = ?QUICK_PASS_WAVE - Len,
            NewAccess = WaveNum + CanAccess,
            if NewAccess > MaxWave ->
                    MaxWave;
                true ->
                    NewAccess
            end
    end.

%% 是否有守卫信标特定时间阶段buff
%% 临时buff机制,指定时间内在特定场景(守卫信标场景)队伍获得一个输出最终伤害加强的效果
calc_guard_buff(SceneId, LongUnixTime) ->
    BuffEffectScenes = data_dun_guard:get_kv(buff_dungeon),
    SceneResult = lists:member(SceneId, BuffEffectScenes),
    if
        not SceneResult ->
            false;
        true ->
            [{{Hour1, Min1},{Hour2, Min2}},{{Hour3, Min3},{Hour4, Min4}}|_] = data_dun_guard:get_kv(buff_time),
            UnixDate = utime:unixdate(),
            BuffStartTime1  = UnixDate + Hour1*60*60+Min1*60,
            BuffEndTime1    = UnixDate + Hour2*60*60+Min2*60,
            BuffStartTime2  = UnixDate + Hour3*60*60+Min3*60,
            BuffEndTime2    = UnixDate + Hour4*60*60+Min4*60,
            Now             = round(LongUnixTime/1000),
            if ((Now >= BuffStartTime1 andalso Now =< BuffEndTime1) orelse (Now >= BuffStartTime2 andalso Now =< BuffEndTime2)) ->
                    true;
                true ->
                    false
            end
    end.    

%% 计算伤害加成
calc_buff_hurt(Hurt, true) ->
    BuffAddition = data_dun_guard:get_kv(buff_addition),
    round(Hurt*BuffAddition/10000);
calc_buff_hurt(Hurt, _) ->
    Hurt.

%% 获取下次或当前的buff时间段
get_guard_buff_time(Time) ->
    [{{Hour1, Min1},{Hour2, Min2}}, {{Hour3, Min3},{Hour4, Min4}}|_] = data_dun_guard:get_kv(buff_time),
    UnixDate = utime:unixdate(),
    BuffStartTime1  = UnixDate + Hour1*60*60+Min1*60,
    BuffEndTime1    = UnixDate + Hour2*60*60+Min2*60,
    BuffStartTime2  = UnixDate + Hour3*60*60+Min3*60,
    BuffEndTime2    = UnixDate + Hour4*60*60+Min4*60,
    if
        Time < BuffStartTime1 ->
            {BuffStartTime1, BuffEndTime1};
        Time >= BuffStartTime1 andalso Time =< BuffEndTime1 ->
            {BuffStartTime1, BuffEndTime1};
        Time >= BuffStartTime2 andalso Time =< BuffEndTime2 ->
            {BuffStartTime2, BuffEndTime2};
        Time > BuffEndTime1 andalso Time < BuffStartTime2 ->
            {BuffStartTime2, BuffEndTime2};
        true ->
            {0, 0}
    end.

%% 获取守卫信标副本通关状态
calc_guard_dun_pass(DungeonRecs) ->
    DunIds = data_dungeon:get_ids_by_type(?DUNGEON_TYPE_GUARD),
    lists:foldl(fun(DunId, AccPassState) ->
        DunRec = maps:get(DunId, DungeonRecs, []),
        case lists:keyfind(?DUNGEON_REC_MAX_SCORE, 1, DunRec) of
            {_, MaxWaveNum} -> 
                DunWaves = data_dungeon_wave:get_dun_waves(DunId),
                CfgMaxWave = lists:max(DunWaves),
                [{DunId, MaxWaveNum >= CfgMaxWave}|AccPassState];
            _ -> [{DunId, false}|AccPassState]
        end
    end, [], DunIds).