%%-----------------------------------------------------------------------------
%% @Module  :       mod_charm_beach_room
%% @Author  :       Yanwf
%% @Created :       2019-3-21
%% @Description:    魅力海滩房间进程
%%-----------------------------------------------------------------------------
-module(mod_charm_beach_room).

-include ("common.hrl").
-include ("predefine.hrl").
-include ("figure.hrl").
-include ("charm_beach.hrl").
-include ("language.hrl").
-include ("server.hrl").
-include ("def_module.hrl").
-include ("def_fun.hrl").
-include ("errcode.hrl").
-include("def_goods.hrl").
-include("goods.hrl").
-include("attr.hrl").
-include("scene.hrl").
-include("czone.hrl").
-behaviour(gen_server).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-export([
    start_link/1, room_close/1, act_end/1
    ,enter_scene/1, quit_scene/1, reconnect/1, insert_rank_list/1, get_rank_info/1, get_self_info/1
    ,click_update_exp/1 ,trigger_addexp/1, player_change_attr/1, vote/1, buy_vote_count/1, get_vote_info/1, get_vote_record/1
    ,sync_room_state/2, create_dummy/1, check_solo/1, get_member_list/1, after_enter_scene_node/1, after_quit_scene_node/2
    ,node_add_log/1, node_add_vote_log/9, update_ta_data/1, update_ta_data_on_server/1, update_ta_data_on_player/2, update_ta_data_on_player /3
]).

%% 改造原旧项目的房间管理：
%% 目的:分散跨服玩法中整个大区房间进程的压力
%% 一个房间由一个进程管理

%% -------------------------------------------------------
start_link([Stage, ActType, Scene, StartTime, EndTime, OffSetTime]) ->
    gen_server:start_link(?MODULE, [Stage, ActType, Scene, StartTime, EndTime, OffSetTime], []).

%% 初始化
init([Stage, ActType, Scene, StartTime, EndTime, OffSetTime]) ->
	process_flag(trap_exit, true),
    State = #beach_room_stage{
        stage = Stage,
        act_type = ActType,
        scene = Scene,
        starttime = StartTime,
        endtime = EndTime,
        offset_time = OffSetTime
    },
    {ok, State}.


handle_cast(Msg, State) ->
    case catch do_handle_cast(Msg, State) of
        {ok, NewState} ->
            {noreply, NewState};
        {stop, normal, NewState} ->
            {stop, normal, 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_room_stage) ->
            {noreply, NewState};
        Err ->
            ?ERR("handle_info Info:~p error:~p~n", [Info, Err]),
            {noreply, State}
    end.

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


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

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

terminate(_Reason, _State) -> ok.

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

%% -----------------------------------------------------------------------
%% 活动结束
room_close([RoomId]) ->
    gen_server:cast(RoomId, {'room_close'}).

%% 结算
act_end([RoomId]) ->
	gen_server:cast(RoomId, {'act_end'}).

%% -----------------------------------------------------------------------
%% 进入场景
enter_scene([RoomId, Node, RoleMsg, FigureMsg]) ->
    gen_server:cast(RoomId, {'enter_scene', Node, RoleMsg, FigureMsg}).

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

%% 排行榜更新(已废弃)
insert_rank_list([RoomId, Node, RoleId, NewMem]) ->
    gen_server:cast(RoomId, {'insert_rank_list', Node, RoleId, NewMem}).

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

%% 获取个人信息
get_self_info([RoomId, Node, RoleId, AddFreeTimes]) ->
    gen_server:cast(RoomId, {'get_self_info', Node, RoleId, AddFreeTimes}).

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

%% 触发经验增加
click_update_exp([RoomId, Node, RoleId, Add]) ->
    gen_server:cast(RoomId, {'click_update_exp', Node, RoleId, Add}).

%% 重连
reconnect([RoomId, Node, RoleId, X, Y]) ->
    gen_server:cast(RoomId, {'reconnect', Node, RoleId, X, Y}).

%% 玩家数据改变
player_change_attr([RoomId, Node, RoleId, AttrList]) ->
    gen_server:cast(RoomId, {'player_change_attr', Node, RoleId, AttrList}).

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

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

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

%% 获取拉票界面
get_vote_info([RoomId, Node, RoleId]) ->
    gen_server:cast(RoomId, {'get_vote_info', Node, RoleId}).

%% 获取拉票记录
get_vote_record([RoomId, Node, RoleId]) ->
    gen_server:cast(RoomId, {'get_vote_record', Node, RoleId}).

