%%%-------------------------------------------------------------------
%%% @author liushl
%%% @doc
%%%     跳一跳：场景内玩法逻辑
%%% @end
%%%-------------------------------------------------------------------
-module(jump_act_play).
-author("liushl").

-include("jump_act.hrl").
-include("custom_act.hrl").
-include("predefine.hrl").
-include("server.hrl").
-include("def_module.hrl").
-include("figure.hrl").
-include("common.hrl").
-include("scene.hrl").
-include("attr.hrl").
-include("team.hrl").
%% API
-export([handle/3, set_room_data/2, settlement_in_server/5, settlement_online/5]).
%%%-------------------------------------------------------------------
%%% @doc
%%%     玩法流程
%%% @end
%%%-------------------------------------------------------------------
%% 第0步：初始化
handle({'JUMP_INIT', RoomId}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, #jump_act_room{roles = RoleList} = Room} ->
            %% 分配场景分线
            ScenePool = scene_mgr:manual_divide_pool(?JUMP_ACT_SCENE, [RoleId || #jump_act_role{role_id = RoleId} <- RoleList]),
            Room1 = Room#jump_act_room{
                begin_time  = utime:unixtime(),
                scene_pool  = ScenePool,
                round       = 1,
                map_id      = 1,
                roles       = urand:list_shuffle(RoleList)       %% 随机打乱玩家，之后每轮都以该顺序轮流跳跃
            },
            
            case map_init(Room1) of
                #jump_act_room{step_foot = #jump_act_step{x = X, y = Y}} = Room2 ->
                    %% 拉玩家入场:出生点就是脚下
                    [
                        custom_act_jump_act:enter_scene_callback(Role, ScenePool, RoomId, X, Y) || Role <- RoleList
                    ],
                    %% 至多等候20秒，等玩家全部加载完场景，即使玩家离线，活动照样继续，不影响在线的玩家跳跃，轮到离线玩家时就会自动超时失败
                    Msg = {common, ActInfo#jump_act_act.sub_type, ?JUMP_ACT_EVENT_PLAY, {'PRE_READY', RoomId}},
                    Room3 = Room2#jump_act_room{
                        all_enter = [One#jump_act_role.role_id || One <- RoleList],
                        ref_act   = erlang:send_after(?JUMP_ACT_WAIT_TIME * 1000, self(), Msg)
                    },
                    NewActInfo = set_room_data(Room3, ActInfo),
                    {State, NewActInfo};
                _ ->
                    ignore
            end;
        error ->
            ?ERR("cannot find room data:[room_id=~p]", [RoomId]),
            ignore
    end;
%% 第00步：玩家加载完场景
handle({'ROLE_ENTER', RoomId, RoleId, Sid}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, #jump_act_room{all_enter = AllEnter, roles = Roles} = Room} ->
            %% 告诉其他人我入场了
            {ok, Bin} = pt_459:write(45916, [RoleId]),
            broadcast_play_info(Room, Bin),
            Room1 =
                case lists:keyfind(RoleId, #jump_act_role.role_id, Roles) of
                    #jump_act_role{} = Role ->
                        NewRole = Role#jump_act_role{is_online = 1},
                        NewRoles = lists:keyreplace(RoleId, #jump_act_role.role_id, Roles, NewRole),
                        Room#jump_act_room{roles = NewRoles};
                    false ->
                        Room
                end,
            notice_play_info(Room1, Sid),
            case AllEnter of
                [] ->
                    %% 如果活动开启，all_enter会清空
                    NewActInfo = set_room_data(Room1, ActInfo),
                    {State, NewActInfo};
                _ ->
                    Room2 = Room1#jump_act_room{
                        all_enter = lists:delete(RoleId, AllEnter)
                    },
                    NewActInfo = set_room_data(Room2, ActInfo),
                    case Room2#jump_act_room.all_enter =:= [] of
                        true  ->
                            %% 大家都进来了，准备开始，可以关闭'PRE_READY'定时器
                            util:cancel_timer(Room#jump_act_room.ref_act),
                            handle({'PRE_READY', RoomId}, State, NewActInfo);
                        false ->
                            {State, NewActInfo}
                    end
            end;
        error ->
            ok
    end;

%% 第000步：5秒倒计时开始
handle({'PRE_READY', RoomId}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, Room} ->
            Msg = {common, ActInfo#jump_act_act.sub_type, ?JUMP_ACT_EVENT_PLAY, {'GO', RoomId}},
            NewRoom = Room#jump_act_room{
                ref_act     = erlang:send_after(?JUMP_ACT_READY_TIME * 1000, self(), Msg),
                all_enter   = []
            },
            NewActInfo = set_room_data(NewRoom, ActInfo),
            %% 广播5秒倒计时
            {ok, Bin} = pt_459:write(45904, [utime:unixtime() + ?JUMP_ACT_READY_TIME]),
            broadcast_play_info(NewRoom, Bin),
            {State, NewActInfo};
        error ->
            ?ERR("cannot find room data:[room_id=~p]", [RoomId]),
            ok
    end;

%% 循环第1步(成功)：确定下一个跳跃玩家/生成一个台子
handle({'GO', RoomId}, State, ActInfo) ->
    {NewState, NewActInfo} = handle({'SWITCH', RoomId}, State, ActInfo),
    case get_room_data(RoomId, NewActInfo) of
        {ok, Room} ->
            %% 获得下一个台子
            Room1 = get_next_step(Room),
            %% 再预生成一个台子
            Room2 = create_step(Room1),
            ActInfo1 = set_room_data(Room2, NewActInfo),
            %% 广播台子
            {ok, Bin} = pt_459:write(45906, [Room2#jump_act_room.x, Room2#jump_act_room.y, Room2#jump_act_room.radius, Room2#jump_act_room.model, Room2#jump_act_room.step_end]),
            broadcast_play_info(Room2, Bin),
            {NewState, ActInfo1};
        error ->
            {NewState, NewActInfo}
    end;

%% 循环第1步(失败)：不生成台子只切换玩家
handle({'FAIL_GO', RoomId}, State, ActInfo) ->
    %% 确定下一个跳跃玩家
    handle({'SWITCH', RoomId}, State, ActInfo);

%% 切换玩家
handle({'SWITCH', RoomId}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, Room} ->
            %% 确定下一个跳跃玩家
            Room1 = get_next_jump_role(Room),
            case is_integer(Room1#jump_act_room.jump_role) of
                true  ->
                    case lists:keyfind(Room1#jump_act_room.jump_role, #jump_act_role.role_id, Room#jump_act_room.roles) of
                        #jump_act_role{is_online = 0} = Role ->
                            broad_cast_offline_role(Room, Role);
                        _ ->
                            ignore
                    end,
                    %% 15秒后跳跃超时
                    Msg = {common, ActInfo#jump_act_act.sub_type, ?JUMP_ACT_EVENT_PLAY, {'JUMP_OVERDUE', RoomId, Room1#jump_act_room.jump_role, false}},
                    Room2 = Room1#jump_act_room{
                        ref_act     = erlang:send_after(?JUMP_ACT_JUMP_TIME * 1000, self(), Msg),
                        jump_time   = utime:unixtime() + ?JUMP_ACT_JUMP_TIME,
                        jump_status = 0
                    },
                    NewActInfo = set_room_data(Room2, ActInfo),
                    %% 广播跳跃玩家
                    {ok, Bin} = pt_459:write(45907, [Room2#jump_act_room.round, Room2#jump_act_room.jump_role, Room2#jump_act_room.jump_time]),
                    broadcast_play_info(Room2, Bin),
                    {State, NewActInfo};
                false ->
                    ?ERR("cannot find role to jump:[room_id=~p]", [Room1]),
                    {State, ActInfo}
            end;
        _ ->
            ?ERR("cannot find room data:[room_id=~p]", [RoomId]),
            {State, ActInfo}
    end;

%% 循环第2步：准备跳跃
handle({'READY_JUMP', RoomId, RoleId}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, #jump_act_room{jump_role = RoleId, jump_progress = ?JUMP_PROGRESS_NONE} = Room} ->
            NewActInfo = set_room_data(Room#jump_act_room{jump_progress = ?JUMP_PROGRESS_PRE}, ActInfo),
            %% 广播动作
            {ok, Bin} = pt_459:write(45909, [RoleId, 1]),
            broadcast_play_info(Room, Bin),
            {State, NewActInfo};
        _ ->
            ignore
    end;

%% 循环第3步：玩家跳跃
%%  失败，-1命。
handle({'JUMP', RoomId, RoleId, TargetX, TargetY, Status, Time}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, #jump_act_room{
            roles = Roles, jump_role = RoleId, jump_progress = ?JUMP_PROGRESS_PRE, round = Round
            } = Room} ->
            case lists:keyfind(RoleId, #jump_act_role.role_id, Roles) of
                #jump_act_role{life = Life} = Role ->
                    %% 广播跳跃动作
                    case TargetX > 0 of
                        true  ->
                            {ok, Bin1} = pt_459:write(45910, [RoleId, TargetX, TargetY, Status, Time]),
                            broadcast_play_info(Room, Bin1);
                        false ->
                            ok
                    end,
                    NewRole =
                        case Status =:= ?JUMP_ACT_JUMP_SUCCESS of
                            true  ->
                                Role;
                            false ->
                                Role1 = Role#jump_act_role{life = Life - 1},
                                %% 判断玩家是否淘汰
                                case Role1#jump_act_role.life =:= 0 of
                                    true  ->
                                        %% 确定他的排名,此时并不判断活动结束，由前端发送跳跃完成再触发判断
                                        calc_rank(Roles, Role1, Round, ActInfo#jump_act_act.sub_type);
                                    false ->
                                        Role1
                                end
                        end,
                    NewRoles = lists:keyreplace(RoleId, #jump_act_role.role_id, Roles, NewRole),
                    Room1 = Room#jump_act_room{roles = NewRoles, jump_status = Status, jump_progress = ?JUMP_PROGRESS_JUMP},
                    %% 成功的话，将当前坐标设置为脚下坐标
                    Room2 = case Status =:= ?JUMP_ACT_JUMP_SUCCESS of
                        true  ->
                            StepFoot = #jump_act_step{
                                x         = Room1#jump_act_room.x      %% 台子坐标
                                , y         = Room1#jump_act_room.y
                                , radius    = Room1#jump_act_room.radius     %% 台子半径
                                , model     = Room1#jump_act_room.model     %% 台子模型
                            },
                            Room1#jump_act_room{
                                step_foot = StepFoot
                            };
                        false ->
                            Room1
                    end,
                    NewActInfo = set_room_data(Room2, ActInfo),
                    {State, NewActInfo};
                _ ->
                    {State, ActInfo}
            end;
        _ ->
            {State, ActInfo}
    end;

%% 跳跃超时-算作跳跃失败，外加直接完成
handle({'JUMP_OVERDUE', RoomId, RoleId, Force}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, #jump_act_room{jump_progress = ?JUMP_PROGRESS_NONE, jump_role = RoleId} = Room} ->
            NewActInfo = set_room_data(Room#jump_act_room{jump_progress = ?JUMP_PROGRESS_PRE}, ActInfo),
            %% 玩家还没开始跳跃，那么直接算作失败
            {State1, ActInfo1} = handle({'JUMP', RoomId, RoleId, 0, 0, ?JUMP_ACT_JUMP_FAIL, 0}, State, NewActInfo),
            handle({'JUMP_END', RoomId, RoleId}, State1, ActInfo1);
        {ok, #jump_act_room{jump_progress = Progress, jump_role = RoleId} = Room} when Progress > 0 andalso not Force  ->
            %% 玩家已经开始跳跃，可能是踩点跳跃，给一个时间，让他跳完
            Msg = {common, ActInfo#jump_act_act.sub_type, ?JUMP_ACT_EVENT_PLAY, {'JUMP_OVERDUE', RoomId, Room#jump_act_room.jump_role, true}},
            Room1 = Room#jump_act_room{
                ref_act     = erlang:send_after(5 * 1000, self(), Msg)
            },
            NewActInfo = set_room_data(Room1, ActInfo),
            {State, NewActInfo};
        {ok, #jump_act_room{jump_progress = ?JUMP_PROGRESS_PRE, jump_role = RoleId}} when Force ->
            %% 玩家在做准备动作，强制失败结算
            {State1, ActInfo1} = handle({'JUMP', RoomId, RoleId, 0, 0, ?JUMP_ACT_JUMP_FAIL, 0}, State, ActInfo),
            handle({'JUMP_END', RoomId, RoleId}, State1, ActInfo1);
        {ok, #jump_act_room{jump_progress = ?JUMP_PROGRESS_JUMP, jump_role = RoleId}} when Force ->
            %% 玩家已经起跳，当时没有收到结束协议，强制结束
            handle({'JUMP_END', RoomId, RoleId}, State, ActInfo);
        _ ->
            ok
    end;

%% 循环第4步：跳跃动作完成
handle({'JUMP_END', RoomId, RoleId}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, #jump_act_room{
            jump_role = RoleId,
            roles = Roles,
            jump_status = Status,
            round = Round,
            jump_progress = ?JUMP_PROGRESS_JUMP
        } = Room} ->
            util:cancel_timer(Room#jump_act_room.ref_act),
            %% 失败广播命数
            Status =:= ?JUMP_ACT_JUMP_FAIL andalso
                case lists:keyfind(RoleId, #jump_act_role.role_id, Roles) of
                    #jump_act_role{life = Life} ->
                        %% 广播积分和命数
                        {ok, Bin} = pt_459:write(45911, [RoleId, Life]),
                        broadcast_play_info(Room, Bin);
                    _ ->
                        ok
                end,
            Room1 = Room#jump_act_room{jump_progress = ?JUMP_PROGRESS_NONE},
        
            case Status =/= ?JUMP_ACT_JUMP_SUCCESS of
                true  ->
                    %% 跳跃失败,判断是否可以结束
                    case check_end_play(ActInfo, Roles, Round) of
                        {true, NewRoles}  ->
                            NewRoom = Room1#jump_act_room{roles = NewRoles},
                            wait_settlement(State, ActInfo, NewRoom);
                        false ->
                            %% 循环重复第1步,
                            NewActInfo = set_room_data(Room1, ActInfo),
                            handle({'FAIL_GO', RoomId}, State, NewActInfo)
                    end;
                false ->
                    %% 成功则判断是否需要切换地图
                    NewActInfo =
                        case not is_record(Room#jump_act_room.step_coming, jump_act_step) of
                            true  ->
                                NewRoom = switch_map(Room1),
                                set_room_data(NewRoom, ActInfo);
                            false ->
                                set_room_data(Room1, ActInfo)
                        end,
                    handle({'GO', RoomId}, State, NewActInfo)
            end;
        _ ->
            ok
    end;

%% （主动离开）离开玩法-则直接结算
handle({'LEAVE', RoomId, RoleId}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, #jump_act_room{roles = Roles, jump_role = JumpRole, round = Round} = Room} ->
            case lists:keyfind(RoleId, #jump_act_role.role_id, Roles) of
                #jump_act_role{is_out = 0, rank = Rank} = Role when Rank > 0 ->
                    %% 已淘汰，退出则直接结算自己
                    Roles1  = lists:keyreplace(Role#jump_act_role.role_id, #jump_act_role.role_id, Roles, Role#jump_act_role{is_out = 1}),
                    jump_settlement_alone(Room, Roles1, Role, ActInfo#jump_act_act.sub_type),
                    Room1  = Room#jump_act_room{roles = Roles1},
                    NewActInfo = set_room_data(Room1, ActInfo),
                    {State, NewActInfo};
                #jump_act_role{is_out = 0} = Role ->
                    %% 未淘汰则确定他的排名,将他结算
                    NewRole = calc_rank(Roles, Role, Round, ActInfo#jump_act_act.sub_type),
                    Roles1  = lists:keyreplace(Role#jump_act_role.role_id, #jump_act_role.role_id, Roles, NewRole#jump_act_role{is_out = 1}),
                    %% 判断是否可以直接结束
                    case check_end_play(State, Roles1, Round) of
                        {true, Roles2}  ->
                            End = true,
                            Roles3 = Roles2;
                        false ->
                            End = false,
                            Roles3 = Roles1
                    end,
                    %% 单独结算自己
                    jump_settlement_alone(Room, Roles3, NewRole, ActInfo#jump_act_act.sub_type),
                    NewRoom = Room#jump_act_room{roles = Roles3},
                    %% 广播玩家退出淘汰
                    {ok, Bin}  = pt_459:write(45911, [RoleId, 0]),
                    {ok, Bin2} = pt_459:write(45917, [NewRole#jump_act_role.figure#figure.name]),
                    broadcast_play_info(NewRoom, <<Bin/binary, Bin2/binary>>),
                    case End of
                        true  ->
                            %% 其他人也可以结算了
                            util:cancel_timer(Room#jump_act_room.ref_act),
                            wait_settlement(State, ActInfo, NewRoom);
                        false ->
                            %% 不会直接结束
                            NewActInfo = set_room_data(NewRoom, ActInfo),
                            case JumpRole =:= RoleId of
                                true  ->
                                    %% 如果当前跳跃玩家离开，那就直接切换
                                    handle({'JUMP_OVERDUE', RoomId, RoleId, true}, State, NewActInfo);
                                false ->
                                    {State, NewActInfo}
                            end
                    end;
                _ ->
                    ok
            end;
        _ ->
            ok
    end;

%% 结算
handle({'JUMP_SETTLEMENT', RoomId}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, Room} ->
            #jump_act_room{roles = Roles, round = Round} = Room,
            %% 给未排名的玩家排个名先/既然是强制结算，那就随便排名
            NewRoles = lists:foldl(
                fun(Role, Temp) ->
                    case Role#jump_act_role.rank =:= 0 of
                        true  ->
                            NewRole = calc_rank(Temp, Role, Round, ActInfo#jump_act_act.sub_type),
                            lists:keyreplace(
                                Role#jump_act_role.role_id,
                                #jump_act_role.role_id,
                                Temp,
                                NewRole
                            );
                        false ->
                            Temp
                    end
                end, Roles, Roles
            ),
            NewActInfo = settlement(Room#jump_act_room{roles = NewRoles}, ActInfo),
            {State, NewActInfo};
        error ->
            ignore
    end;

%% 玩家离线
handle({'OFFLINE', RoomId, RoleId}, State, ActInfo) ->
    case get_room_data(RoomId, ActInfo) of
        {ok, #jump_act_room{roles = Roles, jump_role = JumpRole} = Room} ->
            case lists:keyfind(RoleId, #jump_act_role.role_id, Roles) of
                #jump_act_role{life = Life} = Role ->
                    NewRole = Role#jump_act_role{is_online = 0},
                    NewRoles = lists:keyreplace(RoleId, #jump_act_role.role_id, Roles, NewRole),
                    NewRoom = Room#jump_act_room{
                        roles = NewRoles
                    },
                    ActInfo1 = ActInfo#jump_act_act{
                        temp_room = [{RoleId, RoomId}|ActInfo#jump_act_act.temp_room]
                    },
                    NewActInfo = set_room_data(NewRoom, ActInfo1),

                    case Life > 0 of
                        true  ->
                            {ok, Bin} = pt_459:write(45915, [RoleId]),
                            broad_cast_offline_role(NewRoom, Role),
                            broadcast_play_info(NewRoom, Bin);
                        false ->
                            ok
                    end,
                    case JumpRole =:= RoleId of
                        true  ->
                            %% 如果当前跳跃玩家离开，那就直接切换
                            handle({'JUMP_OVERDUE', RoomId, RoleId, true}, State, NewActInfo);
                        false ->
                            {State, NewActInfo}
                    end;
                false ->
                    ok
            end;
        _ ->
            ok
    end;

%% 玩家重新上线
handle({'RE_LOGIN', RoleId}, State, ActInfo) ->
    case lists:keytake(RoleId, 1, ActInfo#jump_act_act.temp_room) of
        {value, {RoleId, RoomId}, Left} ->
            case get_room_data(RoomId, ActInfo) of
                {ok, #jump_act_room{roles = Roles, step_foot = #jump_act_step{x = X, y = Y}, scene_pool = ScenePool}} ->
                    case lists:keyfind(RoleId, #jump_act_role.role_id, Roles) of
                        #jump_act_role{is_out = 0} = Role ->
                            %% 没有离开才需要重新拉进来
                            custom_act_jump_act:enter_scene_callback(Role, ScenePool, RoomId, X, Y),
                            ok;
                        false ->
                            ok
                    end;
                error ->
                    ok
            end,
            {State, ActInfo#jump_act_act{temp_room = Left}};
        false ->
            ok
    end;

handle(_Msg, State, _Arg) ->
    erlang:display({"[LIUSHL]", erlang:localtime(), ?MODULE, ?LINE, _Msg}),
    State.

%%%-------------------------------------------------------------------
%%% Internal Function
%%%-------------------------------------------------------------------
%% 房间数据
get_room_data(RoomId, ActInfo) ->
    case maps:find(RoomId, ActInfo#jump_act_act.rooms) of
        {ok, Room} when is_record(Room, jump_act_room) ->
            {ok, Room};
        _ ->
            error
    end.
set_room_data(#jump_act_room{id = RoomId} = Room, #jump_act_act{rooms = Rooms} = ActInfo) ->
    ActInfo#jump_act_act{rooms =Rooms#{RoomId => Room}}.

remove_room(RoomId, #jump_act_act{rooms = Rooms} = ActInfo) ->
    ActInfo#jump_act_act{rooms = maps:remove(RoomId, Rooms)}.

%% 广播玩法信息
broadcast_play_info(Room, Bin) ->
    #jump_act_room{
        roles = Roles
    } = Room,
    [
        lib_server_send:send_to_uid(Node, RoleId, Bin)
        || #jump_act_role{node = Node, role_id = RoleId, is_out = IsOut, is_online = Online} <- Roles, IsOut =:= 0, Online =:= 1
    ].

%% 进入场景推送玩法信息
notice_play_info(Room, Sid) ->
    #jump_act_room{
        id          = RoomId
        , roles     = Roles
        , step_foot = StepFoot
        , round     = Round
        , x         = X
        , y         = Y
        , radius    = Radius
        , model     = Model
        , step_end  = StepEnd
        , jump_role = JumpRole
        , jump_time = JumpTime
    } = Room,
    Info = [
        RoomId,
        [{Role#jump_act_role.figure, Role#jump_act_role.life, Role#jump_act_role.is_online} || Role <- Roles],
        Round,
        StepFoot#jump_act_step.x, StepFoot#jump_act_step.y, StepFoot#jump_act_step.radius, StepFoot#jump_act_step.model
    ],
    {ok, Bin} = pt_459:write(45905, Info),
    {ok, Bin2} = pt_459:write(45906, [X, Y, Radius, Model, StepEnd]),
    {ok, Bin3} = pt_459:write(45907, [Round, JumpRole, JumpTime]),
    lib_server_send:send_to_sid(Sid, <<Bin/binary, Bin2/binary, Bin3/binary>>),
    ok.

%% 对于离线玩家给前端推一个假数据, 因为前端没有缓存玩家形象，所以轮到离线玩家跳跃的时候
%% 给前端创建一个玩家形象
broad_cast_offline_role(Room, Role) ->
    User = #ets_scene_user{
        id = Role#jump_act_role.role_id,
        scene = ?SCENE_TYPE_JUMP_ACT,
        scene_pool_id = Room#jump_act_room.scene_pool,
        copy_id = Room#jump_act_room.id,
        figure = Role#jump_act_role.figure,
        server_num = Role#jump_act_role.server_num,
        battle_attr = #battle_attr{pk = #pk{}},
        team = #status_team{},
        x = Room#jump_act_room.step_foot#jump_act_step.x,
        y = Room#jump_act_room.step_foot#jump_act_step.y
    },
    {ok, Bin} = pt_120:write(12003, User),
    broadcast_play_info(Room, Bin).
%%%-------------------------------------------------------------------
%%% @doc
%%%     生成下一个台子
%%% @end
%%%-------------------------------------------------------------------
%% 获得下一个台子
get_next_step(Room) ->
    case Room#jump_act_room.step_coming of
        #jump_act_step{} = Step ->
            Room#jump_act_room{
                x         = Step#jump_act_step.x
                , y         = Step#jump_act_step.y
                , radius    = Step#jump_act_step.radius
                , model     = Step#jump_act_step.model
            };
        _ ->
            ?ERR("get_next_step:[~p]", [Room]),
            Room
    end.

%% 预生成一个台子
create_step(Room) ->
    #jump_act_room{
        step_coming = StepComing
        , step_foot = StepFoot
        , round     = Round
        , map_id    = MapId
    } = Room,
    case StepComing of
        #jump_act_step{x = X, y = Y} ->
            ok;
        _ ->
            X = StepFoot#jump_act_step.x, Y = StepFoot#jump_act_step.y
    end,
    case create_step_done(X, Y, Round, MapId) of
        #jump_act_step{} = Step->
            Room#jump_act_room{step_coming = Step, step_end = 0};
        switch_map ->
            Room#jump_act_room{step_coming = undefined, step_end = 1};
        _ ->
            Room
    end.

create_step_done(CurX, CurY, Round, MapId) ->
    case
        {
            data_jump_act:get_distance_by_round(Round),         %% 配置距离区间
            data_jump_act:get_radius_by_round(Round),           %% 配置半径区间
            data_jump_act:get_jump_act_kv(jump_step_model),     %% 配置台子模型列表
            data_jump_act:get_map_border_xy(MapId)              %% 配置地图终点
        }
    of
        {[DisMin, DisMax], [RadiusMin, RadiusMax], [StepModelList], {BorderX, BorderY}} ->
            %% 随机下一个台子的距离
            Dis     = urand:rand(DisMin, DisMax),
            %% 随机下一个台子的半径
            Radius  = urand:rand(RadiusMin, RadiusMax),
            %% 随机下一个台子的模型
            Model   = urand:list_rand(StepModelList),
            %% 跳跃路径是从出生点A出发，跳到上面一个顶角B位置，中间随机向左上或者右上
            %% 地图是菱形样式的正方形
            %% y     B
            %% ↑     /\
            %% |   C/  \D
            %% |    \  /
            %% |     \/A
            %% ------------->x
            %% 由图可以看出，因为是往左上或者右上行进，所以路线一定与边AC或者AD平行
            %% Y坐标一直递增，所以当Y越界的时候表明地图跑完，切换到下一地图
            %% 如果，下一个跳点在CD下面，不需要判断越界问题，如果下一个跳点在CD上面，则需要判断X坐标是否越界
            %% 如果X往左越界，则改为往右；如果X往右越界，则改为往左；如果仍然越界，则切换到一个地图
            %% 判断是否越界方法：目标点是否在BC或者BD以下。
            %%    第一步：计算出目标点X坐标在BC或者BD上对应的Y的坐标。
            %%    第二步：如果第一步计算出的Y坐标大于目标点Y坐标，则目标点越界。
            Angle = math:sin(math:pi() * 45 / 180),
            XYDis = erlang:round(Dis * Angle),
            TargetY = CurY + XYDis, %% Y坐标是一直增大的
            case TargetY > BorderY of
                true  ->
                    %% 到达终点，则切向下一张地图，传送至下一张地图的出生点
                    switch_map;
                false ->
                    Move = case urand:rand(0, 100) > 50 of %% 50%的概率往右上或者往左上
                        true  ->
                            XYDis;
                        false ->
                            -XYDis
                    end,
                    TargetX = CurX + Move,
                    case check_bounds(Move, BorderX, BorderY, TargetX, TargetY) of
                        false ->
                            %% 没有越界
                            #jump_act_step{x = TargetX, y = TargetY, radius = Radius, model = Model};
                        true  ->
                            %% 越界了，切换方向
                            SwitchX = CurX - Move,
                            case check_bounds(-Move, BorderX, BorderY, SwitchX, TargetY) of
                                false ->
                                    %% 没有越界
                                    #jump_act_step{x = SwitchX, y = TargetY, radius = Radius, model = Model};
                                true  ->
                                    %% 还是越界了，那就切换地图
                                    switch_map
                            end
                    end
            end;
        _ ->
            ?ERR("step conf error:[Round=~p, map=~p]", [Round, MapId]),
            false
    end.

check_bounds(Move, BorderX, BorderY, X, Y) when Move > 0 ->
    %% 往右走，判断XY是否越过BD
    %% 计算出X在BD上的Y点
    %% BD函数为斜率-1，过B点的直线
    %% y = -x + b 带入B点
    %% BorderY = -BorderX + b => b = BorderX + BorderY
    %% 函数为 y = -x + BorderX + BorderY
    CurBorderY = -X + BorderX + BorderY,
    CurBorderY < Y;
check_bounds(_Move, BorderX, BorderY, X, Y)->
    %% 往左走，判断XY是否越过BC
    %% 计算出X在BC上的Y点
    %% BD函数为斜率1，过B点的直线
    %% y = x + b 带入B点
    %% BorderY = BorderX + b => b = BorderY - BorderX
    %% 函数为 y = x + BorderY - BorderX
    CurBorderY = X + BorderY - BorderX,
    CurBorderY < Y.

%% 进入下一地图
switch_map(Room) ->
    NextMapId = data_jump_act:get_next_map(Room#jump_act_room.map_id),
    Room1 = Room#jump_act_room{map_id = NextMapId},
    Room2 = map_init(Room1),
    Step = Room2#jump_act_room.step_foot,
    %% 2、广播下一个地图的出生点台子
    {ok, Bin1} = pt_459:write(45906, [Step#jump_act_step.x, Step#jump_act_step.y, Step#jump_act_step.radius, Step#jump_act_step.model, 0]),
    %% 3、通知所有玩家传送至下一张地图的出生点
    {ok, Bin2} = pt_459:write(45912, [Step#jump_act_step.x, Step#jump_act_step.y]),
    broadcast_play_info(Room2, <<Bin1/binary, Bin2/binary>>),
    Room2.

map_init(Room) ->
    %% 生成地图的出生点台子
    Room1 = init_born_step(Room),
    %% 再预生成一个台子
    Room2 = create_step(Room1),
    if
        not is_record(Room2#jump_act_room.step_foot, jump_act_step) ->
            ?ERR("init step error:[room_info=~p]", [Room2]),
            ignore;
        not is_record(Room2#jump_act_room.step_coming, jump_act_step) ->
            ?ERR("init step error:[room_info=~p]", [Room2]),
            ignore;
        true ->
            Room2
    end.

%% 初始化出生点台子
init_born_step(Room) ->
    case
        {
            data_jump_act:get_jump_act_kv(jump_step_model),             %% 配置台子模型列表
            data_jump_act:get_map_born_xy(Room#jump_act_room.map_id)    %% 配置地图起点
        }
    of
        {[StepModelList], {BornX, BornY}} ->
            Model = urand:list_rand(StepModelList),
            Step = #jump_act_step{
                x         = BornX
                , y         = BornY
                , radius    = 100
                , model     = Model
            },
            Room#jump_act_room{
                step_foot = Step
            };
        _ ->
            ?ERR("init_born_step error: [~p]", [Room]),
            Room
    end.
%%%-------------------------------------------------------------------
%%% @doc
%%%     下一个操作跳跃玩家
%%% @end
%%%-------------------------------------------------------------------
get_next_jump_role(Room) ->
    #jump_act_room{
        roles = Roles
        , jump_role = JumpRole
        , round     = Round
    } = Room,
    case JumpRole =:= 0 of
        true  ->
            Room#jump_act_room{
                jump_role   = turn_next(Roles)
            };
        false ->
            case turn_next(Roles, JumpRole) of
                next_round ->
                    Room#jump_act_room{
                        jump_role   = turn_next(Roles),
                        round       = Round + 1
                    };
                NextJump ->
                    Room#jump_act_room{
                        jump_role   = NextJump
                    }
            end
    end.

%% 按照顺序，找到下一个未淘汰的玩家
turn_next([#jump_act_role{role_id = JumpRole}|T], JumpRole) ->
    turn_next(T);
turn_next([_|T], JumpRole) ->
    turn_next(T, JumpRole);
turn_next([], _) -> error.

turn_next([]) ->
    next_round;
turn_next([#jump_act_role{role_id = NextJump, rank = 0, life = Life}|_]) when Life > 0 ->
    NextJump;
turn_next([_|T]) ->
    turn_next(T).
%%%-------------------------------------------------------------------
%%% @doc
%%%     跳跃结果分析
%%% @end
%%%-------------------------------------------------------------------
%% 跳跃失败，生命为0时
%% 玩家直接退出时
%% 计算当前排名
calc_rank(Roles, Role, Round, SubType) ->
    CalcRank = case calc_rank_do(lists:keysort(#jump_act_role.rank, Roles)) of
        0 ->
            length(Roles);
        Rank ->
            Rank
    end,
    AwardInfo = case Role#jump_act_role.can_get_award of
        true  ->
            calc_award(CalcRank, ?CUSTOM_ACT_TYPE_JUMP_ACT, SubType);
        false ->
            []
    end,
    Role#jump_act_role{rank = CalcRank, stop_round = Round, life = 0, award_info = AwardInfo}.

calc_rank_do([#jump_act_role{rank = 0}|T]) ->
    calc_rank_do(T);
calc_rank_do([#jump_act_role{rank = Rank}|_]) ->
    Rank - 1;
calc_rank_do([]) ->
    0.

%% 如果最终只剩下一个人，则结算
check_end_play(ActInfo, Roles, Round) ->
    case lists:keysort(#jump_act_role.rank, Roles) of
        [#jump_act_role{life = Life, rank = Rank} = Role]
            when Life =:= 0 orelse Rank =:= 1 ->
            %% 如果只有一个人那就结算
            NewRole = Role#jump_act_role{rank = 1},
            {true, [NewRole]};
        [#jump_act_role{rank = 0} = Role, #jump_act_role{rank = Rank}|_] when Rank > 0 ->
            NewRole = Role#jump_act_role{
                rank = 1,
                stop_round = Round,
                award_info = calc_award(1, ?CUSTOM_ACT_TYPE_JUMP_ACT, ActInfo#jump_act_act.sub_type)
            },
            {true, lists:keyreplace(Role#jump_act_role.role_id, #jump_act_role.role_id, Roles, NewRole)};
        _ ->
            false
    end.

%%%-------------------------------------------------------------------
%%% @doc
%%%     结算
%%% @end
%%%-------------------------------------------------------------------
%% 等候结算
wait_settlement(State, ActInfo, #jump_act_room{id = RoomId} = Room) ->
    {ok, Bin} = pt_459:write(45918, [utime:unixtime() + ?JUMP_ACT_END_TIME]),
    broadcast_play_info(Room, Bin),
    Msg = {common, ActInfo#jump_act_act.sub_type, ?JUMP_ACT_EVENT_PLAY, {'JUMP_SETTLEMENT', RoomId}},
    NewActInfo = set_room_data(Room#jump_act_room{
        ref_act   = erlang:send_after(?JUMP_ACT_END_TIME * 1000, self(), Msg),
        jump_role = 0
    }, ActInfo),
    {State, NewActInfo}.

%% 统一结算
settlement(Room, ActInfo) ->
    #jump_act_act{sub_type = SubType} = ActInfo,
    util:cancel_timer(Room#jump_act_room.ref_act),
    %% 结算推送
    Roles = Room#jump_act_room.roles,
    ResultBin = pack_settlement_page(Room, Roles),
    check_send_tv(Roles),
    [
        clusters_center:apply_cast(Node, ?MODULE, settlement_in_server, [RoleId, AwardInfo, ResultBin, SubType, Room#jump_act_room.pattern])
        || #jump_act_role{node = Node, role_id = RoleId, rank = Rank, award_info = AwardInfo} = One <- Roles, One#jump_act_role.is_out =:= 0, Rank > 0
    ],
    remove_room(Room#jump_act_room.id, ActInfo).

%% 提前退出独自结算
jump_settlement_alone(Room, Roles, Role, SubType) ->
    %% 结算推送
    ResultBin = pack_settlement_page(Room, Roles),
    clusters_center:apply_cast(Role#jump_act_role.node, ?MODULE, settlement_in_server,
        [Role#jump_act_role.role_id, Role#jump_act_role.award_info, ResultBin, SubType, Room#jump_act_room.pattern]).

%% 第一名在各个服广播
check_send_tv(Roles) ->
    case lists:keyfind(1, #jump_act_role.rank, Roles) of
        #jump_act_role{} = Role ->
            send_tv(Roles, Role);
        false ->
            ok
    end.
send_tv(Roles, Role) ->
    #jump_act_role{award_info = AwardInfo, figure = #figure{name = Name}, server_num = ServerNum} = Role,
    Nodes = lists:usort([Node || #jump_act_role{node = Node} <- Roles]),
    [
    
        case Award of
            {_, GoodsId, GoodsNum} ->
                [ clusters_center:apply_cast(N, {lib_chat, send_TV, [{all}, ?MOD_AC_CUSTOM, 40, [ServerNum, Name, GoodsId, GoodsNum]]})|| N <- Nodes];
                    _ ->
            ok
        end
        || {_GradeId, Award, Tv} <- AwardInfo, Tv =:= true
    ].

%% 在对应玩家服务器进行结算
settlement_in_server(RoleId, AwardInfo, PageBin, SubType, Pattern) ->
    case lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, settlement_online, [AwardInfo, PageBin, SubType, Pattern]) of
        skip ->
            settlement_offline(RoleId, AwardInfo, SubType, Pattern);
        _ ->
            ok
    end.

%% 在线玩家结算
settlement_online(Player, AwardInfo, ResultBin, SubType, Pattern) ->
    #player_status{sid = Sid, id = RoleId, figure = Figure} = Player,
    lib_server_send:send_to_sid(Sid, ResultBin),
    lib_daily:increment(RoleId, ?MOD_JUMP_ACT, ?JUMP_ACT_COUNTER_DAILY_COUNT, Pattern),
    check_record_single_pattern(RoleId, Pattern),
    Reward = [Award || {_Grade, Award, _Tv} <- AwardInfo],
    case Reward =/= [] of
        true  ->
            Player1 = lib_goods_api:send_reward(Player, Reward, jump_act),
            Grades = [Grade || {Grade, _Award, _Tv} <- AwardInfo],
            lib_log_api:log_custom_act_reward(RoleId, ?CUSTOM_ACT_TYPE_JUMP_ACT, SubType, 0, 0, Reward, util:term_to_bitstring(Grades), Figure#figure.vip, Figure#figure.lv,
                Player#player_status.gold, Player1#player_status.gold, Player#player_status.bgold, Player1#player_status.bgold, utime:unixtime()),
            Player1;
        false ->
            Player
    end.
%% 离线玩家结算
settlement_offline(RoleId, AwardInfo, SubType, Pattern) ->
    lib_daily:increment_offline(RoleId, ?MOD_JUMP_ACT, ?JUMP_ACT_COUNTER_DAILY_COUNT, Pattern),
    check_record_single_pattern(RoleId, Pattern),
    Reward = [Award || {_Grade, Award, _Tv} <- AwardInfo],
    case Reward =/= [] of
        true  ->
            Grades = [Grade || {Grade, _Award, _Tv} <- AwardInfo],
            lib_log_api:log_custom_act_reward(RoleId, ?CUSTOM_ACT_TYPE_JUMP_ACT, SubType, 0, 0, Reward, util:term_to_bitstring({Grades, offline}), 0, 0, 0, 0, 0, 0, utime:unixtime()),
            lib_mail_api:send_sys_mail([RoleId], ?MOD_JUMP_ACT, utext:get(33112101), utext:get(33112102), Reward);
        false ->
            ok
    end.

%% 是否记录终生次数
check_record_single_pattern(RoleId, ?JUMP_ACT_PATTERN_SINGLE) ->
    Count = lib_counter:get_count_offline(RoleId, ?MOD_JUMP_ACT, ?JUMP_ACT_COUNTER_LIFE_COUNT),
    Count =:= 0 andalso lib_counter:increment_offline(RoleId, ?MOD_JUMP_ACT, ?JUMP_ACT_COUNTER_LIFE_COUNT);
check_record_single_pattern(_, _) -> ignore.

%% 根据排名计算奖励
calc_award(Rank, Type, SubType) ->
    WeightList = get_award_weight_list(Type, SubType, Rank =:= 1),
    GradeList  = urand:repeat_list_rand_by_weight(WeightList, 3),
    lists:foldl(
        fun(GradeId, Temp) ->
            case lib_custom_act_util:get_act_reward_cfg_info(Type, SubType, GradeId) of
                #custom_act_reward_cfg{condition = Condition, reward = [Reward]} ->
                    ShowTv =
                    case lists:keyfind(tv_show, 1, Condition) of
                        {tv_show, 1} ->
                            true;
                        _ ->
                            false
                    end,
                    [{GradeId, Reward, ShowTv}|Temp];
                _ ->
                    Temp
            end
        end, [], GradeList
    ).

get_award_weight_list(Type, SubType, Win) ->
    lists:foldl(
        fun(GradeId, Temp) ->
            #custom_act_reward_cfg{condition = Condition}
                = lib_custom_act_util:get_act_reward_cfg_info(Type, SubType, GradeId),
            case lists:keyfind(weight, 1, Condition) of
                {weight, Weight} ->
                    case lists:keyfind(is_win, 1, Condition) of
                        {is_win, 1} when Win ->
                            [{Weight, GradeId}|Temp];
                        {is_win, 0} when not Win ->
                            [{Weight, GradeId}|Temp];
                        _ ->
                            Temp
                    end;
                false ->
                    Temp
            end
        end, [], lib_custom_act_util:get_act_reward_grade_list(Type, SubType)
    ).

%% 结算界面
pack_settlement_page(Room, Roles) ->
    {ok, Bin} = pt_459:write(45914,
        [
            [{
                Role#jump_act_role.figure,
                Role#jump_act_role.stop_round,
                Role#jump_act_role.rank, [Award||{_Grade, Award, _} <- Role#jump_act_role.award_info],
                Role#jump_act_role.server_num
            } || Role <- Roles],
            Room#jump_act_room.pattern
        ]
    ),
    Bin.


