--[[------------------------------------------------------------------------------------------------ Зоны пси-излучения. Отыгрывают постпроцесс и фантомов по настройкам из custom_data --------------------------------------------------------------------------------------------------]] local state_outside = 0 -- актер снаружи local state_inside = 1 -- актер внутри local state_void = 2 -- неизвестный статус psy_antenna = false class "PsyAntenna" function PsyAntenna:__init () -- ---------------------------------------------------------------------------------------- -- settings -- ---------------------------------------------------------------------------------------- -- phantom gen -- ---------------------------------------------------------------------------------------- self.phantom_max = 3 --10 -- max phantoms self.phantom_spawn_probability = 0.5 -- spawn probability (0..1) self.phantom_spawn_radius = 30.0 -- average radius 30.0m +-15m self.phantom_spawn_height = 2.5 --3 -- average height from actor pos +-1.5m self.phantom_fov = 45 -- ширина сектора перед глазами актёра, в котором могут рождаться фантомы -- antenna self.hit_amplitude = 1.0 -- размер хита = self.hit_amplitude*self.hit_intensity -- ---------------------------------------------------------------------------------------- -- class variables initialize -- ---------------------------------------------------------------------------------------- self.eff_time = 0 self.hit_time = 0 self.phantom_time = 0 self.intensity_inertion = 0.05 -- скорость изменения интенсивности ед/секунду self.hit_intensity = 0 self.sound_intensity = 0 self.sound_intensity_base = 0 -- базовая интенсивность. к ней стремится текущая со скоростью self.intensity_inertion self.postprocess_count = 0 --'счетчик зарегистрированных постпроцессов self.postprocess = {} --'контейнер постпроцессов -- ---------------------------------------------------------------------------------------- -- sound -- ---------------------------------------------------------------------------------------- self.sound_initialized = false self.sound_obj_right, self.sound_obj_left = sound_object("anomaly\\brain_scorcher_r"), sound_object("anomaly\\brain_scorcher_l") self.sound_obj_left.volume = self.sound_intensity self.sound_obj_right.volume = self.sound_intensity self.snd_volume = level.get_snd_volume() self.mute_sound_threshold = 0 --' Предел до которого можно занижать звук уровня. 0 - можно заглушить полностью self.max_mumble_volume = 10 self.no_static = false self.no_mumble = false self.hit_type = "wound" self.hit_freq = 5000 self.global_state = state_void end function PsyAntenna:destroy () self.sound_obj_right:stop() self.sound_obj_left:stop() level.set_snd_volume (self.snd_volume) local hud = get_hud() if (hud) then hud:enable_fake_indicators(false) end end function PsyAntenna:update_psy_hit(dt) local hud = get_hud() if not (hud) then return end local custom_static = hud:GetCustomStatic("cs_psy_danger") if self.hit_intensity > 0.0001 then if custom_static == nil and not self.no_static then hud:AddCustomStatic("cs_psy_danger", true) hud:GetCustomStatic("cs_psy_danger"):wnd():TextControl():SetTextST("st_psy_danger") end else if custom_static ~= nil then hud:RemoveCustomStatic("cs_psy_danger") end end if time_global() - self.hit_time > self.hit_freq then self.hit_time = time_global() local power = self.hit_amplitude*self.hit_intensity --printf("HIT: power = %s", tostring(power)) if power > 0.0001 then local psy_hit = hit() psy_hit.power = power psy_hit.direction = vector():set( 0, 0, 0 ) psy_hit.impulse = 0 psy_hit.draftsman = db.actor local hit_value = ((power <= 1) and power) or 1 if self.hit_type == "chemical" then hud:update_fake_indicators(2, hit_value) psy_hit.type = hit.chemical_burn else hud:update_fake_indicators(3, hit_value) psy_hit.type = hit.telepatic end --[[ if self.last_health then printf("actor health [%s], hit taken [%s]", tostring(db.actor.health), tostring(self.last_health-db.actor.health)) end self.last_health = db.actor.health ]]-- db.actor:hit(psy_hit) if db.actor.health < 0.0001 and db.actor:alive() then db.actor:kill( db.actor ) end end end end function PsyAntenna:generate_phantoms() if (db.actor:is_talking()) then return end if self.phantom_idle == nil then self.phantom_idle = math.random(2000,5000) end if time_global() - self.phantom_time > self.phantom_idle then self.phantom_time = time_global() self.phantom_idle = math.random(5000,10000) if (phantom_manager:phantom_count() < self.phantom_max and (math.random(1,100)/100) < self.phantom_spawn_probability) then local radius = self.phantom_spawn_radius * ( math.random()/2.0+0.5 ) local ang = self.phantom_fov * math.random() - self.phantom_fov * 0.5 local dir = vector_rotate_y( db.actor:direction(), ang ) --printf("spawn_phantom") phantom_manager.spawn_phantom( db.actor:position():add( dir:mul(radius) ) ) end end end function PsyAntenna:update_sound() if not self.sound_initialized then self.sound_obj_left:play_at_pos (db.actor, vector():set(-1, 0, 1), 0, sound_object.s2d + sound_object.looped) self.sound_obj_right:play_at_pos (db.actor, vector():set( 1, 0, 1), 0, sound_object.s2d + sound_object.looped) self.sound_initialized = true end local vol = 1 - ( self.sound_intensity ^ 3 ) * 0.9 if vol < self.mute_sound_threshold then level.set_snd_volume( self.mute_sound_threshold ) else level.set_snd_volume( vol ) end self.sound_obj_left.volume = 1 / vol - 1 self.sound_obj_right.volume = 1 / vol - 1 end function PsyAntenna:update_postprocess(pp) if pp.intensity == 0 then self.postprocess_count = self.postprocess_count - 1 level.remove_pp_effector(pp.idx) return false end level.set_pp_effector_factor(pp.idx, pp.intensity, 0.3) return true end function PsyAntenna:update(dt) if (db.actor == nil or device().precache_frame > 1) then return end self.eff_time = self.eff_time + dt local function update_intensity(intensity_base, intensity) local di = self.intensity_inertion * dt * 0.01 local ii = intensity_base if math.abs(intensity_base - intensity) >= di then if intensity_base < intensity then ii = intensity - di else ii = intensity + di end end if ii < 0.0 then ii = 0.0 elseif ii > 1.0 then ii = 1.0 end return ii end if (self.global_state == 1) then --self:generate_phantoms() -- IMPORTANT: disabled phantoms cause they lead to game_graph crash, probably something about them spawning outside level borders? end if not self.no_mumble then self.sound_intensity = update_intensity(self.sound_intensity_base, self.sound_intensity) self:update_sound() end for k,v in pairs(self.postprocess) do v.intensity = update_intensity(v.intensity_base, v.intensity) local exist = self:update_postprocess(v) if exist == false then self.postprocess[k] = nil end end self:update_psy_hit(dt) end function PsyAntenna:save_state(m_data) --utils_data.debug_write("PsyAntenna:save_state BEFORE") m_data.PsyAntenna = {} m_data.PsyAntenna.hit_intensity = self.hit_intensity m_data.PsyAntenna.sound_intensity = self.sound_intensity m_data.PsyAntenna.sound_intensity_base = self.sound_intensity_base m_data.PsyAntenna.mute_sound_threshold = self.mute_sound_threshold m_data.PsyAntenna.no_static = self.no_static m_data.PsyAntenna.no_mumble = self.no_mumble m_data.PsyAntenna.hit_type = self.hit_type m_data.PsyAntenna.hit_freq = self.hit_freq m_data.PsyAntenna.postprocess = self.postprocess --utils_data.debug_write("PsyAntenna:save_state AFTER") end function PsyAntenna:load_state(m_data) if not (m_data.PsyAntenna) then return end --utils_data.debug_write("PsyAntenna:load_state BEFORE") self.hit_intensity = m_data.PsyAntenna.hit_intensity or 0 self.sound_intensity = m_data.PsyAntenna.sound_intensity or 0 self.sound_intensity_base = m_data.PsyAntenna.sound_intensity_base or 0 self.mute_sound_threshold = m_data.PsyAntenna.mute_sound_threshold or 0 self.no_static = m_data.PsyAntenna.no_static or false self.no_mumble = m_data.PsyAntenna.no_mumble or false self.hit_type = m_data.PsyAntenna.hit_type or "wound" self.hit_freq = m_data.PsyAntenna.hit_freq or 5000 self.postprocess = m_data.PsyAntenna.postprocess or 0 --utils_data.debug_write("PsyAntenna:load_state AFTER") end function PsyAntenna:save(p) --printf("Psy Antenna SAVE") set_save_marker(p, "save", false, "psy_antenna_obj") p:w_float(self.hit_intensity) p:w_float(self.sound_intensity) p:w_float(self.sound_intensity_base) p:w_float(self.mute_sound_threshold) p:w_bool(self.no_static) p:w_bool(self.no_mumble) p:w_stringZ(self.hit_type) p:w_u32(self.hit_freq) p:w_u8(self.postprocess_count) for k,v in pairs(self.postprocess) do p:w_stringZ(k) p:w_float(v.intensity) p:w_float(v.intensity_base) p:w_u16(v.idx) end set_save_marker(p, "save", true, "psy_antenna_obj") end function PsyAntenna:load(p) --printf("Psy Antenna LOAD") set_save_marker(p, "load", false, "psy_antenna_obj") self.hit_intensity = p:r_float() self.sound_intensity = p:r_float() self.sound_intensity_base = p:r_float() self.mute_sound_threshold = p:r_float() self.no_static = p:r_bool() self.no_mumble = p:r_bool() self.hit_type = p:r_stringZ() self.hit_freq = p:r_u32() self.postprocess_count = p:r_u8() self.postprocess = {} for i=1, self.postprocess_count do local k = p:r_stringZ() local ii = p:r_float() local ib = p:r_float() local idx = p:r_u16() self.postprocess[k] = {intensity_base = ib, intensity = ii, idx = idx} level.add_pp_effector(k, idx, true) level.set_pp_effector_factor(idx, ii) end set_save_marker(p, "load", true, "psy_antenna_obj") end ---------------------------------------------------------------------------------------------------- -- логическая схема для space restrictor ---------------------------------------------------------------------------------------------------- class "action_psy_antenna" function action_psy_antenna:__init( obj, storage ) self.object = obj self.st = storage self.state = state_void --' еще не ясно, в зоне он, или нет end function action_psy_antenna:reset_scheme( loading ) if loading then self.state = load_var( self.object, "inside" ) end if self.state == state_inside then self:zone_leave() end self.state = state_void self:switch_state(db.actor) --' end --' printf("[psy_antenna] reset_scheme %s, inside %s", tostring(loading), tostring(self.state)) end function action_psy_antenna:deactivate() if self.state == state_inside then self:zone_leave() end end function action_psy_antenna:update( delta ) local actor = db.actor if xr_logic.try_switch_to_another_section( self.object, self.st, actor ) then return end self:switch_state( actor ) end function action_psy_antenna:switch_state( actor ) if self.state ~= state_inside then if (db.actor_inside_zones[self.object:name()]) then --if self.object:inside( actor:position() ) then self:zone_enter() end elseif (self.state == state_inside) then if not (db.actor_inside_zones[self.object:name()]) then --if not self.object:inside( actor:position() ) then self:zone_leave() end end end function action_psy_antenna:zone_enter() --printf("[psy_antenna] zone_enter") self.state = state_inside psy_antenna.global_state = state_inside --включаем фейковый индикатор хита. local hud = get_hud() if (hud) then hud:enable_fake_indicators(true) end psy_antenna.sound_intensity_base = psy_antenna.sound_intensity_base + self.st.intensity psy_antenna.mute_sound_threshold = psy_antenna.mute_sound_threshold + self.st.mute_sound_threshold psy_antenna.hit_intensity = psy_antenna.hit_intensity + self.st.hit_intensity psy_antenna.phantom_spawn_probability = psy_antenna.phantom_spawn_probability + self.st.phantom_prob psy_antenna.no_static = self.st.no_static psy_antenna.no_mumble = self.st.no_mumble psy_antenna.hit_type = self.st.hit_type psy_antenna.hit_freq = self.st.hit_freq --' printf("[psy_antenna] zone_enter. hit_intensity=%s", tostring(psy_antenna.hit_intensity)) if self.st.postprocess == "nil" then return end if psy_antenna.postprocess[self.st.postprocess] == nil then psy_antenna.postprocess_count = psy_antenna.postprocess_count + 1 psy_antenna.postprocess[self.st.postprocess] = { intensity_base = 0, intensity = 0, idx = 1500+psy_antenna.postprocess_count} level.add_pp_effector(self.st.postprocess, psy_antenna.postprocess[self.st.postprocess].idx, true) level.set_pp_effector_factor(psy_antenna.postprocess[self.st.postprocess].idx, 0.01) end psy_antenna.postprocess[self.st.postprocess].intensity_base = psy_antenna.postprocess[self.st.postprocess].intensity_base + self.st.intensity end function action_psy_antenna:zone_leave() --printf("[psy_antenna] zone_leave. hit_intensity=%s", tostring(psy_antenna.hit_intensity)) self.state = state_outside psy_antenna.global_state = state_outside --выключаем фейковый индикатор хита. local hud = get_hud() if (hud) then hud:enable_fake_indicators(false) end psy_antenna.sound_intensity_base = psy_antenna.sound_intensity_base - self.st.intensity psy_antenna.mute_sound_threshold = psy_antenna.mute_sound_threshold - self.st.mute_sound_threshold psy_antenna.hit_intensity = psy_antenna.hit_intensity - self.st.hit_intensity psy_antenna.phantom_spawn_probability = psy_antenna.phantom_spawn_probability - self.st.phantom_prob --' printf("[psy_antenna] zone_leave. hit_intensity=%s, minus=%s", tostring(psy_antenna.hit_intensity), tostring(self.st.hit_intensity)) if self.st.postprocess == "nil" then return end if psy_antenna.postprocess[self.st.postprocess] ~= nil then psy_antenna.postprocess[self.st.postprocess].intensity_base = psy_antenna.postprocess[self.st.postprocess].intensity_base - self.st.intensity end end function action_psy_antenna:save() save_var( self.object, "inside", self.state ) end --------------------------------------------------------------------------------------------------------------------- function save( p ) if (USE_MARSHAL) then return end set_save_marker(p, "save", false, "sr_psy_antenna") if psy_antenna and psy_antenna.global_state ~= state_outside and not level_changing() then p:w_bool( true ) psy_antenna:save( p ) else p:w_bool( false ) end set_save_marker(p, "save", true, "sr_psy_antenna") end function load(p) if (USE_MARSHAL) then return end set_save_marker(p, "load", false, "sr_psy_antenna") local b = p:r_bool() if b then if psy_antenna then abort("sr_psy_antenna.psy_antenna already exists!") end psy_antenna = PsyAntenna() psy_antenna:load(p) end set_save_marker(p, "load", true, "sr_psy_antenna") end function add_to_binder(npc, ini, scheme, section, storage) --printf("DEBUG: add_to_binder: scheme='%s', section='%s'", scheme, section) if not psy_antenna then psy_antenna = PsyAntenna() psy_antenna.global_state = state_void end local new_action = action_psy_antenna(npc, storage) -- Зарегистрировать все actions, в которых должен быть вызван метод reset_scheme при изменении настроек схемы: xr_logic.subscribe_action_for_events(npc, storage, new_action) end function set_scheme(npc, ini, scheme, section, gulag_name) local st = xr_logic.assign_storage_and_bind(npc, ini, scheme, section) st.logic = xr_logic.cfg_get_switch_conditions(ini, section, npc) st.intensity = (ini:r_float_ex(section,"eff_intensity") or 0) * 0.01 st.postprocess = ini:r_string_ex(section,"postprocess") or "psy_antenna.ppe" st.hit_intensity = (ini:r_float_ex(section,"hit_intensity") or 0) * 0.01 st.phantom_prob = (ini:r_float_ex(section,"phantom_prob") or 0) * 0.01 st.mute_sound_threshold = ini:r_float_ex(section,"mute_sound_threshold") or 0 st.no_static = ini:r_bool_ex(section,"no_static",false) st.no_mumble = ini:r_bool_ex(section,"no_mumble",false) st.hit_type = ini:r_string_ex(section,"hit_type") or "wound" st.hit_freq = ini:r_float_ex(section,"hit_freq") or 5000 end