%%%--------------------------------------
%%% @Module  : pp_battle
%%% @Author  : xyao
%%% @Email   : jiexiaowen@gmail.com
%%% @Created : 2011.07.25
%%% @Description: 战斗
%%%--------------------------------------
-module(pp_battle).
-export([handle/3]).
-include("server.hrl").
-include("scene.hrl").
-include("skill.hrl").
-include("battle.hrl").
-include("errcode.hrl").
-include("common.hrl").
-include("attr.hrl").
-include("team.hrl").
-include("def_fun.hrl").
-include("figure.hrl").
-include("dungeon.hrl").
-include("collect.hrl").
-include("def_act_label.hrl").

%% 玩家发起攻击
%% MonList          怪物Id列表
%% PlayerList       玩家key列表
%% SkillId          技能Id
%% AttDirection     攻击朝向
handle(20001, Status, [MonList, PlayerList, _SkillId, AttX, AttY, AttAngle, AttObjectId, SkillStage, MainType]) ->
    NowTime = utime:longunixtime(),
    SkillId = lib_robot:trans_skill_id(Status, _SkillId),
    FixSkillStage = max(1, SkillStage),
    case use_skill_base_check(Status, AttObjectId, SkillId, FixSkillStage, NowTime) of
        {false, ErrCode} ->
            lib_battle:battle_fail(ErrCode, Status, [ErrCode], SkillId),
            {ok, Status};
        {true, IsCombo, IsTmp, IsTalentActive, SkillLv, Sign, SkillConfig} -> %% 玩家|宠物|宝宝
            case check_battle(Status) of
                ok ->
                    #player_status{id = RoleId,
                        scene = SceneId, scene_pool_id = ScenePoolId,
                        last_att_time = LastAttTime, last_att_main = LastAttMain,
                        battle_attr = BattleAttr
                    } = Status,
                    %% 先取被攻击的数量，避免进行不必要的人数操作
                    {NMonList, NPlayerList} = select_att_num_der(Status, SkillConfig, Sign, MonList, PlayerList, NowTime),
                    GuardDunBuff = lib_dungeon_guard:calc_guard_buff(SceneId, NowTime),
                    BattleArgs = #battle_args{att_key = RoleId,
                        mon_list = NMonList, player_list = NPlayerList,
                        att_x = AttX, att_y = AttY, sign = Sign,
                        skill_id = SkillId, skill_lv = SkillLv,
                        now_time = NowTime, att_angle = AttAngle, att_object_id = AttObjectId,
                        is_combo = IsCombo, skill_stage = FixSkillStage, main_type = MainType,
                        guard_dun_buff = GuardDunBuff
                    },
                    mod_scene_agent:apply_cast_with_scene(SceneId, ScenePoolId, lib_battle, start_battle, [BattleArgs]),
                    %% Status1 = use_tmp_skill_event(IsTmp, Status, SkillId),
                    NewLastAttTime = ?IF(MonList =/= [] orelse PlayerList =/= [], NowTime, LastAttTime),
                    NewLastAttMain = ?IF(Sign == ?BATTLE_SIGN_PLAYER andalso FixSkillStage =< 1, NowTime, LastAttMain),
                    NewBattleAttr = lib_battle_api:skill_stage_set(SkillId, FixSkillStage, NowTime, SkillConfig, BattleAttr),
                    Status1 = Status#player_status{
                        last_att_time = NewLastAttTime, last_att_main = NewLastAttMain,
                        battle_attr = NewBattleAttr
                    },
                    Args = [NMonList, NPlayerList, Sign],
                    LastStatus = after_battle_event(Status1, IsTmp, IsTalentActive, SkillId, Args),
                    {ok, LastStatus};
                {false, ErrCode} ->
                    lib_battle:battle_fail(ErrCode, Status, [ErrCode], SkillId),
                    {ok, Status}
            end
    end;

%% 加载玩家战斗buff列表
handle(20002, Status, []) ->
    #player_status{id = RoleId, scene = SceneId, scene_pool_id = ScenePoolId} = Status,
    mod_scene_agent:apply_cast(SceneId, ScenePoolId, lib_battle, send_player_buff_list, [RoleId]),
    ok;

