%% -----------------------------------
%% desc     :放置经验相关逻辑&接口
%% moudle   :lib_exp_dun_drop.erl
%% author   :hh
%% time     :20/01/18
%% -----------------------------------

-module(lib_exp_dun_drop).

-export([
    handle_event/2
    ,re_login/2
    ,calc_drop_role/3
    
    ,do_update_role_drop/2
    ,db_update_role_wave/4
    ,db_update_role_wave/5
    
    ,calc_manual_exp_drop_reward/1
    ,get_exp_drop_reward/1
    ,send_exp_drop_reward/5

    ,query_exp_drop_reward_return/2
    ,role_query_exp_drop_reward_return/2

    ,db_update_drop_role/2
    ,db_update_drop_role_do/1
    ,calc_avalible_loc_goods/2
    ,calc_exp_drop_add_ratio_rewards/2  %% 计算放置经验加成奖励
    ,tran_rewards_show/3
    ,get_exp_drop_time_limit/1
    ,calc_off_time_last/1

    ,get_extra_data/2
    ,update_drop_role_extra_data/2

    ,calc_rest_off_buff_time/3
    ,calc_new_calc_time_last/6
    ,get_rest_off_buff_time/1
    ]).

-export([role_send_drop_reward/5]).
-export([check/1]).

-export([do_calc_interval_goods/6]).
-export([gm_delete_exp_drop/1]).

-export([get_new_calc_time/3]).

-compile(export_all).

-include("server.hrl").
-include("common.hrl").
-include("def_goods.hrl").
-include("def_event.hrl").
-include("rec_event.hrl").
-include("exp_dun_drop.hrl").
-include("equipment.hrl").
-include("errcode.hrl").
-include("predefine.hrl").
-include("goods.hrl").
-include("def_vip.hrl").
-include("def_module.hrl").
-include("figure.hrl").
-include("drop.hrl").

