Files
CybelovZOVod/addons/Weapons/Scripts/WeaponManagerScript.gd
T

351 lines
15 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
# НАЙДИ И ПОЛНОСТЬСТЬЮ ЗАМЕНИ ЭТУ ФУНКЦИЮ В WeaponManagerScript.gd:
# Сначала загружаем файл искр в самом верху скрипта (к остальным preload, если есть, или прямо перед функцией):
const SPARKS_SCENE = preload("res://addons/Weapons/bullet_sparks.tscn.tscn") # Укажи правильный путь к файлу из Шага 1!
func displayBulletHole(colliderPoint : Vector3, colliderNormal : Vector3):
# 1. Оригинальный код автора для спавна декали дырки от пули
if bulletDecal != null:
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)
# 2. НАШЕ НОВОЕ ДОБАВЛЕНИЕ: СПАВН ПАРТИКЛОВ ИСКР
if SPARKS_SCENE != null:
# Было: var sparks_instance = SPARKS_SCENE.instAPE()
# НАДО (СТРОГО ВОТ ТАК):
var sparks_instance = SPARKS_SCENE.instantiate()
get_tree().get_root().add_child(sparks_instance)
# Слегка смещаем искры назад от стены (на 0.05 метра вдоль нормали),
# чтобы они гарантированно не рендерились внутри текстуры стены!
sparks_instance.global_position = colliderPoint + (colliderNormal * 0.05)
if colliderNormal.length() > 0.01:
sparks_instance.look_at(sparks_instance.global_position + colliderNormal, Vector3.UP)
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] Оружие полностью разблокировано.")