%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 场景怪物管理器
%%%
%%% 场景怪物逻辑主进程实例入口模块，
%%% 负责管理单个场景分线中的所有怪物的行为
%%%
%%% =======================================================
%%% 关于怪物信息#ob_act{}中
%%% 两个存储系统怪物“内部驱动事件”的字段的说明：
%%% =======================================================
%%%  字段名字 | general_driven              | state_driven
%%% -------------------------------------------------------
%%%  字段含义 | 通用驱动事件                | 状态驱动事件
%%% -------------------------------------------------------
%%%  数据格式 | [{TimeMS, Info}]            | {TimeMS, Info}
%%% -------------------------------------------------------
%%%  数据更替 | 支持多个Info事件，          | 仅支持单个状态消息
%%%           | 删除指定Info的旧事件，可起到| 新状态消息会取代
%%%           | 类似旧版cancel_timer的作用  | 旧的状态消息
%%% -------------------------------------------------------
%%%  有序性   | 有序，时间小在前            | 有序，时间小在前
%%% -------------------------------------------------------
%%%  旧版对比 | 取代旧版定时器，例如        | 取代旧版的
%%%           | eref、hurt_ref、            | 状态消息定时器，
%%%           | hurt_remove_ref、           | 即ref
%%%           | cycle_check_ref、           |
%%%           | special_ai_ref              |
%%% -------------------------------------------------------
%%%
%%% 注意，以上两个字段，
%%% 1. 对于新的同类自定义事件，都可以直接存储在其中，不必增加其他字段进行维护管理
%%% 2. 字段的有序性是为了便于快速从列表头取得最近时间的事件进行处理，
%%%    有序性由本模块在返回结果时进行保证，外部模块一般可当作普通lists直接操作
%%% 3. 外部模块对general_driven字段做修改的时候，自由决定相同Info是否唯一替换。
%%%    参照旧版逻辑，对于旧版的eref、hurt_ref、hurt_remove_ref、cycle_check_ref等
%%%    几个定时器对应消息，采用唯一替换方式。
%%%    对于旧版special_ai_ref的对应消息，则不采用唯一替换，并且废弃原来逻辑，
%%%    将原来special_ai_ref中的每一个消息都独立出来放在general_driven中。
%%% 4. 对于旧版其他直接触发（未存储在字段中，使用erlang:send/2,3等发送）的外部消息，
%%%    也要改为general_driven中的特定消息，
%%%    通常采用相同的消息体作为新版的Info值，便于代码移植修改
%%%
%%% =======================================================
%%% 旧版定时器及相关消息对应于新版Info的关系：
%%% =======================================================
%%% 定时器+旧版Msg      => 新版Info
%%% -------------------------------------------------------
%%% (state_driven)
%%% -------------------------------------------------------
%%% ref + repeat        => repeat
%%% ref + go            => go
%%% ref + {accumulate_power_over, OldStateName} => {accumulate_power_over, OldStateName}
%%% -------------------------------------------------------
%%% (general_driven)
%%% -------------------------------------------------------
%%% eref + {'special_event', Event}     => {'special_event', Event}
%%% hurt_ref + 'broadcast_hurt_list'    => 'broadcast_hurt_list'
%%% hurt_remove_ref + {'hurt_remove'}   => 'hurt_remove'
%%% cycle_check_ref + 'cycle_check'     => 'cycle_check'
%%% special_ai_ref + {ai_kill_scene_role, X} => {ai_kill_scene_role, X}
%%% 其他不存储或遗漏未指出的定时器消息X => X
%%% -------------------------------------------------------
%%% @end
%%% @since 2020-10-10
%%% -------------------------------------------------------
-module(mon_mgr).

-behaviour(gen_server).

%% API
-export([
        start_link/2
        ,stop/1
        ,stop/2
        %% 创建怪物
        ,create_mon/2
        ,create_mon/3
        %% 创建假人
        ,create_dummy/2
        ,create_dummy/3
        %% 结束怪物（清理怪物）
        ,stop_mon/3
        ,stop_all/1
        %% 结束假人（清理假人）
        ,stop_dummy/3
        %% 杀死怪物
        ,mon_die/2
        ,insert_mon/2
        %% 采集怪物
        ,collect_mon/4
        ,stop_collect/3
        %% 拾取怪物
        ,pick_mon/3
        %% 向怪物管理进程发送各类消息处理单个怪物的资料
        %% 注：一般在怪物管理进程优先用state_driven及general_driven处理
        %%     外部进程才使用send、send_after向进程发送消息做触发
        ,send/3
        ,send_all/2
        ,send_after/4
        ,send_after_all/3
        %% 获取怪物管理进程pid/注册名
        ,mon_mgr_pid/2                      % 获取怪物管理进程的pid
        ,mon_mgr_proc_name/2                % 怪物管理进程的注册名
        
        ,gm_get_mon_info/4
        ,catch_gm_message/3
        ,gm_get_scene_mon_info/2
    ]).

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

-compile({inline, [
            extract_driven/1
            ,state_data_remove_obj/2
        ]
    }).

-include("common.hrl").
-include("scene.hrl").
-include("figure.hrl").
-include("battle.hrl").
-include("attr.hrl").

%% 场景对象在管理器进程字典中的数据存储KEY
-define(OBJ_KEY(ObjId), {obj_data, ObjId}).

%% 通用的消息包装，用于对发送给怪物管理进程的常规消息进行格式统一
%% 其中Info是旧版怪物模块中向怪物进程发送的消息体
-define(INFO_ONE(ObjId, Info),      {to_obj_one, ObjId, Info}).
-define(INFO_LIST(ObjIdList, Info), {to_obj_list, ObjIdList, Info}).
-define(INFO_ALL(Info),             {to_obj_all, Info}).

%% 内部驱动消息统一收集消息的延迟时间（单位: ms）
-define(INFO_DELAY, 20).

