--'******************************************************
-- Edited by Alundaio
--
-- edited 1/14/2016: Added ability to create squad npcs without specifying a smart
--'******************************************************

-- Current Action: 0 - Reach Target | 1 - Stay Point

local STAY_POINT_IDLE_MIN = 2*60*60
local STAY_POINT_IDLE_MAX = 8*60*60

dbg_map_hud = false
local excl_dist = ui_options.get("alife/general/excl_dist")

-- setup location types
local location_types = {}
do -- to localize below
	local locations_ini	= ini_file("misc\\smart_terrain_masks.ltx")
	
	local function itr(section)
		local a,key,val = locations_ini:r_line_ex(section,0,"","")
		location_types[key] = key
		return false
	end
	
	locations_ini:section_for_each(itr)
end


--------------------------------------------------------------
-- INITIALIZATION
--------------------------------------------------------------
class "sim_squad_scripted" (cse_alife_online_offline_group)
function sim_squad_scripted:__init(section) super (section)
	self.smart_id 			= nil
	self.current_spot_id 	= nil
	self.current_action 	= nil
	self.current_target_id	= nil
	self.assigned_target_id	= nil
	self.rush = nil
	self.item_on_all = nil
	self:init_squad()
	--if (USE_MARSHAL) then
	--	RegisterScriptCallback("save_state",self.save_state,self)
	--end
	self.first_update 		= false
end

function sim_squad_scripted:init_squad()
	--utils_data.debug_write("sim_squad_scripted:init_squad")
	local settings_id 		= self:section_name()
	
	self.player_id 			= ini_sys:r_string_ex(settings_id,"faction") or "stalker"
	self.common				= ini_sys:r_bool_ex(settings_id,"common")
	self.action_condlist	= ini_sys:r_string_to_condlist(settings_id,"target_smart")
	self.death_condlist		= ini_sys:r_string_to_condlist(settings_id,"on_death")
	self.invul_condlist		= ini_sys:r_string_to_condlist(settings_id,"invulnerability")
	
	self.relationship_condlist = ini_sys:r_string_to_condlist(settings_id,"relationship")
	if (self.relationship_condlist) then 
		local new_relation = xr_logic.pick_section_from_condlist(db.actor, self, self.relationship_condlist)
		if (new_relation) and (new_relation == "enemy" or new_relation == "friend" or new_relation == "neutral") then 
			self.relationship = new_relation
		end
	end
	
	local s	= ini_sys:r_string_ex(settings_id, "target_random_smart")
	if (s) then
		self.random_targets = str_explode(s,",") 
	end
	
	s	= ini_sys:r_string_ex(settings_id, "item_on_all")
	if (s) then
		self.item_on_all = str_explode(s,",") 
	end
	
	self.rush = ini_sys:r_bool_ex(settings_id, "rush")
	
	self.idle_time			= ini_sys:r_float_ex(settings_id,"idle_time") or math.random(STAY_POINT_IDLE_MIN,STAY_POINT_IDLE_MAX)	
	
	self.sympathy			= ini_sys:r_float_ex(settings_id,"sympathy")

	self.always_arrived		= ini_sys:r_bool_ex(settings_id,"always_arrived",false)
	self:set_location_types_section("squad_terrain")
	self:set_location_types_section("stalker_terrain")
	self:set_squad_sympathy()
end

function sim_squad_scripted:init_squad_on_load()
	--utils_data.debug_write(self:name().."init_squad_on_load start")
	self:set_squad_sympathy()
	sim_board.get_sim_board():assign_squad_to_smart(self, self.smart_id)
	self.current_action = 0
	self.need_to_reset_location_masks = true
	--utils_data.debug_write(self:name().."init_squad_on_load end")
end


