%%%-------------------------------------------------------------------
%%% @author liushl
%%% @doc
%%%     所有物品保存于进程字典
%%%     物品对外接口
%%% @end
%%%-------------------------------------------------------------------
-module(lib_goods_api).
-author("liushl").
-include("common.hrl").
-include("def_goods.hrl").
-include("sql_goods.hrl").
-include("goods.hrl").
-include("server.hrl").
-include("scene.hrl").
-include("predefine.hrl").
-include("figure.hrl").
-include("guild.hrl").
-include("errcode.hrl").
-include("def_event.hrl").
-include("rec_event.hrl").
-include("def_module.hrl").
-include("def_fun.hrl").
-include("goods_extra.hrl").
%% API
%% 建议对外接口
-export([
    make_produce/6
    ,send_reward/2                  %% 发送奖励
    ,send_reward/3                  %% 发送奖励
    ,send_reward/4                  %% 发送奖励
    ,send_reward/5                  %% 发送奖励
    ,send_reward_with_mail/2        %% 发送奖励[玩家背包已满就通过邮件]
    ,send_reward_with_mail/3        %% 发送奖励[玩家背包已满就通过邮件]
    ,send_reward_with_mail/4
    ,after_send_reward/2
    ,give_goods_with_loc/3          %% 将物品发放到指定背包
    ,give_goods_with_loc/5          %% 将物品发放到指定背包
    ,do_send_mail/4
    ,send_reward_by_id/2
    ,send_reward_by_id/3            %% 异步发放奖励
    ,send_reward_by_id/4            %% 异步发放奖励
    ,send_reward_by_id/5            %% 异步发放奖励
    ,add_money/3
    ,add_money/5                    %% 给玩家发钱，单种货币
    ,add_currency/3                 %% 给玩家发虚拟货币，单种

    ,delete_one_by_id/5             %% 根据物品Id删除物品并刷新客户端以及做物品日志
    ,delete_more_by_list/3          %% 根据物品Id列表删除物品并刷新客户端以及做物品日志

    ,cost_object_list/4             %% 扣除消耗列表object_list
    ,cost_object_list_with_check/4  %% 扣除消耗列表object_list
    ,cost_objects_with_auto_buy/4   %% 扣除消耗列表object_list 不够尝试将其转换成货币
    ,cost_money/5                   %% 扣取单种货币，写入消耗日志

    ,can_give_goods/1               %% 检查物品是否可以加入到背包
    ,can_give_goods/2               %% 检查物品是否可以加入到背包
    ,can_give_goods_self/1
    ,check_object_list/2            %% 判断是否满足消耗列表
    ,check_object_list/3            %% 判断是否满足消耗列表
    ,check_object_list_with_auto_buy/2
                                    %% 判断是否满足消耗列表 尝试将其转换成货币
    ,get_goods_num/2                %% 查询道具数量
    ,get_goods_num/3                %% 查询道具数量
    ,get_cell_num/1                 %% 获取空格子数量
    ,get_cell_num/2                 %% 获取空格子数量
    ,get_cell_num_list/1            %% 获取空格子数量列表
    ,get_need_cell_num/2            %% 获得物品列表需要多少格子数
    ,get_goods_info/1               %% 获取物品信息
    ,get_goods_info/2               %% 获取物品信息
    ,get_goods_info/3
    ,get_goods_info_by_id_type/2
    ,get_goods_info_by_id_type/3
    ,get_currency/2                 %% 查询特殊货币数量

    ,move_a2b/4                     %% 移动物品从A背包到B背包
    ,move_more_a2b/3                %% 移动物品从A背包到B背包，直到B背包没有空格子

    ,notify_client/3                %% 通知前端更新物品信息、增加物品
    ,notify_client_num/2            %% 通知前端更新物品数量 (若物品数量为0,即是要删除的物品)
    ,send_tv_tip/2                  %% 系统提示获得物品
    ,send_tv_tip/3                  %% 系统提示获得物品
]).
-export([
    trans_goods_format/1            %% 转换格式
    ,trans_object_list/1            %% 转换格式
    ,make_reward_unique/1           %% 物品唯一处理
    ,calc_auto_buy_list/2           %% 计算自动购买的物品
    ,get_first_object_name/1        %% 获得物品列表第一个物品的名字
    ,make_goods_tv_format_3/1
    ,get_id_pid/1
    ,get_goods_bind/1
]).
%% 秘籍接口，其他地方勿用
-export([
    set_goods_expired_time/3
    , set_goods_expired_time_core/3
]).

make_produce(ProduceType, ProduceSubtype, Title, Content, Reward, ShowTips) ->
    #produce{type = ProduceType, subtype = ProduceSubtype, title = Title, content = Content, reward = Reward, show_tips = ShowTips}.

%% ---------------------------------------------------------------------------
%% @doc 发送奖励 !!! 物品背包不足会发送失败
-spec send_reward(PS, RewardList, ProduceType, SubType) -> Return when
    PS          :: #player_status{}, %% 玩家记录
    RewardList  :: list(),
    %% 目前物品格式只有以下几种
    %% RewardList = [{?TYPE_GOODS, GoodsTypeId, GoodsNum }, ...]
    %% RewardList = [{?TYPE_BIND_GOODS, GoodsTypeId, GoodsNum }, ...]
    %% RewardList = [{OtherType, GoodsTypeId, GoodsNum }, ...]
    %% RewardList = [{?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, AttrList}, ...]
    ProduceType :: atom(),      %% 需要后台定义"产出消耗类型--产出类型"
    SubType     :: integer(),   %% 子类型,主要是怪物的产出
    Return      :: #player_status{}. %% 玩家记录