%% 进程状态数据
-record(mproc_state, {
        state_timer = undefined     % 状态超时定时器
        ,objs_driven = []           % ordsets记录需要驱动的怪物未来最近驱动时间[{TimeMS, MonId}]
        ,all_obj_ids = []           % 全部存在实例的怪物id列表（大世界复活状态中的也计算）
        ,parent_pid = undefined     % 父进程pid，具体到这里的设计实际就是场景分线的进程pid
        ,scene_id = 0               % 场景id
        ,pool_id = 0                % 场景分线id
    }).

%%%===================================================================
%%% API
%%%===================================================================

%% 启动进程（跟随场景分线启动，由场景分线执行调用）
-spec start_link(SceneId, PoolId) -> {ok, Pid} | ignore | {error, Error} when
    SceneId :: non_neg_integer(), PoolId :: non_neg_integer(),
    Pid :: pid(), Error :: term().
start_link(SceneId, PoolId) ->
    SpawnOpts = [
        {spawn_opt, [{fullsweep_after, 128}]},
        {hibernate_after, 60000}
    ],
    LocalName = mon_mgr_proc_name(SceneId, PoolId),
    gen_server:start_link({local, LocalName}, ?MODULE, [self(), SceneId, PoolId], SpawnOpts).

%% 关闭进程
%% 通常不应该被调用，如未来有重启怪物管理进程的特殊需求，
%% 应由场景管理进程实现对怪物管理进程的重启（暂未实施）
stop(SceneId, PoolId) ->
    LocalName = mon_mgr_proc_name(SceneId, PoolId),
    gen_server:stop(LocalName).
stop(MonMgrPid) ->
    gen_server:stop(MonMgrPid).

%% --------------------------------------------------------
%% 创建怪物
%% 用于代替原mod_mon_active:start/1接口
create_mon(SceneId, PoolId, MonInfo) ->
    Pid = mon_mgr_pid(SceneId, PoolId),
    create_mon(Pid, MonInfo).

create_mon(Pid, [_|_] = MonInfo) ->
    gen_server:cast(Pid, {create_mon, MonInfo}).
%% --------------------------------------------------------

%% --------------------------------------------------------
%% 创建假人
%% 用于代替原mod_dummy_active:start/1接口
create_dummy(SceneId, PoolId, MonInfo) ->
    Pid = mon_mgr_pid(SceneId, PoolId),
    create_dummy(Pid, MonInfo).

create_dummy(Pid, [_|_] = MonInfo) ->
    gen_server:cast(Pid, {create_dummy, MonInfo}).
%% --------------------------------------------------------

%% --------------------------------------------------------
%% 插入怪物
insert_mon(ObjData, Args) ->
    #ob_act{object = #scene_object{scene = SceneId, scene_pool_id = PoolId}} = ObjData,
    Pid = mon_mgr_pid(SceneId, PoolId),
    gen_server:cast(Pid, {insert_obj, ObjData, Args}).
%% --------------------------------------------------------

%% --------------------------------------------------------
%% 结束单个怪物（清理怪物） / 结束同个mon_mgr实例进程下的一批怪物
%% 用于代替原mod_mon_active:stop/2接口
-spec stop_mon(MonMgrInfo, MonIdData, BroadCast) -> any() when
    MonMgrInfo :: {SceneId, PoolId} | MonMgrPid,
    SceneId :: non_neg_integer(), PoolId :: non_neg_integer(),
    MonMgrPid :: pid(),
    MonIdData :: MonId | [MonId],
    MonId :: non_neg_integer(),
    BroadCast :: 0 | 1.
stop_mon(MonMgrInfo, MonIdData, BroadCast) ->
    Info = {'stop', BroadCast},
    send(MonMgrInfo, MonIdData, Info).

%% 清理全部对象（通常为清理场景用）
stop_all(MonMgrInfo) ->
    Info = {'stop', 0},
    send_all(MonMgrInfo, Info).

%% 结束假人（清理假人）
%% 用于代替原mod_dummy_active:stop/2接口
%% —— 实际上与清理怪物一样，只是接口名称区别
-spec stop_dummy(MonMgrInfo, DummyIdData, BroadCast) -> any() when
    MonMgrInfo :: {SceneId, PoolId} | MonMgrPid,
    SceneId :: non_neg_integer(), PoolId :: non_neg_integer(),
    MonMgrPid :: pid(),
    DummyIdData :: DummyId :: [DummyId],
    DummyId :: non_neg_integer(),
    BroadCast :: 0 | 1.
stop_dummy(MonMgrInfo, DummyIdData, BroadCast) ->
    Info = {'stop', BroadCast},
    send(MonMgrInfo, DummyIdData, Info).
%% --------------------------------------------------------

%% --------------------------------------------------------
%% （批量）杀死怪物
%% 用于代替原mod_mon_active:die/1接口，及所有直接向原怪物进程发送'die'消息的操作
%% MonIdData :: MonId | [MonId]
mon_die(MonMgrPid, MonIdData) ->
    Info = 'die',
    send(MonMgrPid, MonIdData, Info).
%% --------------------------------------------------------

%% --------------------------------------------------------
%% 采集怪物
%% 用于代替原mod_mon_active:collect_mon/3
collect_mon(Pid, MonId, SceneUser, Type) ->
    #ets_scene_user{
        id = UserId, server_id = ServerId,
        node = Node, pid = RolePid
    } = SceneUser,
    Info = {'collect_mon', UserId, ServerId, Node, RolePid, Type},
    send(Pid, MonId, Info).

%% 中止采集
stop_collect(Pid, MonId, RoleId) ->
    Info = {'stop_collect', RoleId},
    send(Pid, MonId, Info).
%% --------------------------------------------------------

