%% ---------------------------------------------------------------------------
%% @doc lib_relationship.erl

%% @author hjh
%% @since  2016-12-06
%% @deprecated 关系(所有接口只在玩家进程调用)
%% ---------------------------------------------------------------------------
-module(lib_relationship).
-include("server.hrl").
-include("figure.hrl").
-include("errcode.hrl").
-include("relationship.hrl").
-include("vip.hrl").
-include("predefine.hrl").
-include("common.hrl").
-include("def_module.hrl").
-include("def_daily.hrl").
-include("def_vip.hrl").
-include("def_event.hrl").
-include("rec_event.hrl").
-include("def_fun.hrl").
-include("role.hrl").
-include("guild.hrl").
-include("chat.hrl").
-include("mate.hrl").
-include("rec_dress_up.hrl").

-include_lib("stdlib/include/ms_transform.hrl").

-export([
    get_rela_list/2                     %% 获取玩家的关系列表
    ,get_relas_by_types/2               %% 获得相应的关系列表
    ,get_rela_with_other_on_dict/2      %% 获取A B玩家之间的关系(A玩家进程)
    ,get_rela_with_other_from_db/2      %% 从数据库获取A B玩家之间的关系
    ,get_rela_from_db/1                 %% 从数据库读取玩家关系
    ,save_ets_rela/4                    %% 更新关系缓存
    ,get_ets_rela/2                     %% 获取关系缓存信息

    ,get_friend_id_on_dict/1            %% 获取好友Id列表
    ,get_friend_on_dict/1               %% 获取好友信息
    ,is_friend_on_dict/2                %% 是否为好友
    ,get_max_intimacy/1                 %% 最大亲密度

    ,get_recommended_list/2             %% 获取玩家的好友推荐列表
    ,get_one_recommended_role_info/1    %% 获取推荐好友的信息

    ,get_mate_list/2

    ,ask_add_friend/3                   %% 请求添加好友
    ,get_friend_ask_list/1              %% 获取好友请求列表
    ,reply_add_friend_ask/2             %% 回复所有好友请求
    ,reply_one_add_friend_ask/4         %% 回复单个玩家的好友请求
    ,del_friend/2                       %% 删除好友
    ,del_friend_ask_after_block/2
    ,del_from_block_list/2

    %% 单向好友相关逻辑
    ,add_single_rela_friend/2           %% 添加单向好友
    ,active_add_single_friend/2         %% 单向好友一方主动添加对方为好友
    ,get_my_rela_type_from_opp/2        %% 获取我在对方所处的关系

    ,get_rela_and_intimacy_online/2     %% 获得ID列表的关系和亲密度(在线)
    ,get_rela_and_intimacy_offline/2    %% 获得ID列表的关系和亲密度(离线)
    ,get_rela_and_intimacy/2            %% 获取关系和亲密度
    ,update_intimacy/8                  %% 更新亲密度
    ,update_intimacy_each_one/5         %% 同时更新好友双方的亲密度
    ,get_intimacy_attr/2
    ,get_intimacy/2                     %% 获取亲密度
    ,get_intimacy_contribute_with_other/2   %% 获取亲密度贡献度
    ,clean_each_other_intimacy/2        %% 清除亲密度

    ,update_last_chat_time/3
    ,update_last_ctime_each_one/3
    ,trigger_intimacy_add/5
    ,rela_type_change/3
    ,quickly_apply/1                    %% 一键申请好友
    ,check_list/1
]).

-export([
    be_asked_add_friend/3              %% 回调函数(外部不要私自调用)
    ,receive_friend_ask_reply/5        %% 回调函数(外部不要私自调用)
    ,del_friend_ask_after_be_blocked/2
    ,opp_do_add_single_rela_friend/3   %% 刷新单向好友对方数据,回调函数(外部不要私自调用)
    ,be_del_friend_by_other/2          %% 回调函数(外部不要私自调用)
    ,be_del_friend_by_other_2/2        %% 单向好友主动删除方处理好友关系,回调函数(外部不要私自调用)
    ,opp_do_refresh_intimacy/3         %% 单向好友一方主动添加对方好友,对方刷新亲密度,回调函数(外部不要私自调用)
    ]).

%% 只能供relation_last_chat调用
-export([
    get_rela_on_dict/1
    ,make_up_friend_info/2
    ]).

-export([
    role_change_online_status/3
    ]).

%% 注意:dict是玩家进程处理;db是数据库;其他函数会先从本玩家进程,再对方玩家进程,最后数据库查询对应的好友关系
make_record(rela, [RoleId, OtherRid, RelaType, Intimacy, Contribute, LastChatTime, CTime]) ->
    #rela{
        role_id = RoleId,
        other_rid = OtherRid,
        rela_type = RelaType,
        intimacy = Intimacy,
        contribute = Contribute,
        last_chat_time = LastChatTime,
        ctime = CTime
    };
%% 好友请求
make_record(friend_ask, [RoleId, AskId, Time]) ->
    #friend_ask{role_id = RoleId, ask_id = AskId, time = Time}.

