first commit

This commit is contained in:
2026-07-20 22:48:58 +07:00
commit e80aea750e
599 changed files with 2375815 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
[gd_scene format=3 uid="uid://dnxeupvhs25c5"]
[ext_resource type="Script" uid="uid://de0b6l5ylu4oy" path="res://addons/NPC/script/blood_particles.gd" id="1_wa0q6"]
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_7mumk"]
lifetime_randomness = 0.34
direction = Vector3(0, 1, 0)
spread = 180.0
initial_velocity_min = 2.0
initial_velocity_max = 4.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0c2lx"]
albedo_color = Color(0.7527466, 0, 0.041491196, 1)
[sub_resource type="BoxMesh" id="BoxMesh_yn2k5"]
material = SubResource("StandardMaterial3D_0c2lx")
size = Vector3(0.055, 0.05, 0)
[node name="BloodParticles" type="GPUParticles3D" unique_id=181301576]
emitting = false
amount = 15
lifetime = 0.4
one_shot = true
explosiveness = 1.0
process_material = SubResource("ParticleProcessMaterial_7mumk")
draw_pass_1 = SubResource("BoxMesh_yn2k5")
script = ExtResource("1_wa0q6")
+34
View File
@@ -0,0 +1,34 @@
[gd_scene format=3 uid="uid://dr6aa08wnojoo"]
[ext_resource type="Script" uid="uid://bgnhdyrrg72kx" path="res://addons/NPC/script/enemy_mob.gd" id="1_uwf7c"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_se2kl"]
[sub_resource type="BoxShape3D" id="BoxShape3D_diulm"]
size = Vector3(2.6951447, 1.3225784, 5.542081)
[sub_resource type="CapsuleMesh" id="CapsuleMesh_sngx8"]
[node name="em" type="StaticBody3D" unique_id=2094693982]
collision_layer = 64
collision_mask = 121
script = ExtResource("1_uwf7c")
[node name="Hitbox" type="CollisionShape3D" parent="." unique_id=1852865682]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.017934263, 0.009854734, 0.019842744)
shape = SubResource("CapsuleShape3D_se2kl")
debug_color = Color(0.52798665, 0.57221377, 5.7756904e-07, 0.41960785)
[node name="DetectArea" type="Area3D" parent="." unique_id=798798411]
transform = Transform3D(1.15835, 0, 0, 0, 1.5085664, 0, 0, 0, 0.5336906, 0.4316032, 0.013125002, -0.39214337)
collision_layer = 64
collision_mask = 2
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectArea" unique_id=1961649541]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5433707, 0.09678304, 0.42794847)
shape = SubResource("BoxShape3D_diulm")
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1417626392]
mesh = SubResource("CapsuleMesh_sngx8")
[connection signal="area_entered" from="DetectArea" to="." method="_on_detect_area_area_entered"]
File diff suppressed because one or more lines are too long
+34
View File
@@ -0,0 +1,34 @@
[gd_scene format=3 uid="uid://vo2yhy5ym5ug"]
[ext_resource type="Script" uid="uid://bgnhdyrrg72kx" path="res://addons/NPC/script/enemy_mob.gd" id="1_g34hb"]
[ext_resource type="Script" uid="uid://drhhet1lt3f5a" path="res://addons/hp/health_component.gd" id="2_pr0ef"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_jkuv2"]
[sub_resource type="BoxShape3D" id="BoxShape3D_g34hb"]
size = Vector3(9.06456, 2.5653687, 8.218338)
[sub_resource type="CapsuleMesh" id="CapsuleMesh_pr0ef"]
[node name="CharacterBody3D" type="CharacterBody3D" unique_id=1156384226]
collision_layer = 64
collision_mask = 121
script = ExtResource("1_g34hb")
[node name="Hitbox" type="CollisionShape3D" parent="." unique_id=1374343369]
shape = SubResource("CapsuleShape3D_jkuv2")
[node name="DetectArea" type="Area3D" parent="." unique_id=356424442]
collision_layer = 64
collision_mask = 121
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectArea" unique_id=2072637957]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.16861972, 0.64931965, -0.60822296)
shape = SubResource("BoxShape3D_g34hb")
debug_color = Color(0.5763886, 0.84150624, 0.017774109, 0.41960785)
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=297370616]
mesh = SubResource("CapsuleMesh_pr0ef")
[node name="HealthComponent" type="Node3D" parent="." unique_id=1263552151]
script = ExtResource("2_pr0ef")
+52
View File
@@ -0,0 +1,52 @@
[gd_scene format=3 uid="uid://cdhaqyi31qpjw"]
[ext_resource type="PackedScene" uid="uid://mlweykxjyekq" path="res://addons/custmode/en.glb" id="1_yy4rf"]
[ext_resource type="Texture2D" uid="uid://dyx44n2j1qyuj" path="res://addons/custmode/en_0.png" id="2_f1rwy"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6soc7"]
albedo_texture = ExtResource("2_f1rwy")
metallic_specular = 0.42
[sub_resource type="Skin" id="Skin_0dhb3"]
bind_count = 8
bind/0/name = &""
bind/0/bone = -1
bind/0/pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
bind/1/name = &""
bind/1/bone = -1
bind/1/pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
bind/2/name = &""
bind/2/bone = -1
bind/2/pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
bind/3/name = &""
bind/3/bone = -1
bind/3/pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
bind/4/name = &""
bind/4/bone = -1
bind/4/pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
bind/5/name = &""
bind/5/bone = -1
bind/5/pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
bind/6/name = &""
bind/6/bone = -1
bind/6/pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
bind/7/name = &""
bind/7/bone = -1
bind/7/pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
[node name="en" unique_id=1144982072 instance=ExtResource("1_yy4rf")]
[node name="temp_mesh_ply" parent="world" parent_id_path=PackedInt32Array(1653574111) index="0" unique_id=1619009496]
material_override = SubResource("StandardMaterial3D_6soc7")
skin = SubResource("Skin_0dhb3")
[node name="FABRIK3D" type="FABRIK3D" parent="." index="1" unique_id=326317683]
setting_count = 1
settings/0/target_node = NodePath("../world/temp_mesh_ply")
settings/0/root_bone_name = ""
settings/0/root_bone = -1
settings/0/end_bone_name = ""
settings/0/end_bone = -1
settings/0/joint_count = 0
[node name="Skeleton3D" type="Skeleton3D" parent="FABRIK3D" index="0" unique_id=1597347973]
+34
View File
@@ -0,0 +1,34 @@
[gd_scene format=3 uid="uid://dw1m2l3al7kxk"]
[ext_resource type="Script" uid="uid://bv488f4v7ly6a" path="res://addons/NPC/script/dialog_npct.gd" id="1_se2kl"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_se2kl"]
[sub_resource type="BoxShape3D" id="BoxShape3D_diulm"]
size = Vector3(1.6360468, 1.3225784, 2.9930296)
[sub_resource type="CapsuleMesh" id="CapsuleMesh_sngx8"]
[node name="npc" type="StaticBody3D" unique_id=2094693982]
collision_layer = 64
collision_mask = 121
script = ExtResource("1_se2kl")
[node name="Hitbox" type="CollisionShape3D" parent="." unique_id=1852865682]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.017934263, 0.009854734, 0.019842744)
shape = SubResource("CapsuleShape3D_se2kl")
debug_color = Color(0.52798665, 0.57221377, 5.7756904e-07, 0.41960785)
[node name="DetectArea" type="Area3D" parent="." unique_id=798798411]
transform = Transform3D(1.15835, 0, 0, 0, 1.5085664, 0, 0, 0, 0.5336906, 0.4316032, 0.013125002, -0.39214337)
collision_layer = 64
collision_mask = 2
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectArea" unique_id=1961649541]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.2729469, 0.09678304, 0.4440172)
shape = SubResource("BoxShape3D_diulm")
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1417626392]
mesh = SubResource("CapsuleMesh_sngx8")
[connection signal="area_entered" from="DetectArea" to="." method="_on_detect_area_area_entered"]
+9
View File
@@ -0,0 +1,9 @@
extends GPUParticles3D
func _ready() -> void:
# Принудительно запускаем взрыв частиц
emitting = true
# Ждем время, равное жизни частиц (Lifetime), плюс небольшой запас
await get_tree().create_timer(lifetime + 0.1).timeout
# Удаляем узел из игры, чтобы не лагало
queue_free()
+1
View File
@@ -0,0 +1 @@
uid://de0b6l5ylu4oy
+65
View File
@@ -0,0 +1,65 @@
extends StaticBody3D
@export var dialogue_data: Array[Dictionary] = [
{"name": "Сталкер", "text": "Привет! Наконец-то ты подошел."},
{"name": "Игрок", "text": "Привет. Что за проблемы со скриптами?"},
{"name": "Сталкер", "text": "Да вот, теперь и подсказки работают как надо!"}
]
var is_player_inside: bool = false
var ui_node: Node = null
var detect_area: Area3D = null
func _ready() -> void:
detect_area = $DetectArea
if detect_area:
print("[NPC] Зона DetectArea найдена. Мониторинг подсказок включен.")
func _process(_delta: float) -> void:
if detect_area == null:
return
# Опрашиваем зону на наличие игрока
var overlapping_bodies = detect_area.get_overlapping_bodies()
var found_player_this_frame = false
for body in overlapping_bodies:
var weapon_manager = body.find_child("WeaponManager", true, false)
if weapon_manager == null and body.get_parent():
weapon_manager = body.get_parent().find_child("WeaponManager", true, false)
if weapon_manager != null or "Player" in body.name or "Character" in body.name:
found_player_this_frame = true
break
# Игрок ТОЛЬКО ЧТО ЗАШЕЛ в зону NPC
if found_player_this_frame and not is_player_inside:
is_player_inside = true
# Ищем узел интерфейса на сцене
ui_node = get_tree().current_scene.find_child("DialogueUI", true, false)
if ui_node == null:
ui_node = get_tree().current_scene.find_child("dialogue_ui", true, false)
# ВКЛЮЧАЕМ ПОДСКАЗКУ «Нажмите E»
if ui_node and ui_node.has_method("show_prompt"):
ui_node.show_prompt()
# Игрок ТОЛЬКО ЧТО ВЫШЕЛ из зоны NPC
elif not found_player_this_frame and is_player_inside:
is_player_inside = false
# ПРЯЧЕМ ПОДСКАЗКУ «Нажмите E»
if ui_node and ui_node.has_method("hide_prompt"):
ui_node.hide_prompt()
if ui_node and ui_node.has_method("end_dialogue"):
ui_node.end_dialogue()
# Проверка нажатия кнопки для старта диалога
if is_player_inside:
if Input.is_key_pressed(KEY_E):
if ui_node and ui_node.has_method("start_dialogue"):
# Прячем подсказку, так как диалог уже начался
if ui_node.has_method("hide_prompt"):
ui_node.hide_prompt()
ui_node.start_dialogue(dialogue_data)
+1
View File
@@ -0,0 +1 @@
uid://bv488f4v7ly6a
+224
View File
@@ -0,0 +1,224 @@
extends CharacterBody3D
@export var move_speed: float = 4.0 # Скорость преследования игрока
@export var damage_to_player: float = 10.0 # Базовый урон за один выстрел
@export var attack_cooldown: float = 1.0 # Интервал стрельбы ПОСЛЕ первого выстрела (в секундах)
@export var vision_range: float = 25.0 # Радиус чутья моба (в метрах)
# --- НАСТРОЙКА ЗАДЕРЖКИ ПЕРЕД ВЫСТРЕЛОМ ---
@export var lock_on_time: float = 2.5 # Время на прицеливание перед выстрелом (в секундах)
var aiming_timer: float = 0.0 # Текущее накопленное время прицеливания
# --- НАСТРОЙКА УНИКАЛЬНОЙ МУЗЫКИ ДЛЯ МОБА ---
@export var custom_battle_music : AudioStream
# --- 🖼 ВЫБОР КАРТИНКИ ВСПЫШКИ В ИНСПЕКТОРЕ ---
@export var muzzle_flash_texture : Texture2D
# Переменные здоровья (работают напрямую, как у мишени)
@export var health: float = 100.0
var isDisabled: bool = false
var player_node: Node = null
var is_player_in_range: bool = false
var is_currently_seeing_player: bool = false
# Динамический узел вспышки, который код создаст сам
var dynamic_flash_sprite : Sprite3D
# Таймер перезарядки оружия
var attack_timer: float = 0.0
# Сила гравитации
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
func _ready() -> void:
if not is_in_group("Enemies"):
add_to_group("Enemies")
print("[ВРАГ] Тактический моб со зрением через укрытия запущен. Здоровье: ", health)
# АВТО-СОЗДАНИЕ И НАСТРОЙКА КАРТИНКИ ВСПЫШКИ
if muzzle_flash_texture != null:
dynamic_flash_sprite = Sprite3D.new()
dynamic_flash_sprite.texture = muzzle_flash_texture
dynamic_flash_sprite.visible = false
dynamic_flash_sprite.billboard = BaseMaterial3D.BILLBOARD_ENABLED # Поворот на игрока
# Пытаемся прикрепить к кончику ствола оружия, если есть контейнер, иначе к центру моба
if has_node("WeaponManager/WeaponContainer"):
get_node("WeaponManager/WeaponContainer").add_child(dynamic_flash_sprite)
# Сдвигаем чуть вперед по оси Z, чтобы картинка не тонула в дуле
dynamic_flash_sprite.position = Vector3(0, 0, -0.4)
else:
add_child(dynamic_flash_sprite)
dynamic_flash_sprite.position = Vector3(0, 1.2, -0.5)
# ИНИЦИАЛИЗАЦИЯ ОРУЖИЯ ДЛЯ АНИМАЦИЙ ПРИ СТАРТЕ
if has_node("WeaponManager/AnimationManager") and has_node("WeaponManager/WeaponContainer"):
var anim_mgr = get_node("WeaponManager/AnimationManager")
var weapon_model = get_node("WeaponManager/WeaponContainer")
var mock_weapon = {
"tiltRotAmount": 0.05,
"tiltRotSpeed": 5.0,
"bobFreq": 0.005,
"bobAmount": 0.05,
"bobSpeed": 5.0,
"onIdleBobFreqDivider": 2.0,
"bobPos": Vector3.ZERO
}
anim_mgr.getCurrentWeapon(mock_weapon, weapon_model)
# --- СИСТЕМА ПОПАДАНИЙ КАК У МИШЕНИ ---
func hitscanHit(damageVal: float, _hitscanDir: Vector3, _hitscanPos: Vector3) -> void:
if isDisabled: return
health -= damageVal
print("[ВРАГ] Хитскан попал в моба! Здоровье моба: ", health)
if health <= 0.0: die()
func projectileHit(damageVal: float, _hitscanDir: Vector3) -> void:
if isDisabled: return
health -= damageVal
print("[ВРАГ] Прожектайл попал в моба! Здоровье моба: ", health)
if health <= 0.0: die()
func die() -> void:
isDisabled = true
print("[ВРАГ] Моб уничтожен!")
if custom_battle_music and is_currently_seeing_player and has_node("/root/MusicManager"):
get_node("/root/MusicManager").unregister_enemy_vision(self, custom_battle_music)
queue_free()
# --- ФИЗИКА, ПРИЦЕЛИВАНИЕ И СТРЕЛЬБА ---
func _physics_process(delta: float) -> void:
if isDisabled: return
if not is_on_floor():
velocity.y -= gravity * delta
else:
velocity.y = 0
if attack_timer > 0:
attack_timer -= delta
if player_node == null:
player_node = get_tree().current_scene.find_child("PlayerCharacter", true, false)
if player_node != null:
var distance = global_position.distance_to(player_node.global_position)
var is_player_alive = true
var p_health = player_node.find_child("HealthComponent", true, false)
if p_health and "current_health" in p_health:
is_player_alive = p_health.current_health > 0
elif "health" in player_node:
is_player_alive = player_node.health > 0
if distance <= vision_range and is_player_alive:
is_player_in_range = true
else:
is_player_in_range = false
else:
is_player_in_range = false
if is_player_in_range and player_node != null:
var distance = global_position.distance_to(player_node.global_position)
var target_pos = player_node.global_position
target_pos.y = global_position.y
if global_position.distance_to(target_pos) > 0.1:
look_at(target_pos, Vector3.UP)
if distance > 2.5:
var direction = (player_node.global_position - global_position).normalized()
velocity.x = direction.x * move_speed
velocity.z = direction.z * move_speed
else:
velocity.x = 0
velocity.z = 0
var sees_now = has_clear_line_of_sight()
if custom_battle_music:
if sees_now != is_currently_seeing_player:
is_currently_seeing_player = sees_now
if has_node("/root/MusicManager"):
if is_currently_seeing_player:
get_node("/root/MusicManager").register_enemy_vision(self, custom_battle_music)
else:
get_node("/root/MusicManager").unregister_enemy_vision(self, custom_battle_music)
if sees_now:
if attack_timer <= 0:
aiming_timer += delta
if aiming_timer >= lock_on_time:
enemy_shoot()
aiming_timer = 0.0
else:
aiming_timer = 0.0
else:
velocity.x = 0
velocity.z = 0
aiming_timer = 0.0
if custom_battle_music and is_currently_seeing_player:
is_currently_seeing_player = false
if has_node("/root/MusicManager"):
get_node("/root/MusicManager").unregister_enemy_vision(self, custom_battle_music)
move_and_slide()
func has_clear_line_of_sight() -> bool:
if player_node == null: return false
var start_pos = global_position + Vector3(0, 1.0, 0)
var end_pos = player_node.global_position + Vector3(0, 1.0, 0)
var space_state = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.create(start_pos, end_pos)
var exclusion_list = [get_rid()]
for child in get_children():
if child is CollisionObject3D: exclusion_list.append(child.get_rid())
query.exclude = exclusion_list
query.collide_with_bodies = true
query.collide_with_areas = false
var result = space_state.intersect_ray(query)
if result.is_empty(): return true
var hit_node = result.collider
if hit_node == player_node or hit_node.get_parent() == player_node or hit_node.is_in_group("Player"):
return true
return false
func enemy_shoot() -> void:
attack_timer = attack_cooldown
if player_node == null: return
# --- 🔊 ВОСПРОИЗВЕДЕНИЕ ЗВУКА ВЫСТРЕЛА ---
var shoot_audio = get_node_or_null("%ShootSound")
if shoot_audio == null: shoot_audio = find_child("ShootSound", true, false)
if shoot_audio:
shoot_audio.stop()
shoot_audio.play()
# --- 🔥 ОТОБРАЖЕНИЕ КАРТИНКИ ВСПЫШКИ ВСПЛЫВАЮЩЕЙ НА ДОЛЮ СЕКУНДЫ ---
if is_instance_valid(dynamic_flash_sprite):
dynamic_flash_sprite.rotation.z = randf_range(0, TAU) # Каждый раз разный угол картинки
dynamic_flash_sprite.visible = true
get_tree().create_timer(0.06).timeout.connect(func():
if is_instance_valid(dynamic_flash_sprite):
dynamic_flash_sprite.visible = false
)
# --- ЗАПУСКАЕМ АНИМАЦИЮ ВЫСТРЕЛА ТЕЛА ---
if has_node("AnimationPlayer"):
var anim = get_node("AnimationPlayer")
anim.stop()
anim.play("ShootAnimPistol")
# --- НАНЕСЕНИЕ УРОНА ИГРОКУ ---
var player_health = player_node.find_child("HealthComponent", true, false)
if player_health and "take_damage" in player_health:
var reduced_damage = damage_to_player * 0.8
print("[ВРАГ] Бах! Вспышка отрисована. Нанесено урона: ", reduced_damage)
player_health.take_damage(reduced_damage)
+1
View File
@@ -0,0 +1 @@
uid://bgnhdyrrg72kx