%% --------------------------------------------------------
%% 拾取怪物
%% 用于代替原mod_mon_active:pick_mon/3
pick_mon(Pid, MonId, SceneUser) ->
    #ets_scene_user{
        id = RoleId, node = Node, pid = RolePid
    } = SceneUser,
    Info = {'pick_mon', RoleId, Node, RolePid},
    send(Pid, MonId, Info).
%% --------------------------------------------------------

%% --------------------------------------------------------
%% 向怪物管理进程发送常规消息，用于处理单个怪物逻辑
%% 用于代替旧版各种直接向怪物进程发送消息的操作
%% 注：一般在怪物管理进程优先用state_driven及general_driven处理
%%     外部进程才使用send、send_after向进程发送消息做触发
%% --------------------------------------------------------

%% 向怪物管理进程发送 针对单个/批量怪物的消息
-spec send(MonMgrInfo, MonIdData, Info) -> any() when
    MonMgrInfo :: {SceneId, PoolId} | MonMgrPid,
    SceneId :: non_neg_integer(), PoolId :: non_neg_integer(),
    MonMgrPid :: pid(),
    MonIdData :: MonId | [MonId],
    MonId :: non_neg_integer(),
    Info :: any().
send({SceneId, PoolId}, MonIdData, Info) ->
    Pid = mon_mgr_pid(SceneId, PoolId),
    send(Pid, MonIdData, Info);
send(Pid, MonId, Info) when is_integer(MonId) ->
    erlang:send(Pid, ?INFO_ONE(MonId, Info));
send(Pid, [MonId], Info) ->
    erlang:send(Pid, ?INFO_ONE(MonId, Info));
send(Pid, MonIdList = [_|_], Info) ->
    erlang:send(Pid, ?INFO_LIST(MonIdList, Info));
send(_Pid, _Other, _Info) ->
    ok.

%% 向怪物管理进程发送 针对该进程管理的全部对象的消息
-spec send_all(MonMgrInfo, Info) -> any() when
    MonMgrInfo :: {SceneId, PoolId} | MonMgrPid,
    SceneId :: non_neg_integer(), PoolId :: non_neg_integer(),
    MonMgrPid :: pid(),
    Info :: any().
send_all({SceneId, PoolId}, Info) ->
    Pid = mon_mgr_pid(SceneId, PoolId),
    send_all(Pid, Info);
send_all(Pid, Info) ->
    erlang:send(Pid, ?INFO_ALL(Info)).

%% 延迟向怪物管理进程发送 针对单个怪物的消息
-spec send_after(DelayMS, MonMgrInfo, MonIdData, Info) -> any() when
    DelayMS :: non_neg_integer(),
    MonMgrInfo :: {SceneId, PoolId} | MonMgrPid,
    SceneId :: non_neg_integer(), PoolId :: non_neg_integer(),
    MonMgrPid :: pid(),
    MonIdData :: MonId | [MonId],
    MonId :: non_neg_integer(),
    Info :: any().
send_after(DelayMS, {SceneId, PoolId}, MonIdData, Info) ->
    Pid = mon_mgr_pid(SceneId, PoolId),
    send_after(DelayMS, Pid, MonIdData, Info);
send_after(DelayMS, Pid, MonId, Info) when is_integer(MonId) ->
    erlang:send_after(DelayMS, Pid, ?INFO_ONE(MonId, Info));
send_after(DelayMS, Pid, [MonId], Info) ->
    erlang:send_after(DelayMS, Pid, ?INFO_ONE(MonId, Info));
send_after(DelayMS, Pid, MonIdList = [_|_], Info) ->
    erlang:send_after(DelayMS, Pid, ?INFO_LIST(MonIdList, Info));
send_after(_DelayMS, _Pid, _Other, _Info) ->
    ok.

%% 延迟向怪物管理进程发送 针对该进程管理的全部对象的消息
-spec send_after_all(DelayMS, MonMgrInfo, Info) -> any() when
    DelayMS :: non_neg_integer(),
    MonMgrInfo :: {SceneId, PoolId} | MonMgrPid,
    SceneId :: non_neg_integer(), PoolId :: non_neg_integer(),
    MonMgrPid :: pid(),
    Info :: any().
send_after_all(DelayMS, {SceneId, PoolId}, Info) ->
    Pid = mon_mgr_pid(SceneId, PoolId),
    send_after_all(DelayMS, Pid, Info);
send_after_all(DelayMS, Pid, Info) ->
    erlang:send_after(DelayMS, Pid, ?INFO_ALL(Info)).

%% --------------------------------------------------------

%% --------------------------------------------------------
%% 获取怪物管理进程pid/注册名
%% --------------------------------------------------------
%% 获取怪物管理进程的pid
mon_mgr_pid(SceneId, PoolId) ->
    whereis(mon_mgr_proc_name(SceneId, PoolId)).

%% 怪物管理进程的注册名
%% 通常来说，在未发生大规模的不同错误参数调用的情况下，
%% 场景Id和PoolId的规模是比较有限的，所以直接用LocalName，不使用其他方式管理
mon_mgr_proc_name(SceneId, PoolId) ->
    list_to_atom(lists:concat(["mon_mgr_", SceneId, "_", PoolId])).
%% --------------------------------------------------------

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

init([ParentPid, SceneId, PoolId]) ->
    process_flag(trap_exit, true),
    StateData = #mproc_state{
        objs_driven = [],
        all_obj_ids = [],
        parent_pid  = ParentPid,
        scene_id    = SceneId,
        pool_id     = PoolId
    },
    {ok, StateData}.

handle_call(_Request, _From, StateData) ->
    handle_event({call, _From}, _Request, StateData).

handle_cast(_Msg, StateData) ->
    handle_event(cast, _Msg, StateData).

handle_info(_Info, StateData) ->
    handle_event(info, _Info, StateData).

terminate(_Reason, _StateData) ->
    %% 只有场景关闭或进程异常挂掉才会terminate，所以不遍历执行怪物移除逻辑
    %% 如果在结束进程前，有需要怪物terminate执行的内容，请先stop_mon
    ok.

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



