%%%-----------------------------------
%%% module  : lib_scene_calc
%%% @author  : xyao
%%% email   : jiexiaowen@gmail.com
%%% Created : 2011.07.22
%%% description: 场景广播算法,在场景进程中使用
%%%-----------------------------------
-module(lib_scene_calc).
-export([
        get_area_for_trace/6,
        get_any_for_trace_cast/8,
        get_any_for_trace_cast_mon_first/9,
        get_area_for_trace_mon_first/7,
        get_area_for_trace_offline/4,
        get_ellipse_object_for_battle/6,
        get_rectangle_object_for_battle/8,
        get_sector_object_for_battle/9,
        get_annulus_object_for_battle/6,
        get_scene_object_for_assist/7,
        get_scene_mon_for_assist/7
        ,get_all_scene_trace/5
        ,ai_add_buff_to_role/7
        ,ai_kill_scene_role/8
    ]).

-export([
        get_xy/2,
        move_broadcast/10,
        move_broadcast/11,
        is_area_scene/4,
        get_broadcast_user/3,
        get_broadcast_object/3,
        get_the_area/2,
        pixel_to_logic_coordinate/2,
        logic_to_pixel_coordinate/2
        ,find_a_dummy/1

        ,x_pixel_to_logic/1
        ,y_pixel_to_logic/1
        ,x_logic_to_pixel/1
        ,y_logic_to_pixel/1
    ]).

%% 场景进程内部接口，不要使用
-export([
        divide_broadcast_area/2
        ,get_nine_broadcast_area/1
    ]).

-compile({inline, [
            divide_broadcast_area/2
            ,get_nine_broadcast_area/1
        ]}).

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

