-- ============================================================ -- -- heli_combat.script -- CoC 1.5b r4 - DoctorX Call of The Zone 1.0 -- -- Modified by: DoctorX -- Last revised: November 09, 2019 -- -- ============================================================ -- ---------------------------------------------------------------------------------------------------- --[[------------------------------------------------------------------------------------------------ Универсальная боевая схема вертолёта Чугай Александр В отличие от сталкеров боевая схема не является отдельным действием, а вызывается из других схем. -- revamped by Alundaio Note: Heli Enemy Flag Is Set/Reset in axr_main, xr_combat_ignore, surge_manager, axr_radio_heli and heli_combat scripts Only flagged targets will be helicopter enemies during A-life heli simulation 8/18/2015: removed retreat combat type --------------------------------------------------------------------------------------------------]] -- //////////////////////////////////////////////////////////////////////////////////////////////// -- -- NPC safe zones -- -- Added by DoctorX -- for DoctorX Call of The Zone 1.0 -- November 09, 2019 -- -- ------------------------------------------------------------------------------------------------ local drx_cotz_heli_safe_zones = {} -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ local combat_type_flyby = 0 -- атака пролётами над целью local combat_type_round = 1 -- кружение вокруг позиции, атака кружась вокруг цели local combat_type_search = 2 -- поиск врага, кружение вокруг точки, где последний раз видел, атака кружась вокруг цели local combat_type_retreat = 3 -- улёт за пределы уровня local flyby_state_to_attack_dist = 0 local flyby_state_to_enemy = 1 function distance_2d( a, b ) return math.sqrt( (b.x-a.x)^2 + (b.z-a.z)^2 ) end -- пересечение луча и круга. -- p - точка начала луча, v - направление луча (орт), o - центр круга, r - радиус круга -- точка p должна быть внутри круга function cross_ray_circle( p, v, o, r ) local po = vector():set( o ):sub( p ) local vperp = vector():set( -v.z, 0, v.x ) local l = math.sqrt( ( r ^ 2 ) - ( vector():set( po ):dotproduct( vperp ) ^ 2 ) ) return vector():set( p ):add( vector():set( v ):mul( vector():set( po ):dotproduct( v ) + l ) ) end ---------------------------------------------------------------------------------------------------- class "heli_combat" function heli_combat:__init( object, heliObject ) self.st = db.storage[object:id()] self.object = object self.heliObject = heliObject self.initialized = false self.level_max_y = level.get_bounding_volume().max.y local ltx = system_ini() local section = object:section() self.flyby_attack_dist = ltx:r_float_ex(section,"flyby_attack_dist") or 0 self.search_attack_dist = ltx:r_float_ex(section,"search_attack_dist") or 0 self.default_safe_altitude = (ltx:r_float_ex(section,"safe_altitude") or 0) + self.level_max_y self.m_max_mgun_dist = ltx:r_float_ex(section,"max_mgun_attack_dist") or 0 self.default_velocity = ltx:r_float_ex(section,"velocity") or 30 self.search_velocity = ltx:r_float_ex(section,"search_velocity") or 40 self.round_velocity = ltx:r_float_ex(section,"round_velocity") or 60 self.vis_time_quant = ltx:r_float_ex(section,"vis_time_quant") or 0 self.vis_threshold = ltx:r_float_ex(section,"vis_threshold") or 0 self.vis_inc = (ltx:r_float_ex(section,"vis_inc") or 0) * self.vis_time_quant * 0.001 self.vis_dec = (ltx:r_float_ex(section,"vis_dec") or 0) * self.vis_time_quant * 0.001 self.vis = 0 self.vis_next_time = 0 self.forget_timeout = (ltx:r_float_ex(section,"forget_timeout") or 0) * 1000 self.flame_start_health = ltx:r_float_ex(section,"flame_start_health") or 0.1 self.attack_before_retreat = false self.section_changed = false self.combat_use_rocket = ltx:r_bool_ex(section,"combat_use_rocket",false) self.combat_use_mgun = ltx:r_bool_ex(section,"combat_use_mgun",true) self.actor_always_enemy= ltx:r_bool_ex(section,"actor_always_enemy",false) self.max_velocity = self.default_velocity self.safe_altitude = self.default_safe_altitude + self.level_max_y end function heli_combat:is_enemy(obj) --alun_utils.debug_write(strformat("heli_combat:is_enemy",obj and obj:name())) -- /////////////////////////////////////////////////////////////////////////////////////////////// -- -- Remove Mutants as Helicopter Enemy -- -- Modified by DoctorX -- for DoctorX Call of The Zone 1.0 -- April 07, 2018 -- -- ----------------------------------------------------------------------------------------------- -- if not (IsStalker(obj) or IsMonster(obj)) then if ( not IsStalker( obj ) ) then -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ return false end if not (obj:alive()) then return false end if (xr_combat_ignore.ignore_enemy_by_overrides(self.object,obj)) then return false end if (IsWounded(obj)) then return false end local se_obj = alife_object(self.object:id()) if not (se_obj and se_obj.community) then return false end -- /////////////////////////////////////////////////////////////////////////////////////////////// -- -- Ignore Bounty Task Targets -- -- Modified by DoctorX -- for DoctorX Call of The Zone 1.0 -- May 13, 2019 -- -- ----------------------------------------------------------------------------------------------- -- Ignore target if assassination target: if ( axr_task_manager.bounties_by_id[obj:id( )] ~= nil ) then return false end -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ -- /////////////////////////////////////////////////////////////////////////////////////////////// -- -- Ignore Hostages -- -- Added by DoctorX -- for DoctorX Call of The Zone 1.0 -- May 17, 2019 -- -- ----------------------------------------------------------------------------------------------- -- Ignore target if hostage: if ( xrs_kill_wounded.hostage_list[obj:id( )] ~= nil ) then return false end -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ -- /////////////////////////////////////////////////////////////////////////////////////////////// -- -- Ignore Story NPC's -- -- Added by DoctorX -- for DoctorX Call of The Zone 1.0 -- May 17, 2019 -- -- ----------------------------------------------------------------------------------------------- -- Ignore target if has story id: if ( (obj:id( ) ~= db.actor:id( )) and (get_object_story_id( obj:id( ) ) ~= nil) ) then return false end -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ -- /////////////////////////////////////////////////////////////////////////////////////////////// -- -- NPC Safe Zones -- -- Added by DoctorX -- for DoctorX Call of The Zone 1.0 -- November 09, 2019 -- -- ----------------------------------------------------------------------------------------------- -- Check if current target is in safe smart: for i = 1, ( #drx_cotz_heli_safe_zones ) do local current_smart = SIMBOARD.smarts_by_names[drx_cotz_heli_safe_zones[i]] if ( current_smart ) then local current_smart_id = current_smart.id local target_obj = alife_object( obj:id( ) ) if ( target_obj ) then if ( (obj:id( ) == db.actor:id( )) or (obj:has_info( "npcx_is_companion" )) ) then if ( (smart_terrain.nearest_to_actor_smart.id or -1) == current_smart_id ) then return false end if ( obj:has_info( "npcx_beh_wait" ) or obj:has_info( "npcx_beh_patrol_mode" ) ) then return false end end local target_smart = (target_obj.m_smart_terrain_id or -1) if ( target_smart == current_smart_id ) then return false end end end end -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if (obj:id() == db.actor:id()) then -- ////////////////////////////////////////////////////////////////////////////////////////////// -- -- Check if Actor Not Enemy -- -- Modified by DoctorX -- for DoctorX Call of The Zone 1.0 -- April 07, 2018 -- -- ---------------------------------------------------------------------------------------------- -- if (string.find(character_community(obj),se_obj.community)) then if ( relation_registry.community_relation( se_obj.community, alife( ):actor( ):community( ) ) > game_relations.ENEMIES ) then -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ return false end -- ////////////////////////////////////////////////////////////////////////////////////////////// -- -- Stop Using Enemy Flag -- -- Modified by DoctorX -- for DoctorX Call of The Zone 1.0 -- April 07, 2018 -- -- ---------------------------------------------------------------------------------------------- -- if (utils.load_var(db.actor,"heli_enemy_flag",false) == true) then -- return true -- end -- return self.actor_always_enemy or false -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ else -- ////////////////////////////////////////////////////////////////////////////////////////////// -- -- Check if NPC Not Enemy -- -- Modified by DoctorX -- for DoctorX Call of The Zone 1.0 -- April 07, 2018 -- -- ---------------------------------------------------------------------------------------------- -- if (se_obj.community == character_community(obj)) then if ( relation_registry.community_relation( se_obj.community, character_community( obj ) ) > game_relations.ENEMIES ) then -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ return false end end if (IsSurvivalMode()) then return true end -- /////////////////////////////////////////////////////////////////////////////////////////////// -- -- Stop Using Enemy Flag -- -- Modified by DoctorX -- for DoctorX Call of The Zone 1.0 -- April 07, 2018 -- -- ----------------------------------------------------------------------------------------------- -- if not (db.storage[obj:id()] and db.storage[obj:id()].heli_enemy_flag) then -- return false -- end -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ local squad = obj:id() ~= 0 and get_object_squad(obj) local cid = squad and squad.id or id if (xr_combat_ignore.safe_zone_npcs[cid]) then return false end return true end function heli_combat:set_enemy(target) --alun_utils.debug_write(strformat("heli_combat:set_enemy",target and target:name())) local tg = time_global() self.st.enemy_id = target:id() self.__keep_target_until = tg + 25000 self.enemy_last_seen_time = tg self.enemy_last_seen_pos = vector():set(target:position()) self.initialized = false self.heliObject:ClearEnemy() end function heli_combat:find_valid_target() --alun_utils.debug_write(strformat("heli_combat:find_valid_target")) -- Evaluate current target local tg = time_global() local current_target = self.st.enemy_id and db.storage[self.st.enemy_id] and db.storage[self.st.enemy_id].object local current_target_visible if (current_target) then if not (self:is_enemy(current_target)) then self:forget_enemy() current_target = nil else current_target_visible = self:SeeEnemy() --self.heliObject:isVisible(current_target) if (current_target_visible) then -- Keep target for short duration to prevent over evaluating and to prevent constant target switching if (self.__keep_target_until and tg < self.__keep_target_until) then return true end db.storage[self.st.enemy_id].heli_enemy_flag = nil if (self.st.enemy_id == 0) then utils.save_var(db.actor,"heli_enemy_flag",false) end current_target = nil elseif (tg-self.enemy_last_seen_time > self.forget_timeout) then self:forget_enemy() current_target = nil end end end -- give priority to last hitted by target local hitted_by = db.storage[self.object:id()].hitted_by local who = hitted_by and db.storage[hitted_by] and db.storage[hitted_by].object if (who and self:is_enemy(who)) then self:set_enemy(who) db.storage[self.object:id()].hitted_by = nil return true end local size = #db.heli_enemies if (size == 0) then self:forget_enemy() return false end if (self.__eindex == nil or self.__eindex > size) then self.__eindex = 1 end local id = db.heli_enemies[self.__eindex] local target = id and db.storage[id] and db.storage[id].object self.__eindex = self.__eindex + 1 if (target == nil or not target:alive()) then return current_target ~= nil end if (current_target and id == self.st.enemy_id) then return true end if (self.heliObject:isVisible(target)) then local target_pos = target:position() if (id == 0 and utils.load_var(db.actor,"heli_enemy_flag",false) and heli_alife.distance_2d_sqr(target:position(),self.object:position()) <= 10000) or (id ~= 0 and db.storage[id].heli_enemy_flag and heli_alife.distance_2d_sqr(target:position(),self.object:position()) <= 10000) then if (self.st.ini and self.st.active_section) then local smart = xr_gulag.get_npc_smart(self.object) if (smart) then local defend_smart = self.st.ini:r_bool_ex(self.st.active_section, "defend_job") if (defend_smart) then local radius = self.st.ini:r_float_ex(self.st.active_section,"defend_job_radius") or 50 if (radius) then local dist = heli_alife.distance_2d_sqr(target_pos,smart.position) if (dist < radius) then if (id == 0) then utils.save_var(db.actor,"heli_enemy_flag",true) else db.storage[target:id()].heli_enemy_flag = true end end end end end end end if (target and self:is_enemy(target)) then if (current_target and current_target_visible) then local pos = self.object:position() local dist = pos:distance_to_sqr(target_pos) local be_dist = pos:distance_to_sqr(current_target:position()) -- Only target the closest target if (dist < be_dist) then --printf("closer target %s",target and target:name()) self:set_enemy(target) return true end else self:set_enemy(target) return true end end end return current_target ~= nil end function heli_combat:read_custom_data( ini, section ) local sys_ini = system_ini() self.combat_use_rocket = ini:r_bool_ex(section,"combat_use_rocket") or sys_ini:r_bool_ex(section,"combat_use_rocket",false) self.combat_use_mgun = ini:r_bool_ex(section,"combat_use_mgun") or sys_ini:r_bool_ex(section,"combat_use_mgun",false) self.actor_always_enemy = ini:r_bool_ex(section,"actor_always_enemy") or sys_ini:r_bool_ex(section,"actor_always_enemy",false) -- self.combat_ignore = utils.cfg_get_bool ( ini, section, "combat_ignore", self.object, false, false ) local combat_ignore = ini:r_string_ex(section,"combat_ignore") if combat_ignore then self.combat_ignore = xr_logic.parse_condlist( self.object, section, "combat_ignore", combat_ignore ) else self.combat_ignore = nil end local combat_enemy = ini:r_string_ex(section,"combat_enemy") self:set_enemy_from_custom_data( combat_enemy ) self.max_velocity = ini:r_float_ex(section,"combat_velocity") or self.default_velocity self.safe_altitude = (ini:r_float_ex(section,"combat_safe_altitude") or self.default_safe_altitude) + self.level_max_y self.section_changed = true end -- установка врага по custom data -- если враг установился этой функцией, то он не будет забываться при длительной потере видимости! -- если установился новый враг, то combat будет переинициализирован function heli_combat:set_enemy_from_custom_data(combat_enemy) if not (combat_enemy) then return end if (combat_enemy == "actor") then if db.actor then self.st.enemy_id = db.actor:id() else self:forget_enemy() end elseif (combat_enemy == "nil") then self:forget_enemy() else self.enemy = get_story_object(combat_enemy) self.st.enemy_id = self.enemy and self.enemy:id() end if (self.st.enemy_id) then self.enemy_from_custom_data = true self.initialized = false else self:forget_enemy() end end function heli_combat:set_combat_type(new_combat_type) --alun_utils.debug_write(strformat("heli_combat:set_combat_type")) if (self.change_combat_type_allowed and new_combat_type ~= self.combat_type) then self.flyby_initialized = false self.round_initialized = false self.search_initialized = false self.combat_type = new_combat_type end end function heli_combat:initialize() self.section_changed = true self.combat_type = combat_type_flyby self.flyby_states_for_one_pass = 2 self.change_combat_type_allowed = true self.heliObject.m_min_mgun_dist = 15 self.heliObject.m_max_mgun_dist = self.m_max_mgun_dist self.object:set_fastcall( self.fastcall, self ) self.heliObject:TurnEngineSound(axr_main.config:r_value("mm_options","enable_heli_engine_sound",1,false)) self.initialized = true self.flyby_initialized = false self.round_initialized = false self.search_initialized = false self.retreat_initialized = true end -- частое обновление. -- нужно для отслеживания видимости врага function heli_combat:fastcall() if self.initialized then if self.vis_next_time < time_global() then self.vis_next_time = time_global() + self.vis_time_quant local ene = self.st.enemy_id and db.storage[self.st.enemy_id] and db.storage[self.st.enemy_id].object if (ene and self.heliObject:isVisible(ene)) then self.vis = self.vis + self.vis_inc if self.vis > 100 then self.vis = 100 end else self.vis = self.vis - self.vis_dec if self.vis < 0 then self.vis = 0 end end end return false else return true end end function heli_combat:SeeEnemy() return self.enemy and self.vis >= self.vis_threshold end function heli_combat:save( packet ) --alun_utils.debug_write(strformat("heli_combat:save")) -- packet:w_bool( self.retreat_already ) -- printf( "heli_combat:save level_changing=%s", tostring( utils.level_changing() ) ) if utils.level_changing() then packet:w_bool( false ) return end packet:w_bool( self.initialized ) if self.initialized then local t = time_global() packet:w_s16 (self.st.enemy_id) packet:w_u32 (t - (self.enemy_last_seen_time or 0)) packet:w_vec3(self.enemy_last_seen_pos or vector()) packet:w_u8(self.combat_type) if self.combat_type == combat_type_search then packet:w_u32 ((self.change_pos_time or 0) - t) packet:w_bool(self.flight_direction or false) packet:w_vec3(self.center_pos or vector()) elseif self.combat_type == combat_type_flyby then packet:w_s16( self.flyby_states_for_one_pass ) end end end function heli_combat:load( packet ) --alun_utils.debug_write(strformat("heli_combat:load")) -- self.retreat_already = packet:r_bool() self.initialized = packet:r_bool() if self.initialized then self.initialized = false -- fix heli freezing after game load local t = time_global() self.st.enemy_id = packet:r_s16() self.enemy_last_seen_time = t - packet:r_u32() self.enemy_last_seen_pos = vector():set(0,0,0) packet:r_vec3(self.enemy_last_seen_pos) self.combat_type = packet:r_u8() if self.combat_type == combat_type_search then self.change_pos_time = packet:r_u32() + t self.flight_direction = packet:r_bool() self.center_pos = vector():set(0,0,0) packet:r_vec3(self.center_pos) elseif self.combat_type == combat_type_flyby then self.flyby_states_for_one_pass = packet:r_s16() end end end function heli_combat:waypoint_callback() if (self.st.enemy_id and not self:combat_ignore_check()) then self.was_callback = true return true end return false end -- Обновление параметров вертолёта, задаваемых в custom data. -- Нужно делать на каждом обновлении на случай, если во время боя логика переключилась на другую секцию. function heli_combat:update_custom_data_settings() if self.section_changed then self.heliObject.m_use_rocket_on_attack = self.combat_use_rocket self.heliObject.m_use_mgun_on_attack = self.combat_use_mgun if self.combat_type == combat_type_flyby then self.heliObject:SetMaxVelocity( self.max_velocity ) end self.section_changed = false end end function heli_combat:forget_enemy() --alun_utils.debug_write(strformat("heli_combat:forget_enemy")) self.st.enemy_id = nil self.enemy = nil self.heliObject:LookAtPoint(vector():set(0,0,0), false) self.heliObject:ClearEnemy() self.initialized = false end function heli_combat:update_combat_type( see_enemy ) --alun_utils.debug_write(strformat("heli_combat:update_combat_type")) if (self.combat_type == combat_type_flyby) then if not (see_enemy) then self:set_combat_type(combat_type_search) elseif (distance_2d(self.object:position(),self.enemy_last_seen_pos) < self.flyby_attack_dist) then self:set_combat_type(combat_type_round) end elseif (self.combat_type == combat_type_round) then if (see_enemy) then if (distance_2d(self.object:position(),self.enemy_last_seen_pos) > self.flyby_attack_dist) then self:set_combat_type(combat_type_flyby) end else self:set_combat_type(combat_type_search) end elseif (self.combat_type == combat_type_search) then if (see_enemy) then if (distance_2d(self.object:position(),self.enemy_last_seen_pos) > self.flyby_attack_dist) then self:set_combat_type(combat_type_flyby) elseif (distance_2d(self.object:position(),self.enemy_last_seen_pos) > self.search_attack_dist) then self:set_combat_type(combat_type_round) end end end end -- нужно ли игнорировать врага function heli_combat:combat_ignore_check() return self.combat_ignore ~= nil and xr_logic.pick_section_from_condlist( db.actor, self.object, self.combat_ignore ) ~= nil end -- Обновление боевой схемы. Вызывается из обновлений схем логики вертолёта. -- возвращает true, если бой активен (то есть нету combat_ignore и есть враг) function heli_combat:update() if self:combat_ignore_check() then return false end --alun_utils.debug_write(strformat("heli_combat:update start")) -- job is exclusive, use custom data logic for combat local smart = xr_gulag.get_npc_smart(self.object) local npc_info = smart and smart.npc_info[self.object:id()] if (npc_info and npc_info.job and npc_info.job.exclusive) then --alun_utils.debug_write(strformat("heli_combat:update end 4")) return false end if not (self.enemy_from_custom_data) then if (not self:find_valid_target() or xr_conditions.surge_started() or not utils.is_day()) then self:forget_enemy() --alun_utils.debug_write(strformat("heli_combat:update end 3")) return false end end self.enemy = self.st.enemy_id and db.storage[self.st.enemy_id] and db.storage[self.st.enemy_id].object if not (self.enemy) then self:forget_enemy() --alun_utils.debug_write(strformat("heli_combat:update end 2")) return false end if not self.initialized then self:initialize() end self:update_custom_data_settings() local see_enemy = self:SeeEnemy() --self.heliObject:isVisible(self.enemy) if (see_enemy) then local tg = time_global() --self.__keep_target_until = tg + 10000 self.enemy_last_seen_time = tg self.enemy_last_seen_pos = vector():set(self.enemy:position()) if (self.combat_use_mgun) then if (self.combat_type == combat_type_round) then if (self.round_begin_shoot_time == nil or tg > self.round_begin_shoot_time) then self.heliObject.m_use_mgun_on_attack = not self.heliObject.m_use_mgun_on_attack if (self.heliObject.m_use_mgun_on_attack) then self.round_begin_shoot_time = tg + math.random(4000,5000) else self.round_begin_shoot_time = tg + math.random(1000,2000) end end if (self.heliObject.m_use_mgun_on_attack and self.enemy) then self.heliObject:SetEnemy(self.enemy) else self.heliObject:ClearEnemy() end else self.heliObject:SetEnemy(self.enemy) end else self.heliObject:ClearEnemy() end else self.heliObject:ClearEnemy() end self:update_combat_type( see_enemy ) if self.combat_type == combat_type_search then self:search_update( see_enemy ) elseif self.combat_type == combat_type_round then self:round_update( see_enemy ) elseif self.combat_type == combat_type_flyby then self:flyby_update( see_enemy ) elseif self.combat_type == combat_type_retreat then self:retreat_update() end --alun_utils.debug_write(strformat("heli_combat:update end 1")) return true end -- посчитать точку на заданном радиусе от последней видимой позиции врага в текущем направлении скорости вертолёта function heli_combat:calc_position_in_radius( r ) --alun_utils.debug_write("heli_combat:calc_position_in_radius") local p = self.object:position() p.y = 0 local v = self.heliObject:GetCurrVelocityVec() v.y = 0 v:normalize() local o = self.enemy_last_seen_pos o.y = 0 local ret = cross_ray_circle( p, v, o, r ) ret.y = self.safe_altitude return ret end ---------------------------------------------------------------------------------------------- -- Фунциии кружащего боя ---------------------------------------------------------------------------------------------- function heli_combat:round_update(see_enemy) --alun_utils.debug_write("heli_combat:round_update") local tg = time_global() if not self.round_initialized then self.change_combat_type_allowed = true self.heliObject:SetMaxVelocity(self.round_velocity) self.heliObject:SetSpeedInDestPoint(self.round_velocity) self.heliObject:UseFireTrail( false ) self.round_initialized = true self.__keep_state_until = tg + 10000 end if (self.__keep_state_until and tg < self.__keep_state_until) then self.change_combat_type_allowed = false else self.change_combat_type_allowed = true end if (self.change_speed_time and tg < self.change_speed_time) then return end self.change_speed_time = tg + 20000 local center_pos = vector():set(self.enemy_last_seen_pos) center_pos.y = self.safe_altitude self.heliObject:GoPatrolByRoundPath(center_pos,self.search_attack_dist, random_choice(true,false)) self.heliObject:LookAtPoint(center_pos,true) local v = math.random(5,self.round_velocity) self.heliObject:SetMaxVelocity(v) self.heliObject:SetSpeedInDestPoint(v) end ---------------------------------------------------------------------------------------------- -- Фунциии для поиска врага (скопировано с кружащего боя) ---------------------------------------------------------------------------------------------- function heli_combat:search_update( see_enemy ) --alun_utils.debug_write("heli_combat:search_update") if not self.search_initialized then self.change_combat_type_allowed = true self.heliObject:UseFireTrail(false) self.search_initialized = true local center_pos = vector():set(self.enemy_last_seen_pos) center_pos.y = self.safe_altitude self.heliObject:GoPatrolByRoundPath(center_pos, self.search_attack_dist, random_choice(true,false)) self.heliObject:LookAtPoint(center_pos, true) end local tg = time_global() if (self.change_speed_time and tg < self.change_speed_time) then return end self.change_speed_time = tg + 5000 local v = math.random(2,self.search_velocity) self.heliObject:SetMaxVelocity(v) self.heliObject:SetSpeedInDestPoint(v) end ---------------------------------------------------------------------------------------------- -- Фунциии для боя с пролётами над целью ---------------------------------------------------------------------------------------------- function heli_combat:flyby_update( see_enemy ) --alun_utils.debug_write("heli_combat:flyby_update") if not self.flyby_initialized then self.flyby_initialized = true self.heliObject:SetMaxVelocity(self.max_velocity) self.heliObject:SetSpeedInDestPoint(self.max_velocity) self.heliObject:LookAtPoint(vector(),false) self.flyby_states_for_one_pass = 3 self.change_combat_type_allowed = false self.state = flyby_state_to_enemy self.fly_target_pos = vector():set(self.enemy_last_seen_pos) self.fly_target_pos.y = self.safe_altitude self.heliObject:SetDestPosition(self.fly_target_pos) else if (self.state == flyby_state_to_enemy and self.fly_target_pos and distance_2d( self.object:position(), self.fly_target_pos ) <= 30) then self.flyby_states_for_one_pass = self.flyby_states_for_one_pass - 1 self.state = flyby_state_to_attack_dist self.fly_target_pos = self:calc_position_in_radius(50) self.heliObject:SetDestPosition(self.fly_target_pos) if (self.flyby_states_for_one_pass <= 0) then self.change_combat_type_allowed = true else self.change_combat_type_allowed = false end elseif (self.state == flyby_state_to_attack_dist and self.fly_target_pos and distance_2d( self.object:position(), self.fly_target_pos ) <= 30) then self.state = flyby_state_to_enemy self.fly_target_pos = vector():set(self.enemy_last_seen_pos) self.fly_target_pos.y = self.safe_altitude self.heliObject:SetDestPosition(self.fly_target_pos) if not (see_enemy) then self.change_combat_type_allowed = true end end end self.change_combat_type_allowed = self.change_combat_type_allowed or distance_2d( self.object:position(), self.enemy_last_seen_pos ) < self.search_attack_dist end ---------------------------------------------------------------------------------------------- -- Фунциии для улетания за пределы уровня ---------------------------------------------------------------------------------------------- function heli_combat:retreat_initialize() self.retreat_initialized = true self.heliObject:SetMaxVelocity( self.max_velocity ) self.heliObject:SetSpeedInDestPoint( self.max_velocity ) self.heliObject:LookAtPoint( vector():set(0,0,0), false ) self.heliObject:SetDestPosition( self:calc_position_in_radius( 5000 ) ) end function heli_combat:retreat_update() --alun_utils.debug_write("heli_combat:retreat_update") if not self.retreat_initialized then self:retreat_initialize() end end -- //////////////////////////////////////////////////////////////////////////////////////////////// -- -- Callback Functions -- -- ------------------------------------------------------------------------------------------------ -- -- Description: -- - Scripts to run when a game is loaded -- -- Usage: -- (called on game load) -- -- Parameters: -- none -- -- Ini requirements: -- drx\drx_cotz_helicopter.ltx -- [safe_zones] (type: string, smart terrain names) -- - List of smart terrains where helicopters will not engage npcs -- -- Return value (type: nil): -- none -- -- ------------------------------------------------------------------------------------------------ -- Created by DoctorX -- for DoctorX Call of The Zone 1.0 -- November 09, 2019 -- ------------------------------------------------------------------------------------------------ -- Scripts to run when the game is loaded: local function drx_cotz_heli_on_game_load_callback( ) -- Fill safe zone smarts array: local ini = ini_file( "drx\\drx_cotz_helicopter.ltx" ) if ( ini ) then drx_cotz_heli_safe_zones = alun_utils.collect_section( ini, "safe_zones" ) end -- Set return value: return end -- Register callback scripts: function on_game_start( ) RegisterScriptCallback( "on_game_load", drx_cotz_heli_on_game_load_callback ) end -- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\