%%%===================================================================
%%% 内部公共的消息管理
%%%===================================================================

%% 使用handle_event统一处理不同类型消息，方便一个入口管理并做好异常捕捉
%% （公共服务进程不允许因某个怪物行为异常而引起整个服务挂掉）
handle_event(EventType, EventContent, StateData) ->
    try
        Result = handle_common(EventType, EventContent, StateData),
        case valid_event_result(Result) of
            true  -> ok;
            false -> throw({error, bad_result})
        end,
        Result
    catch
        ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
            logger:error("~w,~w,~p~n", [_Class, _Reason, ?GET_STACK(_Stacktrace)]),
            {noreply, StateData}
    end.

handle_common(info, {'EXIT', Pid, _Reason}, StateData) ->
    #mproc_state{parent_pid = ParentPid} = StateData,
    case ParentPid =:= Pid of
        true  ->    % 父进程退出，本进程自杀
            {stop, normal, StateData};
        false ->
            {noreply, StateData}
    end;

handle_common(cast, {create_mon, InitInfo}, StateData) ->
    create_obj(mon_state_logic, InitInfo, StateData);
handle_common(cast, {create_dummy, InitInfo}, StateData) ->
    create_obj(dummy_state_logic, InitInfo, StateData);
handle_common(cast, {insert_obj, ObjData, Args}, StateData) ->
    insert_obj(dummy_state_logic, ObjData, StateData, Args);
handle_common(info, driven_check, StateData) ->
    %% 遍历Actions，分别调用driving_monster进行每个必要的事件的处理
    NowTimeMS = utime:unixtime(ms),
    #mproc_state{objs_driven = ObjsDriven} = StateData,
    NewStateData = driving_monsters_state(NowTimeMS, ObjsDriven, StateData),
    StateDataUpdateTimer = calc_state_timer(NewStateData),
    {noreply, StateDataUpdateTimer};

%% 处理针对单个Obj的消息（不区分info/cast消息类型）
handle_common(_EventType, ?INFO_ONE(ObjId, Info), StateData) ->
    NewStateData = driving_monster_event(ObjId, Info, StateData),
    StateDataUpdateTimer = calc_state_timer(NewStateData),
    {noreply, StateDataUpdateTimer};

%% 处理针对一批Obj的消息（不区分info/cast消息类型）
handle_common(_EventType, ?INFO_LIST(ObjIdList, Info), StateData) ->
    NewStateData = handle_batch_obj_info(ObjIdList, Info, StateData),
    StateDataUpdateTimer = calc_state_timer(NewStateData),
    {noreply, StateDataUpdateTimer};

%% 处理针对全部Obj的消息（不区分info/cast消息类型）
handle_common(_EventType, ?INFO_ALL(Info), StateData) ->
    #mproc_state{all_obj_ids = AllObjIds} = StateData,
    NewStateData = handle_batch_obj_info(AllObjIds, Info, StateData),
    StateDataUpdateTimer = calc_state_timer(NewStateData),
    {noreply, StateDataUpdateTimer};

%% GM消息
handle_common({call, From}, {gm, Mod, Func, Arg}, StateData) ->
    Res = apply(Mod, Func, [StateData|Arg]),
    gen_server:reply(From, Res),
    {noreply, StateData};

%% 匹配其他任意消息
handle_common(_EventType, _EventContent, StateData) ->
    %% 意外消息仅在开发版做打印
    ?DEBUG("意外消息，可能为尚未修正的旧版怪物消息：~nEventType:~w, EventContent:~w",
        [_EventType, _EventContent]
    ),
    NewStateData = reset_state_timer(StateData, ?INFO_DELAY),
    {noreply, NewStateData}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

%% --------------------------------------------------------
%% 怪物数据进程字典操作
%% --------------------------------------------------------

%% 保存进程中怪物的主要数据 及 怪物的状态信息
%% 继承自原mod_mon_active中的进程状态中的怪物数据部分，即#ob_act{}，及状态
proc_put_obj_state(ObjState, ObjData = #ob_act{object = #scene_object{id = ObjId}}) ->
    ObjStateData = #obj_state_data{
        obj_data = ObjData
        ,obj_state = ObjState
    },
    erlang:put(?OBJ_KEY(ObjId), ObjStateData);
proc_put_obj_state(_, _) ->
    error.

%% 获取object数据 -> #obj_state_data{}.
proc_get_obj_state(ObjId) ->
    erlang:get(?OBJ_KEY(ObjId)).

proc_erase_obj_state(ObjId) ->
    erlang:erase(?OBJ_KEY(ObjId)).

%% --------------------------------------------------------
%% 内部数据校验
%% --------------------------------------------------------

%% 检验事件处理返回结果是否有效 -> boolean()
valid_event_result(Result) ->
    case Result of
        {noreply, _StateData} -> true;
        {reply, _Reply, _StateData} -> true;
        {stop, _Reason, _StateData} -> true;
        _ -> false
    end.

%% --------------------------------------------------------
%% 转换CallbackResult
%% --------------------------------------------------------
trans_callback_result({next_state, ObjState, ObjData}) when is_record(ObjData, ob_act) ->
    NewObjData = obj_sort_driven(ObjData),
    {next_state, ObjState, NewObjData};
%% 结束怪物
trans_callback_result({stop, _Reason, _ObjData} = Result) ->
    Result;
%% 特别注意：原则上ignore只是本管理器用于内部状态区分，
%% mon_state_logic:handle_info/3不可以返回ignore
trans_callback_result(ignore) ->
    ignore;
trans_callback_result(_Error) ->
    {error, _Error}.

%% 怪物创建结果统一返回值
trans_create_result({ok, ObjState, ObjData}) when is_record(ObjData, ob_act) ->
    NewObjData = obj_sort_driven(ObjData),
    AllDriven = extract_driven(NewObjData),
    {ok, ObjState, NewObjData, AllDriven};
