%%%-------------------------------------------------------------------
%%% @author liushl
%%% @doc
%%%     沙盘争夺战分区-管理系统
%%% 玩法特别说明：
%%% 1）跨服进度2变4的时刻分区内开启活动，所以每个分区的赛季进度都可能是不一致。
%%% 2）分区合并的时候，赛季直接重置。
%%% 3）固定每周一(暂定)4点赛季重置，但是如果最近3天(暂定)内有触发过赛季重置，那么就不会赛季重置。
%%% 4）据点BUFF激活后，BUFF归属不会随着据点归属变化而变化。
%%% 5）据点被攻打过一次后，整个赛季都可以扫荡，扫荡数据是玩家在该据点的历史最佳成绩。
%%% 6）防守据点时，效果是扣除攻击方的积分。
%%% 7）国家对据点的占领时长会一直累积，抢占回据点后会加上之前的占领时长。
%%% 8）插旗时，可以同时存在两个旗子，插第三个旗子时会取消最老的一个旗子。
%%% 9）波数副本结算时，积分计算是至上一波的波数积分+当前波的怪物输出比列积分。
%%% 10）副本结算时，如果是在上一个赛季进入的副本时，数据丢弃。
%%% @end
%%%-------------------------------------------------------------------
-module(cross_point_fight_zone).
-author("liushl").
-include("cross_point_fight.hrl").
-include("errcode.hrl").
-include("common.hrl").
-include("def_module.hrl").
%% API
-export([handle/3]).

%%%-------------------------------------------------------------------
%% 服务器启动初始化分区数据
handle(init, _, ZoneData) ->
    ZoneId = ZoneData#point_fight_zone.zone_id,
    AllPoints       = db:get_all(io_lib:format(?DB_GET_ZONE_POINTS, [ZoneId])),
    AllBattleLog    = db:get_all(io_lib:format(?DB_GET_POINT_BATTLE_LOG, [ZoneId])),
    AllRoleData     = db:get_all(io_lib:format(?DB_GET_ROLE_DATA, [ZoneId])),
    NewZoneData     = init_zone_data(AllPoints, AllBattleLog, ZoneData),
    RoleData        = maps:from_list(
        [
            {RoleId, #role_data{
                role_id     = RoleId,        %% 玩家id
                name        = Name,       %% 玩家名字
                server_id   = ServerId,        %% 服务器id
                server_num  = ServerNum,
                faction     = Faction         %% 阵营
            }}
            || [RoleId, Name, ServerId, ServerNum, Faction] <- AllRoleData
        ]),
    
    NewZoneData#point_fight_zone{role_data = RoleData};

%% 游戏服连接上跨服中心
handle({node_connect, ServerId}, Faction, ZoneData) ->
    #point_fight_zone{
        point_buff = Buff,
        buff_time = BuffTime
    } = ZoneData,
    Now = utime:unixtime(),
    case BuffTime > Now of
        true  ->
            MyBuff =
                [
                    case data_cross_point_fight:get_point_fight_point(Id) of
                        #base_cross_point_fight_point{buff = SkillId} ->
                            SkillId;
                        _ ->
                            0
                    end|| {Id, SeizeFaction} <- Buff, SeizeFaction =:= Faction
                ],
            case MyBuff =/= [] of
                true  ->
                    %% 目前只同步了BUFF，只在有BUFF的时候进行同步
                    %% 之后如果有需求再修改
                    Info = [MyBuff, BuffTime],
                    cross_point_fight_local:sync_cross_info(ServerId, Info);
                false ->
                    ok
            end;
        false ->
            ok
    end,
    ok;