%% ---------------------------------------------------------------------------
send_reward(PS, RewardList, ProduceType) ->
    send_reward(PS, #produce{type = ProduceType, subtype = 0, reward = RewardList, remark = ""}).

send_reward(PS, RewardList, ProduceType, SubType) ->
    send_reward(PS, #produce{type = ProduceType, subtype = SubType, reward = RewardList, remark = ""}).

send_reward(PS, RewardList, ProduceType, SubType, ShowTips) ->
    send_reward(PS, #produce{type = ProduceType, subtype = SubType, reward = RewardList, remark = "", show_tips = ShowTips}).

send_reward(PS, #produce{reward = RewardList} = Produce) ->
    Player1 = goods_add:send_reward_helper(PS, RewardList, [], Produce),
    {ok, Player2} = offline_hosting:host_behavior_reward(Player1, RewardList),
    %% 物品进入背包后检测是否需要将整件分解成碎片
    Pid = misc:get_player_process(PS#player_status.id),
    if Pid == self() ->
            after_send_reward(Player2, RewardList);
        true ->
            lib_player:apply_cast(PS#player_status.id, ?APPLY_CAST_STATUS, ?MODULE, after_send_reward, [RewardList]),
            Player2
    end.

after_send_reward(Player, RewardList) ->
    AllFashionId = data_fashion:get_all_fashion_id(),
    F = fun(Reward, TList) ->
        case Reward of
            {_, RewardId, _} -> ok;
            {_, RewardId, _, _} -> ok;
            _ -> RewardId = 0
        end,
        IsFashion = lists:member(RewardId, AllFashionId),
        if
            IsFashion ->
                case lists:keyfind(?GOODS_TYPE_FASHION, 1, TList) of
                    {_, RewardIdList} -> [{?GOODS_TYPE_FASHION, [RewardId|lists:delete(RewardId, RewardIdList)]}|TList];
                    _ -> [{?GOODS_TYPE_FASHION, [RewardId]}|TList]
                end;
            true ->
                TList
        end end,
    DeComposeIdList = lists:foldl(F, [], RewardList),
    case DeComposeIdList of
        [] -> Player;
        _  -> check_decompose_after(Player, DeComposeIdList)
    end.

check_decompose_after(Player, DeComposeIdList) ->
    %% 对时装功能进行检测
    {IsFashionDe, GoodsList} = lib_fashion_check:decompose_check(Player, DeComposeIdList),
    IsFashionDe == true andalso pp_goods:handle(15019, Player, [GoodsList]),
    Player.

%% ---------------------------------------------------------------------------
%% @doc 发送奖励[带邮件的]
%% @param Produce 产出record
%% 目前物品格式只有以下几种
%% RewardList = [{?TYPE_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{?TYPE_BIND_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{OtherType, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, AttrList}, ...]
send_reward_with_mail(RoleId, Produce) when is_integer(RoleId) ->
    send_reward_with_mail(RoleId, Produce, ?MOD_NONE);
send_reward_with_mail(Player, Produce) ->
    send_reward_with_mail(Player, Produce, ?MOD_NONE).

send_reward_with_mail(RoleId, Award, Type, Remark) when is_integer(RoleId)  ->
    send_reward_with_mail(RoleId, #produce{reward = Award, type = Type, remark = Remark});
send_reward_with_mail(Player, Award, Type, Remark) ->
    send_reward_with_mail(Player, #produce{reward = Award, type = Type, remark = Remark}).

send_reward_with_mail(RoleId, Produce, ModuleId) when is_integer(RoleId) ->
    UniqueReward = lib_goods_util:merge_same_goods(Produce#produce.reward),
    NewProduce = Produce#produce{reward = UniqueReward},
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) of
        true  ->
            lib_player:apply_cast(RoleId, ?APPLY_CAST_SAVE, lib_goods_api, send_reward_with_mail, [NewProduce]);
        false ->
            do_send_mail(RoleId, NewProduce, ModuleId, offline)
    end;
send_reward_with_mail(Player, Produce, ModuleId) ->
    PlayerId = Player#player_status.id,
    #produce{reward = GoodsList} = Produce,
    {EnoughCellGoods, NotEnoughGoods} = split_enough_cell_goods(Player, GoodsList),
    NotEnoughGoods =/= [] andalso
        do_send_mail(PlayerId, Produce#produce{reward = NotEnoughGoods}, ModuleId, online),
    Player1 = if
                     EnoughCellGoods =/= [] ->
                         NewProduce = Produce#produce{reward = EnoughCellGoods},
                         send_reward(Player, NewProduce);
                     true -> Player
                 end,
    {ok, Player2} = offline_hosting:host_behavior_reward(Player1, NotEnoughGoods),
    {ok, Player2}.
do_send_mail(RoleId, Produce, _ModuleId, Online) ->
    #produce{off_title = OffTitle, off_content = OffContent, module = Mod, module_sub = ModSub,
        reward = Reward, title = Title, content = Content} = Produce,
    case _ModuleId > 0 of
        true  ->
            ModuleId = _ModuleId, ModuleSubId = 0;
        false ->
            ModuleId = Mod, ModuleSubId = ModSub
    end,
    if
        OffTitle =/= "" andalso OffContent =/= "" ->
            lib_mail_api:send_sys_mail([RoleId], ModuleId, ModuleSubId, OffTitle, OffContent, Reward);
        Title =/= "" andalso Content =/= "" ->
            lib_mail_api:send_sys_mail([RoleId], ModuleId, ModuleSubId, Title, Content, Reward);
        Online =:= offline ->
            lib_mail_api:send_sys_mail([RoleId], ModuleId, ModuleSubId, utext:get(13), utext:get(14), Reward);
        true ->
            lib_mail_api:send_sys_mail([RoleId], ModuleId, ModuleSubId, utext:get(11), utext:get(12), Reward)
    end.

split_enough_cell_goods(PlayerInfo, GoodsTypeList) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    BagCellMaps = lib_goods_util:get_null_cell_num_maps(GoodsStatus),
    split_enough_cell_goods_helper(PlayerInfo#player_status.id, GoodsStatus, BagCellMaps, [], [], GoodsTypeList).
split_enough_cell_goods_helper(_PlayerId, _GoodsStatus, _BagCellMaps, EnoughCellGoods, NotEnoughCellGoods, []) ->
    {EnoughCellGoods, NotEnoughCellGoods};
split_enough_cell_goods_helper(PlayerId, GoodsStatus, BagCellMaps, EnoughCellGoods, NotEnoughCellGoods, [T|H]) ->
    case get_need_cell_num(PlayerId, GoodsStatus, [T]) of
        [] -> split_enough_cell_goods_helper(PlayerId, GoodsStatus, BagCellMaps, [T|EnoughCellGoods], NotEnoughCellGoods, H);
        [{Location, NeedCell}] ->
            HasCell = maps:get(Location, BagCellMaps, 0),
            case HasCell >= NeedCell of
                true  ->
                    NewEnoughCellGoods = [T|EnoughCellGoods],
                    NewBagCellMaps = maps:put(Location, HasCell - NeedCell, BagCellMaps),
                    split_enough_cell_goods_helper(PlayerId, GoodsStatus, NewBagCellMaps, NewEnoughCellGoods, NotEnoughCellGoods, H);
                false ->
                    NewNotEnoughCellGoods = [T|NotEnoughCellGoods],
                    split_enough_cell_goods_helper(PlayerId, GoodsStatus, BagCellMaps, EnoughCellGoods, NewNotEnoughCellGoods, H)
            end
    end.
%% ---------------------------------------------------------------------------
%% @doc 异步发放奖励
%% 目前物品格式只有以下几种
%% RewardList = [{?TYPE_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{?TYPE_BIND_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{OtherType, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, AttrList}, ...]
%% @return ok | false
send_reward_by_id(RewardList, ProduceType, Id) ->
    send_reward_by_id(RewardList, ProduceType, 0, Id).
send_reward_by_id(RewardList, ProduceType, SubType, Id) ->
    Produce = #produce{type = ProduceType, subtype = SubType, reward = RewardList, remark = ""},
    send_reward_by_id(Produce, Id).

send_reward_by_id(RewardList, ProduceType, SubType, Id, ShowTips) ->
    Produce = #produce{type = ProduceType, subtype = SubType, reward = RewardList, remark = "", show_tips = ShowTips},
    send_reward_by_id(Produce, Id).

send_reward_by_id(Produce, Id) ->
    Pid = misc:get_player_process(Id),
    case misc:is_process_alive(Pid) of
        true  -> gen_server:cast(Pid, {'send_reward', Produce});
        false -> do_send_mail(Id, Produce, 0, offline)
    end.

%% ---------------------------------------------------------------------------
%% @doc 给玩家发钱，单种货币
add_money(PlayerStatus, Amount, Type) ->
    add_money(PlayerStatus, Amount, Type, none, "").
add_money(PlayerStatus, Amount, MoneyType, ProduceType, About) ->
    goods_add:add_money(PlayerStatus, Amount, MoneyType, ProduceType, About).

%% ---------------------------------------------------------------------------
%% @doc 给玩家发虚拟货币，单种
add_currency(PS, CurrencyId, Value) ->
    goods_add:add_currency(PS, CurrencyId, Value).

%% ---------------------------------------------------------------------------
%% @doc 唯一处理(不修改格式)
%% 推荐使用 lib_goods_util:merge_same_goods
%% RewardList :: [{Type, GoodsId, Num}]
make_reward_unique(RewardList) ->
    List = make_reward_unique(RewardList, []),
    RewardList2 = make_reward_unique_format(List, []),
    RewardList2.

make_reward_unique_format([], AwardList) ->
    AwardList;
make_reward_unique_format([{{Type, GoodsTypeId}, GoodsNum} | T], AwardList) ->
    AwardList2 = [{Type, GoodsTypeId, GoodsNum} | AwardList],
    make_reward_unique_format(T, AwardList2);
make_reward_unique_format([{{Type, GoodsTypeId, Args}, GoodsNum} | T], AwardList) ->
    AwardList2 = [{Type, GoodsTypeId, GoodsNum, Args} | AwardList],
    make_reward_unique_format(T, AwardList2);
make_reward_unique_format([_ | T], AwardList) ->
    make_reward_unique_format(T, AwardList).


make_reward_unique([], ObjectList) -> ObjectList;
make_reward_unique([H|T], ObjectList) ->
    case H of
        {Type, GoodsTypeId, GoodsNum} when is_integer(Type) ->
            NewObjectList = case lists:keytake({Type, GoodsTypeId}, 1, ObjectList) of
                {value, {_, Num}, RestList} ->
                    [{{Type, GoodsTypeId}, GoodsNum+Num} | RestList];
                false ->
                    [{{Type, GoodsTypeId}, GoodsNum} | ObjectList]
            end,
            make_reward_unique(T, NewObjectList);
        {Type, GoodsTypeId, GoodsNum, Args} when is_integer(Type) ->
            NewObjectList = case lists:keytake({Type, GoodsTypeId, Args}, 1, ObjectList) of
                {value, {_, Num}, RestList} ->
                    [{{Type, GoodsTypeId, Args}, GoodsNum+Num} | RestList];
                false ->
                    [{{Type, GoodsTypeId, Args}, GoodsNum} | ObjectList]
            end,
            make_reward_unique(T, NewObjectList);
        _ ->
            make_reward_unique(T, ObjectList)
    end.

%% 转换格式
%% GoodsList = [{GoodsTypeId, GoodsNum }, ...]
%% GoodsList = [{goods, GoodsTypeId, GoodsNum }, ...]
%% GoodsList = [{goods, GoodsTypeId, GoodsNum, Bind(0非绑1绑定)}, ...]
%% GoodsList = [{goods_attr, GoodsTypeId, GoodsNum, AttrList}, ...]
%% GoodsList = [{?TYPE_GOODS, GoodsTypeId, GoodsNum}, ...]
trans_goods_format(RawGoodsList) ->
    trans_goods_format(RawGoodsList, []).
trans_goods_format([], GoodsList) -> GoodsList;
trans_goods_format([H|T], GoodsList) ->
    case H of
        {info, GoodsInfo } ->
            trans_goods_format(T, [GoodsInfo|GoodsList]);
        {GoodsTypeId, GoodsNum} ->
            NewGoodsList = do_trans_goods(GoodsTypeId, GoodsNum, GoodsList),
            trans_goods_format(T, NewGoodsList);
        {?TYPE_GOODS, GoodsTypeId, GoodsNum} ->
            NewGoodsList = do_trans_goods(GoodsTypeId, GoodsNum, GoodsList),
            trans_goods_format(T, NewGoodsList);
        {?TYPE_BGOLD, GoodsTypeId, GoodsNum} ->
            NewGoodsList = do_trans_goods(GoodsTypeId, GoodsNum, GoodsList),
            trans_goods_format(T, NewGoodsList);
        {?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, _AttrList} ->
            NewGoodsList = do_trans_goods(GoodsTypeId, GoodsNum, GoodsList),
            trans_goods_format(T, NewGoodsList);
        {goods, GoodsTypeId, GoodsNum} ->
            NewGoodsList = do_trans_goods(GoodsTypeId, GoodsNum, GoodsList),
            trans_goods_format(T, NewGoodsList);
        {goods, GoodsTypeId, GoodsNum, _Bind} ->
            NewGoodsList = do_trans_goods(GoodsTypeId, GoodsNum, GoodsList),
            trans_goods_format(T, NewGoodsList);
        {goods, GoodsTypeId, GoodsNum, _Bind, _ExpireTime, _LockTime} ->
            NewGoodsList = do_trans_goods(GoodsTypeId, GoodsNum, GoodsList),
            trans_goods_format(T, NewGoodsList);
        {goods_attr, GoodsTypeId, GoodsNum, _AttrList} ->
            NewGoodsList = do_trans_goods(GoodsTypeId, GoodsNum, GoodsList),
            trans_goods_format(T, NewGoodsList)
    end.
do_trans_goods(GoodsTypeId, GoodsNum, GoodsList) ->
    case lists:keyfind(GoodsTypeId, 1, GoodsList) of
        {_, Num} -> [{GoodsTypeId, GoodsNum+Num}| lists:keydelete(GoodsTypeId, 1, GoodsList)] ;
        false    -> [{GoodsTypeId, GoodsNum} | GoodsList]
    end.

%% 扣除的时候 转换格式
%% ObjectList :: [{Type, GoodsId, Num}]
trans_object_list(ObjectList) ->
    trans_object_list(ObjectList, []).
trans_object_list([], ObjectList) -> ObjectList;
trans_object_list([H|T], ObjectList) ->
    case H of
        {Type, GoodsTypeId, GoodsNum} ->
            NewGoodsList = case lists:keyfind({Type, GoodsTypeId}, 1, ObjectList) of
                               {_, Num} -> [{ {Type, GoodsTypeId}, GoodsNum+Num}| lists:keydelete({Type, GoodsTypeId}, 1, ObjectList)];
                               false    -> [{ {Type, GoodsTypeId}, GoodsNum} | ObjectList]
                           end,
            trans_object_list(T, NewGoodsList);
        {GoodsTypeId, GoodsNum} ->
            NewGoodsList = case lists:keyfind({?TYPE_GOODS, GoodsTypeId}, 1, ObjectList) of
                               {_, Num} -> [{ {?TYPE_GOODS, GoodsTypeId}, GoodsNum+Num}| lists:keydelete({?TYPE_GOODS, GoodsTypeId}, 1, ObjectList)];
                               false    -> [{ {?TYPE_GOODS, GoodsTypeId}, GoodsNum} | ObjectList]
                           end,
            trans_object_list(T, NewGoodsList);
        _ ->
            trans_object_list(T, ObjectList)
    end.

get_id_pid(PlayerInfo) ->
    PlayerId = case PlayerInfo of
                   Id when is_integer(Id) ->
                       Id;
                   PS when is_record(PS, player_status) ->
                       PS#player_status.id
               end,
    Pid = misc:get_player_process(PlayerId),
    {PlayerId, Pid}.

get_goods_bind(GoodsTypeId) ->
    case data_goods_type:get(GoodsTypeId) of
        #ets_goods_type{bind = ?UNBIND}-> ?UNBIND;
        #ets_goods_type{bind = ?BIND}-> ?BIND;
        _ ->
            % ?ERR("get_goods_bind bind error:~n~p~n", [GoodsTypeId]),
            ?BIND
    end.
%% ---------------------------------------------------------------------------
%% @doc 满足条件才扣除
%% 目前物品格式只有以下几种
%% RewardList = [{?TYPE_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{OtherType, GoodsTypeId, GoodsNum }, ...]
%% @return {true, #player_status{},ObjectList} | {false, integer(), #player_status{}}
cost_object_list_with_check(PS, ObjectList, Type, About) ->
    case goods_del:check_object_list(PS, ObjectList, Type) of
        {false, ErrorCode} -> {false, ErrorCode, PS};
        true ->
            case goods_del:cost_object_list(PS, ObjectList, Type, About) of
                {true, NewPS} ->
                    {true, NewPS, ObjectList};
                {false, ErrorCode, NewPS} ->
                    {false, ErrorCode, NewPS}
            end
    end.

cost_objects_with_auto_buy(PS, ObjectList, Type, About) ->
    case check_object_list(PS, ObjectList) of
        {false, ErrorCode} ->
            case ErrorCode =:= ?GOODS_NOT_ENOUGH of
                true ->
                    case calc_auto_buy_list(PS, ObjectList) of
                        {ok, NewObjectList} ->
                            case cost_object_list_with_check(PS, NewObjectList, Type, About) of
                                {true, NewPS, _} -> {true, NewPS, NewObjectList};
                                {false, ErrorCode1, NewPS} -> {false, ErrorCode1, NewPS}
                            end;
                        _A ->
                            {false, ?GOODS_NOT_ENOUGH, PS}
                    end;
                _ ->
                    {false, ErrorCode, PS}
            end;
        true ->
            case cost_object_list(PS, ObjectList, Type, About) of
                {true, NewPS} -> {true, NewPS, ObjectList};
                {false, ErrorCode, NewPS} -> {false, ErrorCode, NewPS}
            end
    end.

%% ---------------------------------------------------------------------------
%% @doc 扣除消耗列表object_list 防止错误调用, 强制改为调用cost_object_list_with_check(PS, ObjectList, Type, About)
-spec cost_object_list(PS, ObjectList, Type, About) ->  Return when
    PS             :: #player_status{},         %% #player_status{}
    ObjectList     :: list(),                   %% object_list :: [{type,goods_id,num}]
    Type           :: atom(),                   %% 物品|货币消耗类型：需要后台定义"消耗类型"
    About          :: string(),                 %% 消耗描述
    Return         :: {true, #player_status{}}|{false, integer(), #player_status{}}. %% 返回：{true, PS}|{false,错误码,PS}
%% ---------------------------------------------------------------------------
cost_object_list(PS, ObjectList, Type, About) ->
    goods_del:cost_object_list(PS, ObjectList, Type, About).

%% ---------------------------------------------------------------------------
%% @doc 判断是否满足消耗列表
-spec check_object_list(PS, ObjectList) ->  Return when
    PS             :: #player_status{},         %% #player_status{}
    ObjectList     :: list(),                   %% object_list :: [{type,goods_id,num}]
    Return         :: true | {false, integer()}.%% 返回：true|{false,错误码}
%% ---------------------------------------------------------------------------
check_object_list(PS, ObjectList) ->
    goods_del:check_object_list(PS, ObjectList).

check_object_list(PS, ObjectList, ConsumeType) ->
    goods_del:check_object_list(PS, ObjectList, ConsumeType).

check_object_list_with_auto_buy(PS,  ObjectList) ->
    case goods_del:check_object_list(PS, ObjectList) of
        {false, ErrorCode} ->
            case ErrorCode =:= ?GOODS_NOT_ENOUGH of
                true ->
                    case calc_auto_buy_list(PS, ObjectList) of
                        {ok, NewObjectList} ->
                            goods_del:check_object_list(PS, NewObjectList);
                        _A ->
                            {false, ErrorCode}
                    end;
                _ ->
                    {false, ErrorCode}
            end;
        Res ->
            Res
    end.

%%% 计算自动购买的物品
calc_auto_buy_list(PS, ObjectList) ->
    UniqueList = [{Type, GoodsTypeId, GoodsNum}||{ {Type, GoodsTypeId}, GoodsNum} <- trans_object_list(ObjectList)],
    {EnoughGoods, NotEnoughGoods}
        = lists:foldl(fun
                          ({GoodsType, GoodsId, Num} = G, {Enough, NotEnough})
                           when GoodsType == ?TYPE_GOODS orelse GoodsType == ?TYPE_BIND_GOODS ->
                              [{_, BagNum}] = get_goods_num(PS, [GoodsId]),
                              if
                                  BagNum =:= 0 ->
                                      {Enough, [G|NotEnough]};
                                  BagNum < Num ->
                                      {[{GoodsType, GoodsId, BagNum}|Enough], [{GoodsType, GoodsId, Num - BagNum}|NotEnough]};
                                  true ->
                                      {[G|Enough], NotEnough}
                              end;
                          (G, {Enough, NotEnough}) ->
                              {[G|Enough], NotEnough}
                      end, {[], []}, UniqueList),
    case calc_price(PS, NotEnoughGoods, [], 0) of
        {ok, PriceList} ->
            {ok, make_reward_unique(PriceList ++ EnoughGoods)};
        _ ->
            error
    end.

calc_price(PS, [{_GoodsType, GoodsId, Num}|T], Acc, TempBGOld)
 when _GoodsType == ?TYPE_GOODS orelse _GoodsType == ?TYPE_BIND_GOODS ->
    case data_goods:get_goods_buy_price(GoodsId) of
        {0, 0} -> error;
        {?TYPE_BGOLD, Price} -> %% 绑玉特殊处理
            LeftBGold = PS#player_status.bgold - TempBGOld,
            case LeftBGold > 0 of
                true  ->
                    Need = Price * Num,
                    BGPrice = min(Need, LeftBGold),
                    NewAcc =
                    case Need > BGPrice of
                        true  -> [{?TYPE_BGOLD, 0, BGPrice}, {?TYPE_GOLD, 0, Need - BGPrice}|Acc];
                        false -> [{?TYPE_BGOLD, 0, BGPrice}|Acc]
                    end,
                    calc_price(PS, T, NewAcc, TempBGOld + BGPrice);
                false ->
                    calc_price(PS, T, [{?TYPE_GOLD, 0, Price * Num}|Acc], TempBGOld)
            end;
        {PriceType, Price} -> calc_price(PS, T, [{PriceType, 0, Price * Num}|Acc], TempBGOld)
    end;

calc_price(_, [], Acc, _) -> {ok, Acc}.

%% ---------------------------------------------------------------------------
%% @doc 扣取货币，写入消耗日志
-spec cost_money(Status, MoneyType, Money, ConsumeType, ABout) ->  Return when
    Status      :: #player_status{},    %% 玩家记录
    MoneyType   :: atom(),              %% coin | gold
    Money       :: integer(),           %% 数量
    ConsumeType :: atom(),              %% 消费类型
    ABout       :: list(),              %% [] | string 备注
    Return      :: {Res::integer, #player_status{} }. %% {错误码(0:货币不足，1成功), 玩家记录}
%% ---------------------------------------------------------------------------
cost_money(Status, MoneyType, Money, ConsumeType, ABout) ->
    case goods_del:is_enough_money(Status, Money, MoneyType) of
        true ->
            NewPS = goods_del:cost_money(Status, Money, MoneyType, ConsumeType, ABout),
            {1, NewPS};
        false ->
            {0, Status}
    end.

%% ---------------------------------------------------------------------------
%% @doc 根据物品Id删除物品并刷新客户端以及做物品日志
-spec delete_one_by_id(PlayerInfo, GoodsId, GoodsTypeId, DeleteNum, Type) -> Res when
    PlayerInfo  :: integer() | #player_status{},    %% 玩家Id或者玩家记录
    GoodsId     :: integer(),                       %% 物品唯一Id
    GoodsTypeId     :: integer(),                   %% 物品类型Id
    DeleteNum   :: integer(),                       %% 要删除的数量
    Type        :: atom(),                          %% 物品消耗类型
    %% 需要后台定义"产出消耗类型--消耗类型"
    Res         :: integer().                       %% 结果码 0:失败|1:成功|2:物品不存在
%% ---------------------------------------------------------------------------
delete_one_by_id(PlayerInfo, GoodsId, GoodsTypeId, DeleteNum, Type) when DeleteNum>0 ->
    {Res, _GoodsInfo} = goods_del:delete_by_id(GoodsId, GoodsTypeId, DeleteNum),
    case Res==1 andalso Type=/=[] of
        true ->
            lib_log_api:log_throw(Type, PlayerInfo#player_status.id, GoodsId, GoodsTypeId, DeleteNum, 0, 0);
        _ ->
            ok
    end,
    Res;
delete_one_by_id(_PlayerInfo, _GoodsId, _GoodsTypeId, DeleteNum, _Type) ->
    ?ERR("~p:~p param error:~p", [?MODULE, ?LINE, DeleteNum]),
    0.

%% ---------------------------------------------------------------------------
%% @doc 根据物品Id列表删除物品并刷新客户端以及做物品日志
-spec delete_more_by_list(PlayerInfo, GoodsList, Type) -> Res when
    PlayerInfo  :: integer() | #player_status{},    %% 玩家Id或者玩家记录
    GoodsList   :: [{GoodsId, GoodsTypeId, DeleteNum}],          %% 物品列表
    GoodsId     :: integer(),                       %% 物品Id
    GoodsTypeId :: integer(),                       %% 物品类型Id
    DeleteNum   :: integer(),                       %% 要删除的数量
    Type        :: atom(),                          %% 物品消耗类型
    %% 需要后台定义"产出消耗类型--消耗类型"
    Res         :: integer().
%% ---------------------------------------------------------------------------
delete_more_by_list(PlayerInfo, GoodsList, Type) when GoodsList=/=[] ->
    {PlayerId, Pid} = get_id_pid(PlayerInfo),
    {Res, GoodsInfoList} = case Pid =:= self() of
                               true ->
                                   goods_del:delete_list(GoodsList);
                               false ->
                                   ?NOT_IN_PLAYER_PROCESS({0, []})
                           end,
    case Res==1 andalso Type=/=[] of
        true ->
            NowTime = utime:unixtime(),
            ConsumeTypeId = data_goods:get_consume_type(Type),
            LogList = [[NowTime, ConsumeTypeId, PlayerId, GoodsId, GoodsTypeId, GoodsNum]
                ||{#goods{id = GoodsId, goods_id = GoodsTypeId}, GoodsNum}<-GoodsInfoList],
            lib_log_api:log_throw(LogList);
        _ ->
            ok
    end,
    Res;
delete_more_by_list(_PlayerInfo, GoodsList, _Type) ->
    ?ERR("~p:~p param error:~p", [?MODULE, ?LINE, GoodsList]),
    tool:back_trace_to_file(),
    0.

%% ---------------------------------------------------------------------------
%% @doc 检查物品是否可以加入到背包，能不能加入完全取决于背包格子数
%% PlayerInfo    :: #player_status{},
%% 目前物品格式只有以下几种
%% RewardList = [{?TYPE_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{?TYPE_BIND_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{OtherType, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, AttrList}, ...]
%% return => false | true
%% ---------------------------------------------------------------------------
can_give_goods(_Player, GoodsTypeList) ->
    can_give_goods_self(GoodsTypeList).

can_give_goods_self(GoodsTypeList) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    check_can_give_goods(GoodsStatus, GoodsTypeList).

check_can_give_goods(GoodsStatus, GoodsTypeList) ->
    #goods_status{player_id = PlayerId} = GoodsStatus,
    NeedNumList = get_need_cell_num(PlayerId, GoodsStatus, GoodsTypeList),
    lists:all(fun({Location, NeedNum}) ->
        HaveNum = lib_goods_util:get_null_cell_num(GoodsStatus, Location),
        HaveNum >= NeedNum
              end, NeedNumList).

%% ---------------------------------------------------------------------------
%% @doc 检查物品是否可以加入到背包，能不能加入完全取决于背包格子数
%% PlayerInfo    :: #player_status{},
%% 目前物品格式只有以下几种
%% RewardList = [{?TYPE_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{?TYPE_BIND_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{OtherType, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, AttrList}, ...]
%% return => true | {false, Error}
%% ---------------------------------------------------------------------------
can_give_goods(GoodsTypeList) ->
    can_give_goods_check(GoodsTypeList).
can_give_goods_check(GoodsTypeList) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    can_give_goods_check(GoodsStatus, GoodsTypeList).
can_give_goods_check(GoodsStatus, GoodsTypeList) ->
    #goods_status{player_id = PlayerId} = GoodsStatus,
    NeedNumList = get_need_cell_num(PlayerId, GoodsStatus, GoodsTypeList),
    can_give_goods_check_do(GoodsStatus, NeedNumList).
can_give_goods_check_do(_, []) -> true;
can_give_goods_check_do(GoodsStatus, [{Location, NeedNum}|T]) ->
    HaveNum = lib_goods_util:get_null_cell_num(GoodsStatus, Location),
    case HaveNum >= NeedNum of
        true  -> can_give_goods_check_do(GoodsStatus, T);
        false -> {false, data_goods:get_no_cell_errcode(Location)}
    end.
%% ---------------------------------------------------------------------------
%% @doc 获得物品列表需要多少格子数 只有物品才占背包格子，其他虚拟货币不占背包格子
%% RewardList = [{?TYPE_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{?TYPE_BIND_GOODS, GoodsTypeId, GoodsNum }, ...]
%% RewardList = [{?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, AttrList}, ...]
%% return: [{背包位置, 格子数}]
get_need_cell_num(PlayerInfo, GoodsTypeList) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    get_need_cell_num(PlayerInfo#player_status.id, GoodsStatus, GoodsTypeList).
get_need_cell_num(PlayerId, GoodsStatus, GoodsTypeList) ->
    get_need_cell_num(PlayerId, GoodsStatus#goods_status.dict, [], lib_goods_util:merge_same_goods(GoodsTypeList)).

get_need_cell_num(_, _, NeedNumList, []) -> NeedNumList;
get_need_cell_num(PlayerId, Dict, NeedNumList, [{ObjectType, _GoodsTypeId, _Num}|H])
    when ObjectType=/=?TYPE_GOODS andalso ObjectType=/=?TYPE_BIND_GOODS ->
    get_need_cell_num(PlayerId, Dict, NeedNumList, H);
get_need_cell_num(PlayerId, Dict, NeedNumList, [T|H]) ->
    case T of
        {?TYPE_GOODS, GoodsTypeId, Num} ->
            Attr = [];
        {?TYPE_BIND_GOODS, GoodsTypeId, Num} ->
            Attr = [{bind, ?BIND}];
        {?TYPE_ATTR_GOODS, GoodsTypeId, Num, Attr} ->
            ok
    end,
    case data_goods_type:get(GoodsTypeId) of
        [] ->
            get_need_cell_num(PlayerId, Dict, NeedNumList, H);
        #ets_goods_type{bag_location = BagLocation, max_overlap = MaxOverlap} = GoodsTypeInfo ->
            case MaxOverlap > 1 of
                true  ->
                    GoodsInfo = lib_goods_util:get_new_goods(GoodsTypeInfo, Attr),
                    GoodsList = lib_goods_util:get_same_goods_list(GoodsInfo, Dict),
                    CellNum = lib_goods_util:get_goods_need_cell_num(GoodsList, GoodsInfo, MaxOverlap, Num);
                false ->
                    CellNum = Num
            end,
            NewNeedNumList = case lists:keyfind(BagLocation, 1, NeedNumList) of
                                 {BagLocation, NeedNum} ->
                                     lists:keystore(BagLocation, 1, NeedNumList, {BagLocation, NeedNum + CellNum});
                                 _ ->
                                     lists:keystore(BagLocation, 1, NeedNumList, {BagLocation, CellNum})
                             end,
            get_need_cell_num(PlayerId, Dict, NewNeedNumList, H)
    end.

%% ---------------------------------------------------------------------------
%% @doc 查询道具数量
%% GoodsTypeList = [GoodsTypeId,...]
%% return: [{GoodsTypeId, Num}]
get_goods_num(_PlayerInfo, GoodsTypeList) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    GoodsNum = [begin
                    case data_goods_type:get(GoodsTypeId) of
                        #ets_goods_type{bag_location = BagLocation} ->
                            GoodsList = lib_goods_util:get_type_goods_list(GoodsStatus#goods_status.player_id, GoodsTypeId, BagLocation, GoodsStatus#goods_status.dict, GoodsStatus#goods_status.player_lv),
                            TotalNum = lib_goods_util:get_goods_total_num(GoodsList),
                            {GoodsTypeId, TotalNum};
                        _ ->
                            ?ERR("goods_num err: goods_type_id = ~p err_config", [GoodsTypeId]),
                            GoodsTypeId == 0 andalso
                                tool:back_trace_to_file(),
                            {GoodsTypeId, 0}
                    end
                end || GoodsTypeId <- GoodsTypeList],
    GoodsNum.

%% 查询道具数量
%% GoodsTypeList = [GoodsTypeId,...]
%% Bind 绑定状态
%% return: [{GoodsTypeId, Num}]
get_goods_num(_PlayerInfo, GoodsTypeList, Bind) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    GoodsNum = [begin
                    case data_goods_type:get(GoodsTypeId) of
                        #ets_goods_type{bag_location = BagLocation} ->
                            GoodsList = lib_goods_util:get_type_goods_list(GoodsStatus#goods_status.player_id, GoodsTypeId, Bind, BagLocation, GoodsStatus#goods_status.dict, GoodsStatus#goods_status.player_lv),
                            TotalNum = lib_goods_util:get_goods_total_num(GoodsList),
                            {GoodsTypeId, TotalNum};
                        _ ->
                            ?ERR("goods_num err: goods_type_id = ~p err_config", [GoodsTypeId]),
                            {GoodsTypeId, 0}
                    end
                end || GoodsTypeId <- GoodsTypeList],
    GoodsNum.

%% ---------------------------------------------------------------------------
%% @doc 获取背包空格子数量，默认基本背包
get_cell_num(PlayerInfo) ->
    get_cell_num(PlayerInfo, ?GOODS_LOC_BAG).

%% 获取某个背包位置空格子数量
get_cell_num(_PlayerInfo, Location) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    lib_goods_util:get_null_cell_num(GoodsStatus, Location).

%% 获取空格子数量列表
get_cell_num_list(_PlayerInfo) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    lib_goods_util:get_null_cell_num_list(GoodsStatus).
%% ---------------------------------------------------------------------------
%% @doc 查询特殊货币数量
get_currency(Player, CurrencyId) ->
    maps:get(CurrencyId, Player#player_status.currency_map, 0).

%% ---------------------------------------------------------------------------
%% @doc 获取物品信息
% spec get_goods_info(GoodsId) -> GoodsInfo | [] when
%      GoodsId     :: integer()    物品Id
%      GoodsInfo   :: #goods{}     物品信息
%      []                          没有物品
% ---------------------------------------------------------------------------
get_goods_info(GoodsId) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    lib_goods_util:get_goods(GoodsId, GoodsStatus#goods_status.dict, ?GOODS_LOC_BAG).

get_goods_info_by_id_type(GoodsId, GoodsTypeId) ->
    case data_goods_type:get(GoodsTypeId) of
        #ets_goods_type{bag_location = BagLocation} ->
            get_goods_info(GoodsId, BagLocation);
        _ ->
            []
    end.
get_goods_info_by_id_type(GoodsStatus, GoodsId, GoodsTypeId) ->
    case data_goods_type:get(GoodsTypeId) of
        #ets_goods_type{bag_location = BagLocation} ->
            get_goods_info(GoodsStatus, GoodsId, BagLocation);
        _ ->
            []
    end.
get_goods_info(GoodsId, Location) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    lib_goods_util:get_goods(GoodsId, GoodsStatus#goods_status.dict, Location).

get_goods_info(GoodsStatus, GoodsId, Location) ->
    lib_goods_util:get_goods(GoodsId, GoodsStatus#goods_status.dict, Location).

%% ---------------------------------------------------------------------------
%% @doc 系统提示获得物品 虚拟货币接口
send_tv_tip(RoleId, RewardL) ->
    send_tv_tip(RoleId, 2, RewardL).

%% RoleId :: 玩家id
%% Type   :: 类型：1右下角飘字|2聊天频道消息|3右下角飘字 + 聊天频道消息
%% RewardL:: [{GoodsType,Id,Num}]
send_tv_tip(RoleId, Type, RewardL) ->
    F = fun(Item, List) ->
        case Item of
            {goods, Id, Num} -> [{Id, Num}|List];
            {goods, Id, Num, _} -> [{Id, Num}|List];
            {goods_attr, Id, Num, _} -> [{Id, Num}|List];
            {?TYPE_GOODS, Id, Num} -> [{Id, Num}|List];
            {?TYPE_BIND_GOODS, Id, Num} -> [{Id, Num}|List];
            {?TYPE_ATTR_GOODS, Id, Num, _Attr} -> [{Id, Num}|List];
            {?TYPE_GOLD, _Id, Num} -> [{?GOODS_ID_GOLD, Num}|List];
            {?TYPE_BGOLD, _Id, Num} -> [{?GOODS_ID_BGOLD, Num}|List];
            {?TYPE_COIN, _Id, Num} -> [{?GOODS_ID_COIN, Num}|List];
            {?TYPE_HONOUR, _Id, Num} -> [{?GOODS_ID_HONOR, Num}|List];
            {?TYPE_EXP, _Id, Num} -> [{?GOODS_ID_EXP, Num}|List];
            {?TYPE_GUILD_GROWTH, _Id, Num} -> [{?GOODS_ID_GUILD_GROWTH, Num}|List];
            {?TYPE_GFUNDS, _Id, Num} -> [{?GOODS_ID_GFUNDS, Num}|List];
            {?TYPE_CHARM, _Id, Num} -> [{?GOODS_ID_CHARM, Num}|List];
            {?TYPE_FAME, _Id, Num} -> [{?GOODS_ID_FAME, Num}|List];
            {?TYPE_GFAME, _Id, Num} -> [{?GOODS_ID_GFAME, Num}|List];
            {?TYPE_CHILD_TOY, _Id, Num} -> [{?GOODS_ID_CHILD_TOY, Num}|List];
            {?TYPE_WORKROOM, _Id, Num} -> [{?GOODS_ID_FASHION_COIN, Num}|List];
            {?TYPE_CURRENCY, Id, Num} -> [{Id, Num}|List];
            _ -> List
        end
        end,
    NewList = lists:foldl(F, [], RewardL),
    case length(NewList) > 0 of
        true ->
            {ok, BinData} = pt_110:write(11060, [Type, NewList]),
            lib_server_send:send_to_uid(RoleId, BinData);
        false -> ok
    end.
%% ---------------------------------------------------------------------------
%% @doc 获得物品列表第一个物品的名字
get_first_object_name([{_, GoodsId, _}|_]) when GoodsId > 0 ->
    data_goods:get_goods_name(GoodsId);

get_first_object_name([{GoodsId, _}|_]) when GoodsId > 0 ->
    data_goods:get_goods_name(GoodsId);

get_first_object_name([{Type, 0, _}|_]) ->
    GoodsId
        = case Type of
              ?TYPE_BGOLD ->
                  ?GOODS_ID_BGOLD;
              ?TYPE_FGOLD ->
                  ?GOODS_ID_FGOLD;
              ?TYPE_GOLD ->
                  ?GOODS_ID_GOLD;
              ?TYPE_COIN ->
                  ?GOODS_ID_COIN;
              ?TYPE_GFUNDS ->
                  ?GOODS_ID_GFUNDS;
              ?TYPE_EXP ->
                  ?GOODS_ID_EXP;
              _ ->
                  0
          end,
    data_goods:get_goods_name(GoodsId).

%% ---------------------------------------------------------------------------
%% @doc
make_goods_tv_format_3(Goods) ->
    #goods{goods_id = GoodsTypeId} = Goods,
    Rating = 0, ExtraAttr = [],
    [GoodsTypeId, Rating, util:term_to_string(data_attr:unified_format_extra_attr(ExtraAttr, []))].

%% ---------------------------------------------------------------------------
%% @doc 增加物品通知前端
-spec notify_client(PlayerInfo, GoodsInfoList, ProduceType) -> ok when
    PlayerInfo   :: #player_status{} | integer(),
    GoodsInfoList:: [#goods{}],
    ProduceType  :: term().
%% ---------------------------------------------------------------------------
notify_client(_, [], _ProduceType) -> skip;
notify_client(#player_status{id = PlayerId}, GoodsInfoList, ProduceType) ->
    notify_client(PlayerId, GoodsInfoList, ProduceType);
notify_client(PlayerId, GoodsInfoList, ProduceType) ->
    ProduceTypeId = data_goods:get_produce_type(ProduceType),
    LocationList = classify_goods_by_location(GoodsInfoList, []),
    Fun = fun({Location, GoodsList}) ->
        GoodsListPack = lib_goods_util:pack_goods_list(GoodsList),
        {ok, BinData} = pt_150:write(15017, [Location, ProduceTypeId, GoodsListPack]),
        lib_server_send:send_to_uid(PlayerId, BinData)
          end,
    [Fun(OneLocationL) || OneLocationL <- LocationList],
    dispatch_event(PlayerId, GoodsInfoList),
    dispatch_classify_event(PlayerId, LocationList),
    ok.

%% ---------------------------------------------------------------------------
%% @doc 扣除通知前端更新物品数量 (若物品数量为0,即是要删除的物品)
-spec notify_client_num(PlayerId, GoodsInfoList) -> ok when
    PlayerId   :: integer(),
    GoodsInfoList:: [#goods{}].
%% ---------------------------------------------------------------------------
notify_client_num(_PlayerId, []) -> skip;
notify_client_num(PlayerId, GoodsInfoList) ->
    LocationList = classify_goods_by_location(GoodsInfoList, []),
    Fun = fun({Location, GoodsList}) ->
        GoodsListPack = [{Goods#goods.id, Goods#goods.num, Goods#goods.goods_id}||Goods<-GoodsList],
        {ok, BinData} = pt_150:write(15018, [Location, GoodsListPack]),
        lib_server_send:send_to_uid(PlayerId, BinData)
          end,
    [Fun(OneLocationL) || OneLocationL <- LocationList],
    ok.
%% 根据物品所在位置把物品分开
classify_goods_by_location([], ClassifyList) -> ClassifyList;
classify_goods_by_location([Goods | T], ClassifyList) ->
    Location = Goods#goods.location,
    case lists:keyfind(Location, 1, ClassifyList) of
        false ->
            NewClassifyL = [{Location, [Goods]} | ClassifyList],
            classify_goods_by_location(T, NewClassifyL);
        {Location, GoodsList} ->
            LocationList = {Location, [Goods | GoodsList]},
            NewClassifyL = lists:keystore(
                Location, 1, ClassifyList, LocationList
            ),
            classify_goods_by_location(T, NewClassifyL)
    end.

%% ---------------------------------------------------------------------------
%% 派发新增物品事件
%% TODO 注意 这里的物品数量不是值新增的物品数量，而是加入背包后，对应背包格子的物品新数量，对于可叠加物品，还包含玩家之前已经有的物品数量
%% TODO 而且 这里并不仅限于新增物品，可能是从仓库移动到背包或者从身上卸载到背包
dispatch_event(PlayerId, GoodsList) ->
    F = fun(#goods{goods_id = GoodsId, num = Num} = Goods, EventMap) ->
        case data_goods_type:get(GoodsId) of
            #ets_goods_type{quick_use = ?QUICK_USE} when Num > 0 ->
                EventData = #callback_give_goods_data{
                    type  = 1, goods = Goods},
                lib_player_event:async_dispatch(PlayerId, ?EVENT_GIVE_GOODS, EventData),
                EventMap;
            #ets_goods_type{} when GoodsId == ?GOODS_ID_VIP_TRIAL_SP andalso Num > 0 ->
                EventData = #callback_give_goods_data{type = 1, goods = Goods},
                lib_player_event:async_dispatch(PlayerId, ?EVENT_GIVE_GOODS, EventData),
                EventMap;
            _ ->
                EventMap
        end
        end,
    LastEventMap = lists:foldl(F, #{}, GoodsList),
    F2 = fun(Type, EventGoodsList, Acc) ->
        EventData = #callback_give_goods_list{type = Type, goods_list = EventGoodsList},
        lib_player_event:async_dispatch(PlayerId, ?EVENT_GIVE_GOODS_LIST, EventData),
        Acc
         end,
    maps:fold(F2, 0, LastEventMap).

dispatch_classify_event(PlayerId, LocationList) ->
    F1 = fun(#goods{goods_id = GoodsId, num = Num} = Goods) ->
        case data_goods_type:get(GoodsId) of
            #ets_goods_type{quick_use = ?QUICK_USE} when Num > 0 -> skip;
            #ets_goods_type{type = ?GOODS_TYPE_FISHING, subtype = ?GOODS_SUBTYPE_FISHING_GEAR} when Num > 0 ->
                EventData = #callback_give_goods_data{
                    type  = 3, goods = Goods},
                lib_player_event:async_dispatch(PlayerId, ?EVENT_GIVE_GOODS, EventData);
            _ ->
                skip
        end
         end,
    F2 = fun({Location, GoodsList}) when Location == ?GOODS_LOC_BAG -> lists:foreach(F1, GoodsList);
        ({_Location, _GoodsList}) -> skip
         end,
    lists:foreach(F2, LocationList).
%% ----------------------------------- GM秘籍 -----------------------------------------------
set_goods_expired_time(RoleIds, GTypeIds, ExpiredTime) ->
    case RoleIds of
        [] ->
            NeedHandle = true,
            RealRoleIdsTmp = db:get_all(<<"select id from player_low">>),
            RealRoleIds = [RoleId || [RoleId] <- RealRoleIdsTmp],
            GoodsIdsTmp = db:get_all(io_lib:format(<<"select id from goods where goods_id in (~s)">>, [util:link_list(GTypeIds)])),
            GoodsIds = [GoodsId || [GoodsId] <- GoodsIdsTmp];
        [_|_] ->
            NeedHandle = true,
            RealRoleIds = RoleIds,
            GoodsIdsTmp = db:get_all(io_lib:format(<<"select id from goods where role_id in (~s) and goods_id in (~s)">>, [util:link_list(RealRoleIds), util:link_list(GTypeIds)])),
            GoodsIds = [GoodsId || [GoodsId] <- GoodsIdsTmp];
        _ -> NeedHandle = false, RealRoleIds = [], GoodsIds = []
    end,
    case NeedHandle of
        true ->
            %% 先统一修改数据库，之后再处理在线玩家的物品信息
            db:execute(io_lib:format(<<"update goods set expire_time = ~p where id in (~s)">>, [ExpiredTime, util:link_list(GoodsIds)])),
            spawn(fun() ->
                do_set_goods_expired_time(RealRoleIds, GTypeIds, ExpiredTime)
                  end),
            ok;
        _ -> skip
    end.

do_set_goods_expired_time([], _, _) -> skip;
do_set_goods_expired_time([RoleId|L], GTypeIds, ExpiredTime) ->
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) of
        true ->
            lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, lib_goods_api, set_goods_expired_time_core, [GTypeIds, ExpiredTime]);
        false -> skip
    end,
    timer:sleep(100),
    do_set_goods_expired_time(L, GTypeIds, ExpiredTime).
set_goods_expired_time_core(PlayerStatus, GTypeIds, ExpiredTime) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    #goods_status{dict = GoodsMaps} = GoodsStatus,
    Dict = lib_goods_util:get_location_goods(GoodsMaps, ?GOODS_LOC_BAG),
    F = fun(_Key, Value) ->
        #goods{goods_id = GTypeId} = Value,
        case lists:member(GTypeId, GTypeIds) of
            true ->
                Value#goods{expire_time = ExpiredTime};
            _ -> Value
        end
        end,
    NewDict = dict:map(F, Dict),
    NewGoodsStatus = GoodsStatus#goods_status{
        dict = lib_goods_util:put_location_goods(GoodsMaps, ?GOODS_LOC_BAG, NewDict)
    },
    lib_goods_do:set_goods_status(NewGoodsStatus),
    %% 帮玩家整理一遍背包
    pp_goods:handle(15004, PlayerStatus, [?GOODS_LOC_BAG]).



%% (列表中GoodsTypeId不能重复)
%% GoodsList = [{location, Location, GoodsTypeId, GoodsNum }, ...]
%% return ErrorCode|{1, GoodsInfoList}
give_goods_with_loc(PlayerInfo, Location, GoodsList) ->
    give_goods_with_loc(PlayerInfo, Location, GoodsList, "", 0).

%% ---------------------------------------------------------------------------
%% @doc 发物品到指定背包(列表中GoodsTypeId不能重复)
%% 某些特定活动才能调用这个接口！！！！！！！！
-spec give_goods_with_loc(Player, Location, GoodsList, Type, SubType) -> Res when
    Player      :: #player_status{},        %% 玩家记录
    GoodsList   :: [tuple()],               %% 物品列表 具体格式参照上面
    Type        :: atom(),                  %% 物品产出类型-需要后台定义"产出消耗类型--产出类型"
    SubType     :: integer(),               %% 物品产出子类型
    Location    :: integer(),               %% 所发奖励要统一放到哪个背包
    Res         :: integer().               %% 结果码： 1 成功， 2物品类型不存在, 3 背包数量不足, 4动态属性设置权限不足, 5列表中GoodsTypeId重复
%% ---------------------------------------------------------------------------
give_goods_with_loc(PlayerInfo, Location, GoodsList, Type, SubType) ->
    Produce = #produce{type = Type, subtype = SubType, remark = ""},
    give_goods_with_loc(PlayerInfo, Location, GoodsList, Produce).

give_goods_with_loc(PlayerInfo, Location, GoodsList, Produce) when GoodsList =/= [] ->
    #produce{type = Type, subtype = SubType, remark = Remark} = Produce,
    {PlayerId, Pid} = get_id_pid(PlayerInfo),
    Res = case Pid =:= self() of
              true ->
                  lib_goods_do:give_more_to_specify_loc(Location, GoodsList, Type);
              false ->
                  ?NOT_IN_PLAYER_PROCESS(0),
                  0
          end,
    case Res of
        {ErrorCode, _GoodsInfoList} when ErrorCode == 1 ->
            case Type =/= [] of
                true ->
                    Fun = fun(OneAdd) ->
                        {GoodsTypeId, GoodsNum} = case OneAdd of
                                                      {location, _Location, TypeId, Num} -> {TypeId, Num}
                                                  end,
                        % #ets_goods_type{bind = Bind} = data_goods_type:get(GoodsTypeId),
                        lib_log_api:log_goods(Type, SubType, GoodsTypeId, GoodsNum, PlayerId, Remark)
                          end,
                    [Fun(OneAdd) || OneAdd <- GoodsList];
                false -> skip
            end;
        _ -> skip
    end,
    Res;
give_goods_with_loc(_PlayerInfo, Location, GoodsList, _Produce) ->
    ?ERR("give_goods_with_loc:~p error:~p", [Location, GoodsList]),
    0.

%%--------------------------------------------------
%% 把物品从A背包移动到B背包
%% @param  Ps          #player_status{}
%% @param  GoodsId     物品唯一id
%% @param  FromLoc     A背包
%% @param  ToLoc       B背包
%% @returns             description
%%--------------------------------------------------
move_a2b(Ps, GoodsId, FromLoc, ToLoc) ->
    {PlayerId, Pid} = get_id_pid(Ps),
    case Pid =:= self() of
        true ->
            GoodsStatus = lib_goods_do:get_goods_status(),
            case lib_goods_check:check_move_a2b(GoodsStatus, GoodsId, FromLoc, ToLoc) of
                {fail, Res} -> Res;
                {ok, GoodsInfo} ->
                    case catch lib_goods_do:move_a2b(GoodsStatus, GoodsInfo, ToLoc) of
                        {ok, NewGoodsStatus, UpGoods} ->
                            lib_goods_do:set_goods_status(NewGoodsStatus),
                            lib_goods_api:notify_client(Ps#player_status.id, UpGoods, bag_move),
                            %% 此处通知客户端将从原背包中移除但不会从服务端删除物品
                            lib_goods_api:notify_client_num(PlayerId, [GoodsInfo#goods{num=0}]),
                            1;
                        _Error ->
                            ?ERR("move_a2b err:~p~n", [_Error]),
                            0
                    end
            end;
        false ->
            ?NOT_IN_PLAYER_PROCESS(0)
    end.
move_more_a2b(Ps, FromLoc, ToLoc) ->
    {PlayerId, Pid} = get_id_pid(Ps),
    case Pid =:= self() of
        true ->
            case lib_goods_check:check_move_a2b_type(FromLoc, ToLoc) of
                true ->
                    GoodsStatus = lib_goods_do:get_goods_status(),
                    GoodsInfoList = lib_goods_util:get_goods_list(PlayerId, FromLoc, GoodsStatus#goods_status.dict),
                    SortList = lists:keysort(#goods.goods_id, GoodsInfoList),
                    NullCellNum = lib_goods_util:get_null_cell_num(GoodsStatus, ToLoc),
                    case NullCellNum > 0 of
                        true ->
                            SubList = lists:sublist(SortList, NullCellNum),
                            case catch lib_goods_do:move_more_a2b(GoodsStatus, SubList, ToLoc) of
                                {ok, NewGoodsStatus, UpGoods} ->
                                    lib_goods_do:set_goods_status(NewGoodsStatus),
                                    lib_goods_api:notify_client(Ps#player_status.id, UpGoods, bag_move),
                                    %% 此处通知客户端将从原背包中移除但不会从服务端删除物品
                                    NotifyRemoveL = [TGoods#goods{num = 0}|| TGoods <- SubList],
                                    lib_goods_api:notify_client_num(PlayerId, NotifyRemoveL),
                                    1;
                                _Error ->
                                    ?ERR("move_a2b err:~p~n", [_Error]),
                                    0
                            end;
                        false ->
                            ?ERRCODE(err151_no_null_cells)
                    end;
                false -> ?ERRCODE(err150_location_err)
            end;
        false ->
            ?NOT_IN_PLAYER_PROCESS(0)
    end.