%% ---------------------------------------------------------------------------
%% @doc 玩家物品buff
%% @author hek
%% @since  2016-09-08
%% @deprecated 本模块提供玩家物品buff计算
%% ---------------------------------------------------------------------------
-module(lib_goods_buff).
-include("buff.hrl").
-include("server.hrl").
-include("attr.hrl").
-include("goods.hrl").
-include("errcode.hrl").
-include("predefine.hrl").
-include("def_fun.hrl").
-include("def_goods.hrl").
-include("def_event.hrl").
-include("rec_event.hrl").
-include("common.hrl").
-include("skill.hrl").
-include("battle.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-define(TEMP_KEY(X), {0, X}).
-define(SKILL_KEY(X), {255, X}).

-export([
         use_buff_goods/3,                   %% 使用buff道具
         login/1,                            %% 初始玩家BUFF表
         logout/1,                           %% 玩家下线，移除ets_buff信息
         update_expire_buff_exp_info/3,      %% 更新下线期间过期的经验药水信息
         refresh_buff/0,                     %% 扫描ets，通知玩家进程移除buff
         refresh_player_buff/1,              %% 移除玩家身上过期的buff
         add_goods_buff/4,
         remove_goods_buff_by_type/2,        %% 通过buff类型移除玩家身上的buff
         remove_goods_buff_by_type/3,        %% 通过buff类型移除玩家身上的buff
         check_player_buff/1,                %% 检查玩家buff
         % get_exp_buff/1,                     %% 获取经验buff
         get_goods_drop_buff/1,              %% 获取物品的掉落加成buff
         get_goods_buff_value/2,             %% 获取物品的buff加成
         send_buff_notice/1,                 %% 客户端获取buff列表
         add_buff/4,                         %% 添加buff，不存数据库
         remove_buff/2,                      %% 移除buff，不操作数据库
         add_goods_temp_buff/4,              %% 加临时的显示buff，不存数据库，不加属性，只是展示
         remove_goods_temp_buff/2,           %% 删除临时的显示buff，不操作数据库，不计算属性
         add_skill_buff/3,                   %% 添加技能buff，不操作数据库
         remove_skill_buff/2,                %% 删除技能buff，不操作数据库
         db_remove_goods_buff/1              %% 直接从数据库移除某种类型Buff
        ]).

%% 初始玩家BUFF表+功能统计类buff
login(#player_status{id = PlayerId, scene = SceneId, skill = _SkillStatus} = PlayerStatus) ->
    NowTime = utime:unixtime(),
    %% 加载下线期间过期的经验药水信息
    case db:get_all(io_lib:format(?sql_select_buff_type_expired, [PlayerId, ?BUFF_EXP_KILL_MON, NowTime])) of
        [[ExpGoodsId, ExpGoodsEndTime]|_] -> skip;
        _ -> ExpGoodsId = 0, ExpGoodsEndTime = 0
    end,
    ExpireBuffExpInfo = {ExpGoodsId, ExpGoodsEndTime},
    %% 先移除玩家过期的buff
    db:execute(io_lib:format(?sql_delete_player_expired_buff, [PlayerId, NowTime])),
    Sql = io_lib:format(?sql_select_buff_all, [PlayerId]),
    F = fun(E, PlayerBuff) ->
        case E of
            [Id, Pid, Type, GoodsId, EffectList, EndTime, Scene] when EndTime == 0 orelse EndTime > NowTime ->
                Buff = #ets_buff{
                      id = Id, pid = Pid, type = Type, goods_id = GoodsId, end_time = EndTime,
                      effect_list = util:bitstring_to_term(EffectList),
                      scene = util:bitstring_to_term(Scene)},
                case misc:is_process_alive(PlayerStatus#player_status.pid) of
                    true -> save_ets_buff(Buff);
                    false -> skip
                end,
                store_player_buff(PlayerBuff, Buff);
            _ -> PlayerBuff
        end
    end,
    {PlayerBuff, BuffAttr} = case db:get_all(Sql) of
            BuffList when is_list(BuffList) ->
                L = lists:foldl(F, [], BuffList),
                {L, get_buff_attr(L, SceneId)};
            _ -> {[], #{}}
    end,
    ExpRatio = get_exp_buff(PlayerBuff, 0),
    PlayerStatus#player_status{
        player_buff = PlayerBuff, buff_attr = BuffAttr, goods_buff_exp_ratio = ExpRatio,
        logout_expire_buff_exp_info = ExpireBuffExpInfo
    }.
    %% 特殊的技能临时buff:展示
    % case lists:keyfind(?SP_SKILL_KILLING, 1, SkillStatus#status_skill.skill_passive) of
    %     false ->
    %         PlayerStatus#player_status{
    %             player_buff = PlayerBuff, buff_attr = BuffAttr, goods_buff_exp_ratio = ExpRatio,
    %             logout_expire_buff_exp_info = ExpireBuffExpInfo};
    %     _ ->
    %         Ps = PlayerStatus#player_status{player_buff = PlayerBuff, buff_attr = BuffAttr,
    %             goods_buff_exp_ratio = ExpRatio, logout_expire_buff_exp_info = ExpireBuffExpInfo},
    %         add_goods_temp_buff(Ps, ?BUFF_TEAM_SHOW, [{?SPBUFF_REBOUND, 1}], 0)
    % end.

%% 玩家下线，移除ets_buff信息
logout(PlayerStatus) ->
    PlayerBuff = PlayerStatus#player_status.player_buff,
    [delete_ets_buff(Buff) || Buff <- PlayerBuff].

%% 更新下线期间过期的经验药水信息
update_expire_buff_exp_info(PS, GoodsTypeId, GoodsEndTime) ->
    PS#player_status{logout_expire_buff_exp_info = {GoodsTypeId, GoodsEndTime}}.

%% 使用buff道具
use_buff_goods(PlayerStatus, GoodsInfo, GoodsNum) ->
    case data_goods:get_effect_val(GoodsInfo#goods.goods_id, buff) of
        [] -> {fail, ?ERRCODE(err150_type_err)};
        BuffArgs -> do_use_buff_goods(PlayerStatus, GoodsInfo, GoodsNum, BuffArgs)
    end.

do_use_buff_goods(PlayerStatus, GoodsInfo, GoodsNum, BuffArgs) ->
    SceneId = PlayerStatus#player_status.scene,
    {_BuffType, _EffectList, _Time, LimitScene} = BuffArgs,
    case LimitScene of
        [] ->
            lib_goods_api:cost_object_list(PlayerStatus, [{0, GoodsInfo#goods.goods_id, GoodsNum}], goods_use, ""),
            {ok, NewPlayerStatus} = do_add_goods_buff(PlayerStatus, GoodsInfo#goods.goods_id, GoodsNum, BuffArgs, []),
            %% 派发使用buff物品事件
            CallBackData = #callback_use_buff_goods_data{goods = GoodsInfo},
            lib_player_event:dispatch(NewPlayerStatus, ?EVENT_USE_BUFF_GOODS, CallBackData);
        _ ->
            case lists:member(SceneId, LimitScene) of
                false -> {fail, ?ERRCODE(err150_scene_wrong)};
                true ->
                    lib_goods_api:cost_object_list(PlayerStatus, [{0, GoodsInfo#goods.goods_id, GoodsNum}], goods_use, ""),
                    {ok, NewPlayerStatus} = do_add_goods_buff(PlayerStatus, GoodsInfo#goods.goods_id, GoodsNum, BuffArgs, []),
                    %% 派发使用buff物品事件
                    CallBackData = #callback_use_buff_goods_data{goods = GoodsInfo},
                    lib_player_event:dispatch(NewPlayerStatus, ?EVENT_USE_BUFF_GOODS, CallBackData)
            end
    end.

%% 为了把需要显示的buff写在一起，不对技能buff另外写一套，统一写在这儿 技能buff不存库
%% 技能id将作为图标buff的类型
add_skill_buff(Ps, SkillId, SkillLv) ->
    case lib_skill_buff:skill_buff_to_goods_buff(SkillId, SkillLv) of
        {SkillId, Time, EffectList} ->
            EndTime = if
                Time =:= 0 ->
                    0;
                true ->
                    utime:unixtime() + Time
            end,
            #player_status{id = PlayerId, player_buff = PlayerBuff, scene = SceneId, buff_attr = OldBuffAttr} = Ps,
            TeamBuff = #ets_buff{id = ?SKILL_KEY(SkillId), pid = PlayerId, type = ?BUFF_SKILL, goods_id = SkillId,
                                 end_time = EndTime, effect_list = [{attr, EffectList}]},
            save_ets_buff(TeamBuff),
            NewPF = store_player_buff(PlayerBuff, TeamBuff),
            BuffAttr = get_buff_attr(NewPF, SceneId),
            ExpRatio = get_exp_buff(NewPF, 0),
            NewPlayerStatus = Ps#player_status{player_buff = NewPF, buff_attr = BuffAttr, goods_buff_exp_ratio = ExpRatio},
            send_buff_notice(PlayerId, SceneId, NewPF),
            case BuffAttr =/= OldBuffAttr of
                true ->
                    count_player_attr(NewPlayerStatus);
                false ->
                    NewPlayerStatus
            end;
        _ ->
            Ps
    end.

remove_skill_buff(Ps, SkillId) ->
    #player_status{id = PlayerId, player_buff = PlayerBuff, scene = SceneId, buff_attr = OldBuffAttr} = Ps,
    Id = ?SKILL_KEY(SkillId),
    case lists:keyfind(Id, #ets_buff.id, PlayerBuff) of
        Buff when is_record(Buff, ets_buff) ->
            NewPF = delete_player_buff(PlayerBuff, Buff),
            ExpRatio = get_exp_buff(NewPF, 0),
            delete_ets_buff(Buff),
            BuffAttr = get_buff_attr(NewPF, SceneId),
            send_buff_notice(PlayerId, SceneId, NewPF),
            NewPlayerStatus = Ps#player_status{player_buff = NewPF, buff_attr = BuffAttr, goods_buff_exp_ratio = ExpRatio},
            case BuffAttr =/= OldBuffAttr of
                true ->
                    count_player_attr(NewPlayerStatus);
                false ->
                    NewPlayerStatus
            end;
        _ ->
            Ps
    end.

%% 添加不存库的buff 如果buff没有战斗属性，推荐使用add_goods_temp_buff
add_buff(PlayerId, BuffType, EffectList, Time) when is_integer(PlayerId) andalso PlayerId > 0 ->
    lib_player:apply_cast(PlayerId, ?APPLY_CAST_SAVE, lib_goods_buff, add_buff, [BuffType, EffectList, Time]);

add_buff(Ps, BuffType, EffectList, Time) when is_record(Ps, player_status) ->
    #player_status{id = PlayerId, player_buff = PlayerBuff, scene = SceneId, buff_attr = OldBuffAttr} = Ps,
    TeamBuff = #ets_buff{id = ?TEMP_KEY(BuffType), pid = PlayerId, type = BuffType,
                         end_time = Time, effect_list = EffectList},
    save_ets_buff(TeamBuff),
    NewPF = store_player_buff(PlayerBuff, TeamBuff),
    BuffAttr = get_buff_attr(NewPF, SceneId),
    ExpRatio = get_exp_buff(NewPF, 0),
    NewPlayerStatus = Ps#player_status{player_buff = NewPF, buff_attr = BuffAttr, goods_buff_exp_ratio = ExpRatio},
    send_buff_notice(PlayerId, SceneId, NewPF),
    case BuffAttr =/= OldBuffAttr of
        true ->
            count_player_attr(NewPlayerStatus);
        false ->
            NewPlayerStatus
    end.

%% 移除不存库的buff
remove_buff(Ps, BuffType) ->
    #player_status{player_buff = PlayerBuff, id = PlayerId, buff_attr = OldBuffAttr, scene = SceneId} = Ps,
    case get_player_all_buff(PlayerBuff, BuffType) of
        [] -> Ps;
        % Buff when is_record(Buff, ets_buff) ->
        %     NewPF = delete_player_buff(PlayerBuff, Buff),
        %     ExpRatio = get_exp_buff(NewPF, 0),
        %     delete_ets_buff(Buff),
        %     BuffAttr = get_buff_attr(NewPF, SceneId),
        %     send_buff_notice(PlayerId, SceneId, NewPF),
        %     NewPlayerStatus = Ps#player_status{player_buff = NewPF, buff_attr = BuffAttr, goods_buff_exp_ratio = ExpRatio},
        %     case BuffAttr =/= OldBuffAttr of
        %         true ->
        %             count_player_attr(NewPlayerStatus);
        %         false ->
        %             NewPlayerStatus
        %     end;
        Buffs = [_|_] ->
            NewPF = lists:foldl(fun(BuffX, TempPlayerBuff) ->
                delete_ets_buff(BuffX),
                delete_player_buff(TempPlayerBuff, BuffX)
            end, PlayerBuff, Buffs),
            ExpRatio = get_exp_buff(NewPF, 0),
            BuffAttr = get_buff_attr(NewPF, SceneId),
            send_buff_notice(PlayerId, SceneId, NewPF),
            NewPlayerStatus = Ps#player_status{player_buff = NewPF, buff_attr = BuffAttr, goods_buff_exp_ratio = ExpRatio},
            case BuffAttr =/= OldBuffAttr of
                true ->
                    count_player_attr(NewPlayerStatus);
                false ->
                    NewPlayerStatus
            end;
        _ ->
            Ps
    end.

%% 加临时的显示buff，不存数据库，不加战斗属性，经验加成生效 如果已经加的buff没有战斗属性，使用这个比add_buff好
add_goods_temp_buff(Ps, BuffType, EffectList, Time)->
    #player_status{id = PlayerId, player_buff = PlayerBuff, scene = SceneId} = Ps,
    TeamBuff = #ets_buff{id = ?TEMP_KEY(BuffType), pid = PlayerId, type = BuffType,
                         end_time = Time, effect_list = EffectList},
    save_ets_buff(TeamBuff),
    NewPF = store_player_buff(PlayerBuff, TeamBuff),
    ExpRatio = get_exp_buff(NewPF, 0),
    send_buff_notice(PlayerId, SceneId, NewPF),
    Ps#player_status{player_buff = NewPF, goods_buff_exp_ratio = ExpRatio}.

%% 删除临时显示buff
remove_goods_temp_buff(Ps, BuffType)->
    #player_status{id = PlayerId, player_buff = PlayerBuff, scene = SceneId} = Ps,
    case get_player_all_buff(PlayerBuff, BuffType) of
        [] -> Ps;
        % Buff ->
        %     NewPF = delete_player_buff(PlayerBuff, Buff),
        %     delete_ets_buff(Buff),
        %     ExpRatio = get_exp_buff(NewPF, 0),
        %     send_buff_notice(PlayerId, SceneId, NewPF),
        %     Ps#player_status{player_buff = NewPF, goods_buff_exp_ratio = ExpRatio};
        Buffs = [_|_] ->
            NewPF = lists:foldl(fun(BuffX, TempPlayerBuff) ->
                delete_ets_buff(BuffX),
                delete_player_buff(TempPlayerBuff, BuffX)
            end, PlayerBuff, Buffs),
            ExpRatio = get_exp_buff(NewPF, 0),
            send_buff_notice(PlayerId, SceneId, NewPF),
            Ps#player_status{player_buff = NewPF, goods_buff_exp_ratio = ExpRatio};
        _ ->
            Ps
    end.

%% 直接给玩家加上某个物品的buff 不用消耗物品
add_goods_buff(PlayerId, GoodsTypeId, GoodsNum, KeyValList) when is_integer(PlayerId) andalso PlayerId > 0 ->
    lib_player:apply_cast(PlayerId, ?APPLY_CAST_SAVE, lib_goods_buff, add_goods_buff, [GoodsTypeId, GoodsNum, KeyValList]);

add_goods_buff(PlayerStatus, GoodsTypeId, GoodsNum, KeyValList) when is_record(PlayerStatus, player_status) ->
    case data_goods:get_effect_val(GoodsTypeId, buff) of
        [] ->
            {ok, PlayerStatus};
        BuffArgs ->
            do_add_goods_buff(PlayerStatus, GoodsTypeId, GoodsNum, BuffArgs, KeyValList)
    end.

do_add_goods_buff(PlayerStatus, GoodsTypeId, GoodsNum, {BuffType, EffectList, Time, LimitScene}, KeyValList) ->
    #player_status{id = PlayerId, scene = SceneId, player_buff = PlayerBuff, buff_attr = OldBuffAttr} = PlayerStatus,
    %% 后台配置buff有效时间为0永久buff 已经有了不用重复添加 需代码里面手动移除改buff
    case Time == 0 of
        true ->
            Duration = 0,
            EndTime = 0;
        false ->
            if BuffType == ?BUFF_CAMP_WAR ->
                    Duration = Time;
                true ->
                    Duration = Time * GoodsNum
            end,
            EndTime = Duration + utime:unixtime()
    end,
    %% 外部设置buff的结束时间和属性
    case lists:keyfind(etime, 1, KeyValList) of
        {etime, EtimeArgs} ->
            RealEtime = EtimeArgs;
        _ -> RealEtime = EndTime
    end,
    case lists:keyfind(effect_list, 1, KeyValList) of
        {effect_list, EffectListArgs} ->
            RealEffectList0 = EffectListArgs;
        _ -> RealEffectList0 = EffectList
    end,
    if
        BuffType == ?BUFF_CAMP_WAR ->
            case lists:keyfind(attr, 1, EffectList) of
                {_, AttrList} ->
                    NewAttrList = [{AttrId, round(Val*GoodsNum)} || {AttrId, Val} <- AttrList],
                    RealEffectList = lists:keystore(attr, 1, EffectList, {attr, NewAttrList});
                _ ->
                    RealEffectList = EffectList
            end;
        true ->
            RealEffectList = RealEffectList0
    end,
    case get_player_buff(PlayerBuff, BuffType, GoodsTypeId) of
        false ->
            {NewBuff, NewPlayerBuff} = add_player_buff(PlayerBuff, PlayerId, BuffType, GoodsTypeId, RealEffectList, RealEtime, LimitScene);
        _ when EndTime == 0 -> %% 如果后台配置的是手动移除类型buff不需要重复添加！！！
            NewBuff = false, NewPlayerBuff = PlayerBuff;
        #ets_buff{goods_id = GoodsTypeId, type = ?BUFF_CAMP_WAR} = Buff -> %% 跨服团战buff不判断时间,默认以初次生效buff时间节点为结束时间
            {NewBuff, NewPlayerBuff} = mod_camp_war_buff(PlayerBuff, Buff, GoodsTypeId, RealEffectList, LimitScene);
        #ets_buff{type = ?BUFF_CAMP_WAR} -> %% 有同种跨服团战buff但不是同个goods_id
            {NewBuff, NewPlayerBuff} = add_player_buff(PlayerBuff, PlayerId, BuffType, GoodsTypeId, RealEffectList, RealEtime, LimitScene);
        #ets_buff{goods_id = OldGoodsTypeId, end_time = OldEndTime} = Buff when OldGoodsTypeId == GoodsTypeId andalso Time =/= 0 -> %% 同类型同效果
            {NewBuff, NewPlayerBuff} = mod_buff(PlayerBuff, Buff, GoodsTypeId, RealEffectList, OldEndTime + Duration, LimitScene);
        Buff when Time =/= 0 ->
            {NewBuff, NewPlayerBuff} = mod_buff(PlayerBuff, Buff, GoodsTypeId, RealEffectList, RealEtime, LimitScene);
        _ -> NewBuff = false, NewPlayerBuff = PlayerBuff
    end,
    case is_record(NewBuff, ets_buff) of
        true ->
            save_ets_buff(NewBuff),
            BuffAttr = get_buff_attr(NewPlayerBuff, SceneId),
            ExpRatio = get_exp_buff(NewPlayerBuff, 0),
            NewPlayerStatus = PlayerStatus#player_status{player_buff = NewPlayerBuff, buff_attr = BuffAttr, goods_buff_exp_ratio = ExpRatio},
            send_buff_notice(PlayerId, SceneId, NewPlayerBuff),
            case BuffAttr =/= OldBuffAttr of
                true ->
                    NewPlayerStatus2 = count_player_attr(NewPlayerStatus);
                false ->
                    NewPlayerStatus2 = NewPlayerStatus
            end,
            case data_goods_effect:get(GoodsTypeId) of
                #goods_effect{
                   limit_type = LimitType,
                   counter_module = CounterModule,
                   counter_id = CounterId
                  } when LimitType =/= 0 ->
                    plus_counter(PlayerId, CounterModule, CounterId, LimitType, GoodsNum);
                _ -> skip
            end,
            LastPlayerStatus = after_buff_event(NewPlayerStatus2, BuffType, EffectList),
            {ok, LastPlayerStatus};
        _ -> {ok, PlayerStatus}
    end.
%% 次数增加
plus_counter(_PlayerId, 0, _CounterId, _CounterType, _UseCount) -> skip;
plus_counter(_PlayerId, _Module, 0, _CounterType, _UseCount) -> skip;
plus_counter(_PlayerId, _Module, _CounterId, 0, _UseCount) -> skip;
plus_counter(PlayerId, CounterModule, CounterId, ?COUNTER_DAILY, UseCount) ->
    lib_daily:plus_count(PlayerId, CounterModule, CounterId, UseCount);
plus_counter(PlayerId, CounterModule, CounterId, ?COUNTER_WEEK, UseCount) ->
    lib_week:plus_count(PlayerId, CounterModule, CounterId, UseCount);
plus_counter(PlayerId, CounterModule, CounterId, ?COUNTER_LIFETIME, UseCount) ->
    lib_counter:plus_count(PlayerId, CounterModule, CounterId, UseCount);
plus_counter(_PlayerId, _Module, _CounterId, _CounterType, _UseCount) -> skip.
%% 添加物品buff后事件
after_buff_event(PS, BuffType, EffectList) ->
    EventList = [is_triger_vip_immune],
    case do_after_buff_event(EventList, PS, BuffType, EffectList) of
        {no, NewPS} -> NewPS;
        {ok, NewPS} -> NewPS
    end.

do_after_buff_event([], PS, _BuffType, _EffectList) -> {no, PS};
do_after_buff_event([H|T], PS, BuffType, EffectList) ->
    case do_after_buff_callback(H, PS, BuffType, EffectList) of
        {ok, NewPS} -> {ok, NewPS};
        {next, NewPS} -> do_after_buff_event(T, NewPS, BuffType, EffectList)
    end.

do_after_buff_callback(is_triger_vip_immune, PS, ?BUFF_VIP_IMMUNE, _EffectList) ->
    IsBossScene = lib_scene:is_boss_scene(PS#player_status.scene),
    case {get_goods_buff_value(PS, ?BUFF_VIP_IMMUNE), IsBossScene}  of
        {BuffValue, true} when BuffValue>0 ->
            CanAttList = [{?BATTLE_SIGN_MON, []}],
            CannotBeattList = [{?BATTLE_SIGN_PLAYER, []}],
            NewPS = PS#player_status{can_att_list = CanAttList, cannot_beatt_list = CannotBeattList},
            mod_scene_agent:update(NewPS, [{can_att_list, CanAttList}, {cannot_beatt_list, CannotBeattList}]),
            {next, NewPS};
        _ -> {next, PS}
    end;
do_after_buff_callback(_Unkown, PS, _BuffType, _EffectList) ->
    {next, PS}.

%% 移除玩家某个类型的Buff
remove_goods_buff_by_type(PlayerId, BuffType, HandleOffline) when is_integer(PlayerId) andalso PlayerId > 0 ->
    Pid = misc:get_player_process(PlayerId),
    case misc:is_process_alive(Pid) of
        true ->
            lib_player:apply_cast(PlayerId, ?APPLY_CAST_SAVE, lib_goods_buff, remove_goods_buff_by_type, [BuffType]);
        _  when HandleOffline == ?HAND_OFFLINE ->
            %% 玩家不在线直接删除数据库
            db:execute(io_lib:format(?sql_delete_player_buff_by_type, [PlayerId, BuffType]));
        _ ->
            skip
    end;
remove_goods_buff_by_type(_, _, _) -> skip.

remove_goods_buff_by_type(PlayerStatus, BuffType) when is_record(PlayerStatus, player_status) ->
    #player_status{id = PlayerId, scene = SceneId, player_buff = PlayerBuff, buff_attr = OldBuffAttr} = PlayerStatus,
    case get_player_all_buff(PlayerBuff, BuffType) of
        [Buff|_] when is_record(Buff, ets_buff) ->
            db:execute(io_lib:format(?sql_delete_player_buff_by_type, [PlayerId, BuffType])),
            F = fun(T, Acc) ->
                        case T of
                            #ets_buff{
                               type = BuffType
                              } = BuffInfo ->
                                delete_ets_buff(BuffInfo),
                                delete_player_buff(Acc, BuffInfo);
                            _ -> Acc
                        end
                end,
            NewPlayerBuff = lists:foldl(F, PlayerBuff, PlayerBuff),
            send_buff_notice(PlayerId, SceneId, NewPlayerBuff),
            BuffAttr = get_buff_attr(NewPlayerBuff, SceneId),
            ExpRatio = get_exp_buff(NewPlayerBuff, 0),
            NewPlayerStatus = PlayerStatus#player_status{player_buff = NewPlayerBuff, buff_attr = BuffAttr, goods_buff_exp_ratio = ExpRatio},
            case BuffAttr =/= OldBuffAttr of
                true ->
                    count_player_attr(NewPlayerStatus);
                false ->
                    NewPlayerStatus
            end;
        _ -> PlayerStatus
    end.

%% 直接从数据库移除某种类型的buff
db_remove_goods_buff(BuffType) ->
    db:execute(io_lib:format(?sql_delete_buff_by_type, [BuffType])).

%% 取玩家BUFF属性
get_buff_attr(PlayerBuff, SceneId) ->
    NowTime = utime:unixtime(),
    F = fun(Buff, AttrMaps) ->
                #ets_buff{scene = SceneList, end_time = EndTime, effect_list = EffectList} = Buff,
                VaildScene = ?IF(SceneList == [] orelse lists:member(SceneId, SceneList), true, false),
                IsExpired = ?IF(EndTime > 0 andalso NowTime >= EndTime, true, false),
                case VaildScene andalso not IsExpired of
                    true ->
                        case lists:keyfind(attr, 1, EffectList) of
                            {attr, Attr} ->
                                lib_player_attr:list_add_to_attr(Attr, AttrMaps);
                            _ -> AttrMaps
                        end;
                    false -> AttrMaps
                end
        end,
    lists:foldl(F, #{}, PlayerBuff).

%% 扫描ets，通知玩家进程移除buff
%% 定时器进程调用
refresh_buff() ->
    NowTime = utime:unixtime(),
    %% 查找有过期buff的玩家进程
    MatchSpec = ets:fun2ms(fun(#ets_buff{pid = Pid, end_time = EndTime} = Buff) when EndTime > 0 andalso NowTime >= EndTime -> Buff end),
    RefreshList = ets:select(?ETS_BUFF, MatchSpec),
    IdList = util:ulist([Pid || #ets_buff{pid = Pid} <- RefreshList]),
    do_batch(IdList, fun do_refresh_buff/1, 1, {10, 200}).

do_refresh_buff(PlayerId) ->
    lib_player:apply_cast(PlayerId, ?APPLY_CAST_SAVE, lib_goods_buff, refresh_player_buff, []).

%% 移除玩家身上过期的buff
refresh_player_buff(PlayerStatus) ->
    NowTime = utime:unixtime(),
    #player_status{id = PlayerId, scene = SceneId, player_buff = PlayerBuff, buff_attr = OldBuffAttr} = PlayerStatus,
    %% 移除玩家过期的buff
    db:execute(io_lib:format(?sql_delete_player_expired_buff, [PlayerId, NowTime])),
    F = fun(#ets_buff{end_time = EndTime} = BuffInfo, {NewPlayerBuff, NewPlayerStatus})
                when EndTime > 0 andalso EndTime < NowTime ->
                delete_ets_buff(BuffInfo),
                NewPlayerBuff2 = delete_player_buff(NewPlayerBuff, BuffInfo),
                NewPlayerStatus2 = after_buff_timeout_event(NewPlayerStatus, BuffInfo),
                {NewPlayerBuff2, NewPlayerStatus2};
           (_BuffInfo, {NewPlayerBuff, NewPlayerStatus}) -> {NewPlayerBuff, NewPlayerStatus}
    end,
    {NewPlayerBuff, NewPlayerStatus} = lists:foldl(F, {PlayerBuff, PlayerStatus}, PlayerBuff),
    ExpRatio = get_exp_buff(NewPlayerBuff, 0),
    send_buff_notice(PlayerId, SceneId, NewPlayerBuff),
    BuffAttr = get_buff_attr(NewPlayerBuff, SceneId),
    NewPlayerStatus2 = NewPlayerStatus#player_status{player_buff = NewPlayerBuff, buff_attr = BuffAttr, goods_buff_exp_ratio = ExpRatio},
    case BuffAttr =/= OldBuffAttr of
        true ->
            LastPlayerStatus = count_player_attr(NewPlayerStatus2);
        false ->
            LastPlayerStatus = NewPlayerStatus2
    end,
    {ok, LastPlayerStatus}.

do_batch([], _F, _Index, {_SleepNum, _SleepTime}) -> ok;
do_batch([Args|T], F, Index, {SleepNum, SleepTime}) ->
    case Index of
        SleepNum ->
            timer:sleep(SleepTime),
            NewIndex = 1;
        _ ->
            NewIndex = Index + 1
    end,
    F(Args),
    do_batch(T, F, NewIndex, {SleepNum, SleepTime}).

%% 物品buff失效后事件
after_buff_timeout_event(PS, BuffInfo) ->
    EventList = [is_vip_immune_timeout],
    case do_after_buff_timeout_event(EventList, PS, BuffInfo) of
        {no, NewPS} -> NewPS;
        {ok, NewPS} -> NewPS
    end.

do_after_buff_timeout_event([], PS, _BuffInfo) -> {no, PS};
do_after_buff_timeout_event([H|T], PS, BuffInfo) ->
    case do_after_buff_timeout_callback(H, PS, BuffInfo) of
        {ok, NewPS} -> {ok, NewPS};
        {next, NewPS} -> do_after_buff_timeout_event(T, NewPS, BuffInfo)
    end.

do_after_buff_timeout_callback(is_vip_immune_timeout, PS, #ets_buff{type = ?BUFF_VIP_IMMUNE}) ->
    IsBossScene = lib_scene:is_boss_scene(PS#player_status.scene),
    case IsBossScene  of
        true ->
            CanAttList = [], CannotBeattList = [],
            NewPS = PS#player_status{can_att_list = CanAttList, cannot_beatt_list = CannotBeattList},
            mod_scene_agent:update(NewPS, [{can_att_list, CanAttList}, {cannot_beatt_list, CannotBeattList}]),
            {next, NewPS};
        false -> {next, PS}
    end;

do_after_buff_timeout_callback(_Unkown, PS, _BuffInfo) ->
    {next, PS}.

%% 检查玩家BUFF
check_player_buff(PlayerStatus) ->
    #player_status{id = PlayerId, scene = SceneId, player_buff = PlayerBuff, buff_attr = OldBuffAttr} = PlayerStatus,
    case PlayerBuff of
        [] ->
            {ok, PlayerStatus};
        BuffList ->
            NowTime = utime:unixtime(),
            NewBuffList = [BuffInfo || BuffInfo <- BuffList, BuffInfo#ets_buff.end_time > NowTime orelse BuffInfo#ets_buff.end_time == 0,
                                       BuffInfo#ets_buff.scene =:= [] orelse lists:member(SceneId, BuffInfo#ets_buff.scene)],
            send_buff_notice(PlayerId, SceneId, NewBuffList),
            %% 属性变化
            BuffAttr = get_buff_attr(NewBuffList, SceneId),
            NewPlayerStatus = PlayerStatus#player_status{buff_attr = BuffAttr},
            case BuffAttr =/= OldBuffAttr of
                true ->
                    LastPlayerStatus = count_player_attr(NewPlayerStatus);
                false ->
                    LastPlayerStatus = NewPlayerStatus
            end,
            {ok, LastPlayerStatus}
    end.

count_player_attr(PlayerStatus) ->
    LastPlayerStatus = lib_player:count_player_attribute(PlayerStatus),
    lib_player:send_attribute_change_notify(LastPlayerStatus, ?NOTIFY_ATTR),
    mod_scene_agent:update(LastPlayerStatus, [{battle_attr, LastPlayerStatus#player_status.battle_attr}]),
    LastPlayerStatus.

%% 获取经验buff
% get_exp_buff(PS) ->
%     #player_status{player_buff = PlayerBuff} = PS,
%     get_exp_buff(PlayerBuff, 0).

get_exp_buff([], Acc) -> Acc;

get_exp_buff([#ets_buff{type = ?BUFF_EXP_KILL_MON, effect_list = EffectList}|PlayerBuff], Acc) ->
    case lists:keyfind(?BUFF_EFFECT_EXP_KILL_MON, 1, EffectList) of
        {_, Value} ->
            get_exp_buff(PlayerBuff, Acc + Value);
        _ ->
            get_exp_buff(PlayerBuff, Acc)
    end;

get_exp_buff([#ets_buff{effect_list = EffectList}|PlayerBuff], Acc) ->
    case lists:keyfind(attr, 1, EffectList) of
        {_, AttrList} ->
            case lists:keyfind(?EXP_ADD_RATIO, 1, AttrList) of
                {_, Value} ->
                    get_exp_buff(PlayerBuff, Acc + Value/?RATIO_MAX);
                _ ->
                    get_exp_buff(PlayerBuff, Acc)
            end;
        _ ->
            get_exp_buff(PlayerBuff, Acc)
    end.

get_goods_drop_buff(PS)->
    get_goods_buff_value(PS, ?BUFF_DROP_KILL_MON).

get_goods_buff_value(PS, BuffType)->
    Buff1 = get_player_buff(PS#player_status.player_buff, BuffType),
    BuffExpMon = ?IF(Buff1==false, #ets_buff{}, Buff1),
    Value1 = exp_buff_helper(BuffExpMon#ets_buff.effect_list, 0),
    Value1.

exp_buff_helper([], Value) -> Value;
exp_buff_helper([{?BUFF_EFFECT_EXP_KILL_MON, Value}|T], Acc) ->
    exp_buff_helper(T, Value + Acc);
exp_buff_helper([{?BUFF_EFFECT_DROP_KILL_MON, Value}|T], Acc) ->
    exp_buff_helper(T, Value + Acc);
exp_buff_helper([{?BUFF_EFFECT_VIP_IMMUNE, Value}|T], Acc) ->
    exp_buff_helper(T, Value + Acc);
exp_buff_helper([{_Key, _Value}|T], Acc) ->
    exp_buff_helper(T, Acc).

%% 添加BUFF状态
add_player_buff(PlayerBuff, PlayerId, Type, GoodsTypeId, EffectList, EndTime, Scene) ->
    F = fun() ->
                Sql = io_lib:format(?sql_insert_buff, [PlayerId, Type, GoodsTypeId, util:term_to_string(EffectList), EndTime, util:term_to_string(Scene)]),
                db:execute(Sql),
                Sql1 = io_lib:format(?sql_select_buff_last_id, [PlayerId, Type, GoodsTypeId]),
                LastId = db:get_one(Sql1),
                {ok, LastId}
        end,
    case catch db:transaction(F) of
        {ok, LastId} ->
            NewBuffInfo = #ets_buff{id = LastId, pid = PlayerId, type = Type, goods_id = GoodsTypeId, effect_list = EffectList, end_time = EndTime, scene = Scene},
            NewPlayerBuff = store_player_buff(PlayerBuff, NewBuffInfo),
            {NewBuffInfo, NewPlayerBuff};
        _Err ->
            ?ERR("add_player_buff err:~p", [_Err]),
            {false, PlayerBuff}
    end.

%% 修改BUFF状态
mod_buff(PlayerBuff, BuffInfo, GoodsTypeId, EffectList, EndTime, Scene) ->
    Sql = io_lib:format(?sql_update_buff, [GoodsTypeId, util:term_to_string(EffectList), EndTime, util:term_to_string(Scene), BuffInfo#ets_buff.id]),
    db:execute(Sql),
    NewBuffInfo = BuffInfo#ets_buff{goods_id = GoodsTypeId, effect_list = EffectList, end_time = EndTime, scene = Scene},
    NewPlayerBuff = store_player_buff(PlayerBuff, NewBuffInfo),
    {NewBuffInfo, NewPlayerBuff}.

%% 跨服团战buff特殊处理
mod_camp_war_buff(PlayerBuff, BuffInfo, _GoodsTypeId, EffectList, _LimitScene) ->
    #ets_buff{effect_list = OldEffectList} = BuffInfo,
    case lists:keyfind(attr, 1, OldEffectList) of
        {_, AttrList} -> ok;
        _   -> AttrList = []
    end,
    case lists:keyfind(attr, 1, EffectList) of
        {_, NewAttrList} -> ok;
        _ -> NewAttrList = []
    end,
    NewAttrList1 = lists:foldl(fun({AttrId, Val}, Acc) ->
        case lists:keyfind(AttrId, 1, Acc) of
            {_, OldVal} ->
                lists:keystore(AttrId, 1, Acc, {AttrId, OldVal+Val});
            _ ->
                lists:keystore(AttrId, 1, Acc, {AttrId, Val})
        end
    end, AttrList, NewAttrList),
    NewEffectList = lists:keystore(attr, 1, OldEffectList, {attr, NewAttrList1}),
    NewBuffInfo = BuffInfo#ets_buff{effect_list = NewEffectList},
    Sql = io_lib:format(<<"UPDATE `buff` set effect_list = '~s' WHERE `id` = ~p">>, [util:term_to_string(NewEffectList), BuffInfo#ets_buff.id]),
    db:execute(Sql),
    NewPlayerBuff = store_player_buff(PlayerBuff, NewBuffInfo),
    {NewBuffInfo, NewPlayerBuff}.

%% ------------------------- local function -------------------------
get_player_buff(PlayerBuff, BuffType) ->
    get_player_buff(PlayerBuff, BuffType, 0).

get_player_buff(PlayerBuff, ?BUFF_CAMP_WAR, GoodsTypeId) ->
    case lists:filter(fun(#ets_buff{type = BuffType, goods_id = GoodsId}) ->
        BuffType == ?BUFF_CAMP_WAR andalso GoodsId == GoodsTypeId
    end, PlayerBuff) of
        [EtsBuff = #ets_buff{}|_] ->
            EtsBuff;
        _ ->
            false
    end;
get_player_buff(PlayerBuff, BuffType, _GoodsTypeId) ->
    lists:keyfind(BuffType, #ets_buff.type, PlayerBuff).

get_player_all_buff(PlayerBuff, BuffType) ->
    lists:filter(fun(#ets_buff{type = BuffTypeX}) ->
        BuffTypeX == BuffType
    end, PlayerBuff).

store_player_buff(PlayerBuff, BuffInfo) ->
    lists:keystore(BuffInfo#ets_buff.id, #ets_buff.id, PlayerBuff, BuffInfo).

delete_player_buff(PlayerBuff, BuffInfo) ->
    lists:keydelete(BuffInfo#ets_buff.id, #ets_buff.id, PlayerBuff).

save_ets_buff(NewBuff) ->
    ets:insert(?ETS_BUFF, NewBuff).

delete_ets_buff(NewBuff) ->
    ets:delete(?ETS_BUFF, NewBuff#ets_buff.id).

%% 客户端请求和发送BUFF状态通知
send_buff_notice(PlayerStatus) ->
    #player_status{id = RoleId, scene = SceneId, player_buff = PlayerBuff} = PlayerStatus,
    send_buff_notice(RoleId, SceneId, PlayerBuff).

%% 除了物品buff外的，其他功能的显示
send_buff_notice(RoleId, SceneId, PlayerBuff) ->
    NowTime  = utime:unixtime(),
    F = fun(BuffInfo, Acc) ->
                #ets_buff{
                   goods_id = GoodsTypeId,
                   type = BuffType,
                   end_time = EndTime,
                   effect_list = EffectList
                  } = BuffInfo,
                LeftTime = max(EndTime - NowTime, 0),
                case data_goods_effect:get(GoodsTypeId) of
                    #goods_effect{
                       time = SingleTime,
                       limit_scene = LimitScene
                      } ->
                        case BuffType =< 16#FF andalso (LimitScene == [] orelse lists:member(SceneId, LimitScene)) of
                            true ->
                                [{GoodsTypeId, BuffType, util:term_to_string(EffectList), LeftTime, SingleTime}| Acc];
                            false -> Acc
                        end;
                    _ when BuffType =< 16#FF ->
                        [{GoodsTypeId, BuffType, util:term_to_string(EffectList), LeftTime, 0}| Acc];
                    _ ->
                        Acc
                end
        end,
    PackList = lists:foldl(F, [], PlayerBuff),
    {ok, BinData} = pt_150:write(15055, [RoleId, PackList]),
    lib_server_send:send_to_uid(RoleId, BinData).
