%% ---------------------------------------------------------
%% Author:  xyao
%% Email:   jiexiaowen@gmail.com
%% Created: 2012-4-26
%% Description: 战斗,已经进入场景进程，不能使用call
%% --------------------------------------------------------
-module(lib_battle).
-export([
        start_battle/2,
        assist_to_anyone/6,
        assist_to_anyone/7,
        object_start_battle/7,
        object_start_battle/9,
        object_assist/4,
        object_assist/5,
        assist_anything/7,
        send_player_buff_list/1,
        battle_fail/4, battle_fail/5,
        send_to_scene/5, send_to_scene_area/3,
        timer_call_back/5, add_resume_timer/6,
        add_resume_timer/7, remove_resume_timer/1, begin_resume_timer/6,
        collect_mon/5, pick_mon/2,
        check_pk_and_safe/4,
        check_distance/4,
        calc_skill_distance/1,
        rpc_cast_to_node/4,
        object_assist_cast/6,
        combo_assist_cast/5,
        object_battle_cast/10,
        combo_battle_cast/9,
        break_steath_force/3,
        clean_active_skill_buff/2
    ]).

-export([
        check_get_anger/3                       %% 角色杀怪获得怒气
        ,kill_mon_get_anger/2                   %% 角色获得怒气
        ,check_skill_use_cond_anger/3           %% 检查技能的怒气条件
    ]).

-compile({inline, [
            remove_resume_timer/1
        ]}).

-include("scene.hrl").
-include("skill.hrl").
-include("battle.hrl").
-include("attr.hrl").
-include("server.hrl").
-include("common.hrl").
-include("predefine.hrl").
-include("figure.hrl").
-include("collect.hrl").
-include("errcode.hrl").
-include("def_fun.hrl").
-include("def_act_label.hrl").

