extends Node3D var weaponStack : Array[int] = [] #weapons current wielded by play char var weaponList : Dictionary = {} #all weapons available in the game (key = weapon name, value = wepakn resource) @export var weaponResources : Array[WeaponResource] #all weapon resources files @export var startWeapons : Array[WeaponSlot] #the weapon the player character will start with var cW = null #current weapon var cWModel = null #current weapon model var weaponIndex : int = 0 #weapon changes variables var canChangeWeapons : bool = true var canUseWeapon : bool = true @export_group("Keybind variables") @export var shoot_action : String @export var reload_action : String @export var weapon_wheel_up_action : String @export var weapon_wheel_down_action : String @onready var playChar : CharacterBody3D = $"../../../.." @onready var cameraHolder : Node3D = %CameraHolder @onready var cameraRecoilHolder : Node3D = %CameraRecoilHolder @onready var camera : Camera3D = %Camera @onready var weaponContainer : Node3D = %WeaponContainer @onready var shootManager : Node3D = %ShootManager @onready var reloadManager : Node3D = %ReloadManager @onready var ammoManager : Node3D = %AmmunitionManager @onready var animPlayer : AnimationPlayer = %AnimationPlayer @onready var animManager : Node3D = %AnimationManager @onready var audioManager : PackedScene = preload("../../Misc/Scenes/AudioManagerScene.tscn") @onready var bulletDecal : PackedScene = preload("../../Weapons/Scenes/BulletDecalScene.tscn") @onready var hud : CanvasLayer = %HUD @onready var linkComponent : Node3D = %LinkComponent func _ready(): initialize() func initialize(): for weapon in weaponResources: #create dict to refer weapons weaponList[weapon.weaponId] = weapon for weapo in weaponList.keys(): #weaponsEmplacements[weapo] = weaponIndex cW = weaponList[weapo] #set each weapon to current, to acess properties useful to set up animations slicing and select correct weapon slot for weaponSlot in weaponContainer.get_children(): if weaponSlot.weaponId == cW.weaponId: #id correspondant #if weapon is in the predetermined start weapons list for startWeapon in startWeapons: if startWeapon.weaponId == cW.weaponId: weaponStack.append(cW.weaponId) cW.weaponSlot = weaponSlot #get weapon slot script ref from weapon list (allows to get access to model, attack point, ...) cWModel = cW.weaponSlot.model cWModel.visible = false forceAttackPointTransformValues(cW.weaponSlot.attackPoint) cW.bobPos = cW.position if weaponStack.size() > 0: #enable (equip and set up) the first weapon on the weapon stack enterWeapon(weaponStack[0]) func exitWeapon(nextWeapon : int): #this function manage the first part of the weapon switching mechanic #in this part, the current weapon is disabled (unequiped and taked down) if nextWeapon != cW.weaponId: canChangeWeapons = false canUseWeapon = false if cW.isShooting: cW.isShooting = false if cW.isReloading: cW.isReloading = false if cW.unequipAnimName != "": animManager.playAnimation("UnequipAnim%s" % cW.weaponName, cW.unequipAnimSpeed, false) await get_tree().create_timer(cW.unequipTime).timeout cWModel.visible = false enterWeapon(nextWeapon) func enterWeapon(nextWeapon): #this function manage the second part of the weapon switching mechanic #in this part, the next weapon is enabled (equiped and set up) # --- СВЕРХ-ЗАЩИТА ОТ БАГОВ СЛОВАРЯ (DICTIONARY) ПРИ СМЕНЕ УРОВНЯ --- # Раз движок уверен, что weaponList — это Dictionary, проверяем наличие ключа через .has() var is_valid_weapon = false if weaponList != null and weaponList.has(nextWeapon): is_valid_weapon = true # Если оружие еще не готово или ключ '0' отсутствует в словаре на новом уровне: if not is_valid_weapon: print("[ОРУЖИЕ] Предупреждение: Ключ ", nextWeapon, " еще не инициализирован в словарe weaponList. Ждем...") # Даем ассету один кадр очухаться и наполнить свои словари пушками await get_tree().process_frame # Повторная проверка if weaponList != null and weaponList.has(nextWeapon): is_valid_weapon = true # Если всё еще пусто — аварийно выходим, чтобы спасти игру от вылета if not is_valid_weapon: print("[ОРУЖИЕ] КРИТИЧЕСКИЙ СБОЙ: Оружие полностью утеряно при генерации сцены!") canUseWeapon = true canChangeWeapons = true return cW = weaponList[nextWeapon] # Проверяем наличие модели оружия перед включением видимости if cW and cW.weaponSlot and "model" in cW.weaponSlot: cWModel = cW.weaponSlot.model if cWModel: cWModel.visible = true # Передаем обновленное оружие в менеджеры, если они существуют if shootManager: shootManager.getCurrentWeapon(cW) if reloadManager: reloadManager.getCurrentWeapon(cW) if animManager: animManager.getCurrentWeapon(cW, cWModel) if cW: weaponSoundManagement(cW.equipSound, cW.equipSoundSpeed) if animPlayer: animPlayer.playback_default_blend_time = cW.animBlendTime if cW.equipAnimName != "" and animManager: animManager.playAnimation("EquipAnim%s" % cW.weaponName, cW.equipAnimSpeed, false) await get_tree().create_timer(cW.equipTime).timeout if cW.isShooting: cW.isShooting = false if cW.isReloading: cW.isReloading = false canUseWeapon = true canChangeWeapons = true func _process(_delta : float): if cW != null and cWModel != null and canUseWeapon: weaponInputs() reloadManager.autoReload() displayStats() func weaponInputs(): if Input.is_action_pressed(shoot_action): # --- ПЕРЕХВАТ ДЛЯ БОНУСА SPEEDBOOSTER --- if cW != null and cW.weaponName == "SpeedBooster": # Проверяем, есть ли заряды и что мы его уже не используем прямо сейчас if cW.totalAmmoInMag > 0 and not cW.isShooting: cW.isShooting = true # === НАШЕ ДОБАВЛЕНИЕ: ПРИНУДИТЕЛЬНО ИГРАЕМ ЗВУК ИЗ РЕСУРСА ПРЕДМЕТА === # Берем звук, который ты прикрепил в поле shot_sound (или shootSound) if "shotSound" in cW and cW.shotSound != null: weaponSoundManagement(cW.shotSound, 1.0) elif "shootSound" in cW and cW.shootSound != null: weaponSoundManagement(cW.shootSound, 1.0) # Тратим одно использование бонуса cW.totalAmmoInMag -= 1 displayStats() # Обновляем цифры на HUD (станет 0) print("[БОНУС] Успешный клик! Активирую скорость на PlayerCharacter.") # Отправляем команду на ускорение в скрипт твоего игрока if playChar and playChar.has_method("activate_speed_boost"): playChar.activate_speed_boost() # Ждем кулдаун использования банки (1 секунда, чтобы проигралась анимация глотка/использования) await get_tree().create_timer(1.0).timeout # --- УДАЛЕНИЕ ИЗ ИНВЕНТАРЯ ПОСЛЕ ИСПОЛЬЗОВАНИЯ --- print("[БОНУС] Энергетик пуст. Удаляю его из инвентаря игрока...") # Находим, под каким номером в инвентаре (weaponStack) лежит наш SpeedBooster var booster_stack_index = weaponStack.find(cW.weaponId) if booster_stack_index != -1: # Полностью выкидываем его из списка оружия игрока weaponStack.remove_at(booster_stack_index) # Принудительно прячем 3D-модельку пустой банки в руках if cWModel: cWModel.visible = false # Переключаем инвентарь на самое первое доступное оружие (индекс 0 - автомат или пистолет) weaponIndex = 0 if weaponStack.size() > 0: changeWeapon(weaponStack[0]) else: # Если у игрока вообще больше нет оружия, сбрасываем текущее оружие в null cW = null cWModel = null canUseWeapon = false # Возвращаем флаг стрельбы в исходное состояние if cW != null: cW.isShooting = false else: # ОРИГИНАЛЬНАЯ СТРЕЛЬБА АССЕТА (Для автоматов и пистолетов) shootManager.shoot() if Input.is_action_just_pressed(reload_action): reloadManager.reload() if Input.is_action_just_pressed(weapon_wheel_up_action): if canChangeWeapons and !cW.isShooting and !cW.isReloading: weaponIndex = min(weaponIndex + 1, weaponStack.size() - 1) #from first element of weapon stack to last element changeWeapon(weaponStack[weaponIndex]) if Input.is_action_just_pressed(weapon_wheel_down_action): if canChangeWeapons and !cW.isShooting and !cW.isReloading: weaponIndex = max(weaponIndex - 1, 0) #from last element of weapon stack to first element changeWeapon(weaponStack[weaponIndex]) func displayStats(): if hud == null or cW == null: return hud.displayWeaponStack(weaponStack.size()) hud.displayWeaponName(cW.weaponName) hud.displayTotalAmmoInMag(cW.totalAmmoInMag, cW.nbProjShotsAtSameTime) # --- БРОНЕБОЙНАЯ ЗАЩИТА ОТ ВЫЛЕТА ИЗ-ЗА ПАТРОНОВ ПРИ СМЕНЕ СЦЕНЫ --- # Используем метод .get() вместо квадратных скобок [...]. # Он никогда не уронит игру, даже если словарь ammoDict полностью пустой! var total_ammo : int = 0 if ammoManager != null and ammoManager.ammoDict != null: total_ammo = ammoManager.ammoDict.get(cW.ammoType, 0) hud.displayTotalAmmo(total_ammo, cW.nbProjShotsAtSameTime) # --- БРОНЕБОЙНАЯ ЗАЩИТА ОТ ВЫЛЕТА ПРИ ПЕРЕХОДЕ НА НОВЫЙ УРОВЕНЬ --- # Проверяем, существует ли ammoManager и есть ли тип патронов текущего оружия в словаре if ammoManager != null and ammoManager.ammoDict != null and ammoManager.ammoDict.has(cW.ammoType): hud.displayTotalAmmo(ammoManager.ammoDict[cW.ammoType], cW.nbProjShotsAtSameTime) else: # Если словарь еще не загрузился, временно пишем 0, чтобы игра не вылетала hud.displayTotalAmmo(0, cW.nbProjShotsAtSameTime) func changeWeapon(nextWeapon : int): if canChangeWeapons and !cW.isShooting and !cW.isReloading: exitWeapon(nextWeapon) else: push_error("Can't change weapon now") return func displayMuzzleFlash(): #create a muzzle flash instance, and display it at the indicated point if cW.muzzleFlashRef != null: var muzzleFlashInstance = cW.muzzleFlashRef.instantiate() add_child(muzzleFlashInstance) muzzleFlashInstance.global_position = cW.weaponSlot.muzzleFlashSpawner.global_position muzzleFlashInstance.emitting = true else: push_error("%s doesn't have a muzzle flash reference" % cW.weaponName) return func displayBulletHole(colliderPoint : Vector3, colliderNormal : Vector3): #create a muzzle flash instance, and display it at the indicated point var bulletDecalInstance = bulletDecal.instantiate() get_tree().get_root().add_child(bulletDecalInstance) bulletDecalInstance.global_position = colliderPoint bulletDecalInstance.look_at(colliderPoint - colliderNormal, Vector3.UP) bulletDecalInstance.rotate_object_local(Vector3(1.0, 0.0, 0.0), 90) func weaponSoundManagement(soundName : AudioStream, soundSpeed : float): var audioIns : AudioStreamPlayer3D = audioManager.instantiate() get_tree().get_root().add_child.call_deferred(audioIns) #makes sure the node is in the scene tree await get_tree().process_frame if audioIns.is_inside_tree(): audioIns.global_transform = cW.weaponSlot.attackPoint.global_transform audioIns.bus = "Sfx" audioIns.pitch_scale = soundSpeed audioIns.stream = soundName audioIns.play() else: print("The sound can't be played, AudioStreamPlayer3D instance is not in the scene tree") func forceAttackPointTransformValues(attackPoint : Marker3D): #reset the attack points rotation values, to ensure that the projectiles will be shot in the correct direction if attackPoint.rotation != Vector3.ZERO: attackPoint.rotation = Vector3.ZERO # Добавьте этот код в самый конец вашего скрипта WeaponManager # Замените эти две функции в самом конце вашего WeaponManager func disable_weapons_for_dialogue() -> void: canUseWeapon = false canChangeWeapons = false # Принудительно сбрасываем флаги стрельбы if cW != null: cW.isShooting = false cW.isReloading = false # ВЫКЛЮЧАЕМ менеджеры стрельбы и перезарядки, чтобы они не видели мышь if shootManager: shootManager.process_mode = Node.PROCESS_MODE_DISABLED if reloadManager: reloadManager.process_mode = Node.PROCESS_MODE_DISABLED print("[WeaponManager] Стрельба и перезарядка ПОЛНОСТЬСТЬЮ заморожены.") func enable_weapons_after_dialogue() -> void: canUseWeapon = true canChangeWeapons = true # ВКЛЮЧАЕМ менеджеры обратно в стандартный режим работы if shootManager: shootManager.process_mode = Node.PROCESS_MODE_INHERIT if reloadManager: reloadManager.process_mode = Node.PROCESS_MODE_INHERIT print("[WeaponManager] Оружие полностью разблокировано.")