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

271 lines
11 KiB
GDScript

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): 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] Оружие полностью разблокировано.")