%%%-----------------------------------
%%% module  : lib_scene_agent
%%% @author  : xyao
%%% email   : jiexiaowen@gmail.com
%%% Created : 2012.05.17
%%% description: 场景管理器
%%%-----------------------------------
-module(lib_scene_agent).
-export([
        get_scene_user/0,
        get_scene_user/1,
        get_scene_user/4,
        send_to_local_area_scene/4,
        send_to_local_scene/2,
        send_to_local_scene/1,
        clear_scene_room/1,
        clear_scene/0,
        clear_all_process_dict/0,
        put_user/1,
        get_user/1,
        del_user/1,
        save_to_area/4,
        del_to_area/4,
        get_area/2,
        send_to_any_area/3,
        get_dynamic_eff/1,
        send_dynamic_eff/4,
        change_dynamic_eff/2,
        move_send_and_getuser/3,
        move_send_and_getid/3,
        get_all_area_user/2,
        % get_att_target_info_by_id/1,
        create_mon_on_user/5
        ,player_apply_cast/5
        ,player_apply_cast/8
        ,kick_out_players/2
    ]).
-export([
        get_trace_info/2,
        get_trace_info_cast/5,
        %% get_scene_user_for_assist/7,
        get_scene_user_pid_area/4,
        get_scene_user_id_pid_area/4,
        get_scene_user_by_condition/4,
        get_scene_near_user/7,
        get_scene_near_user_whole_scene/4,
        timer_call_back/3,
        check_battle_broadcast/2,
        check_move_broadcast/2,
        check_broadcast/3
        ,ai_buff_timer/3
    ]).
-export([
        get_area_role_count/2
        ,save_area_role_count/3
        ,init_role_count/3
        ,get_role_count/0
        ,get_role_count/1
        ,get_mon_mgr_pid/0
        ,put_mon_mgr_pid/1
    ]).
%% for debug tool
-export([
        get_room_user_keys/1
        ,print_mon_attr/4
    ]).

-include("scene.hrl").
-include("server.hrl").
-include("attr.hrl").
-include("team.hrl").
-include("common.hrl").
-include("battle.hrl").
-include("dungeon.hrl").
-include("figure.hrl").

%% 获取场景所有玩家数据
get_scene_user() ->
    AllUser = get(),
    [ User || {_Key, User} <- AllUser, is_record(User, ets_scene_user)].

%% 获取场景玩家数据
get_scene_user(CopyId) ->
    AllUser = get_room_user_keys(CopyId),
    get_scene_user_helper(AllUser, CopyId, []).

get_scene_user(CopyId, X, Y, Distance) ->
    AllArea = lib_scene_calc:get_the_area(X, Y),
    AllUser = get_all_area_user(AllArea, CopyId),
    X1 = X + Distance,
    X2 = X - Distance,
    Y1 = Y + Distance,
    Y2 = Y - Distance,
    [
        User ||
        User = #ets_scene_user{x = UserX, y = UserY} <- AllUser,
        UserX >= X2 andalso UserX =< X1 andalso
        UserY >= Y2 andalso UserY =< Y1
    ].

get_scene_user_helper([], _, Data) ->
    Data;
get_scene_user_helper([Key | T], CopyId, Data) ->
    case get(Key) of
        undefined ->
            del_to_room(CopyId, Key),
            get_scene_user_helper(T, CopyId, Data);
        User ->
            get_scene_user_helper(T, CopyId, [User | Data])
    end.

%% 获取一定范围内玩家的pid
get_scene_user_pid_area(CopyId, X, Y, Distance) ->
    AllArea = lib_scene_calc:get_the_area(X, Y),
    AllUser = get_all_area_user(AllArea, CopyId),
    X1 = X + Distance,
    X2 = X - Distance,
    Y1 = Y + Distance,
    Y2 = Y - Distance,
    [
        Pid ||
        #ets_scene_user{pid = Pid, x = UserX, y = UserY} <- AllUser,
        UserX >= X2 andalso UserX =< X1 andalso
        UserY >= Y2 andalso UserY =< Y1
    ].

get_scene_user_id_pid_area(CopyId, X, Y, Distance) ->
    AllArea = lib_scene_calc:get_the_area(X, Y),
    AllUser = get_all_area_user(AllArea, CopyId),
    X1 = X + Distance,
    X2 = X - Distance,
    Y1 = Y + Distance,
    Y2 = Y - Distance,
    [
        [{UserId, ServerId}, Pid] ||
        #ets_scene_user{
            id = UserId, server_id = ServerId, pid = Pid,
            x = UserX, y = UserY,
            battle_attr = #battle_attr{hp = Hp}
        } <- AllUser,
        Hp > 0 andalso
        UserX >= X2 andalso UserX =< X1 andalso
        UserY >= Y2 andalso UserY =< Y1
    ].

%% 条件获取
get_scene_user_by_condition(CopyId, X, Y, Condition) ->
    AllArea = lib_scene_calc:get_the_area(X, Y),
    AllUser = get_all_area_user(AllArea, CopyId),
    [
        [UserId] ||
        User = #ets_scene_user{id = UserId, battle_attr = #battle_attr{hp = Hp}} <-AllUser,
        Hp > 0 andalso check_get_condition(User, Condition)
    ].