%% 开始战斗(带客户端攻击对象)--场景进程中
start_battle(EtsScene, #battle_args{att_key=AttKey, skill_id = SkillId, main_type = MainType, now_time = NowTime} = BattleArgs) ->
    {MonList, PlayerList} = lib_robot:get_battle_object(AttKey, BattleArgs),
    case check_start_battle(AttKey, MonList, PlayerList, MainType, NowTime) of
        {true, AttUser = #ets_scene_user{battle_attr = #battle_attr{anger = OldAnger}}, DerList, NotExistList} ->
            case NotExistList of
                [] -> skip;
                _  -> battle_fail(?ERR_ANY_MON_NOT_EXISTS, AttUser, #battle_status{}, SkillId, NotExistList)
            end,
            case mod_battle:battle(BattleArgs#battle_args{att_user=AttUser, mon_list=[], player_list=[], der_list=DerList}, EtsScene) of
                {false, ErrCode, _Aer} ->
                    battle_fail(ErrCode, AttUser, #battle_status{}, SkillId);
                {true, Aer = #ets_scene_user{id = RoleId, battle_attr = #battle_attr{anger = Anger}}, _} ->
                    SkillR = data_skill:get(BattleArgs#battle_args.skill_id, BattleArgs#battle_args.skill_lv),
                    NewAer = after_battle_event(AttUser, Aer, SkillR, BattleArgs),
                    lib_scene_agent:put_user(NewAer),
                    %% 更新怒气信息到角色进程
                    OldAnger =/= Anger andalso lib_player:update_player_info(RoleId, [{anger, {notice, Anger}}]),
                    ok
            end;
        {false, AttUser} ->
            battle_fail(?ERR_ATTACKER, AttUser, #battle_status{}, SkillId);
        false ->
            skip
    end.

%% 释放技能后事件
after_battle_event(OldUser, User, SkillR, BattleArgs) ->
    EventList = [is_trigger_talent, is_god_anger_add, is_update_last_active_skill_id],
    case do_after_battle_event(EventList, OldUser, User, SkillR, BattleArgs) of
        {no, NewUser} -> NewUser;
        {ok, NewUser} -> NewUser
    end.

do_after_battle_event([], _OldUser, User, _SkillR, _BattleArgs) -> {no, User};
do_after_battle_event([H|T], OldUser, User, SkillR, BattleArgs) ->
    case do_after_battle_callback(H, OldUser, User, SkillR, BattleArgs) of
        {ok, NewUser} -> {ok, NewUser};
        {next, NewUser} -> do_after_battle_event(T, OldUser, NewUser, SkillR, BattleArgs)
    end.

%% 是否触发天赋技能
do_after_battle_callback(is_trigger_talent, _OldUser, #ets_scene_user{skill_talent_active_list = TalentActiveList, skill_cd = SkillCd} = User, SkillR, BattleArgs)
when TalentActiveList=/=[] ->
    case SkillR of
        #skill{talent= [{SkillId, PerMil, LastTime}|_]} ->
            NowTime = utime:longunixtime(),
            NotInCd = mod_battle:check_cd_skill(SkillCd, SkillId, NowTime, 1),
            IsExist = lists:keyfind(SkillId, 1, TalentActiveList),
            case NotInCd==true andalso IsExist=/=false andalso (PerMil == 0 orelse PerMil >= 1000 orelse urand:rand(1, 1000) =< PerMil) of
                true ->
                    OldSkillId = BattleArgs#battle_args.skill_id,
                    Args = [User#ets_scene_user.id, ?APPLY_CAST_STATUS, lib_skill, trigger_talent_active, [OldSkillId, SkillId, LastTime]],
                    rpc_cast_to_node(User#ets_scene_user.node, lib_player, apply_cast, Args),
                    {next, User};
                false -> {next, User}
            end;
        _ -> {next, User}
    end;
%% 是否更新最后释放的主动技id
do_after_battle_callback(is_update_last_active_skill_id, _OldUser, User, SkillR, BattleArgs) when BattleArgs#battle_args.sign == ?BATTLE_SIGN_PLAYER ->
    case SkillR of
        #skill{id = SkillId, type = ?SKILL_TYPE_ACTIVE, is_combo = 0} ->
            BA = User#ets_scene_user.battle_attr,
            NewBA = BA#battle_attr{last_active_skill_id = SkillId},
            {next, User#ets_scene_user{battle_attr = NewBA}};
        _ ->  {next, User}
    end;
do_after_battle_callback(_Unkown, _OldUser, User, _SkillR, _BattleArgs) ->
    {next, User}.

%% 开始战斗(带客户端攻击对象)--场景进程中
assist_to_anyone(EtsScene, AerKey, DerKey, IsCombo, SkillId, SkillLv) ->
    assist_to_anyone(EtsScene, AerKey, DerKey, IsCombo, SkillId, SkillLv, 0).

assist_to_anyone(EtsScene, AerKey, DerKey, IsCombo, SkillId, SkillLv, AttObjectId) ->
    AerO = case lib_scene_agent:get_user(AerKey) of
        [] ->
            battle_fail(3, #battle_status{}, #battle_status{}, SkillId),
            [];
        TmpAer ->
            TmpAer
    end,
    DerO = case lib_scene_agent:get_user(DerKey) of
        [] ->
            battle_fail(1, AerO, #battle_status{}, SkillId),
            [];
        TmpDer ->
            TmpDer
    end,
    case [] == AerO orelse [] == DerO of
        true  -> {false, 0};
        false ->
            case mod_battle:assist(AerO, DerO, IsCombo, SkillId, SkillLv, AttObjectId, EtsScene) of
                {false, ErrCode, _Aer} ->
                    battle_fail(ErrCode, AerO, #battle_status{}, SkillId),
                    {false, ErrCode};
                {true, Aer, MainSkillId} ->
                    lib_scene_agent:put_user(Aer),
                    #skill_return{aer_info = Aer, used_skill = SkillId, main_skill = MainSkillId}
                    % {true, Aer, MainSkillId}
            end
    end.

%% 怪物释放主动技能
object_battle_cast(EtsScene, From, Sign, ObjectId, DerInfo, SkillId, SkillLv, SkillAdjust, OldRadian, SelectOption) ->
    % old: From ! {'object_battle_back', object_start_battle(EtsScene, Sign, ObjectId, DerInfo, SkillId, SkillLv, SkillAdjust, OldRadian, SelectOption)}.
    BattleReturn = object_start_battle(EtsScene, Sign, ObjectId, DerInfo, SkillId, SkillLv, SkillAdjust, OldRadian, SelectOption),
    Msg = {'object_battle_back', BattleReturn},
    mon_mgr:send(From, ObjectId, Msg).

%% 副技能
combo_battle_cast(EtsScene, From, Sign, ObjectId, DerInfo, SkillId, SkillLv, OldRadian, SelectOption) ->
    % old: From ! {'combo_battle_back', object_start_battle(EtsScene, Sign, ObjectId, DerInfo, SkillId, SkillLv, [], OldRadian, SelectOption)}.
    BattleReturn = object_start_battle(EtsScene, Sign, ObjectId, DerInfo, SkillId, SkillLv, [], OldRadian, SelectOption),
    Msg = {'combo_battle_back', BattleReturn},
    mon_mgr:send(From, ObjectId, Msg).

object_start_battle(EtsScene, Sign, ObjectId, DerInfo, SkillId, SkillLv, OldRadian) ->
    SkillAdjust = [],
    SelectOption = undefined,
    object_start_battle(EtsScene, Sign, ObjectId, DerInfo, SkillId, SkillLv, SkillAdjust, OldRadian, SelectOption).

%% 场景对象使用主动技能
object_start_battle(EtsScene, Sign, ObjectId, DerInfo, SkillId, SkillLv, SkillAdjust, OldRadian, SelectOption) ->
    case data_skill:get(SkillId, SkillLv) of
        [] -> {false, 0};
        #skill{is_combo = IsCombo, refind_target = IsReFindT, mod = Mod, obj = AttObj,
            is_normal = IsNormal, range = Range, lv_data=#skill_lv{distance = ThisSkillDis, area = Area}
            ,career = SkillCareer
        } ->
            %% 攻击者
            Object = case Sign of
                ?BATTLE_SIGN_PLAYER -> lib_scene_agent:get_user(ObjectId);
                _ -> lib_scene_object_agent:get_object(ObjectId)
            end,
            case Object of
                [] ->
                    {false, 0};
                #scene_object{skill_att_xy = {0, 0}} when SkillCareer =:= ?SKILL_CAREER_AI_POINT ->
                    %% 怪物释放?SKILL_CAREER_AI_POINT类型的技能但是没有指定攻击坐标时，跳过
                    {false, 0};
                _  ->
                    case Object of
                        #scene_object{sign=Sign, x=Xnow, y=Ynow, copy_id=CopyId, battle_attr=BA, skill_owner=SkillOwner} -> ok;
                        #ets_scene_user{x=Xnow, y=Ynow, copy_id=CopyId, battle_attr=BA} -> Sign=?BATTLE_SIGN_PLAYER, SkillOwner=undefined
                    end,
                    {OwnerId, OwnerSign} = lib_scene_object_ai:get_skill_owner_args(SkillOwner),

                    {OX, OY, TX, TY, OneObjectL, Distance} =
                    case DerInfo of
                        {target, ?BATTLE_SIGN_PLAYER, TId} ->
                            case lib_scene_agent:get_user(TId) of
                                #ets_scene_user{x=Xtmp, y=Ytmp} = User ->
                                    {Xnow, Ynow, Xtmp, Ytmp, [User], ThisSkillDis};
                                _ ->
                                    {Xnow, Ynow, Xnow, Ynow, [], ThisSkillDis}
                            end;

                        {target, _, TId} ->
                            case lib_scene_object_agent:get_object(TId) of
                                #scene_object{x=Xtmp, y=Ytmp} = BeAttObj ->
                                    {Xnow, Ynow, Xtmp, Ytmp, [BeAttObj], ThisSkillDis};
                                _ ->
                                    {Xnow, Ynow, Xnow, Ynow, [], ThisSkillDis}
                            end;

                        %% {xy, 主技能参考点x, 主技能参考点y, 攻击点x, 攻击点y}
                        {xy, Xrefer, Yrefer, Xatt, Yatt} ->
                            {Xrefer, Yrefer, Xatt, Yatt, [], ThisSkillDis};

                        %% 怪物有ai指定的攻击坐标
                        find_target when SkillCareer =:= ?SKILL_CAREER_AI_POINT andalso Sign =:= ?BATTLE_SIGN_MON ->
                            #scene_object{skill_att_xy = {AttX, AttY}, skill_att_xy_change = {ChDegree, ChDistance}} = Object,
                            %% 怪物坐标点和{AttX, AttY}攻击的连线，逆时针旋转ChDegree度，
                            %% 然后取在这个方向距离怪物坐标点ChDistance远的点
                            {AttX2, AttY2} = case {ChDegree, ChDistance} of
                                {0, 0} ->
                                    {AttX, AttY};
                                _ ->
                                    %% 怪物坐标点和{AttX, AttY}攻击的连线和x轴的弧度
                                    AttRadian = math:atan2(AttY - Ynow, AttX - Xnow),
                                    ChangeRadian = umath:degree_to_radian(ChDegree),
                                    Cos = math:cos(AttRadian + ChangeRadian),
                                    Sin = math:sin(AttRadian + ChangeRadian),
                                    PolarX = trunc(ChDistance * Cos),
                                    PolarY = trunc(ChDistance * Sin * 0.5),
                                    {Xnow + PolarX, Ynow + PolarY}
                            end,
                            {Xnow, Ynow, AttX2, AttY2, [], ThisSkillDis};

                        find_target -> %% 查找必须为自己为中心的椭圆
                            {Xnow, Ynow, Xnow+100, Ynow+100, [], ThisSkillDis}
                    end,

                    %% atan2算出来的是弧度
                    Radian = case IsCombo == 0 orelse OldRadian == 0 orelse IsReFindT == 1 of
                        true -> math:atan2(TY-OY, TX-OX);
                        _ -> OldRadian
                    end,
                    Degree = umath:radian_to_360degree(Radian),
                    BattleArgs = #battle_args{
                        att_key = ObjectId, att_user = Object, skill_id = SkillId, skill_lv = SkillLv
                        ,now_time = utime:longunixtime(), att_angle = Degree, is_combo = IsCombo
                    },
                    LastBattleArgsTemp = if
                        IsCombo == 1 andalso Sign == ?BATTLE_SIGN_PLAYER ->
                            RX = OX, RY = OY,
                            SFun = fun({DerSign, DerId}, TL) ->
                                    DerObject = case DerSign of
                                        ?BATTLE_SIGN_PLAYER -> lib_scene_agent:get_user(DerId);
                                        _ -> lib_scene_object_agent:get_object(DerId)
                                    end,
                                    case DerObject of
                                        [] -> TL;
                                        _ -> [DerObject|TL]
                                    end
                            end,
                            NObjects = lists:foldl(SFun, [], Object#ets_scene_user.skill_combo_ders),
                            BattleArgs#battle_args{der_list = NObjects, att_x=OX, att_y=OY};
                        true ->
                            case Mod of
                                1 -> %% 单体攻击
                                    RX = OX, RY=OY,
                                    BattleArgs#battle_args{der_list=OneObjectL, att_x=TX, att_y=TY};
                                _ when Range==1 -> %% 圆形 AttObj:攻击参照对象1:自己; 2:选取的目标
                                    RX = case AttObj of
                                        1 -> OX;
                                        _ -> TX end,
                                    RY = case AttObj of
                                        1 -> OY;
                                        _ -> TY
                                    end,
                                    Args = {BA#battle_attr.group, Sign, ObjectId, OwnerSign, OwnerId},
                                    Objects = lib_scene_calc:get_ellipse_object_for_battle(CopyId, RX, RY, Area, Args, SelectOption),
                                    NObjects = obj_select_att_num_der(Objects, Object, SkillId, SkillLv),
                                    BattleArgs#battle_args{der_list = NObjects, att_x = RX, att_y = RY};

                                _ when Range==2 -> %% 矩形
                                    Args = {BA#battle_attr.group, Sign, ObjectId, OwnerSign, OwnerId},
                                    Objects = lib_scene_calc:get_rectangle_object_for_battle(CopyId, Distance, Area, OX, OY, Radian, Args, SelectOption),
                                    RX = OX, RY = OY,
                                    NObjects = obj_select_att_num_der(Objects, Object, SkillId, SkillLv),
                                    BattleArgs#battle_args{der_list= NObjects, att_x=OX, att_y=OY};

                                _  -> %% 扇形
                                    Args = {BA#battle_attr.group, Sign, ObjectId, OwnerSign, OwnerId},
                                    Objects = lib_scene_calc:get_sector_object_for_battle(CopyId, OX, OY, TX, TY, Distance, Area, Args, SelectOption),
                                    RX = OX, RY = OY,
                                    NObjects = obj_select_att_num_der(Objects, Object, SkillId, SkillLv),
                                    BattleArgs#battle_args{der_list=NObjects, att_x=RX, att_y=RY}
                            end
                    end,
                    LastBattleArgs = case SkillCareer =:= ?SKILL_CAREER_AI_POINT andalso Sign =:= ?BATTLE_SIGN_MON of
                        true -> LastBattleArgsTemp#battle_args{att_x = TX, att_y = TY, skill_adjust = SkillAdjust};
                        _ -> LastBattleArgsTemp#battle_args{skill_adjust = SkillAdjust}
                    end,
                    case mod_battle:battle(LastBattleArgs, EtsScene) of
                        {false, ErrCode, _Aer} ->
                            {false, ErrCode};
                        {true, Aer, MainSkillId} ->
                            case Sign of
                                ?BATTLE_SIGN_PLAYER ->
                                    if
                                        IsNormal == 0 andalso IsCombo == 0 ->
                                            Fun = fun(X, TL) ->
                                                    case X of
                                                        #ets_scene_user{id = RId} -> [{?BATTLE_SIGN_PLAYER, RId}|TL];
                                                        #scene_object{id = ObjId} -> [{?BATTLE_SIGN_MON, ObjId}|TL];
                                                        _ -> TL
                                                    end
                                            end,
                                            ComboDers = lists:foldl(Fun, [], LastBattleArgs#battle_args.der_list),
                                            NewAer = Aer#ets_scene_user{skill_combo_ders = ComboDers},
                                            lib_scene_agent:put_user(NewAer),
                                            #skill_return{
                                                aer_info = NewAer,
                                                rx = RX, ry = RY, tx = TX, ty = TY, radian = Radian,
                                                used_skill = SkillId,
                                                main_skill = MainSkillId
                                            };
                                        % {true, NewAer, RX, RY, TX, TY, MainSkillId, Radian};
                                        true ->
                                            lib_scene_agent:put_user(Aer),
                                            #skill_return{
                                                aer_info = Aer,
                                                rx = RX, ry = RY, tx = TX, ty = TY, radian = Radian,
                                                used_skill = SkillId,
                                                main_skill = MainSkillId
                                            }
                                            % {true, Aer, RX, RY, TX, TY, MainSkillId, Radian}
                                    end;
                                _ ->
                                    lib_scene_object_agent:put_object(Aer),
                                    #skill_return{
                                        aer_info = lib_battle_api:slim_back_data(Aer, Sign),
                                        rx = RX, ry = RY, tx = TX, ty = TY, radian = Radian,
                                        used_skill = SkillId,
                                        main_skill = MainSkillId
                                    }
                                    % {true, lib_battle_api:slim_back_data(Aer, Sign), RX, RY, TX, TY, MainSkillId, Radian}
                            end

                    end
            end
    end.

%% 怪物释放辅助技能
object_assist_cast(EtsScene, From, ObjectId, SkillId, SkillLv, SkillAdjust) ->
    From ! {'object_assist_back', object_assist(EtsScene, ObjectId, SkillId, SkillLv, SkillAdjust)}.

combo_assist_cast(EtsScene, From, ObjectId, SkillId, SkillLv) ->
    From ! {'combo_assist_back', object_assist(EtsScene, ObjectId, SkillId, SkillLv)}.

%% 场景对象释放辅助技能
object_assist(EtsScene, ObjectId, SkillId, SkillLv) ->
    SkillAdjust = [],
    object_assist(EtsScene, ObjectId, SkillId, SkillLv, SkillAdjust).

object_assist(EtsScene, ObjectId, SkillId, SkillLv, SkillAdjust) ->
    case data_skill:get(SkillId, SkillLv) of
        [] -> {false, 0};
        #skill{is_combo=IsCombo} ->
            case lib_scene_object_agent:get_object(ObjectId) of
                [] -> {false, 0};
                #scene_object{sign = Sign} = Object ->
                    case mod_battle:assist(Object, Object, IsCombo, SkillId, SkillLv, 0, SkillAdjust, EtsScene) of
                        {false, ErrCode, _Assister} ->
                            {false, ErrCode};
                        {true, Assister, MainSkillId} ->
                            lib_scene_object_agent:put_object(Assister),
                            #skill_return{
                                aer_info = lib_battle_api:slim_back_data(Assister, Sign),
                                % rx = RX, ry = RY, tx = TX, ty = TY, radian = Radian,
                                used_skill = SkillId,
                                main_skill = MainSkillId
                            }
                            % {true, lib_battle_api:slim_back_data(Assister), MainSkillId, SkillId}
                    end
            end
    end.

%% 释放辅助技能(不能包括副技能)
assist_anything(EtsScene, SignA, IdA, SignT, IdT, SkillId, SkillLv) ->
    case data_skill:get(SkillId, SkillLv) of
        #skill{is_combo=IsCombo} ->
            AObject = case SignA of
                ?BATTLE_SIGN_PLAYER -> lib_scene_agent:get_user(IdA);
                _ -> lib_scene_object_agent:get_object(IdA)
            end,
            TObject = case SignT of
                ?BATTLE_SIGN_PLAYER -> lib_scene_agent:get_user(IdT);
                _ -> lib_scene_object_agent:get_object(IdT)
            end,
            case [] =/= AObject andalso [] =/= TObject of
                true ->
                    case mod_battle:assist(AObject, TObject, IsCombo, SkillId, SkillLv, EtsScene) of
                        {false, _ErrCode, _Assister} -> false;
                        {true, _Aer, _} -> true
                    end;
                false -> false
            end;
        _ -> false
    end.

%% 加载玩家战斗buff列表
send_player_buff_list(PlayerId) ->
    EtsPlayer = lib_scene_agent:get_user(PlayerId),
    case EtsPlayer of
        #ets_scene_user{battle_attr = BA} = User->
            NowTime = utime:longunixtime(),
            #battle_attr{attr_buff_list=AttrBuffList, other_buff_list=OtherBuffList} = BA,
            AerBuffList = lib_skill_buff:pack_buff(AttrBuffList++OtherBuffList, NowTime, []),
            {ok, BinData}  = pt_200:write(20002, [AerBuffList]),
            lib_server_send:send_to_sid(User#ets_scene_user.node, User#ets_scene_user.sid, BinData);
        _ -> skip
    end.

%% 采集怪物
%% MyKey : 玩家id
%% SrcId : 怪物自动id
%% MonCfgId : 怪物配置id
%% Type : 1开始采集, 2结束采集 3中断采集
collect_mon(RoleId, MonId, MonCfgId, Type, _ExtrArgs) ->
    case lib_scene_agent:get_user(RoleId) of
        User = #ets_scene_user{x = UserX, y = UserY, copy_id = UserCopyId,
            battle_attr = #battle_attr{hp = UserHp},
            figure = #figure{guild_id = UserGuildId},
            collect_pid = CollectPid, node = UserNode, sid = Sid
        } ->
            Res = case lib_scene_object_agent:get_object(MonId) of
                #scene_object{sign = ?BATTLE_SIGN_MON, mon = #scene_mon{mid = Mid},
                    aid = MonPid, x = MonX, y = MonY, copy_id = MonCopyId,
                    battle_attr = #battle_attr{hp = MonHp},
                    figure = #figure{guild_id = MonGuildId}
                } ->
                    if
                        MonHp =< 0 orelse UserHp =< 0 ->
                            ?ERRCODE(err200_have_collected);
                        MonCfgId =/= Mid ->
                            ?ERRCODE(err200_collect_mon_error);
                        abs(UserX - MonX) > ?LOGIC_PT_WIDTH * 8
                        orelse abs(UserY - MonY) > ?LOGIC_PT_HEIGHT * 8
                        orelse UserCopyId /= MonCopyId ->
                            ?ERRCODE(err200_long_distance);
                        MonGuildId > 0 andalso UserGuildId =/= MonGuildId ->
                            ?ERRCODE(err200_belong_error);
                        Type =:= ?COLLECT_END andalso CollectPid =/= {MonPid, MonId, Mid} ->
                            ?ERRCODE(err200_have_collected);
                        %% 这里检查剩余次数，如果前端不能保证开始与结束/中断成对出现，
                        %% 很容易出BUG，所以转移到怪物进程中检查
                        %% 场景中has_cltimes和is_collecting只作为不重要的情况下备查，
                        %% 实际以mon中数据为准
                        true ->
                            mon_mgr:collect_mon(MonPid, MonId, User, Type),
                            if
                                Type == ?COLLECT_START ->
                                    %% 检测是否有旧采集物，中止对旧采集物的采集
                                    TmpUser = collect_api:check_stop_collect(User),
                                    NewUser = TmpUser#ets_scene_user{collect_pid = {MonPid, MonId, Mid}},
                                    lib_scene_agent:put_user(NewUser),
                                    ok;
                                Type == ?COLLECT_END ->
                                    User1 = User#ets_scene_user{collect_pid = {0, 0, 0}},
                                    lib_scene_agent:put_user(User1),
                                    ok;
                                Type == ?COLLECT_STOP ->
                                    User1 = User#ets_scene_user{collect_pid= {0, 0, 0}},
                                    lib_scene_agent:put_user(User1),
                                    ?ERRCODE(err200_stop_collect);
                                true ->
                                    ?FAIL
                            end
                    end;
                _ -> ?FAIL
            end,
            case Res of
                ok -> ignore;   %% 由怪物来通知人物（怪物还要检查其他条件）
                _  ->
                    {ok, BinData} = pt_200:write(20008, [Res, MonCfgId, MonId]),
                    rpc_cast_to_node(UserNode, lib_server_send, send_to_sid, [Sid, BinData])
            end;
        _ ->
            ignore
    end.

%% 拾取型怪物
%% UserId : 玩家id
%% MonList : [怪物自增id, ...]
pick_mon(UserId, MonList) ->
    case lib_scene_agent:get_user(UserId) of
        [] -> skip;
        User ->
            #ets_scene_user{battle_attr=BA, copy_id = UserCopyId, x = UserX, y = UserY} = User,
            F = fun(SrcId) ->
                    case lib_scene_object_agent:get_object(SrcId) of
                        #scene_object{sign=?BATTLE_SIGN_MON,
                            aid=Aid, id = ObjId,
                            mon = #scene_mon{kind = Kind}
                        } = Object ->
                            Res = if
                                BA#battle_attr.hp =< 0 -> 2;
                                Kind =/= ?MON_KIND_PICK -> 4;
                                abs(UserX - Object#scene_object.x) > ?LOGIC_PT_WIDTH*3
                                orelse abs(UserY - Object#scene_object.y) > ?LOGIC_PT_HEIGHT*3
                                orelse UserCopyId /= Object#scene_object.copy_id -> 3;
                                true ->
                                    % old: mod_mon_active:pick_mon(Aid, User),
                                    mon_mgr:pick_mon(Aid, ObjId, User),
                                    %% do something...
                                    1
                            end,
                            {Res, SrcId};
                        _ ->
                            {2, SrcId}
                    end
            end,
            ResList = [F(Id) || Id <- MonList],
            {ok, BinData} = pt_200:write(20010, ResList),
            rpc_cast_to_node(User#ets_scene_user.node, lib_server_send, send_to_uid, [UserId, BinData])
    end,
    ok.

%% 检查开始战斗的攻击者和受击者
check_start_battle(AttKey, MonIdList, PlayerIdList, MainType, NowTime) ->
    case lib_scene_agent:get_user(AttKey) of
        AttUser = #ets_scene_user{battle_attr = #battle_attr{hp = Hp}} when Hp =< 0 ->
            {false, AttUser};
        AttUser = #ets_scene_user{scene = SceneId, figure = #figure{lv = Level}} ->
            %% MainType为前端指定的主目标类型，0为人1为怪，
            %% 将主目标所在列表放在DerList的最前面，方便初始化战斗对象数据时找到主目标
            %% 获取玩家
            IsOutsideScene = lib_scene:is_outside_scene(SceneId),
            IsRobot = lib_robot:is_robot(AttUser),
            IsRebScene = local_boss_api:is_reward_boss_scene(SceneId),
            IsDebScene = local_boss_api:is_deserted_boss_scene(SceneId),
            PlayerList =
            case IsOutsideScene andalso Level =< 50 andalso IsRobot =/= true of
                true  -> [];
                false ->
                    %% 受击者 是 攻击者的协助者 则不能攻击
                    F = fun(UserKey) ->
                            SceneUser = lib_scene_agent:get_user(UserKey),
                            case SceneUser of
                                #ets_scene_user{figure = #figure{lv = Level}} when Level =< 50 ->
                                    false;
                                #ets_scene_user{reb_protect_time = EndTime} when EndTime*1000 > NowTime andalso IsRebScene ->
                                    false;
                                #ets_scene_user{deb_protect_time = EndTime} when EndTime*1000 > NowTime andalso IsDebScene ->
                                    false;
                                #ets_scene_user{} ->
                                    {true, SceneUser};
                                _ ->
                                    false
                            end
                    end,
                    lists:filtermap(F, PlayerIdList)
            end,
            %% 获取怪物
            {NotExistList, MonList} = lists:foldl(
                fun(MonId, Acc) -> hurt_check_mon(AttUser, MonId, Acc) end, {[], []}, lists:reverse(MonIdList)
            ),
            DerList = case MonList of
                [] -> PlayerList;
                _  ->
                    %% MainType代表主目标类型，0是人，1是怪，
                    %% 将主目标放在列表前面，初始化数据时将设置为主目标
                    case MainType of
                        0 -> PlayerList ++ MonList;
                        _ -> MonList ++ PlayerList
                    end
            end,
            {true, AttUser, DerList, NotExistList};
        _ ->
            false
    end.

hurt_check_mon(AttUser, MonId, {NotExistList, MonList}) ->
    case lib_scene_object_agent:get_object(MonId) of
        #scene_object{battle_attr = #battle_attr{hp = Hp}} when Hp =< 0 ->
            {NotExistList, MonList};
        #scene_object{hurt_check = {Mod, Fun, Args}} = Object ->
            case Mod:Fun(AttUser, Object, Args) of
                true  ->
                    {NotExistList, [Object | MonList]};
                false ->
                    {NotExistList, MonList}
            end;
        #scene_object{} = Object ->
            {NotExistList, [Object | MonList]};
        _ ->
            {[MonId | NotExistList], MonList}
    end.

%% 战斗失败
%% ErrCode =
%%        1 对方没血
%%        2 出手太快
%%        3 自己没血
%%        4 距离太远
%%        5 技能cd未到
%%        6 您未学此技能
battle_fail(ErrCode, Aer, Der, SkillId) ->
    battle_fail(ErrCode, Aer, Der, SkillId, []).
battle_fail(ErrCode, Aer, Der, SkillId, InexistenceList) ->
    ErrData = pack_error_data(Aer, Der),
    send_battle_fail(ErrCode, ErrData, SkillId, InexistenceList).

%% 打包错误信息
pack_error_data(Aer, _Der) when Aer#battle_status.sign == 1 orelse is_record(Aer, scene_object) -> [];
pack_error_data(Aer, Der) ->
    AerData = if
        is_record(Aer, battle_status) andalso is_record(Aer#battle_status.battle_attr, battle_attr) ->
            {2, Aer#battle_status.id, Aer#battle_status.battle_attr#battle_attr.hp,
                Aer#battle_status.x, Aer#battle_status.y, Aer#battle_status.node};
        is_record(Aer, ets_scene_user) ->
            {2, Aer#ets_scene_user.id, Aer#ets_scene_user.battle_attr#battle_attr.hp,
                Aer#ets_scene_user.x, Aer#ets_scene_user.y, Aer#ets_scene_user.node};
        is_record(Aer, player_status) ->
            {2, Aer#player_status.id, Aer#player_status.battle_attr#battle_attr.hp,
                Aer#player_status.x, Aer#player_status.y, none};
        true ->
            {2, 0, 0, 0, 0, none}
    end,

    DerData = if
        is_record(Der, battle_status) andalso is_record(Der#battle_status.battle_attr, battle_attr) ->
            {Der#battle_status.sign, Der#battle_status.id, Der#battle_status.battle_attr#battle_attr.hp,
                Der#battle_status.x, Der#battle_status.y};
        is_record(Der, ets_scene_user) ->
            {2, Der#ets_scene_user.id, Der#ets_scene_user.battle_attr#battle_attr.hp,
                Der#ets_scene_user.x, Der#ets_scene_user.y};
        is_record(Der, scene_object) ->
            {1, Der#scene_object.id, Der#scene_object.battle_attr#battle_attr.hp,
                Der#scene_object.x, Der#scene_object.y};
        true ->
            {0, 0, 0, 0, 0}
    end,
    [AerData, DerData].

%% 发送错误信息
send_battle_fail(State, [{Sign1, PlayerId1, Hp1, X1, Y1, Node}, {Sign2, PlayerId2, Hp2, X2, Y2}], SkillId, NotExistList) ->
    {ok, BinData} = pt_200:write(20005, [State, Sign1, PlayerId1, Hp1, X1, Y1, Sign2, PlayerId2, Hp2, X2, Y2, SkillId, NotExistList]),
    rpc_cast_to_node(Node, lib_server_send, send_to_uid, [PlayerId1, BinData]);
send_battle_fail(_, _R, _SkillId, _) -> skip.

%% 远程过程调用函数(跨服中心服->单服跨服节点)
rpc_cast_to_node(Node, M, F, A) ->
    case Node =:= none of
        true  -> erlang:apply(M, F, A);
        false -> rpc:cast(Node, M, F, A)
    end.

%% 广播战斗信息
send_to_scene(CopyId, X, Y, Broadcast, BattleBin) ->
    case Broadcast of
        ?BROADCAST_AREA -> lib_scene_agent:send_to_local_area_scene(CopyId, X, Y, BattleBin);
        ?BROADCAST_ALL  -> lib_scene_agent:send_to_local_scene(CopyId, BattleBin)
    end.

%% 战斗协议返回
send_to_scene_area(#ets_scene_user{copy_id=CopyId, x=X, y=Y}, Broadcast, BattleBin) ->
    case Broadcast of
        ?BROADCAST_AREA -> lib_scene_agent:send_to_local_area_scene(CopyId, X, Y, BattleBin);
        ?BROADCAST_ALL  -> lib_scene_agent:send_to_local_scene(CopyId, BattleBin)
    end;
send_to_scene_area(#scene_object{copy_id=CopyId, x=X, y=Y}, Broadcast, BattleBin) ->
    case Broadcast of
        ?BROADCAST_AREA -> lib_scene_agent:send_to_local_area_scene(CopyId, X, Y, BattleBin);
        ?BROADCAST_ALL  -> lib_scene_agent:send_to_local_scene(CopyId, BattleBin)
    end;
send_to_scene_area(_, _, _) -> skip.

%% 战斗定时器处理
timer_call_back(#ets_scene_user{id = Id, node = Node, copy_id = CopyId, x = X,
        y = Y, battle_attr = BA, hp_resume_ref = HpReRef} = User, EventId, Args, EtsScene, TimeRef) ->
    case EventId of
        ?HP_RESUME ->
            NowTime = utime:longunixtime(),
            #battle_attr{hp = Hp, hp_lim = HpLim,
                hp_resume_time = HpResumeTime, hp_resume_add = HpResumeAdd,
                other_buff_list = OtherBuffList
            } = BA,
            IsUnresume = lib_skill_buff:is_unresume(OtherBuffList, NowTime),
            case Hp > 0 andalso HpResumeAdd > 0 andalso IsUnresume == false of
                true ->
                    NewHp = min(Hp + HpResumeAdd, HpLim),
                    {ok, BinData} = pt_120:write(12009, [Id, NewHp, HpLim]),
                    send_to_scene(CopyId, X, Y, EtsScene#ets_scene.broadcast, BinData),
                    util:cancel_timer(HpReRef),
                    Ref1 = add_resume_timer(undefined, NewHp, HpLim, Id, NowTime, HpResumeTime),
                    rpc_cast_to_node(Node, lib_player, update_player_info, [ Id, [{hp, NewHp}] ]),
                    %% 回血被动技能触发(走辅助技能施放)
                    NewUser = User#ets_scene_user{battle_attr = BA#battle_attr{hp = NewHp}, hp_resume_ref = Ref1},
                    lib_scene_agent:put_user(NewUser);
                false when is_reference(HpReRef) ->
                    lib_scene_agent:put_user(User#ets_scene_user{hp_resume_ref = undefined});
                _ ->
                    skip
            end;

        ?SPBUFF_RESUME -> %% buff回血:回复固定值
            NowTime = utime:longunixtime(),
            {GapTime, AddHp, Personality, Count} = Args,
            #battle_attr{hp = Hp, hp_lim = HpLim, other_buff_list = OtherBuffList} = BA,
            IsUnresume = lib_skill_buff:is_unresume(OtherBuffList, NowTime),
            case Hp > 0 andalso Count > 0 andalso IsUnresume == false of
                true ->
                    NewAddHp = lib_skill_buff:calc_hp_be_resume(User, AddHp, NowTime),
                    NewHp = min(round(Hp+NewAddHp), HpLim),
                    {ok, BinData} = pt_120:write(12009, [Id, NewHp, HpLim]),
                    send_to_scene(CopyId, X, Y, EtsScene#ets_scene.broadcast, BinData),
                    lib_scene_timer:add_timer(NowTime, GapTime, ?BATTLE_SIGN_PLAYER, Id, ?SPBUFF_RESUME, {GapTime, AddHp, Personality, Count-1}),
                    rpc_cast_to_node(Node, lib_player, update_player_info, [Id, [{hp, NewHp}]]),
                    %% 回血被动技能触发(走辅助技能施放)
                    NewUser = User#ets_scene_user{battle_attr = BA#battle_attr{hp = NewHp}},
                    lib_scene_agent:put_user(NewUser);
                false ->
                    skip
            end;

        ?SPEED -> %% 速度恢复
            NowTime = utime:longunixtime(),
            [Float, Int] = lib_skill_buff:calc_speed_helper(BA#battle_attr.attr_buff_list++BA#battle_attr.other_buff_list, NowTime, [1.0, 0]),
            Speed = round(BA#battle_attr.attr#attr.speed * Float+Int),
            {ok, BinData} = pt_120:write(12082, [?BATTLE_SIGN_PLAYER, Id, Speed]),
            send_to_scene(CopyId, X, Y, EtsScene#ets_scene.broadcast, BinData),
            lib_scene_agent:put_user(User#ets_scene_user{battle_attr=BA#battle_attr{speed=Speed}});

        ?SPBUFF_FIRING ->
            {GapTime, FireHurt, Count, {AtterId, AttSign, _AttName} = ATTer} = Args,
            #battle_attr{hp = Hp, hp_lim = HpLim,  fire_ref = OFireRefs} = BA,
            case Hp > 0 andalso Count > 0 of
                true ->
                    NowTime = utime:longunixtime(),
                    NewHp = max(0, Hp-FireHurt),
                    {ok, BinData} = if
                        AttSign == ?BATTLE_SIGN_PLAYER -> pt_120:write(12024, [AtterId, Id, NewHp, HpLim]);
                        true -> pt_120:write(12009, [Id, NewHp, HpLim])
                    end,
                    send_to_scene(CopyId, X, Y, EtsScene#ets_scene.broadcast, BinData),
                    FireRef = lib_scene_timer:add_timer(NowTime, GapTime, ?BATTLE_SIGN_PLAYER, Id, ?SPBUFF_FIRING, {GapTime, FireHurt, Count-1, ATTer}),
                    NewFireRefs = [FireRef|lists:delete(TimeRef, OFireRefs)],
                    NewUser = User#ets_scene_user{battle_attr = BA#battle_attr{hp = NewHp, fire_ref = NewFireRefs}},
                    if
                        NewHp =< 0->
                            Atter = case AttSign of
                                ?BATTLE_SIGN_PLAYER -> lib_scene_agent:get_user(AtterId);
                                _ -> lib_scene_object_agent:get_object(AtterId)
                            end,
                            case Atter of
                                [] -> skip;
                                _  -> send_die_msg_to_object(NewUser, Atter, NewHp, FireHurt)
                            end;
                        true ->
                            rpc_cast_to_node(Node, lib_player, update_player_info, [Id, [{hp, NewHp}]])
                    end,
                    lib_scene_agent:put_user(NewUser);
                false ->
                    NewUser = User#ets_scene_user{battle_attr = BA#battle_attr{fire_ref = []}},
                    lib_scene_agent:put_user(NewUser)
            end;

        ?SPBUFF_BLEED ->
            {GapTime, Bleed, Count, {AtterId, AttSign, _AttName} = ATTer} = Args,
            #battle_attr{hp = Hp, hp_lim = HpLim} = BA,
            GodUn = god_api:is_un_control(User#ets_scene_user.god_fighting),
            case not GodUn andalso (Hp > 0 andalso Count > 0) of
                true ->
                    NowTime = utime:longunixtime(),
                    NewHp = round(max(0, Hp-Bleed)),
                    %% 血量广播
                    {ok, BinData} = if
                        AttSign == ?BATTLE_SIGN_PLAYER -> pt_120:write(12024, [AtterId, Id, NewHp, HpLim]);
                        true -> pt_120:write(12009, [Id, NewHp, HpLim])
                    end,
                    send_to_scene(CopyId, X, Y, EtsScene#ets_scene.broadcast, BinData),
                    lib_scene_timer:add_timer(NowTime, GapTime, ?BATTLE_SIGN_PLAYER, Id, ?SPBUFF_BLEED, {GapTime, Bleed, Count-1, ATTer}),
                    NewUser = User#ets_scene_user{battle_attr = BA#battle_attr{hp = NewHp}},
                    if
                        NewHp =< 0->
                            Atter = case AttSign of
                                ?BATTLE_SIGN_PLAYER -> lib_scene_agent:get_user(AtterId);
                                _ -> lib_scene_object_agent:get_object(AtterId)
                            end,
                            case Atter of
                                [] -> skip;
                                _  -> send_die_msg_to_object(NewUser, Atter, NewHp, Bleed)
                            end;
                        true ->
                            rpc_cast_to_node(Node, lib_player, update_player_info, [Id, [{hp, NewHp}]])
                    end,
                    lib_scene_agent:put_user(NewUser);
                false ->
                    skip
            end;
        %% 暂时没有使用，未来不排除需要重新开启
        % ?SKILL_BUFF_TRIGGER_HPRESUME ->
        %     %% 被动技能回血后触发
        %     skip;
        _ ->
            skip
    end;
timer_call_back(#scene_object{
        sign = Sign, id = Id, copy_id = CopyId, x = X, y = Y,
        battle_attr = BA} = Object, EventId, Args, EtsScene, TimeRef) ->
    case EventId of
        ?HP_RESUME ->
            %% 怪物不会有回血事件，由怪物状态机逻辑维护回血
            skip;

        ?SPEED -> %% 速度
            NowTime = utime:longunixtime(),
            [Float, Int] = lib_skill_buff:calc_speed_helper(BA#battle_attr.attr_buff_list++BA#battle_attr.other_buff_list, NowTime, [1.0, 0]),
            Speed = round(BA#battle_attr.attr#attr.speed * Float+Int),
            {ok, BinData} = pt_120:write(12082, [Sign, Id, Speed]),
            send_to_scene(CopyId, X, Y, EtsScene#ets_scene.broadcast, BinData),
            lib_scene_object_agent:put_object(Object#scene_object{battle_attr=BA#battle_attr{speed=Speed}});

        ?SPBUFF_RESUME -> %% buff回血
            NowTime = utime:longunixtime(),
            {GapTime, Value, Personality, Count} = Args,
            #battle_attr{hp=Hp, hp_lim=HpLim, other_buff_list = OtherBuffList} = BA,
            IsUnresume = lib_skill_buff:is_unresume(OtherBuffList, NowTime),
            case Hp > 0 andalso Count > 0 andalso IsUnresume ==false of
                true ->
                    NewAddHp = lib_skill_buff:calc_hp_be_resume(Object, Value, NowTime),
                    Hp1 = min(round(Hp+NewAddHp), HpLim),
                    {ok, BinData} = pt_120:write(12009, [Id, Hp1, HpLim]),
                    send_to_scene(CopyId, X, Y, EtsScene#ets_scene.broadcast, BinData),
                    lib_scene_timer:add_timer(NowTime, GapTime, Sign, Id, ?SPBUFF_RESUME, {GapTime, Value, Personality, Count-1}),
                    lib_scene_object_agent:change_attr_by_ids([Id], [{hp, Hp1}]),
                    lib_scene_object_agent:put_object(Object#scene_object{battle_attr=BA#battle_attr{hp=Hp1}});
                false ->
                    skip
            end;

        ?SPBUFF_FIRING -> %% 灼烧
            {GapTime, FireHurt, Count, {AtterId, AttSign, _AttName}} = Args,
            #battle_attr{hp = Hp, hp_lim = HpLim, fire_ref = OFireRefs} = BA,
            case Hp > 0 andalso Count > 0 of
                true ->
                    NowTime = utime:longunixtime(),
                    NewHp = max(0, Hp-FireHurt),
                    %% 血量广播
                    {ok, BinData} = pt_120:write(12009, [Id, NewHp, HpLim]),
                    send_to_scene(CopyId, X, Y, EtsScene#ets_scene.broadcast, BinData),
                    FireRef = lib_scene_timer:add_timer(NowTime, GapTime, Sign, Id, ?SPBUFF_FIRING,
                        {GapTime, FireHurt, Count-1, {AtterId, AttSign, _AttName}}),
                    NewFireRefs = [FireRef|lists:delete(TimeRef, OFireRefs)],
                    lib_scene_object_agent:change_attr_by_ids([Id], [{hp, NewHp}]),
                    NewOj = Object#scene_object{battle_attr = BA#battle_attr{hp = NewHp, fire_ref = NewFireRefs}},
                    lib_scene_object_agent:put_object(NewOj),
                    if
                        NewHp =< 0->
                            Atter = case AttSign of
                                ?BATTLE_SIGN_PLAYER -> lib_scene_agent:get_user(AtterId);
                                _ -> lib_scene_object_agent:get_object(AtterId)
                            end,
                            case Atter of
                                [] -> skip;
                                _  -> send_die_msg_to_object(NewOj, Atter, NewHp, FireHurt)
                            end;
                        true -> skip
                    end;
                false ->
                    NewOj = Object#scene_object{battle_attr = BA#battle_attr{fire_ref = []}},
                    lib_scene_object_agent:put_object(NewOj)
            end;
        ?SPBUFF_BLEED ->
            {GapTime, Bleed, Count, {AtterId, AttSign, _AttName} = ATTer} = Args,
            #battle_attr{hp = Hp, hp_lim = HpLim} = BA,
            case Hp > 0 andalso Count > 0 of
                true ->
                    NowTime = utime:longunixtime(),
                    NewHp = round(max(0, Hp-Bleed)),
                    {ok, BinData} = pt_120:write(12009, [Id, NewHp, HpLim]),
                    send_to_scene(CopyId, X, Y, EtsScene#ets_scene.broadcast, BinData),
                    lib_scene_timer:add_timer(NowTime, GapTime, Sign, Id, ?SPBUFF_BLEED, {GapTime, Bleed, Count-1, ATTer}),
                    lib_scene_object_agent:change_attr_by_ids([Id], [{hp, NewHp}]),
                    NewOj = Object#scene_object{battle_attr = BA#battle_attr{hp = NewHp}},
                    lib_scene_object_agent:put_object(NewOj),
                    if
                        NewHp =< 0->
                            Atter = case AttSign of
                                ?BATTLE_SIGN_PLAYER -> lib_scene_agent:get_user(AtterId);
                                _ -> lib_scene_object_agent:get_object(AtterId)
                            end,
                            case Atter of
                                [] -> skip;
                                _  -> send_die_msg_to_object(NewOj, Atter, NewHp, Bleed)
                            end;
                        true ->
                            skip
                    end;
                false ->
                    skip
            end;

        _ -> skip
    end;
timer_call_back(_, _, _, _, _) ->
    skip.

%% 进场景 启动回血定时器
add_resume_timer(Ref, Hp, HpLim, Id, NowTime, GapTime) ->
    add_resume_timer(Ref, Hp, HpLim, ?BATTLE_SIGN_PLAYER, Id, NowTime, GapTime).
add_resume_timer(Ref, Hp, HpLim, Sign, Id, NowTime, GapTime) ->
    if
        Hp >= HpLim orelse Hp == 0 orelse
        GapTime == 0 orelse is_reference(Ref) -> undefined;
        true -> lib_scene_timer:add_timer(NowTime, GapTime, Sign, Id, ?HP_RESUME, 0)
    end.

remove_resume_timer(HpResumeTimer) ->
    util:cancel_timer(HpResumeTimer).

%% 被打 开启回血定时器
begin_resume_timer(Ref, Sign, Id, BA, NowTime, GapTime) ->
    #battle_attr{hp=Hp, hp_lim=HpLim, hp_resume_add = HpResumeAdd} = BA,
    if
        Hp =< 0 orelse Hp >= HpLim orelse HpResumeAdd =< 0 -> undefined;
        is_reference(Ref) -> Ref;
        true -> lib_scene_timer:add_timer(NowTime, max(3000, GapTime), Sign, Id, ?HP_RESUME, 0)
    end.

%% 检查pk状态
%% check_pk_and_safe(_, _, _) -> true | {false, ErrCode}
%% ErrCode实际上没有使用，可能与战斗协议文档中的内容不相符
check_pk_and_safe(Aer, Der, EtsScene, NowTime) ->
    #battle_status{id=IdA, server_id = ServerIdA, copy_id=CopyIdA,
        sign=InitAerSign, battle_attr=#battle_attr{pk=AerPk, group=GroupA, ghost=GhostA},
        team_id=TeamIdA, guild_id=GuildIdA, skill_owner=SkillOwnerA, att_tired=AttTired,
        can_att_list = CanAttListA, support_id = AerSId, support_team = AerSTeam} = Aer,

    #battle_status{id=IdD, config_id = ConfigIdD, server_id = ServerIdD, copy_id=CopyIdD,
        sign=DerSign, battle_attr=#battle_attr{hp = DerHp, pk=DerPk, group=GroupD, ghost=GhostD},
        team_id=TeamIdD, guild_id=GuildIdD, skill_owner=SkillOwnerD, is_be_atted=IsBeAttedD, kind = _KindD,
        support_id = DerSId, support_team = DerSTeam
    } = Der,

    AerSign = mod_battle:calc_real_sign(InitAerSign),

    %% ============================================ 特殊判断 ===========================================
    %% 限定攻击不为空，对象不在限定攻击列表中，则不可攻击
    CanAtt = case {CanAttListA =/=[], lists:keyfind(DerSign, 1, CanAttListA)} of
        {true, {?BATTLE_SIGN_MON, []} } -> true;
        {true, {?BATTLE_SIGN_MON, AttList} } -> lists:member(ConfigIdD, AttList);
        {true, {?BATTLE_SIGN_PLAYER, AttList} } ->
            case AttList =:= [] of
                true -> true;
                false -> lists:member(IdD, AttList)
            end;
        {true, _} -> false;
        _ -> true
    end,
    %% 赏金BOSS场景判断伤害, 疲劳值
    IsBossForbid = local_boss_api:is_forbid_battle(Aer, Der, EtsScene),

    %% TODO 关于CheckPkSafeTime，需要游戏服与跨服全局时间校正再修改，
    %% CheckPkSafeTime也应该直接用战斗发起时校正后的本节点时间，
    %% 就不需要每次都重新生成一个NowTime了
    %% ===================================================================================================
    Res = if
        AttTired >= 100 -> {false, 1};     %% 疲劳值上限

        CopyIdA =/= CopyIdD -> {false, 2}; %% 不在同个房间

        IsBeAttedD == 0 -> {false, 3};      %% 不允许被攻击

        DerHp =< 0 -> {false, 4};           %% 没血不允许被攻击

        %% 不能攻击父单位
        SkillOwnerA#skill_owner.id > 0 andalso
        SkillOwnerA#skill_owner.id == IdD ->
            {false, 5};

        %% 不能攻击子单位
        IdA == SkillOwnerD#skill_owner.id -> {false, 6};

        %% 同一所属的子单位不能互相攻击
        SkillOwnerA#skill_owner.id > 0 andalso
        SkillOwnerA#skill_owner.id == SkillOwnerD#skill_owner.id ->
            {false, 7};

        IdA == IdD andalso AerSign == DerSign -> {false, 8}; %% 不能攻击自己

        GroupA > 0 andalso GroupA == GroupD -> {false, 9}; %% 分组一样

        GroupA == 0 andalso GroupD == 0 andalso AerSign == ?BATTLE_SIGN_MON andalso DerSign == ?BATTLE_SIGN_MON -> {false, 10}; %% 分组为0，怪物不能互相攻击

        GhostA > 0 orelse GhostD > 0 -> {false, 11}; %% 是幽灵状态

        EtsScene#ets_scene.subtype == ?SCENE_SUBTYPE_SAFE
            andalso (AerSign =:= ?BATTLE_SIGN_PLAYER andalso Der#battle_status.sign =:= ?BATTLE_SIGN_PLAYER) -> {false, 12}; %% 安全场景不能pk

        NowTime < DerPk#pk.pk_protect_time -> {false, 13}; %% 处于保护时间，不可被攻击

        CanAtt == false -> {false, 14};

        %% 忽略PK状态，协助者与被协助者及其队员不能相互攻击/协助者不可攻击被协助者的子单位
        (DerSId > 0 andalso IdA =:= DerSId)
          orelse (DerSTeam > 0 andalso TeamIdA =:= DerSTeam)
          orelse (AerSId > 0 andalso AerSId =:= IdD)
          orelse (AerSTeam > 0 andalso AerSTeam =:= TeamIdD)
          orelse (AerSId > 0 andalso AerSId =:= SkillOwnerD#skill_owner.id) -> {false, 15};

        IsBossForbid -> {false, 16};
        %% ================================================================================
        %% 以下为pk_status状态和活动状态相关判断，非相关pk_status状态或活动判断请勿写在下面
        %% ================================================================================
        %% 非全体 不能攻击同队伍和同帮派的玩家
        AerPk#pk.pk_status /= ?PK_ALL andalso (TeamIdA > 0 andalso TeamIdA == TeamIdD andalso GroupA == 0) -> {false, 17}; %% 同一队伍
        AerPk#pk.pk_status /= ?PK_ALL andalso GroupA == 0  andalso (
            GuildIdA > 0 andalso (GuildIdA == GuildIdD orelse GuildIdA == SkillOwnerD#skill_owner.guild_id)
        ) ->  {false, 18};   % 同一家族

        %% 服务器模式不能攻击同服玩家
        AerPk#pk.pk_status == ?PK_DIFF_SERVER andalso ServerIdA>0 andalso ServerIdA == ServerIdD -> {false, 19};

        %% 和平模式下只能攻击 怪物 和 红名的非同队和同工会玩家
        AerPk#pk.pk_status == ?PK_PEACE andalso AerSign =:= ?BATTLE_SIGN_PLAYER andalso DerSign == ?BATTLE_SIGN_PLAYER andalso
            (DerPk#pk.pk_value == 0 orelse (TeamIdA > 0 andalso TeamIdA == TeamIdD) orelse (GuildIdA > 0 andalso GuildIdA == GuildIdD)) -> {false, 20};

        %% 公会PK模式不能攻击本公会玩家
        AerPk#pk.pk_status == ?PK_GUILD andalso GuildIdA == GuildIdD andalso GuildIdA > 0 -> {false, 21};

        %% 攻击方终极和平模式，不能攻击任何人
        AerPk#pk.pk_status == ?PK_PEACE_ULTIMATE andalso DerSign == ?BATTLE_SIGN_PLAYER -> {false, 22};

        %% 防守方终极和平模式，不能任何人被攻击
        DerPk#pk.pk_status == ?PK_PEACE_ULTIMATE andalso AerSign =:= ?BATTLE_SIGN_PLAYER -> {false, 23};

        %% 强制模式：不能攻击同帮/同队
        AerPk#pk.pk_status == ?PK_FORCE andalso (
            (GuildIdA > 0 andalso GuildIdA == GuildIdD)
            orelse (TeamIdA > 0 andalso TeamIdA == TeamIdD)) -> {false, 24};
        true -> check_pk_and_safe_helper(Aer, Der, EtsScene)
    end,
    Res.

check_pk_and_safe_helper(Aer, Der, EtsScene) ->
    #battle_status{id = IdA, config_id = ConfigIdA, copy_id=CopyId, x=XA, y=YA, sign=InitAerSign} = Aer,
    #battle_status{x=XD, y=YD, sign = DerSign, cannot_beatt_list = CannotBeattListD, owner = Owner} = Der,
    AerSign = mod_battle:calc_real_sign(InitAerSign),
    %% 限定某些对象不能攻击我
    CannotHitMe = if  CannotBeattListD =/=[] ->
            case lists:keyfind(AerSign, 1, CannotBeattListD) of
                {?BATTLE_SIGN_MON, CannotBeattList} -> lists:member(ConfigIdA, CannotBeattList);
                {?BATTLE_SIGN_PLAYER, AttListA} ->
                    case AttListA =:= [] of
                        true -> true;
                        false -> lists:member(IdA, AttListA)
                    end;
                _ -> false
            end;
        true -> false
    end,
    %% 限定只有某个人可以攻击我
    OwnerHit = Owner == 0 orelse Owner =:= IdA,
    if
        CannotHitMe -> {false, 30}; %% 不可攻击对象类型
        not OwnerHit -> {false, 31};
        true ->
            RealDerSign = mod_battle:calc_real_sign(DerSign),
            %% 只有人打人判断安全区(废土战场(饥饿游戏)，假人也需要计算入内)
            WastelandScene = lib_wasteland_war:is_in_wasteland_scene(EtsScene#ets_scene.id),
            case WastelandScene of
                true ->
                    IsRealAerRole = AerSign =:= ?BATTLE_SIGN_PLAYER orelse AerSign =:= ?BATTLE_SIGN_DUMMY,
                    IsRealDerRole = RealDerSign =:= ?BATTLE_SIGN_PLAYER orelse RealDerSign =:= ?BATTLE_SIGN_DUMMY;
                _ ->
                    IsRealAerRole = AerSign =:= ?BATTLE_SIGN_PLAYER,
                    IsRealDerRole = RealDerSign =:= ?BATTLE_SIGN_PLAYER
            end,
            case (IsRealAerRole andalso IsRealDerRole) of
                true ->
                    case check_safe_area(EtsScene#ets_scene.id, CopyId, XA, YA, XD, YD) of
                        true -> {false, 32};
                        false -> true
                    end;
                false ->
                    true
            end
    end.

check_safe_area(SceneId, CopyId, XA, YA, XD, YD) ->
    lib_scene:is_safe(SceneId, CopyId, XD, YD) orelse
        lib_scene:is_safe(SceneId, CopyId, XA, YA).

%% 判断攻击距离
check_distance(#skill{calc = 1}, SkillDistance, Aer, Der) ->
    %% 不计算伤害的技能不验证距离，计算伤害的才真实检查
    sub_check_distance(SkillDistance, Aer, Der);
check_distance(_SkillConfig, _SkillDistance, _Aer, _Der) ->
    false.

sub_check_distance(Distance, Aer, Der) ->
    #battle_status{scene = AerScene, copy_id = AerCopyId, x = AerX, y = AerY} = Aer,
    #battle_status{scene = DerScene, copy_id = DerCopyId, x = DerX, y = DerY} = Der,
    if
        {AerScene, AerCopyId} =/= {DerScene, DerCopyId} ->
            false;
        true ->
            DisX = abs(AerX - DerX),
            DisY = abs(AerY - DerY),
            %% 放宽一定距离验证
            (Distance + 120 >= DisX) andalso (Distance * 1.5 + 120 >= DisY)
    end.

%% 特殊技能检测(子弹类技能落点为技能触发点计算伤害范围)
%% distance作为技能的投射范围,area最技能伤害范围
%%sub_check_special_distance(Distance, Area, Aer, Der) ->
%%    #battle_status{scene = AerScene, copy_id = AerCopyId, x = AerX, y = AerY} = Aer,
%%    #battle_status{scene = DerScene, copy_id = DerCopyId, x = DerX, y = DerY} = Der,
%%    if
%%        {AerScene, AerCopyId} =/= {DerScene, DerCopyId} ->
%%            false;
%%        true ->
%%            DisX = abs(AerX - DerX),
%%            DisY = abs(AerY - DerY),
%%            %% 放宽一定距离验证(类似环形范围)
%%            (max(0, Distance-50) =< DisX) andalso (max(0, Distance-50) =< DisY) andalso
%%            (Distance + Area + 120 >= DisX) andalso ((Distance + Area) * 1.5 + 120 >= DisY)
%%    end.

%% 强制打破隐身
break_steath_force(EtsScene, Sign, Id) ->
    case Sign of
        ?BATTLE_SIGN_PLAYER ->
            case lib_scene_agent:get_user(Id) of
                #ets_scene_user{copy_id=CopyId, x=X, y=Y, battle_attr=BA} = User when BA#battle_attr.hide==1->
                    lib_skill_buff:handle_steath_state(?BATTLE_SIGN_PLAYER, Id, CopyId,
                        X, Y, EtsScene#ets_scene.broadcast, 0, 0, 0),
                    lib_scene_agent:put_user(User#ets_scene_user{battle_attr=BA#battle_attr{hide=0}});
                _ -> skip
            end;
        _ ->
            case lib_scene_object_agent:get_object(Id) of
                #scene_object{copy_id=CopyId, x=X, y=Y, battle_attr=BA} = Object when BA#battle_attr.hide==1 ->
                    lib_skill_buff:handle_steath_state(Sign, Id,CopyId,
                        X, Y, EtsScene#ets_scene.broadcast, 0, 0, 0),
                    lib_scene_object_agent:put_object(Object#scene_object{battle_attr=BA#battle_attr{hide=0}});
                _ -> skip
            end
    end.

%% 发送死亡信息
send_die_msg_to_object(Object, Atter, Hpb, Hurt)->
    Aer = mod_battle:init_data(Atter),
    Der = mod_battle:init_data(Object),
    [RetrunAtter, RetrunSign]
    = case Aer#battle_status.skill_owner of
        %% 父类属性
        #skill_owner{id=OwnerId, pid=OwnerPid, node=OwnerNode,
            guild_id = OwnerGuildId, team_id=OwnerTeamId, sign=OwnerSign} ->
            [ #battle_return_atter{sign = OwnerSign, id = OwnerId, node = OwnerNode, server_id = Aer#battle_status.server_id, mid = Aer#battle_status.mid,
                    kind = Aer#battle_status.kind, pid = OwnerPid, team_id = OwnerTeamId, x = Aer#battle_status.x,
                    y = Aer#battle_status.y, hide = Aer#battle_status.battle_attr#battle_attr.hide, guild_id = OwnerGuildId,
                    real_id = Aer#battle_status.id, real_sign = Aer#battle_status.sign, real_name = Aer#battle_status.figure#figure.name,
                    lv = Aer#battle_status.figure#figure.lv, career = Aer#battle_status.figure#figure.career,group = Aer#battle_status.battle_attr#battle_attr.group
                }, OwnerSign];
        _ ->
            OwnerNode = Aer#battle_status.node,
            OwnerId = Aer#battle_status.id,
            [ #battle_return_atter{sign = Aer#battle_status.sign, id = OwnerId, node = OwnerNode, server_id = Aer#battle_status.server_id,
                    mid = Aer#battle_status.mid, kind = Aer#battle_status.kind, pid = Aer#battle_status.pid,
                    team_id = Aer#battle_status.team_id, x = Aer#battle_status.x, y = Aer#battle_status.y,
                    hide = Aer#battle_status.battle_attr#battle_attr.hide, guild_id = Aer#battle_status.guild_id,
                    guild_name=Aer#battle_status.figure#figure.guild_name,flag = Aer#battle_status.figure#figure.flag,
                    real_id = Aer#battle_status.id, real_sign = Aer#battle_status.sign, real_name = Aer#battle_status.figure#figure.name,
                    lv = Aer#battle_status.figure#figure.lv, career = Aer#battle_status.figure#figure.career,group = Aer#battle_status.battle_attr#battle_attr.group
                }, Aer#battle_status.sign]
    end,
    %% 防守方信息
    #battle_status{node = DefNode, id = DerId, pid = DefPid, sign = DerSign} = Der,
    BattleReturn = #battle_return{hp = Hpb, hurt = Hurt, sign = RetrunSign, atter = RetrunAtter,
        attr_buff_list = Der#battle_status.battle_attr#battle_attr.attr_buff_list,
        other_buff_list = Der#battle_status.battle_attr#battle_attr.other_buff_list},
    if
        DerSign == ?BATTLE_SIGN_PLAYER ->
            mod_battle:send_to_node_pid(DefNode, DefPid, {'battle_info_die', BattleReturn});
        true ->
            mod_battle:send_to_node_mon(DefNode, DefPid, DerId, {'battle_info', BattleReturn})
    end.

%% 选择攻击对象
obj_select_att_num_der(DerObjects, Object, SkillId, SkillLv)->
    #skill{lv_data = #skill_lv{num  = [PNum, MNum]}} = data_skill:get(SkillId, SkillLv),
    case Object of
        #scene_object{can_att_list = CanAttList} -> ok;
        #ets_scene_user{} -> CanAttList = []
    end,
    obj_select_att_num_der(DerObjects, CanAttList, PNum, MNum, [], 0, 0).

obj_select_att_num_der([], _CanAttList, _PNum, _MNum, Ders, _PN, _MN) -> Ders;
obj_select_att_num_der([#ets_scene_user{}=DerObj|DerObjects], CanAttList, PNum, MNum, Ders, PN, MN)->
    CanAtt = if CanAttList =/= [] ->
            case lists:keyfind(?BATTLE_SIGN_PLAYER, 1, CanAttList) of
                {?BATTLE_SIGN_PLAYER, AttListA} -> lists:member(DerObj#ets_scene_user.id, AttListA);
                _ -> false
            end;
        true -> true
    end,
    if
        PN < PNum andalso CanAtt == true -> obj_select_att_num_der(DerObjects, CanAttList, PNum, MNum, [DerObj|Ders], PN+1, MN);
        true -> obj_select_att_num_der(DerObjects, CanAttList, PNum, MNum, Ders, PN, MN)
    end;
obj_select_att_num_der([#scene_object{config_id = ConfigId} = DerObj|DerObjects], CanAttList, PNum, MNum, Ders, PN, MN)->
    CanAtt = if CanAttList =/= [] ->
            case lists:keyfind(?BATTLE_SIGN_MON, 1, CanAttList) of
                {?BATTLE_SIGN_MON, []} -> true;
                {?BATTLE_SIGN_MON, AttList} -> lists:member(ConfigId, AttList);
                _ -> false
            end;
        true -> true
    end,
    if
        MN < MNum andalso CanAtt == true -> obj_select_att_num_der(DerObjects, CanAttList, PNum, MNum, [DerObj|Ders], PN, MN+1);
        true -> obj_select_att_num_der(DerObjects, CanAttList, PNum, MNum, Ders, PN, MN)
    end.

%% 清除主动技buff
clean_active_skill_buff(User, EtsScene) ->
    BA = User#ets_scene_user.battle_attr,
    SkillId = BA#battle_attr.last_active_skill_id,
    case data_skill:get(SkillId, 1) of
        #skill{combo = ComboList} when ComboList =/= [] ->
            clean_active_skill_buff_helper(EtsScene, ComboList, User);
        _ -> User
    end.

clean_active_skill_buff_helper(_EtsScene, [], User) -> User;
clean_active_skill_buff_helper(EtsScene, [{SkillId, _Time, _IsInterrupt}|T], User) ->
    NewUser = lib_skill_buff:clean_buff(EtsScene, User, SkillId),
    clean_active_skill_buff_helper(EtsScene, T, NewUser).

%% 计算技能攻击距离
calc_skill_distance(SkillConfig) ->
    #skill{range = Range, lv_data = #skill_lv{distance = Distance, area = Area}} = SkillConfig,
    case Range of
        1 ->
            Distance + 0.8 * Area;
        _ ->
            Distance
    end.
%% ---------------------------------------------------------------------------
%% @doc 角色杀怪获得怒气
-spec check_get_anger(SceneId, PoolId, Atter) -> ok when
    SceneId     :: integer(),
    PoolId      :: integer(),
    Atter       :: #battle_return_atter{}.
%% ---------------------------------------------------------------------------
check_get_anger(SceneId, PoolId, Atter = #battle_return_atter{sign = ?BATTLE_SIGN_PLAYER}) ->
    #battle_return_atter{
        id = RoleId, pid = AttPid, anger = Anger, anger_lim = AngerLim
        ,attr_skill_id = HitSkillId
    } = Atter,
    HitSkillCfg = data_skill:get(HitSkillId, 1),
    case {is_pid(AttPid), Anger < AngerLim, HitSkillCfg} of
        {true, true, #skill{type = ?SKILL_TYPE_ACTIVE, is_anger = 0}} ->
            GetAngerValue = ?ANGER_ADD,
            Args = [RoleId, GetAngerValue],
            mod_scene_agent:apply_cast(SceneId, PoolId, ?MODULE, kill_mon_get_anger, Args);
        _ ->
            ok
    end,
    ok;
check_get_anger(_SceneId, _PoolId, _Atter) ->
    ok.


%% ---------------------------------------------------------------------------
%% @doc 角色获得怒气
-spec kill_mon_get_anger(RoleId, GetAngerValue) -> ok when
    RoleId          :: integer(),
    GetAngerValue   :: integer().
%% ---------------------------------------------------------------------------
kill_mon_get_anger(RoleId, GetAngerValue) ->
    case lib_scene_agent:get_user(RoleId) of
        SceneUser = #ets_scene_user{battle_attr = BattleAttr, pid = Pid} ->
            #battle_attr{anger = OldAnger, anger_lim = AngerLim} = BattleAttr,
            NewAnger = max(min(AngerLim, OldAnger + GetAngerValue), 0),
            case NewAnger =/= OldAnger of
                true ->
                    BattleAttr2 = BattleAttr#battle_attr{anger = NewAnger},
                    NewSceneUser = SceneUser#ets_scene_user{battle_attr = BattleAttr2},
                    lib_scene_agent:put_user(NewSceneUser),
                    gen_server:cast(Pid, {'set_data', [{anger, {notice, NewAnger}}]}),
                    ok;
                false ->
                    ok
            end;
        _ ->
            ok
    end,
    ok.

%% ---------------------------------------------------------------------------
%% @doc 检查技能的怒气条件
-spec check_skill_use_cond_anger(BattleAttr, SkillConfig, SkillStage) -> IsEnoughAnger when
    BattleAttr      :: #battle_attr{},
    SkillConfig     :: #skill{},
    SkillStage      :: non_neg_integer(),
    IsEnoughAnger   :: boolean().
%% ---------------------------------------------------------------------------
check_skill_use_cond_anger(#battle_attr{anger = Anger},
    #skill{type = ?SKILL_TYPE_ACTIVE, is_anger = 1}, SkillStage) when SkillStage =< 1 ->
    Anger >= ?ANGER_MAX;
check_skill_use_cond_anger(_, _, _SkillStage) ->
    true.

