%%%-------------------------------------------------------------------
%%% @doc     宝宝副本
%%% Module  : lib_dungeon_child.erl
%%% Created : 2020-08-24
%%% @Author : tyl
%%%-------------------------------------------------------------------
-module(lib_dungeon_child).

-include("dungeon.hrl").
-include("common.hrl").
-include("goods.hrl").
-include("def_goods.hrl").
-include("def_module.hrl").
-include("predefine.hrl").
-include("errcode.hrl").
-include("server.hrl").
-include("scene.hrl").
-include("battle.hrl").
-include("team.hrl").
-include("attr.hrl").
-include("dungeon_child.hrl").
-include("rec_event.hrl").
-include("def_event.hrl").
-include("figure.hrl").
-include("skill.hrl").

%% API
-export([
	%% 副本相关
	get_role_rec/6
	,get_start_dun_args/4
	,handle_enter_dungeon/2
	,dispatch_dun_event/2
	% ,handle_wave_state/2
	,handle_kill_mon_do/2
	,check_quit_state/2
	,get_send_reward/2
	,pack_account_kv_data/2
	,wave_timeout_check/1
	,handle_dungeon_result/1
	,calc_box_mon_reward/2

	%% 怪物
	,get_born_pos/1
	,get_born_spot/1
	,create_mon_path/2
	,mon_with_new_path/1
	,get_create_mon_args/1
	,trace_to_att/9
	,check_run_away_mon/1
	,timer_check_act_skill/3
	,when_walk_mon_born/3
	,record_walk_mon/2
	,when_walk_mon_hp_change/3
	,record_role_energy/2
	,when_box_mon_die/5
	,record_box_mon_reward/2
	,change_energy_on_copy/2

	%% 玩家操作
	,check_use_dun_child_skill/2    %% 出手判断
	,cost_energy/2                  %% 消耗能力
	,create_tower/2                 %% 创建塔怪
	,clean_tower_cd/2               %% 清除塔怪cd
	,update_role_best_record/4      %% 更新玩家最高波数记录
	,sweep_dun_child/1              %% 扫荡副本

	,reconnect/2
	,reconnect_on_player/3

	,gm_set_max/1
]).

%% 进入副本(赋值最大通关数)
handle_enter_dungeon(Player, Dun) ->
	#player_status{dungeon_record = DungeonRecs, role_dun_child = RoleDunChild} = Player,
	RoleDunChild1 = trans_role_dun_child(RoleDunChild),
	#dun_cfg{id = DunId} = Dun,
	DungeonRec = maps:get(DunId, DungeonRecs, []),
	case lists:keyfind(?DUNGEON_REC_MAX_SCORE, 1, DungeonRec) of
		{_, MaxDunWave} -> ok;
		_ -> MaxDunWave = 0
	end,
	NewRoleDunChild = RoleDunChild1#role_dun_child{
		energy_pot = 0, wave = MaxDunWave
	},
	Player#player_status{role_dun_child = NewRoleDunChild}.

%% 进入副本前 -- 玩家进程下获取副本记录
get_role_rec(_DunId, DunType, RoleId, DungeonRec, _DunData, TypicalData) ->
	case lists:keyfind(?DUNGEON_REC_MAX_SCORE, 1, DungeonRec) of
		{_, MaxDunWave} -> ok;
		_ -> MaxDunWave = 0
	end,
	DailyMaxWave = lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_PASS_LV, DunType),
	%% 历史最高波数(往后打会变动)
	NewTypicalData  = maps:put(?DUNGEON_REC_MAX_SCORE, MaxDunWave, TypicalData),
	%% 今日通关波数
	NewTypicalData1 = maps:put(?DUNGEON_REC_DAILY_MAX_SCORE, DailyMaxWave, NewTypicalData),
	%% 初始波数(日最高波数+1)
	NewTypicalData2 = maps:put(?DUN_ROLE_CHILD_INIT_WAVE, DailyMaxWave+1, NewTypicalData1),
	%% 记录历史最高波数(本次副本不会变动)
	NewTypicalData3 = maps:put(?DUN_ROLE_CHILD_MAX_WAVE, MaxDunWave, NewTypicalData2),
	NewTypicalData3.

%% 进去副本前 -- 获取副本的开始波数
get_start_dun_args(_Player, DunRole, _DunCfg, _OptionType) ->
	#dungeon_role{typical_data = TypicalData} = DunRole,
	Wave = maps:get(?DUNGEON_REC_DAILY_MAX_SCORE, TypicalData, 1),
	%% 从每日最大波数+1开始下一次副本
	[{wave, Wave+1}].

%% 初始波数时记录本波怪物数
dispatch_dun_event(Msg = {'init_dun_wave', Wave}, State) ->
	#dungeon_state{
		dun_id = DunId
		,role_list = [DungeonRole|_]
	} = State,
	WaveNum1 = case Wave of
					0 -> 1;
					_ -> Wave
	           end,
	case data_dungeon_wave:get_dun_wave(DunId, WaveNum1) of
		#base_dun_wave_cfg{mon_list = MonList} ->
			MonList1 = [MonId ||{MonId, _TX, _TY, _Ratio, _Args} <- MonList],
			LeftNum = erlang:length(MonList1);
		_ ->
			LeftNum = ?DEFAULT_DUN_CHILD_MON
	end,
	#dungeon_role{typical_data = TypicalData} = DungeonRole,
	%% 剩余怪物需要把上一波也计算在内
	OldLeftNum = maps:get(?DUN_ROLE_CHILD_LEFT_MON, TypicalData, 0),
	NewTypicalData  = maps:put(?DUN_ROLE_CHILD_LEFT_MON, OldLeftNum+LeftNum, TypicalData),
	NewDungeonRole  = DungeonRole#dungeon_role{typical_data = NewTypicalData},
	%% 新一波开始前(证明前一波顺利通过)更新当天最大波数 和 历史最大波数
	% NewDungeonRole1 = update_max_dun_wave(DunId, max(0, Wave-1), NewDungeonRole),
	NewState = State#dungeon_state{role_list = [NewDungeonRole]},
	%% 更新第一波首通时间
	% lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, update_dun_data, [max(0, Wave-1)]),
	%% 波数怪默认处理
	dungeon_default:dispatch_dun_event(Msg, NewState);
%% 其他信息默认处理
dispatch_dun_event(Msg, State) ->
	dungeon_default:dispatch_dun_event(Msg, State).

%% 怪物生成额外参数
get_create_mon_args(_State) ->
	% BornHandler = {?MODULE, when_walk_mon_born, [self()]},
	% ExtraArgs = [{born_handler, BornHandler}],
	[].