%% 复活
%% @param Type   1元宝原地复活 2绑定元宝原地复活 3回城复活 4原地满血复活
handle(20004, Status, Type) when Type>0, Type=<?REVIVE_MAX_TYPE ->
    %% 数据初始化
    #player_status{sid=Sid} = Status,
    %% 不允许复活的地图列表
    % NoReviveSceneList = [],
    % IsCantReviveScene = lists:member(SceneId, NoReviveSceneList),
    if
        % IsCantReviveScene -> ok;
        true  ->
            {Result, Status1} = lib_revive:revive(Status, Type),
            %% 告诉客户端复活成功
            {ok, BinData} = pt_200:write(20004, [Type, Result]),
            lib_server_send:send_to_sid(Sid, BinData),
            %% 角色属性同步到场景这一过程因内部逻辑需要已经更新，此处不要再重复更新
            {ok, Status1}
    end;

%% 发动辅助技能
handle(20006, Status, [PlayerId, AttObjectId, SkillId]) ->
    NowTime = utime:longunixtime(),
    SkillStage = 1,
    case use_skill_base_check(Status, AttObjectId, SkillId, SkillStage, NowTime) of
        {false, ErrCode} ->
            lib_battle:battle_fail(ErrCode, Status, [], SkillId);
        {true, IsCombo, _IsTmp, _IsTalentActive, SkillLv, _Sign, _SR} ->
            mod_scene_agent:apply_cast_with_scene(Status#player_status.scene, Status#player_status.scene_pool_id,
                    lib_battle, assist_to_anyone, [Status#player_status.id, PlayerId, IsCombo, SkillId, SkillLv, AttObjectId]),
            %% Status1 = use_tmp_skill_event(IsTmp, Status, SkillId),
            {ok, Status}
    end;

%% 采集怪物
%% MonId    :  怪物唯一id
%% MonCfgId :  怪物配置id
handle(20008, Status, [MonId, MonCfgId, Type]) ->
    #player_status{id = Id, sid = Sid, scene = SceneId, scene_pool_id=ScenePoolId} = Status,
    CollectArgs = #collect_args{mon_id = MonId, mid = MonCfgId, option = Type},
    CheckRet = collect_api:pre_check(Status, CollectArgs),
    case CheckRet of
        true  ->
            mod_scene_agent:apply_cast(SceneId, ScenePoolId, lib_battle, collect_mon, [Id, MonId, MonCfgId, Type, []]);
        {false, ErrCode} ->
            lib_server_send:send_to_sid(Sid, pt_200, 20008, [ErrCode, MonCfgId, MonId])
    end;

%% 复活
handle(20009, Status, _Data) ->
    #player_status{id = RoleId, revive_status = #revive_status{is_revive = IsRevive, revive_time = ReviveTime}} = Status,
    IsOnDungeon = lib_dungeon:is_on_dungeon(Status),
    if
        IsOnDungeon -> lib_dungeon:send_reveive_info(Status);
        true -> lib_server_send:send_to_uid(RoleId, pt_200, 20009, [IsRevive, ReviveTime])
    end;

%% 拾取怪物
handle(20010, Status, MonList) ->
    #player_status{id = RoleId, scene = Scene, scene_pool_id=ScenePoolId} = Status,
    mod_scene_agent:apply_cast(Scene, ScenePoolId, lib_battle, pick_mon, [RoleId, MonList]);

%% 客户端新手演示打坐，扣除血量
handle(20012, Status, DHp) when DHp>0 ->
    #player_status{id=Id, scene=Scene, scene_pool_id=ScenePoolId, battle_attr=BA} = Status,
    IsLimitScene = true,
    SceneList = [],
    CanDel = IsLimitScene == false orelse (IsLimitScene andalso lists:member(Scene, SceneList)),
    case CanDel of
        true ->
            #battle_attr{hp=Hp} = BA,
            HpDel = case Hp > DHp of
                true  -> DHp;
                false -> Hp-1
            end,
            mod_scene_agent:cast_to_scene(Scene, ScenePoolId, {'client_del_hp', Id, HpDel}),
            ok;
        false -> skip
    end;

%% 玩家登录请求被谁杀死
handle(20013, Status, []) ->
    #player_status{sid = Sid, figure = Figure, last_be_kill = LastBeKill, battle_attr = BA} = Status,
    if
        BA#battle_attr.hp > 0 -> skip;
        true ->
            case LastBeKill of
                [{sign, Sign}, {id, Id}, {name, Name}, {lv, Lv}|_] ->
                    {ok, Bin} = pt_200:write(20013, [Sign, Name, BA#battle_attr.pk#pk.pk_value, 0, Lv, Figure#figure.turn, Id]),
                    lib_server_send:send_to_sid(Sid, Bin);
                _ ->
                    skip
            end
    end;

%% 5分钟内的回城复活次数
handle(20017, Status, []) ->
    #player_status{id = Id, sid = Sid} = Status,
    NowTime = utime:unixtime(),
    case get({Id, revive_tired}) of
        {LastTime, Num} when NowTime - LastTime < 300 ->
            LTime = LastTime + 300;
        _ ->
            Num = 0, LTime = 0
    end,
    {ok, BinData} = pt_200:write(20017, [Num, LTime]),
    lib_server_send:send_to_sid(Sid, BinData);

handle(Cmd, _Status, Data) ->
    {error, {"pp_battle no match", Cmd, Data}}.

%% 技能使用基本判断
%% @return {false, ErrCode} | {true, IsCombo, IsTmp, IsTalentActive, SkillLv, BattleSign, Skill}
use_skill_base_check(Status, _AttObjectId, SkillId, SkillStage, NowTime) ->
    #player_status{skill = Skill, last_att_main = LastAttMain, battle_attr = BattleAttr, scene = SceneId} = Status,
    case data_skill:get(SkillId, 1) of
        #skill{career = Career, type=Type, is_combo = IsCombo, combo = _ComboList,
            multistage = StageMax, stage_cd = StageCd
        } = SR
          when Type == ?SKILL_TYPE_ACTIVE orelse Type == ?SKILL_TYPE_ASSIST ->
            case IsCombo of
                0 ->
                    StageCheckResult = lib_battle_api:check_multistage(SkillId, SkillStage, BattleAttr, StageMax, StageCd, NowTime),
                    IsEnoughAnger = lib_battle:check_skill_use_cond_anger(BattleAttr, SR, SkillStage),
                    IsDunPetScene = lib_dungeon_pet:is_dun_pet_scene(SceneId),
                    if
                        not IsEnoughAnger ->
                            %% 怒气不足
                            {false, ?ERR_ANGER_NO_ENOUGH};
                        StageCheckResult =/= true ->
                            StageCheckResult;
                        SceneId == 2300 ->
                            %% 宝宝副本只允许使用特殊技能并且不允许其他助战单位发起技能
                             case lib_dungeon_child:check_use_dun_child_skill(Status, SkillId) of
                                 false -> {false, ?ERR_CONF};
                                 true  -> {true, IsCombo, 0, 0, 1, ?BATTLE_SIGN_PLAYER, SR}
                             end;
                        Career == ?SKILL_CAREER_PET andalso IsDunPetScene ->
                            %% 宠物副本特殊技能只允许场景内使用并且不允许其他助战单位发起技能
                            case lib_dungeon_pet:check_use_dun_pet_skill(Status, SkillId) of
                                false -> {false, ?ERR_CONF};
                                true -> {true, IsCombo, 0, 0, 1, ?BATTLE_SIGN_PET, SR}
                            end;
                        Career == ?SKILL_CAREER_PARTNER andalso Type == ?SKILL_TYPE_ACTIVE -> %% 伙伴/ai娘技能
                            case lib_role_foster:check_have_learn_skill(Status, SkillId, partner_battle_attr) of
                                false -> {false, ?ERR_CONF};
                                true -> {true, IsCombo, 0, 0, 1, ?BATTLE_SIGN_PARTNER, SR}
                            end;
                        Career == ?SKILL_CAREER_BABY andalso Type == ?SKILL_TYPE_ACTIVE -> %% 宝具技能
                            case lib_role_foster:check_have_learn_skill(Status, SkillId, baby_battle_attr) of
                                false -> {false, ?ERR_CONF};
                                true -> {true, IsCombo, 0, 0, 1, ?BATTLE_SIGN_WEAPON, SR}
                            end;
                        Career == ?SKILL_CAREER_CHILD andalso Type == ?SKILL_TYPE_ACTIVE ->
                            case lib_child_api:check_have_learn_skill(Status, SkillId) of
                                false -> {false, ?ERR_CONF};
                                true -> {true, IsCombo, 0, 0, 1, ?BATTLE_SIGN_CHILD, SR}
                            end;
                        Career == ?SKILL_CAREER_NUCLEON andalso Type == ?SKILL_TYPE_ACTIVE ->
                            case lib_role_nucleon:check_have_learn_skill(Status, SkillId) of
                                false ->
                                    {false, ?ERR_CONF};
                                true ->
                                    {true, IsCombo, 0, 0, 1, ?BATTLE_SIGN_PLAYER, SR}
                            end;
                        Career == ?SKILL_CAREER_PET ->
                            case lib_pet_api:check_battle_skills(Status, SkillId) of
                                true  ->
                                    {true, IsCombo, 0, 0, 1, ?BATTLE_SIGN_PET, SR};
                                false ->
                                    {false, ?ERR_CONF}
                            end;
                        %% 以人为主体施展的技能，都要放在公共CD判断之后
                        %% 其他战斗单位由于仅单个主动技能，才可以不加公共CD（如果有多个技能，也要单独加另外的公共CD记录）
                        SkillStage =< 1 andalso LastAttMain + 250 > NowTime ->
                            %% 玩家出手太快（主技能之间公共CD，前端应为300毫秒，服务端宽限50ms）
                            {false, ?ERR_TOO_FAST};
                        Career == ?SKILL_CAREER_GOD_1 andalso Type == ?SKILL_TYPE_ACTIVE ->
                             case god_api:check_use_skill(Status, SkillId) of
                                 false ->
                                     {false, ?ERR_GOD_SKILL};
                                 {_, SkillLV} ->
                                     {true, IsCombo, 0, 0, SkillLV, ?BATTLE_SIGN_PLAYER, SR}
                             end;
                        true ->
                            case lists:keyfind(SkillId, 1, Skill#status_skill.skill_list) of
                                false ->
                                    %% 天赋主动技能
                                    case lib_skill:get_talent_active_info(Skill, SkillId, NowTime) of
                                        {SkillId, SkillLv} -> {true, IsCombo, 0, 1, SkillLv, ?BATTLE_SIGN_PLAYER, SR};
                                        _ ->
                                            %% 临时技能
                                            case lists:keyfind(SkillId, 1,  Skill#status_skill.tmp_skill_list) of
                                                false -> {false, ?ERR_CONF};
                                                {_, SkillLv} -> {true, IsCombo, 1, 0, SkillLv, ?BATTLE_SIGN_PLAYER, SR}
                                            end
                                    end;
                                {_, SkillLv} -> {true, IsCombo, 0, 0, SkillLv, ?BATTLE_SIGN_PLAYER, SR}
                            end
                    end;
                _ ->
                    %% 理论上其他战斗单位没有组合技能，所以这里是人
                    %% 本应增加数据保存状态以控制组合技能的CD，
                    %% 策划可能暂时不打算使用了，先不用加
                    %% combo技能在mod_battle模块继承主技能等级
                    {true, IsCombo, 0, 0, 1, ?BATTLE_SIGN_PLAYER, SR}
            end;
        _ ->
            {false, ?ERR_CONF}
    end.

%% 检测玩家是否能进行战斗
check_battle(Status) ->
    #player_status{forbid_pk_etime = ForBidPkEtime, scene = _Scene} = Status,
    NowTime = utime:unixtime(),
    if
        ForBidPkEtime >= NowTime -> {false, ?ERRCODE(in_forbid_pk_status)};
        true -> ok
    end.

%% 先计算战斗人数
select_att_num_der(Ps, SR, ?BATTLE_SIGN_PLAYER, MonList, PlayerList, NowTime)->
    #skill{lv_data=#skill_lv{num = [PNum, MNum]}} = SR,
    #player_status{scene = SceneId, figure = Figure} = Ps,
    NewMonList   = lists:sublist(MonList, min(MNum, ?MAX_ATTACK_NUM)),
    IsRobot = lib_robot:is_robot(Ps),
    IsPkLimit = is_new_player_pk_limit(SceneId, Figure),
    IsRBProtect  = local_boss_api:is_rb_protect_time(Ps, NowTime div 1000),
    IsDeBProtect = local_boss_api:is_deb_protect_time(Ps, NowTime div 1000),
    NewPlayerList =
    if
        IsRobot ->
            lists:sublist(PlayerList, min(PNum, ?MAX_ATTACK_NUM));
        IsPkLimit ->
            [];
        IsDeBProtect ->
            [];
        IsRBProtect ->
            [];
        true ->
            lists:sublist(PlayerList, min(PNum, ?MAX_ATTACK_NUM))
    end,
    {NewMonList, NewPlayerList};
select_att_num_der(Ps, _SR, _Sign, MonList, PlayerList, NowTime) ->
    IsRBProtect  = local_boss_api:is_rb_protect_time(Ps, NowTime div 1000),
    IsDeBProtect = local_boss_api:is_deb_protect_time(Ps, NowTime div 1000),
    if
        IsDeBProtect -> {[], []};
        IsRBProtect -> {[], []};
        PlayerList =/= [] -> [PId|_] = PlayerList, {[], [PId]};
        MonList =/= [] -> [MId|_] = MonList, {[MId], []};
        true -> {[], []}
    end.

is_new_player_pk_limit(SceneId, Figure) ->
    lib_scene:is_outside_scene(SceneId) andalso Figure#figure.lv =< 50.

%% 释放技能后事件
after_battle_event(Status, IsTmpSkill, IsTalentActive, SkillId, Args) ->
    EventList = [is_talent_active, in_god, in_dun_child],
    case do_after_battle_event(EventList, Status, IsTmpSkill, IsTalentActive, SkillId, Args) of
        {no, NewPS} -> NewPS;
        {ok, NewPS} -> NewPS
    end.

do_after_battle_event([], PS, _IsTmpSkill, _IsTalentActive, _SkillId, _Args) -> {no, PS};
do_after_battle_event([H|T], PS, IsTmpSkill, IsTalentActive, SkillId, Args) ->
    case do_after_battle_callback(H, PS, IsTmpSkill, IsTalentActive, SkillId, Args) of
        {ok, NewPS} -> {ok, NewPS};
        {next, NewPS} -> do_after_battle_event(T, NewPS, IsTmpSkill, IsTalentActive, SkillId, Args)
    end.

%% 天赋主动技能
do_after_battle_callback(is_talent_active, PS, _IsTmpSkill, 1, SkillId, _Args) ->
    #player_status{skill = SkillStatus} = PS,
    TalentActiveTriggers = SkillStatus#status_skill.skill_talent_active_triggers,
    NewTalentActiveTriggers = lists:keydelete(SkillId, 1, TalentActiveTriggers),
    NewPS = PS#player_status{skill = SkillStatus#status_skill{skill_talent_active_triggers = NewTalentActiveTriggers}},
    {next, NewPS};
%% 处理变身怒气(要放最前面处理)
do_after_battle_callback(in_god, PS, _IsTmpSkill, _IsTalentActive, _SkillId, Args) ->
    [MonList, PlayerList, AttType| _T] = Args,
    #player_status{scene = SceneId} = PS,
    KeepAngerSceneTypes = [],  %% 不增加变身怒气的场景
    [SceneType | _] = lib_scene:get_scene_type(SceneId),
    IsFilterScene = lists:member(SceneType, KeepAngerSceneTypes),
    if
        AttType =/= ?BATTLE_SIGN_PLAYER -> {next, PS};
        MonList == [] andalso PlayerList == [] -> {next, PS};
        IsFilterScene == true -> {next, PS};
        true ->
            {next,PS}
    end;
%% 宝宝副本消耗能力
do_after_battle_callback(in_dun_child, PS, _IsTmpSkill, _IsTalentActive, SkillId, _Args) ->
    #player_status{scene = SceneId} = PS,
    case SceneId of
        2300 ->
            NewPS = lib_dungeon_child:cost_energy(PS, SkillId);
        _ ->
            NewPS = PS
    end,
    {next, NewPS};

do_after_battle_callback(_Unkown, PS, _IsTmpSkill, _IsTalentActive, _SkillId, _Args) ->
    {next, PS}.
