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

327 lines
14 KiB
GDScript
Raw Normal View History

2026-07-21 00:27:17 +07:00
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):
2026-07-21 00:27:17 +07:00
#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
2026-07-21 00:27:17 +07:00
cW = weaponList[nextWeapon]
# Проверяем наличие модели оружия перед включением видимости
if cW and cW.weaponSlot and "model" in cW.weaponSlot:
cWModel = cW.weaponSlot.model
if cWModel:
cWModel.visible = true
2026-07-21 00:27:17 +07:00
# Передаем обновленное оружие в менеджеры, если они существуют
if shootManager: shootManager.getCurrentWeapon(cW)
if reloadManager: reloadManager.getCurrentWeapon(cW)
if animManager: animManager.getCurrentWeapon(cW, cWModel)
2026-07-21 00:27:17 +07:00
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
2026-07-21 00:27:17 +07:00
canUseWeapon = true
canChangeWeapons = true
2026-07-21 00:27:17 +07:00
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()
2026-07-21 00:27:17 +07:00
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])
2026-07-21 00:27:17 +07:00
func displayStats():
if hud == null or cW == null:
return
2026-07-21 00:27:17 +07:00
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)
2026-07-21 00:27:17 +07:00
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] Оружие полностью разблокировано.")