% find_min_attr([#ets_scene_user{} = Object|T], AttrType, MinAttrValue, Result) ->
%     #ets_scene_user{id=Id, x=X, y=Y, pid=Pid, battle_attr=#battle_attr{hp=Hp, attr = Attr}} = Object,
%     AttrValue = case AttrType of
%         ?HP -> Hp;
%         _ -> lib_player_attr:get_value_by_id(AttrType, Attr)
%     end,
%     case AttrValue < MinAttrValue of
%         true -> find_min_attr(T, AttrType, AttrValue, {?BATTLE_SIGN_PLAYER, Id, Pid, X, Y});
%         false -> find_min_attr(T, AttrType, MinAttrValue, Result)
%     end;
% find_min_attr([#scene_object{} = Object|T], AttrType, MinAttrValue, Result) ->
%     #scene_object{sign=Sign, aid=Aid, id=Id, x=X, y=Y, battle_attr=#battle_attr{hp=Hp, attr = Attr}} = Object,
%     AttrValue = case AttrType of
%         ?HP -> Hp;
%         _ -> lib_player_attr:get_value_by_id(AttrType, Attr)
%     end,
%     case AttrValue < MinAttrValue of
%         true -> find_min_attr(T, AttrType, AttrValue, {Sign, Id, Aid, X, Y});
%         false -> find_min_attr(T, AttrType, MinAttrValue, Result)
%     end;
% find_min_attr([], _AttrType, MinAttrValue, Result) -> {MinAttrValue, Result}.
%
% find_max_attr([#ets_scene_user{} = Object|T], AttrType, MaxAttrValue, Result) ->
%     #ets_scene_user{id=Id, x=X, y=Y, pid=Pid, battle_attr=#battle_attr{hp=Hp, attr = Attr}} = Object,
%     AttrValue = case AttrType of
%         ?HP -> Hp;
%         _ -> lib_player_attr:get_value_by_id(AttrType, Attr)
%     end,
%     case AttrValue >= MaxAttrValue of
%         true -> find_max_attr(T, AttrType, AttrValue, {?BATTLE_SIGN_PLAYER, Id, Pid, X, Y});
%         false -> find_max_attr(T, AttrType, MaxAttrValue, Result)
%     end;
% find_max_attr([#scene_object{} = Object|T], AttrType, MaxAttrValue, Result) ->
%     #scene_object{sign=Sign, aid=Aid, id=Id, x=X, y=Y, battle_attr=#battle_attr{hp=Hp, attr = Attr}} = Object,
%     AttrValue = case AttrType of
%         ?HP -> Hp;
%         _ -> lib_player_attr:get_value_by_id(AttrType, Attr)
%     end,
%     case AttrValue >= MaxAttrValue of
%         true -> find_max_attr(T, AttrType, AttrValue, {Sign, Id, Aid, X, Y});
%         false -> find_max_attr(T, AttrType, MaxAttrValue, Result)
%     end;
% find_max_attr([], _AttrType, MaxAttrValue, Result) -> {MaxAttrValue, Result}.

find_close_obj([#ets_scene_user{id=Id, pid=Pid, x=X, y=Y} |T], OX, OY, MinDis, Result) ->
    ThisDis = abs(OX-X)+abs(OY-Y),
    case ThisDis < MinDis of
        true  -> find_close_obj(T, OX, OY, ThisDis, {?BATTLE_SIGN_PLAYER, Id, Pid, X, Y});
        false -> find_close_obj(T, OX, OY, MinDis, Result)
    end;
find_close_obj([#scene_object{sign=Sign, id=Id, aid=Aid, x=X, y=Y} |T], OX, OY, MinDis, Result) ->
    ThisDis = abs(OX-X)+abs(OY-Y),
    case ThisDis < MinDis of
        true  -> find_close_obj(T, OX, OY, ThisDis, {Sign, Id, Aid, X, Y});
        false -> find_close_obj(T, OX, OY, MinDis, Result)
    end;
find_close_obj([], _, _, MinDis, Result) -> {MinDis, Result}.

% find_min_power([#ets_scene_user{} = Object|T], MinAttrValue, Result) ->
%     #ets_scene_user{id=Id, x=X, y=Y, pid=Pid, battle_attr=#battle_attr{combat_power = Power}} = Object,
%     case Power < MinAttrValue of
%         true -> find_min_power(T, Power, {?BATTLE_SIGN_PLAYER, Id, Pid, X, Y});
%         false -> find_min_power(T, MinAttrValue, Result)
%     end;
% find_min_power([#scene_object{} = Object|T], MinAttrValue, Result) ->
%     #scene_object{sign=Sign, aid=Aid, id=Id, x=X, y=Y, battle_attr=#battle_attr{combat_power = Power}} = Object,
%     case Power < MinAttrValue of
%         true -> find_min_power(T, Power, {Sign, Id, Aid, X, Y});
%         false -> find_min_power(T, MinAttrValue, Result)
%     end;
% find_min_power([], MinAttrValue, Result) -> {MinAttrValue, Result}.

% %% 2 1       5 6
% %% 4 3  <->  3 4
% %% 6 5       1 2
% get_pos_seq(1) -> [5,6,3,4,1,2];
% get_pos_seq(2) -> [5,6,3,4,1,2];
% get_pos_seq(3) -> [3,4,5,6,1,2];
% get_pos_seq(4) -> [3,4,5,6,1,2];
% get_pos_seq(5) -> [1,2,3,4,5,6];
% get_pos_seq(6) -> [1,2,3,4,5,6];
% get_pos_seq(_) -> [].
%
% %% 同一排对方的阵位列表
% get_same_row_pos_seq(1) -> [5,6];
% get_same_row_pos_seq(2) -> [5,6];
% get_same_row_pos_seq(3) -> [3,4];
% get_same_row_pos_seq(4) -> [3,4];
% get_same_row_pos_seq(5) -> [1,2];
% get_same_row_pos_seq(6) -> [1,2];
% get_same_row_pos_seq(_Pos) -> [].

% get_pos_target([H|T], CopyId, Id, Group, Args) ->
%     case lib_scene_object_agent:get_from_deploy(CopyId, H) of
%         false -> get_pos_target(T, CopyId, Id, Group, Args);
%         L ->
%             case [EId ||{EId, EGroup} <- L, EGroup /= Group, Id /= EId] of
%                 [] -> get_pos_target(T, CopyId, Id, Group, Args);
%                 Ids ->
%                     F = fun(_Xtmp, _Ytmp, Hide) -> Hide == 0 end,
%                     Objects = lib_scene_object_agent:get_scene_object_by_ids(Ids, all),
%                     case  filter_battle_obj(Objects, F, Args, [], undefined) of
%                         [Object|_] ->
%                             {Object#scene_object.sign, Object#scene_object.id, Object#scene_object.aid, Object#scene_object.x, Object#scene_object.y};
%                         _ -> get_pos_target(T, CopyId, Id, Group, Args)
%                     end
%             end
%     end;
% get_pos_target([], _CopyId, _Id, _Group, _Args) -> false.

find_a_dummy([#scene_object{sign = ?BATTLE_SIGN_DUMMY, id=Id, aid=Aid, x=X, y=Y}|_]) ->
    {?BATTLE_SIGN_DUMMY, Id, Aid, X, Y};
find_a_dummy([_|T]) ->
    find_a_dummy(T);
find_a_dummy([]) -> false.

get_any_for_trace_cast(From, ObjectId, CopyId, X, Y, Area, Args, SelectOp) ->
    Res = get_area_for_trace(CopyId, X, Y, Area, Args, SelectOp),
    % old: From ! {'get_for_trace', Res}.
    mon_mgr:send(From, ObjectId, {'get_for_trace', Res}).

%% 优先找怪FindType:1怪优先|2人优先|3只找怪
get_any_for_trace_cast_mon_first(From, ObjectId, CopyId, X, Y, Area, Args, SelectOp, FindType) ->
    Res = get_area_for_trace_mon_first(CopyId, X, Y, Area, Args, SelectOp, FindType),
    mon_mgr:send(From, ObjectId, {'get_for_trace', Res}).

%% 从九宫格中获取可攻击的对象(离线挂机只选怪物挂机)
get_area_for_trace_offline(CopyId, X, Y, Args) ->
    AllArea = get_the_area(X, Y),
    Area = 600,
    Xmax = X + Area,
    Xmin = X - Area,
    Ymax = Y + Area,
    Ymin = Y - Area,
    F = fun(Xtmp, Ytmp, Hide) -> Hide == 0 andalso not (Xtmp > Xmax orelse Xtmp < Xmin orelse Ytmp > Ymax orelse Ytmp < Ymin) end,
    AllObjects  = lib_scene_object_agent:get_all_area_object(AllArea, CopyId),
    case  filter_battle_objs(AllObjects, F, Args, [], undefined) of
        [] -> false;
        [Object] -> {Object#scene_object.sign, Object#scene_object.id, Object#scene_object.aid, Object#scene_object.x, Object#scene_object.y};
        ObjectList when is_list(ObjectList)->
            [Object|_]  = ulists:list_shuffle(ObjectList),
            {Object#scene_object.sign, Object#scene_object.id, Object#scene_object.aid, Object#scene_object.x, Object#scene_object.y};
        _ -> false
    end.
%% F = fun(Xtmp, Ytmp, Hide) -> Hide == 0 andalso not (Xtmp > Xmax orelse Xtmp < Xmin orelse Ytmp > Ymax orelse Ytmp < Ymin) end,
%% AllUsers    = lib_scene_agent:get_all_area_user(AllArea, CopyId),
%% UserInfo = case filter_battle_objs(AllUsers, F, Args, []) of
%%                [User|_] -> {?BATTLE_SIGN_PLAYER, User#ets_scene_user.id, User#ets_scene_user.pid, User#ets_scene_user.x, User#ets_scene_user.y};
%%                _ -> false
%%            end,
%% case UserInfo of
%%     false ->
%%         AllObjects  = lib_scene_object_agent:get_all_area_object(AllArea, CopyId),
%%         case  filter_battle_objs(AllObjects, F, Args, []) of
%%             [Object|_] -> {Object#scene_object.sign, Object#scene_object.id, Object#scene_object.aid, Object#scene_object.x, Object#scene_object.y};
%%             _ -> false
%%         end;
%%     _ -> UserInfo
%% end.

get_area_for_trace(CopyId, X, Y, Area, Args, SelectOp) ->
    AllArea = get_the_area(X, Y),
    %% Area = 600,
    Xmax = X + Area,
    Xmin = X - Area,
    Ymax = Y + Area,
    Ymin = Y - Area,
    F = fun(Xtmp, Ytmp, Hide) -> Hide == 0 andalso not (Xtmp > Xmax orelse Xtmp < Xmin orelse Ytmp > Ymax orelse Ytmp < Ymin) end,
    AllUsers = lib_scene_agent:get_all_area_user(AllArea, CopyId),
    UserInfo = case filter_battle_objs(AllUsers, F, Args, [], SelectOp) of
        [#ets_scene_user{id=IdU, pid=PidU, x=XU, y=YU} |_] -> {?BATTLE_SIGN_PLAYER, IdU, PidU, XU, YU};
        _ -> false
    end,
    case UserInfo of
        false ->
            %% 宠物副本入侵怪需要跟踪场景守护和妖宠
            case SelectOp of
                {select_all_exclude, ExcludeMonId} ->
                    AllObjects0 = lib_scene_object_agent:get_scene_object(CopyId),
                    AllObjects  = lists:filter(fun(#scene_object{config_id = ConfigId}) -> ConfigId =/= ExcludeMonId end, AllObjects0);
                _ ->
                    AllObjects  = lib_scene_object_agent:get_all_area_object(AllArea, CopyId)
            end,
            Ret = filter_battle_objs(AllObjects, F, Args, [], SelectOp),
            case Ret of
                [#scene_object{scene = SceneId, sign=SignM, id=IdM, aid=AidM, x=XM, y=YM}|_] = SceneMons ->
                    IsDunPetScene = lib_dungeon_pet:is_dun_pet_scene(SceneId),
                    SelectRet =
                        case SelectOp of
                            {select_all_exclude, _} -> true;
                            _ -> false
                        end,
                    if IsDunPetScene andalso SelectRet =/= false -> %% 宠物副本入侵怪需要找最靠近的守护和妖宠
                            MonList = [{MId, (X-OX)*(X-OX) + (Y-OY)*(Y-OY)}||#scene_object{id = MId, x = OX, y = OY}<-SceneMons],
                            [{MId1, _} | _MonList1] = lists:sort(fun({_, DistanceA}, {_, DistanceB}) -> DistanceA < DistanceB end, MonList),
                            #scene_object{sign=SignM1, id=IdM1, aid=AidM1, x=XM1, y=YM1} = lists:keyfind(MId1, #scene_object.id, SceneMons),
                            {SignM1, IdM1, AidM1, XM1, YM1};
                        true ->
                            {SignM, IdM, AidM, XM, YM}
                    end;
                _ -> false
            end;
        _ -> UserInfo
    end.

%% 优先找怪FindType:1怪优先|2人优先|3只找怪
get_area_for_trace_mon_first(CopyId, X, Y, Area, Args, SelectOp, FindType) ->
    AllArea = get_the_area(X, Y, Area),
    Xmax = X + Area,
    Xmin = X - Area,
    Ymax = Y + Area,
    Ymin = Y - Area,
    F = fun(Xtmp, Ytmp, Hide) -> Hide == 0 andalso not (Xtmp > Xmax orelse Xtmp < Xmin orelse Ytmp > Ymax orelse Ytmp < Ymin) end,
    case FindType of
        1 ->
            case  find_closest_object(X, Y, AllArea, CopyId, F, Args, SelectOp) of
                {SignM, IdM, AidM, XM, YM} ->
                    {SignM, IdM, AidM, XM, YM};
                _ ->
                    find_user_to_attack(AllArea, CopyId, F, Args, SelectOp)
            end;
        2 ->
            case find_user_to_attack(AllArea, CopyId, F, Args, SelectOp) of
                {SignM, IdM, AidM, XM, YM} ->
                    {SignM, IdM, AidM, XM, YM};
                _ ->
                    find_closest_object(X, Y, AllArea, CopyId, F, Args, SelectOp)
            end;
        3 ->
            find_closest_object(X, Y, AllArea, CopyId, F, Args, SelectOp)
    end.

%% 全场景找怪
get_all_scene_trace(CopyId, X, Y, Args, SelectOp) ->
    AllObjects = lib_scene_object_agent:get_scene_object(CopyId),
    F = fun(_, _, Hide) -> Hide == 0  end,
    case  filter_battle_objs(AllObjects, F, Args, [], SelectOp) of
        [_|_] = Candidates ->
            {_, Res} = find_close_obj(Candidates, X, Y, max, false),
            Res;
        _ ->
            false
    end.

find_user_to_attack(AllArea, CopyId, F, Args, SelectOp) ->
    AllUsers    = lib_scene_agent:get_all_area_user(AllArea, CopyId),
    case filter_battle_objs(AllUsers, F, Args, [], SelectOp) of
        [#ets_scene_user{id=IdU, pid=PidU, x=XU, y=YU} |_] -> {?BATTLE_SIGN_PLAYER, IdU, PidU, XU, YU};
        _ -> false
    end.
find_closest_object(X, Y, AllArea, CopyId, F, Args, SelectOp) ->
    AllObjects  = lib_scene_object_agent:get_all_area_object(AllArea, CopyId),
    case  filter_battle_objs(AllObjects, F, Args, [], SelectOp) of
        [_|_] = Candidates ->
            {_, Res} = find_close_obj(Candidates, X, Y, max, false),
            Res;
        _ ->
            false
    end.

%% 获取环形范围内的场景对象X,Y是参考点的坐标
%% Area是小圆半径，BigArea是大圆半径
get_annulus_object_for_battle(CopyId, X, Y, AreaTemp, BigAreaTemp, Args) ->
    Area = abs(AreaTemp),
    BigArea = abs(BigAreaTemp),
    AllArea = get_the_area(X, Y),
    AllObjects = lib_scene_object_agent:get_all_area_object(AllArea, CopyId),
    AllUsers = lib_scene_agent:get_all_area_user(AllArea, CopyId),
    Area2 = Area * Area,
    BigArea2 = BigArea * BigArea,
    Fun = fun(Xtmp, Ytmp, Hide) ->
            DX = Xtmp - X, DY = Ytmp - Y,
            Hide == 0 andalso abs(DX) =< BigArea andalso abs(DY) =< (BigArea bsr 1)
            andalso (DX*DX + 4*DY*DY =< BigArea2) andalso (DX*DX + 4*DY*DY >= Area2)
    end,
    FilterObjets = filter_battle_objs(AllObjects, Fun, Args, [], undefined),
    filter_battle_objs(AllUsers, Fun, Args, FilterObjets, undefined).

%% 获取椭圆范围内的场景对象X,Y是参考点的坐标
get_ellipse_object_for_battle(CopyId, X, Y, Area, Args, SelectOption) ->
    AllArea     = get_the_area(X, Y),
    AllObjects  = lib_scene_object_agent:get_all_area_object(AllArea, CopyId),
    AllUsers    = lib_scene_agent:get_all_area_user(AllArea, CopyId),
    Area2 = Area*Area,
    F = fun(Xtmp, Ytmp, Hide) ->
            DX = Xtmp-X, DY = Ytmp-Y,
        Hide == 0 andalso abs(DX) =< Area andalso abs(DY) =< (Area bsr 1) andalso DX*DX + 4*DY*DY =< Area2
    end,
    FilterObjets = filter_battle_objs(AllObjects, F, Args, [], undefined),
    filter_battle_objs(AllUsers, F, Args, FilterObjets, SelectOption).

%% 获取矩形范围可攻击的场景对象
get_rectangle_object_for_battle(CopyId, Length, Width, OX, OY, Radian, Args, SelectOption) ->
    Cos     = math:cos(Radian),
    Sin     = math:sin(Radian),
    OXtr    = trunc(OX*Cos+OY*Sin), %% 旋转坐标
    OYtr    = trunc(OY*Cos-OX*Sin),
    Xmax    = OXtr + Length,
    Xmin    = OXtr,
    Ymax    = OYtr + (Width bsr 2),
    Ymin    = OYtr - (Width bsr 2),
    AllArea = get_the_area(OX, OY),
    AllObjects = lib_scene_object_agent:get_all_area_object(AllArea, CopyId),
    AllUsers = lib_scene_agent:get_all_area_user(AllArea, CopyId),
    F = fun(X, Y, Hide) ->
            Xtr = trunc(X*Cos+Y*Sin), Ytr = trunc(Y*Cos-X*Sin), %% 旋转坐标
        (Hide == 0 ) andalso (not (Xtr<Xmin orelse Xtr>Xmax orelse Ytr<Ymin orelse Ytr>Ymax))
    end,
    FilterObjets = filter_battle_objs(AllObjects, F, Args, [], undefined),
    filter_battle_objs(AllUsers, F, Args, FilterObjets, SelectOption).

%% 获取扇形内的攻击对象
get_sector_object_for_battle(CopyId, X, Y, TX, TY, Distance, Area, Args, SelectOption) ->
    AllArea     = get_the_area(X, Y),
    AllObjects  = lib_scene_object_agent:get_all_area_object(AllArea, CopyId),
    AllUsers    = lib_scene_agent:get_all_area_user(AllArea, CopyId),
    R2 = Distance*Distance,
    %% 向量OT
    Tvx = TX - X, Tvy = TY - Y,
    TLen2 = math:pow(Tvx, 2) + math:pow(Tvy, 2),
    CosArea = math:cos(umath:degree_to_radian(min(180, Area) bsr 1)),
    CosArea2 = math:pow(CosArea, 2),
    F = fun(Xtmp, Ytmp, Hide) ->
            DX = Xtmp-X, DY = Ytmp-Y,
            %% 1.> 点要在圆内
            case Hide == 0 andalso abs(DX) =< Distance andalso abs(DY) =< (Distance bsr 1) andalso DX*DX + 4*DY*DY =< R2 of
                true ->
                    Tmpvx = Xtmp - X, Tmpvy = Ytmp - Y,
                    TmpLen2 = math:pow(Tmpvx, 2) + math:pow(Tmpvy, 2),
                    Dot = Tvx*Tmpvx+Tvy*Tmpvy, %% 点积
                    %% 2.> 用点积判断两个夹角的大小
                    if
                        Dot > 0 andalso CosArea > 0 -> math:pow(Dot, 2) > TmpLen2*TLen2*CosArea2;
                        Dot < 0 andalso CosArea < 0 -> math:pow(Dot, 2) < TmpLen2*TLen2*CosArea2;
                        true -> Dot >= 0
                    end;
                false -> false
            end
    end,
    FilterObjets = filter_battle_objs(AllObjects, F, Args, [], undefined),
    filter_battle_objs(AllUsers, F, Args, FilterObjets, SelectOption).

%% 过滤战斗对象列表
filter_battle_objs([H | T], Fun, Args, Result, SelectOp) ->
    NewResult = filter_battle_obj(H, Fun, Args, Result, SelectOp),
    filter_battle_objs(T, Fun, Args, NewResult, SelectOp);
filter_battle_objs([], _Fun, _Args, Result, _SelectOp) ->
    Result.

%% 过滤单个战斗对象
%% Result :: [Obj],
%% -> ObjList :: [Obj]
filter_battle_obj(Obj, Fun,
    {GroupA, SignA, IdA, OwnerSignA, OwnerIdA},
    Result, SelectOp) when is_record(Obj, scene_object) orelse is_record(Obj, ets_scene_user) ->
    case Obj of
        #scene_object{scene = Scene, id=Id, sign=Sign, x=X, y=Y, is_be_atted=IsBeAtted, skill_owner=SkillOwner, mon = Mon,
            battle_attr=#battle_attr{hp=Hp, ghost=Ghost, hide=Hide, group=Group}} -> ok;
        #ets_scene_user{scene = Scene, id=Id, x=X, y=Y, battle_attr=#battle_attr{hp=Hp, ghost=Ghost, hide=Hide, group=Group}} ->
            Sign=?BATTLE_SIGN_PLAYER, IsBeAtted=1, SkillOwner=[], Mon = undefined
    end,
    %% 安全区不能打人
    IsSafe = SignA =:= ?BATTLE_SIGN_PLAYER andalso Sign =:= ?BATTLE_SIGN_PLAYER andalso scene_landform:is_safe_pos(Scene, X, Y),
    %% 废土场景假人安全区也不能打人
    IsWastelandSafe = ((SignA =:= ?BATTLE_SIGN_PLAYER orelse SignA =:= ?BATTLE_SIGN_DUMMY)
        orelse (Sign =:= ?BATTLE_SIGN_PLAYER orelse Sign =:= ?BATTLE_SIGN_DUMMY))
        andalso lists:member(Scene, [7600,7601])
        andalso scene_landform:is_safe_pos(Scene, X, Y),
    if
        IsBeAtted == 0                      -> Result;
        Hp =< 0                             -> Result;
        Ghost == 1                          -> Result; %% 幽灵不能攻击
        SignA == Sign andalso IdA == Id     -> Result; %% 同一个对象
        Group /= 0 andalso Group == GroupA  -> Result;  %% 同组
        Group == 0 andalso Sign == ?BATTLE_SIGN_MON andalso Sign == SignA ->
            Result; %% group值为0的非玩家对象不能选择同类攻击
        SignA == ?BATTLE_SIGN_PLAYER andalso is_record(Mon, scene_mon) andalso
%%        Mon#scene_mon.boss /= ?MON_NORMAL_OUSIDE ->
%%            Result;
        OwnerIdA > 0 andalso Sign == OwnerSignA andalso Id == OwnerIdA ->
            Result; %% 是本对象的下属
        is_record(SkillOwner, skill_owner) andalso SkillOwner#skill_owner.id > 0 andalso (
            (SkillOwner#skill_owner.sign == SignA andalso SkillOwner#skill_owner.id == IdA)     %% 是攻击方的所属
            orelse (SkillOwner#skill_owner.sign == OwnerSignA andalso SkillOwner#skill_owner.id == OwnerIdA)    %% 同一所属
        ) ->
            Result;
        IsSafe ->
            Result;
        IsWastelandSafe ->
            Result;
        true ->
            case Fun(X, Y, Hide) of
                false ->
                    Result;
                true ->
                    case SelectOp of
                        {closest, OX, OY} ->
                            case Result of
                                [#scene_object{x = X1, y = Y1}|_] when abs(OX-X)+abs(OY-Y) < abs(OX-X1)+abs(OY-Y1) ->
                                    [Obj];
                                [#ets_scene_user{x = X1, y = Y1}|_] when abs(OX-X)+abs(OY-Y) < abs(OX-X1)+abs(OY-Y1) ->
                                    [Obj];
                                [] ->
                                    [Obj];
                                _ ->
                                    Result
                            end;
                        {boss_tired_filter, _MonTypeId, Boss} ->
                            %% 赏金boss根据疲劳度过滤攻击对象
                            RewardBossForbidAttack = local_boss_api:mon_is_forbid_attack_man(Obj, Boss),
                            if
                                RewardBossForbidAttack -> Result;
                                true -> [Obj | Result]
                            end;
                        Func when is_function(Func) ->
                            case Func(Obj) of
                                true  ->
                                    [Obj | Result];
                                false ->
                                    Result
                            end;
                        _ ->
                            [Obj | Result]
                    end
            end
    end;
filter_battle_obj(_, _Fun, _Args, Result, _SelectOp) ->
    Result.

get_scene_object_for_assist(SceneId, CopyId, OX, OY, Area, TeamIdA, Args) ->
    {GroupA, SignA, IdA, OwnerSignA, OwnerIdA} = Args,
    IsFindAllScene = is_find_all_scene(SceneId, SignA),
    if
        IsFindAllScene ->
            AllUsers = lib_scene_agent:get_scene_user(CopyId),
            AllObjects = lib_scene_object_agent:get_scene_object(CopyId);
        true ->
            AllArea = lib_scene_calc:get_the_area(OX, OY),
            AllUsers = lib_scene_agent:get_all_area_user(AllArea, CopyId),
            AllObjects = lib_scene_object_agent:get_all_area_object(AllArea, CopyId)
    end,
    Xmax = OX + Area,
    Xmin = OX - Area,
    Ymax = OY + Area,
    Ymin = OY - Area,
    F = fun(Elem) ->
            case Elem of
                #scene_object{id=Id, sign=Sign, x=X, y=Y, battle_attr=#battle_attr{hp=Hp, ghost=Ghost, group=Group}, skill_owner=SkillOwner} -> TeamId = 0;
                #ets_scene_user{id=Id, x=X, y=Y, battle_attr=#battle_attr{hp=Hp, ghost=Ghost, group=Group}, team=#status_team{team_id=TeamId} } ->
                    Sign=?BATTLE_SIGN_PLAYER, SkillOwner=[]
            end,
            if
                Ghost == 1 -> false;
                Hp =< 0 -> false;
                IsFindAllScene ==false andalso (X > Xmax orelse X < Xmin orelse Y > Ymax orelse Y < Ymin) -> false;
                SignA == Sign andalso IdA == Id -> false; %% 自己排除
                TeamIdA > 0 andalso TeamIdA == TeamId -> true;
                GroupA > 0 andalso GroupA == Group -> true;
                OwnerIdA > 0 andalso Sign == OwnerSignA andalso Id == OwnerIdA -> true; %% 是本对象的下属
                SkillOwner#skill_owner.id > 0 andalso SkillOwner#skill_owner.sign == SignA andalso SkillOwner#skill_owner.id == IdA -> true;
                SkillOwner#skill_owner.id > 0 andalso SkillOwner#skill_owner.sign == OwnerSignA andalso SkillOwner#skill_owner.id == OwnerIdA -> true;
                true -> false
            end
    end,
    lists:filter(F, AllUsers) ++ lists:filter(F, AllObjects).

%% 只找敌方怪物释放辅助技能
%% 筛选依据与 ?MODULE:get_scene_object_for_assist/7 刚好相反
get_scene_mon_for_assist(SceneId, CopyId, OX, OY, Area, TeamIdA, Args) ->
    {GroupA, SignA, IdA, OwnerSignA, OwnerIdA} = Args,
    IsFindAllScene = is_find_all_scene(SceneId, SignA),
    if
        IsFindAllScene ->
            AllObjects = lib_scene_object_agent:get_scene_object(CopyId);
        true ->
            AllArea = lib_scene_calc:get_the_area(OX, OY),
            AllObjects = lib_scene_object_agent:get_all_area_object(AllArea, CopyId)
    end,
    Xmax = OX + Area,
    Xmin = OX - Area,
    Ymax = OY + Area,
    Ymin = OY - Area,
    F = fun(Elem) ->
        #scene_object{id=Id, sign=Sign, x=X, y=Y, battle_attr=#battle_attr{hp=Hp, ghost=Ghost, group=Group}, skill_owner=SkillOwner} = Elem,
        TeamId = 0,
        if
            Ghost == 1 -> false;
            Hp =< 0 -> false;
            IsFindAllScene == false andalso (X > Xmax orelse X < Xmin orelse Y > Ymax orelse Y < Ymin) -> false;
            TeamIdA > 0 andalso TeamIdA == TeamId -> false; %% 玩家同队伍排除
            GroupA > 0 andalso GroupA == Group -> false;
            OwnerIdA > 0 andalso Sign == OwnerSignA andalso Id == OwnerIdA -> false; %% 是本对象的下属
            SkillOwner#skill_owner.id > 0 andalso SkillOwner#skill_owner.sign == SignA andalso SkillOwner#skill_owner.id == IdA -> false;
            SkillOwner#skill_owner.id > 0 andalso SkillOwner#skill_owner.sign == OwnerSignA andalso SkillOwner#skill_owner.id == OwnerIdA -> false;
            true -> true
        end end,
    lists:filter(F, AllObjects).

%% 全场景索敌地图
is_find_all_scene(_SceneId, _Sign) -> false.

% is_find_all_scene(SceneId) ->
%     [SceneType|_T] = lib_scene:get_scene_type(SceneId),
%     DunType = lib_dungeon_api:get_dungeon_type_by_scene(SceneId),
%     FillAllSceneList = [?SCENE_TYPE_TOPPK],
%     FillAllDunList = [?DUNGEON_TYPE_STAR, ?DUNGEON_TYPE_STARSOUL_WAR],
%     case lists:member(SceneType, FillAllSceneList) of
%         true -> true;
%         false when SceneType == ?SCENE_TYPE_DUNGEON -> lists:member(DunType, FillAllDunList);
%         false -> false
%     end.

%%  获取要广播的范围用户信息
get_broadcast_user(CopyId, X, Y) ->
    Area = get_the_area(X, Y),
    lib_scene_agent:get_all_area_user(Area, CopyId).

%%  获取要广播的范围场景对象信息
get_broadcast_object(CopyId, X, Y) ->
    Area = get_the_area(X, Y),
    lib_scene_object_agent:get_all_area_object(Area, CopyId).

%%是否在9宫格内
is_area_scene(X1, Y1, X2, Y2) ->
    AreaId2 = get_xy(X2, Y2),
    AreaId1 = get_xy(X1, Y1),
    AreaIdList2 = get_nine_broadcast_area(AreaId2),
    lists:member(AreaId1, AreaIdList2).

%% 获取九宫格
get_the_area(X, Y) ->
    AreaId = get_xy(X, Y),
    get_nine_broadcast_area(AreaId).

get_the_area(X, Y, Distance) ->
    Dis     = trunc(util:ceil(Distance / ?GRID_WIDTH)),
    Edge    = Dis * 2 + 1,
    AreaId  = get_xy(X, Y),
    Left    = AreaId - (?HORIZ_GRID_CNT * Dis) - Dis,
    Grids   = [Left + Line * ?HORIZ_GRID_CNT + Col || Line <- lists:seq(0, Edge - 1), Col <- lists:seq(0, Edge - 1)],
    [P || P <- Grids, P > 0].

%% ---------------------------------------------------------------------------
%% @doc 给玩家加buff，buff结束后要执行特殊效果
-spec ai_add_buff_to_role(EtsScene, CopyId, X, Y, WarningRange, Args, BuffArgs) ->
    ok when
    CopyId      :: integer() | pid(),   %% 房间Id
    X           :: integer(),
    Y           :: integer(),
    WarningRange:: integer(),           %% 警戒范围
    Args        :: tuple(),
    BuffArgs    :: #add_buff_to_role_args{},
    EtsScene    :: #ets_scene{}.
%% ---------------------------------------------------------------------------
ai_add_buff_to_role(EtsScene, CopyId, X, Y, WarningRange, Args, BuffArgs) ->
    #add_buff_to_role_args{
        role_num = RoleNum, skill_id = SkillId, skill_lev = SkillLev
        ,buff_time = BuffTime
    } = BuffArgs,
    AllArea = get_the_area(X, Y),
    Xmax = X + WarningRange,
    Xmin = X - WarningRange,
    Ymax = Y + WarningRange,
    Ymin = Y - WarningRange,
    Fun = fun(Xtmp, Ytmp, Hide) ->
            Hide == 0 andalso not
            (Xtmp > Xmax orelse Xtmp < Xmin orelse Ytmp > Ymax orelse Ytmp < Ymin)
    end,
    AllUsers = lib_scene_agent:get_all_area_user(AllArea, CopyId),
    UserList = case filter_battle_objs(AllUsers, Fun, Args, [], {}) of
        UserL = [#ets_scene_user{} | _] ->
            urand:get_rand_list(RoleNum, UserL);
        _ ->
            []
    end,
    RoleIdList = add_user_buff(UserList, SkillId, SkillLev, EtsScene, []),
    BuffArgs2 = BuffArgs#add_buff_to_role_args{role_id_list = RoleIdList},
    erlang:start_timer(BuffTime, self(), {ai_buff_timer, BuffArgs2}),
    ok.

%% 给玩家增加buff
add_user_buff([], _SkillId, _SkillLev, _EtsScene, RoleIdList) ->
    RoleIdList;
add_user_buff([User | T], SkillId, SkillLev, EtsScene, RoleIdList) ->
    #ets_scene_user{id = RoleId} = User,
    lib_skill_buff:add_buff(EtsScene, RoleId, SkillId, SkillLev),
    RoleIdList2 = [RoleId | RoleIdList],
    add_user_buff(T, SkillId, SkillLev, EtsScene, RoleIdList2).


%% ---------------------------------------------------------------------------
%% @doc 怪物ai秒杀场景内的玩家
-spec ai_kill_scene_role(EtsScene, CopyId, X, Y, WarningRange, Args, RoleNum, Killer) ->
    ok when
    CopyId      :: integer() | pid(),   %% 房间Id
    X           :: integer(),
    Y           :: integer(),
    WarningRange:: integer(),           %% 警戒范围
    Args        :: tuple(),
    RoleNum     :: integer(),           %% 秒杀玩家人数
    Killer      :: #battle_return_atter{},
    EtsScene    :: #ets_scene{}.
%% ---------------------------------------------------------------------------
ai_kill_scene_role(EtsScene, CopyId, _X, _Y, _WarningRange, Args, RoleNum, Killer) ->
    Fun = fun(_Xtmp, _Ytmp, Hide) -> Hide =:= 0 end,
    AllUsers = lib_scene_agent:get_scene_user(CopyId),
    UserList = case filter_battle_objs(AllUsers, Fun, Args, [], {}) of
        UserL = [#ets_scene_user{} | _] ->
            urand:get_rand_list(RoleNum, UserL);
        _ ->
            []
    end,
    [ai_kill_role(SceneUser, Killer, EtsScene) || SceneUser <- UserList],
    ok.

ai_kill_role(SceneUser, Atter, EtsScene) ->
    #ets_scene{broadcast = Broadcast} = EtsScene,
    #ets_scene_user{
        id = RoleId, pid = Pid, battle_attr = BattleAttr, copy_id = CopyId
        ,x = X, y = Y
    } = SceneUser,
    #battle_attr{
        hp = Hp, attr_buff_list = AttrBuffList, other_buff_list = OtherBuffList
        ,anger = Anger, hp_lim = HpLim
    } = BattleAttr,
    BattleReturn = #battle_return{
        hurt = Hp + 1, move_x = 0, move_y = 0, anger = Anger
        ,attr_buff_list = AttrBuffList, other_buff_list = OtherBuffList
        ,hit_list = [], atter = Atter, sign = ?BATTLE_SIGN_MON
    },
    erlang:send(Pid, {'battle_info_die', BattleReturn}),
    BattleAttr2 = BattleAttr#battle_attr{hp = 0},
    lib_scene_agent:put_user(SceneUser#ets_scene_user{battle_attr = BattleAttr2}),
    {ok, BinData} = pt_120:write(12009, [RoleId, 0, HpLim]),
    lib_battle:send_to_scene(CopyId, X, Y, Broadcast, BinData),
    ok.

%%--------------------------九宫格加载场景---------------------------
%% 根据每张地图实际大小，分成一个个10*15的大格子，并从左到右赋予编号。
%% 如把整个地图共有100*150个格子，0，0坐标点为原点，以10*15为一个格子，从左到右编号， 那么坐标50,50所在的格子的编号就是Y div 15 * 10 + X div 10 + 1.

%% 获取当前所在的格子的编号
%% get_xy(X, Y) -> AreaId.
get_xy(X, Y) ->
    Y div ?GRID_HEIGHT * ?HORIZ_GRID_CNT + X div ?GRID_WIDTH + 1.

%%当人物或者怪物移动时候的广播
%%终点要X1，Y1，原点是X2,Y2
%%BinData走路协议包, BinData1移除玩家包 BinData2有玩家进入
move_broadcast(SceneId, CopyId, X1, Y1, X2, Y2, BinData, BinData1, BinData2, SendList) ->
    move_broadcast(true, SceneId, CopyId, X1, Y1, X2, Y2, BinData, BinData1, BinData2, SendList).
move_broadcast(IsBroadcast, SceneId, CopyId, X1, Y1, X2, Y2, BinData, BinData1, BinData2, SendList) ->
    AreaId1 = get_xy(X1, Y1),
    AreaId2 = get_xy(X2, Y2),
    move_user_broadcast(IsBroadcast, CopyId, AreaId1, AreaId2, BinData, BinData1, BinData2, SendList),
    %% 怪物移动不用通知怪物
    case SendList of
        [] -> skip;
        _  -> move_mon_broadcast(SceneId, CopyId, AreaId1, AreaId2, SendList)
    end.

%% 广播给玩家
move_user_broadcast(IsBroadcast, CopyId, AreaIdNew, AreaIdOld, BinData, BinData1, BinData2, SendList) ->
    {AreaAdd, AreaDel, AreaMove} = divide_broadcast_area(AreaIdOld, AreaIdNew),
    UserAdd = lib_scene_agent:move_send_and_getuser(AreaAdd, CopyId, BinData2),
    UserDel = lib_scene_agent:move_send_and_getid(AreaDel, CopyId, BinData1),
    % <<_:32, 12001:16, _:8, _BX:16, _BY:16, _Id:64, _/binary>> = BinData,
    % ?INFO("UserId:~w,BX:~w,BY:~w, AreaIdOld:~w, AreaIdNew:~w,~nAreaAdd:~w~nAreaDel:~w~nAreaMove:~w~nUserAdd:~w~nUserDel:~w~n",
    %     [_Id, _BX, _BY, AreaIdOld, AreaIdNew, AreaAdd, AreaDel, AreaMove, UserAdd, UserDel]
    % ),
    case IsBroadcast of
        true  -> lib_scene_agent:send_to_any_area(AreaMove, CopyId, BinData);
        false -> skip
    end,
    case SendList =:= [] orelse (UserAdd == [] andalso UserDel == []) of
        true ->
            ok;
        false ->
            %% 加入和移除玩家
            {ok, BinData3} = pt_120:write(12011, [UserAdd, UserDel]),
            [Node, Sid] = SendList,
            % ?INFO("notice to [~w] user add and del", [_Id]),
            lib_server_send:send_to_sid(Node, Sid, BinData3)
    end.


%% =====================================
%% =============== 怪物 ================
%% =====================================
%% 注意：这里仅用于mod_scene_agent进程内调用
%% 怪物移除和加入对于一些强制广播的场景，不需要处理
move_mon_broadcast(SceneId, CopyId, AreaId1, AreaId2, SendList) ->
    case lib_scene:is_force_broadcast_scene(SceneId) of
        true  -> ignore;
        false ->
            {AreaAdd, AreaDel, _} = divide_broadcast_area(AreaId2, AreaId1),
            ObjectAdd = lib_scene_object_agent:get_all_area_object(AreaAdd, CopyId),
            ObjectDel = lib_scene_object_agent:get_all_area(AreaDel, CopyId),
            case ObjectAdd == [] andalso ObjectDel == [] of
                true ->
                    ok;
                false ->
                    %%加入和移除怪物
                    {ok, BinData4} = pt_120:write(12012, [ObjectAdd, ObjectDel]),
                    [Node, Sid] = SendList,
                    lib_server_send:send_to_sid(Node, Sid, BinData4)
            end
    end.

%% 划分广播区域
divide_broadcast_area(StartAreaNo, EndAreaNo) ->
    if
        StartAreaNo =:= EndAreaNo ->                    % 相同网格移动
            MoveArea = get_nine_broadcast_area(EndAreaNo),
            {[], [], MoveArea};

        StartAreaNo + ?GRID_OFFSET_R =:= EndAreaNo ->  % 向右移动
            AddArea  = [EndAreaNo + ?GRID_OFFSET_R, EndAreaNo + ?GRID_OFFSET_RD, EndAreaNo + ?GRID_OFFSET_RU],
            DelArea  = [StartAreaNo + ?GRID_OFFSET_L, StartAreaNo + ?GRID_OFFSET_LU, StartAreaNo + ?GRID_OFFSET_LD],
            MoveArea = [StartAreaNo, StartAreaNo + ?GRID_OFFSET_R, StartAreaNo + ?GRID_OFFSET_U,
                StartAreaNo + ?GRID_OFFSET_D,  StartAreaNo + ?GRID_OFFSET_RD, StartAreaNo + ?GRID_OFFSET_RU],
            {AddArea, DelArea, MoveArea};

        StartAreaNo + ?GRID_OFFSET_L == EndAreaNo ->   % 向左移动
            AddArea  = [EndAreaNo + ?GRID_OFFSET_L, EndAreaNo + ?GRID_OFFSET_LU, EndAreaNo + ?GRID_OFFSET_LD],
            DelArea  = [StartAreaNo + ?GRID_OFFSET_R, StartAreaNo + ?GRID_OFFSET_RD,  StartAreaNo + ?GRID_OFFSET_RU],
            MoveArea = [StartAreaNo, StartAreaNo + ?GRID_OFFSET_LU, StartAreaNo + ?GRID_OFFSET_L,
                StartAreaNo + ?GRID_OFFSET_U, StartAreaNo + ?GRID_OFFSET_D, StartAreaNo+ ?GRID_OFFSET_LD],
            {AddArea, DelArea, MoveArea};

        StartAreaNo + ?GRID_OFFSET_U == EndAreaNo ->   % 向上移动
            AddArea  = [EndAreaNo + ?GRID_OFFSET_U, EndAreaNo + ?GRID_OFFSET_LU, EndAreaNo + ?GRID_OFFSET_RU],
            DelArea  = [StartAreaNo + ?GRID_OFFSET_D, StartAreaNo + ?GRID_OFFSET_RD, StartAreaNo + ?GRID_OFFSET_LD],
            MoveArea = [StartAreaNo, StartAreaNo + ?GRID_OFFSET_R, StartAreaNo + ?GRID_OFFSET_L,
                StartAreaNo + ?GRID_OFFSET_U, StartAreaNo + ?GRID_OFFSET_LU, StartAreaNo + ?GRID_OFFSET_RU],
            {AddArea, DelArea, MoveArea};

        StartAreaNo + ?GRID_OFFSET_D == EndAreaNo ->   % 向下移动
            AddArea  = [EndAreaNo + ?GRID_OFFSET_D, EndAreaNo + ?GRID_OFFSET_RD, EndAreaNo + ?GRID_OFFSET_LD],
            DelArea  = [StartAreaNo + ?GRID_OFFSET_U, StartAreaNo + ?GRID_OFFSET_LU, StartAreaNo + ?GRID_OFFSET_RU],
            MoveArea = [StartAreaNo, StartAreaNo + ?GRID_OFFSET_R, StartAreaNo + ?GRID_OFFSET_L,
                StartAreaNo + ?GRID_OFFSET_D, StartAreaNo + ?GRID_OFFSET_RD, StartAreaNo + ?GRID_OFFSET_LD],
            {AddArea, DelArea, MoveArea};

        StartAreaNo + ?GRID_OFFSET_LU == EndAreaNo ->  % 向左上移动
            AddArea  = [EndAreaNo + ?GRID_OFFSET_L, EndAreaNo + ?GRID_OFFSET_LU,
                EndAreaNo + ?GRID_OFFSET_U, EndAreaNo + ?GRID_OFFSET_RU, EndAreaNo + ?GRID_OFFSET_LD],
            DelArea  = [StartAreaNo + ?GRID_OFFSET_R, StartAreaNo + ?GRID_OFFSET_RD,
                StartAreaNo + ?GRID_OFFSET_D, StartAreaNo + ?GRID_OFFSET_LD, StartAreaNo + ?GRID_OFFSET_RU],
            MoveArea = [StartAreaNo, StartAreaNo + ?GRID_OFFSET_LU, StartAreaNo + ?GRID_OFFSET_L, StartAreaNo + ?GRID_OFFSET_U],
            {AddArea, DelArea, MoveArea};

        StartAreaNo + ?GRID_OFFSET_LD == EndAreaNo ->  % 向左下移动
            AddArea  = [EndAreaNo + ?GRID_OFFSET_LU, EndAreaNo + ?GRID_OFFSET_L,
                EndAreaNo + ?GRID_OFFSET_LD, EndAreaNo + ?GRID_OFFSET_D, EndAreaNo + ?GRID_OFFSET_RD],
            DelArea  = [StartAreaNo + ?GRID_OFFSET_RD, StartAreaNo + ?GRID_OFFSET_R,
                StartAreaNo + ?GRID_OFFSET_RU, StartAreaNo + ?GRID_OFFSET_U, StartAreaNo + ?GRID_OFFSET_LU],
            MoveArea = [StartAreaNo, StartAreaNo + ?GRID_OFFSET_L, StartAreaNo + ?GRID_OFFSET_D,  StartAreaNo + ?GRID_OFFSET_LD],
            {AddArea, DelArea, MoveArea};

        StartAreaNo + ?GRID_OFFSET_RU == EndAreaNo ->  % 向右上移动
            AddArea  = [EndAreaNo + ?GRID_OFFSET_R, EndAreaNo + ?GRID_OFFSET_RD,
                EndAreaNo + ?GRID_OFFSET_RU, EndAreaNo + ?GRID_OFFSET_U, EndAreaNo + ?GRID_OFFSET_LU],
            DelArea  = [StartAreaNo + ?GRID_OFFSET_L, StartAreaNo + ?GRID_OFFSET_LU,
                StartAreaNo + ?GRID_OFFSET_LD, StartAreaNo + ?GRID_OFFSET_D, StartAreaNo + ?GRID_OFFSET_RD],
            MoveArea = [StartAreaNo, StartAreaNo + ?GRID_OFFSET_R, StartAreaNo + ?GRID_OFFSET_U, StartAreaNo + ?GRID_OFFSET_RU],
            {AddArea, DelArea, MoveArea};

        StartAreaNo + ?GRID_OFFSET_RD == EndAreaNo ->  % 向右下移动
            AddArea  = [EndAreaNo + ?GRID_OFFSET_R, EndAreaNo + ?GRID_OFFSET_LD,
                EndAreaNo + ?GRID_OFFSET_D, EndAreaNo + ?GRID_OFFSET_RD, EndAreaNo + ?GRID_OFFSET_RU],
            DelArea  = [StartAreaNo + ?GRID_OFFSET_L, StartAreaNo + ?GRID_OFFSET_RU,
                StartAreaNo + ?GRID_OFFSET_U, StartAreaNo + ?GRID_OFFSET_LU, StartAreaNo + ?GRID_OFFSET_LD],
            MoveArea = [StartAreaNo, StartAreaNo + ?GRID_OFFSET_R, StartAreaNo + ?GRID_OFFSET_D, StartAreaNo + ?GRID_OFFSET_RD],
            {AddArea, DelArea, MoveArea};

        true ->                                         % 跨区域移动
            % 跨9格区域移动（非相邻区域移动，计算方法是全部移动形式通用的，
            % 包括前面9种情形，只是要多一步计算变动区域，前面是写死广播区域的）
            StartPointArea  = get_nine_broadcast_area(StartAreaNo),
            EndPointArea    = get_nine_broadcast_area(EndAreaNo),
            DelArea         = StartPointArea -- EndPointArea,
            AddArea         = EndPointArea -- StartPointArea,
            MoveArea        = EndPointArea -- AddArea,      % 广播移动消息给重叠区域，超远距离时可能为空
            {AddArea, DelArea, MoveArea}
    end.

%% 获得九格广播区域
get_nine_broadcast_area(MiddleArea) ->
    % 左上 上 右上
    % 左   中 右
    % 左下 下 右下
    [
        AreaId ||
        AreaId <- [
            MiddleArea + ?GRID_OFFSET_LU, MiddleArea + ?GRID_OFFSET_U, MiddleArea + ?GRID_OFFSET_RU,
            MiddleArea + ?GRID_OFFSET_L,  MiddleArea,                  MiddleArea + ?GRID_OFFSET_R,
            MiddleArea + ?GRID_OFFSET_LD, MiddleArea + ?GRID_OFFSET_D, MiddleArea + ?GRID_OFFSET_RD
        ], AreaId >= 0
    ].

%% 像素坐标转换为逻辑坐标
pixel_to_logic_coordinate(Xpx, Ypx) ->
    {Xpx div ?LOGIC_PT_WIDTH, Ypx div ?LOGIC_PT_HEIGHT}.

%% 逻辑坐标转换为像素坐标
logic_to_pixel_coordinate(Xl, Yl) ->
    {Xl * ?LOGIC_PT_WIDTH, Yl * ?LOGIC_PT_HEIGHT}.

%% 单个的x值像素到逻辑坐标转换
x_pixel_to_logic(Xpx) ->
    Xpx div ?LOGIC_PT_WIDTH.

%% 单个的y值像素到逻辑坐标转换
y_pixel_to_logic(Ypx) ->
    Ypx div ?LOGIC_PT_HEIGHT.

%% 单个的x值逻辑坐标到像素转换
x_logic_to_pixel(LogicX) ->
    LogicX * ?LOGIC_PT_WIDTH.

%% 单个的y值逻辑坐标到像素转换
y_logic_to_pixel(LogicY) ->
    LogicY * ?LOGIC_PT_HEIGHT.

% %% 获取螺旋生长的格子 由内而外
% get_helix_points_from_inside(X, Y, MaxX, MaxY, Num, Check) ->