trans_create_result({ok, _ObjState, _ObjData}) ->
    {stop, _ObjState, _ObjData};
trans_create_result(_Error) ->
    _Error.

%% ObjData确保driven数据有序性（数据格式不检查）
obj_sort_driven(ObjData = #ob_act{general_driven = GeneralDriven}) ->
    NewGeneralDriven = lists:sort(GeneralDriven),
    ObjData#ob_act{
        general_driven = NewGeneralDriven
    }.

extract_driven(#ob_act{state_driven = StateDriven, general_driven = GeneralDriven}) ->
    case StateDriven of
        ?EMPTY_STATE_DRIVEN -> GeneralDriven;
        {_TimeMS, _Info}    -> [StateDriven | GeneralDriven];
        _                   -> GeneralDriven
    end.


%% --------------------------------------------------------
%% 创建场景对象（怪物、假人等）
%% --------------------------------------------------------
create_obj(CallbackMod, InitInfo, StateData) ->
    InitResult = trans_create_result(
        CallbackMod:init(InitInfo)
    ),
    after_create(InitResult, StateData).

%% --------------------------------------------------------
%% 插入场景对象（怪物、假人等）-一般是怪物切换场景
%% --------------------------------------------------------
insert_obj(CallbackMod, ObjData, StateData, Args) ->
    InitResult = trans_create_result(
        CallbackMod:change_scene(ObjData, Args)
    ),
    after_create(InitResult, StateData).

after_create({ok, ObjState, ObjData, ObjDriven}, StateData) ->
    %% ObjState => 旧版mod_mon_active中的StateName，即sleep、walk、trace、...
    %% ObjData  => 旧版mod_mon_active中的StateData，即#ob_act{}
    case proc_put_obj_state(ObjState, ObjData) of
        error ->
            ?ERR("create mon return invalid:~w,~p", [ObjState, ObjData]),
            {noreply, StateData};
        _Old ->
            #ob_act{object = #scene_object{id = ObjId}} = ObjData,
            #mproc_state{
                objs_driven = ObjsDriven
                ,all_obj_ids = AllObjIds
            } = StateData,
            NewStateData =
            case ObjDriven of
                [_|_] ->
                    NewObjsDriven = objs_driven_update(ObjId, ObjDriven, ObjsDriven),
                    StateData#mproc_state{
                        objs_driven = NewObjsDriven
                        ,all_obj_ids = [ObjId | AllObjIds]
                    };
                _ ->
                    StateData#mproc_state{
                        all_obj_ids = [ObjId | AllObjIds]
                    }
            end,
            StateDataUpdateTimer = calc_state_timer(NewStateData),
            {noreply, StateDataUpdateTimer}
    end;
after_create(_OtherInitResult, _StateData) ->
    ?ERR("create mon error:~w", [_OtherInitResult]),
    {noreply, _StateData}.

%% --------------------------------------------------------
%% 针对批量怪物的消息的处理
%% --------------------------------------------------------

%% -> NewStateData
handle_batch_obj_info(ObjIdList, Info, StateData) ->
    lists:foldl(fun(ObjId, StateDataAcc) ->
                try
                    driving_monster_event(ObjId, Info, StateDataAcc)
                catch
                    ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
                        ?ERR("ObjId[~w] handle [~p] error: {~w,~w}~n  ~p",
                            [ObjId, Info, _Class, _Reason, ?GET_STACK(_Stacktrace)]
                        ),
                        StateDataAcc
                end
        end, StateData, ObjIdList).

%% --------------------------------------------------------
%% 怪物逻辑驱动管理
%% --------------------------------------------------------
%% 怪物的逻辑处理有两类驱动方式：
%% 1. 外部info触发驱动
%%    一般指收到外部进程发送的怪物变更/交互操作消息，
%%    此时，一般调用mon_state_logic:handle_info/3进行处理
%%    也包含旧版怪物进程内部通过erlang:send/2之类发送的消息处理
%% 2. 内部自驱动
%%    在这里指由本模块实例进程根据怪物状态，不定期统一进行的行为管理，
%%    对应于旧版mod_mon_active中的gen_fsm:send_event_after/2驱动，
%%    此时，一般由相应的怪物状态函数进行逻辑处理
%% --------------------------------------------------------

%% 状态驱动事件——驱动批量怪物执行相关事件处理
driving_monsters_state(NowTimeMS, [{TimeMS, ObjId} | RemainObjsDriven], StateDataAcc) ->
    case TimeMS =< NowTimeMS of
        true  ->
            %% 检查指定怪物的所有Driven
            TmpStateDataAcc = StateDataAcc#mproc_state{objs_driven = RemainObjsDriven},
            NewStateDataAcc = driving_monster_state(ObjId, TmpStateDataAcc, NowTimeMS),
            #mproc_state{objs_driven = NewObjsDriven} = NewStateDataAcc,
            driving_monsters_state(NowTimeMS, NewObjsDriven, NewStateDataAcc);
        false ->
            %% ObjsDriven是一个有序列表，碰到时间未到的，直接结束检查
            StateDataAcc
    end;
driving_monsters_state(_NowTimeMS, [_ | RemainObjsDriven], StateDataAcc) ->
    %% 丢弃无效驱动数据
    NewStateDataAcc = StateDataAcc#mproc_state{objs_driven = RemainObjsDriven},
    driving_monsters_state(_NowTimeMS, RemainObjsDriven, NewStateDataAcc);
driving_monsters_state(_NowTimeMS, [], StateDataAcc) ->
    StateDataAcc.

