%%%--------------------------------------
%%% Module  : pp_scene
%%% Author  : zhenghehe
%%% Created : 2010.12.23
%%% Description:  场景
%%%--------------------------------------
-module(pp_scene).
-export([handle/3]).
-include("server.hrl").
-include("scene.hrl").
-include("task.hrl").
-include("skill.hrl").
-include("predefine.hrl").
-include("common.hrl").
-include("attr.hrl").
-include("def_event.hrl").
-include("dungeon.hrl").
-include("team.hrl").
-include("errcode.hrl").
-include("vip.hrl").
-include("figure.hrl").
-include("def_goods.hrl").
-include("def_vip.hrl").
-include("dance.hrl").
-include("motion.hrl").

%% 玩家请求移动
%% ---------------------------------------------------------------------
%% ClientSceneId        客户端所处场景
%% TargetX, TargetY     本次移动的目标坐标
%% Param1, ..., Param4  客户端自定义参数（特殊行为服务端可能也有使用）
%% MotionType           移动行为类型
%% ---------------------------------------------------------------------
%% 三段跳跃客户端自定义参数Param1，服务端需要用于检测执行能量的扣除逻辑
%% 100~199 为第一跳
%% 200~299 为第二跳
%% 300~399 为第三跳
%% ---------------------------------------------------------------------
handle(12001, Status = #player_status{fin_change_scene = 1},
    [ClientSceneId, TargetX, TargetY, MotionType, Param1, Param2, Param3, Param4]) ->
    #player_status{
        id = RoleId, sid = Sid,
        scene = SceneId, scene_pool_id = PoolId,
        copy_id = CopyId, x = OldX, y = OldY,
        team = #status_team{team_id = TeamId},
        role_dance = RoleDance,
        fly_state = _OldFlyState
    } = Status,
    {MoveErrCode, X, Y, FixMotionType, NewRoleMotion} =
    motion_check:check_motion(Status, ClientSceneId, MotionType, TargetX, TargetY, Param1),
    % #player_status{figure = #figure{name = Name}} = Status,
    % ?INFO("[~w,~ts] motion:~w, scene:~w,old:[~w,~w],target:[~w,~w],fix:[~w,~w]",
    %     [RoleId,Name,MotionType, SceneId,OldX,OldY,TargetX,TargetY,X,Y]
    % ),
    case MoveErrCode of
        ?MOTION_ERR_OK  ->
            mod_scene_agent:move(X, Y, Param1, Param2, FixMotionType, Param3, Param4, Status),
            %% TODO 考虑优化掉，队员位置有实际意义吗？没有他会坏事吗？如果优化掉，在change_scene中也要处理
            case TeamId > 0 andalso lib_scene_calc:get_xy(X, Y) =/= lib_scene_calc:get_xy(OldX, OldY) of
                true ->
                    %% 队员位置修改通知
                    lib_team_api:where_i_am(TeamId, RoleId, SceneId, PoolId, CopyId, X, Y),
                    %% 跟随用的，应该可以干掉
                    mod_team:cast_to_team(TeamId,
                        {'change_location', RoleId, SceneId, PoolId, CopyId, X, Y}
                    );
                false ->
                    skip
            end,
            Param1 =:= 399 andalso contract_task_api:jump_task_trigger(RoleId, 1),
            ok;
        _Err ->
            lib_server_send:send_to_sid(Sid, pt_120, 12099, [
                    ClientSceneId, SceneId, TargetX, TargetY,
                    MotionType, OldX, OldY, MoveErrCode
                ]),
            ignore
    end,
    % 限制小飞鞋，前端任务流程的状态配合很多问题，所以服务端不校验
    % NewFlyState = case MoveErrCode =:= ?MOTION_ERR_OK andalso MotionType =:= ?MOVE_ROLE_TASK_JUMP of
    %     true  -> 0;
    %     false -> OldFlyState
    % end,
    %% 取消温泉跳舞
    NewRoleDance =
    case RoleDance =/= 0 of
        true ->
            NewDance = 0,
            lib_server_send:send_to_sid(Sid, pt_120, 12025, [?SUCCESS, NewDance]),
            mod_scene_agent:change_dance(SceneId, PoolId, CopyId, RoleId, NewDance),
            NewDance;
        _ ->
            RoleDance
    end,
    NewPlayer = Status#player_status{
        x = X, y = Y
        ,role_motion = NewRoleMotion
        ,role_dance = NewRoleDance
        % ,fly_state = NewFlyState
    },
    {ok, NewPlayer};

%% 屏蔽切换场景期间的移动信息
handle(12001, Status, _) ->
    {ok, Status};