%% 合服处理
handle({merge, NowServerId, NowServerNum, MergeServers}, Faction, ZoneData) ->
    RoleData = ZoneData#point_fight_zone.role_data,
    {NewRoleData, UpdateList} = maps:fold(
        fun(Key, Value, {TempRoleData, TempUpdate}) ->
            case lists:member(Value#role_data.server_id, MergeServers) of
                true  ->
                    NewValue = Value#role_data{
                        server_id = NowServerId,
                        server_num = NowServerNum,
                        faction = Faction
                    },
                    {TempRoleData#{Key => NewValue}, [NewValue|TempUpdate]};
                false ->
                    {TempRoleData#{Key => Value}, TempUpdate}
            end
        end, {#{}, []}, RoleData
    ),
    db_batch_save_role_data(ZoneData#point_fight_zone.zone_id, UpdateList),
    ZoneData#point_fight_zone{role_data = NewRoleData};

%% 赛季切换。
%% 判断是否强制切换。
%% 一般是周一0点切换新赛季，但是如果周一0点时，新赛季开启时间是3天内，则忽略该消息，超过3天则强制切换；
%% 如果是重新分区，则强制切换。
%% SwitchType : 1赛季初始化/2分区合并/3每周固定
handle({season_switch, SwitchType}, _, ZoneData) ->
    #point_fight_zone{
        zone_id   = ZoneId,
        season_id = SeasonId,
        season_time = SeasonTime
    } = ZoneData,
    IsSwitch =
        case SwitchType of
            ?SWITCH_TYPE_INIT  ->
                true;
            ?SWITCH_TYPE_RECALC_ZONE ->
                true;
            _ ->
                utime:diff_day(SeasonTime) >= cross_point_fight_logic:get_point_cfg(season_stay_day, 3)
        end,
    case IsSwitch of
        true  ->
            NewZoneData = #point_fight_zone{
                zone_id     = ZoneId,
                season_id   = SeasonId + 1,
                season_time = utime:unixtime()
            },
            %% 通知军团会议室，重置数据
            SwitchType =/= ?SWITCH_TYPE_INIT andalso
                mod_legion_rank_mgr:issue_reset(ZoneId),
            %% 通知分区内各个游戏服，清理玩家身上的参与数据
            cross_point_fight_local:notice_season_switch(ZoneId),
            %% 保存新赛季信息
            handle({save_zone_state, 1}, 0, NewZoneData),
            %% 清理赛季数据
            SwitchType =/= ?SWITCH_TYPE_INIT andalso
                (self() ! {clean_zone_data, ZoneId, 1}),
            lib_chat:send_TV({zone, ZoneId}, ?MOD_CROSS_POINT_FIGHT, 1, []),
            NewZoneData;
        false ->
            ok
    end;

%% 关闭分区数据
handle(season_close, _, ZoneData) ->
    ZoneId = ZoneData#point_fight_zone.zone_id,
    self() ! {del_zone, ZoneId, 1},
    self() ! {clean_zone_data, ZoneId, 1},
    remove_self;
 
%% 保存分区赛季信息
handle({save_zone_state, N}, _, ZoneData) when N =< 5 ->
    ZoneId = ZoneData#point_fight_zone.zone_id,
    Self = self(),
    spawn(
        fun() ->
            case catch db_save_zone(ZoneData) of
                ok ->
                    ok;
                Error ->
                    ?ERR("~p", [Error]),
                    cross_point_fight_center:send_after_zone(5, Self, ZoneId, 0, {save_zone_state, N + 1})
            end
        end
    ),
    ok;

%% 激活据点BUFF
handle({active_point_buff, LastTime}, _, ZoneData) ->
    #point_fight_zone{
        zone_id = ZoneId,
        point_info = PointInfo
    } = ZoneData,
    BuffPoints = data_cross_point_fight:get_point_by_type(?POINT_TYPE_FIGHT_BUFF),
    BuffFaction =
        lists:foldl(
            fun(Id, Temp) ->
                case lists:keyfind(Id, #point_info.point_id, PointInfo) of
                    #point_info{faction = Faction} when Faction > 0 ->
                        [{Id, Faction}|Temp];
                    _ ->
                        Temp
                end
            end, [], BuffPoints
        ),
    BuffTime = utime:unixtime() + LastTime,
    NewZoneData = ZoneData#point_fight_zone{point_buff = BuffFaction, buff_time = BuffTime},
    cross_point_fight_local:active_point_buff(ZoneId, BuffFaction, BuffTime),
    handle({save_zone_state, 1}, 0, NewZoneData),
    NewZoneData;

%% 查看沙盘信息-据点整体信息
handle({ask_global_info, Sid, Info}, Faction, ZoneData) ->
    #point_fight_zone{
        point_mass = PointMass,
        point_info = PointInfo,
        point_buff = BuffInfo,
        buff_time = BuffTime
    } = ZoneData,
    MyMass = case lists:keyfind(Faction, 1, PointMass) of
                 {Faction, MassInfo, _} -> MassInfo;
                 false -> []
             end,
    Now = utime:unixtime(),
    PointsInfo =
    [
        begin
            case lists:keyfind(PointId, 1, Info) of
                {PointId, IsSweep, AwardTimes} ->
                    ok;
                _ ->
                    IsSweep = 0,
                    AwardTimes = 0
            end,
            {AwardShowTime, _, _} = calc_last_award_time(Point, Faction, AwardTimes, Now),
            case lists:member(PointId, MyMass) of
                true  -> IsMass = 1;
                false -> IsMass = 0
            end,
            {PointId, SeizeFaction, SeizeInfo, AwardShowTime, IsMass, IsSweep}
        end
        || #point_info{
                point_id        = PointId,
                faction         = SeizeFaction,
                seize_info      = SeizeInfo
            } = Point <- PointInfo
    ],
    TheBuffInfo =
        case Now < BuffTime of
            true  -> BuffInfo;
            false -> []
        end,
    {ok, Bin} = pt_604:write(60401, [PointsInfo, TheBuffInfo]),
    lib_server_send:send_to_sid(Sid, Bin),
    ZoneData;

