%%---------------------------------------------------------------------------
%% @title魅力海滩 （跨服）
%% @create yxf
%% @date 20171216
%%---------------------------------------------------------------------------
-module(mod_charm_beach).
-include("common.hrl").
-include("charm_beach.hrl").
-include("server.hrl").
-include("language.hrl").
-include("goods.hrl").
-include("def_module.hrl").
-include("errcode.hrl").
-include("def_fun.hrl").
-include("activitycalen.hrl").
-include("kv.hrl").
-include("figure.hrl").
-include("predefine.hrl").
-behaviour(gen_server).
-compile(export_all).

-define (TIME_OFFSET,
    case get(gm_time_offset) of
        undefined -> 0;
        LogicTime -> LogicTime
    end).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
    process_flag(trap_exit, true),
    case lib_activitycalen_api:get_first_enabled_ac_id(?MOD_BEACH, 0) of
        {ok, ActId} ->
            %% 跨服活动中,如果跨服没开启,通知跨服开启
            State = init_act(ActId);
        _ ->
            ActType
            = case config:get_cls_type() of
                ?NODE_CENTER -> ?ACT_TYPE_KF;
                _ -> ?ACT_TYPE_BF
            end,
            ActSceneId = ?SCENE_ID(ActType),
            State = #beach_stage{act_type = ActType, scene = ActSceneId}
    end,
    {ok, State}.

%% --------------------------------------------------------------------------
handle_cast(Msg, State) ->
    case catch do_handle_cast(Msg, State) of
        {ok, NewState} when is_record(NewState, beach_stage) ->
            {noreply, NewState};
        Err ->
            ?ERR("handle_cast Msg:~p error:~p~n", [Msg, Err]),
            {noreply, State}
    end.

handle_info(Info, State) ->
    case catch do_handle_info(Info, State) of
        {ok, NewState} when is_record(NewState, beach_stage) ->
            {noreply, NewState};
        Err ->
            ?ERR("handle_info Info:~p error:~p~n", [Info, Err]),
            {noreply, State}
    end.

handle_call(Request, _From, State) ->
    case catch do_handle_call(Request, State) of
        {ok, Reply, NewState} ->
            {reply, Reply, NewState};
        Err ->
            ?ERR("handle_call Request:~p error:~p~n", [Request, Err]),
            {noreply, State}
    end.

do_handle_call(_Request, State) -> {ok, ok, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) -> {ok, State}.

%% ----------------------------- 测试使用 -----------------------------
%% 秘籍
gm_next() ->
    gen_server:cast(?MODULE, {'gm_next'}).

%% 秘籍开始
gm_start() ->
    ActId = 1,
    gen_server:cast(?MODULE, {'act_start', ActId}).

%% 秘籍关闭
gm_stop() ->
    gen_server:cast(?MODULE, {'gm_stop'}).

%% 秘籍托管
gm_start_hosting() ->
    gen_server:cast(?MODULE, {'gm_start_hosting'}).

%% 秘籍结束托管
gm_stop_hosting() ->
    gen_server:cast(?MODULE, {'gm_stop_hosting'}).

%% ----------------------------- 活动和房间相关 -----------------------------------
%% 活动开启
act_start(ActId) ->
    gen_server:cast(?MODULE, {'act_start', ActId}).

%% 活动准备
act_ready(ActId) ->
    gen_server:cast(?MODULE, {'act_ready', ActId}).

%% 活动信息
get_act_info(Node, RoleId) ->
    gen_server:cast(?MODULE, {'get_act_info', Node, RoleId}).

%% 更新场景分线人数
update_room_role_count(RoomId, PoolId) ->
    gen_server:cast(?MODULE, {'update_count', RoomId, PoolId}).

%% 获取魅力值排行榜
get_rank_info(Node, RoleId, Length) ->
    gen_server:cast(?MODULE, {'get_rank_info', Node, RoleId, Length}).

%% 魅力榜更新(已废弃)
insert_rank_list(RoleId, Node, NewMem) ->
    gen_server:cast(?MODULE, {'insert_rank_list', Node, RoleId, NewMem}).

%% 获取玩家(公会/好友)列表
get_member_list(Node, RoleId, Type) ->
    gen_server:cast(?MODULE, {'get_member_list', Node, RoleId, Type}).

%% 创建假人
create_dummy(RoomId) ->
    gen_server:cast(?MODULE, {'create_dummy', RoomId}).

%% 开始托管(本服才会调用)
start_hosting() ->
    gen_server:cast(?MODULE, {'start_hosting'}).

%% 结束托管
stop_hosting() ->
    gen_server:cast(?MODULE, {'stop_hosting'}).

%% 托管记录
add_host_reward_log(RoleId, Reward) ->
    gen_server:cast(?MODULE, {'add_host_reward_log', RoleId, Reward}).