%% 同步房间数据
sync_room_state(RoomPId, RoomId) ->
    gen_server:cast(RoomPId, {'sync_room_state', RoomId, 0}).

%% 创建氛围假人
create_dummy(RoomPid) ->
	gen_server:cast(RoomPid, {'create_dummy'}).

%% 定时更新solo玩家
check_solo([RoomId]) ->
    gen_server:cast(RoomId, {'update_solo'}).

%% TA数据上报
update_ta_data([RoomId]) ->
    gen_server:cast(RoomId, {'update_ta_data'}).

%% -----------------------------------------------------------------------
%% 房间关闭，送玩家出场景
do_handle_cast({'room_close'}, State) ->
	#beach_room_stage{members = Members, room_id = _RoomId, scene = SceneId, pools_id = Pools} = State,
    %% 玩家离场
	Fun = fun(RoleId, #beach_member{scene = Scene, node = Node}) ->
		if
			Scene > 0 ->
				Args = [RoleId, 0, 0, 0, []],
				unode:apply(Node, lib_scene, change_player_scene, Args);
			true -> skip
		end
	end,
	maps:map(Fun, Members),
    %% 清理假人&删除场景进程
    Fun1 = fun(PoolId) ->
        mod_scene_agent:clear_scene(SceneId, PoolId),
        mod_scene_agent:close_scene(SceneId, PoolId)
    end,
    lists:map(Fun1, Pools),
    erase(vote_id),
    % mod_charm_beach:room_close(RoomId), %% 通知管理进程房间关闭
	{stop, normal, #beach_room_stage{}};

%% 活动结束,结算排名和奖励
do_handle_cast({'act_end'}, State) ->
    #beach_room_stage{
        room_id   = RoomId,
        members   = PreMembers,
        rank_list = PreRankList,
        endtime   = EndTime,
	    scene     = SceneId,
	    pools_id  = PoolIds
    } = State,
	%% 发送魅力榜第一名传闻
    {FirstFigure, FirstServerNum} =
    case PreRankList of
        [{RoleId, [_Rank, _CharmValue|_]}|_] ->
            case maps:get(RoleId, PreMembers, false) of
                #beach_member{figure = Figure, server_num = ServerNum} -> skip;
                _ -> Figure = #figure{name = data_charm_beach:get_cfg(dancer_name), sex = 2}, ServerNum = 1
            end,
            case config:get_cls_type() of
                ?NODE_CENTER ->
                    clusters_center:apply_to_all_node(lib_charm_beach, send_end_tv, [{scene, SceneId, PoolIds}, ?MOD_BEACH, 3, [Figure#figure.name]]);
                _ ->
	                lib_charm_beach:send_end_tv({scene, SceneId, PoolIds}, ?MOD_BEACH, 3, [Figure#figure.name])
            end,
            {Figure, ServerNum};
        _ -> Figure = #figure{name = data_charm_beach:get_cfg(dancer_name), sex = 2}, ServerNum = 1,
            {Figure, ServerNum}
    end,
    CalSceneMemFun = fun(TempRoleId, TempMem) ->
        #beach_member{acc_exp = TempAccExp, node = TempNode, scene = MemScene} = TempMem,
         case lists:keyfind(TempRoleId, 1, PreRankList) of
            {_, [TempRank, _TCharmValue]} -> skip;
            _ -> TempRank = 0
         end,
        Reward  = data_charm_beach:get_reward_cfg(TempRank),
        {DesignReward, Reward1} =
        case lists:keyfind(?TYPE_DESIGN, 1, Reward) of
            {_, _GoodsId, _Num} = Design ->
                {[Design], lists:keydelete(?TYPE_DESIGN, 1, Reward)};
            _ ->
                Design = [],
                {Design, Reward}
        end,
        if
            Reward =/= []  -> %% 上榜 发送排名奖励
                Args = [[TempRoleId], ?MOD_BEACH, ?LAN_MSG(?OFF_TITLE), ?LAN_MSG(?OFF_CONTENT), Reward1],
                Produce = #produce{reward = DesignReward, type = charm_beach, show_tips = 1},
                DesignReward =/= [] andalso unode:apply(TempNode, lib_goods_api, send_reward_with_mail, [TempRoleId, Produce, ?MOD_BEACH]),
	            unode:apply(TempNode, mod_charm_beach, add_host_reward_log, [TempRoleId, Reward1]),
                unode:apply(TempNode, lib_mail_api, send_sys_mail, Args);
            true -> skip
        end,
        %% 玩家在场景中则通知排名奖励
        ?IF(MemScene > 0, res_client(TempNode, 41807, TempRoleId, [EndTime, TempRank, TempAccExp, FirstFigure#figure.name, FirstServerNum]), skip),
        TempMem
    end,
    NewSceneMem = maps:map(CalSceneMemFun, PreMembers),
    %% 日志
    add_log(RoomId, PreMembers, PreRankList),
    {ok, State#beach_room_stage{members = NewSceneMem}};

%% 同步房间信息
do_handle_cast({'sync_room_state', RoomId, _PoolId}, State) ->
    NewState = State#beach_room_stage{room_id = RoomId},
    {ok, NewState};

do_handle_cast({'create_dummy'}, State) ->
	#beach_room_stage{scene = Scene, dummy = DummyMap, pools_id = Pools} = State,
	{NonPools, _HasPools} =
		case DummyMap of
			[] -> {Pools, []};
			_ ->
				THasPools = maps:keys(DummyMap),
				TNonPools = [TPool||TPool <- Pools, lists:member(TPool, THasPools) == false],
				{TNonPools, THasPools}
		end,
	F = fun(TPoolId, TMap) ->
        ok = lib_charm_beach:create_dummy(Scene, TPoolId),
        case TMap of
            [] -> maps:put(TPoolId, 1, #{});
            _  -> maps:put(TPoolId, 1, TMap)
        end
    end,
	NewDummyMap = lists:foldl(F, DummyMap, NonPools),
	{ok, State#beach_room_stage{dummy = NewDummyMap}};

%% 更新solo玩家(只做假人形象的更新)
do_handle_cast({'update_solo'}, State) ->
    #beach_room_stage{
        members   = Members,
        rank_list = RankList,
        scene     = Scene,
        pools_id  = Pools,
        dummy     = ObjectIds
    } = State,
    SoloId =
    case RankList of
        [First|_] ->
            {RoleId, [_Rank, _CharmValue]} = First,
            RoleId;
        _ ->
            0
    end,
    %% 广播舞台solo玩家
    case maps:get(SoloId, Members, []) of
        Solo when is_record(Solo, beach_member) ->
            #beach_member{figure = Figure} = Solo,
            %% 有些分线可能是活动开始之后才创建的,所以要分离已经创建和还没创建假人的分线
            {_NonPools, HasPools} =
            case ObjectIds of
                [] -> {Pools, []};
                _ ->
                    THasPools = maps:keys(ObjectIds),
                    TNonPools = [TPool||TPool <- Pools, lists:member(TPool, THasPools) == false],
                    {TNonPools, THasPools}
            end,
            %% 更新第一名形象
            F = fun(PoolId) ->
                TObjectId = maps:get(PoolId, ObjectIds),
                mod_scene_agent:apply_cast(Scene, PoolId, lib_scene_object_agent, update, [TObjectId, [{figure, Figure}]])
            end,
            lists:map(F, HasPools),
            {ok, State};
        _ ->
            {ok, State}
    end;

%% 上报TA数据
do_handle_cast({'update_ta_data'}, State) ->
    #beach_room_stage{
        members   = Members
    } = State,
    F = fun(_, Member, TMap) ->
        case Member of
            #beach_member{node = Node, role_id = RoleId, exp_reward_time = LastTime, is_hosting = IsHosting} ->
                RoleList = maps:get(Node, TMap, []),
                NewRoleList = [{RoleId, LastTime, IsHosting} | lists:keydelete(RoleId, 1, RoleList)],
                maps:put(Node, NewRoleList, TMap);
            _ ->
                TMap
        end end,
    RoleInfoMap = maps:fold(F, #{}, Members),
    ForFun = fun(Node, RoleInfo) ->
        update_ta_data_on_node(Node, RoleInfo)
             end,
    maps:map(ForFun, RoleInfoMap),
    {ok, State};

%% 进入场景
do_handle_cast({'enter_scene', Node, RoleMsg, FigureMsg}, State) ->
	#beach_room_stage{
        members = PreMembers,
        scene = ActSceneId,
        pools_id = Pools,
        rank_list = RankList
    } = State,
    {RoleId, ServerId, ServerNum, AllocLine, IsHosting} = RoleMsg,
    {X, Y} = lib_charm_beach:get_scene_born_xy(ActSceneId),
    Args = [RoleId, ActSceneId, AllocLine, 0, X, Y, false, [{mount, 0}]],
    %% 更新玩家数据和场景进程PoolId
    case maps:find(RoleId, PreMembers) of
        {ok, Mem} when is_record(Mem, beach_member) ->
            NewMem = Mem#beach_member{scene = ActSceneId, node = Node,
                scene_pool = AllocLine, is_hosting = IsHosting},
            NewMembers = maps:update(RoleId, NewMem, PreMembers),
            case lists:member(AllocLine, Pools) of
                true ->
                    NewPools = Pools;
                _ ->
                    NewPools = [AllocLine|Pools]
            end,
            NewState = State#beach_room_stage{members = NewMembers, pools_id = NewPools},
            unode:apply(Node, lib_scene, change_player_scene, Args),
            %% 进入场景后事件触发
            after_enter_scene(Node, RoleId),
            res_client(Node, 41802, RoleId, [?SUCCESS]),
            {ok, NewState};
        _ -> %% 第一次进
            NewMem = #beach_member{
                role_id = RoleId,
                figure = FigureMsg,
                node = Node,
                scene = ActSceneId,
                scene_pool = AllocLine,
                acc_exp = 0,
                server_id = ServerId,
                server_num = ServerNum,
                pre_time = utime:unixtime(),
                is_hosting = IsHosting
            },
            NewMembers = maps:put(RoleId, NewMem, PreMembers),
            case lists:member(AllocLine, Pools) of
                true ->
                    NewPools = Pools;
                _ ->
                    NewPools = [AllocLine|Pools]
            end,
            NewState = State#beach_room_stage{members = NewMembers, pools_id = NewPools},
            unode:apply(Node, lib_scene, change_player_scene, Args),
            %% 进入场景后事件触发
            after_enter_scene(Node, RoleId),
            res_client(Node, 41802, RoleId, [?SUCCESS]),
            {ok, NewState#beach_room_stage{rank_list = lib_charm_beach:insert_rank_list(RankList, NewMem)}}
    end;

%% 退出场景
do_handle_cast({'quit_scene', Node, RoleId, Scene, _PoolId, _CopyId, _X, _Y}, State) ->
    #beach_room_stage{room_id = RoomId, members = Members} = State,
    Args = [RoleId, 0, 0, 0, []],
    case maps:find(RoleId, Members) of
        {ok, #beach_member{scene = SceneId, result = Result, scene_pool = PoolId} = OneMember} when SceneId > 0 ->
            unode:apply(Node, lib_scene, change_player_scene, Args),
            ?IF(Result =/= undefined, res_client(Node, 41817, RoleId, Result), ok),
            %% desc:保留离开的玩家数据,重新进入时投票数据不变
            NewMembers = maps:put(RoleId, OneMember#beach_member{scene = 0, result = undefined}, Members),
            %% 更新分区管理进程人数
            mod_charm_beach:update_room_role_count(RoomId, PoolId),
            res_client(Node, 41803, RoleId, [?SUCCESS]),
            after_quit_scene(OneMember),
            NewState = State#beach_room_stage{members = NewMembers};
        _ ->
            IsInCharmBeach = lists:member(Scene, ?BEACH_SCENE_ID),
            ?IF(IsInCharmBeach, unode:apply(Node, lib_scene, change_player_scene, Args), ok),
            NewState = State
    end,
    {ok, NewState};

%% 获取个人信息
do_handle_cast({'get_self_info', Node, RoleId, AddFreeTimes}, State) ->
    #beach_room_stage{members = Members, rank_list = RankList} = State,
    case maps:find(RoleId, Members) of
        {ok, Mem} when is_record(Mem, beach_member) ->
            #beach_member{acc_exp = AccExp, vote_times = VoteTimes, buy_times = BuyTimes} = Mem,
            Rank = lib_charm_beach:get_rank_by_roleid(RoleId, RankList),
            FreeTimes = data_charm_beach:get_cfg(free_vote_times),
            LeftVoteTimes = FreeTimes + AddFreeTimes + BuyTimes - VoteTimes,
            res_client(Node, 41804, RoleId, [Rank, AccExp, VoteTimes, LeftVoteTimes, BuyTimes]);
        _ ->
            res_client(Node, 41800, RoleId, [?ERRCODE(err418_not_join_act)])
    end,
    {ok, State};

%% 获取排行榜
do_handle_cast({'get_rank_info', Node, RoleId, Length}, State) ->
    #beach_room_stage{members = Members, rank_list = RankList} = State,
    case maps:find(RoleId, Members) of
        {ok, Mem} when is_record(Mem, beach_member) ->
            {TopRank, LeftRank} = lib_charm_beach:rank_format(Members, RoleId, Mem#beach_member.voted_ids, RankList, Length),
            MyRank = lib_charm_beach:rank_format(RoleId, RankList, []),
            case MyRank of
                [{Rank, CharmValue}] -> skip;
                _ -> Rank = 0, CharmValue = 0
            end,
            res_client(Node, 41805, RoleId, [Rank, CharmValue, TopRank, LeftRank]);
        _ ->
            {TopRank, LeftRank} = lib_charm_beach:rank_format(Members, 0, [], RankList, Length),
            MyRank = lib_charm_beach:rank_format(RoleId, RankList, []),
            case MyRank of
                [{Rank, CharmValue}] -> skip;
                _ -> Rank = 0, CharmValue = 0
            end,
            res_client(Node, 41805, RoleId, [Rank, CharmValue, TopRank, LeftRank])
    end,
    {ok, State};

%% 触发加经验（由客户端做计时器，定时发送协议领取经验）
do_handle_cast({'trigger_addexp', Node, RoleId, VipType, VipLv, _WorldLv}, State) ->
    #beach_room_stage{members = Members, scene = ActSceneId, offset_time = OffSetTime} = State,
    case maps:find(RoleId, Members) of
        {ok, #beach_member{scene = SceneId} = Mem} ->
            #beach_member{figure = Figure, pre_time = PreTime, exp_reward_time = ExpRewardTime} = Mem,
            if
                SceneId == ActSceneId ->
                    NowTime = utime:unixtime(),
                    NewPreTime = OffSetTime + NowTime,
                    Interval = NewPreTime - PreTime, %% 两次领取奖励的间隔时间
                    ExpInterval = data_charm_beach:get_cfg(send_exp_time),
                    MaxExpRewardTime = data_charm_beach:get_cfg(max_exp_reward),
                    if
                        Interval >= ExpInterval andalso ExpRewardTime < MaxExpRewardTime ->
                            NewMembers = maps:put(RoleId, Mem#beach_member{pre_time = NewPreTime,
                                figure = Figure#figure{vip_type = VipType, vip = VipLv}, exp_reward_time = ExpRewardTime + ExpInterval}, Members),
                            lib_player:apply_cast(Node, RoleId, ?APPLY_CAST_SAVE, ?NOT_HAND_OFFLINE, lib_charm_beach_mod, add_exp, [0, ?ADD_EXP_TYPE_TRIGGER]),
                            % lib_log_api:log_charm_beach_exp(ServerId, RoleId, RoleName, WorldLv, VipLv, 0, OppServerId, 0, OppName, VipLv, AddExp, NewAccExp, NowTime),
                            {ok, State#beach_room_stage{members = NewMembers}};
                        true ->
                            {ok, State}
                    end;
                true ->
                    res_client(Node, 41806, RoleId, [?ERRCODE(err418_not_join_act), 0]),
                    {ok, State}
            end;
        _ ->
            res_client(Node, 41806, RoleId, [?ERRCODE(err418_not_join_act), 0]),
            {ok, State}
    end;

do_handle_cast({'click_update_exp', _Node, RoleId, Add},State)->
    #beach_room_stage{members = Members} = State,
    case maps:find(RoleId, Members) of
        {ok, #beach_member{acc_exp = AccExp} = Mem} ->
            NewMembers = maps:put(RoleId, Mem#beach_member{acc_exp = AccExp+Add}, Members),
            {ok, State#beach_room_stage{members = NewMembers}};
        _ ->
            {ok, State}
    end;

%% 玩家重连
do_handle_cast({'reconnect', Node, RoleId, X, Y}, State) ->
    #beach_room_stage{stage = Stage, members = Members, scene = ActSceneId} = State,
    if
        Stage == ?BEACH_OPEN ->
            case maps:find(RoleId, Members) of  %% todo: pool_id
                {ok, #beach_member{scene = SceneId}} when SceneId > 0 ->
                    Args = [RoleId, ActSceneId, 0, 0, X, Y, []],
                    unode:apply(Node, lib_scene, change_player_scene, Args);
                _ ->
                    Args = [RoleId, []],
                    unode:apply(Node, lib_scene, player_change_default_scene, Args)
            end,
            {ok, State};
        true ->
            Args = [RoleId, []],
            unode:apply(Node, lib_scene, player_change_default_scene, Args),
            case maps:find(RoleId, Members) of
                {ok, #beach_member{scene = SceneId} = Mb} when SceneId > 0 ->
                    NewMembers = maps:put(RoleId, Mb#beach_member{scene = 0}, Members),
                    {ok, State#beach_room_stage{members = NewMembers}};
                _ ->
                    {ok, State}
            end
    end;

%% 玩家数据改变回调
do_handle_cast({'player_change_attr', _Node, RoleId, AttrList}, State) ->
    #beach_room_stage{members = Members, rank_list = RankList} = State,
    case maps:find(RoleId, Members) of
        {ok, #beach_member{scene = SceneId} = Mem} when SceneId > 0 ->
            NewMem = update_player_attr(Mem, AttrList),
            NewMembers = maps:put(RoleId, NewMem, Members),
            {ok, State#beach_room_stage{rank_list = lib_charm_beach:insert_rank_list(RankList, NewMem), members = NewMembers}};
        _ ->
            {ok, State}
    end;

%% 获取玩家(公会/好友)列表(已废弃)
do_handle_cast({'get_member_list', Node, RoleId, Type}, State) ->
    #beach_room_stage{members = Members} = State,
    case maps:find(RoleId, Members) of
        {ok, #beach_member{figure = Figure, scene_pool = PoolId}} ->
            case Type of
                1 -> %% 好友列表
                    MemberList = [{Id, CharmV} || {Id, #beach_member{scene = S, scene_pool = TPool, charm_value = CharmV}} <- maps:to_list(Members),
                        S > 0 andalso Id =/= RoleId andalso TPool == PoolId],
                    %% 到玩家进程判断是否好友(数据量最多为120条)
                    lib_player:apply_cast(Node, RoleId, ?APPLY_CAST_STATUS, ?NOT_HAND_OFFLINE, lib_charm_beach_mod, get_friend, [MemberList]);
                2 -> %% 公会成员
                    #figure{guild_id = GuildId} = Figure,
                    MemberList = [M || {Id, #beach_member{scene = S, scene_pool = TPool, figure = #figure{guild_id = TGuildId}} = M} <- maps:to_list(Members),
                        S > 0 andalso Id =/= RoleId andalso TPool == PoolId andalso TGuildId == GuildId],
                    FormatList = [{RId, Name, CharmV} || #beach_member{role_id = RId, figure = #figure{name = Name}, charm_value = CharmV} <- MemberList],
                    res_client(Node, 41819, RoleId, [2, FormatList])
            end,
            {ok, State};
        _ ->
            res_client(Node, 41819, RoleId, [?ERRCODE(err418_not_join_act)]),
            {ok, State}
    end;

%% 投票
do_handle_cast({'vote', Node, RoleId, BeVoteId, IsFriend, AddFreeTimes}, State) ->
    CheckList = [{'in_act', RoleId}, {'enough_times', RoleId, AddFreeTimes}, {'self_vote', RoleId, BeVoteId}, {'opp_in_act', BeVoteId}],
    case lib_charm_beach:check(State, CheckList) of
        true ->
            FreeTimes   = data_charm_beach:get_cfg(free_vote_times),
            #beach_room_stage{members = Members, rank_list = RankList, scene = SceneId, room_id = RoomId} = State,
            BeVoteMember = maps:get(BeVoteId, Members),
            #beach_member{vote_role = VoteRoleIds, figure = BeVoteFigure, server_id = BeVoteServerId, node = BeVoteNode} = BeVoteMember,
            case lists:keyfind(RoleId, 1, VoteRoleIds) of
                {_, VoteCount, _, _} -> skip;
                _ -> VoteCount = 0
            end,
            RoleMember = maps:get(RoleId, Members),
            #beach_member{vote_times = VoteTimes, voted_ids = VotedIds, buy_times = BuyTimes, figure = Figure,
                acc_exp = AccExp, server_id = ServerId, server_num = ServerNum, scene_pool = PoolId} = RoleMember,
            NewRoleMember = RoleMember#beach_member{vote_times = VoteTimes + 1, voted_ids = [BeVoteId|lists:delete(BeVoteId,VotedIds)]},
            NewVoteMember = BeVoteMember#beach_member{vote_role = [{RoleId, VoteCount+1, ServerNum, IsFriend}|lists:keydelete(RoleId, 1, VoteRoleIds)],
                charm_value = BeVoteMember#beach_member.charm_value + ?ONE_VOTE_CHARM},
            NewRankList = lib_charm_beach:insert_rank_list(RankList, NewVoteMember),

            NewMembers = Members#{RoleId => NewRoleMember, BeVoteId => NewVoteMember},
            if
                VoteTimes < (FreeTimes + BuyTimes + AddFreeTimes) ->
                    %% 增加经验
                    AddExp = lib_charm_beach:get_vote_add_exp(?MOD_BEACH, 1, Figure#figure.lv),
                    lib_player:apply_cast(Node, RoleId, ?APPLY_CAST_SAVE, ?NOT_HAND_OFFLINE, lib_charm_beach_mod, add_exp, [AddExp, ?ADD_EXP_TYPE_VOTE]),
                    AddExpCount = data_charm_beach:get_cfg(buy_vote_times),
                    case VoteTimes >= AddExpCount of
                        true ->
                            NewMembers1 = NewMembers;
                        _ ->
                            NewMembers1 = NewMembers#{RoleId => NewRoleMember#beach_member{acc_exp = AccExp + AddExp}}
                    end,
                    % NewVoteList = lists:keystore({RoleId, BeVoteId}, #rec_vote.key, VoteList, NewRecVote),
                    NewState = State#beach_room_stage{members = NewMembers1, rank_list = NewRankList},
                    %% 推送次数和增加经验值
                    {ok, BinData0} = pt_418:write(41808, [?SUCCESS, VoteTimes, (FreeTimes + BuyTimes + AddFreeTimes) - (VoteTimes + 1)]),
                    case VoteTimes < AddExpCount of
                        true ->
                            {ok, BinData1} = pt_418:write(41806, [?SUCCESS, AddExp]),
                            BinData = <<BinData0/binary, BinData1/binary>>;
                        _ ->
                            BinData = BinData0
                    end,
                    res_client(Node, RoleId, BinData),
	                %% 传闻
	                unode:apply(Node, lib_chat, send_TV, [{player, RoleId}, ?MOD_BEACH, 5, [BeVoteFigure#figure.name, BeVoteId]]),
	                unode:apply(BeVoteNode, lib_chat, send_TV, [{player, BeVoteId}, ?MOD_BEACH, 6, [Figure#figure.name, RoleId]]),
                    %% 日志
                    LeftFreeTimes = max(0, FreeTimes + AddFreeTimes - VoteTimes - 1),
                    VoteTimes < AddExpCount andalso add_vote_log(Node, RoomId, PoolId, ServerId, ServerNum, RoleId,
                        BeVoteId, BeVoteFigure#figure.name, LeftFreeTimes, AddExp),
                    %% 场景推送
                    {ok, SBinData} = pt_418:write(41813, [Figure, BeVoteFigure#figure.name, ServerId, BeVoteServerId]),
                    lib_server_send:send_to_scene(SceneId, RoleMember#beach_member.scene_pool, SBinData),

                    %% 给被投票者推送红点
                    res_client(Node, 41812, BeVoteId, []);
                true -> %% 次数不够了
                    NewState = State,
                    res_client(Node, 41808, RoleId, [?ERRCODE(err418_not_vote_times), VoteTimes, 0])
            end;
            % lib_log_api:log_charm_beach(ServerId, RoleId, ?LOGTYPE_VOTE, [], 1, VoteServerId, VoteRoleId, VoteName, 0, 0 + ?ONE_VOTE_CHARM),
        {false, ErrCode} ->
            NewState = State,
            res_client(Node, 41808, RoleId, [ErrCode, 0, 0])
    end,
    {ok, NewState};

%% 购买投票次数
do_handle_cast({'buy_vote_count', Node, RoleId, BuyCount, AddFreeTimes}, State) ->
    {ok, NewState} = lib_charm_beach_mod:buy_vote_count(Node, RoleId, BuyCount, AddFreeTimes, State),
    {ok, NewState};

%% 获取我的投票界面
do_handle_cast({'get_vote_info', Node, RoleId}, State) ->
    lib_charm_beach_mod:get_vote_info(Node, RoleId, State),
    {ok, State};

%% 获取我的被投票记录
do_handle_cast({'get_vote_record', Node, RoleId}, State) ->
    lib_charm_beach_mod:get_vote_record(Node, RoleId, State),
    {ok, State};

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

%% -------------- Builtin Function -----------------------
after_enter_scene(Node, RoleId) ->
    unode:apply(Node, ?MODULE, after_enter_scene_node, [RoleId]),
    ok.

after_enter_scene_node(RoleId) ->
    lib_child_task:async_event(RoleId, lib_child_task, finish_charm_beach, []),
    lib_role_legion:trigger_task(RoleId, ?MOD_BEACH, 0),
    ok.

after_quit_scene(Member) ->
    #beach_member{node = Node, role_id = RoleId, exp_reward_time = LastTime} = Member,
    unode:apply(Node, ?MODULE, after_quit_scene_node, [RoleId, LastTime]),
    ok.

after_quit_scene_node(RoleId, LastTime) ->
    lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, update_ta_data_on_player, [LastTime]),
    ok.

update_ta_data_on_node(Node, RoleInfoList) ->  %% todo：数据量太大？
    unode:apply(Node, ?MODULE, update_ta_data_on_server, [RoleInfoList]),
    ok.

update_ta_data_on_server(RoleInfoList) ->
    F = fun(RoleInfo) ->
        case RoleInfo of
            {RoleId, LastTime, IsHosting} ->
                lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, update_ta_data_on_player, [LastTime, IsHosting]);
            _ ->
                ignore
        end end,
    lists:map(F, RoleInfoList),
    ok.

update_ta_data_on_player(Player, UseTime, IsHosting) ->
    IsCountDown = lib_activitycalen_api:get_enter_activity_record(?MOD_BEACH, Player#player_status.id),
    BoolHosting = case IsHosting of
                      1 -> true;
                      _ -> false
                  end,
    ta_agent_fire:activity_beach(Player, [BoolHosting, UseTime, IsCountDown]),
    {ok, Player}.

update_ta_data_on_player(Player, UseTime) ->
    IsHosting   = offline_hosting_api:is_offline_hosting(Player),
    IsCountDown = lib_activitycalen_api:get_enter_activity_record(?MOD_BEACH, Player#player_status.id),
    ta_agent_fire:activity_beach(Player, [IsHosting, UseTime, IsCountDown]),
    {ok, Player}.

res_client(Node, RoleId, BinData) ->
    unode:apply(Node, lib_server_send, send_to_uid, [RoleId, BinData]).

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

update_player_attr(Mem, [{figure, Figure}|T]) ->
    update_player_attr(Mem#beach_member{figure = Figure}, T);
update_player_attr(Mem, [_|T]) ->
    update_player_attr(Mem, T);
update_player_attr(Mem, []) -> Mem.

%% 排行榜日志
add_log(RoomId, MemberMap, RankList) ->
    MemberList = lists:flatten(maps:values(MemberMap)),
    %% 区分服务器发往对应服务器
    ForFun = fun(#beach_member{role_id = RoleId, figure = Figure, scene_pool = PoolId,
        server_id = ServerId, server_num = ServerNum, node = Node}, TMap) ->
        case lists:keyfind(RoleId, 1, RankList) of
            {_, [Rank, CharmValue]} -> skip;
            _ -> Rank = 0, CharmValue = 0
        end,
        Reward = data_charm_beach:get_reward_cfg(Rank),
        TList  = maps:get(Node, TMap, []),
        TList1 = [{RoomId, PoolId, ServerId, ServerNum, RoleId, Figure#figure.vip, Figure#figure.power, CharmValue, Rank, Reward}|TList],
        maps:put(Node, TList1, TMap)
    end,
    NodeMsgMap = lists:foldl(ForFun, #{}, MemberList),
    SendFun = fun(TNode, Msg) ->
        unode:apply(TNode, ?MODULE, node_add_log, [Msg])
    end,
    maps:map(SendFun, NodeMsgMap),
    ok.

node_add_log(Msg) ->
    KfGrade = case czone_api:env_get_zone() of
                  #czone_svr_local{stage = Stage} -> Stage;
                  _ -> 1
              end,
    LogFun = fun({RoomId, PoolId, ServerId, ServerNum, RoleId, VipLv, Power, Votes, Rank, Reward}) ->
            lib_log_api:log_beach_rank(RoomId, PoolId, ServerId, ServerNum, KfGrade, RoleId, VipLv, Power, Votes, Rank, Reward)
        end,
    lists:map(LogFun, Msg),
    ok.

%% 投票日志
add_vote_log(Node, RoomId, PoolId, ServerId, ServerNum, RoleId, BeVoteId, BeVoteName, LeftFreeTimes, AddExp) ->
    unode:apply(Node, ?MODULE, node_add_vote_log, [RoomId, PoolId, ServerId, ServerNum, RoleId, BeVoteId, BeVoteName, LeftFreeTimes, AddExp]).

node_add_vote_log(RoomId, PoolId, ServerId, ServerNum, RoleId, BeVoteId, BeVoteName, LeftFreeTimes, AddExp) ->
    lib_log_api:log_beach_vote(RoomId, PoolId, ServerId, ServerNum, RoleId, BeVoteId, BeVoteName, LeftFreeTimes, AddExp).