check_get_condition(_User, []) -> true;
check_get_condition(User, [{K, V}|Confdition])->
    Bool = case K of
        team_id ->
            User#ets_scene_user.team#status_team.team_id == V;
        _ ->
            true
    end,
    if
        Bool -> check_get_condition(User, Confdition);
        true -> false
    end.

%% 获取boss的归属玩家返回
%% 获取附近范围内的玩家
%% CallBack :: atom() 回调函数
get_scene_near_user(CallBack, Pid, ObjectId, CopyId, X, Y, Distance) ->
    AllArea = lib_scene_calc:get_the_area(X, Y),
    AllUser = get_all_area_user(AllArea, CopyId),
    X1 = X + Distance,
    X2 = X - Distance,
    Y1 = Y + Distance,
    Y2 = Y - Distance,
    UserIds = [
        UserId ||
        #ets_scene_user{id = UserId, x = UserX, y = UserY} <- AllUser,
        UserX >= X2 andalso UserX =< X1 andalso
        UserY >= Y2 andalso UserY =< Y1
    ],
    % old: Pid ! {CallBack, UserIds}.
    mon_mgr:send(Pid, ObjectId, {CallBack, UserIds}).

get_scene_near_user_whole_scene(CallBack, Pid, ObjectId, CopyId) ->
    AllUser = get_scene_user(CopyId),
    UserIds = [UserId || #ets_scene_user{id = UserId} <-AllUser],
    % old: Pid ! {CallBack, UserIds}.
    mon_mgr:send(Pid, ObjectId, {CallBack, UserIds}).

%% 给场景房间的玩家发信息
send_to_local_scene(CopyId, Bin = <<_/binary>>)->
    AllUser = get_room_user_keys(CopyId),
    priv_send_to_room_roles(CopyId, Bin, AllUser);
send_to_local_scene(CopyId, BinList = [_|_]) ->
    AllBin = list_to_binary(BinList),
    send_to_local_scene(CopyId, AllBin);
send_to_local_scene(_CopyId, _) ->
    ok.

priv_send_to_room_roles(CopyId, Bin, [UserKey | UserKeys]) ->
    case get(UserKey) of
        undefined ->
            del_to_room(CopyId, UserKey);
        % 调试使用，一般要求sid一定为有效值
        % #ets_scene_user{sid = _Sid, id = _Id, scene = _Scene} when not is_pid(_Sid) ->
        %     ?ERR("priv_send_to_room_roles/3: error sid:~w, id:~w, scene:~w", [_Sid, _Id, _Scene]);
        #ets_scene_user{sid = Sid, node = Node, online = ?ONLINE_ON} ->
            lib_server_send:send_to_sid(Node, Sid, Bin);
        _ ->
            ignore
    end,
    priv_send_to_room_roles(CopyId, Bin, UserKeys);
priv_send_to_room_roles(_CopyId, _Bin, []) ->
    ok.

%% 给场景玩家发信息
send_to_local_scene(Bin = <<_/binary>>) ->
    AllUser = get_scene_user(),
    priv_send_to_roles(Bin, AllUser),
    ok;
send_to_local_scene(BinList = [_|_]) ->
    AllBin = list_to_binary(BinList),
    send_to_local_scene(AllBin);
send_to_local_scene(_) ->
    ok.

priv_send_to_roles(Bin, [User | Users]) ->
    case User of
        % 调试使用，一般要求sid一定为有效值
        % #ets_scene_user{sid = Sid, id = _Id, scene = _Scene} when not is_pid(Sid) ->
        %     ?ERR("priv_send_to_roles/2: error sid:~w, id:~w, scene:~w", [Sid, _Id, _Scene]);
        #ets_scene_user{sid = Sid, node = Node, online = ?ONLINE_ON} ->
            lib_server_send:send_to_sid(Node, Sid, Bin);
        _ ->
            ignore
    end,
    priv_send_to_roles(Bin, Users);
priv_send_to_roles(_Bin, []) ->
    ok.

%% 给区域玩家发信息
send_to_local_area_scene(CopyId, X, Y, Bin) ->
    Area = lib_scene_calc:get_the_area(X, Y),
    send_to_any_area(Area, CopyId, Bin).

%% 通知踢玩家出场景
kick_out_players(CopyId, KV) ->
    [
        lib_scene:change_player_scene(Pid, 0, 0, 0, KV) ||
        #ets_scene_user{pid = Pid} <- case CopyId =:= all of
                                          true  -> get_scene_user();
                                          false -> get_scene_user(CopyId)
                                      end
    ].

%% ==========================  mod_scene_agent进程字典数据 ===================================

clear_scene_room(CopyId) ->
    lib_scene_object_agent:clear_scene_object(0, CopyId, 1),
    AllUser = get_scene_user(CopyId),
    [del_user(Id)|| #ets_scene_user{id=Id} <- AllUser],
    lib_scene_object_agent:del_all_area(CopyId),
    lib_scene_object_agent:del_trace(CopyId),
    lib_scene_object_agent:del_room(CopyId),
    % clear_area_mark(CopyId),
    clear_dynamic_eff(CopyId),
    %% 装备副本、经验副本之类超多怪物的场景，容易爆内存，
    %% 暂时在清理副本的时候，对于无人的场景进行内存GC
    RoleCount = get_role_count(),
    case RoleCount =< 0 of
        true  -> erlang:garbage_collect();
        false -> ignore
    end,
    ok.

%% 清理场景数据
clear_scene() ->
    lib_scene_object_agent:clear_scene_object(0, [], 0), %% stop所有场景对象进程
    clear_all_process_dict(),
    erlang:garbage_collect(),
    ok.

%% 清理场景中所有进程字典数据
clear_all_process_dict() ->
    F = fun (Key) when is_tuple(Key) ->
            Tag = element(1, Key),
            NeedErase = lists:member(Tag, [
                    %% 角色相关
                    user
                    ,room_roles
                    ,area_roles
                    ,area_role_count
                    ,role_move
                    ,role_battle
                    %% 场景元素
                    ,object
                    ,object_area
                    ,object_room
                    ,object_trace
                    ,coodinate
                    ,deploy
                    ,scene_eff
                ]),
            case NeedErase of
                true  -> erase(Key);
                false -> ignore
            end;
        %% 场景角色数记录不能清除掉，需要重新初始化
        (?SCENE_ROLE_COUNT) ->
            SceneRoleCount = get(?SCENE_ROLE_COUNT),
            NewSceneRoleCount = SceneRoleCount#scene_role_count{
                num = 0, change_time = utime:unixtime(), num_maps = #{}
            },
            set_scene_role_count(NewSceneRoleCount);
        %% 其他元素
        (_) ->
            ignore
    end,
    [F(Key) || Key <- get_keys()],
    ok.

%% 保存玩家数据
-spec put_user(SceneUser :: #ets_scene_user{}) -> AddCount :: integer().
put_user(SceneUser) ->
    #ets_scene_user{id=Id, copy_id=CopyId, x=X, y=Y} = SceneUser,
    UserKey = {user, Id},
    AddCount =
    case get(UserKey) of
        undefined ->
            save_to_room(CopyId, UserKey),
            save_to_area(CopyId, X, Y, UserKey),
            save_area_role_count(CopyId, X, Y, 1),
            change_role_count(CopyId, 1);
        #ets_scene_user{copy_id=OldCopyId, x=OldX, y=OldY} ->
            NewAreaId = lib_scene_calc:get_xy(X, Y),
            OldAreaId = lib_scene_calc:get_xy(OldX, OldY),
            if
                CopyId == OldCopyId andalso OldAreaId == NewAreaId ->
                    ignore;
                true ->
                    del_to_area(OldCopyId, OldAreaId, UserKey),
                    save_to_area(CopyId, NewAreaId, UserKey),
                    change_area_update_role_count(OldCopyId, OldAreaId, CopyId, NewAreaId)
            end,
            0
    end,
    put(UserKey, SceneUser),
    AddCount.

%% 获取玩家数据
get_user(UserKey) ->
    case get({user, UserKey}) of
        undefined -> [];
        User      -> User
    end.

%% 删除玩家数据
-spec del_user(Id :: integer()) -> MinusCount :: integer().
del_user(Id) ->
    UserKey = {user, Id},
    case get(UserKey) of
        undefined ->
            0;
        #ets_scene_user{copy_id = CopyId, x = X, y = Y} ->
            del_to_room(CopyId, UserKey),
            del_to_area(CopyId, X, Y, UserKey),
            save_area_role_count(CopyId, X, Y, -1),
            del_broadcast_data(Id),
            erase(UserKey),
            change_role_count(CopyId, -1)
    end.

%% ==========================  mod_scene_agent进程九宫格数据 ===================================
%% 删除在九宫格
del_to_area(CopyId, X, Y, UserKey) ->
    AreaId = lib_scene_calc:get_xy(X, Y),
    del_to_area(CopyId, AreaId, UserKey).

del_to_area(CopyId, AreaId, UserKey) ->
    AreaRolesKey = ?AREA_ROLES(AreaId, CopyId),
    case get(AreaRolesKey) of
        undefined ->
            skip;
        AreaRolesList ->
            NewAreaRolesList = lists:delete(UserKey, AreaRolesList),
            case NewAreaRolesList of
                [_ | _] -> put(AreaRolesKey, NewAreaRolesList);
                _       -> erase(AreaRolesKey)
            end
    end.

%% 添加角色Key信息到网格
save_to_area(CopyId, X, Y, UserKey) ->
    AreaId = lib_scene_calc:get_xy(X, Y),
    save_to_area(CopyId, AreaId, UserKey).

%% 添加角色Key信息到网格
%% 这里用list即可，因为只存不查找，用list最合适，不需要maps
%% 为了免去去重检查，所以调用的上层接口应保证是新增加的才调用
save_to_area(CopyId, AreaId, UserKey) ->
    AreaRolesKey = ?AREA_ROLES(AreaId, CopyId),
    case get(AreaRolesKey) of
        undefined ->
            put(AreaRolesKey, [UserKey]);
        AreaRolesList ->
            put(AreaRolesKey, [UserKey | AreaRolesList])
    end.

%% 获取9宫格玩家
get_area(AreaId, CopyId) ->
    AreaRolesKey = ?AREA_ROLES(AreaId, CopyId),
    case get(AreaRolesKey) of
        undefined -> [];
        AreaRolesList -> AreaRolesList
    end.

%% 更新周围网格玩家数量
save_area_role_count(CopyId, X, Y, AddCount) ->
    AreaIdList = lib_scene_calc:get_the_area(X, Y),
    [save_area_role_count(AreaId, CopyId, AddCount) || AreaId <- AreaIdList].

save_area_role_count(AreaId, CopyId, AddCount) ->
    AreaRoleCountKey = ?AREA_ROLE_COUNT(AreaId, CopyId),
    case get(AreaRoleCountKey) of
        undefined when AddCount > 0 ->
            put(AreaRoleCountKey, AddCount);
        undefined ->
            ignore;
        OldCount ->
            NewCount = max(0, OldCount + AddCount),
            case NewCount =:= 0 of
                true  -> erase(AreaRoleCountKey);
                false -> put(AreaRoleCountKey, NewCount)
            end
    end.

%% 获取格子玩家数量
%% AreaId, 即lib_scene_calc:get_xy(X, Y)取得的结果
get_area_role_count(AreaId, CopyId) ->
    AreaRoleCountKey = ?AREA_ROLE_COUNT(AreaId, CopyId),
    case get(AreaRoleCountKey) of
        undefined -> 0;
        Count -> Count
    end.

%% 同CopyId下转移网格，更新所有涉及网格的附近九格角色数量
change_area_update_role_count(OldCopyId, OldAreaId, NewCopyId, NewAreaId) ->
    {AddAreaIdList, DelAreaIdList} =
    case OldCopyId =:= NewCopyId of
        true  ->    %% 同场景原CopyId下角色跨网格
            {AddArea, DelArea, _} = lib_scene_calc:divide_broadcast_area(OldAreaId, NewAreaId),
            {AddArea, DelArea};
        false ->    %% 同场景变更CopyId
            AddArea = lib_scene_calc:get_nine_broadcast_area(NewAreaId),
            DelArea = lib_scene_calc:get_nine_broadcast_area(OldAreaId),
            change_role_count(NewCopyId,  1),
            change_role_count(OldCopyId, -1),
            {AddArea, DelArea}
    end,
    change_area_list_role_count(AddAreaIdList, NewCopyId,  1),
    change_area_list_role_count(DelAreaIdList, OldCopyId, -1).

%% 场景附近9格区域角色数量变更批量维护
change_area_list_role_count([AreaId | Others], CopyId, Change) ->
    change_area_role_count(AreaId, CopyId, Change),
    change_area_list_role_count(Others, CopyId, Change);
change_area_list_role_count(_, _CopyId, _Change) ->
    ok.

%% 更新单个AreaId对应的网格的附近9格角色数量
change_area_role_count(AreaId, CopyId, Change) ->
    RoleCount    = get_area_role_count(AreaId, CopyId),
    NewRoleCount = erlang:max(RoleCount + Change, 0),
    % ?INFO_TTY("now role count:~w, new role count:~w", [RoleCount, NewRoleCount]),
    case NewRoleCount =:= 0 of
        true  -> erase(?AREA_ROLE_COUNT(AreaId, CopyId));
        false -> put(?AREA_ROLE_COUNT(AreaId, CopyId), NewRoleCount)
    end.

%% ==========================  mod_scene_agent进程房间信息 ===================================
%% 保存角色的Key
%% 这里用list即可，因为只存不查找，用list最合适，不需要maps
%% 为了免去去重检查，所以调用的上层接口应保证是新增加的才调用
save_to_room(CopyId, UserKey) ->
    RoomRolesKey = ?ROOM_ROLES(CopyId),
    case get(RoomRolesKey) of
        undefined ->
            put(RoomRolesKey, [UserKey]);
        RoomRolesList ->
            put(RoomRolesKey, [UserKey | RoomRolesList])
    end.

%% 获取角色id
get_room_user_keys(CopyId) ->
    RoomRolesKey = ?ROOM_ROLES(CopyId),
    case get(RoomRolesKey) of
        undefined ->
            [];
        RoomRolesList ->
            RoomRolesList
    end.

%% 删除id
del_to_room(CopyId, UserKey) ->
    RoomRolesKey = ?ROOM_ROLES(CopyId),
    case get(RoomRolesKey) of
        undefined -> ignore;
        RoomRolesList ->
            NewRoomRolesList = lists:delete(UserKey, RoomRolesList),
            case NewRoomRolesList of
                [_ | _] -> put(RoomRolesKey, NewRoomRolesList);
                _       -> erase(RoomRolesKey)
            end
    end.

%% ==========================  mod_scene_agent进程九宫格广播 ===================================

%% 发送消息给部分区域
send_to_any_area([OneArea | Areas], CopyId, Bin = <<_/binary>>) ->
    AreaUserKeys = get_area(OneArea, CopyId),
    priv_send_to_area_roles(OneArea, CopyId, Bin, AreaUserKeys),
    send_to_any_area(Areas, CopyId, Bin);
send_to_any_area(Areas, CopyId, BinList = [_|_]) ->
    AllBin = list_to_binary(BinList),
    send_to_any_area(Areas, CopyId, AllBin);
send_to_any_area([], _CopyId, _Bin) ->
    ok.

priv_send_to_area_roles(OneArea, CopyId, Bin, [UserKey | UserKeys]) ->
    case get(UserKey) of
        undefined ->
            del_to_room(CopyId, UserKey),
            del_to_area(CopyId, OneArea, UserKey);
        #ets_scene_user{online = ?ONLINE_ON, node = Node, sid = Sid} ->
            lib_server_send:send_to_sid(Node, Sid, Bin);
        _ ->
            ignore
    end,
    priv_send_to_area_roles(OneArea, CopyId, Bin, UserKeys);
priv_send_to_area_roles(_OneArea, _CopyId, _Bin, []) ->
    ok.

%% 移动9宫格广播用
move_send_and_getuser(AreaList, CopyId, Bin = <<_/binary>>) ->
    lists:foldl(
        fun(Area, RoleListAcc) ->
                List = get_area(Area, CopyId),
                priv_send_and_getuser(List, CopyId, Area, Bin, RoleListAcc)
        end, [], AreaList);
move_send_and_getuser(AreaList, CopyId, BinList = [_ | _]) ->
    AllBin = list_to_binary(BinList),
    move_send_and_getuser(AreaList, CopyId, AllBin);
move_send_and_getuser(_AreaList, _CopyId, _Bin) ->
    [].

priv_send_and_getuser([UserKey | T], CopyId, Area, Bin, Acc) ->
    NewAcc =
    case get(UserKey) of
        undefined ->
            del_to_room(CopyId, UserKey),
            del_to_area(CopyId, Area, UserKey),
            Acc;
        User = #ets_scene_user{online = OnlineFlag, node = Node, sid = Sid} ->
            case OnlineFlag of
                ?ONLINE_ON -> lib_server_send:send_to_sid(Node, Sid, Bin);
                _ -> ignore
            end,
            % case Bin of
            %     <<_:32, 12003:16, _:8, _Id:64, _PL:16, _Plat:_PL/binary-unit:8, _SId:16, _SNum:16, BinRest/binary>> ->
            %         {_Figure, RestBin2} = pt:read_figure(BinRest),
            %         <<_X:16, _Y:16, _/binary>> = RestBin2,
            %         ?INFO("notice to [~w,~ts], [id:~w] enter to [~w,~w]",
            %             [User#ets_scene_user.id, User#ets_scene_user.figure#figure.name, _Id, _X, _Y]
            %         );
            %     _ -> ignore
            % end,
            [User | Acc]
    end,
    priv_send_and_getuser(T, CopyId, Area, Bin, NewAcc);
priv_send_and_getuser([], _, _, _, Acc) ->
    Acc.

%% 移动9宫格广播用
move_send_and_getid(AreaList, CopyId, Bin = <<_/binary>>) ->
    lists:foldl(
        fun(Area, IdAcc) ->
                AreaUserKeys = get_area(Area, CopyId),
                priv_send_and_getid(AreaUserKeys, CopyId, Area, Bin, IdAcc)
        end, [], AreaList);
move_send_and_getid(AreaList, CopyId, BinList = [_|_]) ->
    AllBin = list_to_binary(BinList),
    move_send_and_getid(AreaList, CopyId, AllBin);
move_send_and_getid(_, _, _) ->
    [].

priv_send_and_getid([UserKey | UserKeys], CopyId, Area, Bin, IdAcc) ->
    NewIdAcc =
    case get(UserKey) of
        undefined ->
            del_to_room(CopyId, UserKey),
            del_to_area(CopyId, Area, UserKey),
            IdAcc;
        _User = #ets_scene_user{online = OnlineFlag, node = Node, sid = Sid, id = Id} ->
            case OnlineFlag =:= ?ONLINE_ON of
                true  -> lib_server_send:send_to_sid(Node, Sid, Bin);
                false -> ignore
            end,
            % case Bin of
            %     <<_:32, 12004:16, _:8, _RemoveId:64>> ->
            %         ?INFO("notice to [~w,~ts], [id:~w] remove", [Id, _User#ets_scene_user.figure#figure.name, _RemoveId]);
            %     _ -> ignore
            % end,
            [Id | IdAcc]
    end,
    priv_send_and_getid(UserKeys, CopyId, Area, Bin, NewIdAcc);
priv_send_and_getid([], _, _, _, IdAcc) ->
    IdAcc.

%% 获取9宫格子玩家信息
get_all_area_user(Area, CopyId) ->
    List = lists:foldl(
        fun(A, L) ->
                get_area(A, CopyId) ++ L
        end,
        [], Area),
    get_scene_user_helper(List, CopyId, []).

%% 获取动态场景特效
get_dynamic_eff(CopyId) ->
    PDAreaKey = {scene_eff, CopyId},
    case get(PDAreaKey) of
        undefined -> [];
        M -> maps:to_list(M)
    end.

send_dynamic_eff(Node, RoleId, SceneId, CopyId) ->
    Eff = get_dynamic_eff(CopyId),
    {ok, BinData} = pt_120:write(12032, [SceneId, Eff]),
    case lib_scene:is_clusters_scene(SceneId) of
        true -> clusters_api:send_to_uid(Node, RoleId, BinData);
        false -> lib_server_send:send_to_uid(RoleId, BinData)
    end.

%% 改变动态场景特效
%% EffChangeValues = [{EffId, DelOrAdd},...]
change_dynamic_eff(CopyId, EffChangeValues) ->
    PDAreaKey = {scene_eff, CopyId},
    case get(PDAreaKey) of
        undefined ->
            Map = maps:from_list(EffChangeValues),
            put(PDAreaKey, Map);
        M ->
            Map = maps:from_list(EffChangeValues),
            NewMap = maps:merge(M, Map),
            put(PDAreaKey, NewMap)
    end,
    ok.

%% 清理动态场景特效
clear_dynamic_eff(CopyId) ->
    erase({scene_eff, CopyId}).

%% 获取玩家部分数据
get_trace_info(Sign, Id) ->
    case Sign of
        ?BATTLE_SIGN_PLAYER ->
            case get_user(Id) of
                #ets_scene_user{battle_attr=BA, x=X, y=Y} when BA#battle_attr.hp > 0 andalso
                BA#battle_attr.hide == 0 andalso BA#battle_attr.ghost == 0 -> {BA#battle_attr.hp, X, Y};
                _ -> false
            end;
        _ ->
            case lib_scene_object_agent:get_object(Id) of
                #scene_object{battle_attr=BA, x=X, y=Y} when BA#battle_attr.hp > 0 andalso
                BA#battle_attr.hide == 0 andalso BA#battle_attr.ghost == 0 -> {BA#battle_attr.hp, X, Y};
                _ -> false
            end
    end.

get_trace_info_cast(From, ObjectId, Sign, Id, Args) ->
    case get_trace_info(Sign, Id) of
        {_Hp, X, Y} ->
            % old: From ! {'trace_info_back', [Id, X, Y, Args]};
            mon_mgr:send(From, ObjectId, {'trace_info_back', [Id, X, Y, Args]});
        _ ->
            % old: From ! {'trace_info_back', false}
            mon_mgr:send(From, ObjectId, {'trace_info_back', false})
    end.

% 调用了已经失踪的接口，而且也没有使用了，有需要再重新实现一个
% %% 怪物追踪目标时获取目标信息
% get_att_target_info_by_id([MonAid, Key, AttType, MonGroup]) ->
%     AttInfo = case get_user(Key) of
%         Player when is_record(Player, ets_scene_user), Player#ets_scene_user.battle_attr#battle_attr.hp > 0 ->
%             case (MonGroup == 0 orelse MonGroup /= Player#ets_scene_user.battle_attr#battle_attr.group) andalso
%                 Player#ets_scene_user.battle_attr#battle_attr.ghost =/= 1 of
%                 true ->
%                     X0 = Player#ets_scene_user.x,
%                     Y0 = Player#ets_scene_user.y,
%                     Hp0 = Player#ets_scene_user.battle_attr#battle_attr.hp,
%                     {true, X0, Y0, Hp0, Player};
%                 false ->
%                     false
%             end;
%         _ ->
%             false
%     end,
%     mod_mon_active:trace_info_back(MonAid, AttType, AttInfo).

%% 定时器返回
timer_call_back(TimeRef, [{Sign, Id, EventId, Args}|T], EtsScene) ->
    User = case Sign of
        ?BATTLE_SIGN_PLAYER -> get_user(Id);
        _ -> lib_scene_object_agent:get_object(Id)
    end,
    case User of
        [] -> skip;
        _  -> lib_battle:timer_call_back(User, EventId, Args, EtsScene, TimeRef)
    end,
    timer_call_back(TimeRef, T, EtsScene);
timer_call_back(_TimeRef, [], _) -> ok.

%%
%% 2017-06-13
%% @author huangyongxing
%% 根据压力测试和实际运行数据，单场景人数作出保守限制如下：
%% 通常单对单战斗场景限制总人数250人
%% 温泉这种无战斗场景单场景限制300人
%% 多人战斗场景，限制在200人以下
%%
%% （压力测试中，缺少宠物、法宝类的战斗单位，
%% 单对单战斗场景能达到600人以上
%% 多人打怪场景每场景开300人以上战斗能平稳运行
%% 正式服实际运行单对单战斗，单场景极限能承受500~600人左右，风险极高
%% ）
%%

%% 检查战斗是否广播
%% 大约按这个公式来计算，由于调用频繁，所以不直接用公式，
%% 而是按限定的并发广播消息量，计算出一系列的RoleCount、间隔广播数对应关系
%% 当Interval为1的时候，使用true即可，这样省去人少时的进程字典读写操作
%% F = fun(MaxSend) ->
%%     G = fun(I, MaxS) -> math:sqrt(I * MaxS) end,
%%     FN = fun(N, Fun) when N > 1 -> Fun(N-1, Fun) + Fun(N-2, Fun); (1, _) -> 1; (0, _) -> 1 end,
%%     List = [FN(N, FN) || N <- lists:seq(1, 10)],
%%     [ {trunc(G(I, MaxSend)), trunc(I)} || I <- List]
%% end.
%% F(600).
%% 根据人数判断是否可广播
check_battle_broadcast(RoleCount, UserId) ->
    Interval = if
        RoleCount =:= undefined -> true;
        RoleCount =<  12        -> true; %% 28
        RoleCount =<  34        -> 2;
        RoleCount =<  45        -> 3;
        RoleCount =<  54        -> 5;
        RoleCount =<  69        -> 8;
        RoleCount =<  88        -> 13;
        RoleCount =< 112        -> 21;
        RoleCount =< 142        -> 34;
        RoleCount =< 181        -> 55;
        true -> false
    end,
    case Interval of
        true  -> true;
        false -> false;
        _     -> check_broadcast(role_battle, UserId, Interval)
    end.

%% 检查移动是否广播
%% 与战斗广播数计算相同公式
%% F(800).
check_move_broadcast(_RoleCount, _UserId) ->
    true.
% 移动应有状态超时，在繁忙时，如果有新数据包，就覆盖掉旧数据包；
% 如果一段时间后无新数据包，就应该主动把最后的数据包广播出去
% （这样玩家位置才能正确同步，移动减频与战斗减频还是有不一样的影响，需要再思考更合适的方式）
% check_move_broadcast(RoleCount, UserId) ->
%     Interval = if
%         RoleCount =:= undefined -> true;
%         RoleCount =<  12        -> true; %% 28
%         RoleCount =<  40        -> 2;
%         RoleCount =<  48        -> 3;
%         RoleCount =<  63        -> 5;
%         RoleCount =<  80        -> 8;
%         RoleCount =< 101        -> 13;
%         RoleCount =< 129        -> 21;
%         RoleCount =< 164        -> 34;
%         RoleCount =< 209        -> 55;
%         true -> false
%     end,
%     case Interval of
%         true  -> true;
%         false -> false;
%         _     -> check_broadcast(role_move, UserId, Interval)
%     end.

%% 检查是否广播（主要针对广播和战斗消息，在线高时量比较大）
%% Key      : role_move | role_battle
%% UserId   : 角色id
%% Interval : 间隔
check_broadcast(Key, UserId, Interval) ->
    {N, LastInterval} = case get({Key, UserId}) of
        {_, _} = Info -> Info;
        _ -> {0, 0}
    end,
    if
        LastInterval =/= Interval ->
            put({Key, UserId}, {1, Interval}),
            true;
        N rem Interval =:= 0 ->
            put({Key, UserId}, {1, Interval}),
            true;
        true ->
            put({Key, UserId}, {N + 1, Interval}),
            false
    end.

del_broadcast_data(UserId) ->
    erase({role_move, UserId}),
    erase({role_battle, UserId}).

create_mon_on_user(CopyId, Data, Mid, IsActive, OtherArgs) ->
    [OX, OY, Distance, N] = Data,
    Users = get_scene_user(CopyId, OX, OY, Distance),
    LuckyUsers = urand:get_rand_list(N, Users),
    [
        lib_mon:async_create_mon(Mid, Scene, ScenePoolId, X, Y, IsActive, CopyId, 1, OtherArgs)
        || #ets_scene_user{scene = Scene, scene_pool_id = ScenePoolId, x = X, y = Y} <- LuckyUsers
    ].

%% 对场景里的玩家执行lib_player:apply_cast/5
player_apply_cast(CopyId, CastType, Module, Fun, Args) ->
    Users = get_scene_user(CopyId),
    [
        begin
                if
                    Node =:= none ->
                        lib_player:apply_cast(Pid, CastType, Module, Fun, Args);
                    true ->
                        rpc:cast(Node, lib_player, apply_cast, [Pid, CastType, Module, Fun, Args])
                end
        end || #ets_scene_user{pid = Pid, node = Node} <- Users
    ],
    ok.

player_apply_cast(CopyId, X, Y, Distance, CastType, Module, Fun, Args) ->
    Users = get_scene_user(CopyId, X, Y, Distance),
    [
        begin
                if
                    Node =:= none ->
                        lib_player:apply_cast(Pid, CastType, Module, Fun, Args);
                    true ->
                        rpc:cast(Node, lib_player, apply_cast, [Pid, CastType, Module, Fun, Args])
                end
        end || #ets_scene_user{pid = Pid, node = Node} <- Users
    ],
    ok.

%% ================= 在线人数相关记录管理 ====================

init_role_count(SceneId, PoolId, SceneType) ->
    SceneKey = {SceneId, PoolId},
    InitSceneRoleCount = #scene_role_count{key = SceneKey, type = SceneType, change_time = utime:unixtime()},
    set_scene_role_count(InitSceneRoleCount).

%% 更新场景玩家数量表
change_role_count(CopyId, ChangeNum) when ChangeNum =/= 0 ->
    #scene_role_count{num = OldNum, num_maps = NumMaps} = SceneRoleCount = get(?SCENE_ROLE_COUNT),
    NowTime     = utime:unixtime(),
    CopyOldNum  = maps:get(CopyId, NumMaps, 0),
    CopyNewNum  = max(0, CopyOldNum + ChangeNum),
    NewNumMaps  =
    case CopyNewNum =:= 0 andalso CopyOldNum > 0 of
        true  -> maps:remove(CopyId, NumMaps);
        false -> NumMaps#{CopyId => CopyNewNum}
    end,
    NewSceneRoleCount = SceneRoleCount#scene_role_count{
        change_time = NowTime,
        num = max(0, OldNum + ChangeNum),
        num_maps = NewNumMaps
    },
    set_scene_role_count(NewSceneRoleCount),
    ChangeNum;
change_role_count(_, _) ->
    0.

%% 获取本场景进程角色数
get_role_count() ->
    #scene_role_count{num = RoleCount} = get(?SCENE_ROLE_COUNT),
    RoleCount.

%% 获取本场景进程某房间角色数
get_role_count(CopyId) ->
    #scene_role_count{num_maps = NumMaps} = get(?SCENE_ROLE_COUNT),
    maps:get(CopyId, NumMaps, 0).

%% 保存场景人数信息
set_scene_role_count(SceneRoleCount) ->
    erlang:put(?SCENE_ROLE_COUNT, SceneRoleCount),
    ets:insert(?SCENE_ROLE_COUNT, SceneRoleCount).

%% 进程字典中取得关联的mon_mgr进程的pid
%% （SceneState中也有存储）
get_mon_mgr_pid() ->
    erlang:get(mon_mgr_pid).

put_mon_mgr_pid(MonMgrPid) ->
    erlang:put(mon_mgr_pid, MonMgrPid).

%% ---------------------------------------------------------------------------
%% @doc 给玩家加buff，buff结束后要执行特殊效果
-spec ai_buff_timer(TimeRef, TimeoutArgs, EtsScene) -> ok when
    TimeRef     :: reference(),
    TimeoutArgs :: #add_buff_to_role_args{},
    EtsScene    :: #ets_scene{}.
%% ---------------------------------------------------------------------------
ai_buff_timer(_TimeRef, TimeoutArgs, EtsScene) ->
    #add_buff_to_role_args{
        role_id_list = RoleIdList, effect = Effect
    } = TimeoutArgs,
    buff_effect(Effect, EtsScene, RoleIdList),
    %% 要取消buff吗？
    ok.

%% buff效果
buff_effect([], _EtsScene, _RoleIdList) ->
    ok;
buff_effect([Effect | T], EtsScene, RoleIdList) ->
    buff_effect_do(Effect, EtsScene, RoleIdList),
    buff_effect(T, EtsScene, RoleIdList).

buff_effect_do(_Effect, _EtsScene, []) ->
    ok;
buff_effect_do(Effect = #create_mon_args{}, EtsScene, [RoleId | T]) ->
     case get_user(RoleId) of
        #ets_scene_user{
            scene = Scene, scene_pool_id = PoolId, x = X, y = Y, copy_id = CopyId
        } ->
            #create_mon_args{
                rate = PreMil, mid = Mid, is_active = IsActive, args = OtherArgs
            } = Effect,
            OtherArgs2 = case data_mon:get(Mid) of
                #mon{auto = 1} ->
                    [{auto_lv, max(1, util:get_world_lv())} | OtherArgs];
                #mon{auto = 2} ->
                    [{auto_lv, max(1, util:get_world_lv())} | OtherArgs];
                _ ->
                    OtherArgs
            end,
            case PreMil ==0 orelse PreMil >= 10000 orelse urand:rand(1, 10000) < PreMil of
                true ->
                    mod_scene_object_create:create_object_cast(
                        ?BATTLE_SIGN_MON, Mid, Scene, PoolId, X, Y, IsActive
                        ,CopyId, 1, OtherArgs2
                    );
                false ->
                    ok
            end;
        _ -> %% 玩家离开场景了，不触发效果
            ok
    end,
    buff_effect_do(Effect, EtsScene, T).

%% gm打印怪物属性
print_mon_attr(RoleId, Sid, CopyId, Mid) ->
    case lib_scene_object_agent:get_scene_mon_by_mids(CopyId, [Mid], #scene_object.battle_attr) of
        [#battle_attr{attr = Attrs}|_] ->
            MsgSend = io_lib:format("怪物属性: ~p ", [lists:keysort(1, attr_conv:to_attr_tuple_list(Attrs))]),
            PackData = [10, 0, 0, "", RoleId, #figure{}, MsgSend, "", 0, utime:unixtime(), 0, 0],
            {ok, BinData} = pt_110:write(11001, PackData),
            lib_server_send:send_to_sid(Sid, BinData),
            ok;
        _ ->
            ok
    end.
