%%%------------------------------------
%%% module  : mod_scene_agent_cast
%%% @author  : xyao
%%% email   : jiexiaowen@gmail.com
%%% Created : 2012.05.18
%%% description: 场景管理cast处理
%%%------------------------------------
-module(mod_scene_agent_cast).
-export([handle_cast/2]).
-include("scene.hrl").
-include("attr.hrl").
-include("common.hrl").
-include("battle.hrl").
-include("dungeon.hrl").
-include("predefine.hrl").
-include("collect.hrl").
-include("figure.hrl").

%% 更新场景玩家数据
handle_cast({'update', Key, KeyValueList}, State = #scene_state{mon_mgr_pid = MonMgrPid}) ->
    case lib_scene_agent:get_user(Key) of
        [] ->
            {noreply, State};
        SceneUser ->
            NewSceneUser = lib_scene_user:set_data_sub(KeyValueList, SceneUser, MonMgrPid),
            lib_scene_agent:put_user(NewSceneUser),
            {noreply, State}
    end;

%% 移动
%% TX, TY  目标坐标像素
handle_cast({'move', [CopyId, TX, TY, ReqtMotionType, Param1, Param2, Param3, Param4, UserId]},
    State = #scene_state{scene = EtsScene, mon_mgr_pid = MonMgrPid}) ->
    case lib_scene_agent:get_user(UserId) of
        #ets_scene_user{pid = Pid, copy_id=CopyId,
            x = OldX, y = OldY,
            scene_motion = SceneMotion,
            battle_attr = BattleAttr
        } = SceneUser ->
            %% 触发怪物AI
            lib_scene:trigger_mon_ai(EtsScene, SceneUser, TX, TY, MonMgrPid),
            %% 禁跳状态，跳跃等修正为走路
            %% （允许以走路方式移动，节省掉同步到角色进程的操作）
            MotionType = motion_check:check_prohibed_jump(BattleAttr, ReqtMotionType),
            {NewSceneMotion, FixMotionType} = motion_energy:check_cost_energy(SceneMotion, MotionType),
            NewMotionType = motion_check:to_client_motion_type(FixMotionType),
            LastSceneUser = SceneUser#ets_scene_user{x = TX, y = TY, scene_motion = NewSceneMotion},
            %% 检测是否需要将能量数据同步到角色进程
            motion_energy:check_async_energy(Pid, NewSceneMotion, MotionType, EtsScene#ets_scene.cls_type),
            %% 走路广播
            lib_scene:user_move(TX, TY, OldX, OldY, Param1, Param2, NewMotionType, Param3, Param4, LastSceneUser),
            lib_scene_agent:put_user(LastSceneUser),
            {noreply, State};
        _ ->
            {noreply, State}
    end;

%% 移动
%% TX, TY  目标坐标像素
handle_cast({'scene_object_move', _CopyId, TX, TY, Id}, State) ->
    case lib_scene_object_agent:get_object(Id) of
        #scene_object{x = OldX, y = OldY} = Object ->
            NewObject = Object#scene_object{x = TX, y = TY},
            lib_scene_object_agent:put_object(NewObject),
            lib_scene_object_agent:object_move_send_msg(OldX, OldY, NewObject),
            {noreply, State};
        _ ->
            {noreply, State}
    end;

%% 删除场景玩家残留数据
handle_cast({'delete_scene_user', RoleId}, State) ->
    handle_cast({'leave', RoleId}, State);

%% 玩家离开场景
handle_cast({'leave', Id}, State = #scene_state{scene = EtsScene, mon_mgr_pid = MonMgrPid}) ->
    case lib_scene_agent:get_user(Id) of
        [] ->
            {noreply, State};
        #ets_scene_user{
            pid = RolePid,
            copy_id = CopyId,
            x = X, y = Y,
            hp_resume_ref = HpResumeTimer
        } = SceneUser ->
            %% 检测中止采集
            collect_api:check_stop_collect(SceneUser),
            lib_scene_agent:del_user(Id),
            lib_battle:remove_resume_timer(HpResumeTimer),
            %% 场景角色移除通知
            {ok, BinRoleLeave} = pt_120:write(12004, Id),
            %% 检查打包特殊场景的场景人数广播数据包
            BinRoleNum = check_pack_role_number_info(EtsScene, CopyId),
            case lib_scene:is_broadcast_scene(EtsScene) of
                true  ->
                    lib_scene_agent:send_to_local_scene(CopyId, [BinRoleLeave, BinRoleNum]);
                false ->
                    lib_scene_agent:send_to_local_area_scene(CopyId, X, Y, BinRoleLeave),
                    lib_scene_agent:send_to_local_scene(CopyId, BinRoleNum)
            end,
            %% 怪物AI触发
            case lib_scene:check_need_ai_scene(EtsScene#ets_scene.type) of
                false -> ignore;
                true  ->
                    {TX, TY} = lib_scene_calc:pixel_to_logic_coordinate(X, Y),
                    Msg = {'leave', Id, RolePid, ?BATTLE_SIGN_PLAYER},
                    [
                        % old: Aid ! {'leave', Id, RolePid, ?BATTLE_SIGN_PLAYER}
                        mon_mgr:send(MonMgrPid, MonId, Msg)
                        || MonId <- lib_scene_object_agent:get_trace(CopyId, TX, TY)
                    ],
                    [
                        % old: Aid ! {'leave', Id, RolePid, ?BATTLE_SIGN_PLAYER}
                        mon_mgr:send(MonMgrPid, MonId, Msg)
                        || MonId <- lib_scene_object_agent:get_cl_mons(CopyId, TX, TY)
                    ]
            end,
            {noreply, State}
    end;

%% 玩家原地复活
handle_cast({'revive', CopyId, Id, TriggerSkill, BattleAttr, KilledPos} , State = #scene_state{scene = EtsScene, mon_mgr_pid = MonMgrPid}) ->
    case lib_scene_agent:get_user(Id) of
        [] -> skip;
        #ets_scene_user{pid = RolePid, copy_id = CopyId, scene_pool_id = PoolId, x = X, y = Y, scene = Scene} = SceneUser ->
            %% 更新血量及其他属性数据
            {NewX, NewY} = case KilledPos of
                [{KilledX, KilledY}|_] -> {KilledX, KilledY};
                _   -> {X, Y}
            end,
            NewSceneUser = SceneUser#ets_scene_user{battle_attr = BattleAttr},
            lib_scene_agent:put_user(NewSceneUser),
            %% 复活广播
            {ok, BinData} = pt_120:write(12003, NewSceneUser),
            lib_server_send:send_to_area_scene(Scene, PoolId, CopyId, NewX, NewY, BinData),
            case lib_scene:check_need_ai_scene(EtsScene#ets_scene.type) of
                true ->
                    %% 通知怪物处理BOSS归属通知
                    {TX, TY} = lib_scene_calc:pixel_to_logic_coordinate(NewX, NewY),
                    Msg = {'enter', Id, RolePid, ?BATTLE_SIGN_PLAYER},
                    [
                        % old: Aid ! {'enter', Id, RolePid, ?BATTLE_SIGN_PLAYER} ||
                        mon_mgr:send(MonMgrPid, MonId, Msg) ||
                        MonId <- lib_scene_object_agent:get_trace(CopyId, TX, TY)
                    ];
                false -> skip
            end,
            case TriggerSkill of
                {SkillId, SkillLv} when SkillId > 0 ->
                    lib_skill_buff:add_buff(EtsScene, NewSceneUser, SkillId, SkillLv);
                _ ->
                    ignore
            end
    end,
    {noreply, State};

%% 给场景所有玩家发送信息
handle_cast({'send_to_scene', CopyId, Bin} , State) ->
    lib_scene_agent:send_to_local_scene(CopyId, Bin),
    {noreply, State};

%% 给场景所有玩家发送信息
handle_cast({'send_to_scene', Bin} , State) ->
    lib_scene_agent:send_to_local_scene(Bin),
    {noreply, State};

%% 给场景九宫格玩家发送信息
handle_cast({'send_to_area_scene', CopyId, X, Y, Bin} , State) ->
    lib_scene_agent:send_to_local_area_scene(CopyId, X, Y, Bin),
    {noreply, State};

%% 加载场景
handle_cast({'load_scene', SceneUser} , State = #scene_state{scene = EtsScene, mon_mgr_pid = MonMgrPid}) ->
    do_user_join(SceneUser, EtsScene, MonMgrPid),
    send_scene_info_to_user(SceneUser, EtsScene),
    {noreply, State};

%% 关闭场景
handle_cast({'close_scene'} , State) ->
    %% 清除怪物
    lib_scene_object_agent:clear_when_scene_close(),
    %% 关闭场景的功能需要自己将玩家踢出去
    {stop, normal, State};

%% 清理场景
handle_cast({'clear_scene'} , State) ->
    lib_scene_object_agent:clear_scene_object(0, [], 0),
    lib_scene_agent:clear_all_process_dict(),
    {noreply, State};

%% 统一模块+过程调用(cast)
handle_cast({'apply_cast', Module, Method, Args} , State) ->
    apply(Module, Method, Args),
    {noreply, State};

%% 统一模块+过程调用(cast)
handle_cast({'apply_cast_with_scene', Module, Method, Args} , State = #scene_state{scene = EtsScene}) ->
    apply(Module, Method, [EtsScene | Args]),
    {noreply, State};

%% 统一模块+过程调用(cast) 让场景内所有玩家执行 方法
handle_cast({'apply_cast_scene_user', CopyId, Module, Method, Args} , State) ->
    [
        lib_player:apply_cast(Pid, ?APPLY_CAST_SAVE, Module, Method, Args)
        || #ets_scene_user{pid = Pid} <-  lib_scene_agent:get_scene_user(CopyId)
    ],
    {noreply, State};

%% 客户端新手演示打坐，扣除血量
handle_cast({'client_del_hp', PlayerId, HpDel}, State = #scene_state{scene = EtsScene}) ->
    case lib_scene_agent:get_user(PlayerId) of
        #ets_scene_user{node = Node, copy_id=CopyId, x=X, y=Y, battle_attr=BA}=User ->
            #battle_attr{hp=Hp, hp_lim=HpLim} = BA,
            Hp1 = max(1, Hp-HpDel),
            {ok, BinData} = pt_120:write(12009, [PlayerId, Hp1, HpLim]),
            lib_battle:send_to_scene(CopyId, X, Y, EtsScene#ets_scene.broadcast, BinData),
            lib_battle:rpc_cast_to_node(Node, lib_player, update_player_info, [PlayerId, [{hp, Hp1}]]),
            lib_scene_agent:put_user(User#ets_scene_user{battle_attr=BA#battle_attr{hp=Hp1}});
        _ -> skip
    end,
    {noreply, State};

%% 发送场景人数
handle_cast({'send_number_info', CopyId, Sid}, State) ->
    SceneUserList = lib_scene_agent:get_scene_user(CopyId),
    Num = length(SceneUserList),
    {ok, BinData} = pt_120:write(12087, [Num]),
    lib_server_send:send_to_sid(Sid, BinData),
    {noreply, State};

%% 发送场景人物简要信息
handle_cast({'send_user_info', CopyId, Sid}, State) ->
    SceneUserList = lib_scene_agent:get_scene_user(CopyId),
    {ok, BinData} = pt_120:write(12088, [[U || U <- SceneUserList, U#ets_scene_user.sid =/= Sid]]),
    lib_server_send:send_to_sid(Sid, BinData),
    {noreply, State};

%% 切换玩家跳舞状态
handle_cast({'change_dance', CopyId, RoleId, DanceType}, State = #scene_state{scene = EtsScene}) ->
    case lib_scene_agent:get_user(RoleId) of
        [] -> skip;
        SceneUser = #ets_scene_user{x = X, y = Y} ->
            lib_scene_agent:put_user(SceneUser#ets_scene_user{role_dance = DanceType}),
            {ok, Bin} = pt_120:write(12075, [_Sign = 2, RoleId, DanceType]),
            lib_server_send:send_to_uid(RoleId, Bin),
            %% 广播,温泉场景跳舞全场景广播(数量可控)
            case EtsScene#ets_scene.type =:= ?SCENE_TYPE_HOT_SPRING orelse EtsScene#ets_scene.type =:= ?SCENE_TYPE_GUILD of
                true -> lib_scene_agent:send_to_local_scene(CopyId, Bin);
                _ ->
                    case lib_scene:is_broadcast_scene(EtsScene) of
                        true -> lib_scene_agent:send_to_local_scene(CopyId, Bin);
                        false -> lib_scene_agent:send_to_local_area_scene(CopyId, X, Y, Bin)
                    end
            end
    end,
    {noreply, State};

%% 默认匹配
handle_cast(Event, Status) ->
    catch ?ERR("mod_scene_agent_cast:handle_cast not match: ~p", [Event]),
    {noreply, Status}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

%% 根据场景发送场景人数
check_pack_role_number_info(EtsScene, CopyId) ->
    %% 公会场景需要广播
    case lib_guild_api:check_in_guild_scene(EtsScene) of
        true ->
            Num = lib_scene_agent:get_role_count(CopyId),
            {ok, BinData} = pt_120:write(12087, [Num]),
            BinData;
        false ->
            <<>>
    end.

%% 处理玩家加入场景
do_user_join(SceneUser, EtsScene, MonMgrPid) ->
    #ets_scene_user{id = RoleId,
        scene = SceneId, scene_pool_id = ScenePoolId,
        copy_id = CopyId, x = X, y = Y,
        last_load_scene = LastLoadScene, last_load_pool = LastLoadPool,
        figure = Figure,
        battle_attr = BA
    } = SceneUser,
    #battle_attr{
        hp = Hp, hp_lim = HpLim, hp_resume_add = HpResumeAdd, hp_resume_time = HpResumeTime
    } = BA,
    %% 进入场景检测是否开启回血定时器
    HpRef =
    if
        HpResumeAdd =< 0 -> undefined;
        true ->
            lib_battle:add_resume_timer(undefined, Hp, HpLim, RoleId, utime:longunixtime(), HpResumeTime)
    end,
    %% 重登的时候这里会更新sid，不需要额外更新
    %% cd保存
    NewSkillCd = case get({user, RoleId}) of
        #ets_scene_user{scene = OSceneId, skill_cd = SCd}
        when SceneId == OSceneId -> SCd;
        _ -> []
    end,
    AddCount = lib_scene_agent:put_user(SceneUser#ets_scene_user{hp_resume_ref = HpRef, skill_cd = NewSkillCd}),
    #ets_scene{type = SceneType} = EtsScene,
    %% 通知九宫格/全场景玩家，本大爷进入
    {ok, BinEnterScene} = pt_120:write(12003, SceneUser),
    %% 通知全场景玩家，本大爷进入（头像基本资料）
    IsNewEnter = is_new_enter(AddCount, SceneId, ScenePoolId, LastLoadScene, LastLoadPool),
    {ok, BinEnterRole} =
    case IsNewEnter andalso lib_scene:is_broadcast_enter(SceneType) of
        true  -> pt_120:write(12013, [Figure]);
        false -> {ok, <<>>}
    end,
    %% 检查打包特殊场景的场景人数广播数据包
    BinRoleNum = check_pack_role_number_info(EtsScene, CopyId),
    case lib_scene:is_broadcast_scene(EtsScene) of
        true  ->
            lib_scene_agent:send_to_local_scene(CopyId, [BinEnterScene, BinEnterRole, BinRoleNum]);
        false ->
            lib_scene_agent:send_to_local_area_scene(CopyId, X, Y, BinEnterScene),
            %% 部分数据需要全场景广播
            lib_scene_agent:send_to_local_scene(CopyId, [BinEnterRole, BinRoleNum])
    end,
    %% 触发怪物AI
    lib_scene:trigger_mon_ai(EtsScene, SceneUser, X, Y, MonMgrPid),
    ok.

%% 把场景对象类信息发送给玩家
send_scene_info_to_user(User, EtsScene) ->
    case User of
        #ets_scene_user{
            node = Node, sid = Sid,
            copy_id = CopyId, x = X, y = Y,
            online = ?ONLINE_ON
        } ->
            %% 真实在线的玩家
            case lib_scene:is_broadcast_scene(EtsScene) of
                true ->
                    SceneUsers = lib_scene_agent:get_scene_user(CopyId),
                    SceneObject = lib_scene_object_agent:get_scene_object(CopyId);
                false ->
                    SceneObject = case lib_scene:is_force_broadcast_scene(EtsScene) of
                        true  ->
                            lib_scene_object_agent:get_scene_object(CopyId);
                        false ->
                            lib_scene_calc:get_broadcast_object(CopyId, X, Y)
                    end,
                    SceneUsers = lib_scene_calc:get_broadcast_user(CopyId, X, Y)
            end,
            AreaMark = [],
            {ok, BinData} = pt_120:write(12002, [SceneUsers, SceneObject, AreaMark]),
            lib_server_send:send_to_sid(Node, Sid, BinData),
            ok;
        _ ->
            %% 非真实在线玩家，没必要去获取周围场景对象信息进行通知
            ignore
    end.

%% 检查角色是否刚刚新进入场景
is_new_enter(AddCount, SceneId, ScenePoolId, LastLoadScene, LastLoadPool) ->
    if
        AddCount =< 0 -> false;
        SceneId == LastLoadScene andalso ScenePoolId == LastLoadPool -> false;
        true -> true
    end.