%% 状态超时事件驱动怪物
%% -> NewStateData
%% 关于driving_monster_state接口，需要注意：
%% 1. 传入的StateData，需要去除当前事件再传入
%% 2. 内部可能一次处理多个驱动消息，返回数据需正确处理objs_driven
driving_monster_state(ObjId, StateData, NowTimeMS) ->
    #mproc_state{all_obj_ids = ObjIds, objs_driven = ObjsDriven} = StateData,
    case lists:member(ObjId, ObjIds) of
        true  ->
            ObjStateData = proc_get_obj_state(ObjId),
            case ObjStateData of
                #obj_state_data{obj_state = ObjState, obj_data = ObjData} ->
                    driving_monster_state_events(ObjState, ObjData, StateData, NowTimeMS);
                _ ->
                    %% 异常：丢失数据
                    lose_obj_data(StateData, ObjId)
            end;
        false ->
            StateData#mproc_state{
                objs_driven = objs_driven_remove(ObjId, ObjsDriven)
            }
    end.

%% 因为收集了一批驱动消息再统一执行，
%% 所以每次执行前要先检查一下哪个事件要先执行
driving_monster_state_events(ObjState, ObjData, StateData, NowTimeMS) ->
    #ob_act{state_driven = StateDriven, general_driven = GeneralDriven} = ObjData,
    case find_next_driven(StateDriven, GeneralDriven) of
        %% 状态驱动事件处理
        {state_driven, TimeMS, Info} when TimeMS =< NowTimeMS ->
            ObjId = ObjData#ob_act.object#scene_object.id,
            %% 先去除本次已触发的状态驱动事件
            TmpObjData = ObjData#ob_act{state_driven = ?EMPTY_STATE_DRIVEN},
            case driving_monster_do(state, ObjId, Info, ObjState, TmpObjData, StateData) of
                {terminate, NewStateData} ->
                    NewStateData;
                {NewObjState, NewObjData, NewStateData} ->
                    driving_monster_state_events(NewObjState, NewObjData, NewStateData, NowTimeMS)
            end;
        {state_driven, _TimeMS, _Info} ->
            %% 时机未至，怪兽先歇着吧
            StateData;
        %% 通用驱动事件处理
        {general_driven, TimeMS, Info, RemainGeneralDriven} when TimeMS =< NowTimeMS  ->
            ObjId = ObjData#ob_act.object#scene_object.id,
            %% 先去除此条已触发的驱动消息
            TmpObjData = ObjData#ob_act{general_driven = RemainGeneralDriven},
            case driving_monster_do(event, ObjId, Info, ObjState, TmpObjData, StateData) of
                {terminate, NewStateData} ->
                    NewStateData;
                {NewObjState, NewObjData, NewStateData} ->
                    driving_monster_state_events(NewObjState, NewObjData, NewStateData, NowTimeMS)
            end;
        {general_driven, _TimeMS, _Info, _Remain} ->
            %% 时机未至，怪兽先歇着吧
            StateData;
        %% 怪兽已经休息了
        _Sleep ->
            StateData
    end.

%% 外部消息驱动怪物
driving_monster_event(ObjId, Info, StateData) ->
    #mproc_state{all_obj_ids = ObjIds} = StateData,
    case lists:member(ObjId, ObjIds) of
        true  ->
            ObjStateData = proc_get_obj_state(ObjId),
            case ObjStateData of
                #obj_state_data{
                    obj_data = ObjData,
                    obj_state = ObjState
                } ->
                    case driving_monster_do(event, ObjId, Info, ObjState, ObjData, StateData) of
                        {_, _, NewStateData}      -> NewStateData;
                        {terminate, NewStateData} -> NewStateData
                    end;
                _ ->
                    %% 异常：丢失数据
                    lose_obj_data(StateData, ObjId)
            end;
        false ->
            StateData
    end.

%% 驱动怪物进行单个事件处理：怪物逻辑常规事件/请求处理
%% 怪物运行 -> {NewObjState, NewObjData, NewStateData}
%% 怪物结束 -> {terminate, NewStateData}
%% 关于参数Type：
%% --------------------------------------------------------
%% 来源             Type值  调用接口
%% --------------------------------------------------------
%% state_driven     state   mon_state_logic:ObjStateName/2
%% general_driven   event   mon_state_logic:handle_info/3
%% 外部'!'消息      event   mon_state_logic:handle_info/3
%% --------------------------------------------------------
%% 注意：
%% driving_monster_do内部需要完成对ObjData数据的更新保存
%% 实际上，上一层传参数前去除了状态/事件驱动数据，所以数据一定有变化，
%% 在外部做保存还需要额外做数据对比，所以统一由此接口做数据更新保存
%% 关于避免无限循环问题的设计：
%% 上层已经去掉StateData的本次事件，可以避免在出错时无限重复本次事件
%% 上层已经去掉对象的driven数据，内部统一保存对象数据，避免下次继续执行本次驱动消息
driving_monster_do(Type, ObjId, Info, ObjState, ObjData, StateData) ->
    #ob_act{object = #scene_object{sign = BattleSign}} = ObjData,
    CallbackMod =
    case BattleSign of
        ?BATTLE_SIGN_MON   -> mon_state_logic;
        ?BATTLE_SIGN_DUMMY -> dummy_state_logic;
        _                  -> undefined
    end,
    case CallbackMod of
        undefined ->
            %% 未定义处理操作的场景对象，保存数据即可
            %% （不删除数据是因为无必要，也为未来可能扩展场景对象做预留）
            % ?ERR("unknown scene object sign"),
            update_after_monster_do(ObjId, ObjState, ObjData, StateData);
        _ ->
            %% 捕捉异常，避免在状态超时遍历的时候因一个怪物异常影响到
            %% 整个场景的怪物逻辑的触发
            try
                Result = case Type of
                    event -> CallbackMod:handle_info(Info, ObjState, ObjData);
                    state -> CallbackMod:ObjState(Info, ObjData);
                    _     -> ignore
                end,
                CallbackResult = trans_callback_result(Result),
                case CallbackResult of
                    {next_state, NewObjState, NewObjData} ->
                        update_after_monster_do(ObjId, NewObjState, NewObjData, StateData);
                    {stop, StopReason, NewObjData} ->
                        terminate_obj(CallbackMod, StopReason, ObjState, NewObjData),
                        NewStateData = state_data_remove_obj(StateData, ObjId),
                        {terminate, NewStateData};
                    ignore ->
                        update_after_monster_do(ObjId, ObjState, ObjData, StateData);
                    {error, Other} ->
                        %% 异常：场景对象处理数据异常
                        %% FIXME 有无必要terminate掉场景对象？
                        ?ERR("scene object logic exception: ~p", [Other]),
                        update_after_monster_do(ObjId, ObjState, ObjData, StateData)
                end
            catch
                ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
                    ?ERR("Info[~w,~w], ObjId:~w, ObjState:~w, error:~w,~w,~n"
                        "~p",
                        [
                            Type, Info, ObjId, ObjState, _Class, _Reason,
                            ?GET_STACK(_Stacktrace)
                        ]
                    ),
                    update_after_monster_do(ObjId, ObjState, ObjData, StateData)
            end
    end.