%% 波数怪刷新后推送创怪时间
%%check_notice_wave_mon(RoleId, MonInfoList, Wave) ->
%%	CfgNum = erlang:length(data_dungeon_child:get_wave_mon_run_time(Wave)),
%%	WaveCreateMon = [TMonId0||{TMonId0, TWave0, _CreateNum, _CreateTime}<-MonInfoList, TWave0 == Wave],
%%	case erlang:length(WaveCreateMon) >= CfgNum of
%%		true ->
%%			PackList = [{TWave, CreateNum, MonId, CreateTime}||{MonId, TWave, CreateNum, CreateTime}<-MonInfoList, TWave == Wave],
%%			lib_server_send:send_to_uid(RoleId, pt_611, 61111, [PackList]);
%%		_ ->
%%			ignore
%%	end,
%%	ok.

%% 获取出生点
get_born_pos(AutoId) ->
	BornXys = data_dungeon_child:get_dun_child_kv_cfg(mon_born),
	SpotNum = AutoId rem erlang:length(BornXys),
	case lists:keyfind(SpotNum+1, 1, BornXys) of
		{_, _, [X, Y]} -> {X, Y};
		_ -> {3024, 4246}                        %% todo: 默认坐标
	end.

%% 获取出生点位
get_born_spot(AutoId) ->
	BornXys = data_dungeon_child:get_dun_child_kv_cfg(mon_born),
	SpotNum = AutoId rem erlang:length(BornXys),
	SpotNum + 1.

%% 创建寻路路径
create_mon_path({X, Y}, MonId) ->
	case data_mon:get(MonId) of
		#mon{speed = Speed} -> ok;
		_ -> Speed = 50
	end,
	BornXys   = data_dungeon_child:get_dun_child_kv_cfg(mon_born),
	DeathXys  = data_dungeon_child:get_dun_child_kv_cfg(mon_death),
	F = fun(SpotInfo, TSpot) ->
		case SpotInfo of
			{_, EndSpot,[X, Y]} -> EndSpot;
			_ -> TSpot
		end end,
	SpotNum =
	case lists:foldl(F, 0, BornXys) of
		TSpotNum when TSpotNum > 0 -> TSpotNum;
		_ -> 1
	end,
	case lists:keyfind(SpotNum, 1, DeathXys) of
		{_, [TX, TY]} -> ok;
		_ -> TX = 1168, TY = 2005       %% todo: 默认坐标
	end,
	Path = lib_scene_object_ai:dest_path(X, Y, TX, TY, Speed),
	Path.

create_path_with_dest_spot(SpotNum, {X, Y}, MonId) ->
	case data_mon:get(MonId) of
		#mon{speed = Speed} -> ok;
		_ -> Speed = 50
	end,
	DeathXys  = data_dungeon_child:get_dun_child_kv_cfg(mon_death),
	case lists:keyfind(SpotNum, 1, DeathXys) of
		{_, [TX, TY]} -> ok;
		_ -> TX = 1168, TY = 2005       %% todo: 默认坐标
	end,
	Path = lib_scene_object_ai:dest_path(X, Y, TX, TY, Speed),
	Path.

%% 重新规划路线
mon_with_new_path(State) ->
	#ob_act{object = SceneObject} = State,
	#scene_object{config_id = MonId, x = X, y = Y, pos = SpotNum} = SceneObject,
	NewPath = create_path_with_dest_spot(SpotNum, {X, Y}, MonId),
	State#ob_act{path = NewPath}.