%% ----------------------------- 玩家相关 ------------------------------
%% 进入场景
enter_scene(Node, RoleMsg, FigureMsg) ->
    gen_server:cast(?MODULE, {'enter_scene', Node, RoleMsg, FigureMsg}).

%% 退出场景
quit_scene(Node, RoleId, Scene, PoolId, CopyId, X, Y) ->
    gen_server:cast(?MODULE, {'quit_scene', Node, RoleId, Scene, PoolId, CopyId, X, Y}).

%% 重连
reconnect(Node, RoleMsg, FigureMsg) ->
    gen_server:cast(?MODULE, {'reconnect', Node, RoleMsg, FigureMsg}).

%% 玩家自己的信息
get_self_info(Node, RoleId, AddFreeTimes) ->
    gen_server:cast(?MODULE, {'get_self_info', Node, RoleId, AddFreeTimes}).

%% 购买投票次数
buy_vote_count(Node, RoleId, BuyCount, AddFreeTimes) ->
    gen_server:cast(?MODULE, {'buy_vote_count', Node, RoleId, BuyCount, AddFreeTimes}).

%% 触发经验增加
trigger_addexp(Node, RoleId, VipType, VipLv, SceneId, WorldLv) ->
    gen_server:cast(?MODULE, {'trigger_addexp', Node, RoleId, VipType, VipLv, SceneId, WorldLv}).

%%数据通知累计
click_update_exp(Node, RoleId, Add)->
    gen_server:cast(?MODULE, {'click_update_exp', Node, RoleId, Add}).

%% 玩家数据改变
%% 目前:
%% 1.整个figure替换
player_change_attr(Node, RoleId, AttrList) ->
    gen_server:cast(?MODULE, {'player_change_attr', Node, RoleId, AttrList}).

%% 投票
vote(Node, RoleId, BeVoteId, IsFriend, AddFreeTimes) ->
    gen_server:cast(?MODULE, {'vote', Node, RoleId, BeVoteId, IsFriend, AddFreeTimes}).

%% 获取我的投票界面(已废弃)
get_vote_info(Node, RoleId) ->
    gen_server:cast(?MODULE, {'get_vote_info', Node, RoleId}).

%% 获取我的投票记录
get_vote_record(Node, RoleId) ->
    gen_server:cast(?MODULE, {'get_vote_record', Node, RoleId}).