handle_event(Player = #player_status{online = OnlineFlag}, #event_callback{type_id = ?EVENT_LOGIN_CAST}) ->
    #player_status{figure = #figure{lv = RoleLv}} = Player,
    OpenLv = data_module:get_module_open_lv(?MOD_DUNGEON, ?EXP_DROP_1),
    if RoleLv >= OpenLv andalso OnlineFlag == ?ONLINE_ON ->
            Now = utime:unixtime(),
            ExpDunDropRole = load_exp_drop_role(Player, Now),
            exp_dun_drop_mgr:role_login(ExpDunDropRole),
            {ok, Player};
        true ->
            {ok, Player}
    end;
handle_event(Player = #player_status{id = RoleId, sid = Sid, figure = #figure{lv = RoleLv}}, #event_callback{type_id = ?EVENT_LV_UP}) ->
    OpenLv = data_module:get_module_open_lv(?MOD_DUNGEON, ?EXP_DROP_1),
    if RoleLv == OpenLv ->
            Wave = 0,
            exp_dun_drop_api:notice_exp_dun_achieve(RoleId, Wave),
            lib_server_send:send_to_sid(Sid, pt_610, 61052, [utime:unixtime(), 0, 0, 0]);
        true ->
            skip
    end,
    {ok, Player};
handle_event(Player, _) ->
    {ok, Player}.

%% 假人托管期间真人上线
re_login(Player, Now) ->
    #player_status{figure = #figure{lv = RoleLv}} = Player,
    OpenLv = data_module:get_module_open_lv(?MOD_DUNGEON, ?EXP_DROP_1),
    if RoleLv >= OpenLv ->
            ExpDunDropRole = load_exp_drop_role(Player, Now),
            exp_dun_drop_mgr:role_login(ExpDunDropRole);
        true ->
            ok
    end.

load_exp_drop_role(Player, Now) ->
    #player_status{id = RoleId} = Player,
    case db:get_row(?SELECT_ROLE_DROP_INFO, [RoleId]) of
        [CalcTime, StartTime, ExtraDataBin|_] ->
            ok;
        _ ->
            CalcTime = Now, StartTime = Now, ExtraDataBin = util:term_to_bitstring([]),
            db:execute(?REPLACE_INTO_DROP_INFO, [RoleId, CalcTime, StartTime, ExtraDataBin])
    end,
    case db:get_all(?SELECT_ROLE_GOODS_DROP, [RoleId]) of
        Res1 = [_|_] ->
            Goods = lists:map(fun([Type, Id, Num]) -> {{Type, Id}, Num} end, Res1);
        _ ->
            Goods = []
    end,
    case db:get_all(?SELECT_ROLE_EXP_DUN_WAVE, [RoleId]) of
        Res2 = [_|_] ->
            WavePoints = lists:map(fun([X1, X2]) -> {X1, X2} end, Res2);
        _ ->
            InitWave = 0,
            db:execute(?REPLACE_INTOEXP_DUN_WAVE, [RoleId, InitWave, Now]),
            WavePoints = [{InitWave, Now}]
    end,
    case db:get_all(?SELECT_ROLE_OFF_POINT, [RoleId]) of
        Res3 = [_|_] ->
            %% 在不考虑剩余离线加成时长的情况下先把未结束的离线挂机时间设置为当前时间,
            %% 后续阶段计算时修正
            OffPoints = lists:map(fun([OffStart, OffEnd]) -> 
                if OffEnd == 0 -> 
                        db:execute(?REPLACE_INTO_EXP_DROP_OFF_POINT, [RoleId, OffStart, Now]),
                        {OffStart, Now};
                    true -> {OffStart, OffEnd}
                end
            end, Res3),
            OffPoints1 = lists:keysort(1, OffPoints);
        _ ->
            OffPoints1 = []
    end,
    ExtraData  = update_extra_data(Player, util:bitstring_to_term(ExtraDataBin)),
    GoodsLimit = calc_limit_goods_num(Goods),
    #drop_role{
        id = RoleId, wave_point = WavePoints, off_point = OffPoints1, calc_time = CalcTime, 
        start_time = StartTime, calc_goods = Goods, goods_limit = GoodsLimit, extra_data = ExtraData
    }.

update_extra_data(Player, ExtraData) ->
    #player_status{id = RoleId} = Player,
    Hours = lib_pet_api:pet_life_skill_about_exp_drop_sum_time(Player),
    % ?ERR("~p", [Hours]),
    case lists:keyfind(?MOD_PET, 1, ExtraData) of
        {_, OldVal} when Hours > OldVal ->
            NewExtraData = lists:keystore(?MOD_PET, 1, ExtraData, {?MOD_PET, Hours}),
            db:execute(?UPDATE_DROP_INFO_EXTRA_DATA, [util:term_to_bitstring(NewExtraData), RoleId]),
            NewExtraData;
        {_, _} ->
            ExtraData;
        _ ->
            NewExtraData = lists:keystore(?MOD_PET, 1, ExtraData, {?MOD_PET, Hours}),
            db:execute(?UPDATE_DROP_INFO_EXTRA_DATA, [util:term_to_bitstring(NewExtraData), RoleId]),
            NewExtraData
    end.

%% 上线统计珍稀道具产出数量
calc_limit_goods_num(Goods) ->
    lists:foldl(fun({{_GoodsType, GoodsId}, Num}, TmpLimitGoods) ->
        case data_equipment:get_equipment_info(GoodsId) of
            #base_equipment{color = Color} ->
                if Color >= ?DROP_BLUE_EQUIP andalso Color < ?DROP_PURPLE_EQUIP ->
                        case lists:keyfind(?DROP_BLUE_EQUIP, 1, TmpLimitGoods) of
                            {_, BlueEquipLimit} ->
                                lists:keystore(?DROP_BLUE_EQUIP, 1, TmpLimitGoods, {?DROP_BLUE_EQUIP, BlueEquipLimit+Num});
                            _ ->
                                lists:keystore(?DROP_BLUE_EQUIP, 1, TmpLimitGoods, {?DROP_BLUE_EQUIP, Num})
                        end;
                    Color >= ?DROP_PURPLE_EQUIP ->
                        case lists:keyfind(?DROP_PURPLE_EQUIP, 1, TmpLimitGoods) of
                            {_, PurpleEquipLimit} ->
                                lists:keystore(?DROP_PURPLE_EQUIP, 1, TmpLimitGoods, {?DROP_PURPLE_EQUIP, PurpleEquipLimit+Num});
                            _ ->
                                lists:keystore(?DROP_PURPLE_EQUIP, 1, TmpLimitGoods, {?DROP_PURPLE_EQUIP, Num})
                        end;
                    true ->
                        TmpLimitGoods
                end;
            _ ->
                TmpLimitGoods
        end
     end, [], Goods).


%% 个人放置产出结算
calc_drop_role(_Index, DropRole, Now) ->
    #drop_role{
        id = RoleId, wave_point = WavePoint, start_time = StartTime,
        calc_time = OldCalcTime, calc_goods = CalcGoods, goods_limit = GoodsLimit,
        extra_data = ExtraData, off_point = OffPoints
    } = DropRole,
    %% 获取开始->当前期间段所处的波数节点
    PassWavePoint   = calc_pass_wave_point(StartTime, Now, WavePoint),
    %% 获取最近的波数节点
    {MaxWave, Time} = calc_max_wave_pass(WavePoint),
    case PassWavePoint of
        [_|_] ->
            NewPassWavePoint = PassWavePoint;
        _     -> %% 本次结算期间无任何新增通关波数,以最近波数为准
            NewPassWavePoint = [{MaxWave, Time}]
    end,
    %% 获取可离线收益剩余的时间
    RestBuffTime  = get_rest_off_buff_time(ExtraData),
    %% 获取放置挂机上限时长
    ResourceLimit = get_exp_drop_time_limit(ExtraData),
    NewCalcTime   = get_new_calc_time(Now, StartTime, ResourceLimit),
    DefaultData   = get_default_calc_time(OldCalcTime, StartTime),
    TimeLast      = get_calc_time_last(ExtraData, DefaultData),
    % ?ERR("exp drop role ~p", [RoleId]),
    {ExpCalcGoods, ExpDbUpdateGoods, _FuturePoints, DelPoints, NewRestBuffTime, NewTimeLast} =
     calc_exp_drop(RoleId, OffPoints, NewPassWavePoint, OldCalcTime, NewCalcTime, RestBuffTime, TimeLast, ResourceLimit, CalcGoods),
    % ?ERR("ResourceLimit ~p old calc_time ~p new calc_time ~p exp_goods ~p FuturePoints ~p DelPoints ~p RestBuffTime ~p NewRestBuffTime ~p", 
    %     [ResourceLimit, OldCalcTime, NewCalcTime, ExpCalcGoods, FuturePoints, DelPoints, RestBuffTime, NewRestBuffTime]),
    Lv = lib_role:get_role_lv(RoleId),
    % Lv = exp_dun_drop_test:get_role_lv(RoleId),
    {NewGoodsLimit, DbUpdateGoods, NewCalcGoods}
     = calc_goods_drop(NewPassWavePoint, OldCalcTime, NewCalcTime, ExpCalcGoods, GoodsLimit, Lv),
    DbUpdateWaves = WavePoint -- [{MaxWave, Time}],
    UpdateExtraData = [
        {{?TYPE_GOODS, ?EXP_DROP_GOODS_SUB_TYPE}, replace, NewRestBuffTime}, 
        {{?MOD_DUNGEON, ?EXP_DROP_1}, replace, NewTimeLast}],
    % ?ERR("delete waves ~p new calc_time ~p", [DbUpdateWaves, NewCalcTime]),
    NewDropRole = do_update_role_drop([
        {update_goods_limit, NewGoodsLimit}, 
        {update_goods_all, NewCalcGoods}, 
        {calc_time, NewCalcTime},
        {update_wave_all, [{MaxWave, Time}]},
        {update_reward_sign, ?EXP_DROP_NOT_REWARD},
        {update_extra_data, UpdateExtraData},
        {del_off_points, DelPoints}
        % {off_points, FuturePoints}
    ], DropRole),
    DbList = [
        {calc_time, NewCalcTime}, 
        {drop_goods, ExpDbUpdateGoods ++ DbUpdateGoods}, 
        {delete_wave, DbUpdateWaves},
        {extra_data, NewDropRole#drop_role.extra_data},
        {del_off_points, DelPoints}
        % {off_points, FuturePoints}
    ],
    exp_dun_drop_logic:save_role_drop(DropRole#drop_role{is_rewarding = ?EXP_DROP_CALCING}),
    {NewDropRole, DbList}.

%% 计算本次结算时间节点的经验产出
calc_exp_drop(RoleId, OffPoints, PassWavePoint, CalcStartTime, CalcEndTime, RestBuffTime, TimeLast, ResourceLimit, CalcGoods) ->
    %% 计算能获得收益的离线时间段
    {OffPoints1, NewRestBuffTime, TimeLast1} = calc_off_time_point(OffPoints, CalcStartTime, CalcEndTime, {RestBuffTime, TimeLast, ResourceLimit*3600}),
    {OnlinePoints, TimeLast2} = calc_online_time_point(OffPoints, CalcStartTime, CalcEndTime, [], CalcStartTime, {TimeLast1, ResourceLimit*3600}),
    % ?ERR("~p ~p ~p ~p ~p", [OffPoints, OnlinePoints, CalcStartTime, CalcEndTime, TimeLast1]),
    SortOffPoints       = lists:keysort(1, OffPoints1),
    SortNormalPoints    = lists:keysort(1, OnlinePoints),
    OffPointExps        = calc_exp_drop_1(off, SortOffPoints, PassWavePoint),
    OnlinePointExps     = calc_exp_drop_1(normal, SortNormalPoints, PassWavePoint),
    RoleId == 8589935212 andalso ?ERR("raw offline ~p calc start ~p calc end ~p sortoffline ~p sortonline ~p", [OffPoints, CalcStartTime, CalcEndTime, SortOffPoints, SortNormalPoints]),
    {DbUpdateGoods, NewCalcGoods} = get_db_update_goods(OffPointExps ++ OnlinePointExps, CalcGoods),
    RoleId == 8589935212 andalso ?ERR("~p ~p ~p ~p", [OffPointExps, OnlinePointExps, RestBuffTime, NewRestBuffTime]),
    {DelPoints, NewOffPoints} = calc_new_off_point(OffPoints),
    % NewTimeLast = update_calc_time_last(SortOffPoints, SortNormalPoints, TimeLast),

    {NewCalcGoods, DbUpdateGoods, NewOffPoints, DelPoints, NewRestBuffTime, TimeLast2}.

update_calc_time_last(OffPoints, OnlinePoints, TimeLast) ->
    OffTimeLast = lists:sum([max(0, OffE - OffS) || {OffS, OffE} <- OffPoints]),
    OnTimeLast  = lists:sum([max(0, OnlineE -  OnlineS) || {OnlineS, OnlineE} <- OnlinePoints]),
    round(OffTimeLast + OnTimeLast + TimeLast).

%% 只保留最近的离线时间时间节点
calc_new_off_point(OffPoints) ->
    case lists:reverse(lists:keysort(1, OffPoints)) of
        [{LastOffS, LastOffE}|_] ->
            case lists:keytake(LastOffS, 1, OffPoints) of
                {value, _, DelPoints} ->
                    {DelPoints, [{LastOffS, LastOffE}]};
                _ ->
                    {[], []}
            end;
        _ ->
            {[], []}
    end.

%% 根据通关波数节点算出每一段正常/离线时间段的效率
%% TimePoints 正常/离线时间段
%% PassWavePoint 通关波数节点
calc_exp_drop_1(EfficiencyType, TimePoints, PassWavePoint) ->
    SortWavePoints = lists:keysort(1, PassWavePoint),
    lists:foldl(fun
        ({Start, End}, AccExps) ->
            do_calc_exp_drop_1(SortWavePoints, Start, End, EfficiencyType, AccExps);
        ({Start, End, _KeyInfos}, AccExps) ->
            do_calc_exp_drop_1(SortWavePoints, Start, End, EfficiencyType, AccExps)
    end, [], TimePoints).

do_calc_exp_drop_1(SortWavePoints, Start, End, EfficiencyType, AccExps) ->
    [{MinWave, MinTime}|Rest] = SortWavePoints,
    IndexWave = MinWave,
    MinIndex  = Start,
    if Start < MinTime -> MaxIndex = MinTime;
        true ->
            case Rest of
                [] -> MaxIndex = End;
                [{_SecondMinWave, SecondMinTime}|_] ->
                    MaxIndex = SecondMinTime
            end
    end,
    TimePointExps = calc_exp_drop_2(EfficiencyType, IndexWave, MinIndex, MaxIndex, End, SortWavePoints, []),
    lists:append(TimePointExps, AccExps).

calc_exp_drop_2(_EfficiencyType, _IndexWave, MinIndex, _MaxIndex, End, _SortWavePoints, TimePointExps) when MinIndex == End ->
    TimePointExps;

calc_exp_drop_2(EfficiencyType, IndexWave, MinIndex, MaxIndex, End, [], TimePointExps) ->
    ExpAdd = calc_interval_exp(MinIndex, MaxIndex, IndexWave, EfficiencyType),
    NewIndexWave = IndexWave,
    NewMinIndex = MaxIndex,
    NewMaxIndex = End,
    calc_exp_drop_2(EfficiencyType, NewIndexWave, NewMinIndex, NewMaxIndex, End, [], lists:append(ExpAdd, TimePointExps));

calc_exp_drop_2(EfficiencyType, IndexWave, MinIndex, MaxIndex, End, [{NextWave, _NextTime}|Rest], TimePointExps) ->
    ExpAdd = calc_interval_exp(MinIndex, MaxIndex, IndexWave, EfficiencyType),
    NewIndexWave = NextWave,
    NewMinIndex  = MaxIndex,
    case Rest of
        [{_SecNextWave, SecNextTime}|_] ->
            NewMaxIndex = SecNextTime;
        _ ->
            NewMaxIndex = End
    end,
    calc_exp_drop_2(EfficiencyType, NewIndexWave, NewMinIndex, NewMaxIndex, End, Rest, lists:append(ExpAdd, TimePointExps)).


%% @doc
%% 根据离线时间节点和结算开始/结束时间点计算正常结算的时间节点
calc_online_time_point(_OffPoints, CalcStartTime, CalcStartTime, OnlinePoints, _LastOffEnd, {TimeLast, _ResourceLimit}) ->
    {OnlinePoints, TimeLast};
calc_online_time_point(_, _CalcStartTime, _CalcEndTime, OnlinePoints, _LastOffEnd, {TimeLast, ResourceLimit}) when
    TimeLast >= ResourceLimit ->
        {OnlinePoints, TimeLast};
calc_online_time_point(OffPoints, CalcStartTime, CalcEndTime, OnlinePoints, LastOffEnd, {TimeLast, ResourceLimit}) ->
    if OffPoints == [] ->
            LastTime = CalcEndTime - LastOffEnd,
            TempTimeLast = TimeLast + LastTime,
            if TempTimeLast >= ResourceLimit ->
                    PointTimesCalc = ResourceLimit - TimeLast,
                    RealPointEnd = LastOffEnd + PointTimesCalc,
                    TimeLast1 = TimeLast + PointTimesCalc;
                true ->
                    PointTimesCalc = LastTime,
                    RealPointEnd = LastOffEnd + PointTimesCalc,
                    TimeLast1 = TempTimeLast
            end,
            {[{LastOffEnd, RealPointEnd}|OnlinePoints], TimeLast1};
        true ->
            [{OffStart, OffEnd}|Rest] = OffPoints,
            if  OffEnd == 0 ->
                    if OffStart =< CalcStartTime -> 
                            {OnlinePoints, TimeLast};
                        true -> %% OffStart > CalcStartTime
                            LastTime = OffStart - LastOffEnd,
                            TempTimeLast = TimeLast + LastTime,
                            if TempTimeLast >= ResourceLimit ->
                                    PointTimesCalc = ResourceLimit - TimeLast,
                                    RealPointEnd = LastOffEnd + PointTimesCalc,
                                    TimeLast1 = TimeLast + PointTimesCalc;
                                true ->
                                    PointTimesCalc = LastTime,
                                    RealPointEnd = LastOffEnd + PointTimesCalc,
                                    TimeLast1 = TempTimeLast
                            end,
                            {[{LastOffEnd, RealPointEnd}|OnlinePoints], TimeLast1}
                    end;
                OffEnd =< CalcStartTime ->
                    calc_online_time_point(Rest, CalcStartTime, CalcEndTime, OnlinePoints, LastOffEnd, {TimeLast, ResourceLimit});
                OffStart > CalcStartTime andalso OffEnd > CalcStartTime  ->
                    LastTime = OffStart - LastOffEnd,
                    TempTimeLast = TimeLast + LastTime,
                    if TempTimeLast >= ResourceLimit ->
                            PointTimesCalc = ResourceLimit - TimeLast,
                            RealPointEnd = LastOffEnd + PointTimesCalc,
                            TimeLast1 = TimeLast + PointTimesCalc;
                        true ->
                            PointTimesCalc = LastTime,
                            RealPointEnd = LastOffEnd + PointTimesCalc,
                            TimeLast1 = TempTimeLast
                    end,
                    calc_online_time_point(Rest, CalcStartTime, CalcEndTime, [{LastOffEnd, RealPointEnd}|OnlinePoints], OffEnd, {TimeLast1, ResourceLimit});
                true -> %% OffStart =< CalcStartTime andalso OffEnd > CalcStartTime
                    calc_online_time_point(Rest, CalcStartTime, CalcEndTime, OnlinePoints, OffEnd, {TimeLast, ResourceLimit})
            end
    end.

calc_off_time_point(OffPoints, CalcStartTime, CalcEndTime, {RestBuffTime, TimeLast, ResourceLimit}) ->
    calc_off_time_point(OffPoints, CalcStartTime, CalcEndTime, {RestBuffTime, TimeLast, ResourceLimit}, {[]}).

calc_off_time_point([], _, _, {RestBuffTime, TimeLast, _ResourceLimit}, {CalcPoints}) ->
    {CalcPoints, RestBuffTime, TimeLast};
calc_off_time_point(_, _, _, {0, TimeLast, _}, {CalcPoints}) ->
    {CalcPoints, 0, TimeLast};
calc_off_time_point(_, _, _, {RestBuffTime, TimeLast, ResourceLimit}, {CalcPoints}) when TimeLast >= ResourceLimit ->
    {CalcPoints, RestBuffTime, TimeLast};
calc_off_time_point([{OffStart, OffEnd}|Rest], CalcStartTime, CalcEndTime, {RestBuffTime, TimeLast, ResourceLimit}, {CalcPoints}) ->
    if
        OffEnd =/= 0 andalso OffEnd =< CalcStartTime -> %% 是过去的时间节点
            calc_off_time_point(Rest, CalcStartTime, CalcEndTime, {RestBuffTime, TimeLast, ResourceLimit}, {CalcPoints});
        OffStart >= CalcEndTime -> %% 是未来的时间
            calc_off_time_point(Rest, CalcStartTime, CalcEndTime, {RestBuffTime, TimeLast, ResourceLimit}, {CalcPoints});
        OffEnd == 0 ->
            PointEndTime    = CalcEndTime,
            PointStartTime  = max(CalcStartTime, OffStart),
            LastTime        = PointEndTime - PointStartTime,
            if LastTime > RestBuffTime -> %% 此段结算时间已经超过剩余可离线收益时间了
                    TempTimeLast = TimeLast + RestBuffTime,
                    if TempTimeLast >= ResourceLimit -> %%
                            PointTimesCalc = ResourceLimit - TimeLast,
                            RealPointEnd = PointStartTime + PointTimesCalc,
                            RestBuffTime1 = RestBuffTime - PointTimesCalc,
                            TimeLast1 = TimeLast + PointTimesCalc;
                        true ->
                            RealPointEnd = PointStartTime + RestBuffTime,
                            RestBuffTime1 = 0,
                            TimeLast1 = TempTimeLast
                    end,
                    CalcPoints1 = [{PointStartTime, RealPointEnd}|CalcPoints];
                true ->
                    TempTimeLast = TimeLast + LastTime,
                    if TempTimeLast >= ResourceLimit ->
                            PointTimesCalc = ResourceLimit - TimeLast,
                            RealPointEnd = PointStartTime + PointTimesCalc,
                            RestBuffTime1 = RestBuffTime - PointTimesCalc,
                            TimeLast1 = TimeLast + PointTimesCalc;
                        true ->
                            RealPointEnd = PointStartTime + LastTime,
                            RestBuffTime1 = RestBuffTime - LastTime,
                            TimeLast1 = TempTimeLast
                    end,
                    % NewTimeLast = TimeLast + LastTime
                    CalcPoints1 = [{PointStartTime, RealPointEnd}|CalcPoints]
            end,
            % CalcPoints2 = 
            calc_off_time_point(Rest, CalcStartTime, CalcEndTime, {RestBuffTime1, TimeLast1, ResourceLimit}, {CalcPoints1});
        OffEnd =/= 0 ->
            if CalcStartTime > OffStart ->
                    PointStartTime = CalcStartTime,
                    if 
                        CalcEndTime > OffEnd ->
                            PointEndTime = OffEnd;
                        true -> %% CalcEndTime =< OffEnd
                            PointEndTime = CalcEndTime
                    end,
                    LastTime = PointEndTime - PointStartTime,
                    if LastTime > RestBuffTime -> %% 此段结算时间已经超过剩余可离线收益时间了
                            TempTimeLast = TimeLast + RestBuffTime,
                            if TempTimeLast >= ResourceLimit ->
                                    PointTimesCalc = ResourceLimit - TimeLast,
                                    RealPointEnd = PointStartTime + PointTimesCalc,
                                    RestBuffTime1 = RestBuffTime - PointTimesCalc,
                                    TimeLast1 = TimeLast + PointTimesCalc;
                                true ->
                                    RealPointEnd = PointStartTime + RestBuffTime,
                                    RestBuffTime1 = 0,
                                    TimeLast1 = TempTimeLast
                            end,
                            CalcPoints1 = [{PointStartTime, RealPointEnd}|CalcPoints];
                        true ->
                            TempTimeLast = TimeLast + LastTime,
                            if TempTimeLast >= ResourceLimit ->
                                    PointTimesCalc = ResourceLimit - TimeLast,
                                    RealPointEnd = PointStartTime + PointTimesCalc,
                                    RestBuffTime1 = RestBuffTime - PointTimesCalc,
                                    TimeLast1 = TimeLast + PointTimesCalc;
                                true ->
                                    RealPointEnd = PointStartTime + LastTime,
                                    RestBuffTime1 = RestBuffTime - LastTime,
                                    TimeLast1 = TempTimeLast
                            end,
                            CalcPoints1 = [{PointStartTime, RealPointEnd}|CalcPoints]
                    end,
                    calc_off_time_point(Rest, CalcStartTime, CalcEndTime, {RestBuffTime1, TimeLast1, ResourceLimit}, {CalcPoints1});
                true -> %% CalcStartTime =< OffStart
                    PointStartTime = OffStart,
                    if  CalcEndTime > OffEnd ->
                            PointEndTime = OffEnd;
                        true -> %% CalcEndTime =< OffEnd
                            PointEndTime = CalcEndTime
                    end,
                    LastTime = PointEndTime - PointStartTime,
                    if LastTime > RestBuffTime ->
                            TempTimeLast = TimeLast + RestBuffTime,
                            if TempTimeLast >= ResourceLimit ->
                                    PointTimesCalc = ResourceLimit - TimeLast,
                                    RealPointEnd = PointStartTime + PointTimesCalc,
                                    RestBuffTime1 = RestBuffTime - PointTimesCalc,
                                    TimeLast1 = TimeLast + PointTimesCalc;
                                true ->
                                    RealPointEnd = PointStartTime + RestBuffTime,
                                    RestBuffTime1 = 0,
                                    TimeLast1 = TempTimeLast
                            end,
                            CalcPoints1 = [{PointStartTime, RealPointEnd}|CalcPoints];
                        true ->
                            TempTimeLast = TimeLast + LastTime,
                            if TempTimeLast >= ResourceLimit ->
                                    PointTimesCalc = ResourceLimit - TimeLast,
                                    RealPointEnd = PointStartTime + PointTimesCalc,
                                    RestBuffTime1 = RestBuffTime - PointTimesCalc,
                                    TimeLast1 = TimeLast + PointTimesCalc;
                                true ->
                                    RealPointEnd = PointStartTime + LastTime,
                                    RestBuffTime1 = RestBuffTime - LastTime,
                                    TimeLast1 = TempTimeLast
                            end,
                            CalcPoints1 = [{PointStartTime, RealPointEnd}|CalcPoints]
                    end,
                    calc_off_time_point(Rest, CalcStartTime, CalcEndTime, {RestBuffTime1, TimeLast1, ResourceLimit}, {CalcPoints1})
            end;
        true -> %% Error
            ?ERR("calc time point error happen ~p ~p", [{OffStart, OffEnd}, {CalcStartTime, CalcEndTime}]),
            calc_off_time_point(Rest, CalcStartTime, CalcEndTime, {RestBuffTime, TimeLast, ResourceLimit}, {CalcPoints})
    end.

%% 临时批处理进程调用
db_update_drop_role_do({Index, DropRole = #drop_role{id = RoleId}, DbList}) ->
    if Index rem 10 == 0 -> 
            timer:sleep(150);
        true -> ok
    end,
    F = fun() ->
        lib_exp_dun_drop:db_update_drop_role(RoleId, DbList),
        ok
    end,
    case catch db:transaction(F) of
        ok    -> exp_dun_drop_mgr:update_role_dict_after_db(DropRole);
        Error ->
            exp_dun_drop_mgr:update_drop_role_info(RoleId, [{update_reward_sign, ?EXP_DROP_NOT_REWARD}]),
            Now = utime:unixtime(),
            case erlang:get(?EXP_DROP_ERROR_LOG_TIME) of
                undefined ->
                    ?ERR("batch update drop role error ~p", [Error]),
                    erlang:put(?EXP_DROP_ERROR_LOG_TIME, Now);
                LastTime ->
                    TimePass = Now - LastTime,
                    if TimePass >= 5 -> %% 防止短时间大批量日志写入
                            ?ERR("batch update drop role error ~p", [Error]),
                            erlang:put(?EXP_DROP_ERROR_LOG_TIME, Now);
                        true ->
                            skip
                    end
            end
    end;
db_update_drop_role_do(_Err) ->
    ?ERR("~p", [_Err]),
    ok.
            
%% 本次结算间隔经历的通波节点
calc_pass_wave_point(StartTime, Now, WavePoint) ->
    lists:filter(fun({_Wave, Time}) ->
        Time >= StartTime andalso Time =< Now 
     end, WavePoint).

%% 获取最近通波节点
calc_max_wave_pass(WavePoint) ->
    [{MaxWave, Time}|_] = lists:sort(fun({_W1, T1}, {_W2, T2}) ->
        T1 >= T2
     end, WavePoint),
    {MaxWave, Time}.


%% 根据波数信息计算本次放置道具掉落产出
%% WavePoint 波数通关节点
%% OldCalcTime 本次结算开始时间
%% NewCalcTime 本次结算结束时间
%% CalcGoods 目前已累计结算物品
%% GoodsLimit 物品上限
calc_goods_drop(WavePoint, OldCalcTime, NewCalcTime, CalcGoods, GoodsLimit, Lv) ->
    [{MinWave, MinTime}|Rest] = lists:keysort(2, WavePoint),
    % NewCalcTime = get_new_calc_time(Now, StartTime, ResourceLimit),
    % ?ERR("new calc time ~p", [NewCalcTime]),
    MinIndex = OldCalcTime,
    OpenDay = util:get_open_day(),
    if OldCalcTime < MinTime -> %% 手动领取后更新了结算&开始时间,又新通波数,会走这里
            IndexWave = MinWave,
            MaxIndex = MinTime,
            {NewGoodsLimit, NewGoods} = do_calc_goods_drop(IndexWave, MinIndex, MaxIndex, NewCalcTime, [{MinWave, MinTime}|Rest], [], GoodsLimit, Lv, OpenDay),
            {DbUpdateGoods, NewCalcGoods} = get_db_update_goods(NewGoods, CalcGoods),
            {NewGoodsLimit, DbUpdateGoods, NewCalcGoods};
        true -> %% OldCalcTime >= MinTime
            IndexWave = MinWave,
            case Rest of
                [] -> 
                    MaxIndex = NewCalcTime;
                [{_SecondMinWave, SecondMinTime}|_] -> 
                    MaxIndex = SecondMinTime
            end,
            {NewGoodsLimit, NewGoods} = do_calc_goods_drop(IndexWave, MinIndex, MaxIndex, NewCalcTime, Rest, [], GoodsLimit, Lv, OpenDay),
            % ?ERR("~p", [NewGoods]),
            %% 获取需要更新数据库的物品列表
            {DbUpdateGoods, NewCalcGoods} = get_db_update_goods(NewGoods, CalcGoods),
            {NewGoodsLimit, DbUpdateGoods, NewCalcGoods}
    end.


%% 获取本次结算时间
get_new_calc_time(Now, _StartTime, _ResourceLimit) ->
    Now.
    % LastTime = round(Now - StartTime),
    % TimeLimit = round(ResourceLimit*3600),
    % if LastTime > TimeLimit -> %% 超过资源掉落小时限制
    %         %% 回溯(LastTime - TimeLimit)秒作为最新结算时间
    %         Now - (LastTime - TimeLimit);
    %     true ->
    %         Now
    % end.

get_db_update_goods(NewGoods, CalcGoods) ->
    % ?ERR("~p", [NewGoods]),
    %% 去重合并
    NewGoods1 = lists:foldl(fun({{GoodsType, GoodsId}, Num}, Acc) ->
        case lists:keyfind({GoodsType, GoodsId}, 1, Acc) of
            {_, OldNum} ->
                lists:keystore({GoodsType, GoodsId}, 1, Acc, {{GoodsType, GoodsId}, OldNum+Num});
            _ ->
                lists:keystore({GoodsType, GoodsId}, 1, Acc, {{GoodsType, GoodsId}, Num})
        end
     end, [], NewGoods),
    get_db_update_goods_do(NewGoods1, CalcGoods, []).

get_db_update_goods_do([], CalcGoods, DbUpdateGoods) -> {DbUpdateGoods, CalcGoods};
get_db_update_goods_do([{{GoodsType, GoodsId}, Num}|Rest], CalcGoods, DbUpdateGoods) ->
    case lists:keyfind({GoodsType, GoodsId}, 1, CalcGoods) of
        {_, OldNum} ->
            NewCalcGoods = lists:keystore({GoodsType, GoodsId}, 1, CalcGoods, {{GoodsType, GoodsId}, round(OldNum+Num)}),
            NewDbUpdateGoods = lists:keystore({GoodsType, GoodsId}, 1, DbUpdateGoods, {{GoodsType, GoodsId}, round(OldNum+Num)});
        _ ->
            NewCalcGoods = lists:keystore({GoodsType, GoodsId}, 1, CalcGoods, {{GoodsType, GoodsId}, Num}),
            NewDbUpdateGoods = lists:keystore({GoodsType, GoodsId}, 1, DbUpdateGoods, {{GoodsType, GoodsId}, Num})
    end,
    get_db_update_goods_do(Rest, NewCalcGoods, NewDbUpdateGoods).


%% 计算放置物品产出具体逻辑
do_calc_goods_drop(_IndexWave, MinIndex, _MaxIndex, NewCalcTime, _Rest, NewGoods, GoodsLimit, _Lv, _OpenDay)
     when MinIndex == NewCalcTime ->
    % ?ERR("当前波数 ~p 起点 ~p 终点 ~p ~p", [_IndexWave, MinIndex, _MaxIndex, NewCalcTime]),
    {GoodsLimit, NewGoods};

do_calc_goods_drop(IndexWave, MinIndex, MaxIndex, NewCalcTime, [], Acc, GoodsLimit, Lv, OpenDay) ->
    % NewGoods1 = calc_interval_exp(MinIndex, MaxIndex, IndexWave),
    {NewGoods2, NewGoodsLimit} = calc_interval_goods(MinIndex, MaxIndex, IndexWave, GoodsLimit, Lv, OpenDay),
    % ?ERR("当前波数 ~p 起点 ~p 终点 ~p", [IndexWave, MinIndex, MaxIndex]),
    NewAcc = lists:append([NewGoods2, Acc]),
    NewIndexWave = IndexWave,
    NewMinIndex  = MaxIndex,
    NewMaxIndex  = NewCalcTime,
    do_calc_goods_drop(NewIndexWave, NewMinIndex, NewMaxIndex, NewCalcTime, [], NewAcc, NewGoodsLimit, Lv, OpenDay);

do_calc_goods_drop(IndexWave, MinIndex, MaxIndex, NewCalcTime, [{NextWave, _NextTime}|Rest], Acc, GoodsLimit, Lv, OpenDay) ->
    % NewGoods1 = calc_interval_exp(MinIndex, MaxIndex, IndexWave),
    {NewGoods2, NewGoodsLimit} = calc_interval_goods(MinIndex, MaxIndex, IndexWave, GoodsLimit, Lv, OpenDay),
    % ?ERR("当前波数 ~p 起点 ~p 终点 ~p", [IndexWave, MinIndex, MaxIndex]),
    NewAcc = lists:append([NewGoods2, Acc]),
    NewIndexWave = NextWave,
    NewMinIndex  = MaxIndex,
    case Rest of
        [{_SecNextWave, SecNextTime}|_] ->
            NewMaxIndex = SecNextTime;
        _ ->
            NewMaxIndex = NewCalcTime
    end,
    do_calc_goods_drop(NewIndexWave, NewMinIndex, NewMaxIndex, NewCalcTime, Rest, NewAcc, NewGoodsLimit, Lv, OpenDay).

%% 结算某段周期内经验掉落
calc_interval_exp(MinIndexTime, MaxIndexTime, IndexWave, EfficiencyType) ->
    %% 波数每秒对应的基础效率
    IntervalExp     = data_exp_dun_drop:get_exp_effiency(IndexWave),
    %% 每X秒加一次经验
    Interval        = data_exp_dun_drop:get_kv(3),
    %% 离线加成效率
    % OffBuffRatio    = data_exp_dun_drop:get_kv(12),
    ExpDropCounts   = umath:floor((MaxIndexTime - MinIndexTime)/Interval),
    TotalExp        = umath:floor(ExpDropCounts*umath:floor(IntervalExp/12)),
    if TotalExp =/= 0 ->
            %% 离线加成收益和在线加成收益不区分了
            if EfficiencyType == off ->
                    [{{?TYPE_EXP, 1}, TotalExp}];
                    % [{{?TYPE_EXP, 1}, round(TotalExp*(1+OffBuffRatio))}];
                true ->
                    [{{?TYPE_EXP, 0}, TotalExp}]
            end;
        true ->
            []
    end.

%% 结算某段周期内物品掉落
calc_interval_goods(MinIndexTime, MaxIndexTime, IndexWave, GoodsLimit, Lv, OpenDay) ->
    GoodsInterval = data_exp_dun_drop:get_kv(4),
    IndexLast     = max(0, round(MaxIndexTime - MinIndexTime)),
    if IndexLast < GoodsInterval -> %% 轮空
            % ?ERR("时间间隔 ~p ~p 轮空波数 ~p 累积时间 ~p", [MinIndexTime, MaxIndexTime, IndexWave, NewInterValLast]),
            {[], GoodsLimit};
        true ->
            DropCount = IndexLast div GoodsInterval,
            % ?ERR("波数 ~p 掉落次数 ~p", [IndexWave, DropCount]),
            {NewGoodsLimit, NewGoods} = do_calc_interval_goods(DropCount, IndexWave, GoodsLimit, Lv, OpenDay, []),
            {NewGoods, NewGoodsLimit}
    end.

do_calc_interval_goods(DropCount, _IndexWave, GoodsLimit, _Lv, _OpenDay, Acc) when DropCount =< 0 -> 
    {GoodsLimit, Acc};
do_calc_interval_goods(DropCount, IndexWave, GoodsLimit, Lv, OpenDay, Acc) ->
    case data_exp_dun_drop:get_rule(Lv, IndexWave) of
        #base_exp_dun_drop_rule{rule = Rule = [_|_]} ->
            DropNumList = get_drop_num_list(Rule, []),
            % ?ERR("波数 ~p 掉落次数列表 ~p", [IndexWave, DropNumList]),
            {NewGoodsLimit, NewGoods} = drop_goods_list(DropNumList, GoodsLimit, OpenDay);
        _ ->
            NewGoodsLimit = GoodsLimit,
            NewGoods = []
    end,
    do_calc_interval_goods(DropCount-1, IndexWave, NewGoodsLimit, Lv, OpenDay, lists:append(NewGoods, Acc)).

drop_goods_list(DropNumList, GoodsLimit, OpenDay) ->
    case DropNumList of
        [] -> {GoodsLimit, []};
        _ ->
            F = fun({DropId, DropNum}, {TmpGoodsLimit, AccRandDropList}) ->
                {NewTmpGoodsLimit, RandDropList} = drop_goods(DropId, DropNum, TmpGoodsLimit, OpenDay),
                % ?ERR("~p", [RandDropList]),
                {NewTmpGoodsLimit, RandDropList ++ AccRandDropList}
            end,
            lists:foldl(F, {GoodsLimit, []}, DropNumList)
    end.

%% 根据单个掉落id:N,获取相同掉落id:N的掉落物品
drop_goods(DropId, DropNum, GoodsLimit, OpenDay) ->
    if
        DropNum =< 0 -> [];
        true ->
            DropList = data_exp_dun_drop:get_goods_ids_by_list_id(DropId),
            RandGoods = get_drop_goods_list(DropList, OpenDay, []),
            %% 随机掉落
            case length(RandGoods) of
                0 -> 
                    {GoodsLimit, []};
                _ -> %% 获取所有相同掉落id的总的概率
                    TotalRatio = get_goods_total_ratio(RandGoods, 0),
                    % ?ERR("本次物品掉落总概率 ~p", [TotalRatio]),
                    {NewGoodsLimit, RandDropList} = find_drop_goods(RandGoods, DropNum, TotalRatio, GoodsLimit, []),
                    {NewGoodsLimit, RandDropList}
            end
    end.

%% 同一个掉落id,随机DropNum次掉落
find_drop_goods(DropList, DropNum, TotalRatio, GoodsLimit, Result) ->
    Rand = urand:rand(1, TotalRatio),
    % ?ERR("~p ~p", [Rand, DropList]),
    %% 随机掉落id里面的物品,找到一个掉落
    case find_goods(DropList, 0, Rand) of
        #base_exp_dun_drop_goods{goods_type = GoodsType, goods_id = GoodsId, min = Min, max = Max} ->
            % ?ERR("~p ~p", [GoodsType, GoodsId]),
            {NewGoodsLimit1, NewResult} = 
            if GoodsType == 0 andalso GoodsId == 0 ->
                    {GoodsLimit, Result};
                true ->
                    DropGoodNum = 
                    if Min >= 1 andalso Max >= 1 -> urand:rand(Min, Max);
                        true -> 1
                    end,
                    {NewGood, NewGoodsLimit} = calc_drop_goods_limit(GoodsType, GoodsId, DropGoodNum, GoodsLimit),
                    {NewGoodsLimit, lists:append([NewGood, Result])}
            end,
            if
                DropNum > 1 ->
                    find_drop_goods(DropList, (DropNum - 1), TotalRatio, NewGoodsLimit1, NewResult);
                true ->
                    {NewGoodsLimit1, NewResult}
            end;
        _ ->
            {GoodsLimit, Result}
    end.

calc_drop_goods_limit(GoodsType, GoodsId, DropGoodNum, GoodsLimit) ->
    [ConfBlueLimit, ConfPurpleLimit|_] = get_limit_good(),
    case data_equipment:get_equipment_info(GoodsId) of
        #base_equipment{color = Color} ->
            if Color >= ?DROP_BLUE_EQUIP andalso Color < ?DROP_PURPLE_EQUIP ->
                    do_calc_drop_goods_limit(?DROP_BLUE_EQUIP, GoodsType, GoodsId, DropGoodNum, GoodsLimit, ConfBlueLimit);
                Color >= ?DROP_PURPLE_EQUIP ->
                    do_calc_drop_goods_limit(?DROP_PURPLE_EQUIP, GoodsType, GoodsId, DropGoodNum, GoodsLimit, ConfPurpleLimit);
                true ->
                    {[{{GoodsType, GoodsId}, DropGoodNum}], GoodsLimit}
            end;
        _ ->
            {[{{GoodsType, GoodsId}, DropGoodNum}], GoodsLimit}
    end.

do_calc_drop_goods_limit(LimitType, GoodsType, GoodsId, DropGoodNum, GoodsLimit, ConfLimit) ->
    case lists:keyfind(LimitType, 1, GoodsLimit) of
        {_, OldNum} ->
            if OldNum >= ConfLimit -> %% 掉落已达限制
                    {[], GoodsLimit};
                true ->
                    TotalNum       = OldNum + DropGoodNum,
                    NewTotalNum    = min(TotalNum, ConfLimit),
                    NewDropGoodNum = max(0, NewTotalNum - OldNum),
                    NewGoodsLimit  = lists:keystore(LimitType, 1, GoodsLimit, {LimitType, NewTotalNum}),
                    if NewDropGoodNum == 0 ->
                            {[], NewGoodsLimit};
                        true ->
                            {[{{GoodsType, GoodsId}, NewDropGoodNum}], NewGoodsLimit}
                    end
            end;
        _ ->
            TotalNum       = DropGoodNum,
            NewTotalNum    = min(TotalNum, ConfLimit),
            NewDropGoodNum = max(0, NewTotalNum),
            NewGoodsLimit  = lists:keystore(LimitType, 1, GoodsLimit, {LimitType, NewTotalNum}),
            if NewDropGoodNum == 0 ->
                    {[], NewGoodsLimit};
                true ->
                    {[{{GoodsType, GoodsId}, NewDropGoodNum}], NewGoodsLimit}
            end
    end.

get_limit_good() ->
    [data_exp_dun_drop:get_kv(7), data_exp_dun_drop:get_kv(8)].

%% 查找匹配机率的值
find_goods([], _, _) -> [];
find_goods([G = #base_exp_dun_drop_goods{weight = Val}|_], S, Ra) when Ra > S andalso Ra =< (S + Val) -> G;
find_goods([#base_exp_dun_drop_goods{weight = Val}|T], S, Ra) -> find_goods(T, (S + Val), Ra);
find_goods([_|T], S, Ra) ->
    find_goods(T, S, Ra).


get_goods_total_ratio([], R) -> R;
get_goods_total_ratio([#base_exp_dun_drop_goods{weight = Val}|T], R) ->
    get_goods_total_ratio(T, Val + R);
get_goods_total_ratio([_R|T], R) ->
    get_goods_total_ratio(T, R).

%% 取掉落物品列表
get_drop_goods_list([], _OpenDay, RandGoods) ->
    RandGoods;
get_drop_goods_list([Id|DropList], OpenDay, RandGoods) ->
    NRandGoods =
        case data_exp_dun_drop:get_goods(Id) of
            Drop = #base_exp_dun_drop_goods{open_day = [MinOpen, MaxOpen|_]} when OpenDay >= MinOpen andalso
            OpenDay =< MaxOpen ->
                [Drop|RandGoods];
            Drop = #base_exp_dun_drop_goods{open_day = []} ->
                [Drop|RandGoods];
            _ ->
                RandGoods
        end,
    get_drop_goods_list(DropList, OpenDay, NRandGoods).


%% 取随机物品掉落数列表
get_drop_num_list([], ResList) -> ResList;
get_drop_num_list([{Round, WeightList}|T], ResList) ->
    F = fun(_I, AccList) ->
        RandVal = urand:rand(1, ?EXP_DUN_RATIO_MAX),
        DropList = find_ratio(WeightList, 0, RandVal),
        DropList ++ AccList
    end,
    DropList1 = lists:foldl(F, [], lists:seq(1, Round)),
    % ?ERR("~p", [DropList1]),
    get_drop_num_list(T, DropList1 ++ ResList).

%% 查找匹配机率的值
find_ratio([], _, _) -> [];
find_ratio([{R, L}|_], S, Ra) when S < Ra andalso Ra =< (S + R) -> L;
find_ratio([{R, _}|T], S, Ra) -> find_ratio(T, (S + R), Ra).

% merge_unique_goods([], Goods) -> Goods;
% merge_unique_goods([{{GoodsType, GoodsId}, Num}|Rest], Goods) ->
%     NewGoods = case lists:keyfind({GoodsType, GoodsId}, 1, Goods) of
%         {_, OldNum} ->
%             lists:keystore({GoodsType, GoodsId}, 1, Goods, {{GoodsType, GoodsId}, round(OldNum+Num)});
%         _ ->
%             lists:keystore({GoodsType, GoodsId}, 1, Goods, {{GoodsType, GoodsId}, Num})
%     end,
%     merge_unique_goods(Rest, NewGoods).


%% 更新玩家信息
do_update_role_drop([], DropRole) -> DropRole;

do_update_role_drop([{add_wave, WaveInfoTuple = {Wave, _Time}}|Rest], DropRole = #drop_role{wave_point = WavePoint}) ->
    NewWavePoint = lists:keystore(Wave, 1, WavePoint, WaveInfoTuple),
    do_update_role_drop(Rest, DropRole#drop_role{wave_point = NewWavePoint});

do_update_role_drop([{update_wave_all, WaveList = [_|_]}|Rest], DropRole = #drop_role{}) ->
    do_update_role_drop(Rest, DropRole#drop_role{wave_point = WaveList});

do_update_role_drop([{start_time, Time}|Rest], DropRole = #drop_role{}) ->
    do_update_role_drop(Rest, DropRole#drop_role{start_time = Time});

do_update_role_drop([{calc_time, Time}|Rest], DropRole = #drop_role{}) ->
    do_update_role_drop(Rest, DropRole#drop_role{calc_time = Time});

do_update_role_drop([{add_goods, AddGoodsL = [_|_]}|Rest], DropRole = #drop_role{calc_goods = Goods}) ->
    do_update_role_drop(Rest, DropRole#drop_role{calc_goods = lists:append(AddGoodsL, Goods)});

do_update_role_drop([{update_goods_all, GoodsL = [_|_]}|Rest], DropRole) ->
    do_update_role_drop(Rest, DropRole#drop_role{calc_goods = GoodsL});

do_update_role_drop([{delete_all_goods, _}|Rest], DropRole) ->
    do_update_role_drop(Rest, DropRole#drop_role{calc_goods = []});

do_update_role_drop([{update_goods_limit, GoodsLimit}|Rest], DropRole) ->
    do_update_role_drop(Rest, DropRole#drop_role{goods_limit = GoodsLimit});

do_update_role_drop([{update_reward_sign, RewardSign}|Rest], DropRole) ->
    do_update_role_drop(Rest, DropRole#drop_role{is_rewarding = RewardSign});

do_update_role_drop([{remain, Remain}|Rest], DropRole) ->
    do_update_role_drop(Rest, DropRole#drop_role{remain = Remain});

do_update_role_drop([{update_extra_data, UpdateArgs}|Rest], DropRole) ->
    #drop_role{extra_data = ExtraData} = DropRole,
    {_, NewExtraData} = update_drop_role_extra_data(ExtraData, UpdateArgs),
    do_update_role_drop(Rest, DropRole#drop_role{extra_data = NewExtraData});

do_update_role_drop([{off_points, NewOffPoints}|Rest], DropRole) ->
    #drop_role{off_point = OffPoints} = DropRole,
    NewOffPoints1 = lists:foldl(fun({OffStart, OffEnd}, AccOffPoints) -> 
        lists:keystore(OffStart, 1, AccOffPoints, {OffStart, OffEnd})
    end, OffPoints, NewOffPoints),
    do_update_role_drop(Rest, DropRole#drop_role{off_point = lists:keysort(1, NewOffPoints1)});

do_update_role_drop([{del_off_points, DelPoints}|Rest], DropRole) ->
    #drop_role{off_point = OffPoints} = DropRole,
    NewOffPoints = lists:foldl(
     fun
        ({OffStart, _OffEnd}, AccOffPoints) -> 
            lists:keydelete(OffStart, 1, AccOffPoints);
        ({_OffStart, _OffEnd, [StartKey]}, AccOffPoints) ->
            lists:keydelete(StartKey, 1, AccOffPoints);
        ({OffStart, _OffEnd, []}, AccOffPoints) ->
            lists:keydelete(OffStart, 1, AccOffPoints)
    end, OffPoints, DelPoints),
    do_update_role_drop(Rest, DropRole#drop_role{off_point = lists:keysort(1, NewOffPoints)});

do_update_role_drop([_|Rest], DropRole) ->
    do_update_role_drop(Rest, DropRole).


%% 更新玩家首通波数时间|结算开启时间
db_update_role_wave(RoleId, Wave, Time, StartTime) ->
    db_update_role_wave(RoleId, Wave, Time, StartTime, []).

db_update_role_wave(RoleId, Wave, Time, StartTime, ExtraData) ->
    Wave == 0 andalso db:execute(?REPLACE_INTO_DROP_INFO, [RoleId, Time, StartTime, util:term_to_bitstring(ExtraData)]),
    db:execute(?REPLACE_INTOEXP_DUN_WAVE, [RoleId, Wave, Time]),
    ok.

db_update_drop_role(_RoleId, []) -> skip;

db_update_drop_role(RoleId, [{drop_goods, DbUpdateGoods = [_|_]}|Rest]) ->
    Len = length(DbUpdateGoods),
    if Len > ?DB_MAX_LEN ->
            DbUpdateGoods1 = lists:sublist(DbUpdateGoods, ?DB_MAX_LEN),
            DbUpdateGoods2 = [[RoleId, GoodsType, GoodsId, Num] || {{GoodsType, GoodsId}, Num} <- DbUpdateGoods1],
            db:insert_batch(?BATCH_UPDATE_ROLE_GOODS, DbUpdateGoods2),
            timer:sleep(150),
            db_update_drop_role(RoleId, [{drop_goods, DbUpdateGoods -- DbUpdateGoods1}|Rest]);
        true ->
            DbUpdateGoods1 = [[RoleId, GoodsType, GoodsId, Num] || {{GoodsType, GoodsId}, Num} <- DbUpdateGoods],
            db:insert_batch(?BATCH_UPDATE_ROLE_GOODS, DbUpdateGoods1),
            db_update_drop_role(RoleId, Rest)
    end;

db_update_drop_role(RoleId, [{delete_all_goods, _}|Rest]) ->
    db:execute(?DELETE_ROLE_ALL_GOODS, [RoleId]),
    db_update_drop_role(RoleId, Rest);

db_update_drop_role(RoleId, [{delete_wave, DbUpdateWaves = [_|_]}|Rest]) ->
    DeleteWaves = [WaveId || {WaveId, _} <- DbUpdateWaves],
    % WaveString  = list_concat(",", DeleteWaves, ""),
    WaveString  = util:link_list(DeleteWaves),
    WaveString1 = lists:concat(["(", WaveString, ")"]),
    db:execute(io_lib:format(?DELETE_ROLE_EXP_DUN_WAVE, [RoleId, WaveString1])),
    db_update_drop_role(RoleId, Rest);

db_update_drop_role(RoleId, [{calc_time, NewCalcTime}|Rest]) when is_integer(NewCalcTime) ->
    db:execute(?UPDATE_DROP_INFO_CALC_TIME, [NewCalcTime, RoleId]),
    db_update_drop_role(RoleId, Rest);

db_update_drop_role(RoleId, [{start_time, StartTime}|Rest]) when is_integer(StartTime) ->
    db:execute(?UPDATE_DROP_INFO_START_TIME, [StartTime, RoleId]),
    db_update_drop_role(RoleId, Rest);

db_update_drop_role(RoleId, [{extra_data, ExtraData}|Rest]) ->
    ExtraDataBin = util:term_to_bitstring(ExtraData),
    db:execute(?UPDATE_DROP_INFO_EXTRA_DATA, [ExtraDataBin, RoleId]),
    db_update_drop_role(RoleId, Rest);

% db_update_drop_role(RoleId, [{update_extra_data, UpdateArgs}|Rest], DropRole) ->
%     #drop_role{extra_data = ExtraData} = DropRole,
%     {_, NewExtraData} = update_drop_role_extra_data(ExtraData, UpdateArgs),
%     ExtraDataBin = util:term_to_bitstring(NewExtraData),
%     db:execute(?UPDATE_DROP_INFO_EXTRA_DATA, [ExtraDataBin, RoleId]),
%     do_update_role_drop(Rest, DropRole#drop_role{extra_data = NewExtraData});

db_update_drop_role(RoleId, [{all_drop_role_info, StartTime, CalcTime, ExtraData}|Rest]) ->
    ExtraDataBin = util:term_to_bitstring(ExtraData),
    db:execute(?UPDATE_DROP_INFO, [StartTime, CalcTime, ExtraDataBin, RoleId]),
    db_update_drop_role(RoleId, Rest);

db_update_drop_role(RoleId, [{all_drop_role_info_exclude_extra_data, StartTime, CalcTime}|Rest]) ->
    db:execute(?UPDATE_DROP_INFO_EXCLUDE_EXTRA_DATA, [StartTime, CalcTime, RoleId]),
    db_update_drop_role(RoleId, Rest);

db_update_drop_role(RoleId, [{off_points, NewOffPoints}|Rest]) ->
    DBSQLList = [io_lib:format(?BATCH_REPLACE_OFF_POINT_VAL, [RoleId, OffStart, OffEnd]) || {OffStart, OffEnd} <- NewOffPoints],
    SQL = io_lib:format(?BATCH_REPLACE_INTO_OFF_POINT, [string:join(DBSQLList, ",")]),
    DBSQLList =/= [] andalso db:execute(SQL),
    db_update_drop_role(RoleId, Rest);

db_update_drop_role(RoleId, [{del_off_points, DelPoints}|Rest]) ->
    DeletePoints = lists:foldl(
     fun
        ({_Start, _End, StartKey}, AccPoint) -> 
            case StartKey of
                [] -> [_Start|lists:delete(_Start, AccPoint)];
                [Start1] -> [Start1|lists:delete(Start1, AccPoint)];
                _ -> AccPoint
            end;
        ({Start, _End}, AccPoint) ->
            [Start|lists:delete(Start, AccPoint)]
    end, [], DelPoints),
    PointString1  = util:link_list(DeletePoints),
    PointString2 = lists:concat(["(", PointString1, ")"]),
    DeletePoints =/= [] andalso db:execute(io_lib:format(?DELETE_ROLE_OFF_POINT, [RoleId, PointString2])),
    db_update_drop_role(RoleId, Rest);

db_update_drop_role(_RoleId, [_|Rest]) ->
    db_update_drop_role(_RoleId, Rest).

%% 拼接整型列表
%% [1,2,3,4] => "4,3,2,1"
% list_concat(_S, [], Acc) -> Acc;
% list_concat(S, [Id], Acc) when is_integer(Id) ->
%     NewAcc = lists:concat([integer_to_list(Id), Acc]),
%     list_concat(S, [], NewAcc);
% list_concat(S, [Id|Rest], Acc) when is_integer(Id) ->
%     NewAcc = lists:concat([S, integer_to_list(Id), Acc]),
%     list_concat(S, Rest, NewAcc);
% list_concat(S, [_|Rest], Acc) ->
%         list_concat(S, Rest, Acc).


%% 查看放置奖励,检查并手动结算自上次结算以后剩余未结算的产出
calc_manual_exp_drop_reward(RoleId) ->
    case manual_exp_drop_reward_check(RoleId) of
        {true, DropRole, Now} ->
            #drop_role{
                id = RoleId, start_time = StartTime, calc_time = CalcTime, wave_point = WavePoint,
                calc_goods = CalcGoods, goods_limit = GoodsLimit, extra_data = ExtraData,
                off_point = OffPoints
            } = DropRole,
            % ?ERR("manual get start_time ~p", [StartTime]),
            PassWavePoint   = calc_pass_wave_point(StartTime, Now, WavePoint),
            {MaxWave, Time} = calc_max_wave_pass(WavePoint),
            case PassWavePoint of
                [_|_] ->
                    NewPassWavePoint = PassWavePoint;
                _     -> %% 本次结算期间无任何新增通波,以最近波数为准
                    NewPassWavePoint = [{MaxWave, Time}]
            end,
            Lv = lib_role:get_role_lv(RoleId),
            %% 获取放置挂机上限时长
            ResourceLimit = get_exp_drop_time_limit(ExtraData),
            NewCalcTime   = get_new_calc_time(Now, StartTime, ResourceLimit),
            RestBuffTime  = get_rest_off_buff_time(ExtraData),
            DefaultData   = get_default_calc_time(CalcTime, StartTime),
            TimeLast      = get_calc_time_last(ExtraData, DefaultData),

            {ExpCalcGoods, ExpDbUpdateGoods, _FuturePoints, DelPoints, NewRestBuffTime, NewTimeLast} =
             calc_exp_drop(RoleId, OffPoints, NewPassWavePoint, CalcTime, NewCalcTime, RestBuffTime, TimeLast, ResourceLimit, CalcGoods),
            {NewGoodsLimit, DbUpdateGoods, NewCalcGoods} =
             calc_goods_drop(NewPassWavePoint, CalcTime, NewCalcTime, ExpCalcGoods, GoodsLimit, Lv),
             UpdateExtraData = [
                {{?TYPE_GOODS, ?EXP_DROP_GOODS_SUB_TYPE}, replace, NewRestBuffTime},
                {{?MOD_DUNGEON, ?EXP_DROP_1}, replace, NewTimeLast}
             ],
            % ?ERR("manual", []),
            % ?ERR("manual start_time ~p get new calc_time ~p calc_goods ~p", [StartTime, NewCalcTime, NewCalcGoods]),
            case NewCalcGoods of
                [_|_] ->
                    DbUpdateWaves = WavePoint -- [{MaxWave, Time}],
                    NewDropRole   = do_update_role_drop([
                        {update_goods_limit, NewGoodsLimit}, 
                        {update_goods_all, NewCalcGoods}, 
                        {calc_time, NewCalcTime}, 
                        {update_wave_all, [{MaxWave, Time}]},
                        % {update_reward_sign, ?EXP_DROP_REWARDING},
                        {update_extra_data, UpdateExtraData},
                        {del_off_points, DelPoints}
                        % {off_points, FuturePoints}
                        ], DropRole),
                    F = fun() -> 
                        db_update_drop_role(RoleId, [
                            {calc_time, NewCalcTime}, 
                            {drop_goods, ExpDbUpdateGoods++DbUpdateGoods}, 
                            {delete_wave, DbUpdateWaves},
                            {del_off_points, DelPoints},
                            % {off_points, FuturePoints},
                            {extra_data, NewDropRole#drop_role.extra_data}
                        ]),
                        ok
                    end,
                    case db:transaction(F) of
                        ok ->
                            exp_dun_drop_logic:save_role_drop(NewDropRole),
                            NewCalcGoods1 = [{GoodsType, GoodsId, Num} || {{GoodsType, GoodsId}, Num} <- NewCalcGoods],
                            {true, NewCalcGoods1};
                        Error ->
                            ?ERR("role ~p manual drop reward error ~p", [RoleId, Error]),
                            {false, ?FAIL}
                    end;
                _ ->
                    {false, ?ERRCODE(err610_not_exp_drop_reward)}
            end;
        {false, Error} ->
            {false, Error}

    end.

%% 发放奖励
send_exp_drop_reward(RoleId, CalcGoods, CostTime, RestBuffTime, UpdateArgs) ->
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) andalso is_pid(Pid) of
        true ->
            lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, role_send_drop_reward, [CalcGoods, CostTime, RestBuffTime, UpdateArgs]);
        _ ->
            skip
    end.

%% 查询奖励返回
query_exp_drop_reward_return(RoleId, CalcGoods) ->
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) andalso is_pid(Pid) of
        true ->
            lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, role_query_exp_drop_reward_return, [CalcGoods]);
        _ ->
            skip
    end.

%% 查询奖励返回--人物进程
role_query_exp_drop_reward_return(Player, CalcGoods) ->
    #player_status{sid = Sid} = Player,
    {_, _, RewardsShow, _} = calc_drop_rewards(Player, CalcGoods),
    {ok, Bin}  = pt_610:write(61050, [?SUCCESS, 0, 0, RewardsShow]),
    lib_server_send:send_to_sid(Sid, Bin),
    {ok, Player}.

%% 发放奖励--人物进程
role_send_drop_reward(Player, CalcGoods, CostTime, RestBuffTime, UpdateArgs) ->
    #player_status{id = RoleId, sid = Sid, figure = #figure{lv = PreRoleLv}, exp_dun_drop = RoleExpDunDrop} = Player,
    {SweepRewards, ExpDropRewards, RewardsShow, DailyGoods} = calc_drop_rewards(Player, CalcGoods),
    % ?ERR("~p", [RewardsShow]),
    FinalRewards = lib_goods_util:merge_same_goods(lists:append(SweepRewards, ExpDropRewards)),
    % ?ERR("manual final get rewards ~p", [FinalRewards]),
    %% 发奖励
    NullCellNum = lib_goods_api:get_cell_num(RoleId),
    {MailGoods, CellGoods, _} = calc_avalible_loc_goods(FinalRewards, NullCellNum),
    #player_status{figure = #figure{lv = AfterRoleLv}} = NewPlayer = lib_goods_api:send_reward(Player, CellGoods, exp_dun_drop_reward),
    NewPlayer1 = NewPlayer#player_status{exp_dun_drop = RoleExpDunDrop#role_exp_dun_drop{cache_time_last = 0, last_time = utime:unixtime()}},
    DailyGoods =/= [] andalso lib_daily:set_count(RoleId, DailyGoods),
    MailGoods =/= [] andalso
     begin
        Title   = utext:get(6100003),
        Content = utext:get(6100004),
        lib_mail_api:send_sys_mail([RoleId], Title, Content, MailGoods) 
     end,
    exp_dun_drop_mgr:sync_drop_role_info_after_reward(RoleId, [{{?MOD_DUNGEON, ?EXP_DROP_1}, 0}] ++ UpdateArgs),
    Now = utime:unixtime(),
    case lists:keyfind(?TYPE_EXP, 1, CellGoods) of
        {_, 1, OffExp} -> ok;
        _ -> OffExp = 0
    end,
    {ok, Bin}  = pt_610:write(61050, [?SUCCESS, 1, 0, RewardsShow]),
    {ok, Bin1} = pt_610:write(61052, [Now, OffExp, RestBuffTime, 0]),
    lib_server_send:send_to_sid(Sid, <<Bin/binary, Bin1/binary>>),
    %% 日志
    case lists:keyfind(?TYPE_EXP, 1, FinalRewards) of
        {_, ExpAdd} -> ok;
        _   -> ExpAdd = 0
    end,
    lib_log_api:log_onhook_result(
        [RoleId, PreRoleLv, AfterRoleLv, ExpAdd, CostTime, util:term_to_bitstring(FinalRewards), Now]
    ),
    {ok, NewPlayer1}.

role_exp_dun_drop_info_update(Player, TimeLast, Now) ->
    #player_status{exp_dun_drop = ExpDunDrop} = Player,
    case ExpDunDrop of
        #role_exp_dun_drop{} ->
            ExpDunDrop1 = ExpDunDrop#role_exp_dun_drop{cache_time_last = TimeLast, last_time = Now};
        _ ->
            ExpDunDrop1 = #role_exp_dun_drop{cache_time_last = TimeLast, last_time = Now}
    end,
    {ok, Player#player_status{exp_dun_drop = ExpDunDrop1}}.

get_exp_drop_reward(RoleId) ->
    Pid = misc:get_player_process(RoleId),
    case is_pid(Pid) andalso misc:is_process_alive(Pid) of
        true ->
            case exp_dun_drop_logic:get_role_drop(RoleId) of
                #drop_role{start_time = StartTime, calc_goods = CalcGoods = [_|_], extra_data = ExtraData} ->
                    ResourceLimit = get_exp_drop_time_limit(ExtraData),
                    RestBuffTime  = get_rest_off_buff_time(ExtraData),
                    NewCalcGoods1 = [{GoodsType, GoodsId, Num} || {{GoodsType, GoodsId}, Num} <- CalcGoods],
                    CostTime = min(utime:unixtime() - StartTime, round(ResourceLimit*3600)),
                    {true, NewCalcGoods1, CostTime, RestBuffTime};
                _ ->
                    {false, ?FAIL}
            end;
        _ ->
            {false, ?ERRCODE(err140_14_not_online)}
    end.


%% 计算奖励
calc_drop_rewards(Player, CalcGoods) ->
    %% 放置经验挂机收益
    {CalcGoods1, DailyGoods} = calc_exp_drop_add_ratio_rewards(Player, CalcGoods),
    %% 检查经验本扫荡奖励,凌晨4点后第一次领取合并扫荡应得奖励
    % SweepRewards = lib_dungeon_exp:get_daily_exp_dun_sweep_reward(Player, get_reward),
    SweepRewards = [],
    % %% 挂机收益+每日扫荡收益
    % FinalRewards = lib_goods_util:merge_same_goods(lists:append(SweepRewards, CalcGoods1)),
    RewardsShow = tran_rewards_show(exp_dun_drop, SweepRewards, CalcGoods1),
    {SweepRewards, CalcGoods1, RewardsShow, DailyGoods}.

tran_rewards_show(CalcType, ExtraRewards, ExpDropRewards) ->
    ExtraRewards1 = lists:foldl(fun({GoodsType, _GoodsId, GoodsNum}, Acc) -> 
        case GoodsType of
            ?TYPE_EXP when CalcType == exp_dun_drop ->
                [{1, GoodsType, 0, GoodsNum}|Acc];
            ?TYPE_EXP when CalcType == fast_exp_drop ->
                [{2, GoodsType, 0, GoodsNum}|Acc];
            _ ->
                [{0, GoodsType, _GoodsId, GoodsNum}|Acc]
        end
    end, [], ExtraRewards),
    ExpDropRewards1 = [begin
        case {GoodsTypeX, GoodsIdX} of
            % {?TYPE_EXP, 1} -> {1, GoodsTypeX, 0, GoodsNumX};
            {?TYPE_EXP, _} -> {1, GoodsTypeX, 0, GoodsNumX};
            _              -> {0, GoodsTypeX, GoodsIdX, GoodsNumX}
        end 
    end || {GoodsTypeX, GoodsIdX, GoodsNumX} <- ExpDropRewards],
    % ?ERR("~p ~p", [ExpDropRewards, ExpDropRewards1]),
    lists:append(ExtraRewards1, ExpDropRewards1).

%% 计算放置经验加成奖励
-spec calc_exp_drop_add_ratio_rewards(Player, Goods) -> {Rewards, DailyGoods} when
    Player :: #player_status{}, %% 人物记录
    Goods  :: list(),           %% 物品列表
    Rewards :: list(),          %% 物品列表
    DailyGoods :: list().        %% 每日掉落限制物品最新数量
calc_exp_drop_add_ratio_rewards(Player, Rewards) ->
    #player_status{id = RoleId} = Player,
    Ratio = lib_player:get_exp_drop_ratio(Player),
    case custom_unite_buff:check_buff(Player, exp_dun_speed) of
        {ok, BuffNum} -> BuffNum;
        _             -> BuffNum = 0
    end,
    % OffExpRatio = data_exp_dun_drop:get_kv(12),
    {DropGoods, DropDailyCounts} =
    lists:foldl(fun({GoodsType, GoodsId, GoodsNum}, {AccGoods, DailyGoods}) ->
        case {GoodsType, GoodsId} of
            {?TYPE_EXP, _} ->
                {[{GoodsType, GoodsId, round(GoodsNum*((Ratio/10000)+(BuffNum/100)))}|AccGoods], DailyGoods};
            % {?TYPE_EXP, 1} -> %% 离线buff加成废弃
                % {[{GoodsType, GoodsId, round(GoodsNum*((Ratio/10000)+(BuffNum/100)+OffExpRatio))}|AccGoods], DailyGoods};
            _ ->
                %% 检测是否有个人每日掉落限制
                case check_is_sp_goods(GoodsId) of
                    true ->
                        case data_drop_limit:get_goods_limit(?TYPE_GOODS, GoodsId) of
                            #base_drop_limit{limit_type = ?DROP_LIMIT_GOODID, limit_day = 1, limit_num = LimitNum} ->
                                Key = {?MOD_GOODS, ?MOD_GOODS_DROP, GoodsId},
                                DropedCount = 
                                case lists:keyfind(Key, 1, DailyGoods) of
                                    {_, OldCount} -> OldCount;
                                    _ -> lib_daily:get_count_offline(RoleId, ?MOD_GOODS, ?MOD_GOODS_DROP, GoodsId)
                                end,
                                NewDropedCount  = DropedCount + GoodsNum,
                                % ?ERR("~p ~p ~p", [RoleId, DropedCount, GoodsNum]),
                                if NewDropedCount >= LimitNum ->
                                        NewGoodsNum = max(0, LimitNum - DropedCount),
                                        if NewGoodsNum > 0 ->
                                            AccGoods1   = [{GoodsType, GoodsId, NewGoodsNum}|AccGoods],
                                            DailyGoods1 = lists:keystore(Key, 1, DailyGoods, {Key, LimitNum}),
                                            {AccGoods1, DailyGoods1};
                                            true -> %% 已达掉落上限
                                                {AccGoods, DailyGoods}
                                        end;
                                    true ->
                                        AccGoods1 = [{GoodsType, GoodsId, GoodsNum}|AccGoods],
                                        DailyGoods1 = lists:keystore(Key, 1, DailyGoods, {Key, NewDropedCount}),
                                        {AccGoods1, DailyGoods1}
                                end;
                            _ -> {[{GoodsType, GoodsId, GoodsNum}|AccGoods], DailyGoods}     
                        end;
                    _ -> {[{GoodsType, GoodsId, GoodsNum}|AccGoods], DailyGoods}
                end
        end  
     end, {[], []}, Rewards),
    % ?ERR("~p ~p", [DropGoods, DropDailyCounts]),
    {DropGoods, DropDailyCounts}.


%% 判断是否是特殊的集字道具|特殊的其他掉落道具
check_is_sp_goods(GoodsId) ->
    case data_goods_type:get(GoodsId) of
        #ets_goods_type{type = ?GOODS_TYPE_STUFF, subtype = 30} ->
            true;
        #ets_goods_type{type = ?GOODS_TYPE_STUFF, subtype = 43} ->
            true;
        _ -> false
    end.

%% 这里只粗略结算,没精确计算现有可叠加
%% (未叠加满)的格子可承载多少数量的物品
calc_avalible_loc_goods(CalcGoods, NullCellNum) ->
    lists:foldl(fun({GoodsType, GoodsId, Num}, {TmpMailGoods, TmpCellGoods, HasNullCell}) ->
        if GoodsType =/= ?TYPE_GOODS ->
                {TmpMailGoods, [{GoodsType, GoodsId, Num}|TmpCellGoods], HasNullCell};
            true ->
                case data_goods_type:get(GoodsId) of
                    #ets_goods_type{max_overlap = MaxOverLap} ->
                        if MaxOverLap == 0 -> %% 不可叠加
                                if HasNullCell >= Num ->
                                        NewHasNullCell = HasNullCell - Num,
                                        {TmpMailGoods, [{GoodsType, GoodsId, Num}|TmpCellGoods], NewHasNullCell};
                                    true ->
                                        NewHasNullCell = 0,
                                        GoodsRestNum = Num - HasNullCell,
                                        if GoodsRestNum =< 0 ->
                                                NewTempMailGoods = TmpMailGoods;
                                            true ->
                                                NewTempMailGoods = [{GoodsType, GoodsId, GoodsRestNum}|TmpMailGoods]
                                        end,
                                        if HasNullCell =< 0 ->
                                                NewTmpCellGoods = TmpCellGoods;
                                            true ->
                                                NewTmpCellGoods = [{GoodsType, GoodsId, HasNullCell}|TmpCellGoods]
                                        end,
                                        {NewTempMailGoods, NewTmpCellGoods, NewHasNullCell}
                                end;
                            true -> %% 可叠加
                                if Num =< MaxOverLap -> %% 一个格子够了
                                        if HasNullCell =< 0 ->
                                                {[{GoodsType, GoodsId, Num}|TmpMailGoods], TmpCellGoods, HasNullCell};
                                            true ->
                                                {TmpMailGoods, [{GoodsType, GoodsId, Num}|TmpCellGoods], max(0, (HasNullCell-1))}
                                        end;
                                    true ->
                                        NeedCell = umath:ceil(Num/MaxOverLap),
                                        if HasNullCell >= NeedCell -> %% 剩余格子够了
                                                {TmpMailGoods, [{GoodsType, GoodsId, Num}|TmpCellGoods], max(0, (HasNullCell-NeedCell))};
                                            true -> %% HasNullCell < NeedCell
                                                GoodsRestNum = Num - round(HasNullCell*MaxOverLap),
                                                NewHasNullCell = 0,
                                                {[{GoodsType, GoodsId, GoodsRestNum}|TmpMailGoods], [{GoodsType, GoodsId, round(HasNullCell*MaxOverLap)}|TmpCellGoods], NewHasNullCell}
                                        end
                                end
                        end;
                    _ -> %% 属于物品但id为0,过滤
                        {TmpMailGoods, TmpCellGoods, HasNullCell}
                end
        end
     end, {[], [], NullCellNum}, CalcGoods).

%% 获取玩家掉落时长上限
get_exp_drop_time_limit(_ExtraData) ->
    % case get_extra_data(?MOD_PET, ExtraData) of
    %     Data when is_integer(Data) -> ok;
    %     _ -> Data = 0
    % end,
    ResourceLimit = data_exp_dun_drop:get_kv(6),
    max(ResourceLimit, 0).

%% 获取玩家离线放置剩余时间
get_rest_off_buff_time(ExtraData) ->
    ModuleKey = {?TYPE_GOODS, ?EXP_DROP_GOODS_SUB_TYPE},
    case get_extra_data(ModuleKey, ExtraData) of
        Data when is_integer(Data) -> Data;
        _ -> 0
    end.

%% 获取已结算的总时间长度
get_calc_time_last(ExtraData, DefaultData) ->
    ModuleKey = {?MOD_DUNGEON, ?EXP_DROP_1},
    case get_extra_data(ModuleKey, ExtraData) of
        Data when is_integer(Data) -> 
            Data;
        _ ->
            DefaultData
    end.

get_default_calc_time(CalcTime, StartTime) ->
    max(0, CalcTime - StartTime).

%% 根据当前未结算的离线节点计算剩余离线buff加成时间
calc_rest_off_buff_time(StartTime, OffPoints, RestBuffTime) ->
    Now = utime:unixtime(),
    NewRestBuffTime = lists:foldl(fun({OffStart, OffEnd}, AccRestBuffTime) -> 
        if OffStart >= StartTime ->
                if OffEnd == 0 -> AccRestBuffTime - (Now - OffStart);
                    true -> AccRestBuffTime - (OffEnd - OffStart)
                end;
            true ->
                AccRestBuffTime
        end
    end, RestBuffTime, OffPoints),
    max(0, NewRestBuffTime).

%% 根据当前未结算的在线时间节点计算最新已累计挂机时长
calc_new_calc_time_last(OffPoints, CalcStartTime, CalcEndTime, RestBuffTime, TimeLast, ResourceLimit) ->
    {_OffPoints1, RestBuffTime1, TimeLast1} = calc_off_time_point(OffPoints, CalcStartTime, CalcEndTime, {RestBuffTime, TimeLast, ResourceLimit}),
    {_OnlinePoints, TimeLast2}  = calc_online_time_point(OffPoints, CalcStartTime, CalcEndTime, [], CalcStartTime, {TimeLast1, ResourceLimit}),
    % ?ERR("~p ~p ~p ~p ~p", [CalcStartTime, CalcEndTime, TimeLast, _OffPoints1, _OnlinePoints]),
    % update_calc_time_last(OffPoints1, OnlinePoints, TimeLast).
    {RestBuffTime1, TimeLast2}.

calc_off_time_last(OffPoints) ->
    lists:foldl(fun({Start, End}, AccOffTime) -> 
        if Start =/= 0 andalso End =/= 0 ->
                AccOffTime + (End - Start);
            true ->
                AccOffTime
        end
    end, 0, OffPoints).

get_extra_data(ModuleKey, ExtraData) ->
    case lists:keyfind(ModuleKey, 1, ExtraData) of
        {_, Val} -> Val;
        _ -> false
    end.

update_drop_role_extra_data(ExtraData, UpdateArgs) ->
    {NewDbUpdate, NewExtraData} =
    lists:foldl(fun
        ({ModuleKey, Data}, {DbUpdate, TempExtraData}) ->
            do_update_role_extra_data({ModuleKey, default, Data}, {DbUpdate, TempExtraData});
        ({ModuleKey, UpdateType, Data}, {DbUpdate, TempExtraData}) ->
            do_update_role_extra_data({ModuleKey, UpdateType, Data}, {DbUpdate, TempExtraData})
    end, {false, ExtraData}, UpdateArgs),
    {NewDbUpdate, NewExtraData}.

do_update_role_extra_data({ModuleKey = ?MOD_PET, _, Data}, {DbUpdate, TempExtraData}) ->
    case lists:keyfind(ModuleKey, 1, TempExtraData) of
        {_, OldVal} -> %% 宠物生活技能
            if Data > OldVal -> {true, lists:keystore(ModuleKey, 1, TempExtraData, {ModuleKey, Data})};
                true         -> {DbUpdate, TempExtraData}
            end;
        false -> {true, lists:keystore(ModuleKey, 1, TempExtraData, {ModuleKey, Data})}
    end;
do_update_role_extra_data({ModuleKey = {?TYPE_GOODS, ?EXP_DROP_GOODS_SUB_TYPE}, _Option, Data}, {_DbUpdate, TempExtraData}) ->
    case lists:keyfind(ModuleKey, 1, TempExtraData) of
        {_, _OldVal} -> %% 离线加成卡
            % if Option == replace ->
                    {true, lists:keystore(ModuleKey, 1, TempExtraData, {ModuleKey, Data})};
            %     true ->
            %         {true, lists:keystore(ModuleKey, 1, TempExtraData, {ModuleKey, OldVal + Data})}
            % end;
        false  -> {true, lists:keystore(ModuleKey, 1, TempExtraData, {ModuleKey, Data})}
    end;
do_update_role_extra_data({ModuleKey = {?MOD_DUNGEON, ?EXP_DROP_1}, _Option, Data}, {_DbUpdate, TempExtraData}) ->
    case lists:keyfind(ModuleKey, 1, TempExtraData) of
        {_, _OldVal} ->
            {true, lists:keystore(ModuleKey, 1, TempExtraData, {ModuleKey, Data})};
        false -> {true, lists:keystore(ModuleKey, 1, TempExtraData, {ModuleKey, Data})}
    end;
do_update_role_extra_data(_, {DbUpdate, TempExtraData}) ->
    {DbUpdate, TempExtraData}.

manual_exp_drop_reward_check(RoleId) ->
    Pid = misc:get_player_process(RoleId),
    ExpDunDropRole = exp_dun_drop_logic:get_role_drop(RoleId),
    % ?ERR("~p", [ExpDunDropRole]),
    Now = utime:unixtime(),
    CheckList = [
        {reward_role_avlie, Pid},
        {exp_dun_wave_point, ExpDunDropRole},
        {exp_dun_drop_get_time, ExpDunDropRole, Now},
        {reward_state_check, ExpDunDropRole#drop_role.is_rewarding}
    ],
    case util:check_lists(?MODULE, CheckList) of
        true ->
            {true, ExpDunDropRole, Now};
        {false, Error} ->
            {false, Error}
    end.

check({reward_role_avlie, Pid}) ->
    case is_pid(Pid) andalso misc:is_process_alive(Pid) of
        true ->
            true;
        _ ->
            {false, ?ERRCODE(err140_14_not_online)}
    end;
check({exp_dun_wave_point, ExpDunDropRole}) ->
    case ExpDunDropRole of
        #drop_role{wave_point = [_|_]} ->
            true;
        _ ->
            {false, ?FAIL}
    end;
check({exp_dun_drop_get_time, ExpDunDropRole, Now}) ->
    case ExpDunDropRole of
        #drop_role{wave_point = [_|_], start_time = StartTime, calc_time = _CalcTime} ->
            PassTime = max(0, Now - StartTime),
            % LastCalcDiff = max(0, Now - CalcTime),
            if PassTime >= ?EXP_DROP_REWARD_INTERVAL ->
                    true;
                true ->
                    {false, ?ERRCODE(err610_not_exp_drop_reward)}
            end;
        _ ->
            {false, ?FAIL}
    end;
check({reward_state_check, IsRewarding}) ->
    if IsRewarding == ?EXP_DROP_CALCING ->
            {false, ?FAIL};
        true ->
            true            
    end;
check(_) ->
    {false, ?FAIL}.

gm_delete_exp_drop(RoleId) ->
    db:execute(<<"DELETE FROM role_exp_dun_achieve WHERE role_id = ~p">>, [RoleId]),
    db:execute(<<"DELETE FROM role_exp_dun_drop where role_id = ~p">>, [RoleId]),
    db:execute(<<"DELETE FROM role_exp_dun_drop_goods WHERE role_id = ~p">>, [RoleId]),
    db:execute(<<"DELETE FROM role_exp_dun_wave_info WHERE role_id = ~p">>, [RoleId]),
    db:execute(<<"DELETE FROM dungeon_best_record WHERE dun_id = 5001 AND player_id = ~p">>, [RoleId]),
    ok.