trace_to_att(State, _TX, _TY, _BattleArgs,
		_NextAttTime, NextMoveTime, NowTime,
		_SkillLinkInfo, _CheckBlock) when NextMoveTime > NowTime -> %% 下一次行动
	% old: Ref1 = lib_scene_object:send_event_after(Ref, NextMoveTime-NowTime, repeat),
	StateDriven = mon_state_util:conv_delay_driven({3000, repeat}),
	{next_state, walk, State#ob_act{state_driven = StateDriven}};

trace_to_att(State, TX, TY, BattleArgs, NextAttTime, _NextMoveTime, NowTime, SkillLinkInfo, CheckBlock) ->
	#ob_act{object = SceneObject} = State,
	#scene_object{sign = ObjectSign, id = ObjectId, aid = Aid,
		scene = SceneId, scene_pool_id = ScenePoolId, x = X, y = Y, copy_id = CopyId,
		battle_attr = BA, att_time = GapAttTime,
		skill = SkillL, temp_skill = TempSkillL, skill_cd = SkillCd,
		striking_distance = StrikingDistance, att_type = AttType} = SceneObject,
	IsSilence = lib_skill_buff:is_silence(BA#battle_attr.other_buff_list, NowTime),
	case lib_scene_object_ai:select_a_skill(TempSkillL++SkillL, SkillCd, StrikingDistance, SkillLinkInfo, IsSilence, AttType) of
		{false, _NextSelectTime} ->
			% old: Ref1 = lib_scene_object:send_event_after(Ref, NextSelectTime+50, repeat),
			StateDriven = mon_state_util:conv_delay_driven({3000, repeat}),
			{next_state, walk, State#ob_act{state_driven = StateDriven}};
		%% 辅助技能
		{SkillId, SkillLv, _, ?SKILL_TYPE_ASSIST, IsReFindT, SpellTime, _, _} ->
			SelectedSkillInfo
				= #selected_skill{
				att_time = NowTime+GapAttTime,
				skill_id = SkillId,
				spell_time = SpellTime,
				is_refind_t = IsReFindT
			},
			mod_scene_agent:apply_cast_with_scene(SceneId, ScenePoolId, lib_battle, object_assist_cast, [Aid, ObjectId, SkillId, SkillLv, []]),
			NewStateDriven = mon_state_util:conv_delay_driven({5000, repeat}, NowTime),
			SelectedSkillMap  = State#ob_act.selected_skill,
			SelectedSkillMap1 = SelectedSkillMap#{SkillId => SelectedSkillInfo},
			{next_state, walk, State#ob_act{state_driven = NewStateDriven, selected_skill = SelectedSkillMap1}};
		%% 主动技能
		{SkillId, SkillLv, SkillDistance, ?SKILL_TYPE_ACTIVE, IsReFindT, SpellTime, SkillLinkInfo1, _IsNormal} ->
			%% 判断是否可以攻击
			IsNormal = lib_skill_api:is_normal(SkillId),
			case lib_scene_object_ai:get_next_step(X, Y, SkillDistance, SceneId, CopyId, TX, TY, CheckBlock) of
				attack when NowTime > NextAttTime ->
					case lib_skill_buff:is_can_attack(IsNormal, ObjectSign, BA#battle_attr.other_buff_list, NowTime) of
						{false, _K, _AttWaitMs} ->
							StateDriven = mon_state_util:conv_delay_driven({3000, repeat}),
							{next_state, walk, State#ob_act{state_driven = StateDriven}};
						true ->
							SelectedSkillInfo
								= #selected_skill{
								att_time = NowTime+GapAttTime,
								skill_id = SkillId,
								spell_time = SpellTime,
								is_refind_t = IsReFindT,
								link_info = SkillLinkInfo1
							},
							SelectOption = lib_scene_object_ai:get_select_option(SceneObject),
							mod_scene_agent:apply_cast_with_scene(SceneId, ScenePoolId, lib_battle, object_battle_cast, [Aid, ObjectSign, ObjectId, BattleArgs, SkillId, SkillLv, [], 0, SelectOption]),
							% old: Ref1 = lib_scene_object:send_event_after(Ref, 5000, repeat),
							NewStateDriven = mon_state_util:conv_delay_driven({5000, repeat}, NowTime),
							SelectedSkillMap  = State#ob_act.selected_skill,
							SelectedSkillMap1 = SelectedSkillMap#{SkillId => SelectedSkillInfo},
							NewState = State#ob_act{selected_skill = SelectedSkillMap1, state_driven = NewStateDriven},
							{next_state, walk, NewState}
					end;

				%% 没有到出手时间
				attack ->
					% old: Ref1 = lib_scene_object:send_event_after(Ref, max(200, NextAttTime-NowTime), repeat),
					% DelayTime = max(200, NextAttTime - NowTime),
					StateDriven = mon_state_util:conv_delay_driven({3000, repeat}),
					{next_state, walk, State#ob_act{state_driven = StateDriven}};

				%% 移动
				{_NextX, _NextY} ->
					StateDriven = mon_state_util:conv_delay_driven({3000, repeat}),
					%mon_with_new_path(State),
					{next_state, walk, State#ob_act{state_driven = StateDriven}}
			end;
		_R ->
			StateDriven = mon_state_util:conv_delay_driven({3000, repeat}),
			%mon_with_new_path(State),
			{next_state, walk, State#ob_act{state_driven = StateDriven}}
	end.

%% =============================================================
%% @doc 记录走路怪列表和创建时间 @end
%% =============================================================
when_walk_mon_born(init, #scene_object{id = MonId}, [CopyId, Wave, CreateNum,NowTime]) ->
	mod_dungeon:apply(CopyId, ?MODULE, record_walk_mon, [MonId, Wave, CreateNum, NowTime]),
	ok.

record_walk_mon(DungeonState, [MonId, Wave, CreateNum, NowTime]) ->
	#dungeon_state{role_list = [DungeonRole|_]} = DungeonState,
	#dungeon_role{typical_data = TypicalData} = DungeonRole,
	MonList = maps:get(?DUN_ROLE_CHILD_WALK_MON, TypicalData, []),
	MonInfoList = maps:get(?DUN_ROLE_CHILD_WALK_MON_TIME, TypicalData, []),
	NewTypicalData  = maps:put(?DUN_ROLE_CHILD_WALK_MON, [MonId|MonList], TypicalData),
	NewMonInfoList  = [{MonId, Wave, CreateNum, NowTime}|MonInfoList],
	% check_notice_wave_mon(RoleId, NewMonInfoList, Wave),
	NewTypicalData1 = maps:put(?DUN_ROLE_CHILD_WALK_MON_TIME, NewMonInfoList, NewTypicalData),
	NewDungeonRole  = DungeonRole#dungeon_role{typical_data = NewTypicalData1},
	NewDungeonState = DungeonState#dungeon_state{role_list = [NewDungeonRole]},
	NewDungeonState.

%% =============================================================
%% @doc 血量变更，生成能量球 @end
%% =============================================================
when_walk_mon_hp_change(Object, MonAtter, _) ->
	#ob_act{
		object = #scene_object{
			battle_attr = #battle_attr{hp = Hp}
		}
	} = Object,
	#mon_atter{id = RoleId} = MonAtter,
	[Num, Energy] = data_dungeon_child:get_dun_child_kv_cfg(fall_energy),
	case Num > 0 andalso Energy > 0 of
		true when Hp >= 0 ->
			lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, record_role_energy, [Num*Energy]);
		_ ->
			ignore
	end,
	ok.

%% 记录能量
record_role_energy(Player, AddEnergy) when AddEnergy > 0 ->
	#player_status{id = RoleId, role_dun_child = RoleDunChild, copy_id = CopyId} = Player,
	RoleDunChild1 = trans_role_dun_child(RoleDunChild),
	#role_dun_child{energy_pot = OldEnergy} = RoleDunChild1,
	CfgMax = data_dungeon_child:get_dun_child_kv_cfg(energy_max),
	NewRoleDunChild =
	case OldEnergy < CfgMax of
		true ->
			NewEnergy = min(OldEnergy + AddEnergy, CfgMax),
			mod_dungeon:apply(CopyId, ?MODULE, change_energy_on_copy, [NewEnergy]),
			RoleDunChild1#role_dun_child{energy_pot = NewEnergy};
		_ ->
			RoleDunChild1
	end,
	lib_server_send:send_to_uid(RoleId, pt_611, 61120, [NewRoleDunChild#role_dun_child.energy_pot]),
	{ok, Player#player_status{role_dun_child = NewRoleDunChild}};
record_role_energy(Player, _) ->
	{ok, Player}.

change_energy_on_copy(DungeonState, [Energy]) ->
	#dungeon_state{role_list = [DungeonRole|_]} = DungeonState,
	#dungeon_role{typical_data = TypicalData} = DungeonRole,
	NewTypicalData = maps:put(?DUN_ROLE_CHILD_ENERGY, Energy, TypicalData),
	NewDungeonRole = DungeonRole#dungeon_role{typical_data = NewTypicalData},
	DungeonState#dungeon_state{role_list = [NewDungeonRole]}.

%% =============================================================
%% @doc 宝箱怪死亡，记录奖励 @end
%% =============================================================
when_box_mon_die(_State, _Klist, _Atter, _AtterSign, [CopyId, WaveNum]) ->
	mod_dungeon:apply(CopyId, ?MODULE, record_box_mon_reward, [WaveNum]),
	ok.

record_box_mon_reward(DungeonState, WaveNum) ->
	#dungeon_state{role_list = [DungeonRole|_]} = DungeonState,
	#dungeon_role{typical_data = TypicalData} = DungeonRole,
	MonWaveList = maps:get(?DUN_ROLE_CHILD_BOX_MON, TypicalData, []),
	NewMonWaveList = [WaveNum|lists:delete(WaveNum, MonWaveList)],
	NewTypicalData  = maps:put(?DUN_ROLE_CHILD_BOX_MON, NewMonWaveList, TypicalData),
	NewDungeonRole  = DungeonRole#dungeon_role{typical_data = NewTypicalData},
	NewDungeonState = DungeonState#dungeon_state{role_list = [NewDungeonRole]},
	NewDungeonState.

%% ===================================================================================================
%% 怪物死亡(记录本波剩余怪数量)
handle_kill_mon_do(DungeonState, [_Mid, Killer, MonXys|_]) ->
	[MAutoId, _X, _Y] = MonXys,
	#dungeon_state{dun_id = DunId, role_list = [DungeonRole|_],
		wave_num = OutWaveNum, wave_info = WaveInfos
	} = DungeonState,
	#dungeon_role{typical_data = TypicalData} = DungeonRole,
	NewLeftNum = case maps:get(?DUN_ROLE_CHILD_LEFT_MON, TypicalData, 0) of
		             LeftNum when LeftNum > 0 -> LeftNum-1;
		             _ ->
			             %% 读取配置
			             case data_dungeon_wave:get_dun_wave(DunId, OutWaveNum) of
				             #base_dun_wave_cfg{mon_list = MonList} ->
					             MonList1 = [MonId ||{MonId, _TX, _TY, _Ratio, _Args} <- MonList],
					             MonNum = erlang:length(MonList1),
					             max(MonNum-1, 0);
				             _ ->
					             ?DEFAULT_DUN_CHILD_MON-1
			             end
	             end,
	WalkMons = maps:get(?DUN_ROLE_CHILD_WALK_MON, TypicalData, []),
	WalkMonInfos = maps:get(?DUN_ROLE_CHILD_WALK_MON_TIME, TypicalData, []),
	NewTypicalData  = maps:put(?DUN_ROLE_CHILD_WALK_MON, lists:delete(MAutoId, WalkMons), TypicalData),
	NewTypicalData0 = maps:put(?DUN_ROLE_CHILD_WALK_MON_TIME, lists:keydelete(MAutoId, 1, WalkMonInfos), NewTypicalData),
	NewTypicalData1 = maps:put(?DUN_ROLE_CHILD_LEFT_MON, NewLeftNum, NewTypicalData0),
	OldRunAwayNum   = maps:get(?DUN_ROLE_CHILD_RUN_AWAY_NUM, TypicalData, 0),
	%% Killer表明是怪物自己逃跑,否则是玩家击杀
	NewDungeonState =
	case Killer of
		0 ->
			NewRunAwayNum = OldRunAwayNum + 1,
			NewTypicalData2 = maps:put(?DUN_ROLE_CHILD_RUN_AWAY_NUM, NewRunAwayNum, NewTypicalData1),
			NewDungeonRole  = DungeonRole#dungeon_role{typical_data = NewTypicalData2},
			DungeonState#dungeon_state{role_list = [NewDungeonRole]};
		_ ->
			NewDungeonRole = DungeonRole#dungeon_role{typical_data = NewTypicalData1},
			DungeonState#dungeon_state{role_list = [NewDungeonRole]}
	end,
	%% 判断是否终结副本，并记录最高波数 TODO:NEW
	case check_run_away_mon(NewDungeonState) of
		{?WAVE_NEXT, NewState} ->
			#dungeon_state{role_list = [CheckDungeonRole|_]} = NewState,
			WaveNum = calc_wave_num(WaveInfos),
			CheckDungeonRole1 = update_max_dun_wave(?DUN_CHILD_ID, max(0, WaveNum), CheckDungeonRole),
			NewState#dungeon_state{role_list = [CheckDungeonRole1]};
		{ResultType, ResultSubType, NewState} ->
			%% 如果killer=0，即逃跑怪物，则不记录最高波数(前一波已经通关的不在讨论范围内，因为已经记录过)
			case Killer == 0 of
				true ->
					lib_dungeon_distribute:dungeon_result(NewState, ResultType, ResultSubType);
				_ ->
					#dungeon_state{role_list = [CheckDungeonRole|_]} = NewState,
					WaveNum = calc_wave_num(WaveInfos),
					CheckDungeonRole1 = update_max_dun_wave(?DUN_CHILD_ID, max(0, WaveNum), CheckDungeonRole),
					NewState1 = NewState#dungeon_state{role_list = [CheckDungeonRole1]},
					lib_dungeon_distribute:dungeon_result(NewState1, ResultType, ResultSubType)
			end
	end.

%% 退出检查是否新波数决定结算类型
check_quit_state(State, RoleId) ->
	#dungeon_state{is_end = IsEnd, role_list = RoleList, wave_info = WaveInfos, result_type = ResultType, result_subtype = ResultSubtype} = State,
	if
		WaveInfos == [] ->
			{?DUN_RESULT_TYPE_FAIL, ?DUN_RESULT_SUBTYPE_ACTIVE_QUIT};
		%% 玩家主动退出
		IsEnd =/= ?DUN_IS_END_YES ->
			case lists:keyfind(RoleId, #dungeon_role.id, RoleList) of
				#dungeon_role{typical_data = TypicalData} ->
					InitWave = maps:get(?DUN_ROLE_CHILD_INIT_WAVE, TypicalData, 1),
					WaveNum  = maps:get(?DUNGEON_REC_DAILY_MAX_SCORE, TypicalData, 1),
					case check_new_pass_wave(InitWave, WaveNum, WaveInfos) of
						true ->
							{?DUN_RESULT_TYPE_SUCCESS, ?DUN_RESULT_SUBTYPE_ACTIVE_QUIT};
						_ ->
							{?DUN_RESULT_TYPE_FAIL, ?DUN_RESULT_SUBTYPE_ACTIVE_QUIT}
					end;
				_ ->
					{?DUN_RESULT_TYPE_FAIL, ?DUN_RESULT_SUBTYPE_ACTIVE_QUIT}
			end;
		true ->
			{ResultType, ResultSubtype}
	end.

check_new_pass_wave(InitWave, WaveNum, _WaveInfos) ->
	WaveNum >= InitWave.

%% 怪物死亡时,检查逃跑怪物数量是否达到限制值
check_run_away_mon(State) ->
	#dungeon_state{dun_id = _DunId, scene_id = _SceneId, scene_pool_id = _PoolId, role_list = [DungeonRole|_]
	,wave_num = _WaveNum, wave_info = WaveInfos} = State,
	#dungeon_role{id = RoleId, typical_data = TypicalData} = DungeonRole,
	% WalkMons = maps:get(?DUN_ROLE_CHILD_WALK_MON, TypicalData, []),
	% clean_scene_mon(SceneId, PoolId, lists:flatten(WalkMons)),
	RunAwayNum = maps:get(?DUN_ROLE_CHILD_RUN_AWAY_NUM, TypicalData, 0),
	LimitNum = data_dungeon_child:get_dun_child_kv_cfg(run_away_num),
	%% 副本数据中的WaveNum有可能只是刷出来的波数，并不是最新通关波数，这里通关波数信息计算通关波数
	WaveNum = calc_wave_num(WaveInfos),
	case RunAwayNum >= LimitNum of
		true ->
			%% 推送新的逃跑数量
			lib_server_send:send_to_uid(RoleId, pt_610, 61097, [RunAwayNum]),
			%% 检查是否有波数更新，这里必须用大于号判断，因为逃跑数量超出的波数不能计算入内
			InitWave = maps:get(?DUN_ROLE_CHILD_INIT_WAVE, TypicalData, 0),
			case WaveNum > InitWave of
				true ->
					%% 宝箱奖励
					% DungeonRole1 = calc_box_mon_reward(DungeonRole, DunId),
					% NewState = State#dungeon_state{role_list = [DungeonRole1]},
					{?DUN_RESULT_TYPE_SUCCESS, ?DUN_RESULT_SUBTYPE_WAVE_TIMEOUT, State};
				_ ->
					{?DUN_RESULT_TYPE_FAIL, ?DUN_RESULT_SUBTYPE_WAVE_TIMEOUT, State}
			end;
		_ ->
			CfgDunMaxWave = lists:max(data_dungeon_wave:get_dun_waves(?DUN_CHILD_ID)),
			case WaveNum == CfgDunMaxWave of
				true ->
					NewDungeonRole = update_max_dun_wave(?DUN_CHILD_ID, max(0, WaveNum), DungeonRole),
					NewState = State#dungeon_state{role_list = [NewDungeonRole]},
					{?DUN_RESULT_TYPE_SUCCESS, ?DUN_RESULT_SUBTYPE_WAVE_TIMEOUT, NewState};
				_ ->
					%% 推送新的逃跑数量
					lib_server_send:send_to_uid(RoleId, pt_610, 61097, [RunAwayNum]),
					{?WAVE_NEXT, State}
			end
	end.

%% 计算通关波数
calc_wave_num(WaveInfos) ->
	F = fun(WaveInfo, TWave) ->
		case WaveInfo of
			#dun_wave_info{status = 1, key = {WaveNum, _}} when WaveNum >= TWave ->
				WaveNum;
			_ ->
				TWave
		end end,
	lists:foldl(F, 0, WaveInfos).

%%clean_scene_mon(SceneId, PoolId, WalkMons) ->
%%	lib_mon:clear_scene_mon_by_ids(SceneId, PoolId, 1, WalkMons),
%%	ok.

%% 副本结束计算掉落奖励
calc_box_mon_reward(DungeonRole, DunId) ->
	#dungeon_role{typical_data = TypicalData, calc_reward_list = OldRewardList} = DungeonRole,
	%% 当前通关波数
	DailyMaxWave = maps:get(?DUNGEON_REC_DAILY_MAX_SCORE, TypicalData, 0),
	%% 判断是否击杀宝箱怪
	MonWaveList = maps:get(?DUN_ROLE_CHILD_BOX_MON, TypicalData, []),
	NewDungeonRole =
		if
			MonWaveList =/= [] ->
				F = fun(TWave, TRewardList) ->
					%% 需要判断这一波是否通关，防止刷单一这波
					RewardList =
						case DailyMaxWave >= TWave of
							true ->
								case data_dungeon_child:get_dun_child_sweep_wave_reward(DunId, TWave) of
									#dun_child_sweep_cfg{mon_reward = MonReward} -> MonReward;
									_ -> []
								end;
							_ -> []
						end,
					RewardList ++ TRewardList
				end,
				CalcRewardList = lists:foldl(F, [], MonWaveList),
				%% 计算完之后置为0
				NewTypicalData = maps:put(?DUN_ROLE_CHILD_BOX_MON, [], TypicalData),
				DungeonRole#dungeon_role{calc_reward_list = OldRewardList++CalcRewardList, typical_data = NewTypicalData};
			true ->
				DungeonRole
		end,
	NewDungeonRole.

%% 波数时间到，检查结算类型
wave_timeout_check(State) ->
	#dungeon_state{role_list = [DungeonRole|_], wave_info = WaveInfos} = State,
	#dungeon_role{typical_data = TypicalData} = DungeonRole,
	InitWave = maps:get(?DUN_ROLE_CHILD_INIT_WAVE, TypicalData, 0),
	%% 副本数据中的WaveNum有可能只是刷出来的波数，并不是最新通关波数，这里通关波数信息计算通关波数
	WaveNum = calc_wave_num(WaveInfos),
	case WaveNum >= InitWave of
		true ->
			{?DUN_RESULT_TYPE_SUCCESS, ?DUN_RESULT_SUBTYPE_WAVE_TIMEOUT};
		_ ->
			{?DUN_RESULT_TYPE_FAIL, ?DUN_RESULT_SUBTYPE_WAVE_TIMEOUT}
	end.

%% 退出清除副本定时器，马上结束副本
handle_dungeon_result(State) ->
	#dungeon_state{
		wave_ref = [_OldWaveStartRef, NewWaveEndRef]
	} = State,
	% util:cancel_timer(ForceRef),
%%	case IsEnd of
%%		?DUN_IS_END_YES ->
%%			#dungeon_role{typical_data = TypicalData} = DungeonRole,
%%			DailyMaxWave = maps:get(?DUNGEON_REC_DAILY_MAX_SCORE, TypicalData, 1),
%%			InitMaxWave  = maps:get(?DUN_ROLE_CHILD_MAX_WAVE, TypicalData, 0),
%%			CfgDunMaxWave  = lists:max(data_dungeon_wave:get_dun_waves(?DUN_CHILD_ID)),
%%			case DailyMaxWave + 1 of
%%				CfgDunMaxWave ->
%%					% 存一下数据(因为最后一波没有新的波数去记录这个数据了)
%%					NewDungeonRole = update_max_dun_wave(?DUN_CHILD_ID, max(0, DailyMaxWave + 1), DungeonRole),
%%					OldRewardList  = DungeonRole#dungeon_role.calc_reward_list,
%%					OldCustomReward= DungeonRole#dungeon_role.custom_reward_list,
%%					Figure = DungeonRole#dungeon_role.figure,
%%					Times = lib_dungeon_api:get_custom_act_reward_times(?DUNGEON_TYPE_CHILD, 0, Figure#figure.lv),
%%					%% 只计算最后一波的奖励
%%					{RewardList, CustomReward} = do_calc_wave_rewards(?DUN_CHILD_ID, CfgDunMaxWave, CfgDunMaxWave, InitMaxWave, Times, [], []),
%%					NewState = State#dungeon_state{role_list = [
%%						NewDungeonRole#dungeon_role{calc_reward_list = OldRewardList++RewardList, custom_reward_list = OldCustomReward++CustomReward}
%%					]};
%%				_ ->
%%					NewState = State
%%			end;
%%		_ ->
%%			NewState = State
%%	end,
	NewState = State,
	util:cancel_timer(NewWaveEndRef),
	NewState#dungeon_state{wave_ref = []}.

%% 副本结束计算奖励
get_send_reward(State, DungeonRole) ->
	#dungeon_state{dun_id = DunId, result_type = _ResultType} = State,
	if
		%% 计算本波通关奖励，首通才拿首通奖励，其他情况拿普通奖励
		DungeonRole#dungeon_role.help_type =:= ?HELP_TYPE_NO ->
			{CalcRewards, CustomRewards} = calc_wave_rewards(DunId, State),
			% NewDungeonRole = calc_box_mon_reward(DungeonRole, DunId),
			DungeonRole#dungeon_role{calc_reward_list = CalcRewards++DungeonRole#dungeon_role.calc_reward_list, custom_reward_list = CustomRewards};
		true ->
			DungeonRole
	end.

calc_wave_rewards(DunId, State) ->
	#dungeon_state{role_list = [DungeonRole|_]} = State,
	#dungeon_role{typical_data = TypicalData, figure = Figure} = DungeonRole,
	WaveNum     = maps:get(?DUNGEON_REC_DAILY_MAX_SCORE, TypicalData, 0),
	InitWave    = maps:get(?DUN_ROLE_CHILD_INIT_WAVE, TypicalData, 0),
	InitMaxWave = maps:get(?DUN_ROLE_CHILD_MAX_WAVE, TypicalData, 0),
	%% 定制活动副本多倍奖励
	case lib_dungeon_api:get_custom_act_reward_times(?DUNGEON_TYPE_CHILD, DunId, Figure#figure.lv) of
		Times when Times > 1 -> ok;
		_ -> Times = 1
	end,
	case WaveNum >= InitWave of
		true ->
			%% 从进入副本的波数开始计算
			do_calc_wave_rewards(DunId, InitWave, WaveNum, InitMaxWave, Times, [], []);
		_ ->
			{[], []}
	end.

do_calc_wave_rewards(_DunId, Wave, FinishWaveNum, _InitMaxWave, _MultiTimes, Reward, CustomReward) when Wave == FinishWaveNum+1 -> {Reward, CustomReward};
do_calc_wave_rewards(DunId, Wave, FinishWaveNum, InitMaxWave, MultiTimes, SumReward, CustomReward) ->
	%% 首通才拿首通奖励
	case Wave > InitMaxWave of
		true ->
			Reward = case data_dungeon_wave:get_dun_wave(DunId, Wave) of
				         #base_dun_wave_cfg{first_reward = FirstReward} -> FirstReward;
				         _ -> []
			         end,
			do_calc_wave_rewards(DunId, Wave+1, FinishWaveNum, InitMaxWave, MultiTimes, SumReward++Reward, CustomReward);
		_ ->
			Reward = case data_dungeon_wave:get_dun_wave(DunId, Wave) of
				         #base_dun_wave_cfg{normal_reward = NormalReward} -> NormalReward;
				         _ -> []
			         end,
			case MultiTimes > 1 of
				true ->
					CustomAddReward = [{TGoodsType1, TGoodsId1, TGoodsNum1*max(1, MultiTimes-1)}||{TGoodsType1, TGoodsId1, TGoodsNum1}<-Reward];
				_ ->
					CustomAddReward = []
			end,
			do_calc_wave_rewards(DunId, Wave+1, FinishWaveNum, InitMaxWave, MultiTimes, SumReward++Reward, CustomReward++CustomAddReward )
	end.

%% 结算界面推送
pack_account_kv_data(DungeonState, _RoleId) ->
	#dungeon_state{role_list = [DungeonRole|_] = _RoleList} = DungeonState,
	#dungeon_role{typical_data = TypicalData} = DungeonRole,
	WaveNum     = maps:get(?DUNGEON_REC_DAILY_MAX_SCORE, TypicalData, 0),
	[{?DUN_RESULT_DUN_CHILD_WAVE, WaveNum}].

%% 检查是否可以使用技能
check_use_dun_child_skill(Player, SkillId) ->
	#player_status{role_dun_child = RoleDunChild} = Player,
	RoleDunChild1 = trans_role_dun_child(RoleDunChild),
	#role_dun_child{energy_pot = Energy, wave = Wave} = RoleDunChild1,
	SkillExpends = data_dungeon_child:get_dun_child_kv_cfg(skill_expends),
	SkillOpen    = data_dungeon_child:get_dun_child_kv_cfg(skill_open),
	%% 非普攻技能判断
	Return =
	case lists:keyfind(SkillId, 2, SkillOpen) of
		{_, _, OpenWave} when Wave >= OpenWave ->
			case lists:keyfind(SkillId, 1, SkillExpends) of
				{_, Expend} when Energy >= Expend -> true;
				_ -> false
			end;
		_ ->
			false
	end,
	Return.

%% 消耗能量
cost_energy(Player, SkillId) ->
	#player_status{id = RoleId, role_dun_child = RoleDunChild, copy_id = CopyId} = Player,
	RoleDunChild1 = trans_role_dun_child(RoleDunChild),
	#role_dun_child{energy_pot = Energy} = RoleDunChild1,
	SkillExpends = data_dungeon_child:get_dun_child_kv_cfg(skill_expends),
	case lists:keyfind(SkillId, 1, SkillExpends) of
		{_, Expend} ->
			NewEnergy = max(0, Energy - Expend);
		_ ->
			NewEnergy = Energy
	end,
	lib_server_send:send_to_uid(RoleId, pt_611, 61120, [NewEnergy]),
	NewRoleDunChild = RoleDunChild1#role_dun_child{energy_pot = NewEnergy},
	mod_dungeon:apply(CopyId, ?MODULE, change_energy_on_copy, [NewEnergy]),
	Player#player_status{role_dun_child = NewRoleDunChild}.

reconnect(DungeonRole, _Wave) ->
	#dungeon_role{id = RoleId, typical_data = TypicalData} = DungeonRole,
	Energy = maps:get(?DUN_ROLE_CHILD_ENERGY, TypicalData, 0),
	Wave = maps:get(?DUNGEON_REC_MAX_SCORE, TypicalData, 0),
	lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, reconnect_on_player, [Wave, Energy]),
	ok.

%% 玩家重连
reconnect_on_player(Player, Wave, Energy) ->
	#player_status{role_dun_child = RoleDunChild, scene = SceneId, id = RoleId} = Player,
	case SceneId == 2300 of
		true ->
			RoleDunChild1 = trans_role_dun_child(RoleDunChild),
			% #status_dungeon{dun_id = DunId} = StatusDungeon,
			NewRoleDunChild = RoleDunChild1#role_dun_child{
				energy_pot = Energy, wave = Wave
			},
			NewPlayer =
			Player#player_status{role_dun_child = NewRoleDunChild},
			lib_server_send:send_to_uid(RoleId, pt_611, 61120, [Energy]),
			{ok, NewPlayer};
		_ ->
			{ok, Player}
	end.

%% 放置防御塔
create_tower(Player, MonId) ->
	#player_status{scene = SceneId, scene_pool_id = PoolId, copy_id = CopyId, x = X, y = Y} = Player,
	OldCdTime = proc_get_tower_cd(MonId),
	case is_dun_child_scene(SceneId) of
		true ->
			case check_cd_time(MonId) of
				true ->
					%% 设置新的cd时间
					NowTime = utime:unixtime(),
					proc_put_tower_cd(MonId, NowTime),
					IceMonCfgId  = data_dungeon_child:get_dun_child_kv_cfg(ice_tower_id),
					FireMonCfgId = data_dungeon_child:get_dun_child_kv_cfg(fire_tower_id),
					%% 塔死亡AI
					case lists:member(MonId, [IceMonCfgId, FireMonCfgId]) of
						true ->
							%% 定时检测释放周围寻路怪进行攻击
							CycleCheckHandler = {?MODULE, timer_check_act_skill, [?CHECK_ACT_SKILL_TIME]},  %% 1秒钟检测一次,释放技能
							lib_mon:async_create_mon(MonId, SceneId, PoolId, X, Y, 0, CopyId, 1, [{group, 99}, {cycle_check_handler, CycleCheckHandler}]),
							lib_server_send:send_to_sid(Player#player_status.sid, pt_610, 61094, [?SUCCESS, MonId, NowTime + ?PUT_TOWER_CD]);
						_ ->
							lib_server_send:send_to_sid(Player#player_status.sid, pt_610, 61094, [?ERRCODE(err610_dun_child_mon_err), MonId, OldCdTime])
					end;
				{false, Err} ->
					lib_server_send:send_to_sid(Player#player_status.sid, pt_610, 61094, [Err, MonId, OldCdTime])
			end;
		_ ->
			lib_server_send:send_to_sid(Player#player_status.sid, pt_610, 61094, [?ERRCODE(err610_not_in_dun), MonId, OldCdTime])
	end,
	ok.

timer_check_act_skill(_State = #ob_act{object = #scene_object{id = MonId, aid = MonPid}}, _StateName, _Time) ->
	% old: erlang:send_after(0, self(), 'find_target'),
	mon_mgr:send(MonPid, MonId, 'find_target'),
	ok;
timer_check_act_skill(_, _, _) ->
	ok.

%% 扣费清除放置塔cd
clean_tower_cd(Player, MonId) ->
	#player_status{scene = SceneId, sid = Sid} = Player,
	case is_dun_child_scene(SceneId) of
		true ->
			case check_cd_time(MonId) of
				%% 理论上很少出现这种情况
				true ->
					%% 清除cd时间
					proc_put_tower_cd(MonId, 0);
				_ ->
					CostMoney = data_dungeon_child:get_dun_child_kv_cfg(clean_cd_cost),
					case lib_goods_api:cost_object_list_with_check(Player, [{?TYPE_GOLD, 0, CostMoney}], dun_child, "") of
						{true, Player1, _} ->
							%% 清除cd时间
							proc_put_tower_cd(MonId, 0),
							lib_server_send:send_to_sid(Player#player_status.sid, pt_610, 61095, [?SUCCESS, MonId]),
							Player1;
						{false, ErrCode, _Player1} ->
							lib_server_send:send_to_sid(Player#player_status.sid, pt_610, 61095, [ErrCode, MonId]),
							Player
					end
			end;
		_ ->
			lib_server_send:send_to_sid(Sid, pt_610, 61095, [?ERRCODE(err610_not_in_dun), MonId]),
			Player
	end.

%% 免费扫荡副本(只扫荡到最高波数的前一阶段)
sweep_dun_child(Player) ->
	#player_status{sid = Sid, id = RoleId, dungeon_record = DungeonRecs, figure = Figure} = Player,
	DunRecord = maps:get(?DUN_CHILD_ID, DungeonRecs, []),
	%% 最大通关数
	case lists:keyfind(?DUNGEON_REC_MAX_SCORE, 1, DunRecord) of
		{_, MaxWave} when MaxWave > 0 ->
			CanSweepToWave = data_dungeon_child:get_dun_child_sweep_wave(?DUN_CHILD_ID, MaxWave),
			case CanSweepToWave of
				%% 还未可以扫荡
				[] -> NewPlayer = Player;
				Wave ->
					case lib_daily:get_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_PASS_LV, ?DUNGEON_TYPE_CHILD) of
						DailyMax when DailyMax < Wave ->
							%% 可以扫荡
							%% 计算奖励
							SweepReward = calc_sweep_reward(DailyMax+1, CanSweepToWave, []),
							%% 定制活动副本多倍奖励
							case lib_dungeon_api:get_custom_act_reward_times(?DUNGEON_TYPE_CHILD, 0, Figure#figure.lv) of
								MultiTimes when MultiTimes > 1 ->
									SweepReward1 = [{TGoodsType, TGoodsId, TGoodsNum*MultiTimes}||{TGoodsType, TGoodsId, TGoodsNum}<-SweepReward];
								_ ->
									SweepReward1 = SweepReward
							end,
							Produce = #produce{
								type = sweep_dun_child, reward = SweepReward1, show_tips = ?SHOW_TIPS_0},
							{ok, Player1} = lib_goods_api:send_reward_with_mail(Player, Produce, ?MOD_DUNGEON),
							lib_daily:set_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_PASS_LV, ?DUNGEON_TYPE_CHILD, CanSweepToWave),
							lib_server_send:send_to_sid(Sid, pt_610, 61096, [?SUCCESS]),
							NewPlayer = Player1;
						DailyMax when is_integer(DailyMax) ->
							%% 已经完成
							lib_server_send:send_to_sid(Sid, pt_610, 61096, [?ERRCODE(err610_cannot_sweep)]),
							NewPlayer = Player;
						_ ->
							%% 没有可以扫荡的
							lib_server_send:send_to_sid(Sid, pt_610, 61096, [?ERRCODE(err610_cannot_sweep)]),
							NewPlayer = Player
					end
			end;
		_ ->
			lib_server_send:send_to_sid(Sid, pt_610, 61096, [?ERRCODE(err610_cannot_sweep)]),
			NewPlayer = Player
	end,
	NewPlayer.

%% 计算扫荡奖励
calc_sweep_reward(NowWave, CanSweepToWave, Reward) when NowWave > CanSweepToWave-> Reward;
calc_sweep_reward(NowWave, CanSweepToWave, Reward) ->
	case data_dungeon_wave:get_dun_wave(?DUN_CHILD_ID, NowWave) of
		#base_dun_wave_cfg{normal_reward = NormalReward} ->
			calc_sweep_reward(NowWave+1, CanSweepToWave, Reward++NormalReward);
		_ ->
			calc_sweep_reward(NowWave+1, CanSweepToWave, Reward)
	end.

%% 判断是否在宝宝副本场景
is_dun_child_scene(SceneId) ->
	case SceneId == ?DUN_CHILD_SCENE of
		true -> true;
		_ -> false
	end.

%% -------------------------------------------
%% @doc 玩家数据转换 @end
%% -------------------------------------------
trans_role_dun_child(RoleDunChild) ->
	case RoleDunChild of
		#role_dun_child{} -> RoleDunChild;
		_ -> #role_dun_child{}
	end.

%% -------------------------- builtin_function -----------------------------
update_max_dun_wave(DunId, WaveNum, DungeonRole) ->
	#dungeon_role{id = RoleId, typical_data = TypicalData} = DungeonRole,
	{IsUpdate1, TypicalData1} = do_update_max_dun_wave(RoleId, DunId, WaveNum, ?DUNGEON_REC_DAILY_MAX_SCORE, TypicalData),
	{IsUpdate2, TypicalData2} = do_update_max_dun_wave(RoleId, DunId, WaveNum, ?DUNGEON_REC_MAX_SCORE, TypicalData1),
	case IsUpdate1 == true orelse IsUpdate2 == true of
		true -> NewDungeonRole = DungeonRole#dungeon_role{typical_data = TypicalData2};
		_    -> NewDungeonRole = DungeonRole
	end,
	NewDungeonRole.

do_update_max_dun_wave(RoleId, DunId, WaveNum, RecordType, TypicalData) ->
	case maps:get(RecordType, TypicalData, 0) of
		OldWave when WaveNum > OldWave ->
			lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?HAND_OFFLINE, ?MODULE, update_role_best_record, [DunId, RecordType, WaveNum]),
			{true, maps:put(RecordType, WaveNum, TypicalData)};
		_ ->
			{false, TypicalData}
	end.

%% 更新玩家身上副本记录
update_role_best_record(Player, DunId, RecordType, WaveNum) ->
	#player_status{id = RoleId, dungeon_record = DungeonRecs
		,role_dun_child = RoleDunChild
	} = Player,
	DungeonRecs1  = trans_dungeon_record(DungeonRecs),
	RoleDunChild1 = trans_role_dun_child(RoleDunChild),
	case RecordType of
		?DUNGEON_REC_DAILY_MAX_SCORE ->
			lib_daily:set_count(RoleId, ?MOD_DUNGEON, ?MOD_DUNGEON_PASS_LV, ?DUNGEON_TYPE_CHILD, WaveNum),
			{ok, Player};
		_ ->
			DunRecord = maps:get(DunId, DungeonRecs1, []),
			case lists:keyfind(RecordType, 1, DunRecord) of
				%% 波数小于原先波数不修改
				{_, OldWave} when WaveNum =< OldWave ->
					NewPlayer = Player;
				_ ->
					NewDunRecord = lists:keystore(RecordType, 1, DunRecord, {RecordType, WaveNum}),
					NewDungeonRecs = maps:put(DunId, NewDunRecord, DungeonRecs1),
					lib_dungeon:save_dungeon_record(RoleId, DunId, NewDunRecord),
					%% 记录达到的下一波波数，可以使用对应技能
					NewRoleDunChild = RoleDunChild1#role_dun_child{wave = WaveNum},
					NewPlayer = Player#player_status{
						dungeon_record  = NewDungeonRecs
						,role_dun_child = NewRoleDunChild
					}
			end,
			{ok, NewPlayer}
	end.

trans_dungeon_record(DungeonRecs) when is_map(DungeonRecs) -> DungeonRecs;
trans_dungeon_record(_) -> #{}.

%% ======================================================
%% 进程字典相关操作
%% ======================================================
proc_put_tower_cd(MonId, CdTime) ->
	CdKey = ?TOWER_CD_KEY(MonId),
	put(CdKey, CdTime).

proc_get_tower_cd(MonId) ->
	CdKey = ?TOWER_CD_KEY(MonId),
	case get(CdKey) of
		OldTime when is_integer(OldTime) -> skip;
		_ -> OldTime = 0
	end,
	OldTime.

%% 玩家进程
%% 10s放置塔CD
check_cd_time(MonId) ->
	NowtTime = utime:unixtime(),
	OldTime  = proc_get_tower_cd(MonId),
	case NowtTime > (OldTime + ?PUT_TOWER_CD) of
		true -> true;
		_    -> {false, ?ERRCODE(err610_create_tower_cd)}
	end.

%% gm设置波数为48波
gm_set_max(Player) ->
	DunId = 14001, WaveNum = 48,
	{ok, NewPlayer} = update_role_best_record(Player, DunId, 3, WaveNum),
	{ok, NewPlayer1} = update_role_best_record(NewPlayer, DunId, 4, WaveNum),
	pp_dungeon:handle(61020, NewPlayer, [?DUNGEON_TYPE_CHILD]),
	NewPlayer1.