%% 查看沙盘信息-单个据点信息
handle({ask_point_info, Sid, PointId, IsSweep, AwardTimes}, Faction, ZoneData) ->
    #point_fight_zone{
        point_mass = PointMass,
        point_info = PointInfo
    } = ZoneData,
    MyMass = case lists:keyfind(Faction, 1, PointMass) of
                 {Faction, MassInfo, _} -> MassInfo;
                 false -> []
             end,
    #point_info{
        point_id        = PointId,
        faction         = SeizeFaction,
        seize_info      = SeizeInfo
    } = Point = cross_point_fight_logic:get_point(PointId, PointInfo),
    Now = utime:unixtime(),
    {AwardShowTime, _, _} = calc_last_award_time(Point, Faction, AwardTimes, Now),
    case lists:member(PointId, MyMass) of
        true  -> IsMass = 1;
        false -> IsMass = 0
    end,
    Info = [PointId, SeizeFaction, SeizeInfo, AwardShowTime, IsMass, IsSweep],
    {ok, Bin} = pt_604:write(60402, Info),
    lib_server_send:send_to_sid(Sid, Bin),
    ZoneData;

%% 查看沙盘信息-单个据点攻占记录
handle({ask_point_log, Sid, PointId, MyScore, RoleId}, _Faction, ZoneData) ->
    #point_fight_zone{
        point_info = PointInfo,
        role_data = RoleData
    } = ZoneData,
    #point_info{
        score_rank = ScoreRank
    } = cross_point_fight_logic:get_point(PointId, PointInfo),
    {Info, ReRank, Length} = lists:foldl(
        fun(One, {Temp, Rank, L}) ->
            #point_role{
                role_id = Id,
                hurt_part = Hurt,
                score   = Score         %% 据点总积分
            } = One,
            #role_data{
                faction = FactionId,    %% 阵营id
                name    = Name,         %% 名字
                server_num = ServerNum
            } = maps:get(Id, RoleData, #role_data{}),
            case Id =:= RoleId of
                true  ->
                    {[{ServerNum, FactionId, Name, Score, Hurt}|Temp], L + 1, L + 1};
                false ->
                    {[{ServerNum, FactionId, Name, Score, Hurt}|Temp], Rank, L + 1}
            end
        end, {[], 0, 0}, ScoreRank
    ),
    MyRank = case ReRank > 0 of
        true  ->
            Length - ReRank + 1;
        false ->
            0
    end,
    {ok, Bin} = pt_604:write(60403, [PointId, Info, MyScore, MyRank]),
    lib_server_send:send_to_sid(Sid, Bin),
    ZoneData;

%% 攻打据点
handle({attack_point, PointId, Sweep, RoleId, Node}, Faction, ZoneData) ->
    #point_fight_zone{
        zone_id = ZoneId,
        point_info = PointInfo      %% 据点信息
    } = ZoneData,
    Point = cross_point_fight_logic:get_point(PointId, PointInfo),
    Config = data_cross_point_fight:get_point_fight_point(PointId),
    #point_info{
        faction = SeizeFaction,
        seize_info = SeizeInfo
    } = Point,
    %% 判断是进攻还是防守
    OpType =
        case SeizeFaction =:= Faction of
            true  ->
                %% 是自己国家的据点，那就只能防守
                LoseScore = lists:sum([ Score || {_, Score} <- SeizeInfo]),
                case LoseScore > 0 of
                    false  ->
                        %% 没有敌国进攻数据，不需要防守，给前端提示已经占领了
                        0;
                    true  ->
                        2
                end;
            false ->
                %% 不是自己国家的据点，那就进攻
                1
        end,
    case cross_point_fight_logic:check(
        [
            %% 是否与己方据点相连
            {is_self_connect, Faction, PointId, PointInfo},
            %%
            {bool, OpType > 0, ?ERRCODE(err604_already_seize)}
        ]
    ) of
        {false, Error} ->
            {ok, Bin} = pt_604:write(60404, [Error]),
            lib_server_send:send_to_uid(Node, RoleId, Bin),
            ok;
        true ->
            #base_cross_point_fight_point{
                dun_id = DunId,
                type = Type
            } = Config,
            HpRatio = case Type of
                ?POINT_TYPE_SAFE ->
                    %% 对于安全据点，攻占比例就是BOSS的伤害比例
                    TotalData = case SeizeInfo of
                                     [{Faction, Data}] -> Data;
                                     _ -> 0
                                 end,
                    ?POINT_BOSS_HP_MAX_RATIO - TotalData;
                _ ->
                    ?POINT_BOSS_HP_MAX_RATIO
            end,
            case Sweep =:= 1 of
                true  ->
                    %% 扫荡处理
                    Arg = [PointId, HpRatio],
                    cross_point_fight_logic:reply_to_role(Node, RoleId, cross_point_fight_logic, sweep_point_callback, Arg);
                false ->
                    WorldLv = czone:get_zone_world_lv(ZoneId),
                    Arg = [PointId, WorldLv, DunId, HpRatio],
                    %% 进入副本处理
                    cross_point_fight_logic:reply_to_role(Node, RoleId, cross_point_fight_logic, attack_point_callback, Arg)
            end
    end,
    ok;