%% 注意，传入的ObjData，其general_driven必须是已经排序的
%% 实际上，
%% monster_do回调操作后，trans_callback_result会处理排序
%% 而传入driving_monster_do的ObjData本身也是上一次操作的
%% 有序数据（即monster_do对ObjData无更新时），所以这个顺序是可以保证的，
%% 如果有特殊的逻辑，需要在逻辑中针对做好处理，本接口内部不做二次排序
update_after_monster_do(ObjId, ObjState, ObjData, StateData) ->
    proc_put_obj_state(ObjState, ObjData),
    ObjDriven = extract_driven(ObjData),
    #mproc_state{objs_driven = ObjsDriven} = StateData,
    NewObjsDriven = objs_driven_update(ObjId, ObjDriven, ObjsDriven),
    NewStateData = StateData#mproc_state{
        objs_driven = NewObjsDriven 
    },
    {ObjState, ObjData, NewStateData}.

%% 计算状态定时器变化
%% -> NewStateData
calc_state_timer(StateData) ->
    #mproc_state{objs_driven = ObjsDriven} = StateData,
    NextTimeout = find_next_timeout(ObjsDriven),
    reset_state_timer(StateData, NextTimeout).

reset_state_timer(StateData, NextTimeout) ->
    #mproc_state{state_timer = OldTimer} = StateData,
    case is_integer(NextTimeout) of
        true  ->
            util:cancel_timer(OldTimer),
            NewTimer = erlang:send_after(NextTimeout, self(), driven_check),
            StateData#mproc_state{state_timer = NewTimer};
        false ->
            StateData
    end.

%% -> NextTimeout | false.
find_next_timeout([{TimeMS, _ObjId} | _]) ->
    NowTimeMS  = utime:unixtime(ms),
    RemainTime = TimeMS - NowTimeMS,
    if
        %% 已经过期的时间，则立即触发
        RemainTime =< 0 -> 0;
        %% 小于指定毫秒数的，以指定毫秒数为超时值，
        %% 在场景怪物比较多或事件较多的情况下，可以收集一批统一执行，减少进程消息数
        RemainTime =< ?INFO_DELAY -> ?INFO_DELAY;
        true -> RemainTime
    end;
find_next_timeout([]) ->
    false.

%% 选择时间最早的一条驱动消息
%% 返回的TimeMS，一定是最小的时间，外部可以用于判断是否到达执行时间
find_next_driven(?EMPTY_STATE_DRIVEN, [{TimeMS, Info} | RemainGeneralDriven]) ->
    {general_driven, TimeMS, Info, RemainGeneralDriven};
find_next_driven(?EMPTY_STATE_DRIVEN, []) ->
    sleep;
find_next_driven({TimeMS, Info}, []) ->
    {state_driven, TimeMS, Info};
find_next_driven({TimeMS1, Info1}, [{TimeMS2, Info2} | RemainGeneralDriven]) ->
    case TimeMS1 =< TimeMS2 of
        true  -> {state_driven, TimeMS1, Info1};
        false -> {general_driven, TimeMS2, Info2, RemainGeneralDriven}
    end.

lose_obj_data(StateData, LoseObjId) ->
    ?ERR("lose object data, objId:~w", [LoseObjId]),
    state_data_remove_obj(StateData, LoseObjId).

state_data_remove_obj(StateData, ObjId) ->
    #mproc_state{all_obj_ids = ObjIds, objs_driven = ObjsDriven} = StateData,
    StateData#mproc_state{
        all_obj_ids = lists:delete(ObjId, ObjIds),
        objs_driven = objs_driven_remove(ObjId, ObjsDriven)
    }.

%% --------------------------------------------------------
%% objs_driven数据管理接口
%% ObjsDriven是一个有序lists，
%% 由于每个怪物只取一个数据，所以实际上ObjsDriven就是一个ordsets，
%% 目前版本ordsets是基于lists的实现，所以直接使用了部分ordsets的接口
%%
%% ObjsDriven :: [{TimeMS, ObjId}]
%% 且每个怪物只记录一个未来时间最早的数据
%% 所有接口需要留意这一设计，保证数据只有一条，否则不正确
%% --------------------------------------------------------

%% 删除Obj对应的数据记录
objs_driven_remove(ObjId, ObjsDriven) ->
    lists:keydelete(ObjId, 2, ObjsDriven).

%% 使用当前ObjDriven更新ObjsDriven，可能发生数据新增、删除、替换
%% 注意，ObjDriven必须是该Obj全部的Actions
objs_driven_update(ObjId, ObjDriven = [_|_], ObjsDriven) ->
    {TimeMS, _Info} = ulists:keymin(1, ObjDriven),
    NewObjsDriven = objs_driven_remove(ObjId, ObjsDriven),
    ordsets:add_element({TimeMS, ObjId}, NewObjsDriven);