--------------------------------------------------------------
-- SCRIPT_TARGET_SELECTION
--------------------------------------------------------------
function sim_squad_scripted:get_script_target()
	--utils_data.debug_write("sim_squad_scripted:get_script_target")
	local new_target 
	if (self.scripted_target) then 
		new_target = self.scripted_target
	elseif (axr_task_manager.hostages_by_id[self:commander_id()]) then
		return axr_task_manager.hostages_by_id[self:commander_id()]
	elseif (self.random_targets) then
		if (self.current_action == 0 and self.assigned_target_id == nil) then 
			new_target = self.random_targets[math.random(#self.random_targets)]
		else 
			return self.assigned_target_id
		end
	elseif (self.action_condlist) then
		new_target = xr_logic.pick_section_from_condlist(db.actor, self, self.action_condlist)
	end 
	
	-- prevent companions from moving to actor if they cannot teleport and actor is not on same level
	if (axr_companions.companion_squads[self.id]) then
		local se_obj = alife_object(self:commander_id())
		if (self.online ~= true) and (se_obj and se_load_var(se_obj.id,se_obj:name(),"companion_cannot_teleport")) then
			return self.id
		end
		return 0
	end
		
	if (new_target == "actor") then
		return 0
	end
	
	local smart = new_target and SIMBOARD.smarts_by_names[new_target]
	if (smart) then
		return smart.id
	end
	
	local new_id = tonumber(new_target)
	local se_target = new_id and alife_object(new_id)
	if se_target and (obj:clsid() == clsid.online_offline_group_s or obj:clsid() == clsid.smart_terrain) then
		return new_id
	end
end

function sim_squad_scripted:update()
	--utils_data.debug_write("sim_squad_scripted:update START")
	cse_alife_online_offline_group.update(self)
	
	if not (self.first_update) then 
		self.first_update = true 
		
		--[[
		local skip = false
		local sim = alife()
		local se_actor = sim:actor()
		local se_obj = self:commander_id() and alife_object(self:commander_id())
		if (se_obj) then
			if (se_load_var(se_obj.id,se_obj:name(),"companion")) then
				skip = true
			elseif (se_load_var(se_obj.id,se_obj:name(),"do_not_deactivate")) then 
				skip = true 
			end
		end
		--]]
		
		if self.rush then
			self.rush_to_target = true
		end
		
		if self.item_on_all then
			local sim = alife()
			for k in self:squad_members() do
				for kk,vv in pairs(self.item_on_all) do
					local itm = alife_create_item(vv, alife_object(k.id))
					-- if itm then printf('spawned %s on %s',vv,k.id) end
				end
			end
		end
		
		--[[ down below here is needed only if the feature to disable squads on non linked levels is true, which breaks lots of stuff 
		if (skip ~= true) then
			local gg = game_graph()

			--printf("m_game_vertex_id=%s actor.m_game_vertex_id=%s",self.m_game_vertex_id,se_actor.m_game_vertex_id)
			if (se_actor and gg:valid_vertex_id(self.m_game_vertex_id) and gg:valid_vertex_id(se_actor.m_game_vertex_id)) then
				local lvl = sim:level_name(gg:vertex(self.m_game_vertex_id):level_id())
				local actor_level = sim:level_name(gg:vertex(se_actor.m_game_vertex_id):level_id())

				-- Disable any squad that is not on actor's level and not linked to actor's level through ai_tweaks\simulation_objects.ltx
				if (lvl ~= actor_level) and (self:commander_id() and not sim:has_info(self:commander_id(),"npcx_is_companion")) then
					if not (string.find(simulation_objects.config:r_value(actor_level,"target_maps",0,""),lvl)) then
						if (DEACTIVATE_SIM_ON_NON_LINKED_LEVELS) then
							SIMBOARD.squads[self.id] = nil
							if (self.smart_id) then
								--SIMBOARD.tmp_entered_squad[self.smart_id] = nil
								SIMBOARD.tmp_assigned_squad[self.smart_id] = nil
							end
							--simulation_objects.unregister(self)
							self.disabled = true
							--printf("%s disabled",self:name())
							--utils_data.debug_write("sim_squad_scripted:update END 3")
							return
						end
					end
				end
			end
		end
		--]]
		
		SendScriptCallback("squad_on_first_update",self)
	end

	self:refresh()
	
	if (self.disabled) then
		--utils_data.debug_write("sim_squad_scripted:update END 4")
		return
	end
	
	if self.need_to_reset_location_masks then
		self:set_location_types()
		self.need_to_reset_location_masks = false
	end
	
	self:check_online_status()
	SendScriptCallback("squad_on_update",self)

	local sim = alife()
	local se_actor = sim:actor()
	self.dist_to_actor = self.position:distance_to(se_actor.position)

	self:check_invulnerability()

	local script_target_id = self:get_script_target()
	if (script_target_id) then
		self:specific_update(script_target_id)
	else
		self:generic_update()
	end
	local cur_lvl = sim:level_name(game_graph():vertex(self.m_game_vertex_id):level_id()) 
	if not (self.last_gvid == self.m_game_vertex_id) then
		SendScriptCallback("squad_on_after_game_vertex_change",self, self.last_gvid, self.m_game_vertex_id, not (cur_lvl == self.level_name))
		self.last_gvid = self.m_game_vertex_id
	end
	if not (cur_lvl == self.level_name) then
		SendScriptCallback("squad_on_after_level_change",self, self.level_name, cur_lvl)
		self.level_name = cur_lvl
	end
	--utils_data.debug_write("sim_squad_scripted:update END 2")
end


--------------------------------------------------------------
-- SIMULATION_TARGET_SELECTION
--------------------------------------------------------------
local function can_help_actor(squad)
	if is_empty(xr_combat_ignore.fighting_with_actor_npcs) then
		return false
	end

	if game_graph():vertex(squad.m_game_vertex_id):level_id() ~= get_player_level_id() then
		return false
	end

	return false
end

local function get_help_target_id(squad)
	if (IsSurvivalMode() and squad.online and squad.dist_to_actor <= 250) then
		if (squad.player_id == "zombied" or is_squad_monster[squad.player_id]) then
			return 0
		end
	end
	return squad.task_target_id
end

function sim_squad_scripted:specific_update(script_target_id) -- This update is called for all squads with a scripted smart_terrain target
	--utils_data.debug_write(strformat("%s:specific_update",self:name()))
	local se_target = alife_object(script_target_id)
	if not (se_target) then
		return
	end
	
	self.assigned_target_id = script_target_id
	if (self.assigned_target_id == self.id) then 
		return 
	end 
	
	if (self.smart_id ~= self.assigned_target_id and se_target:clsid() == clsid.smart_terrain) then 
		self:assign_smart(se_target,self.smart_id)
	end
	
	if (self.current_action) then
	
		-- in such case assigned is changed (such as get_help_target_id) but current action is 1 (reached)
		if (self.current_action == 1 and self.assigned_target_id ~= self.current_target_id) then 
			self.current_action = 0
		end
		
		-- If current action is not finished, then return
		if (self.current_action == 0) then
			-- if not (self.online) then
				-- TeleportSquad(self,se_target.position,se_target.m_level_vertex_id,se_target.m_game_vertex_id)
			-- end
			if (se_target:am_i_reached(self)) then
				se_target:on_after_reach(self)
				self.current_target_id = self.assigned_target_id
				self.current_action = 1
				
				self.stay_time = game.get_game_time()
			end
			return
		elseif (self.current_action == 1 and self.current_target_id == self.assigned_target_id) then
			if (self.stay_time == nil or game.get_game_time():diffSec(self.stay_time) < self.idle_time) then
				return
			end
		end
	end
	
	se_target:on_reach_target(self)
	
	self.assigned_target_id = nil
	self.current_target_id = nil
	self.current_action = 0
	
	for k in self:squad_members() do
		local se_obj = alife_object(k.id)
		if (se_obj) then
			SIMBOARD:setup_squad_and_group(se_obj)
		end
	end
end 

function sim_squad_scripted:generic_update() -- This update is called for all squads with no scripted assigned target
	--utils_data.debug_write(strformat("%s:generic_update",self:name()))
	
	if (self.__lock) then 
		return -- so that squad doesn't continously try to find target which lowers FPS
	end 
	
	local forced_id = get_help_target_id(self)
	self.assigned_target_id = forced_id or self.assigned_target_id or self.current_target_id
	
	if (self.current_action) then
		
		-- in such case assigned is changed (such as get_help_target_id) but current action is 1 (reached)
		if (self.current_action == 1 and self.assigned_target_id ~= self.current_target_id) then
			self.current_action = 0
		end
		
		local se_target = self.assigned_target_id == self.id and self or simulation_objects.get_server_entity(self.assigned_target_id)
		-- Evaluate current assigned target; make sure it is still valid
		if (se_target and se_target:target_precondition(self,true)) then
			-- If current action is not finished, then return
			
			if (self.smart_id ~= self.assigned_target_id and se_target:clsid() == clsid.smart_terrain) then 
				self:assign_smart(se_target,self.smart_id)
			end
	
			if (self.current_action == 0) then
				if (se_target:am_i_reached(self)) then
					se_target:on_after_reach(self)
					self.current_target_id = self.assigned_target_id
					self.current_action = 1
					self.stay_time = game.get_game_time()
				end
				return
			elseif (self.current_action == 1 and self.current_target_id == self.assigned_target_id) then
				if (forced_id) then
					return -- infinite stay time
				end
				if (se_target.locked == true) or (self.stay_time and game.get_game_time():diffSec(self.stay_time) < self.idle_time) then 
					return
				end
			end
		end
	end
	
	-- Find a new target
	local target = SIMBOARD:get_squad_target(self) or SIMBOARD:get_squad_target(self,false,true)
	if not (target) then
		--printf("squad %s has no target",self:name())
		self:on_reach_target(self)
		
		-- we have to clear AFTER on_reach_target, not BEFORE so we can get location mask of old smart
		self.assigned_target_id = nil
		self.current_target_id = nil
		self.task_target_id = nil
		self.current_action = nil
		self.__lock = db.actor and db.actor.afterFirstUpdate -- keep trying until first update
		return 
	end

	target:on_reach_target(self)
	
	self.assigned_target_id = target.id
	self.current_target_id = nil
	self.task_target_id = nil
	self.current_action = 0
	
	local sim = alife()
	for k in self:squad_members() do
		local se_obj = alife_object(k.id)
		if (se_obj) then
			SIMBOARD:setup_squad_and_group(se_obj)
		end
	end
end


--------------------------------------------------------------
-- MEMBERS_CONTROL
--------------------------------------------------------------
function sim_squad_scripted:remove_squad()
	--utils_data.debug_write(strformat("START sim_squad_scripted:remove_squad %s",self:name()))
	
	self.current_action = nil
	SIMBOARD:assign_squad_to_smart(self, nil)
	SIMBOARD.squads[self.id] = nil
	
	local squad_npcs = {}
	for k in self:squad_members() do
		squad_npcs[k.id] = true
	end
	
	local sim = alife()
	for id,v in pairs(squad_npcs) do
		local se_obj = sim:object(id)
		if (se_obj) then
			local smart_id = se_obj.m_smart_terrain_id --se_obj:smart_terrain_id()
			if (smart_id and smart_id ~= 65535) then
				local smart = alife_object(smart_id)
				if smart ~= nil then
					smart:unregister_npc(self)
				end
			end
			self:unregister_member(id)
			safe_release_manager.release(se_obj)
		end
	end
	self:hide()
	safe_release_manager.release(self)
	--utils_data.debug_write(strformat("END sim_squad_scripted:remove_squad %s",self:name()))
end

function sim_squad_scripted:remove_npc(npc_id, force)
	--utils_data.debug_write(strformat("%s:remove_npc %s",self:name(),npc_id))

	local se_obj = alife_object(npc_id)
	if (se_obj) then
		local smart_id = se_obj:smart_terrain_id()
		if smart_id ~= 65535 then
			local smart = alife_object(smart_id)
			if smart ~= nil then
				smart:unregister_npc(self)
			end
		end
	
		self:unregister_member(npc_id)
		if force or (not se_obj.online) then
			safe_release_manager.release(se_obj)
		end
	end
	
	if self:npc_count() > 0 then
		self:refresh()
	else
		self.current_action = nil
		if (self.death_condlist) then
			xr_logic.pick_section_from_condlist(db.actor, self, self.death_condlist)
		end
		
		SIMBOARD:assign_squad_to_smart(self, nil)
		SIMBOARD.squads[self.id] = nil
		
		self:hide()
		safe_release_manager.release(self)
	end
end

function sim_squad_scripted:on_npc_death(se_npc,se_killer)
	--utils_data.debug_write(strformat("%s:on_npc_death %s",self:name(),se_npc and se_npc:name()))
	--printf("Squad %s. Killed member %s", tostring(self.id), se_npc.id)
	--self.sound_manager:unregister_npc(npc.id)
		
	local npc = db.storage[se_npc.id] and db.storage[se_npc.id].object 
	if (npc) then
		local npc_binder = npc:binded_object()
		if (npc_binder) then 
			npc_binder.squad = nil
		end
	end 
	
	SendScriptCallback("squad_on_npc_death",self,se_npc,se_killer)
	
	self:remove_npc(se_npc.id)
end
function sim_squad_scripted:assign_squad_member_to_smart(member_id, smart, old_smart_id)
	local sim = alife()
	local obj = sim:object(member_id)
	if obj ~= nil then
		--printf("    npc [%s] smart [%s]", obj:name(), tostring(obj.m_smart_terrain_id))
		--utils_data.debug_write(strformat("sim_squad_scripted:assign_squad_member_to_smart npc=%s smart=%s",obj and obj:name(),smart and smart:name()))
		if obj.m_smart_terrain_id == self.smart_id then
			return
		end

		if obj.m_smart_terrain_id ~= 65535 and old_smart_id ~= nil and (obj.m_smart_terrain_id == old_smart_id) and SIMBOARD.smarts[old_smart_id] ~= nil then
			SIMBOARD.smarts[old_smart_id].smrt:unregister_npc(obj)
		end

		if smart ~= nil then
			smart:register_npc(obj)
		end
	end
end

function sim_squad_scripted:assign_smart(smart,old_smart_id)
	--utils_data.debug_write(strformat("%s:assign_smart %s",self:name(),smart and smart:name()))
	local old_smart = old_smart_id and old_smart_id ~= 65535 and alife_object(old_smart_id)
	if (smart == nil and old_smart == nil) then 
		return 
	end 
	
	if (smart) then
		self.smart_id = smart.id
	end

	for k in self:squad_members() do
		local se_obj = alife_object(k.id)
		if (se_obj) then
			if (old_smart) then
				old_smart:unregister_npc(se_obj)
			end 
			
			if (smart) then 
				smart:register_npc(se_obj)
			end		
		end
	end
end

function sim_squad_scripted:check_invulnerability()
	if self.online ~= true then
		return
	end
	local invulnerability = self.invul_condlist and xr_logic.pick_section_from_condlist(db.actor, self, self.invul_condlist) == "true"
	if (invulnerability) then
		for k in self:squad_members() do
			local npc_st = db.storage[k.id]
			local npc = npc_st and npc_st.object
			if (npc) then
				npc:invulnerable(invulnerability)
			end
		end
	end
end

function sim_squad_scripted:set_location_types_section (section)
	local location = location_types[section]
	if (location) then
		self:add_location_type(location)
	end
end

function sim_squad_scripted:set_location_types(new_smart_name)
	self:clear_location_types()
	
	self:set_location_types_section("stalker_terrain")
	self:set_location_types_section("squad_terrain")
	
	local old_target = self.assigned_target_id and db.smart_terrain_by_id[self.assigned_target_id]
	if (old_target) then 
		self:set_location_types_section(old_target:name())
	end
	
	if (new_smart_name) then 
		self:set_location_types_section(new_smart_name)
	else
		for id,se_obj in pairs(db.smart_terrain_by_id) do
			local props_base = se_obj.props and se_obj.props["base"]
			if (props_base and tonumber(props_base) ~= 0) then
				self:set_location_types_section(se_obj:name())
			end
		end
	end
end

function sim_squad_scripted:add_new_member_forced(section,pos,lvid,gvid)
	local se_obj = alife_create(section,pos,lvid,gvid)
	if (se_obj) then
		self:register_member(se_obj.id)
		local smart = self.smart_id and db.smart_terrain_by_id[self.smart_id]
		if (smart) then 
			smart:register_npc(se_obj)
			SIMBOARD:setup_squad_and_group(se_obj)
		end
	end
end 

function sim_squad_scripted:add_squad_member(spawn_section, spawn_position, lv_id, gv_id)
	--utils_data.debug_write("sim_squad_scripted:add_squad_member")
	
	if not (ini_sys:section_exist(spawn_section)) then 
		printe("!ERROR: npc section %s does not exist!",spawn_section)
		return
	end 
	
	local custom_data = ini_sys:r_string_ex(spawn_section,"custom_data") or "default_custom_data.ltx"
	if custom_data ~= "default_custom_data.ltx" then
		printf("INCORRECT npc_spawn_section USED [%s]. You cannot use npc with custom_data in squads", spawn_section)
	end

	local sim = alife()
	local obj = alife_create(spawn_section,spawn_position,lv_id,gv_id)

	self:register_member(obj.id)
	--self.sound_manager:register_npc(obj.id)

	local actor = sim:actor()
	if (simulation_objects.is_on_the_same_level(obj, actor) and spawn_position:distance_to_sqr(actor.position) <= sim:switch_distance()^2) then
		db.spawned_vertex_by_id[obj.id] = lv_id
	end
	-- Alundaio
	SendScriptCallback("squad_on_add_npc",self,obj,spawn_section,spawn_position,lv_id,gv_id)
	-- End Alundaio
	return obj.id
end

function sim_squad_scripted:create_npc(spawn_smart,pos,lvid,gvid)
	--utils_data.debug_write("sim_squad_scripted:create_npc")
	local settings_id = self:section_name()
	local spawn_sections = parse_names(ini_sys:r_string_ex(settings_id, "npc") or "")
	
	local base_spawn_position, base_lvi, base_gvi, spawn_vid
	if (spawn_smart) then
	
		-- Try to look/parse spawn_vid in either squad logic or smart logic
		local spawn_vid = ini_sys:r_string_to_condlist(settings_id,"spawn_vid") or spawn_smart.ini:r_string_to_condlist(settings_id,"spawn_vid")
		if (spawn_vid) then
			local str = xr_logic.pick_section_from_condlist(db.actor,self,spawn_vid)
			if (str and str ~= "" and str ~= "nil") then
				local vid = str_explode(str,":")
				base_lvi = tonumber(vid[1])
				base_spawn_position = level.vertex_position(base_lvi)
				base_gvi = tonumber(vid[2])
			end
		end
		
		-- Try to look/parse spawn_point in either squad logic or smart logic
		if not (base_spawn_position and base_lvi and base_gvi) then
			local spawn_point = ini_sys:r_string_to_condlist(settings_id,"spawn_point") or spawn_smart.ini:r_string_to_condlist(settings_id,"spawn_point")
			if (spawn_point) then 
				local p_path = xr_logic.pick_section_from_condlist(db.actor, self, spawn_point)
				if (p_path and p_path ~= "" and p_path ~= "nil") then 
					local pat = patrol(p_path)
					if (pat) then
						base_spawn_position = pat:point(0)
						base_lvi = pat:level_vertex_id(0)
						base_gvi = pat:game_vertex_id(0)
					end
				end
			end
		end
		
		-- Try spawn_smart.spawn_point
		if not (base_spawn_position and base_lvi and base_gvi) then
			if (spawn_smart.spawn_point) then	
				local pat = patrol(spawn_smart.spawn_point)
				if (pat) then
					base_spawn_position = pat:point(0)
					base_lvi = pat:level_vertex_id(0)
					base_gvi = pat:game_vertex_id(0)
				end
			end
		end
		
		-- if all else fail, spawn on smart
		if not (base_spawn_position and base_lvi and base_gvi) then
			base_spawn_position = spawn_smart.position
			base_lvi = spawn_smart.m_level_vertex_id
			base_gvi = spawn_smart.m_game_vertex_id
		end
		
		self.smart_id = spawn_smart.id
	else 
		printf('spawn smart: %s', spawn_smart)
		base_spawn_position = pos or self.position
		base_lvi = lvid or self.m_level_vertex_id
		base_gvi = gvid or self.m_game_vertex_id
	end 

	local npcs_list = {}
	for ind=1,#spawn_sections do
		local id = self:add_squad_member(spawn_sections[ind], base_spawn_position, base_lvi, base_gvi,ind)
		if (id) then
			npcs_list[#npcs_list+1] = id
		end
	end
	
	local random_spawn = ini_sys:r_string_ex(settings_id,"npc_random")
	if random_spawn ~= nil then
		random_spawn = parse_names(random_spawn)

		local count = ini_sys:r_string_ex(settings_id,"npc_in_squad") or "1,2"
		count = str_explode(count,",")
		count[1] = count[1] and tonumber(count[1])
		count[2] = count[2] and tonumber(count[2]) or count[1]

		if _G.WARFARE then
			local faction = ini_sys:r_string_ex(settings_id, "faction")
			if (warfare_options.options.factions[faction]) then
				if (warfare_options.options.factions[faction].random_squad_count) then
					printf("!!! GETTING RANDOM COUNT FOR "..faction.." | " .. tonumber(warfare_options.options.factions[faction].min_random_squad_count) .. " - " .. tonumber(warfare_options.options.factions[faction].max_random_squad_count) .. " !!!!")
					count[1] = tonumber(warfare_options.options.factions[faction].min_random_squad_count)
					count[2] = tonumber(warfare_options.options.factions[faction].max_random_squad_count)
				end
			end
		end
		
		-- check for random squad members for each faction here.
		
		local random_count = count[1] and count[2] and math.random(count[1],count[2]) or math.random(1,2)
		random_count = random_count - self:npc_count()
		
		local random_id
		for i = 1,random_count do
			random_id = math.random(1, #random_spawn)
			local id = self:add_squad_member(random_spawn[random_id], base_spawn_position, base_lvi, base_gvi)
			if (id) then
				npcs_list[#npcs_list+1] = id
			end
		end
	elseif #spawn_sections == 0 then
		printf("You are trying to spawn an empty squad [%s]!!!", settings_id)
	end

	if not (spawn_smart) then 
		local smart = self.smart_id and db.smart_terrain_by_id[self.smart_id]
		if (smart) then
			local id 
			for i=1, #npcs_list do 
				id = npcs_list[i]
				local se_obj = id and alife_object(id)
				if (se_obj) then 
					smart:register_npc(se_obj)
					SIMBOARD:setup_squad_and_group(se_obj)
				end
			end
		end
	end 
	
	self:set_squad_relation()
	self:refresh()
end

function sim_squad_scripted:set_squad_sympathy(sympathy)
	--utils_data.debug_write("sim_squad_scripted:set_squad_sympathy")
	local symp = sympathy or self.sympathy
	if(symp~=nil) then
		local npc
		for k in self:squad_members() do
			npc = db.storage[k.id] and db.storage[k.id].object
			if(npc) then
				game_relations.set_npc_sympathy(npc, symp)
			else
				if(db.goodwill.sympathy==nil) then
					db.goodwill.sympathy = {}
				end
				db.goodwill.sympathy[k.id] = symp
			end
		end
	end
end

function sim_squad_scripted:set_squad_relation(relation)
	--utils_data.debug_write("sim_squad_scripted:set_squad_relation")
	if (is_squad_monster[self.player_id]) then 
		return
	end
	local rel = relation or self.relationship
	if (rel and (rel == "enemy" or rel == "friend" or rel == "neutral")) then
		local sim = alife()
		for k in self:squad_members() do
			local npc = db.storage[k.id] and db.storage[k.id].object
			if (npc) then
				game_relations.set_npcs_relation(npc, db.actor, rel)
			elseif (k.object) then
				local goodwill = rel == "enemy" and -1000 or rel == "friend" and 1000 or 0
				k.object:force_set_goodwill(goodwill, 0)
			end
		end
	end
end

local function reset_animation(npc)
	--utils_data.debug_write("sim_squad_scripted:reset_animation")
	local state_mgr = db.storage[npc:id()].state_mgr
	if state_mgr == nil then
		return
	end
	local planner = npc:motivation_action_manager()
	--if not planner or not planner:initialized() then return end


	state_mgr.animation:set_state(nil, true)
	state_mgr.animation:set_control()
	state_mgr.animstate:set_state(nil, true)
	state_mgr.animstate:set_control()

	state_mgr:set_state("idle", nil, nil, nil, {fast_set = true})

	state_mgr:update()
	state_mgr:update()
	state_mgr:update()
	state_mgr:update()
	state_mgr:update()
	state_mgr:update()
	state_mgr:update()

	npc:set_body_state(move.standing)
	npc:set_mental_state(anim.free)
end

function sim_squad_scripted:set_squad_position(position)
	--utils_data.debug_write("sim_squad_scripted:set_squad_position")
	if self.online == false then
		self:force_change_position(position)
	end

	local cl_object
	for k in self:squad_members() do
		cl_object = db.storage[k.id] and db.storage[k.id].object
		if not (db.offline_objects[k.id]) then
			db.offline_objects[k.id] = {}
		end
		db.offline_objects[k.id].level_vertex_id = level.vertex_id(position)
		if cl_object then
			reset_animation(cl_object)
			cl_object:set_npc_position(position)
		else
			k.object.position = position
		end
	end
end

function sim_squad_scripted:has_detector()
	local sim = alife()
	for k in self:squad_members() do
		local se_obj = sim:object(k.id)
		if se_obj and se_obj:has_detector() then
			return true
		end
	end
	return false
end

function sim_squad_scripted:get_squad_community()
	local squad_community = squad_community_by_behaviour[self.player_id]
	if squad_community == nil then
		printf("squad community is 'nil' for player_id [%s]", self.player_id)
	end
	return squad_community
end

function sim_squad_scripted:has_items_to_sell()
	if not (self.online) then
		return false
	end
	local sim = alife()
	local st
	for k in self:squad_members() do
		st = db.storage[k.id]
		if (st and st.has_items_to_sell) then
			return true
		end
	end
	return false
end

function sim_squad_scripted:has_tech_items()
	if not (self.online) then
		return false
	end
	local sim = alife()
	local st
	for k in self:squad_members() do
		st = db.storage[k.id]
		if (st and st.has_tech_items) then
			return true
		end
	end
	return false
end


--------------------------------------------------------------
-- SAVE\LOAD
--------------------------------------------------------------
--[[
function sim_squad_scripted:save_state(m_data)
	--utils_data.debug_write(strformat("%s:%s sim_squad_scripted:save_state BEFORE",self:name(),self.id))
	m_data.sim_squad_scripted[self.id] = {}
	m_data.sim_squad_scripted[self.id].name = self:name() -- debugging only
	m_data.sim_squad_scripted[self.id].current_target_id = self.current_target_id
	m_data.sim_squad_scripted[self.id].respawn_point_id = self.respawn_point_id
	m_data.sim_squad_scripted[self.id].respawn_point_prop_section = self.respawn_point_prop_section
	m_data.sim_squad_scripted[self.id].smart_id = self.smart_id
	--utils_data.debug_write(strformat("%s:%s sim_squad_scripted:save_state AFTER",self:name(),self.id))
end 

function sim_squad_scripted:load_state(m_data)
	if not (m_data.sim_squad_scripted and m_data.sim_squad_scripted[self.id]) then 
		return 
	end
	--utils_data.debug_write("sim_squad_scripted:load_state BEFORE")
	self.current_target_id = m_data.sim_squad_scripted[self.id].current_target_id
	self.respawn_point_id = m_data.sim_squad_scripted[self.id].respawn_point_id
	self.respawn_point_prop_section = m_data.sim_squad_scripted[self.id].respawn_point_prop_section
	self.smart_id = m_data.sim_squad_scripted[self.id].smart_id
	m_data.sim_squad_scripted[self.id] = nil
	--utils_data.debug_write("sim_squad_scripted:load_state AFTER")
end 
--]]

function sim_squad_scripted:STATE_Write(packet)
	--utils_data.debug_write("sim_squad_scripted:STATE_Write")
	cse_alife_online_offline_group.STATE_Write (self, packet)
	
	--if (USE_MARSHAL) then 
		--return 
	--end
	
	set_save_marker(packet, "save", false, "sim_squad_scripted")

	packet:w_stringZ(tostring(self.current_target_id))
	packet:w_stringZ(tostring(self.respawn_point_id))
	packet:w_stringZ(tostring(self.respawn_point_prop_section))
	packet:w_stringZ(tostring(self.smart_id))
	
	local scripted_target_tosave = self.scripted_target
	if not scripted_target_tosave then scripted_target_tosave = "nil" end
	
	packet:w_stringZ(tostring(scripted_target_tosave))
	--packet:w_stringZ(tostring(self.online))

	set_save_marker(packet, "save", true, "sim_squad_scripted")
end

function sim_squad_scripted:STATE_Read(packet, size)
	--utils_data.debug_write( strformat("\n%s:STATE_Read start",self:name()) )
	cse_alife_online_offline_group.STATE_Read (self, packet, size)
	
	--if (USE_MARSHAL) then 
		--self:load_state(alife_storage_manager.get_state())
		--self:init_squad_on_load()
		--return 
	--end 
	
	set_save_marker(packet, "load", false, "sim_squad_scripted")	

	self.current_target_id = packet:r_stringZ()
	if self.current_target_id == "nil" then
		self.current_target_id = nil
	else
		self.current_target_id = tonumber(self.current_target_id)
	end
	self.respawn_point_id = packet:r_stringZ()
	if self.respawn_point_id == "nil" then
		self.respawn_point_id = nil
	else
		self.respawn_point_id = tonumber(self.respawn_point_id)
	end
	self.respawn_point_prop_section = packet:r_stringZ()
	if self.respawn_point_prop_section == "nil" then
		self.respawn_point_prop_section = nil
	end
	self.smart_id = packet:r_stringZ()
	if self.smart_id == "nil" then
		self.smart_id = nil
	else
		self.smart_id = tonumber(self.smart_id)
	end
	
	self.scripted_target = packet:r_stringZ()
	if self.scripted_target == "nil" then
		self.scripted_target = nil
	end
	
	--[[
	local isOnline = packet:r_stringZ()
	
	if isOnline == "false" then
		self.online = false
	end
	--]]
	self:init_squad_on_load()
	
	set_save_marker(packet, "load", true, "sim_squad_scripted")
	--utils_data.debug_write( strformat("%s:STATE_Read end",self:name()) )
end


--------------------------------------------------------------
-- SERVER_OBJECT
--------------------------------------------------------------
function sim_squad_scripted:on_register()
	--utils_data.debug_write( strformat("%s:on_register start",self:name()) )
	cse_alife_online_offline_group.on_register( self )
	SendScriptCallback("server_entity_on_register",self,"sim_squad_scripted")
	sim_board.get_sim_board().squads[self.id] = true
	--simulation_objects.register(self)
	--SendScriptCallback("squad_on_register",self)
	--utils_data.debug_write( strformat("%s:on_register end",self:name()) )
end
function sim_squad_scripted:on_unregister()
	--utils_data.debug_write("sim_squad_scripted:on_unregister")
	--SendScriptCallback("squad_on_unregister",self)
	SendScriptCallback("server_entity_on_unregister",self,"sim_squad_scripted")
	SIMBOARD.squads[self.id] = nil
	SIMBOARD:assign_squad_to_smart(self, nil)
	--simulation_objects.unregister(self)
	if self.respawn_point_id ~= nil then
		local smart = alife_object(self.respawn_point_id)
		if smart == nil then
			return
		end
		if smart.already_spawned ~= nil and smart.already_spawned[self.respawn_point_prop_section] ~= nil then
			smart.already_spawned[self.respawn_point_prop_section].num = smart.already_spawned[self.respawn_point_prop_section].num - 1
		end
	end
	cse_alife_online_offline_group.on_unregister(self)
end

-- warning: the one inside warfare_options.script is used regardless of if warfare mode is on or not
function sim_squad_scripted:check_online_status()
	local b = nil
	if (get_object_story_id(self.id)) then 
		-- don't enforce any rule
	elseif (self.force_online) then -- for tasks
		b = true
	elseif (self.__lock) then -- couldn't find a target
		b = false
	elseif (IsSurvivalMode()) then
		b = true
	elseif (axr_companions.companion_squads[self.id]) then 
		b = true
	elseif not (level_weathers.valid_levels[level.name()]) then -- underground levels
		-- don't enforce any rule
	else
		if (self.online ~= true and is_squad_monster[self.player_id]) then -- monster behaviors
			local hour = level.get_time_hours()
			if (self.player_id == "monster_predatory_day") then 
				-- Monster will not come online during the hours of (10PM till 5AM)
				if (hour <= 5 or hour >= 22) then
					b = false 
				end
			elseif (self.player_id == "monster_zombied_day") then 
				-- Monster will not come online during the hours of (10PM till 5AM)
				if (hour <= 5 or hour >= 22) then
					b = false
				end
			elseif (self.player_id == "monster_predatory_night") then 
				-- Monster will not come online during the hours of (6AM till 6PM)
				if (hour >= 6 and hour <= 18) then
					b = false
				end
			elseif (self.player_id == "monster_zombied_night") then 
				-- Monster will not come online during the hours of (5AM till 7PM)
				if (hour >= 6 and hour <= 18) then
					b = false
				end
			end
			if (b == false) then
				self.assigned_target_id = self.id
				self.current_target_id = self.id
				self.current_action = 0
			end
		end
		if (b == nil and db.actor) then
			if (db.actor:has_info("enforce_online_exclusion")) then -- For story needs
				if (self.online) then -- exclude from coming online except if already online or storied
					if (self.was_forced_offline or self.position:distance_to(db.actor:position()) >= 2500) then
						self.was_forced_offline = false
						b = false -- switch online to offline
					end
				else
					b = false -- all simulation already offline, refuse to come online while actor has info
				end
			else
				if not (self.online) then
					local dist = excl_dist
					if (dist > 0 and not db.actor:has_info("actor_is_sleeping")) then 
						if (self.position:distance_to(db.actor:position()) <= dist) then
							b = false -- force offline for online exclusion
						end
					end
				end
			end
		end
	end
	self.forced_online_status = b
end

function sim_squad_scripted:can_switch_offline()
	--[[
	if (self.current_action == 1) then 
		local current_target = self.current_target_id and alife_object(self.current_target_id)
		if (current_target.online and current_target.faction_controlled and current_target.faction_war_in_progress == true) then
			--printf("forced online %s",current_target:name())
			return false
		end
	end
	--]]
	
	if not (cse_alife_online_offline_group.can_switch_offline(self)) then 
		return false
	end
	
	if (self.forced_online_status == true) then
		return false
	end

	return true
end

function sim_squad_scripted:can_switch_online()
	-- important to check with inherited method first if it's okay to switch online, otherwise this leads to
	-- crash saying 'object with ID already exists' when returning false and object is already online
	if not cse_alife_online_offline_group.can_switch_online(self) then 
		return false
	end
	
	if (self.forced_online_status == false) then 
		return false
	end
	
    return true
end
function sim_squad_scripted:switch_offline()
	cse_alife_online_offline_group.switch_offline(self)
end
function sim_squad_scripted:switch_online()
	-- if (DEV_DEBUG) then
	--	printf("sim_squad_scripted:switch_online() [%s]",self:name())
	-- end
	cse_alife_online_offline_group.switch_online(self)
end


--------------------------------------------------------------
-- MAP LOCATION
--------------------------------------------------------------
function sim_squad_scripted:refresh()
	if(self:commander_id()==nil) then
		self:hide()
		return
	end
	self:show()
end

function sim_squad_scripted:hide()
	if(self.current_spot_id==nil) or (self.spot_section==nil) then
		return
	end
	level.map_remove_object_spot(self.current_spot_id, self.spot_section)
	self.current_spot_id = nil
	self.spot_section = nil
end

local squad_icons = {
		["stalker"]  	= "warfare_stalker_spot",
		["dolg"]     	= "warfare_duty_spot",
		["freedom"]  	= "warfare_freedom_spot",
		["killer"]   	= "warfare_killer_spot",
		["csky"]     	= "warfare_csky_spot",
		["monolith"] 	= "warfare_monolith_spot",
		["army"]     	= "warfare_army_spot",
		["ecolog"]   	= "warfare_ecolog_spot",
		["bandit"]   	= "warfare_bandit_spot",
		["greh"]   		= "warfare_greh_spot",
		["isg"]   		= "warfare_isg_spot",
		["renegade"]   	= "warfare_renegade_spot",
		["greh_npc"] 	= "warfare_greh_spot",
		["army_npc"] 	= "warfare_army_spot",
		["zombied"]  	= "warfare_zombied_spot",
		["monster"]  	= "alife_presentation_squad_monster_1",
		["friends"]  	= "alife_presentation_squad_friend_1",
		["neutral"]  	= "alife_presentation_squad_neutral_1",
		["enemy"]    	= "alife_presentation_squad_enemy_1"
		--["companion"]	= "companion_spot"
}
function sim_squad_scripted:show()
	if self.show_disabled then
		self:hide()
		return
	end

    if(level.map_has_object_spot(self:commander_id(), "ui_pda2_trader_location")~=0) or
	(level.map_has_object_spot(self:commander_id(), "ui_pda2_mechanic_location")~=0) or
	(level.map_has_object_spot(self:commander_id(), "ui_pda2_scout_location")~=0) or
	(level.map_has_object_spot(self:commander_id(), "ui_pda2_quest_npc_location")~=0) or
	(level.map_has_object_spot(self:commander_id(), "ui_pda2_medic_location")~=0) then
		self.show_disabled = true
		return
	end

	if self.current_spot_id ~= self:commander_id() then
		self:hide()
		self.current_spot_id = self:commander_id()
		self:show()
		return
	end
	
	-- local param = squad_smart.get_params() or {}
	local sim = alife()
	local se_actor = sim:actor()
	local spot = ""

	-- 1. Debug mode + Debug HUD enabled
	if (DEV_DEBUG and dbg_map_hud) then
		spot = squad_icons[self.player_id] or "warfare_mutant_spot"
    
	-- 2. Old World Addon - squad leaders show up by relation instead of faction and don't require a PDA to see. Game seems to be using sim_squad_warfare for this one, but I am putting it here anyway.
	elseif not(is_squad_monster[self.player_id]) then
		local relation = game_relations.get_squad_relation_to_actor_by_id(self.id)
		if(relation=="friends") then
			spot = "alife_presentation_squad_friend"
		elseif(relation=="neutral") then
			spot = "alife_presentation_squad_neutral"
		end
	end

	
	if (spot ~= "") then
		if spot == self.spot_section then
			level.map_change_spot_hint(self.current_spot_id, self.spot_section, self:get_squad_props())
			return
		end
		
		if (self.spot_section == nil) then
			level.map_add_object_spot(self.current_spot_id, spot, self:get_squad_props())
		else
			level.map_remove_object_spot(self.current_spot_id, self.spot_section)
			level.map_add_object_spot(self.current_spot_id, spot, self:get_squad_props())
		end
		
		self.spot_section = spot
		
	elseif (self.spot_section ~= nil) then
		level.map_remove_object_spot(self.current_spot_id, self.spot_section)
		self.spot_section = nil
	end
end

function sim_squad_scripted:get_squad_props()
	local t = ""
	
	-- 1. Debug mode + Debug HUD enabled
	if (DEV_DEBUG and dbg_map_hud) then
		local assigned_target = self.assigned_target_id and alife_object(self.assigned_target_id)
		local current_target = self.current_target_id and alife_object(self.current_target_id)

		t = "%s\\n\\n%s\\n\\nnpcs=%s\\n\\ncurrent_target=%s\\nassigned_target=%s\\n\\n"
		t = strformat(t,self.disabled and "[disabled]" or "",self:name(),self:npc_count(),self.current_target_id == AC_ID and "actor" or current_target and current_target:name(),self.assigned_target_id == AC_ID and "actor" or assigned_target and assigned_target:name())

		if (self.current_action) then
			if (self.current_action == 0) then 
				t = t .. "Trying to Reach Target"
			elseif (self.current_action == 1) then 
				t = t .. "Stay On Target"
			end
			
			if (self.current_action == 1 and self.stay_time) then
				t = strformat("%s = %s (%s)",t,tostring(self.idle_time - game.get_game_time():diffSec(self.stay_time)),self.idle_time)
			end
		end

		return t
	
	-- 2. Everything else
	elseif (not is_squad_monster[self.player_id]) then
		return game.translate_string(self.player_id)
	end

	
	local assigned_target = self.assigned_target_id and alife_object(self.assigned_target_id)
	local assigned_target_name = (self.assigned_target_id == AC_ID and "actor") or (assigned_target and assigned_target:name())
	local current_target = self.current_target_id and alife_object(self.current_target_id)
	local current_target_name = (self.current_target_id == AC_ID and "actor") or (current_target and current_target:name())

	if assigned_target_name and (assigned_target_name ~= current_target_name) then
		assigned_target_name = (assigned_target_name == "actor" and alife():actor():character_name()) or game.translate_string("st_"..assigned_target_name.."_name")
		t = t .. "\\n" .. game.translate_string("st_sq_target") .. " - \\n" .. assigned_target_name
	end
	
	return t
end

function sim_squad_scripted:get_squad_relation()
    local squad = alife_object(self.id)
	if (is_squad_monster[squad.player_id]) then 
		return "monster"
	end
    local goodwill = 0
    local npc_count = 0
    for k in squad:squad_members() do
    local object = db.storage[k.id] and db.storage[k.id].object
        if object and db.actor then
            goodwill = goodwill + object:general_goodwill(db.actor)
            npc_count = npc_count + 1
        end
    end
    if npc_count ~= 0 then
        local delta = goodwill/npc_count
        if delta <= -1000 then
            return "enemy"
        elseif delta >= 1000 then
            return "friends"
        elseif delta < 1000 and delta > -1000 then
            return "neutral"
        end
    end   
    local relation = 0
    if db.actor then
		relation = db.actor:community_goodwill(self.player_id)+game_relations.get_factions_community(self.player_id, alife():actor():community())
    else
		relation = game_relations.get_factions_community(self.player_id, alife():actor():community())
    end
    
    if relation >= 1000 then
		return "friends"
    elseif relation <= -1000 then
		return "enemy"
    else
		return "neutral"
    end
end


--------------------------------------------------------------
-- SIMULATION_TARGET_SQUAD
--------------------------------------------------------------
function sim_squad_scripted:get_location()
	return self.position, self.m_level_vertex_id, self.m_game_vertex_id
end

function sim_squad_scripted:get_current_task() -- called from engine
	--utils_data.debug_write("sim_self_scripted:get_current_task")
	local se_target = self.assigned_target_id and simulation_objects.get_server_entity(self.assigned_target_id)
	if (se_target) then
		local npc_info = se_target.npc_info and se_target.npc_info[self:commander_id()]
		if (npc_info and npc_info.job) then
			return npc_info.job.alife_task or se_target:get_alife_task() or self:get_alife_task()
		end
		return se_target:get_alife_task()
	end
	return self:get_alife_task()
end

function sim_squad_scripted:am_i_reached(squad)
	return self:npc_count() == 0 or self.id == squad.id
end

function sim_squad_scripted:on_after_reach(squad)
	squad.current_action = nil
	squad.current_target_id = nil
end

function sim_squad_scripted:on_reach_target(squad)
	--utils_data.debug_write("sim_squad_scripted:on_reach_target")
	squad:set_location_types()
 	for k in squad:squad_members() do
		db.spawned_vertex_by_id[k.id] = nil
		if db.offline_objects[k.id] ~= nil then
			db.offline_objects[k.id] = db.offline_objects[k.id] and empty_table(db.offline_objects[k.id])
		end
		db.spawned_vertex_by_id[k.id] = nil
	end
	SIMBOARD:assign_squad_to_smart(squad, nil)
end

function sim_squad_scripted:get_alife_task()
	return CALifeSmartTerrainTask(self.m_game_vertex_id, self.m_level_vertex_id)
end

local smarts_by_no_assault_zones = {
									["zat_a2_sr_no_assault"] 	= "zat_stalker_base_smart",
									["jup_a6_sr_no_assault"] 	= "jup_a6",
									["jup_b41_sr_no_assault"] 	= "jup_b41"
}

function sim_squad_scripted:sim_available()
	return false 
	--[[
	if self.smart_id == nil then
		return true
	end

	local smart = simulation_objects.get_server_entity(self.smart_id)
	if not (smart) then
		return false
	end

	-- Can't be targetted by other squads if at base or trade prop smart
	if (smart.props and smart.props.base and smart.props.base > 0) then
		return false
	end

	return true
	--]]
end

function sim_squad_scripted:target_precondition(squad)
	if (self.id == squad.id) then 
		return true 
	end 
	
	if (sim_board.general_squad_precondition(squad,self)) then 
		return true 
	end

	return false
end

function sim_squad_scripted:evaluate_prior(squad)
	return simulation_objects.evaluate_prior(self, squad)
end

-- function on_game_start()
	-- RegisterScriptCallback("squad_on_after_game_vertex_change",ongv)
	-- RegisterScriptCallback("squad_on_after_level_change",onlv)
-- end 

-- function onlv(se, old_lvl, cur_lvl)
	-- printf('[%s]: changed level: [%s] --> [%s]', se:name(), old_lvl, cur_lvl)
-- end

-- function ongv(se, last_gv, cur_gv, change)
	-- printf('[%s]: changed game vertex: [%s] --> [%s] (level changed: %s)', se:name(), last_gv, cur_gv, change)
-- end