%% 加载场景
%% （前端加载场景是靠12005的协议返回来驱动的，上线也是如此，因为共用了一些表现）
handle(12002, Status, _) ->
    #player_status{
        fin_change_scene_ref = OldRef
        ,change_scene_num = ChangeSceneNum
        ,scene = SceneId
        ,scene_pool_id = ScenePoolId
        ,battle_attr = BattleAttr
    } = Status,
    mod_scene_agent:load_scene(Status),
    {ok, NewStatus} = lib_player_event:dispatch(Status, ?EVENT_PREPARE_CHANGE_SCENE),
    Ref = util:send_after(OldRef, 15*1000, self(), {'mod', lib_scene, notify_fin_change_scene, []}),
    LastStatus = NewStatus#player_status{
        fin_change_scene_ref = Ref
        ,change_scene_num = ChangeSceneNum + 1
        ,fin_change_scene = 1
        ,last_load_scene = SceneId
        ,last_load_pool = ScenePoolId
        ,battle_attr = lib_battle_api:reset_skill_stage(BattleAttr)
    },
    {ok, LastStatus};

%% @doc 切换场景
%% DunId:非副本场景切换=0；副本场景：副本id
%% ChangeSceneId:切换的场景id
%% @end
%% 说明：reconnect默认为1，首次切换场景后，会将reconnect置为0
%%
%% **特别说明**：
%% 前端因为封装处理的关系，需要由12005协议驱动才会调用12002加载场景，
%% 同时，服务端也需要通过12005中调用change_scene执行场景分线的选择
handle(12005, Status, [DunId, ChangeSceneId, CallBackValue, TransType, TX, TY]) ->
    change_scene(Status, DunId, ChangeSceneId, CallBackValue, TransType, TX, TY);

%% 12017 掉落包信息

%% 上线请求玩家周围的掉落包
handle(12018, Status, []) ->
    #player_status{id = RoleId, scene = SceneId, scene_pool_id = PoolId, copy_id = CopyId} = Status,
    case lib_scene:is_clusters_scene(SceneId) of
        false ->
            mod_drop:get_drops_info(none, RoleId, SceneId, PoolId, CopyId);
        true ->
            Node = clusters_node:get_node(),
            clusters_node:apply_cast(mod_drop, get_drops_info, [Node, RoleId, SceneId, PoolId, CopyId])
    end;

%% 请求刷新npc状态
handle(12020, Status, []) ->
    lib_role_task:refresh_npc_ico(lib_task_api:ps2task_args(Status)),
    ok;