objs_driven_update(ObjId, [], ObjsDriven) ->
    objs_driven_remove(ObjId, ObjsDriven).

%% --------------------------------------------------------

%% 场景对象中止时调用以执行清理操作，
%% 内部回调CallbackMod:terminate/2，及清理进程字典数据
terminate_obj(CallbackMod, StopReason, ObjState, ObjData) ->
    #ob_act{object = #scene_object{id = ObjId, config_id = ConfigId}} = ObjData,
    try
        CallbackMod:terminate(StopReason, ObjState, ObjData)
    catch
        ?EXCEPTION(_Class, _Reason, _Stacktrace) ->
            ?ERR("terminate_obj exception:~w,~w,~n~p", [_Class, _Reason, ?GET_STACK(_Stacktrace)])
    end,
    proc_erase_obj_state(ObjId),
    case StopReason of
        normal -> ignore;
        _ -> ?ERR("scene obj [id:~w,config_id:~w] stop exception: ~p", [ObjId, ConfigId, StopReason])
    end,
    ok.

gm_get_mon_info(Scene, Pool, CopyId, Arg) ->
    LocalName = mon_mgr_proc_name(Scene, Pool),
    gen_statem:call(LocalName, {gm, ?MODULE, catch_gm_message, [CopyId, Arg]}).

catch_gm_message(StateData, CopyId, Arg) ->
    #mproc_state{all_obj_ids = ObjIds, scene_id = SceneId, pool_id = PoolId} = StateData,
    Mons = include_info(CopyId, ObjIds, Arg, []),
    case Arg of
        position ->
            mod_scene_agent:apply_call(SceneId, PoolId, ?MODULE, gm_get_scene_mon_info, [Mons, all]);
        position_df ->
            mod_scene_agent:apply_call(SceneId, PoolId, ?MODULE, gm_get_scene_mon_info, [Mons, diff]);
        _ ->
            Mons
    end.

include_info(_CopyId, [], _Arg, List) -> List;
include_info(CopyId, [Id|T], Arg, List) ->
    case proc_get_obj_state(Id) of
        #obj_state_data{
            obj_data = #ob_act{object = #scene_object{copy_id = TheCopy, battle_attr = BattleAttr} = SceneObject} = Obj,
            obj_state = _ObjState
        } when TheCopy =:= CopyId ->
            case Arg of
                all ->
                    include_info(CopyId, T, Arg, [Obj|List]);
                position ->
                    include_info(CopyId, T, Arg,
                        [{SceneObject#scene_object.id,SceneObject#scene_object.config_id,SceneObject#scene_object.x,SceneObject#scene_object.y }|List]);
                position_df ->
                    include_info(CopyId, T, Arg,
                    [{SceneObject#scene_object.id,SceneObject#scene_object.config_id,SceneObject#scene_object.x,SceneObject#scene_object.y }|List]);
                num ->
                    include_info(CopyId, T, Arg,
                    [{SceneObject#scene_object.id,SceneObject#scene_object.config_id}|List]);
                {ob_act, Pos} ->
                    include_info(CopyId, T, Arg,
                    [{SceneObject#scene_object.id, erlang:element(Pos, Obj)}|List]);
                {ob_act, MonId, Pos} when MonId == Id ->
                    include_info(CopyId, T, Arg,
                        [{SceneObject#scene_object.id, erlang:element(Pos, Obj)}|List]);
                {object, MonId, Pos} when MonId == Id ->
                    include_info(CopyId, T, Arg,
                    [{SceneObject#scene_object.id, erlang:element(Pos, SceneObject)}|List]);
                {object, Pos} ->
                    include_info(CopyId, T, Arg,
                        [{SceneObject#scene_object.id, erlang:element(Pos, SceneObject)}|List]);
                {mon_type, MonType, all} ->
                    if
                        SceneObject#scene_object.sign == MonType ->
                            include_info(CopyId, T, Arg, [Obj|List]);
                        true ->
                            include_info(CopyId, T, Arg, List)
                    end;
                {mon_type, MonType, Pos} ->
                    if
                        SceneObject#scene_object.sign == MonType ->
                            include_info(CopyId, T, Arg,
                        [{SceneObject#scene_object.id, SceneObject#scene_object.figure#figure.id, erlang:element(Pos, SceneObject)}|List]);
                        true ->
                            include_info(CopyId, T, Arg, List)
                    end;
                attr ->
                    include_info(CopyId, T, Arg,
                        [#{
                            id => Id, mon_id => SceneObject#scene_object.config_id,
                            mon_attr => BattleAttr#battle_attr.attr
                        } |List]);
                _ ->
                    include_info(CopyId, T, Arg, List)
            end;
        _ ->
            include_info(CopyId, T, Arg, List)
    end.

gm_get_scene_mon_info(Mons, diff) ->
    lists:foldl(
        fun({Id, ConfigId, X, Y}, Temp) ->
            case lib_scene_object_agent:get_object(Id) of
                #scene_object{x = SceneX, y = SceneY} when X =/= SceneX orelse Y =/= SceneY->
                    [{Id, ConfigId, #{scene_xy => {SceneX, SceneY}, mgr_xy => {X, Y}}}|Temp];
                _ ->
                    Temp
            end
        end, [], Mons
    );
gm_get_scene_mon_info(Mons, all) ->
    lists:foldl(
        fun({Id, ConfigId, X, Y}, Temp) ->
            case lib_scene_object_agent:get_object(Id) of
                #scene_object{x = SceneX, y = SceneY} ->
                    [{Id, ConfigId, #{scene_xy => {SceneX, SceneY}, mgr_xy => {X, Y}}}|Temp];
                _ ->
                    [{Id, ConfigId, #{scene_xy => missing, mgr_xy => {X, Y}}}|Temp]
            end
        end, [], Mons
    ).