%% 获取亲密度
get_intimacy(RoleId, OtherRid) ->
    RelaList = get_rela_on_dict(RoleId), %% 获取关系列表
    case lists:keyfind(OtherRid, #rela.other_rid, RelaList) of
        #rela{intimacy = Intimacy} ->
            Intimacy;
        _ ->
            0
    end.

%% 获得相应的关系列表
get_relas_by_types(RoleId, Type) ->
    RelaTypeList = case Type of
        1 -> ?RELA_FRIEND_TYPES;
        2 -> ?RELA_FETTERS_TYPES;
        _ -> []
    end,
    get_relas_by_types_on_dict(RoleId, RelaTypeList).

%% 根据关系类型获得关系列表
get_relas_by_types_on_dict(_, []) -> [];
get_relas_by_types_on_dict(RoleId, RelaTypeList) ->
    RelaList = get_rela_on_dict(RoleId),
    [R || R <- RelaList, lists:member(R#rela.rela_type, RelaTypeList)].

%% 从内存获取玩家和某玩家的关系
%% RoleId:玩家自己id
%% OtherRid:另一个玩家的id
get_rela_with_other_on_dict(RoleId, OtherRid) ->
    RelaList = get_rela_on_dict(RoleId),
    lists:keyfind(OtherRid, #rela.other_rid, RelaList).

%% 从数据库获取玩家和某玩家的关系
get_rela_with_other_from_db(RoleId, OtherRid) ->
    case db:get_row(io_lib:format(?sql_rela_select_by_rid_and_oid, [RoleId, OtherRid])) of
        [] -> [];
        List -> make_record(rela, List)
    end.

%% 获取好友Id列表
get_friend_id_on_dict(RoleId) ->
    List = get_rela_on_dict(RoleId),
    [OtherRid || #rela{other_rid = OtherRid, rela_type = RelaType} <- List, lists:member(RelaType, ?RELA_FRIEND_TYPES)].

%% 获取最大亲密度
get_max_intimacy(RoleId) ->
    List = get_rela_on_dict(RoleId),
    Intimacys = [Intimacy || #rela{intimacy=Intimacy,rela_type = RelaType} <- List, lists:member(RelaType, ?RELA_FRIEND_TYPES)],
    lists:max([0|Intimacys]).

%% 获取好友列表
get_friend_on_dict(RoleId) ->
    get_relas_by_types_on_dict(RoleId, ?RELA_FRIEND_TYPES).

%% 获取单向好友列表
get_single_friend_on_dict(RoleId) ->
    get_relas_by_types_on_dict(RoleId, ?RELA_SINGLE_FRIEND_TYPES).

%% 玩家间是否存在指定的关系
is_sepcify_rela_on_dict(RoleId, OtherRid, RelaTypeList) ->
    case get_rela_with_other_on_dict(RoleId, OtherRid) of
        false -> false;
        #rela{rela_type = RelaType} -> lists:member(RelaType, RelaTypeList)
    end.

%% 是否为好友
is_friend_on_dict(RoleId, OtherRid) ->
    is_sepcify_rela_on_dict(RoleId, OtherRid, ?RELA_FRIEND_TYPES).

%% 获得关系列表,最近联系人&羁绊(非正式关系)单独处理
%% 最近联系人
get_rela_list(RoleId, _Type = 5) ->
    relation_last_chat:get_last_rela_list(RoleId);
get_rela_list(RoleId, Type) when is_integer(Type) ->
    RelaTypeList = case Type of
        ?RELA_LIST_TYPE_FRIEND -> ?RELA_FRIEND_TYPES;
        ?RELA_LIST_TYPE_FETTERS -> ?RELA_FETTERS_TYPES;
        ?RELA_LIST_TYPE_SINGLE_FRIEND -> ?RELA_SINGLE_FRIEND_TYPES;
        _ -> []
    end,
    get_rela_list(RoleId, RelaTypeList);
get_rela_list(RoleId, RelaTypeList) when is_list(RelaTypeList) andalso RelaTypeList =/= [] ->
    List = get_relas_by_types_on_dict(RoleId, RelaTypeList),
    get_rela_list_help(List, []);
get_rela_list(_, _) -> [].


%% 组装好友信息
make_up_friend_info(Rela, RoleId) when is_integer(RoleId) ->
    case lib_role:get_role_show(RoleId) of
        RoleInfo = #ets_role_show{} -> skip;
        _ -> RoleInfo = #ets_role_show{}
    end,
    make_up_friend_info(Rela, RoleInfo);
make_up_friend_info(Rela, RoleInfo) ->
    {
        RoleInfo#ets_role_show.id,
        RoleInfo#ets_role_show.figure#figure.name,
        RoleInfo#ets_role_show.figure#figure.career,
        RoleInfo#ets_role_show.figure#figure.sex,
        RoleInfo#ets_role_show.figure#figure.turn,
        RoleInfo#ets_role_show.figure#figure.lv,
        RoleInfo#ets_role_show.figure#figure.sup_vip,
        RoleInfo#ets_role_show.figure#figure.vip,
        RoleInfo#ets_role_show.figure#figure.picture,
        RoleInfo#ets_role_show.figure#figure.picture_ver,
        RoleInfo#ets_role_show.figure#figure.profile_photo_id,
        _DressId = begin case lists:keyfind(?DRESS_UP_FRAME, 1, RoleInfo#ets_role_show.figure#figure.dress_list) of
            {_, DressBoard} -> DressBoard;
            _ -> 0 end end,
        RoleInfo#ets_role_show.online_flag,
        Rela#rela.intimacy,
        0,
        Rela#rela.last_chat_time,
        Rela#rela.ctime,
        RoleInfo#ets_role_show.figure#figure.guild_name,
        RoleInfo#ets_role_show.last_logout_time
    }.

get_rela_list_help([], Result) -> Result;
get_rela_list_help([#rela{other_rid = OtherRid} = Rela|L], Result) ->
    case lib_role:get_role_show(OtherRid) of
        RoleInfo = #ets_role_show{} ->
            T = make_up_friend_info(Rela, RoleInfo),
            get_rela_list_help(L, [T|Result]);
        _ -> get_rela_list_help(L, Result)
    end.

%% 获取某个推荐好友的信息(从缓存中获取)
get_one_recommended_role_info(RoleId) when is_integer(RoleId) ->
    case lib_role:get_role_show(RoleId) of
        RoleInfo = #ets_role_show{} ->
            {
                RoleId,
                RoleInfo#ets_role_show.figure#figure.name,
                RoleInfo#ets_role_show.figure#figure.career,
                RoleInfo#ets_role_show.figure#figure.sex,
                RoleInfo#ets_role_show.figure#figure.turn,
                RoleInfo#ets_role_show.figure#figure.lv,
                RoleInfo#ets_role_show.figure#figure.sup_vip,
                RoleInfo#ets_role_show.figure#figure.vip,
                RoleInfo#ets_role_show.figure#figure.picture,
                RoleInfo#ets_role_show.figure#figure.picture_ver,
                RoleInfo#ets_role_show.figure#figure.profile_photo_id,
                _DressId = begin case lists:keyfind(?DRESS_UP_FRAME, 1, RoleInfo#ets_role_show.figure#figure.dress_list) of
                    {_, DressBoard} -> DressBoard;
                    _ -> 0
                end end,
                RoleInfo#ets_role_show.online_flag,
                RoleInfo#ets_role_show.figure#figure.power
            };
        _ -> false
    end;
get_one_recommended_role_info(_) -> false.

%% 获取推荐好友列表(不在协议分发的地方设置cd，在这里设置cd)
%% 加了一个Type变量为了防止首次打开界面时的刷新计入cd时间导致短时间内无法刷新
% get_recommended_list(#player_status{id = RoleId} = Player, 0) ->
%     RecommendedRids = case get(?FRIEND_RECOMMEND_LIST) of
%         undefined -> %% 若推荐列表为空，则刷新
%             do_get_recommended_list(Player);
%         Val when Val =/= [] -> %% 列表非空，判断列表中是否有玩家已成为好友
%             Boolean = lists:any(fun(OneId) -> is_friend_on_dict(RoleId, OneId) end, Val),
%             if
%                 Boolean == true -> %% 重新获取推荐列表
%                     do_get_recommended_list(Player);
%                 true ->
%                     Val
%             end;
%         _ -> %% 如果之前的推荐列表是空的,打开界面自动刷新
%             do_get_recommended_list(Player)
%     end,
%     RecommendedList = lists:foldl(fun(OneId, ListTmp) -> %% 获取推荐列表所有玩家信息
%         case get_one_recommended_role_info(OneId) of
%             false -> ListTmp;
%             Tmp -> ListTmp ++ [Tmp]
%         end
%     end, [], RecommendedRids),
%     {?SUCCESS, RecommendedList};
get_recommended_list(Player, _Type) ->
    LastSearchTime = case get(?FRIEND_SEARCH_TIME) of
        undefined -> 0;
        Val -> Val
    end,
    Unixtime = utime:unixtime(),
    case Unixtime - LastSearchTime >= ?RECOMMENDED_SEARCH_CD of
        true ->
            RecommendedRids = do_get_recommended_list(Player),
            RecommendedList = lists:foldl(fun(OneId, ListTmp) ->
                case get_one_recommended_role_info(OneId) of
                    false -> ListTmp;
                    Tmp -> ListTmp ++ [Tmp]
                end
            end, [], RecommendedRids),
            put(?FRIEND_SEARCH_TIME, Unixtime),
            {?SUCCESS, RecommendedList};
        false ->
            {?ERRCODE(err140_26_recommended_too_often), []}
    end.

get_online_id_and_lv(RoleId, RoleSex, RoleLv, RolePower) ->
    OnlineRIds0 = lib_player:get_online_ids(),
    OnlineRIds  = lists:delete(RoleId, OnlineRIds0),
    RecommendPools = lists:sublist(ulists:list_shuffle(OnlineRIds), ?RECOMMENDED_ROLE_POOL),
    OnlineRIdLv = [begin
        #figure{sex = Sex1, power = PowerX, lv = RoleLvX} = lib_role:get_role_figure(Rid),
        {Rid, Sex1, abs(RoleLv - RoleLvX), abs(RolePower - PowerX)} end || Rid <- RecommendPools],
    {OppositeSex, SameSex} = lists:partition(fun({_RidX, Sex2, _LvX, _PowerX}) ->
        Sex2 =/= RoleSex
     end, OnlineRIdLv),
    Sortfun = fun({_, _, Lv1, Power1}, {_, _, Lv2, Power2}) ->
        if Lv1 =/= Lv2 -> Lv1 < Lv2;
            true -> Power1 < Power2
        end
     end,
    OppositeSex1 = lists:sort(Sortfun, OppositeSex),
    SameSex1     = lists:sort(Sortfun, SameSex),
    {OppositeSex1, SameSex1}.

%% 推荐列表在总玩家数>=10个时，固定推荐10个玩家，不足则推荐全部玩家
%% 在同性玩家>=3个，异性玩家>=7个时，固定推荐3个同性玩家和7个异性玩家
%% 若莫一方数量不足，则用另一方补齐总共10个玩家
%% 其中，以等级差小的优先推荐
%% 等价差相等时，则以战力差相等优先推荐
do_get_recommended_list(Player) ->
    #player_status{id = RoleId, figure = #figure{lv = Lv, sex = Sex, power = Power}} = Player,
    {OppositeSex, SameSex} = get_online_id_and_lv(RoleId, Sex, Lv, Power),
    {OppositeSexNum, SameSexNum} = calc_recommend_num(length(OppositeSex), length(SameSex)),
    ResultLen = 0,
    RecommendedRids0 = do_get_recommended_list_helper(opposite_sex, OppositeSex, Player, [], ResultLen, OppositeSexNum),
    RecommendedRids1 = do_get_recommended_list_helper(same_sex, SameSex, Player, [], ResultLen, SameSexNum),
    RecommendedRids  = lists:append(RecommendedRids0, RecommendedRids1),
    put(?FRIEND_RECOMMEND_LIST, RecommendedRids),
    RecommendedRids.

calc_recommend_num(OppoNum, SameNum) ->
    SameConfigRecommendNum = ?RECOMMENDED_LIST_MAX_LEN - ?RECOMMENDED_OPPOSITE_ROLE,
    if OppoNum >= ?RECOMMENDED_OPPOSITE_ROLE andalso SameNum >= SameConfigRecommendNum ->
            {?RECOMMENDED_OPPOSITE_ROLE, SameConfigRecommendNum};
        OppoNum >= ?RECOMMENDED_OPPOSITE_ROLE andalso SameNum < SameConfigRecommendNum ->
            {abs(?RECOMMENDED_LIST_MAX_LEN - SameNum), SameNum};
        OppoNum < ?RECOMMENDED_OPPOSITE_ROLE andalso SameNum >= SameConfigRecommendNum ->
            {OppoNum, abs(?RECOMMENDED_LIST_MAX_LEN - OppoNum)};
        OppoNum < ?RECOMMENDED_OPPOSITE_ROLE andalso SameNum < SameConfigRecommendNum ->
            {OppoNum, SameNum}
    end.

do_get_recommended_list_helper(_, [], _, Result, _, _) -> Result;
do_get_recommended_list_helper(same_sex, _, _, Result, ResultLen, ResultLen1) when ResultLen >= ResultLen1 ->
    Result;
do_get_recommended_list_helper(opposite_sex, _, _, Result, ResultLen, ResultLen1) when ResultLen >= ResultLen1 ->
    Result;
do_get_recommended_list_helper(_Type, [{OneId, _, _DiffLv, _DiffPower}|OnlineRIds], Player, Result, ResultLen, ResultLen1) ->
    #player_status{id = RoleId} = Player,
    %% 排除等级不够、好友达到上限、已是好友或黑名单的玩家
    CheckList = [{lv_limit, OneId}, {check_rela_type, RoleId, OneId},
                 {friend_num_limit, OneId}, {add_self, RoleId, OneId}],
    case check_list(CheckList) of
        true ->
            case lib_chat_limit:is_blocked(Player, OneId) of
                false ->
                    do_get_recommended_list_helper(_Type, OnlineRIds, Player, [OneId|Result], ResultLen + 1, ResultLen1);
                _ ->
                    do_get_recommended_list_helper(_Type, OnlineRIds, Player, Result, ResultLen, ResultLen1)
            end;
        {false, _ErrCode} ->
            do_get_recommended_list_helper(_Type, OnlineRIds, Player, Result, ResultLen, ResultLen1)
    end.

get_rela_role_info_element(RelaRoleInfo, Element) ->
    {_RoleId, _Name, _Career, Sex, _Turn, _Lv, _VipType, _VipLv, _Picture, _PictureVer, _, _, _OnlineFlag, _Intimacy, _MarriageType, _LastChatTime, _Ctime, _GuildName, _LastLogoutTime} = RelaRoleInfo,
    case Element of
        sex -> Sex;
        _   -> false
    end.

%% 获取我在对方所处的关系类型
get_my_rela_type_from_opp(RoleId, OtherRidList) ->
    F = fun(OppRoleId, TTypeList) ->
                #ets_role_rela{rela = RelaType, res_status = ResponseStatus} = get_ets_rela(RoleId, OppRoleId),
                lists:keystore(OppRoleId, 1, TTypeList, {OppRoleId, RelaType, ResponseStatus})
    end,
    TypeList = lists:foldl(F, [], OtherRidList),
    {ok, BinData} = pt_140:write(14021, [1, TypeList]),
    lib_server_send:send_to_uid(RoleId, BinData).

%% ets缓存里面玩家在对方所处的好友状态
save_ets_rela(EtsRoleRela) ->
    ets:insert(?ETS_ROLE_RELA, EtsRoleRela).

save_ets_rela(RoleId, OppRoleId, Type, Value) ->
    case ets:lookup(?ETS_ROLE_RELA, {RoleId, OppRoleId}) of
        [#ets_role_rela{} = EtsRoleRela|_] ->
            ok;
        _ ->
            EtsRoleRela = get_ets_rela(RoleId, OppRoleId)
    end,
    %% 1-在对方所处的好友状态，2-好友答复状态
    NewEtsRoleRela =
        case Type of
            ?UPDATE_RELA -> EtsRoleRela#ets_role_rela{rela = Value};
            ?UPDATE_RESPONSE_STATUS -> EtsRoleRela#ets_role_rela{res_status = Value};
            _ -> EtsRoleRela
        end,
    ets:insert(?ETS_ROLE_RELA, NewEtsRoleRela).

%% 获取缓存中的好友状态数据
get_ets_rela(RoleId, OppRoleId) ->
    case ets:lookup(?ETS_ROLE_RELA, {RoleId, OppRoleId}) of
        [#ets_role_rela{} = EtsRoleRela|_] -> EtsRoleRela;
        _ ->
            EtsRoleRela = get_opp_rela_from_db(RoleId, OppRoleId),
            ets:insert(?ETS_ROLE_RELA, EtsRoleRela),
            EtsRoleRela
    end.

get_opp_rela_from_db(RoleId, OppRoleId) ->
    case db_rela_select_rela_type_from_other(RoleId, OppRoleId) of
        [] ->
            #ets_role_rela{key = {RoleId, OppRoleId}};
        [RelaType|_] ->
            case db_get_friend_ask_by_oppo_role_id(OppRoleId, RoleId) of
                [] ->
                    ResponseStatus = ?IF(RelaType == ?RELA_LIST_TYPE_SINGLE_FRIEND, ?ASK_RESPONSE_REFUSE, ?ASK_RESPONSE_AGREE);
                _  -> ResponseStatus = 0
            end,
            #ets_role_rela{key = {RoleId, OppRoleId}, rela = RelaType, res_status = ResponseStatus}
    end.

%% 一键申请好友
quickly_apply(#player_status{id = RoleId} = Player) ->
    RecommendedRids = get(?FRIEND_RECOMMEND_LIST),
    Fun = fun(OneId, ResultList) ->
        CheckList = [{add_self, RoleId, OneId}, {friend_num_limit, RoleId}, {check_rela_type, RoleId, OneId}, {other_side_online, OneId}],
        case check_list(CheckList) of
            true ->
                case lib_chat_limit:is_blocked(Player, OneId) of
                    false ->
                        OnePid = misc:get_player_process(OneId),
                        case misc:is_process_alive(OnePid) of
                            true ->
                                lib_player:apply_cast(OneId, ?APPLY_CAST_STATUS, lib_relationship, be_asked_add_friend, [OneId, RoleId]),
                                [{OneId, ?SUCCESS}|ResultList];
                            false ->
                                db_asked_friend_req(RoleId, OneId)
                        end;
                    _ ->
                        [{OneId, ?ERRCODE(err110_have_shield)}|ResultList]
                end;
            {false, ErrCode} ->
                [{OneId, ErrCode}|ResultList]
        end
    end,
    lists:foldl(Fun, [], RecommendedRids).

%% 请求添加好友(请求者进程)
ask_add_friend(_Sid, RoleId, BeAskId) ->
    BeAskPid = misc:get_player_process(BeAskId),
    case misc:is_process_alive(BeAskPid) of
        true -> %% 被请求玩家在线跳到被请求玩家进程处理
            lib_player:apply_cast(BeAskId, ?APPLY_CAST_STATUS, lib_relationship, be_asked_add_friend, [BeAskId, RoleId]),
            {ok, BinData} = pt_140:write(14003, [?SUCCESS]),
            lib_server_send:send_to_uid(RoleId, BinData);
        false -> %% 对方不在线不能加好友
            db_asked_friend_req(RoleId, BeAskId)
    end,
    %% 更新亲密度推送
    SenMsg = [{BeAskId, ?RELA_TYPE_SINGLE_FRIEND, ?ASK_RESPONSE_SUSPEND}],
    {ok, BinData1} = pt_140:write(14021, [2, SenMsg]),
    lib_server_send:send_to_uid(RoleId, BinData1).

%% 收到添加好友请求(被请求者进程)
be_asked_add_friend(Player, RoleId, AskId) ->
    case lib_chat_limit:is_blocked(Player, AskId) of
        %% 不在自己的黑名单里面才处理
        false ->
            %% 如果已经是朋友，不处理
            RelaList = lib_relationship:get_rela_on_dict(RoleId),
            case lists:keyfind(AskId, #rela.other_rid, RelaList) of
                #rela{rela_type = ?RELA_TYPE_FRIEND} -> skip;
                _ ->
                    CurFriendAskList = get_friend_ask_on_dict(RoleId),
                    Unixtime = utime:unixtime(),
                    NewFriendAskList = lists:keystore(AskId, #friend_ask.ask_id, CurFriendAskList, #friend_ask{ask_id = AskId, time = Unixtime}),
                    save_friend_ask_on_dict(RoleId, NewFriendAskList),
                    db_add_friend_ask(RoleId, AskId, Unixtime),
                    case lib_role:get_role_show(AskId) of
                        #ets_role_show{
                           figure = AskFigure
                        } ->
                            %% 通知客户端有新的好友请求
                            {ok, BinData} = pt_140:write(14008, [AskId, AskFigure#figure.name, AskFigure#figure.lv, Unixtime]),
                            lib_server_send:send_to_uid(RoleId, BinData);
                        _ -> skip
                    end
            end;
        true -> skip
    end,
    ok.

%% 发起给离线玩家的好友请求
db_asked_friend_req(RoleId, BeAskId) ->
    BlockList = case db:get_row(?SQL_CHAT_LIMIT_GET, [BeAskId]) of
        [_, _, SheildBin] ->
            util:bitstring_to_term(SheildBin);
        _ ->
            []
    end,
    case lists:member(RoleId, BlockList) of
        true ->
            skip;
        _ ->
            %% 已经为好友关系则过滤请求,避免无用请求数据在数据库积累
            case db_rela_select_rela_type_from_other(RoleId, BeAskId) of
                [] -> db_add_friend_ask(BeAskId, RoleId, utime:unixtime());
                [RelaType|_] when RelaType == ?RELA_TYPE_FRIEND ->
                    skip;
                _ -> db_add_friend_ask(BeAskId, RoleId, utime:unixtime())
            end
    end.

%% 获取好友请求列表
get_friend_ask_list(RoleId) ->
    case get_friend_ask_on_dict(RoleId) of
        [] -> [];
        CurFriendAskList ->
            F = fun(FriendAsk) ->
                case lib_role:get_role_show(FriendAsk#friend_ask.ask_id) of
                    #ets_role_show{figure = AskerFigure} ->
                        #figure{name = Name, turn = Turn, career = Career, sex = Sex, lv = Lv, vip = VipLv, dress_list = DressList,
                                picture = Picture, picture_ver = PictureVer, power = Power, profile_photo_id = ProfilePhoto,
                                sup_vip = SupVipType} = AskerFigure,
                        DressBoard = case lists:keyfind(?DRESS_UP_FRAME, 1, DressList) of
                            {_, DressBoard0} -> DressBoard0;
                            _ -> 0
                        end,
                        {FriendAsk#friend_ask.ask_id, Name, Turn, Career, Sex, Picture, PictureVer, ProfilePhoto, DressBoard, Lv, FriendAsk#friend_ask.time, Power, VipLv, SupVipType};
                    _ ->
                        {FriendAsk#friend_ask.ask_id, "???", 0, 0, 0, "", 0, 0, 0, 0, FriendAsk#friend_ask.time, 0, 0, 0}
                end
            end,
            [F(FriendAsk) || FriendAsk <- CurFriendAskList, is_record(FriendAsk, friend_ask)]
    end.

%% 按照申请的时间先后顺序，自动逐条接受申请并删除申请直到列表为空或好友人数达到上限
%% 排序由客户端来排 服务端这边不做排序
agree_add_friend_ask([], RelaList, FriendAskList, _RoleId, _RoleName, _FriendNum, _FriendNumLimit, _Unixtime) ->
    {ok, RelaList, FriendAskList};
agree_add_friend_ask(_AskIds, RelaList, FriendAskList, _RoleId, _RoleName, FriendNum, FriendNumLimit, _Unixtime) when FriendNum >= FriendNumLimit ->
    {friend_num_limit, RelaList, FriendAskList};
agree_add_friend_ask([AskId|AskIds], RelaList, FriendAskList, RoleId, RoleName, FriendNum, FriendNumLimit, Unixtime) when AskId =/= RoleId ->
    case lists:keyfind(AskId, #friend_ask.ask_id, FriendAskList) of
        #friend_ask{} -> %% 请求存在
            case lists:keyfind(AskId, #rela.other_rid, RelaList) of
                #rela{rela_type = RelaType} = PreRela ->
                    % InBlackList = lists:member(RelaType, ?RELA_BLACK_TYPES),
                    %% 黑名单检测不在此处理,每次黑名单变动双方的好友请求都会更新,理论上操作时请求都应该是合法的
                    InFriendList = lists:member(RelaType, ?RELA_FRIEND_TYPES),
                    case InFriendList == false of
                        true -> %% 不在好友列表和黑名单才能添加
                            {NewRelaList, NewFriendAskList} = do_agree_add_friend_ask(RoleId, AskId, PreRela, RelaList, FriendAskList, RoleName, Unixtime),
                            agree_add_friend_ask(AskIds, NewRelaList, NewFriendAskList, RoleId, RoleName, FriendNum + 1, FriendNumLimit, Unixtime);
                        false ->
                            agree_add_friend_ask(AskIds, RelaList, FriendAskList, RoleId, RoleName, FriendNum, FriendNumLimit, Unixtime)
                    end;
                _ -> %% 不存在关系
                    {NewRelaList, NewFriendAskList} = do_agree_add_friend_ask(RoleId, AskId, #rela{role_id = RoleId, other_rid = AskId}, RelaList, FriendAskList, RoleName, Unixtime),
                    agree_add_friend_ask(AskIds, NewRelaList, NewFriendAskList, RoleId, RoleName, FriendNum + 1, FriendNumLimit, Unixtime)
            end;
        _ ->
            agree_add_friend_ask(AskIds, RelaList, FriendAskList, RoleId, RoleName, FriendNum, FriendNumLimit, Unixtime)
    end;
agree_add_friend_ask([_AskId|AskIds], RelaList, FriendAskList, RoleId, RoleName, FriendNum, FriendNumLimit, Unixtime) ->
    agree_add_friend_ask(AskIds, RelaList, FriendAskList, RoleId, RoleName, FriendNum, FriendNumLimit, Unixtime).

do_agree_add_friend_ask(RoleId, AskId, PreRela, RelaList, FriendAskList, RoleName, Time) ->
    NewRelaType = rela_type_change(PreRela#rela.rela_type, ?RELA_TYPE_FRIEND, add_friend),
    %% 数据库插入新关系、删除好友请求
    % db_rela_insert(RoleId, AskId, NewRelaType, Time),
    db:execute(io_lib:format(?sql_save_role_rela,
     [RoleId, AskId, NewRelaType, PreRela#rela.intimacy, PreRela#rela.contribute, 0, PreRela#rela.ctime])),
    db_del_friend_ask_by_asker_id(RoleId, AskId),
    NewOneRela = make_record(rela, [RoleId, AskId, NewRelaType, PreRela#rela.intimacy, PreRela#rela.contribute, 0, Time]),
    NewRelaList = lists:keystore(AskId, #rela.other_rid, RelaList, NewOneRela),
    NewFriendAskList = lists:keydelete(AskId, #friend_ask.ask_id, FriendAskList),
    AskPid = misc:get_player_process(AskId),
    case misc:is_process_alive(AskPid) of
        true -> %% 好友请求者在线跳到请求玩家进程处理
            lib_player:apply_cast(AskId, ?APPLY_CAST_SAVE, lib_relationship, receive_friend_ask_reply, [AskId, RoleId, RoleName, Time]);
        false -> %% 玩家不在线直接操作数据库
            case get_rela_with_other_from_db(AskId, RoleId) of
                #rela{rela_type = PreRelaType1, intimacy = Intimacy1, contribute = Contribute, ctime = Ctime1} ->
                    %% 如果对方之前有发好友请求过来要删掉
                    db_del_friend_ask_by_asker_id(AskId, RoleId),
                    NewRelaType1 = rela_type_change(PreRelaType1, ?RELA_TYPE_FRIEND, add_friend),
                    db:execute(io_lib:format(?sql_save_role_rela, [AskId, RoleId, NewRelaType1, Intimacy1, Contribute, 0, Ctime1]));
                    % db_rela_insert(AskId, RoleId, NewRelaType1, Time);
                _ ->
                    PreRelaType1 = ?RELA_TYPE_NONE,
                    NewRelaType1 = ?RELA_TYPE_FRIEND,
                    db_rela_insert(AskId, RoleId, ?RELA_TYPE_FRIEND, Time)
            end,
            %% 日志
            lib_log_api:log_rela(AskId, RoleId, PreRelaType1, ?BE_ADD_FRIEND, NewRelaType1)
    end,

    %% 更新好友缓存信息
    EtsRoleRela = #ets_role_rela{key = {AskId, RoleId}, rela = ?RELA_TYPE_FRIEND, res_status = ?ASK_RESPONSE_AGREE},
    save_ets_rela(EtsRoleRela),

    %% 触发任务
    lib_task_api:add_friend(RoleId),
    %% 触发成就
    handle_achievement(RoleId, NewRelaList),
    %% 日志
    lib_log_api:log_rela(RoleId, AskId, PreRela#rela.rela_type, ?ADD_FRIEND, NewRelaType),

    RelaRoleInfo = make_up_friend_info(NewOneRela, AskId),
    %% 派发事件
    case get_rela_role_info_element(RelaRoleInfo, sex) of
        false -> Sex = 0;
        Sex -> ok
    end,
    lib_player_event:async_dispatch(RoleId, ?EVENT_ADD_FRIEND, #callback_add_friend{friend_id = AskId, sex = Sex}),
    %% 更新亲密度
    SenMsg = [{AskId, ?RELA_TYPE_FRIEND, ?ASK_RESPONSE_AGREE}],
    {ok, BinData} = pt_140:write(14021, [2, SenMsg]),
    lib_server_send:send_to_uid(RoleId, BinData),

    notify_client_rela_update(?RELA_UPDATE_ADD, RoleId, ?RELA_LIST_TYPE_FRIEND, [RelaRoleInfo]),

    {NewRelaList, NewFriendAskList}.

%% 添加单向好友(添加方直接变为对方好友)
add_single_rela_friend(Player, BeAskId) ->
    #player_status{id = RoleId} = Player,
	%% 判断之前是否是双向好友关系
	RelaList = get_rela_on_dict(RoleId),
	case lists:keyfind(BeAskId, #rela.other_rid, RelaList) of
	    #rela{rela_type = RelaType}
            when RelaType == ?RELA_TYPE_FETTERS orelse RelaType == ?RELA_TYPE_FRIEND ->
	        skip;
	    _ ->
            RelaType = ?RELA_TYPE_FRIEND,
            CreateTime = utime:unixtime(),
            NewRela =
            case lists:keyfind(BeAskId, #rela.other_rid, RelaList) of
                #rela{} = PreRela ->
                    PreRela#rela{rela_type = RelaType};
                _ ->
                    PreRela = #rela{
                        role_id   = RoleId,
                        other_rid = BeAskId,
                        rela_type = RelaType,
                        ctime     = CreateTime
                    },
                    PreRela
            end,
		    %% 更新对方好友关系
            BeAskPid = misc:get_player_process(BeAskId),
            case misc:is_process_alive(BeAskPid) of
                true ->
		            lib_player:apply_cast(BeAskId, ?APPLY_CAST, ?MODULE, opp_do_add_single_rela_friend, [BeAskId, RoleId, CreateTime]);
                _ ->
                    db:execute(io_lib:format(?sql_save_role_rela,
                        [BeAskId, RoleId, ?RELA_TYPE_SINGLE_FRIEND, NewRela#rela.intimacy, NewRela#rela.contribute, NewRela#rela.last_chat_time, CreateTime]))
             end,
            %% 更新自己的数据
	        NewRelaList = lists:keystore(BeAskId, #rela.other_rid, RelaList, NewRela),
	        db:execute(io_lib:format(?sql_save_role_rela,
	            [RoleId, BeAskId, RelaType, NewRela#rela.intimacy, NewRela#rela.contribute, NewRela#rela.last_chat_time, CreateTime])),
	        save_rela_on_dict(RoleId, NewRelaList),

            %% 更新好友缓存信息
            EtsRoleRela = #ets_role_rela{key = {RoleId, BeAskId}, rela = ?RELA_TYPE_SINGLE_FRIEND, res_status = ?ASK_RESPONSE_SUSPEND},
            save_ets_rela(EtsRoleRela),
            EtsRoleRela1 = #ets_role_rela{key = {BeAskId, RoleId}, rela = ?RELA_TYPE_FRIEND, res_status = ?ASK_RESPONSE_AGREE},
            save_ets_rela(EtsRoleRela1),

            %% 刷新亲密度
            SenMsg = [{BeAskId, ?RELA_TYPE_SINGLE_FRIEND, ?ASK_RESPONSE_SUSPEND}],
            {ok, BinData} = pt_140:write(14021, [2, SenMsg]),
            lib_server_send:send_to_sid(Player#player_status.sid, BinData),

            RelaRoleInfo = make_up_friend_info(NewRela, BeAskId),
            notify_client_rela_update(?RELA_UPDATE_ADD, RoleId, ?RELA_LIST_TYPE_FRIEND, [RelaRoleInfo]),

            %% 日志
            lib_log_api:log_rela(RoleId, NewRela#rela.other_rid, PreRela#rela.rela_type, ?ADD_FRIEND, NewRela#rela.rela_type),
            %% 触发任务
            lib_task_api:add_friend(Player),
            %% 触发成就
            % case lists:keyfind(BeAskId, #rela.other_rid, RelaList) of
            %    #rela{} ->
            %        skip;
            %    _ ->
            %% 触发成就
            handle_achievement(Player, NewRelaList),
            % end,
            %% 事件派发
            case get_rela_role_info_element(RelaRoleInfo, sex) of
                false -> Sex = 0;
                Sex -> ok
            end,
            lib_player_event:async_dispatch(RoleId, ?EVENT_ADD_FRIEND, #callback_add_friend{friend_id = BeAskId, sex = Sex})
	end.

%% 更新单向好友数据到对方
opp_do_add_single_rela_friend(RoleId, AskId, CreateTime) ->
    RelaList = get_rela_on_dict(RoleId),
    case lists:keyfind(AskId, #rela.other_rid, RelaList) of
        #rela{rela_type = RelaType}
            when RelaType == ?RELA_TYPE_FETTERS orelse RelaType == ?RELA_TYPE_FRIEND -> %% 原来已经有好友关系
            skip;
        _ ->
            case lists:keyfind(AskId, #rela.other_rid, RelaList) of
                #rela{} = PreRela ->
                    NewRela = PreRela#rela{rela_type = ?RELA_TYPE_SINGLE_FRIEND};
                _ ->
                    NewRela = #rela{
                        role_id   = RoleId,
                        other_rid = AskId,
                        rela_type = ?RELA_TYPE_SINGLE_FRIEND,
                        ctime     = CreateTime
                    }
            end,
            NewRelaList = lists:keystore(AskId, #rela.other_rid, RelaList, NewRela),
            save_rela_on_dict(RoleId, NewRelaList),
            db:execute(io_lib:format(?sql_save_role_rela,
                [RoleId, AskId, ?RELA_TYPE_SINGLE_FRIEND, NewRela#rela.intimacy, NewRela#rela.contribute, NewRela#rela.last_chat_time, CreateTime])),
            %% 更新亲密度
            SenMsg = [{AskId, ?RELA_TYPE_SINGLE_FRIEND, ?ASK_RESPONSE_SUSPEND}],
            {ok, BinData} = pt_140:write(14021, [2, SenMsg]),
            lib_server_send:send_to_uid(RoleId, BinData),
            RelaRoleInfo = make_up_friend_info(NewRela, AskId),
            notify_client_rela_update(?RELA_UPDATE_ADD, RoleId, ?RELA_LIST_TYPE_SINGLE_FRIEND, [RelaRoleInfo])
    end.

%% 单向好友一方主动添加对方为好友
active_add_single_friend(Player, PreRela) ->
    #rela{role_id = RoleId, other_rid = BeAskId} = PreRela,
    NewRelaType = rela_type_change(PreRela#rela.rela_type, ?RELA_TYPE_FRIEND, add_friend),
    CreateTime  = utime:unixtime(),
    NewOneRela = make_record(rela, [RoleId, BeAskId, NewRelaType, PreRela#rela.intimacy, PreRela#rela.contribute, PreRela#rela.last_chat_time, CreateTime]),
    db:execute(io_lib:format(?sql_save_role_rela, [RoleId, BeAskId, NewRelaType, PreRela#rela.intimacy, PreRela#rela.contribute, PreRela#rela.last_chat_time, CreateTime])),
    RelaList = get_rela_on_dict(RoleId),
    NewRelaList = lists:keystore(BeAskId, #rela.other_rid, RelaList, NewOneRela),
    save_rela_on_dict(RoleId, NewRelaList),
    %% 删除对方的好友申请
    CurFriendAskList = get_friend_ask_on_dict(RoleId),
    AskLogId = BeAskId,
    case lists:keyfind(AskLogId, #friend_ask.ask_id, CurFriendAskList) of
        #friend_ask{} ->
            NewFriendAskList = lists:keydelete(AskLogId, #friend_ask.ask_id, CurFriendAskList),
            save_friend_ask_on_dict(RoleId, NewFriendAskList),
            db_del_friend_ask_by_asker_id(RoleId, AskLogId);
        _ ->
            skip
    end,
    %% 更新好友缓存信息
    EtsRoleRela = #ets_role_rela{key = {BeAskId, RoleId}, rela = ?RELA_TYPE_FRIEND, res_status = ?ASK_RESPONSE_AGREE},
    save_ets_rela(EtsRoleRela),

    %% 更新亲密度
    SenMsg = [{BeAskId, ?RELA_TYPE_FRIEND, ?ASK_RESPONSE_AGREE}],
    {ok, BinData0} = pt_140:write(14021, [2, SenMsg]),
    lib_server_send:send_to_sid(Player#player_status.sid, BinData0),
    %% 更新前端好友列表
    RelaRoleInfo = make_up_friend_info(NewOneRela, BeAskId),
    notify_client_rela_update(?RELA_UPDATE_ADD, RoleId, ?RELA_LIST_TYPE_FRIEND, [RelaRoleInfo]),
    %% 日志
    lib_log_api:log_rela(RoleId, BeAskId, PreRela#rela.rela_type, ?ADD_FRIEND, NewOneRela#rela.rela_type),
    %% 触发任务
    lib_task_api:add_friend(Player),
    %% 触发成就
    handle_achievement(Player, NewRelaList),
    %% 事件派发
    case get_rela_role_info_element(RelaRoleInfo, sex) of
        false -> Sex = 0;
        Sex -> ok
    end,
    lib_player_event:async_dispatch(RoleId, ?EVENT_ADD_FRIEND, #callback_add_friend{friend_id = BeAskId, sex = Sex}),

    {ok, BinData} = pt_140:write(14003, [?SUCCESS]),
    lib_server_send:send_to_sid(Player#player_status.sid, BinData),
    %% 更新对方好友亲密度
    BeAskPid = misc:get_player_process(BeAskId),
    case misc:is_process_alive(BeAskPid) of
        true ->
            lib_player:apply_cast(BeAskId, ?APPLY_CAST_STATUS, ?MODULE, opp_do_refresh_intimacy, [RoleId, ?RELA_TYPE_FRIEND]);
        _ ->
            skip
    end,
    ok.

%% 单向好友一方主动添加对方好友,对方刷新亲密度
opp_do_refresh_intimacy(Player, AskId, RelaType) ->
    SendMsg = [{AskId, RelaType, ?ASK_RESPONSE_AGREE}],
    {ok, BinData} = pt_140:write(14021, [2, SendMsg]),
    lib_server_send:send_to_sid(Player#player_status.sid, BinData),
    {ok, Player}.

%% 收到同意加好友的回复
receive_friend_ask_reply(Player, RoleId, BeAskId, BeAskName, Time) ->
    %% 如果对方之前有发好友请求过来要删掉
    case get_friend_ask_on_dict(RoleId) of
        [] -> skip;
        CurFriendAskList ->
            db_del_friend_ask_by_asker_id(RoleId, BeAskId),
            NewFriendAskList = lists:keydelete(BeAskId, #friend_ask.ask_id, CurFriendAskList),
            save_friend_ask_on_dict(RoleId, NewFriendAskList)
    end,
    %% 改变双方的关系
    RelaList = get_rela_on_dict(RoleId),
    PreRela = case lists:keyfind(BeAskId, #rela.other_rid, RelaList) of
        #rela{} = PreRelaTmp -> PreRelaTmp;
        _ -> #rela{role_id = RoleId, other_rid = BeAskId, rela_type = 0}
    end,
    NewRelaType = rela_type_change(PreRela#rela.rela_type, ?RELA_TYPE_FRIEND, add_friend),
    % db_rela_insert(RoleId, BeAskId, NewRelaType, Time),
    db:execute(io_lib:format(?sql_save_role_rela,
        [RoleId, BeAskId, NewRelaType, PreRela#rela.intimacy, PreRela#rela.contribute, PreRela#rela.last_chat_time, PreRela#rela.ctime])),
    NewOneRela = make_record(rela, [RoleId, BeAskId, NewRelaType, PreRela#rela.intimacy, PreRela#rela.contribute, PreRela#rela.last_chat_time, Time]),
    NewRelaList = lists:keystore(BeAskId, #rela.other_rid, RelaList, NewOneRela),
    %% 保存好友列表到玩家的进程字典中
    save_rela_on_dict(RoleId, NewRelaList),

    BinData = lib_chat:make_tv(?MOD_RELA, 1, [BeAskName]),
    lib_server_send:send_to_uid(RoleId, BinData),

    %% 刷新对方好友亲密度
    SenMsg = [{BeAskId, ?RELA_TYPE_FRIEND, ?ASK_RESPONSE_AGREE}],
    {ok, BinData1} = pt_140:write(14021, [2, SenMsg]),
    lib_server_send:send_to_sid(Player#player_status.sid, BinData1),

    RelaRoleInfo = make_up_friend_info(NewOneRela, BeAskId),
    notify_client_rela_update(?RELA_UPDATE_ADD, RoleId, ?RELA_LIST_TYPE_FRIEND, [RelaRoleInfo]),

    %% 触发任务
    lib_task_api:add_friend(Player),
    %% 日志
    lib_log_api:log_rela(RoleId, BeAskId, PreRela#rela.rela_type, ?BE_ADD_FRIEND, NewRelaType),
    %% 派发事件
    case get_rela_role_info_element(RelaRoleInfo, sex) of
        false -> Sex = 0;
        Sex -> ok
    end,
    %% 触发成就
    handle_achievement(Player, NewRelaList),

    {ok, LastPlayer} = lib_player_event:dispatch(Player, ?EVENT_ADD_FRIEND, #callback_add_friend{friend_id = BeAskId, sex = Sex}),
    {ok, LastPlayer}.


%% 拉黑后清除双方的好友请求
%% 在MasterId玩家进程执行
del_friend_ask_after_block(MasterId, BlockedId) ->
    %% 删除好友请求
    case get_friend_ask_on_dict(MasterId) of
        [] -> skip;
        CurFriendAskList ->
            case lists:keyfind(BlockedId, #friend_ask.ask_id, CurFriendAskList) of
                #friend_ask{} ->
                    db_del_friend_ask_by_asker_id(MasterId, BlockedId),
                    NewFriendAskList = lists:keydelete(BlockedId, #friend_ask.ask_id, CurFriendAskList),
                    save_friend_ask_on_dict(MasterId, NewFriendAskList);
                _ ->
                    skip
            end
    end,
    case get_rela_with_other_on_dict(MasterId, BlockedId) of
        #rela{rela_type = RelaType} -> ok;
        _ -> RelaType = 0
    end,
    %% 从最近聊天中删除
    % del_black_from_last_rela(MasterId, BlockedId),
    %% 日志
    lib_log_api:log_rela(MasterId, BlockedId, RelaType, ?ADD_BLACK, RelaType),
    BlockPid = misc:get_player_process(BlockedId),
    case is_pid(BlockPid) andalso misc:is_process_alive(BlockPid) of
        true ->
            lib_player:apply_cast(BlockedId, ?APPLY_CAST, ?MODULE, del_friend_ask_after_be_blocked, [BlockedId, MasterId]);
        _ ->
            db_del_friend_ask_by_asker_id(BlockedId, MasterId),
            % db_del_last_rela_by_role_id(BlockedId, MasterId),
            lib_log_api:log_rela(BlockedId, MasterId, RelaType, ?BE_ADD_BLACK, RelaType)
    end,
    % notify_client_rela_update(?RELA_UPDATE_REMOVE, MasterId, ?RELA_LIST_TYPE_BLACK, [BlockedId]).
    ok.

del_friend_ask_after_be_blocked(BlockedId, MasterId) ->
    case get_friend_ask_on_dict(BlockedId) of
        [] -> skip;
        CurFriendAskList ->
            case lists:keyfind(MasterId, #friend_ask.ask_id, CurFriendAskList) of
                #friend_ask{} ->
                    db_del_friend_ask_by_asker_id(BlockedId, MasterId),
                    NewFriendAskList = lists:keydelete(MasterId, #friend_ask.ask_id, CurFriendAskList),
                    save_friend_ask_on_dict(BlockedId, NewFriendAskList);
                _ ->
                    skip
            end
    end,
    case get_rela_with_other_on_dict(BlockedId, MasterId) of
        #rela{rela_type = RelaType} -> ok;
        _ -> RelaType = 0
    end,
    %% 被拉黑把对方从最近联系人列表中删除
    % del_black_from_last_rela(BlockedId, MasterId),
    %% 日志
    lib_log_api:log_rela(BlockedId, MasterId, RelaType, ?BE_ADD_BLACK, RelaType),
    ok.

%% 删除好友
del_friend(RoleId, BeDelId) ->
    RelaList = get_rela_on_dict(RoleId),
    PreRela = case lists:keyfind(BeDelId, #rela.other_rid, RelaList) of
        #rela{} = PreRelaTmp -> PreRelaTmp;
        _ -> #rela{role_id = RoleId, other_rid = BeDelId, rela_type = 0}
    end,
    case lists:member(PreRela#rela.rela_type, ?RELA_FRIEND_TYPES) of
        true ->
            NewRelaType = rela_type_change(PreRela#rela.rela_type, ?RELA_TYPE_NONE, del_friend),
            %% 因要保留亲密度,所以保留相互关系数据
            db:execute(io_lib:format(?sql_save_role_rela,
                [RoleId, BeDelId, NewRelaType, PreRela#rela.intimacy, PreRela#rela.contribute, PreRela#rela.last_chat_time, PreRela#rela.ctime])),
            NewOneRela = PreRela#rela{rela_type = NewRelaType},
            NewRelaList = lists:keystore(BeDelId, #rela.other_rid, RelaList, NewOneRela),
            save_rela_on_dict(RoleId, NewRelaList),

            %% 更新好友缓存信息
            AEtsRoleLela = #ets_role_rela{key = {BeDelId, RoleId}, rela = ?RELA_TYPE_SINGLE_FRIEND, res_status = ?ASK_RESPONSE_REFUSE},
            save_ets_rela(AEtsRoleLela),

            %% 更新亲密度
            SenMsg = [{BeDelId, ?RELA_TYPE_SINGLE_FRIEND, ?ASK_RESPONSE_REFUSE}],
            {ok, BinData} = pt_140:write(14021, [2, SenMsg]),
            lib_server_send:send_to_uid(RoleId, BinData),
            RelaRoleInfo = make_up_friend_info(NewOneRela, BeDelId),
            notify_client_rela_update(?RELA_UPDATE_ADD, RoleId, ?RELA_LIST_TYPE_SINGLE_FRIEND, [RelaRoleInfo]),
            %% 日志
            lib_log_api:log_rela(RoleId, BeDelId, PreRela#rela.rela_type, ?DEL_FRIEND, NewRelaType),
            %% 处理对方数据
            BeDelPid = misc:get_player_process(BeDelId),
            case misc:is_process_alive(BeDelPid) of
                true -> %% 对方在线
                    lib_player:apply_cast(BeDelId, ?APPLY_CAST, lib_relationship, be_del_friend_by_other, [BeDelId, RoleId]);
                false -> %% 玩家不在线,直接操作数据库
                    case get_rela_with_other_from_db(BeDelId, RoleId) of
                        #rela{rela_type = RelaType1} = PreRela1 ->
                            case RelaType1 == ?RELA_TYPE_SINGLE_FRIEND of  %% 单向好友关系才需要处理
                                true ->
                                    NewRelaType1 = rela_type_change(RelaType1, ?RELA_TYPE_NONE, del_friend),
                                    %% 日志
                                    lib_log_api:log_rela(BeDelId, RoleId, RelaType1, ?BE_DEL_FRIEND, NewRelaType1),
                                    db:execute(io_lib:format(?sql_save_role_rela,
                                        [BeDelId, RoleId, ?RELA_TYPE_NONE, PreRela#rela.intimacy, PreRela#rela.contribute, PreRela#rela.last_chat_time, PreRela1#rela.ctime])),
                                    %% 自己也要变成对方的陌生人
                                    db:execute(io_lib:format(?sql_save_role_rela,
                                        [RoleId, BeDelId, ?RELA_TYPE_NONE, PreRela#rela.intimacy, PreRela#rela.contribute, PreRela#rela.last_chat_time, PreRela1#rela.ctime])),
                                    %% 更新内存
                                    NewOneRela1  = NewOneRela#rela{rela_type = ?RELA_TYPE_NONE},
                                    TNewRelaList = get_rela_on_dict(RoleId),
                                    NewRelaList1 = lists:keystore(BeDelId, #rela.other_rid, TNewRelaList, NewOneRela1),
                                    save_rela_on_dict(RoleId, NewRelaList1),
                                    %% 触发成就
                                    handle_achievement(RoleId, NewRelaList1),
                                    %% 更新好友缓存信息
                                    EtsRoleLela = #ets_role_rela{key = {BeDelId, RoleId}, rela = ?RELA_TYPE_NONE, res_status = ?ASK_RESPONSE_REFUSE},
                                    save_ets_rela(EtsRoleLela),
                                    EtsRoleLela1 = #ets_role_rela{key = {RoleId, BeDelId}, rela = ?RELA_TYPE_NONE, res_status = ?ASK_RESPONSE_REFUSE},
                                    save_ets_rela(EtsRoleLela1),

                                    notify_client_rela_update(?RELA_UPDATE_REMOVE, RoleId, ?RELA_LIST_TYPE_FRIEND, [BeDelId]);
                                _ -> skip
                            end;
                        _ -> skip
                    end
            end,
            ?SUCCESS;
        false -> %% 非好友操作失败
            ?ERRCODE(err140_6_not_friend)
    end.

%% 触发成就
handle_achievement(Player, NewRelaList) when is_record(Player, player_status) ->
    Filter = fun(TRela) -> TRela#rela.rela_type =:= ?RELA_TYPE_FRIEND end,
    FriendList = lists:filter(Filter, NewRelaList),
    lib_achievement_api:add_friend_event(Player, erlang:length(FriendList)),
    ok;
handle_achievement(RoleId, NewRelaList) when is_integer(RoleId) ->
    Filter = fun(TRela) -> TRela#rela.rela_type =:= ?RELA_TYPE_FRIEND end,
    FriendList = lists:filter(Filter, NewRelaList),
    lib_achievement_api:async_event(
        RoleId, lib_achievement_api, add_friend_event, erlang:length(FriendList)
    ),
    ok;
handle_achievement(_, _) -> ignore.

%% 删除好友告知被删除方(对方是单向好友才需要处理)
be_del_friend_by_other(RoleId, OtherRid) ->
    RelaList = get_rela_on_dict(RoleId),
    PreRela = case lists:keyfind(OtherRid, #rela.other_rid, RelaList) of
        #rela{} = PreRelaTmp -> PreRelaTmp;
        _ -> #rela{role_id = RoleId, other_rid = OtherRid, rela_type = 0}
    end,
    IsFriend = lists:member(PreRela#rela.rela_type, ?RELA_FRIEND_TYPES),
    IsSingleFriend = lists:member(PreRela#rela.rela_type, ?RELA_SINGLE_FRIEND_TYPES),
    if
        IsFriend ->
            %% 更新缓存中好友关系
            EtsRoleLela = #ets_role_rela{key = {RoleId, OtherRid}, rela = ?RELA_TYPE_SINGLE_FRIEND, res_status = ?ASK_RESPONSE_REFUSE},
            save_ets_rela(EtsRoleLela),

            %% 更新亲密度
            SenMsg = [{OtherRid, ?RELA_TYPE_SINGLE_FRIEND, ?ASK_RESPONSE_REFUSE}],
            {ok, BinData} = pt_140:write(14021, [2, SenMsg]),
            lib_server_send:send_to_uid(RoleId, BinData),
            skip;
        IsSingleFriend ->
            NewRelaType = rela_type_change(PreRela#rela.rela_type, ?RELA_TYPE_NONE, del_friend),

            %% 更新缓存中好友关系(互相变为陌生人状态) %% todo
            EtsRoleLela = #ets_role_rela{key = {OtherRid, RoleId}, rela = ?RELA_TYPE_NONE, res_status = ?ASK_RESPONSE_REFUSE},
            save_ets_rela(EtsRoleLela),
            EtsRoleLela1 = #ets_role_rela{key = {RoleId, OtherRid}, rela = ?RELA_TYPE_NONE, res_status = ?ASK_RESPONSE_REFUSE},
            save_ets_rela(EtsRoleLela1),

            %% 日志
            lib_log_api:log_rela(RoleId, OtherRid, PreRela#rela.rela_type, ?BE_DEL_FRIEND, NewRelaType),
            db:execute(io_lib:format(?sql_save_role_rela,
                [RoleId, OtherRid, NewRelaType, PreRela#rela.intimacy, PreRela#rela.contribute, PreRela#rela.last_chat_time, PreRela#rela.ctime])),
            NewOneRela = PreRela#rela{rela_type = NewRelaType},
            NewRelaList = lists:keystore(OtherRid, #rela.other_rid, RelaList, NewOneRela),
            %% 单向好友变为陌生人,对方同时也要修改变为陌生人
            OtherPid = misc:get_player_process(OtherRid),
            case misc:is_process_alive(OtherPid) of
                true -> %% 对方在线
                    lib_player:apply_cast(OtherRid, ?APPLY_CAST, lib_relationship, be_del_friend_by_other_2, [OtherRid, RoleId]);
                _ ->
                    db:execute(io_lib:format(?sql_save_role_rela,
                        [OtherRid, RoleId, ?RELA_TYPE_NONE, NewOneRela#rela.intimacy, NewOneRela#rela.contribute, PreRela#rela.last_chat_time, NewOneRela#rela.ctime]))
            end,
            %% 更新亲密度
            SenMsg = [{OtherRid, ?RELA_TYPE_NONE, ?ASK_RESPONSE_REFUSE}],
            {ok, BinData} = pt_140:write(14021, [2, SenMsg]),
            lib_server_send:send_to_uid(RoleId, BinData),
            %% 触发成就
            handle_achievement(RoleId, NewRelaList),
            %% 更新好友列表
            notify_client_rela_update(?RELA_UPDATE_REMOVE, RoleId, ?RELA_LIST_TYPE_SINGLE_FRIEND, [OtherRid]),
            save_rela_on_dict(RoleId, NewRelaList);
        true -> %% 非好友操作忽略
            skip
    end.

%% 单向好友转为陌生人,同时告知对方
be_del_friend_by_other_2(RoleId, BeDelId) ->
    RelaList = get_rela_on_dict(RoleId),
    PreRela = case lists:keyfind(BeDelId, #rela.other_rid, RelaList) of
                  #rela{} = PreRelaTmp -> PreRelaTmp;
                  _ -> #rela{role_id = RoleId, other_rid = BeDelId, rela_type = 0}
              end,
    NewRelaType = rela_type_change(PreRela#rela.rela_type, ?RELA_TYPE_NONE, del_friend),
    case PreRela#rela.rela_type == ?RELA_TYPE_SINGLE_FRIEND of
        true ->
            %% 日志
            lib_log_api:log_rela(RoleId, BeDelId, PreRela#rela.rela_type, ?BE_DEL_FRIEND, NewRelaType),
            db:execute(io_lib:format(?sql_save_role_rela,
                [RoleId, BeDelId, NewRelaType, PreRela#rela.intimacy, PreRela#rela.contribute, PreRela#rela.last_chat_time, PreRela#rela.ctime])),
            NewOneRela = PreRela#rela{rela_type = NewRelaType},
            NewRelaList = lists:keystore(BeDelId, #rela.other_rid, RelaList, NewOneRela),
            save_rela_on_dict(RoleId, NewRelaList),
            %% 更新亲密度
            SenMsg = [{BeDelId, ?RELA_TYPE_NONE, ?ASK_RESPONSE_REFUSE}],
            {ok, BinData} = pt_140:write(14021, [2, SenMsg]),
            lib_server_send:send_to_uid(RoleId, BinData),
            %% 触发成就
            handle_achievement(RoleId, NewRelaList),
            notify_client_rela_update(?RELA_UPDATE_REMOVE, RoleId, ?RELA_LIST_TYPE_SINGLE_FRIEND, [BeDelId]),
            ok;
        _ ->  %% 其他关系忽略
            skip
    end.

del_from_block_list(RoleId, BeCtrlId) ->
    case get_rela_with_other_on_dict(RoleId, BeCtrlId) of
        #rela{rela_type = RelaType} -> ok;
        _ -> RelaType = 0
    end,
    %% 日志
    lib_log_api:log_rela(RoleId, BeCtrlId, RelaType, ?DEL_BLACK, RelaType),
    %% notify_client_rela_update(?RELA_UPDATE_REMOVE, RoleId, ?RELA_LIST_TYPE_BLACK, [BeCtrlId]),
    ok.


%% 批量回复好友请求
reply_add_friend_ask(Player, ?REPLY_TYPE_AGREE) ->
    #player_status{id = RoleId, figure = Figure} = Player,
    #figure{name = RoleName} = Figure,
    CurFriendAskList = get_friend_ask_on_dict(RoleId),
    HasSortList = lists:keysort(#friend_ask.time, CurFriendAskList),
    AskIds = [AskId || #friend_ask{ask_id = AskId} <- HasSortList],
    CurRelaList = get_rela_on_dict(RoleId),
    Unixtime = utime:unixtime(),
    FriendNum = length(get_friend_on_dict(RoleId)),
    FriendNumLimit = ?FRIEND_NUM_LIMIT,
    {Res, NewRelaList, NewFriendAskList} =
        agree_add_friend_ask(AskIds, CurRelaList, CurFriendAskList, RoleId, RoleName, FriendNum, FriendNumLimit, Unixtime),
    save_rela_on_dict(RoleId, NewRelaList),
    save_friend_ask_on_dict(RoleId, NewFriendAskList),
    Res;
reply_add_friend_ask(Player, ?REPLY_TYPE_REFUSE) ->
    #player_status{id = RoleId, figure = Figure} = Player,
    #figure{name = RoleName} = Figure,
    FriendAskList = get_friend_ask_on_dict(RoleId),
    db_del_one_role_all_friend_ask(RoleId),
    BinData = lib_chat:make_tv(?MOD_RELA, 2, [RoleName]),
    F = fun(OneAsk) -> %% 通知被拒绝的玩家
        %% 更新好友缓存信息
        save_ets_rela(OneAsk#friend_ask.ask_id, RoleId, ?UPDATE_RESPONSE_STATUS, ?ASK_RESPONSE_REFUSE),
        SenMsg = [{RoleId, ?RELA_TYPE_SINGLE_FRIEND, ?ASK_RESPONSE_REFUSE}],
        {ok, BinData1} = pt_140:write(14021, [2, SenMsg]),
        AllBinData = <<BinData/binary, BinData1/binary>>,
        lib_server_send:send_to_uid(OneAsk#friend_ask.ask_id, AllBinData)
    end,
    [F(OneAsk) || OneAsk <- FriendAskList],
    save_friend_ask_on_dict(RoleId, []),
    ok;
reply_add_friend_ask(_, _) -> ok.

%% 回复单个玩家的好友请求
reply_one_add_friend_ask(AskId, RoleId, RoleName, ResponseType) ->
    FriendAskList = get_friend_ask_on_dict(RoleId),
    case lists:keyfind(AskId, #friend_ask.ask_id, FriendAskList) of
        #friend_ask{} -> %% 请求存在
            do_reply_one_add_friend_ask(AskId, RoleId, RoleName, FriendAskList, ResponseType);
        _ ->
            ?FAIL
    end.

% do_reply_one_add_friend_ask(AskId, RoleId, RoleName, _FriendAskList, ?REPLY_TYPE_PULL_BLACK) ->
%     pull_black(RoleId, RoleName, AskId);
do_reply_one_add_friend_ask(AskId, RoleId, RoleName, FriendAskList, ?REPLY_TYPE_AGREE) ->
    CheckList = [{friend_num_limit, RoleId}, {check_rela_type, RoleId, AskId}],
    case check_list(CheckList) of
        true ->
            Unixtime = utime:unixtime(),
            RelaList = get_rela_on_dict(RoleId),
            case lists:keyfind(AskId, #rela.other_rid, RelaList) of
                #rela{rela_type = RelaType} = PreRela ->
                    InFriendList = lists:member(RelaType, ?RELA_FRIEND_TYPES),
                    if
                        InFriendList ->
                            ?ERRCODE(err140_4_is_friend);
                        true ->
                            {NewRelaList, NewFriendAskList} = do_agree_add_friend_ask(RoleId, AskId, PreRela, RelaList, FriendAskList, RoleName, Unixtime),
                            save_rela_on_dict(RoleId, NewRelaList),
                            save_friend_ask_on_dict(RoleId, NewFriendAskList),
                            ?SUCCESS
                    end;
                _ -> %% 不存在关系
                    {NewRelaList, NewFriendAskList} = do_agree_add_friend_ask(RoleId, AskId, #rela{role_id = RoleId, other_rid = AskId}, RelaList, FriendAskList, RoleName, Unixtime),
                    save_rela_on_dict(RoleId, NewRelaList),
                    save_friend_ask_on_dict(RoleId, NewFriendAskList),
                    ?SUCCESS
            end;
        {false, Code} -> Code
    end;
do_reply_one_add_friend_ask(AskId, RoleId, RoleName, FriendAskList, ?REPLY_TYPE_REFUSE) ->
    db_del_friend_ask_by_asker_id(RoleId, AskId),
    NewFriendAskList = lists:keydelete(AskId, #friend_ask.ask_id, FriendAskList),
    save_friend_ask_on_dict(RoleId, NewFriendAskList),
    %% 更新好友缓存信息
    save_ets_rela(AskId, RoleId, ?UPDATE_RESPONSE_STATUS, ?ASK_RESPONSE_REFUSE),
    %% 通知被拒绝的玩家
    BinData = lib_chat:make_tv(?MOD_RELA, 2, [RoleName]),
    SenMsg = [{RoleId, ?RELA_TYPE_SINGLE_FRIEND, ?ASK_RESPONSE_REFUSE}],
    {ok, BinData1} = pt_140:write(14021, [2, SenMsg]),
    AllBinData = <<BinData/binary, BinData1/binary>>,
    lib_server_send:send_to_uid(AskId, AllBinData),
    ?SUCCESS;
do_reply_one_add_friend_ask(_AskId, _RoleId, _RoleName, _FriendAskList, _ResponseType) -> ?FAIL.

%% 清除亲密度
clean_each_other_intimacy(RoleId, OtherRid) ->
    RelaList = get_rela_on_dict(RoleId),
    PreRela = case lists:keyfind(OtherRid, #rela.other_rid, RelaList) of
        PreRelaTmp when is_record(PreRelaTmp, rela) -> PreRelaTmp;
        _ -> #rela{role_id = RoleId, other_rid = OtherRid, rela_type = 0}
    end,
    OldIntimacy = PreRela#rela.intimacy,
    update_intimacy_each_one(RoleId, OtherRid, - OldIntimacy, ?INTIMACY_TYPE_CLEAN, []).

%% 处理好友上下线通知
role_change_online_status(RoleId, Figure, OnlineFlag) ->
    %% 改成不读数据库,读内存操作
    % List = db:get_all(io_lib:format(?sql_select_rela_data_by_rela,
    %     [RoleId, util:link_list([?RELA_TYPE_FRIEND,?RELA_TYPE_FETTERS])])),
    FriendRelas = get_friend_on_dict(RoleId),
    SingleFriendRelas = get_single_friend_on_dict(RoleId),
    F = fun(OneIdX, RelaType) ->
        {ok, BinData} = pt_140:write(14009, [RoleId, Figure#figure.name, RelaType, OnlineFlag]),
        lib_server_send:send_to_uid(OneIdX, BinData)
    end,
    [F(OneId, OneRelaType) || #rela{other_rid = OneId, rela_type = OneRelaType} <- FriendRelas++SingleFriendRelas].

%% 同时更新好友双方的最后聊天时间
update_last_ctime_each_one(RoleId, OtherRid, Time) ->
    update_last_chat_time(RoleId, OtherRid, Time),
    update_last_chat_time(OtherRid, RoleId, Time).

%% 更新最后聊天时间，玩家在线，则更新dict
update_last_chat_time(RoleId, OtherRid, Time) ->
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) of
        true ->
            case is_self_pid(RoleId) of
                true ->
                    do_update_last_chat_time(RoleId, OtherRid, Time);
                false ->
                    lib_player:apply_cast(RoleId, ?APPLY_CAST, ?MODULE, update_last_chat_time, [RoleId, OtherRid, Time])
            end;
        false -> skip
    end.

do_update_last_chat_time(RoleId, OtherRid, Time) ->
    RelaList = get_rela_on_dict(RoleId),
    case lists:keyfind(OtherRid, #rela.other_rid, RelaList) of
        #rela{last_chat_utime = LastChatUTime} = PreRela ->
            case Time - LastChatUTime >= 120 of %% 定时更新一次最后聊天时间
                true ->
                    spawn(fun() ->
                        db:execute(io_lib:format(?sql_update_last_chat_time, [Time, RoleId, OtherRid, OtherRid, RoleId]))
                    end),
                    NewLastChatUTime = Time;
                false ->
                    NewLastChatUTime = LastChatUTime
            end,
            NewRela = PreRela#rela{last_chat_time = Time, last_chat_utime = NewLastChatUTime},
            NewRelaList = lists:keystore(OtherRid, #rela.other_rid, RelaList, NewRela),
            relation_last_chat:update_last_rela_list(RoleId, NewRela),
            save_rela_on_dict(RoleId, NewRelaList);
        _ -> %% 没有任何关系
            LastRelas = relation_last_chat:get_last_rela_on_dict(RoleId),
            NewLastRelas =
            case lists:keyfind(OtherRid, #last_rela_chat.other_id, LastRelas) of
                LastRela = #last_rela_chat{} ->
                    NewLastRela = LastRela#last_rela_chat{time = Time},
                    lists:keystore(OtherRid, #last_rela_chat.other_id, LastRelas, NewLastRela);
                _ ->
                    LastRela1 = #last_rela_chat{role_id = RoleId, other_id = OtherRid, time = Time},
                    lists:keystore(LastRela1, #last_rela_chat.other_id, LastRelas, LastRela1)
            end,
            NewLastRelas1 = lists:sort(fun(#last_rela_chat{time = Time1}, #last_rela_chat{time = Time2}) -> Time1 > Time2 end, NewLastRelas),
            Len = length(NewLastRelas1),
            case Len > ?LAST_RELA_CHAT_LIMIT of
                true ->
                    NewLastRelas2 = lists:sublist(NewLastRelas1, 1, ?LAST_RELA_CHAT_LIMIT); %% 前十个
                _ -> NewLastRelas2 = NewLastRelas1
            end,
            DeleteLastRelas = NewLastRelas1 -- NewLastRelas2,
            relation_last_chat:save_last_relas_on_dict(RoleId, NewLastRelas2),
            %% 数据库更新操作
            relation_last_chat:update_last_rela_on_db(delete, DeleteLastRelas, RoleId),
            relation_last_chat:update_last_rela_on_db(replace, NewLastRelas2, RoleId)
    end.

trigger_intimacy_add([], _Intimacy, _ObtainType, _SubType, _Extra) -> skip;
trigger_intimacy_add(RoleL, Intimacy, ObtainType, SubType, Extra) ->
    OnlineRoleL = [RoleId || RoleId <- RoleL, lib_player:is_online_global(RoleId)],
    do_trigger_intimacy_add(OnlineRoleL, Intimacy, ObtainType, SubType, Extra).

do_trigger_intimacy_add([], _Intimacy, _ObtainType, _SubType, _Extra) -> skip;
do_trigger_intimacy_add([T|L], Intimacy, ObtainType, SubType, Extra) ->
    do_trigger_intimacy_add_core(T, L, Intimacy, ObtainType, SubType, Extra),
    do_trigger_intimacy_add(L, Intimacy, ObtainType, SubType, Extra).

do_trigger_intimacy_add_core(_RoleId, [], _Intimacy, _ObtainType, _SubType, _Extra) -> ok;
do_trigger_intimacy_add_core(RoleId, [OtherRid|L], Intimacy, ObtainType, SubType, Extra) ->
    F1 = fun() ->
        lib_daily:increment_offline(RoleId, ?MOD_RELA, ObtainType, SubType)
    end,
    F2 = fun() ->
        lib_daily:increment_offline(OtherRid, ?MOD_RELA, ObtainType, SubType)
    end,
    F = fun() ->
        update_intimacy(?ACTIVE_CON, RoleId, OtherRid, Intimacy, ObtainType, SubType, Extra, F1),
        update_intimacy(?PASS_CON, OtherRid, RoleId, Intimacy, ObtainType, SubType, Extra, F2)
    end,
    catch db:transaction(F),
    do_trigger_intimacy_add_core(RoleId, L, Intimacy, ObtainType, SubType, Extra).

%% 同时更新好友双方的亲密度
update_intimacy_each_one(RoleId, OtherRid, Intimacy, ObtainType, Extra) ->
    update_intimacy(?ACTIVE_CON, RoleId, OtherRid, Intimacy, ObtainType, 0, Extra, []),
    update_intimacy(?PASS_CON, OtherRid, RoleId, Intimacy, ObtainType, 0, Extra, []).

%% 更新亲密度，玩家在线，则更新dict，不在线则直接操作数据库
update_intimacy(_, _RoleId, _OtherRid, 0, _ObtainType, _SubType, _Extra, _CallBack) -> skip; %% 变化为0不处理
update_intimacy(ConType, RoleId, OtherRid, Intimacy, ObtainType, SubType, Extra, CallBack) ->
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) of
        true ->
            case is_self_pid(RoleId) of
                true ->
                    RelaList = get_rela_on_dict(RoleId),
                    case lists:keyfind(OtherRid, #rela.other_rid, RelaList) of
                        #rela{rela_type = RelaType, intimacy = OldIntimacy, contribute = Contribute} = PreRela
                            when RelaType == ?RELA_TYPE_FRIEND orelse RelaType == ?RELA_TYPE_SINGLE_FRIEND ->
                            case data_rela:get_intimacy_obtain_cfg(ObtainType) of
                                #intimacy_obtain_cfg{times = TimesLim} ->
                                    HasTriggerTimes = lib_daily:get_count(RoleId, ?MOD_RELA, ObtainType, SubType),
                                    CanAdd = ?IF(TimesLim > HasTriggerTimes, true, false);
                                _ -> CanAdd = true
                            end,
                            case CanAdd of
                                true ->
                                    NewIntimacy = max(0, OldIntimacy + Intimacy),
                                    NewContribute = update_role_intimacy_contribute(ConType, Intimacy, Contribute),
                                    NewRela = PreRela#rela{intimacy = NewIntimacy, contribute = NewContribute},
                                    NewRelaList = lists:keystore(OtherRid, #rela.other_rid, RelaList, NewRela),
                                    save_rela_on_dict(RoleId, NewRelaList),
                                    %% 日志
                                    lib_log_api:log_intimacy(RoleId, OtherRid, OldIntimacy, ObtainType, NewIntimacy, Extra),

                                    RelaRoleInfo = make_up_friend_info(NewRela, OtherRid),
                                    notify_client_rela_update(?RELA_UPDATE_ADD, RoleId, RelaType, [RelaRoleInfo]),

                                    db_update_intimacy(RoleId, OtherRid, NewIntimacy, NewContribute),
                                    NewLv = data_relationship:get_intimacy_lv(NewIntimacy),
                                    OldLv = data_relationship:get_intimacy_lv(OldIntimacy),
                                    lib_task_api:friend_exp(RoleId,NewIntimacy),
                                    NewLv > OldLv andalso lib_player:apply_cast(self(), ?APPLY_CAST_STATUS, mate_api, when_upgrade_intimacy_lv, [NewLv, OtherRid]),
                                    case is_function(CallBack) of
                                        true -> CallBack();
                                        false -> skip
                                    end;
                                false -> skip
                            end;
                        _ -> skip
                    end;
                false ->
                    lib_player:apply_cast(RoleId, ?APPLY_CAST, ?MODULE, update_intimacy, [ConType, RoleId, OtherRid, Intimacy, ObtainType, SubType, Extra, CallBack])
            end;
        false ->
            case data_rela:get_intimacy_obtain_cfg(ObtainType) of
                #intimacy_obtain_cfg{} -> %% 触发类型加的亲密度 离线玩家不加
                    skip;
                _ ->
                    case get_rela_with_other_from_db(RoleId, OtherRid) of
                        false -> skip;
                        #rela{role_id = RoleId, other_rid = OtherRid, rela_type = RelaType, intimacy = OldIntimacy, contribute = Contribute}
                            when RelaType == ?RELA_TYPE_FRIEND ->
                            lib_log_api:log_intimacy(RoleId, OtherRid, OldIntimacy, ObtainType, max(0, OldIntimacy + Intimacy), Extra),
                            NewContribute = update_role_intimacy_contribute(ConType, Intimacy, Contribute),
                            db_update_intimacy(RoleId, OtherRid, max(0, OldIntimacy + Intimacy), NewContribute);
                        _ -> skip
                    end
            end
    end.

%% 获取A(RoleId)与B(OtherRid)双方关系中A的亲密度贡献
%% 暂时只处理线上及处于玩家进程的情况
get_intimacy_contribute_with_other(RoleId, OtherRid) ->
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) of
        true ->
            case is_self_pid(RoleId) of
                true ->
                    RelaList = get_rela_on_dict(RoleId),
                    case lists:keyfind(OtherRid, #rela.other_rid, RelaList) of
                        #rela{intimacy = Intimacy, contribute = Contribute} ->
                            {Intimacy, Contribute};
                        _ ->
                            {0, 0}
                    end;
                _ ->
                    {0, 0}
            end;
        _ ->
            {0, 0}
    end.
update_role_intimacy_contribute(_ConType, Intimacy, Contribute) when Intimacy < 0 ->
    max(0, Contribute + Intimacy);
update_role_intimacy_contribute(ConType, Intimacy, Contribute) ->
    if ConType == ?ACTIVE_CON ->
            NewContribute = Contribute + Intimacy;
        true ->
            NewContribute = Contribute
    end,
    NewContribute.

%% 通知客户端好友列表更新
notify_client_rela_update(?RELA_UPDATE_ADD, RoleId, RelaListType, UpdateL) ->
    Args = [{RelaListType, UpdateL}],
    {ok, BinData} = pt_140:write(14013, [Args]),
    lib_server_send:send_to_uid(RoleId, BinData);
notify_client_rela_update(?RELA_UPDATE_REMOVE, RoleId, RelaListType, UpdateL) ->
    Args = [{RelaListType, UpdateL}],
    {ok, BinData} = pt_140:write(14014, [Args]),
    lib_server_send:send_to_uid(RoleId, BinData);
notify_client_rela_update(_UpdateType, _RoleId, _RelaListType, _UpdateL) -> skip.

%% 关系转换
%% add_friend           添加好友
%% del_friend           删除好友
%% add_fetters          添加羁绊
%% del_fetters          删除羁绊
rela_type_change(PreRelaType, CurRelaType, CtrlType) ->
    if
        PreRelaType == ?RELA_TYPE_FRIEND andalso CtrlType == add_fetters ->
            ?RELA_TYPE_FETTERS;
        PreRelaType == ?RELA_TYPE_FETTERS andalso CtrlType == del_fetters ->
            ?RELA_TYPE_FRIEND;
        PreRelaType == ?RELA_TYPE_SINGLE_FRIEND andalso CtrlType == add_friend ->
            ?RELA_TYPE_FRIEND;
        PreRelaType == ?RELA_TYPE_FRIEND andalso CtrlType == del_friend ->
            ?RELA_TYPE_SINGLE_FRIEND;
        true -> CurRelaType
    end.

%% 获取好友亲密度加的属性
%% (如果这个属性需要实时变化需要在更新亲密度接口出发事件通知玩家)
%% 返回[{属性,值}]
get_intimacy_attr(RoleId, OtherRid) ->
    #rela{intimacy = Intimacy} = get_rela_with_other_from_db(RoleId, OtherRid),
    data_relationship:get_intimacy_attr(Intimacy).

%% 是否玩家自身进程
is_self_pid(RoleId) ->
    Pid = misc:get_player_process(RoleId),
    Pid =:= self().

%% 保存好友列表到玩家的进程字典中
save_rela_on_dict(RoleId, RelaList) ->
    case is_self_pid(RoleId) of
        true ->
            case put(?RELAS_KEY, RelaList) of
                undefined -> RelaList;
                _ -> RelaList
            end;
        false ->
            []
    end.

%% 获取进程字典中的好友列表信息
get_rela_on_dict(RoleId) ->
    case is_self_pid(RoleId) of
        true ->
            case get(?RELAS_KEY) of
                undefined ->
                    get_rela_on_db(RoleId);
                Value ->
                    Value
            end;
        false ->
            []
    end.
%% 获取数据库中的好友列表信息
get_rela_on_db(RoleId) ->
    RelaList = get_rela_from_db(RoleId),
    save_rela_on_dict(RoleId, RelaList).

%% 获得数据库中的好友信息
get_rela_from_db(RoleId) ->
    case db_rela_select_by_role_id(RoleId) of
        List when List =/= [] ->
            [make_record(rela, T) || T <- List];
        _ -> []
    end.
%% 获取进程字典中的好友请求信息
get_friend_ask_on_dict(RoleId) ->
    case is_self_pid(RoleId) of
        true ->
            case get(?FRIEND_ASK_LIST) of
                undefined ->
                    get_friend_ask_by_role_id_on_db(RoleId);
                Value ->
                    Value
            end;
        false ->
            []
    end.

%% 获取数据库中的好友请求信息
get_friend_ask_by_role_id_on_db(RoleId) ->
    FriendAskList = case db_get_friend_ask_by_role_id(RoleId) of
        List when List =/= [] ->
            [make_record(friend_ask, T) || T <- List];
        _ -> []
    end,
    save_friend_ask_on_dict(RoleId, FriendAskList).

%% 保存好友请求信息到进程字典
save_friend_ask_on_dict(RoleId, FriendAskList) ->
    case is_self_pid(RoleId) of
        true ->
            case put(?FRIEND_ASK_LIST, FriendAskList) of
                undefined -> FriendAskList;
                _ -> FriendAskList
            end;
        false ->
            []
    end.

check_list([]) -> true;
check_list([H|T]) ->
    case check(H) of
        true -> check_list(T);
        {false, Res} -> {false, Res}
    end.

check({lv_limit, RoleId}) ->
    OpenLv = lib_module:get_open_lv(?MOD_RELA),
    case lib_role:get_role_show(RoleId) of
        #ets_role_show{
           figure = #figure{lv = Lv},
           online_flag = ?ONLINE_ON
        } when Lv >= OpenLv ->
            true;
        _ -> {false, ?ERRCODE(lv_limit)}
    end;
check({add_self, RoleId, BeAskId}) ->
    ?IF(RoleId == BeAskId, {false, ?ERRCODE(err140_5_not_reply_myself_ask)}, true);
check({friend_num_limit, RoleId}) ->
    RelaList = get_friend_on_dict(RoleId),
    case length(RelaList) < ?FRIEND_NUM_LIMIT of
        true -> true;
        false -> {false, ?ERRCODE(err140_1_myself_friend_num_limit)}
    end;
check({check_rela_type, RoleId, BeAskId}) ->
    case get_rela_with_other_on_dict(RoleId, BeAskId) of
        #rela{rela_type = RelaType} ->
            IsFriend = lists:member(RelaType, ?RELA_FRIEND_TYPES),
            % IsBlack = lists:member(RelaType, ?RELA_BLACK_TYPES),
            if
                IsFriend == true ->
                    {false, ?ERRCODE(err140_4_is_friend)};
                % IsBlack == true ->
                %     {false, ?ERRCODE(err140_25_in_blacklist)};
                true -> true
            end;
        false -> true
    end;
check({other_side_online, BeAskId}) ->
    case lib_role:get_role_show(BeAskId) of
        #ets_role_show{online_flag = ?ONLINE_ON} -> true;
        _ -> {false, ?ERRCODE(err140_14_not_online)}
    end;
check({is_lover, OtherId, LoverId}) ->
    if
        OtherId == LoverId ->
            {false, ?ERRCODE(err140_is_lover)};
        true -> true
    end;
check({cross_role, ServerId, ToRoleId}) ->
    ToRoleSvrId = mod_player_create:get_real_serid_by_id(ToRoleId),
    if ToRoleSvrId == ServerId ->
            true;
        true ->
            {false, ?ERRCODE(err140_not_same_svr)}
    end;
check(_) ->
    {false, ?FAIL}.

%% ---------------- 数据库 ----------------------
%% 查询双向好友
db_rela_select_by_role_id(RoleId) ->
    db:get_all(io_lib:format(?sql_rela_select_by_role_id, [RoleId])).

%% 查询己方在对方所处的关系类型
db_rela_select_rela_type_from_other(RoleId, OtherId) ->
    db:get_row(io_lib:format(?sql_rela_select_rela_type_from_other, [OtherId, RoleId])).

%% 保存玩家关系
db_rela_insert(RoleId, OtherRid, RelaType, Unixtime) ->
    Sql = io_lib:format(?sql_save_role_rela, [RoleId, OtherRid, RelaType, 0, 0, 0, Unixtime]),
    db:execute(Sql).

%% 获取玩家的所有好友请求
db_get_friend_ask_by_role_id(RoleId) ->
    db:execute(io_lib:format(?sql_del_out_time_friend_ask_by_role_id, [max(utime:unixtime() - ?FRIEND_ASK_VAILD_TIME, 0), RoleId])),
    Sql = io_lib:format(?sql_get_friend_ask_by_role_id, [RoleId]),
    db:get_all(Sql).

%% 获取玩家的单条好友请求
db_get_friend_ask_by_oppo_role_id(RoleId, AskId) ->
    Sql = io_lib:format(?sql_get_friend_ask_by_oppo_role_id, [RoleId, AskId]),
    db:get_row(Sql).

%% 数据库添加新的好友请求
db_add_friend_ask(RoleId, AskId, Time) ->
    Sql = io_lib:format(?sql_add_friend_ask, [RoleId, AskId, Time]),
    db:execute(Sql).

%% 数据库删除好友请求
db_del_friend_ask_by_asker_id(RoleId, AskId) ->
    Sql = io_lib:format(?db_del_friend_ask_by_asker_id, [RoleId, AskId]),
    db:execute(Sql).

%% 数据库删除好友请求
db_del_one_role_all_friend_ask(RoleId) ->
    Sql = io_lib:format(?db_del_one_role_all_friend_ask, [RoleId]),
    db:execute(Sql).

%% 更新亲密度
db_update_intimacy(RoleId, OtherRid, Intimacy, Contribute) ->
    Sql = io_lib:format(?sql_update_intimacy, [Intimacy, Contribute, RoleId, OtherRid]),
    db:execute(Sql).


get_rela_and_intimacy_online(RoleId, IdList) ->
    RelaList = get_rela_on_dict(RoleId),
    F = fun(TmpRoleId) ->
        case lists:keyfind(TmpRoleId, #rela.other_rid, RelaList) of
            false -> {TmpRoleId, ?RELA_TYPE_NONE, 0};
            #rela{rela_type = RelaType, intimacy = Intimacy} -> {TmpRoleId, RelaType, Intimacy}
        end
    end,
    lists:map(F, IdList).

get_rela_and_intimacy_offline(RoleId, IdList) ->
    RelaList = get_rela_on_db(RoleId),
    F = fun(TmpRoleId) ->
        case lists:keyfind(TmpRoleId, #rela.other_rid, RelaList) of
            false -> {TmpRoleId, ?RELA_TYPE_NONE, 0};
            #rela{rela_type = RelaType, intimacy = Intimacy} -> {TmpRoleId, RelaType, Intimacy}
        end
    end,
    lists:map(F, IdList).

%% 获得ID列表的关系和亲密度
get_rela_and_intimacy(RoleId, IdList) ->
    case is_self_pid(RoleId) of
        true -> %% 在玩家进程则直接读取
            get_rela_and_intimacy_online(RoleId, IdList);
        _ ->
            Pid = misc:get_player_process(RoleId),
            case misc:is_process_alive(Pid) of
                true ->
                    case catch lib_player:apply_call(RoleId, ?APPLY_CALL, lib_relationship, get_rela_and_intimacy_online, [RoleId, IdList]) of
                        Res when is_list(Res) ->
                            Res;
                        _ ->
                            get_rela_and_intimacy_offline(RoleId, IdList)
                    end;
                false ->
                    get_rela_and_intimacy_offline(RoleId, IdList)
            end
    end.


get_mate_list(RoleId, MateList) ->
    lists:foldl(fun(#mate_info{id = MateId}, Acc) ->
        R = case get_rela_with_other_on_dict(RoleId, MateId) of
            #rela{intimacy = Intimacy, last_chat_time = LastChatUTime, ctime = CTime} ->
                #rela{intimacy = Intimacy, last_chat_time = LastChatUTime, ctime = CTime};
            _ ->
                #rela{}
        end,
        case lib_role:get_role_show(MateId) of
            EtsRole = #ets_role_show{} -> ok;
            _ -> EtsRole = #ets_role_show{id = MateId}
        end,
        [make_up_friend_info(R, EtsRole)|Acc]
     end, [], MateList).