%% 进行跳舞状态
handle(12025, Status, [Type]) ->
    #player_status{id = RoleId,scene = SceneId, scene_pool_id = ScenePoolId, copy_id = CopyId, dance = Dance} = Status,
    #dance{dance_list = DanceList} = Dance,
    IsHas = lists:member(Type, DanceList),
    DanceType =
        if
            IsHas ->
                Type;
            true ->
                0
        end,
    lib_server_send:send_to_uid(RoleId, pt_120, 12025, [?SUCCESS, DanceType]),
    mod_scene_agent:change_dance(SceneId, ScenePoolId, CopyId, RoleId, DanceType),
    {ok, Status#player_status{role_dance = DanceType}};

%% 场景动态特效信息
handle(12032, Status, []) ->
    Node = clusters_node:get_node(),
    #player_status{scene=Scene, scene_pool_id=ScenePoolId, copy_id=CopyId, id=RoleId} = Status,
    mod_scene_agent:apply_cast(Scene, ScenePoolId, lib_scene_agent, send_dynamic_eff, [Node, RoleId, Scene, CopyId]),
    ok;

%% 小飞鞋
handle(12033, Status, _) ->
    #player_status{id = _RoleId, sid = Sid} = Status,
    case check_fly_shoes(Status) of
        {false, ErrorRes} ->
            {ok, BinData} = pt_120:write(12033, [ErrorRes]),
            lib_server_send:send_to_sid(Sid, BinData);
        {true, NewStatus} ->
            {ok, BinData} = pt_120:write(12033, [?SUCCESS]),
            lib_server_send:send_to_sid(Sid, BinData),
            {ok, NewStatus#player_status{fly_state = 1}};
        true ->
            {ok, BinData} = pt_120:write(12033, [?SUCCESS]),
            lib_server_send:send_to_sid(Sid, BinData),
            {ok, Status#player_status{fly_state = 1}}
    end;

%% 超链接场景传送
handle(12034, Status, [TargetSceneId, TargetLine, _TargetX, _TargetY]) ->
    #player_status{sid = Sid, scene = SceneId, scene_pool_id = PoolId, copy_id = CopyId} = Status,
    RealPoolId = 0,
    RealCopyId = 0,
    {IsChangeLine, ErrCode} =
    case data_scene:get(TargetSceneId) of
        #ets_scene{type = Type} when Type == ?SCENE_TYPE_NORMAL; Type == ?SCENE_TYPE_OUTSIDE->
            % case catch mod_scene_line:check_can_enter_line(TargetSceneId, TargetLine) of
            %     {true, RealPoolId, RealCopyId} -> {IsChangeLine, ErrCode} = {true, ?SUCCESS};
            %     _ -> RealPoolId = 0, RealCopyId = 0, {IsChangeLine, ErrCode} = {false, ?FAIL}
            % end;
            % FIXME 暂时不允许指定
            case TargetLine =:= 0 of
                true  -> {true, ?SUCCESS};
                false -> {false, ?FAIL}
            end;
        _ ->
            {false, ?ERRCODE(err120_cannot_transfer_to_target_scene)}
    end,
    {IsChangeLine2, ErrCode2} =
    if
        IsChangeLine == true andalso SceneId == TargetSceneId andalso RealPoolId == PoolId andalso RealCopyId == CopyId ->
            {false, ?ERRCODE(err120_already_in_scene)};
        true ->
            {IsChangeLine, ErrCode}
    end,
    {ErrCode3, LastStatus} = case IsChangeLine2 of
        false -> {ErrCode2, Status};
        true  ->
            %% 检查键入场景
            case lib_scene:check_enter(Status, 0, TargetSceneId, ?TRANS_TYPE_WORLD) of
                {false, ErrorCode} -> {ErrorCode, Status};
                {true, X, Y} ->
                    Status2 = lib_scene:change_scene(Status, TargetSceneId, RealPoolId, RealCopyId, X, Y, true, []),
                    NewStatus = Status2#player_status{pre_scene_time = 0, reconnect = 0},
                    {?SUCCESS, NewStatus}
            end
    end,
    lib_server_send:send_to_sid(Sid, pt_120, 12034, [ErrCode3, _TargetX, _TargetY]),
    {ok, LastStatus};

%% 通知完成了场景切换
handle(12035, Status, _) ->
    case Status#player_status.fin_change_scene_ref of
        undefined ->
            ok;
        _ ->
            {ok, NewStatus} = lib_scene:notify_fin_change_scene(Status),
            {ok, NewStatus}
    end;

%% 获取场景线路状态
handle(12040, Status, []) ->
    #player_status{scene = SceneId, scene_pool_id = ScenePoolId, sid = Sid} = Status,
    %% 前端显示的线路号起始值为1，相应线路服务端PoolId+1
    {MyLine, Lines} =
    case data_scene:get(SceneId) of
        #ets_scene{type = Type, cls_type = ClsType} ->
            case lib_scene:check_can_active_switch(Type) of
                true  ->
                    case scene_mgr:get_pool_scope(SceneId) of
                        {Min, Max} ->
                            PoolRoleCouts =
                            case ClsType of
                                1 -> clusters_node:apply_call(lib_scene, get_pool_role_count, [SceneId, {Min, Max}]);
                                _ -> lib_scene:get_pool_role_count(SceneId)
                            end,
                            case PoolRoleCouts of
                                [_|_] ->
                                    %% 后端分线编号转换为前端分线编号
                                    LineRoleNums = [{PoolId - Min + 1, Count} || {PoolId, Count} <- PoolRoleCouts],
                                    {ScenePoolId - Min + 1, LineRoleNums};
                                _ ->
                                    {1, [{1, 0}]}
                            end;
                        _ ->
                            {1, [{1, 0}]}
                    end;
                false ->
                    {1, [{1, 0}]}
            end;
        _ ->
            {1, [{1, 0}]}
    end,
    {ok, BinData} = pt_120:write(12040, [SceneId, MyLine, Lines]),
    lib_server_send:send_to_sid(Sid, BinData);

%% 切换场景线路
handle(12041, Status, LineNo) when LineNo > 0 ->
    #player_status{id = RoleId, action_lock = Lock,
        scene = SceneId, scene_pool_id = ScenePoolId,
        copy_id = _CopyId, x = X, y = Y, sid = Sid
    } = Status,
    {IsChangeLine, ErrCode, NewPoolId, NewCopyId} =
    case data_scene:get(SceneId) of
        #ets_scene{type = Type, cls_type = ClsType} ->
            case lib_scene:check_can_active_switch(Type) of
                true  ->
                    %% 前端分线编号转换为后端分线编号
                    PoolBase = case scene_mgr:get_pool_scope(SceneId) of
                        {Min, _Max} -> Min;
                        _           -> 0
                    end,
                    ChangePoolId = PoolBase + LineNo - 1,
                    ChangeCopyId = 0,
                    IsConvoying = convoy_api:is_convoy_action(Lock),
                    if
                        % 护送
                        IsConvoying ->
                             {false, Lock, ChangePoolId, ChangeCopyId};
                        ScenePoolId == ChangePoolId ->
                            {false, ?ERRCODE(err120_same_line), ChangePoolId, ChangeCopyId};
                        true ->
                            case scene_mgr:check_lock_enter(RoleId, SceneId, ChangePoolId, ChangeCopyId, Type, ClsType) of
                                true ->
                                    {true, ?SUCCESS, ChangePoolId, ChangeCopyId};
                                _ ->
                                    {false, ?ERRCODE(err120_too_many_people), ChangePoolId, ChangeCopyId}
                            end
                    end;
                false ->
                    {false, ?FAIL, ScenePoolId, 0}
            end;
        _ ->
            {false, ?FAIL, ScenePoolId, 0}
    end,
    case IsChangeLine of
        false ->
            {ok, BinData} = pt_120:write(12041, ErrCode),
            lib_server_send:send_to_sid(Sid, BinData),
            ok;
        true  ->
            NewStatus = lib_scene:change_scene(Status, SceneId, NewPoolId, NewCopyId, X, Y, false, []),
            {ok, NewStatus}
    end;

%% 客户端切换区域类型
handle(12085, Status, Type) ->
    {ok, BinData} = pt_120:write(12085, [Status#player_status.id, Type]),
    lib_server_send:send_to_area_scene(Status#player_status.scene, Status#player_status.scene_pool_id,
        Status#player_status.copy_id, Status#player_status.x, Status#player_status.y, BinData);

%% 玩家当前场景人数
handle(12087, Status, []) ->
    #player_status{scene=SceneId, scene_pool_id=ScenePoolId, copy_id=CopyId, sid=Sid} = Status,
    mod_scene_agent:send_number_info(SceneId, ScenePoolId, CopyId, Sid);

handle(12088, Status, []) ->
    #player_status{scene=SceneId, scene_pool_id=ScenePoolId, copy_id=CopyId, sid=Sid} = Status,
    mod_scene_agent:send_user_info(SceneId, ScenePoolId, CopyId, Sid);

handle(_Cmd, _Status, _Data) ->
    ?PRINT("no handle math ~p, ~p~n",  [_Cmd, _Data]),
    {error, "pp_scene no match"}.

%% ================================= private fun =================================
%% @doc 切换场景
%% DunId:非副本场景切换=0；副本场景：副本id
%% ChangeSceneId:切换的场景id
%% @end
%% 说明：reconnect默认为0，登录置为1，relogin置为2，首次切换场景后，会将reconnect置为0
change_scene(#player_status{sid = Sid, scene = _OldSceneId, fly_state = FlyState} = Status,
    DunId, ChangeSceneId, BackValue, TransType, TX, TY) ->
    if
        %% 服务端不是小飞鞋状态，客户端请求飞鞋状态
        FlyState == 0 andalso TransType == ?TRANS_TYPE_FLY ->
            DataArgs = [0, 0, 0, ?ERRCODE(err120_transtype), 0, BackValue, TransType],
            lib_server_send:send_to_sid(Sid, pt_120, 12005, DataArgs),
            {ok, Status};
        % %% 任务小飞鞋，不需要判断飞鞋状态且仅限150级前使用
        % TransType == ?TRANS_TYPE_GATE_NEAR andalso Status#player_status.figure#figure.lv > 150 ->
        %     DataArgs = [0, 0, 0, ?ERRCODE(err120_transtype), 0, BackValue, TransType],
        %     lib_server_send:send_to_sid(Sid, pt_120, 12005, DataArgs),
        %     {ok, Status};
        true ->
            {ok, NewStatus} = do_change_scene(Status, DunId, ChangeSceneId, BackValue, TransType, TX, TY),
            {ok, NewStatus#player_status{fly_state = 0}}
    end.

do_change_scene(Status, DunId, ChangeSceneId, CallBackValue, ClientTransType, TX, TY) ->
    #player_status{
        scene = RoleSceneId, x = RoleX, y = RoleY, sid = Sid,
        reconnect = Reconnect, fin_change_scene = FinChangeScene,
        last_load_scene = LastLoadScene
    } = Status,
    %% 历史设计原因，前端有很多情况下默认发0，
    %% 即为?TRANS_TYPE_SERVER，需要修正为?TRANS_TYPE_WORLD
    TransType =
    case ClientTransType =:= ?TRANS_TYPE_SERVER of
        true  -> ?TRANS_TYPE_WORLD;
        false -> ClientTransType
    end,
    if
        %% 已加载完成场景后，同场景切同场景的处理
        ChangeSceneId == RoleSceneId andalso (Reconnect == ?RE_LOGIN orelse FinChangeScene > 0) ->
            DataArgs = [ChangeSceneId, RoleX, RoleY, ?SUCCESS, DunId, CallBackValue, TransType],
            lib_server_send:send_to_sid(Sid, pt_120, 12005, DataArgs),
            {ok, Status};
        true ->
            case lib_scene:check_enter(Status, DunId, ChangeSceneId, TransType, TX, TY) of
                {false, ErrorCode} ->
                    %% io:format("~p ~p ErrorCode:~w~n", [?MODULE, ?LINE, [ErrorCode]]),
                    {ok, BinData} =
                    case LastLoadScene =:= 0 of
                        true  ->
                            %% 从未加载过场景（意味首次登录时发送此协议），
                            %% 此时要通知前端成功进入到原场景，因为前端登录时也依靠12005来驱动发送12002加载场景
                            #player_status{dungeon = #status_dungeon{dun_id = CurrDunId}} = Status,
                            pt_120:write(12005, [RoleSceneId, RoleX, RoleY, ?SUCCESS, CurrDunId, CallBackValue, TransType]);
                        false ->
                            pt_120:write(12005, [0, 0, 0, ErrorCode, 0, CallBackValue, TransType])
                    end,
                    lib_server_send:send_to_sid(Sid, BinData),
                    {ok, Status};
                {true, DstX, DstY} ->
                    NewStatus = lib_scene:change_scene(Status, ChangeSceneId, 0, 0, DstX, DstY, true, TransType, []),
                    % 用于检查按理论计算得到的坐标，实际进入时是否被修正调整
                    % ?INFO("role:~w, need enter pos:{~w,~w,~w}, real enter pos:{~w,~w,~w}", [
                    %         Status#player_status.id, ChangeSceneId, DstX, DstY,
                    %         NewStatus#player_status.scene, NewStatus#player_status.x, NewStatus#player_status.y
                    %     ]),
                    {ok, NewStatus#player_status{pre_scene_time = 0}}
            end
    end.

%% 小飞鞋检查
check_fly_shoes(Status)->
    #player_status{scene = SceneId,
        fin_change_scene = _FinChangeScene,
        fly_state = _FlyState,
        action_lock = Lock
    } = Status,
    if
        %% 前端飞鞋如果同时受12005、12002影响，会很难控制状态，希望服务端放宽限制
        %% 所以服务端去掉必须加载完场景的使用限制（理论上存在可能会多消耗小飞鞋）
        % FinChangeScene == 0 ->
        %     %% 场景还没有加载完
        %     {false, ?ERRCODE(err120_scene_loading)};
        %% 由于异步操作，这个限制前端不容易配合好任务流程处理，所以去掉（可能多用小飞鞋）
        % FlyState == 1 ->
        %     %% 小飞鞋状态还在，帮你省着点小飞鞋
        %     {false, ?ERRCODE(err120_transtype)};
        true ->
            #ets_scene{type = Type} = data_scene:get(SceneId),
            IsSpDungeon = lib_dungeon:is_allow_fly_shoes(Status),
            IsDesertedScene = local_boss_api:is_allow_fly_shoes(Status),
            PrivilegeVal = lib_vip_api:get_vip_privilege(Status, ?GLOBAL_MAP_TRANSABLE, 1),
            IsConvoying = convoy_api:is_convoy_action(Lock),
            if
                IsConvoying -> {false, Lock};
                IsSpDungeon == true -> true;
                IsDesertedScene -> true;
                Type =/= ?SCENE_TYPE_NORMAL andalso Type =/= ?SCENE_TYPE_OUTSIDE -> %% 小飞鞋只能切主城和野外
                    {false, ?ERRCODE(err120_cannot_transfer_scene)};
                PrivilegeVal > 0 -> true;
                true ->
                    case lib_goods_api:cost_object_list_with_check(Status, [{?TYPE_GOODS, ?GOODS_ID_SHOES, 1}], fly_shoes, "") of
                        {true, NewStatus, _} -> {true, NewStatus};
                        _ -> {false, ?ERRCODE(err120_no_fly_shoes)}
                    end
            end
    end.