%% 完成副本，上传积分
%% 如果是上一赛季的数据，则忽略
handle({finish_dun, RoleId, PointId, AddScore, Value, StartTime, SweepOrFight, Node}, Faction, ZoneData)
    when StartTime > ZoneData#point_fight_zone.season_time ->
    #point_fight_zone{
        zone_id = ZoneId,
        point_info = PointInfo
    } = ZoneData,
    #base_cross_point_fight_point{base_score = NeedScore, name = PointName, type = PointType}
        = data_cross_point_fight:get_point_fight_point(PointId),
    %% 对于占领进度判断：
    %% 如果是安全据点，按照输出比例进行判断
    %% 如果是中立据点，按照获得积分来进行判断
    {AddData, BaseData} =
        case PointType =:= ?POINT_TYPE_SAFE of
            true  ->
                {Value, ?POINT_BOSS_HP_MAX_RATIO};
            false ->
                {AddScore, NeedScore}
        end,
    #point_info{
        faction = SeizeFaction,
        seize_time = SeizeTime,
        seize_info = SeizeInfo
    } = Point = cross_point_fight_logic:get_point(PointId, PointInfo),
    Res = case SeizeFaction =:= Faction of
              true  ->
                  %% 攻打的是自己据点，则性质是防守
                  LoseScore = lists:sum([ FactionScore || {_, FactionScore} <- SeizeInfo]),
                  case LoseScore > 0 of
                      true  ->
                          %% 每个进攻方都按比例扣除进攻积分
                          SeizeInfoTemp =
                              lists:foldl(
                                  fun({TheFaction, FactionScore}, Temp) ->
                                      Left = max(0, FactionScore - util:ceil((AddData * FactionScore / LoseScore))),
                                      case Left > 0 of
                                          true  ->
                                              [{TheFaction, Left}|Temp];
                                          false ->
                                              Temp
                                      end
                                  end, [], SeizeInfo
                              ),
                          CurLoseScore = lists:sum([ FactionScore || {_, FactionScore} <- SeizeInfo]),
                          %% 当前我方剩余没有被打下的积分就是我的总积分
                          {defense, SeizeInfoTemp, BaseData - CurLoseScore, CurLoseScore == 0};
                      false ->
                          %% 两种情形：进攻的时候被同国别人打下来了/防守的时候，同国别人已经防守成功了
                          %% 那就直接把初始值发给前端就行
                          {ignore, BaseData, 1}
                  end;
              false ->
                  %% 进攻的时候:如果跟自己国家断开链接，则进攻失效/连接中，进攻有效
                  OldData = case lists:keyfind(Faction, 1, SeizeInfo) of
                                 {Faction, _OldData} ->
                                     _OldData;
                                 false ->
                                     0
                             end,
                  case cross_point_fight_logic:check(
                      [
                          {is_self_connect, Faction, PointId, PointInfo}
                      ]
                  ) of
                      {false, _} ->
                          {ignore, OldData, 0};
                      true ->
                          NewData = OldData + AddData,
                          SeizeInfoTemp = lists:keystore(Faction, 1, SeizeInfo, {Faction, NewData}),
                          {attack, SeizeInfoTemp, NewData, OldData == 0}
                  end
          end,
    case Res of
        {ignore, FactionTotalData, Seize}  ->
            %% 不影响国家攻占数据
            %% 不管玩家的行为是否会影响到该据点的归属情况，玩家都会以总积分来刷新榜单数据，榜单只是一个记录数据而已
            cross_point_fight_logic:reply_to_role(Node, RoleId, cross_point_fight_logic, finish_dun_callback,
                [PointId, FactionTotalData, 0, AddScore, Value, Seize, SweepOrFight]),
            ok;
        {OpType, NewSeizeInfo, CurFactionData, Notice} ->
            %% 进攻的时候需要判断是否进攻成功，据点归属发生变化
            {Change, NewPoint} =
                case OpType of
                    attack ->
                        AttackScore = lists:sum([ FactionScore || {_, FactionScore} <- NewSeizeInfo]),
                        case AttackScore >= BaseData of
                            true  ->
                                Now = utime:unixtime(),
                                %% 进攻积分超过了初始积分，则积分贡献最多的国家成为占领国
                                {TheFaction, _} = ulists:keymax(2, NewSeizeInfo),
                                %%%-------------------------------------------------------------------
                                %% 每个国家的总占领时长是会一直累积的
                                %% 所以占领后，需要读取之前的占领时长
                                %% 被抢占后需要保存当前的占领时长
                                %% 领奖说明看方法：calc_last_award_time
                                %%-------------------------------------------------------------------
                                case lists:keytake(TheFaction, 1, Point#point_info.add_up_time) of
                                    {value, {TheFaction, _OldTime}, LeftTimeList} ->
                                        OldTime = _OldTime,
                                        LeftTimeList;
                                    false ->
                                        OldTime = 0,
                                        LeftTimeList = []
                                end,
                                %% 被占领的国家保存当次占领时长
                                NewTimeList = case SeizeTime > 0 of
                                                  true  ->
                                                      [{SeizeFaction, Now - SeizeTime}|LeftTimeList];
                                                  false ->
                                                      LeftTimeList
                                              end,
                                {true, Point#point_info{
                                    faction         = TheFaction,
                                    %% 由于占领时长需要累计，所以天才的修改此次占领初始时间，从而达到加上累积时长的效果
                                    seize_time      = Now - OldTime,
                                    seize_info      = [],
                                    add_up_time     = NewTimeList
                                }};
                            false ->
                                {false, Point#point_info{seize_info = NewSeizeInfo}}
                        end;
                    _ ->
                        {false, Point#point_info{seize_info = NewSeizeInfo}}
                end,
            case Change of
                true  ->
                    FactionTotalData = BaseData,
                    Seize = 1;
                false ->
                    FactionTotalData = CurFactionData,
                    Seize = 0
            end,
            spawn(fun() -> db_save_point(ZoneId, NewPoint) end),
            %% 归属发生变化
            case Change of
                true  ->
                    case czone:get_faction_name(ZoneId, Faction) of
                        {ok, FactionName} ->
                            lib_chat:send_TV({zone, ZoneId}, ?MOD_CROSS_POINT_FIGHT, 2, [FactionName, PointName]);
                        _ ->
                            ok
                    end;
                false ->
                    ok
            end,
            %% 广播据点变化信息
            (Notice orelse Change) andalso cross_point_fight_local:point_change(ZoneId, PointId),
            cross_point_fight_logic:reply_to_role(Node, RoleId, cross_point_fight_logic, finish_dun_callback,
                [PointId, FactionTotalData, AddData, AddScore, Value, Seize, SweepOrFight]),
            NewPointInfo = lists:keystore(PointId, #point_info.point_id, PointInfo, NewPoint),
            ZoneData1 = ZoneData#point_fight_zone{point_info = NewPointInfo},
            %% 归属变化后，取消新旧国家对该据点的旗子
            case Change of
                true  ->
                    ZoneData2 = handle({mass_status, PointId, 0, 0, "", Node}, Faction, ZoneData1),
                    ZoneData3 = handle({mass_status, PointId, 0, 0, "", Node}, Point#point_info.faction, ZoneData2),
                    ZoneData3;
                false ->
                    ZoneData1
            end
    end;

%% 玩家刷新在某个据点的积分数据
handle({refresh_point_score, RoleInfo, PointId, Score, Hurt}, _Faction, ZoneData) ->
    #point_fight_zone{
        zone_id = ZoneId,
        role_data = RoleData,
        point_info = PointInfo      %% 据点信息
    } = ZoneData,
    #role_data{
        role_id = RoleId
    } = RoleInfo,
    #point_info{
        score_rank = ScoreRank
    } = Point = cross_point_fight_logic:get_point(PointId, PointInfo),
    MaxLength = ?POINT_BATTLE_LOG_LENGTH,
    Res = case lists:keyfind(RoleId, #point_role.role_id, ScoreRank) of
        #point_role{} = One ->
            NewOne = One#point_role{score = Score, hurt_part = Hurt},
            ScoreRankTemp = lists:keyreplace(RoleId, #point_role.role_id, ScoreRank, NewOne),
            {true, lists:keysort(#point_role.score, ScoreRankTemp), NewOne, none};
        false ->
            One = #point_role{score = Score, role_id = RoleId, hurt_part = Hurt},
            L = length(ScoreRank),
            case L >= MaxLength of
                true  ->
                    [#point_role{score = MinScore} = OldOne|T] = ScoreRank,
                    case Score > MinScore of
                        true  ->
                            {true, lists:keysort(#point_role.score, [One|T]), One, OldOne};
                        false ->
                            false
                    end;
                false ->
                    {true, lists:keysort(#point_role.score, [One|ScoreRank]), One, none}
            end
    end,
    case Res of
        {true, NewScoreRank, UpdateOne, DelOne} ->
            spawn(fun() -> db_save_log(ZoneId, PointId, UpdateOne) end ),
            DelOne =/= none andalso spawn(fun() -> db_del_log(ZoneId, PointId, DelOne) end),
            NewPoint = Point#point_info{score_rank = NewScoreRank},
            NewPointInfo = lists:keystore(PointId, #point_info.point_id, PointInfo, NewPoint),
            NewRoleData = check_insert_role_data(ZoneId, RoleData, RoleInfo),
            ZoneData#point_fight_zone{point_info = NewPointInfo, role_data = NewRoleData};
        false ->
            ok
    end;

%% 插旗状态 RoleId =:= 0 则表明是系统操作
handle({mass_status, PointId, Status, RoleId, Name, Node}, Faction, ZoneData) ->
    #point_fight_zone{
        zone_id = ZoneId,
        point_mass = MassInfo,
        point_info = PointInfo
    } = ZoneData,
    {CurMass, LastTime} =
        case lists:keyfind(Faction, 1, MassInfo) of
            {Faction, Mass, _LastTime} ->
                {Mass, _LastTime};
            false ->
                {[], 0}
        end,
    Point = case lists:keyfind(PointId, #point_info.point_id, PointInfo) of
        #point_info{} = _Point ->
            Exist = true,
            _Point;
        false ->
            Exist = false,
            cross_point_fight_logic:default_point(PointId)
    end,
    Now = utime:unixtime(),
    Res =
        case Status =:= 1 of
            true  ->
                %% 插旗-可以对自己的据点或者与自己的据点相连的据点插旗
                CanSet = case Point#point_info.faction =:= Faction of
                    true  ->
                        true;
                    false ->
                        ErrCode = ?ERRCODE(err604_mass_cd),
                        case cross_point_fight_logic:check(
                            [
                                %% 是否与己方据点相连
                                {is_self_connect, Faction, PointId, PointInfo},
                                {bool, Now - LastTime > ?POINT_MASS_CD, ErrCode}
                            ]
                        ) of
                            {false, ErrCode} -> {false, ErrCode, ?POINT_MASS_CD + LastTime - Now};
                            {false, Error} -> {false, Error};
                            true ->
                                case lists:member(PointId, CurMass) of
                                    true  -> ignore;
                                    false ->
                                        true
                                end
                        end
                end,
                case CanSet of
                    true  ->
                        case CurMass of
                            [One, Two] ->
                                {true, [PointId, One], Now, [PointId, Two]};
                            _ ->
                                {true, [PointId|CurMass], Now, [PointId]}
                        end;
                    Other ->
                        Other
                end;
            false ->
                %% 取消插旗
                case lists:member(PointId, CurMass) of
                    false -> ignore;
                    true  ->
                        {true, lists:delete(PointId, CurMass), LastTime, [PointId]}
                end
        end,
    case Res of
        {false, Err, CD} when RoleId =/= 0 ->
            {ok, Bin} = pt_110:write(11017, [1, Err, CD]),
            lib_server_send:send_to_uid(Node, RoleId, Bin),
            ZoneData;
        {false, Err} when RoleId =/= 0 ->
            {ok, Bin} = pt_604:write(60405, [Err, 0, 0]),
            lib_server_send:send_to_uid(Node, RoleId, Bin),
            ZoneData;
        {true, NewMass, NewMassTime, Changes} ->
            RoleId =/= 0 andalso
                [
                    cross_point_fight_local:point_change(ZoneId, Id) || Id <- Changes
                ],
            NewMassInfo = lists:keystore(Faction, 1, MassInfo, {Faction, NewMass, NewMassTime}),
            case not Exist of
                true  ->
                    %% 这个据点还没有数据，那么写入一份数据
                    spawn(fun() -> db_save_point(ZoneId, Point) end),
                    NewPointInfo = [Point|PointInfo];
                false ->
                    NewPointInfo = PointInfo
            end,
            %% 插旗发一封全国邮件
            #base_cross_point_fight_point{name = PointName} = data_cross_point_fight:get_point_fight_point(PointId),
            Status =:= 1 andalso
                czone:zone_faction_apply_cast(ZoneId, Faction,
                    {lib_mail_api, send_sys_mail_to_active, [utext:get(6040001), utext:get(6040002, [Name, PointName]), [], 200, 7]}),
            NewZoneData = ZoneData#point_fight_zone{point_mass = NewMassInfo, point_info = NewPointInfo},
            handle({save_zone_state, 1}, 0, NewZoneData),
            {ok, Bin} = pt_604:write(60405, [1, PointId, Status]),
            lib_server_send:send_to_uid(Node, RoleId, Bin),
            NewZoneData;
        _ ->
            ZoneData
    end;

%% 领取据点占领奖励
handle({get_seize_award, PointId, RolePid, Node, AwardTimes}, Faction, ZoneData) ->
    #point_fight_zone{
        point_info = PointInfo      %% 据点信息
    } = ZoneData,
    Point = cross_point_fight_logic:get_point(PointId, PointInfo),
    Now = utime:unixtime(),
    %% 上次领奖时间
    {_AwardShowTime, LastAwardTime, IntervalTime} = calc_last_award_time(Point, Faction, AwardTimes, Now),
    Res = case cross_point_fight_logic:check(
        [
            {bool, Now - LastAwardTime >= IntervalTime, ?ERRCODE(err604_not_award_time)}
        ]
    ) of
        {false, Error} -> {false, Error};
        true ->
            Num = (Now - LastAwardTime) div IntervalTime,
            {true, Num, LastAwardTime + Num * IntervalTime}
    end,
    cross_point_fight_logic:reply_to_role(Node, RolePid, cross_point_fight_logic, get_seize_award_callback,
        [PointId, Res]),
    ok;

%% 更新玩家数据
handle({role_data_update, RoleId, Key, Value}, _Faction, ZoneData) ->
    #point_fight_zone{
        zone_id = ZoneId,
        role_data = RoleData
    } = ZoneData,
    case maps:find(RoleId, RoleData) of
        {ok, Role} ->
            NewRole = case Key of
                name ->
                    Role#role_data{name = Value};
                _ ->
                    Role
            end,
            db_save_role_data(ZoneId, NewRole),
            ZoneData#point_fight_zone{
                role_data = maps:put(RoleId, NewRole, RoleData)
            };
        _ ->
            ok
    end;

handle(_, _, _) -> ok.

%%%-------------------------------------------------------------------
%%% @doc
%%%     Internal Function
%%% @end
%%%-------------------------------------------------------------------
%% 初始化分区数据
init_zone_data([], _AllBattleLog, ZoneData) -> ZoneData;
init_zone_data([[PointId, Faction, SeizeTime, AddUpTimeBin, SeizeInfoBin]|AllPoints], AllBattleLog, ZoneData) ->
    {ScoreRank, RestBattleLog}  = init_battle_log(AllBattleLog, PointId),
    Point = #point_info{
        point_id        = PointId,
        faction         = Faction,
        seize_time      = SeizeTime,
        score_rank      = lists:keysort(#point_role.score, ScoreRank),
        add_up_time     = util:bitstring_to_term(AddUpTimeBin),
        seize_info      = util:bitstring_to_term(SeizeInfoBin)
    },
    NewZoneData = ZoneData#point_fight_zone{point_info = [Point|ZoneData#point_fight_zone.point_info]},
    init_zone_data(AllPoints, RestBattleLog, NewZoneData).

%% 初始化据点战斗记录
init_battle_log(AllBattleLog, PointId) ->
    init_battle_log(AllBattleLog, PointId, [], []).
init_battle_log([], _ThePointId, ScoreRank, RestBattleLog) -> {ScoreRank, RestBattleLog};
init_battle_log([[PointId, RoleId, Score, Hurt]|AllBattleLog], ThePointId, ScoreRank, RestBattleLog) ->
    case PointId =:= ThePointId of
        true  ->
            One = #point_role{
                role_id = RoleId,
                score   = Score,
                hurt_part = Hurt
            },
            init_battle_log(AllBattleLog, ThePointId, [One|ScoreRank], RestBattleLog);
        false ->
            init_battle_log(AllBattleLog, ThePointId, ScoreRank, [[PointId, RoleId, Score, Hurt]|RestBattleLog])
    end.
%%%-------------------------------------------------------------------
%% 计算上次领奖时间
%% 如果是自己占领的据点，则 seize_time 就是占领时间
%% 如果不是自己占领的据点，则 当前时间 - AddUpTime 里 自己国家的累计时间就是占领时间
%% 上次领奖时间是通过占领初始时间与领奖次数巧妙的算出来的，每次领奖都是加领奖间隔的固定倍数
%% 缺点是如果领奖间隔配置修改了，会导致上次领奖时间发生变化
%% 占领时间保存看消息 {finish_dun 处理
%%%-------------------------------------------------------------------
calc_last_award_time(Point, Faction, AwardTimes, Now) ->
    #point_info{
        point_id = PointId,
        faction = SeizeFaction,
        seize_time = SeizeTime,
        add_up_time = AddUpTime
    } = Point,
    IntervalTime = case data_cross_point_fight:get_point_fight_point(PointId) of
                       #base_cross_point_fight_point{interval_time = Time} -> Time;
                       _ -> 10000000000
                   end,
    CheckSeizeTime = case SeizeFaction =:= Faction of
        true  ->
            SeizeTime;
        false ->
            case lists:keyfind(Faction, 1, AddUpTime) of
                {Faction, AddTime} ->
                    Now - AddTime;
                _ ->
                    Now
            end
    end,
    %% 占领时间+领奖次数*领奖间隔 就是上次领奖时间
    LastAwardTime = CheckSeizeTime + AwardTimes * IntervalTime,
    %% 特殊处理：如果不是自己自己占领，又不能领奖，那就展示给前端为0
    AwardShowTime = case SeizeFaction =:= Faction of
        true  ->
            LastAwardTime;
        false ->
            case Now - LastAwardTime >= IntervalTime of
                true  ->
                    LastAwardTime;
                false ->
                    0
            end
    end,
    {
        AwardShowTime,
        LastAwardTime,
        IntervalTime
    }.

%% 保存活动状态
db_save_zone(ZoneData) ->
    #point_fight_zone{
        zone_id      = ZoneId,
        season_id    = SeasonId,
        season_time  = SeasonTime,
        point_mass   = PointMass,
        point_buff   = PointBuff,
        buff_time    = BuffTime
    } = ZoneData,
    SQL = io_lib:format(?DB_SAVE_ZONE_INFO,
        [ZoneId, SeasonId, SeasonTime, util:term_to_bitstring(PointMass), util:term_to_bitstring(PointBuff), BuffTime]),
    db:execute(SQL),
    ok.

%% 保存据点信息
db_save_point(ZoneId, Point) ->
    #point_info{
        point_id        = PointId,
        faction         = Faction,
        seize_time      = SeizeTime,
        add_up_time     = AddUpTime,
        seize_info      = SeizeInfo
    } = Point,
    SQL = io_lib:format(?DB_SAVE_ZONE_POINT, [ZoneId, PointId, Faction, SeizeTime, util:term_to_bitstring(AddUpTime), util:term_to_bitstring(SeizeInfo)]),
    db:execute(SQL).

%% 保存据点攻击记录
db_save_log(ZoneId, PointId, PointRole) ->
    #point_role{
        role_id = RoleId,        %% 玩家id
        hurt_part = Hurt,
        score   = Score         %% 据点总积分
    } = PointRole,
    SQL = io_lib:format(?DB_SAVE_POINT_BATTLE_LOG, [ZoneId, PointId, RoleId, Score, Hurt]),
    db:execute(SQL).

%% 删除一条攻击记录
db_del_log(ZoneId, PointId, #point_role{role_id = RoleId}) ->
    SQL = io_lib:format(?DB_DEL_POINT_BATTLE_LOG, [ZoneId, PointId, RoleId]),
    db:execute(SQL).

%% 检查新增玩家信息
check_insert_role_data(ZoneId, RoleData, RoleInfo) ->
    case maps:is_key(RoleInfo#role_data.role_id, RoleData) of
        true ->
            RoleData;
        false ->
            db_save_role_data(ZoneId, RoleInfo),
            RoleData#{RoleInfo#role_data.role_id => RoleInfo }
    end.

db_save_role_data(ZoneId, RoleInfo) ->
    #role_data{
        role_id     = RoleId,        %% 玩家id
        name        = Name,       %% 玩家名字
        server_id   = ServerId,        %% 服务器id
        server_num  = ServerNum,
        faction     = Faction         %% 阵营
    } = RoleInfo,
    SQL = io_lib:format(?DB_SAVE_ROLE_DATA, [ZoneId, RoleId, Name, ServerId, ServerNum, Faction]),
    db:execute(SQL).

db_batch_save_role_data(_ZoneId, []) ->
    ok;
db_batch_save_role_data(ZoneId, RoleDataList) ->
    SqlList =
        [
            io_lib:format(<<"(~p,~p,'~ts',~p,~p,~p)">>, [ZoneId, RoleId, Name, ServerId, ServerNum, Faction])
            || #role_data{
                    role_id     = RoleId,        %% 玩家id
                    name        = Name,       %% 玩家名字
                    server_id   = ServerId,        %% 服务器id
                    server_num  = ServerNum,
                    faction     = Faction         %% 阵营
                } <- RoleDataList
        ],
    SQL = io_lib:format(?DB_SAVE_ROLE_DATA_BATCH, [string:join(SqlList, ",")]),
    db:execute(SQL),
    ok.