%% --------------------------------------------------------------------------
%% 划分分区
do_handle_info({'apply_zone'}, #beach_stage{act_type = ActType} = State) ->
    RoomMaps = case ActType of
                   ?ACT_TYPE_KF ->
                       SrvZoneMap = czone:get_svr_zone_map(),
                       %NodeList = clusters_center:get_nodes_info(),
                       lib_charm_beach:calc_zone(SrvZoneMap);
                   ?ACT_TYPE_BF -> %% 本地不区分
                       #{}
               end,
    State1 = State#beach_stage{room_maps = RoomMaps},
    NewState = create_new_room(State1),
	brocast_act_info(NewState),
    {ok, NewState};

%% 定时检查
do_handle_info({'check'}, State) ->
    #beach_stage{stage = PreStage,
        ref = PreRef,
        room_pids = RoomPids,
        starttime = StartTime,
        endtime = EndTime} = State,
    {Stage, NextCheckTime} = lib_charm_beach:get_next_check_time(?TIME_OFFSET, StartTime, EndTime),
    if
        (PreStage == ?BEACH_READY orelse PreStage == ?BEACH_CLOSE) andalso Stage == ?BEACH_OPEN -> %% 活动开启
            NewState = clean_beach_stage(State), %% 清空上次活动的数据
            lib_activitycalen_api:success_start_activity(?MOD_BEACH),
            NewRef = util:send_after(PreRef, (NextCheckTime + 1) * 1000, self(), {'check'}), %% 定时执行状态检测
            NewState1 = NewState#beach_stage{stage = Stage, ref = NewRef},
            %% 广播、发送送礼提示
            brocast_act_info(NewState1),
            % delay_send_gift_tv(1),
            {ok, NewState1};

        PreStage == ?BEACH_OPEN andalso Stage == ?BEACH_REWARD -> %% 执行结算
            %% 结束托管
            stop_hosting(),
            case config:get_cls_type() of
                ?NODE_CENTER ->
                    [mod_charm_beach_room:update_ta_data([RoomId])||RoomId<-maps:values(RoomPids)];
                _ ->
                    ignore
            end,
            [mod_charm_beach_room:act_end([RoomId])||RoomId<-maps:values(RoomPids)],
            NewRef = util:send_after(PreRef, (NextCheckTime + 1) * 1000, self(), {'check'}), %% 下次更新状态
            {ok, State#beach_stage{stage = Stage, ref = NewRef}};

        Stage == ?BEACH_CLOSE -> %% 执行结束
            if
                RoomPids == #{} ->
                    NewState = clean_beach_stage(State), %% 清空数据
                    %% 结束托管
                    stop_hosting(),
                    lib_activitycalen_api:success_end_activity(?MOD_BEACH);
                true ->
                    NewState = clean_beach_stage(State),
                    %% 结束托管
                    stop_hosting(),
                    lib_activitycalen_api:success_end_activity(?MOD_BEACH),
                    [mod_charm_beach_room:room_close([RoomId])||RoomId<-maps:values(RoomPids)]
            end,
            {ok, NewState#beach_stage{stage = Stage}};
        true -> %% 容错处理
            ?ERR("Charm Beach Check {PreStage, Stage}:{~p, ~p}~n{StarTime, EndTime}:{~p, ~p}~n", [PreStage, Stage, StartTime, EndTime]),
            if
                RoomPids == #{} ->
                    NewState = clean_beach_stage(State), %% 清空数据
                    lib_activitycalen_api:success_end_activity(?MOD_BEACH);
                true ->
                    NewState = clean_beach_stage(State),
                    lib_activitycalen_api:success_end_activity(?MOD_BEACH),
                    [mod_charm_beach_room:room_close([RoomId])||RoomId<-maps:values(RoomPids)]
            end,
            {ok, NewState#beach_stage{stage = ?BEACH_CLOSE}}
    end;

do_handle_info({'check_dummy'}, State) ->
    #beach_stage{stage = Stage,
        ref1 = PreRef,
        room_pids = RoomMap,
        starttime = StartTime,
        endtime = EndTime
        } = State,
    NextCheckTime = lib_charm_beach:get_next_solo_time(0, StartTime, EndTime),
    if
        Stage == ?BEACH_OPEN -> %% 活动开启
	        %% 如果没有假人,则创建一个新的假人
			RoomPids = maps:values(RoomMap),
			F = fun(Pid) when is_pid(Pid) ->
				% mod_charm_beach_room:check_solo([Pid]),
				mod_charm_beach_room:create_dummy(Pid);
				(_) -> skip
			    end,
			lists:foreach(F, RoomPids),
			NewRef = util:send_after(PreRef, NextCheckTime * 1000, self(), {'check_dummy'}),
			{ok, State#beach_stage{ref1 = NewRef}};
        true ->
            {ok, State}
    end;

%% 结束托管
do_handle_info({'stop_hosting'}, State) ->
    #beach_stage{host_ref = HostRef} = State,
    case config:get_cls_type() of
        ?NODE_CENTER ->
            ignore;
        _ ->
            % HostPid = State#beach_stage.host_pid,
            % misc:is_process_alive(HostPid) andalso erlang:send(kill, HostPid),
            util:cancel_timer(HostRef),
            offline_hosting_charm_beach:act_end()
    end,
    {ok, State#beach_stage{host_pid = [], host_ref = []}};

do_handle_info(_Info, State) -> {ok, State}.

%% ------------------------------------------------------------------------
do_handle_cast({'gm_next'}, State) ->
    #beach_stage{stage = PreStage, starttime = StartTime, endtime = EndTime} = State,
    STime = utime:unixtime(),
    if
        PreStage =:= ?BEACH_READY orelse PreStage =:= ?BEACH_CLOSE ->
            TimeOffset = ?TIME_OFFSET,
            ActType =
            case config:get_cls_type() of
                ?NODE_CENTER ->
                    ?ACT_TYPE_KF;
                _ ->
                    ?ACT_TYPE_BF
            end,
            ActSceneId = ?SCENE_ID(ActType),
            {Stage, NextCheckTime} = lib_charm_beach:get_next_check_time(TimeOffset, STime, STime+600),
            NextSoloTime = lib_charm_beach:get_next_solo_time(TimeOffset, STime, STime+600),
            Ref  = util:send_after([], (NextCheckTime + 1) * 1000, self(), {'check'}),                %% 计时器更新活动状态
            Ref1 = case NextSoloTime > 0 of
                       true ->
                           util:send_after([], NextSoloTime * 1000, self(), {'check_dummy'});         %% 计时器更新舞台玩家,10s检测一次各分线
                       _ -> []
                   end,
            BeachStage = #beach_stage{
                stage = Stage,
                ref = Ref,
                ref1 = Ref1,
                starttime = STime,
                endtime = STime+600,
                act_type = ActType,
                scene = ActSceneId},
            NewState =
            case ActType of
                ?ACT_TYPE_KF ->
                    util:send_after([], 1000, self(), {'apply_zone'}),
                    BeachStage;
                _ ->
                    %% 只分一个房间管理
                    {ok, Pid} = mod_charm_beach_room:start_link([Stage, ActType, ActSceneId, STime, STime+600, TimeOffset]),
                    NewRoomPids = maps:put(1, Pid, #{}),
                    mod_charm_beach_room:sync_room_state(Pid, 1),
	                brocast_act_info(BeachStage),
                    BeachStage#beach_stage{room_pids = NewRoomPids}
            end;
        true ->
            TimeOffset = ?TIME_OFFSET,
            {_, NextCheckTime} = lib_charm_beach:get_next_check_time(TimeOffset, StartTime, EndTime),
            put(gm_time_offset, TimeOffset + NextCheckTime),
            NewState = State
    end,
    {ok, NewState};

%% 开始托管
do_handle_cast({'start_hosting'}, State) ->
    case config:get_cls_type() of
        ?NODE_CENTER ->
            HostPid = [], HostRef = [];
        _ ->
            %% 本服拉人托管,托管玩家根据具体情况进入本服/跨服场景
            HostPid = offline_hosting_charm_beach:charm_beach_hosting(),
            StartTime = utime:unixtime(),
            EndTime   = StartTime + 20*60,  %% 正式活动时间20分钟
            {_, NextCheckTime} = lib_charm_beach:get_next_check_time(0, StartTime, EndTime),
            HostRef = util:send_after([], NextCheckTime*1000, self(), {'stop_hosting'})
    end,
    {ok, State#beach_stage{host_pid = HostPid, host_ref = HostRef}};

%% 奖励日志
do_handle_cast({'add_host_reward_log', RoleId, Reward}, State) ->
    case config:get_cls_type() of
        ?NODE_CENTER ->
            ignore;
        _ ->
            %% 将奖励记录日志
            offline_hosting_charm_beach:add_log(RoleId, Reward),
            ok
    end,
    {ok, State};

do_handle_cast({'gm_start_hosting'}, State) ->
    case config:get_cls_type() of
        ?NODE_CENTER ->
            HostPid = [], HostRef = [];
        _ ->
            %% 本服拉人托管,托管玩家根据具体情况进入本服/跨服场景
            HostPid = offline_hosting_charm_beach:charm_beach_hosting(),
            StartTime = utime:unixtime(),
            EndTime   = StartTime + 10*60,  %% GM活动时间10分钟
            {_, NextCheckTime} = lib_charm_beach:get_next_check_time(0, StartTime, EndTime),
            HostRef = util:send_after([], NextCheckTime*1000, self(), {'stop_hosting'})
    end,
    {ok, State#beach_stage{host_pid = HostPid, host_ref = HostRef}};

%% 结束托管
do_handle_cast({'gm_stop_hosting'}, State) ->
    #beach_stage{host_ref = HostRef} = State,
    case config:get_cls_type() of
        ?NODE_CENTER ->
            ignore;
        _ ->
            % HostPid = State#beach_stage.host_pid,
            % misc:is_process_alive(HostPid) andalso erlang:send(kill, HostPid),
            util:cancel_timer(HostRef),
            offline_hosting_charm_beach:act_end()
    end,
    {ok, State#beach_stage{host_pid = [], host_ref = []}};

%% 秘籍关闭
do_handle_cast({'gm_stop'}, State) ->
    if
        State#beach_stage.room_pids == #{} ->
            NewRef = [],
            NewState = clean_beach_stage(State); %% 清空数据
        true ->
            case config:get_cls_type() of
                ?NODE_CENTER ->
                    [mod_charm_beach_room:update_ta_data([RoomId])||RoomId<-maps:values(State#beach_stage.room_pids)];
                _ ->
                    ignore
            end,
            [mod_charm_beach_room:act_end([RoomId])||RoomId<-maps:values(State#beach_stage.room_pids)],
            NewRef = util:send_after(State#beach_stage.ref, 11 * 1000, self(), {'check'}),
            NewState = State
            % spawn(fun() -> timer:sleep(11000), [mod_charm_beach_room:room_close([RoomId])||RoomId<-maps:values(State#beach_stage.room_pids)] end)
    end,
    {ok, NewState#beach_stage{stage = ?BEACH_REWARD, ref = NewRef}};

%% 活动开启
do_handle_cast({'act_start', ActId}, State) ->
    if
        State#beach_stage.stage =/= ?BEACH_READY andalso State#beach_stage.stage =/= ?BEACH_CLOSE  ->
            NewState = State;
        true ->
            NewState = init_act(ActId)
    end,
    {ok, NewState};

do_handle_cast({'act_ready', Time}, #beach_stage{stage = ?BEACH_CLOSE} = State) ->
    StartTime = utime:unixtime() + Time,
    NewState = State#beach_stage{stage = ?BEACH_READY, starttime = StartTime},
    {ok, NewState};

%% 排行榜排序
do_handle_cast({'insert_rank_list', Node, RoleId, NewMem}, State) ->
    distribute(State, Node, RoleId, insert_rank_list, [NewMem]),
    {ok, State};

%% 获取玩家列表
do_handle_cast({'get_member_list', Node, RoleId, Type}, State) ->
    distribute(State, Node, RoleId, get_member_list, [Type]),
    {ok, State};

%% 获取活动信息
do_handle_cast({'get_act_info', Node, RoleId}, State) ->
    #beach_stage{stage = PreStage, starttime = StartTime, endtime = EndTime} = State,
    if
        PreStage =:= ?BEACH_CLOSE ->
            res_client(Node, 41801, RoleId, [PreStage, 0, 0]);
        PreStage =:= ?BEACH_READY ->
            res_client(Node, 41801, RoleId, [PreStage, StartTime, 0]);
        true ->
            RwTime = data_charm_beach:get_cfg(reward_to_over_time),
            res_client(Node, 41801, RoleId, [PreStage, StartTime, EndTime - StartTime - RwTime])
    end,
    {ok, State};

%% 更新场景分线数据(只更新RoomLines分线人数,每次调用会减一)
do_handle_cast({'update_count', RoomId, PoolId}, State) ->
    #beach_stage{room_lines = RoomLines} = State,
    NewPools = case maps:get(RoomId, RoomLines, []) of
                   [_|_] = Pools ->
                       case lists:keyfind(PoolId, 1, Pools) of
                           {_, Count} ->
                               lists:keystore(PoolId, 1, Pools, {PoolId, Count - 1});
                           _ ->
                               Pools
                       end;
                   TPools ->
                       TPools
               end,
    NewRoomLines = maps:put(RoomId, NewPools, RoomLines),
    {ok, State#beach_stage{room_lines = NewRoomLines}};

%% 创建假人
do_handle_cast({'create_dummy', RoomId}, State) ->
    #beach_stage{room_pids = RoomPids} = State,
    case maps:get(RoomId, RoomPids, false) of
        RoomPid when is_pid(RoomPid) ->
            mod_charm_beach_room:create_dummy(RoomPid);
        _-> skip
    end,
    {ok, State};

%% 获取个人信息
do_handle_cast({'get_self_info', Node, RoleId, AddFreeTimes}, State) ->
    distribute(State, Node, RoleId, get_self_info, [AddFreeTimes]),
    {ok, State};

%% 获取排行榜
do_handle_cast({'get_rank_info', Node, RoleId, Length}, State) ->
    distribute(State, Node, RoleId, get_rank_info, [Length]),
    {ok, State};

%% 进入场景
do_handle_cast({'enter_scene', Node, RoleMsg, FigureMsg}, State) ->
    #beach_stage{
        act_type = ActType,
        scene = ActSceneId,
        stage = PreStage,
        members = PreMembers,
        room_pids = RoomPids,
        room_maps = RoomMaps,
        room_lines = RoomLines,
        cur_line = CurLineM
    } = State,
    {RoleId, _RolePid, ServerId, ServerNum, CurScene, IsHosting} = RoleMsg,
    if
        CurScene == ActSceneId ->
            res_client(Node, 41802, RoleId, [?ERRCODE(err418_has_in_scene)]),
            {ok, State};
        true ->
            if
                PreStage == ?BEACH_OPEN ->
                    case maps:find(RoleId, PreMembers) of
                        {ok, Room} ->  %% 曾经进入过温泉场景
                            CurLine = maps:get(Room, CurLineM, ?ROOM_POOL(Room)),
                            {AllocLine, NewRoomLine, NewRoomLines} = lib_charm_beach:select_line_from_alloc_room(Room, CurLine, RoomLines, ActSceneId),
                            NewState = State#beach_stage{
                                cur_line = maps:put(Room, NewRoomLine, CurLineM),
                                room_lines = NewRoomLines
                            },
                            NewRoleMsg = {RoleId, ServerId, ServerNum, AllocLine, IsHosting},
                            % distribute(State, Node, RoleId, enter_scene, [NewRoleMsg, FigureMsg]),
                            RoomId = maps:get(Room, RoomPids, false),
                            mod_charm_beach_room:enter_scene([RoomId, Node, NewRoleMsg, FigureMsg]),
                            unode:apply(Node, lib_chat, send_TV, [{player, RoleId}, ?MOD_BEACH, 4, []]),
                            {ok, NewState};
                        _ ->  %% 首次进入
                            CurLine = lib_charm_beach:get_current_line(ServerId, RoomMaps, CurLineM),
                            case lib_charm_beach:get_room(ActType, ServerId, Node, CurLine, RoomLines, RoomMaps, ActSceneId) of
                                false ->
                                    res_client(Node, 41802, RoleId, [?FAIL]),
                                    {ok, State};
                                {AllocRoom, AllocLine, NewCurLine, NewRoomLines, NewRoomMaps} ->
                                    NewMembers = maps:put(RoleId, AllocRoom, PreMembers),
                                    NewState = State#beach_stage{
                                        members   = NewMembers,
                                        room_lines   = NewRoomLines,
                                        room_maps    = NewRoomMaps,
                                        cur_line     = maps:put(AllocRoom, NewCurLine, CurLineM)
                                    },
                                    NewRoleMsg = {RoleId, ServerId, ServerNum, AllocLine, IsHosting},
                                    % distribute(State, Node, RoleId, enter_scene, [NewRoleMsg, FigureMsg]),
                                    RoomId = maps:get(AllocRoom, RoomPids, false),
                                    mod_charm_beach_room:enter_scene([RoomId, Node, NewRoleMsg, FigureMsg]),
                                    unode:apply(Node, lib_chat, send_TV, [{player, RoleId}, ?MOD_BEACH, 4, []]),
                                    %% 触发成就
                                    unode:apply(Node, lib_achievement_api, charm_beach_event, [RoleId, 1]),
                                    unode:apply(Node, lib_activitycalen_api,role_success_end_activity,[RoleId,?MOD_BEACH,0]),
                                    unode:apply(Node, lib_activity_reserve, enter_act, [RoleId, ?MOD_BEACH]),
                                    {ok, NewState}
                            end
                    end;
                true -> %% 未开放
                    res_client(Node, 41800, RoleId, [?ERRCODE(err418_not_open)]),
                    {ok, State}
            end
    end;

%% 退出场景
do_handle_cast({'quit_scene', Node, RoleId, Scene, PoolId, CopyId, X, Y}, State) ->
    #beach_stage{members = Members, room_pids = RoomPids} = State,
    IsInCharmBeach = lists:member(Scene, ?BEACH_SCENE_ID),
    Args = [RoleId, 0, 0, 0, []],
    case maps:get(RoleId, Members, false) of
        false -> %% 不在房间，容错一下
            ?IF(IsInCharmBeach, unode:apply(Node, lib_scene, change_player_scene, Args), skip);
        RoomId ->
            case maps:get(RoomId, RoomPids, false) of
                RoomPid when is_pid(RoomPid) ->
                    mod_charm_beach_room:quit_scene([RoomPid, Node, RoleId, Scene, PoolId, CopyId, X, Y]);
                _ ->
                    unode:apply(Node, lib_scene, change_player_scene, Args)
            end
    end,
    {ok, State};

%% 重连
%% 对于分线满了的情况需要重新分配新的分线 %% TODO:目前玩家离线(玩家进程还在),场景分线玩家数量没有-1,此处理应不+1
do_handle_cast({'reconnect', Node, RoleMsg, FigureMsg}, State) ->
    #beach_stage{stage = Stage, members = Members, scene = _ActSceneId} = State,
    {RoleId, _RolePid, _ServerId, _ServerNum, _CurScene, _IsHosting} = RoleMsg,
    if
        Stage == ?BEACH_OPEN ->
            case maps:get(RoleId, Members, false) of
                false ->
                    change_default_scene(RoleId, Node),
                    {ok, State};
                _RoomId ->
	                do_handle_cast({'enter_scene', Node, RoleMsg, FigureMsg}, State)
                    % case maps:get(RoomId, RoomPids, []) of
					%	RoomPid when is_pid(RoomPid) ->
					%		distribute(State, Node, RoleId, reconnect, [X, Y]);
                    %        % Args = [RoleId, ActSceneId, PoolId, RoomId, X, Y, []],
                    %        % unode:apply(Node, lib_scene, change_player_scene, Args);
                    %    _ ->
                    %        change_default_scene(RoleId, Node)
                    % end
            end;
        true ->
            change_default_scene(RoleId, Node),
            {ok, State}
    end;

%% 购买投票次数
do_handle_cast({'buy_vote_count', Node, RoleId, BuyCount, AddFreeTimes}, State) ->
    distribute(State, Node, RoleId, buy_vote_count, [BuyCount, AddFreeTimes]),
    {ok, State};

%% 触发加经验（由客户端做计时器，定时发送协议领取经验）
do_handle_cast({'trigger_addexp', Node, RoleId, VipType, VipLv, _SceneId, WorldLv}, State) ->
    %% 只在活动时间内增加经验,其他时间过滤协议
    if
        State#beach_stage.stage == ?BEACH_OPEN ->
            distribute(State, Node, RoleId, trigger_addexp, [VipType, VipLv, WorldLv]);
        true ->
            skip
    end,
    {ok, State};

do_handle_cast({'click_update_exp', Node, RoleId, Add}, State) ->
    %% 只在活动时间内增加经验,其他时间过滤协议
    if
        State#beach_stage.stage == ?BEACH_OPEN ->
            distribute(State, Node, RoleId, click_update_exp, [Add]);
        true ->
            skip
    end,
    {ok, State};

%% 玩家属性改变回调
do_handle_cast({'player_change_attr', Node, RoleId, AttrList}, State) ->
    distribute(State, Node, RoleId, player_change_attr, [AttrList]),
    {ok, State};

%% 投票
do_handle_cast({'vote', Node, RoleId, BeVoteId, IsFriend, AddFreeTimes}, State) ->
    %% 只在活动时间内增加经验,其他时间过滤协议
    if
        State#beach_stage.stage == ?BEACH_OPEN ->
            distribute(State, Node, RoleId, vote, [BeVoteId, IsFriend, AddFreeTimes]);
        true ->
            skip
    end,
    {ok, State};

%% 获取投票界面
do_handle_cast({'get_vote_info', Node, RoleId}, State) ->
    distribute(State, Node, RoleId, get_vote_info, []),
    {ok, State};

%% 获取被投票记录
do_handle_cast({'get_vote_record', Node, RoleId}, State) ->
    distribute(State, Node, RoleId, get_vote_record, []),
    {ok, State};

do_handle_cast(_, State) ->
    {ok, State}.

%% ------------------- builtin function --------------------------
res_client(Node, ProCode, RoleId, Args) ->
    {ok, BinData} = pt_418:write(ProCode, Args),
    unode:apply(Node, lib_server_send, send_to_uid, [RoleId, BinData]).

%% 活动初始化
init_act(ActId) ->
    Act = data_activitycalen:get_ac(?MOD_BEACH, 0, ActId),
    #base_ac{time_region = TimeRegion} = Act,
    {NowDay, {NowH,NowM,_}} = calendar:local_time(),
    Now = NowH * 60 + NowM,
    % ZeroAclock = utime:unixdate(),
    ActType
    = case config:get_cls_type() of
        ?NODE_CENTER ->
            ?ACT_TYPE_KF;
        _ ->
            ?ACT_TYPE_BF
    end,
    TimeOffset = ?TIME_OFFSET,
    ActSceneId = ?SCENE_ID(ActType),
    case ulists:find(fun
        ({{SH, SM}, {EH, EM}}) ->
            SH * 60 + SM =< Now andalso Now =< EH * 60 + EM
    end, TimeRegion) of
        {ok, {{SH, SM}, {EH, EM}}} -> %% 处于活动开始时间
	        StartTime = utime:unixtime({NowDay, {SH, SM, 0}}),
	        EndTime   = utime:unixtime({NowDay, {EH, EM, 0}}),
            lib_activitycalen_api:success_start_activity(?MOD_BEACH), %% 活动开启，图标显示，记录日志
            {Stage, NextCheckTime} = lib_charm_beach:get_next_check_time(TimeOffset, StartTime, EndTime),
	        NextSoloTime = lib_charm_beach:get_next_solo_time(TimeOffset, StartTime, EndTime),
            Ref  = util:send_after([], (NextCheckTime + 1) * 1000, self(), {'check'}),                %% 计时器更新活动状态
            Ref1 = case NextSoloTime > 0 of
		        true ->
		            util:send_after([], NextSoloTime * 1000, self(), {'check_dummy'});          %% 计时器更新舞台玩家,10s检测一次各分线
				_ -> []
            end,
            BeachStage = #beach_stage{
                stage = Stage,
                ref = Ref,
                ref1 = Ref1,
                starttime = StartTime,
                endtime = EndTime,
                members = #{},
                act_type = ActType,
                scene = ActSceneId},
            case ActType of
                ?ACT_TYPE_KF ->
                    util:send_after([], 500, self(), {'apply_zone'}),
                    BeachStage;
                _ ->
                    %% 只分一个房间管理
                    {ok, Pid} = mod_charm_beach_room:start_link([Stage, ActType, ActSceneId, StartTime, EndTime, TimeOffset]),
                    NewRoomPids = maps:put(1, Pid, #{}),
                    mod_charm_beach_room:sync_room_state(Pid, 1),
	                brocast_act_info(BeachStage),
                    BeachStage#beach_stage{room_pids = NewRoomPids}
            end;
        _ ->
            #beach_stage{act_type = ActType, scene = ActSceneId}
    end.

%% 清空活动状态
clean_beach_stage(State) ->
    #beach_stage{
        ref = PreRef,
        ref1 = PreRef1
    } = State,
    util:cancel_timer(PreRef),
    util:cancel_timer(PreRef1),
    put(gm_time_offset, 0),
    #beach_stage{}.

brocast_act_info(#beach_stage{act_type = ?ACT_TYPE_BF} = State) ->
    case lib_charm_beach:get_act_cls_type() of
        ?ACT_TYPE_KF ->
            ignore;
        _ ->
            lib_chat:send_TV({all}, ?MOD_BEACH, 2, []),
            #beach_stage{stage = PreStage, starttime = StartTime, endtime = EndTime} = State,
            RwTime = data_charm_beach:get_cfg(reward_to_over_time),
            ConfigLv = data_module:get_module_open_lv(?MOD_BEACH),
            {ok, BinData} = pt_418:write(41801, [PreStage, StartTime, EndTime - StartTime - RwTime]),
            lib_server_send:send_to_all(all_lv, {ConfigLv, 65535}, BinData)
    end;

brocast_act_info(#beach_stage{act_type = ?ACT_TYPE_KF} = State) ->
    #beach_stage{stage = PreStage, starttime = StartTime, endtime = EndTime} = State,
    RwTime = data_charm_beach:get_cfg(reward_to_over_time),
    {ok, BinData} = pt_418:write(41801, [PreStage, StartTime, EndTime - StartTime - RwTime]),
    ConfigLv = data_module:get_module_open_lv(?MOD_BEACH),
    case config:get_cls_type() of
        ?NODE_CENTER ->
            MFA1 = {?MODULE, broadcast_act_local, [BinData]},
            cross_zone:apply_cast(czone, {cross_zone, proc_apply_to_zone, [all, MFA1]});
        _ ->
            lib_chat:send_TV({all}, ?MOD_BEACH, 2, []),
            lib_server_send:send_to_all(all_lv, {ConfigLv, 65535}, BinData)
    end;

brocast_act_info(_) -> ok.

broadcast_act_local(BinData) ->
    case lib_charm_beach:get_act_cls_type() of
        ?ACT_TYPE_KF ->
            ConfigLv = data_module:get_module_open_lv(?MOD_BEACH),
            lib_chat:send_TV({all}, ?MOD_BEACH, 2, []),
            lib_server_send:send_to_all(all_lv, {ConfigLv, 65535}, BinData);
        _ ->
            ignore
    end.

delay_send_gift_tv(Count) ->
    case data_charm_beach:get_cfg(send_gift_times) of
        {TvTime, CountLimit} when Count =< CountLimit ->
            erlang:send_after(TvTime * 1000, self(), {send_gift_tv, Count + 1});
        _ ->
            ok
    end.

get_empty_room([]) -> false;%% 没有空房子了
get_empty_room([#room_state{room_id = RoomId, members_num = Num}|RoomList]) ->
    MaxNum = data_charm_beach:get_cfg(max_people),
    ?IF(Num >= MaxNum, get_empty_room(RoomList), RoomId).

%% 创建房间进程 RoomId = ZoneId
create_new_room(#beach_stage{stage = Stage, act_type = ActType, scene = ActSceneId, starttime = StartTime, endtime = EndTime, room_maps = RoomMaps, room_pids = RoomPids} = State) ->
    RoomList = maps:to_list(RoomMaps),
    RoomIds = [RoomId || {RoomId, _} <- RoomList],
    NewRoomPids = do_create_new_room(Stage, ActType, ActSceneId, StartTime, EndTime, RoomIds, RoomPids),
    NewState = State#beach_stage{room_pids = NewRoomPids},
    NewState.

do_create_new_room(_Stage, _ActType, _ActSceneId, _StartTime, _EndTime, [], RoomPids) -> RoomPids;
do_create_new_room(Stage, ActType, ActSceneId, StartTime, EndTime, [RoomId|RoomIds], RoomPids) ->
    {ok, Pid} = mod_charm_beach_room:start_link([Stage, ActType, ActSceneId, StartTime, EndTime, ?TIME_OFFSET]),
    NewRoomPids = maps:put(RoomId, Pid, RoomPids),
    mod_charm_beach_room:sync_room_state(Pid, RoomId),  %% TODO
    do_create_new_room(Stage, ActType, ActSceneId, StartTime, EndTime, RoomIds, NewRoomPids).

%% 房间人数+1
room_member_plus(RoomList, RoomId) ->
    case lists:keyfind(RoomId, #room_state.room_id, RoomList) of
        #room_state{members_num = MembersNum} = RoomState ->
            NewRoomState = RoomState#room_state{members_num = MembersNum + 1},
            lists:keyreplace(RoomId, #room_state.room_id, RoomList, NewRoomState);
        _ ->
            RoomList
    end.

distribute(State, Node, RoleId, Method, Args) ->
    #beach_stage{members = Members, room_pids = RoomPids} = State,
    case maps:get(RoleId, Members, false) of
        false -> res_client(Node, 41800, RoleId, [?ERRCODE(err418_not_join_act)]);
        RoomId ->
            case maps:get(RoomId, RoomPids, []) of
                RoomPid when is_pid(RoomPid) ->
                    case misc:is_process_alive(RoomPid) of
                        true -> mod_charm_beach_room:Method([RoomPid, Node, RoleId | Args]);
                        _ -> res_client(Node, 41800, RoleId, [?ERRCODE(err418_not_open)])
                    end;
                _ ->
                    res_client(Node, 41800, RoleId, [?ERRCODE(err418_not_join_act)])
            end
    end.

change_default_scene(RoleId, Node) ->
    Args = [RoleId, []],
    unode:apply(Node, lib_scene, player_change_default_scene, Args).