точно есть дэшм
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var ammoToRefill : Dictionary = {}
|
||||
|
||||
func _on_detect_area_area_entered(area: Area3D):
|
||||
if area.get_parent() is PlayerCharacter:
|
||||
var playChar = area.get_parent()
|
||||
var linkToAmmoRefill : Node3D = playChar.get_node("LinkComponent")
|
||||
|
||||
if linkToAmmoRefill != null:
|
||||
linkToAmmoRefill.ammoRefillLink(ammoToRefill)
|
||||
else:
|
||||
print("Player character can't refill ammunition")
|
||||
|
||||
queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bp4miwy6defwl
|
||||
@@ -0,0 +1,20 @@
|
||||
extends Node3D
|
||||
|
||||
var ammoDict : Dictionary = { #key = ammo type, value = ammo start number
|
||||
"LightAmmo" : 90,
|
||||
"MediumAmmo" : 60,
|
||||
"HeavyAmmo" : 9,
|
||||
"ShellAmmo" : 128,
|
||||
"RocketAmmo" : 3,
|
||||
"GrenadeAmmo" : 12
|
||||
}
|
||||
|
||||
var maxNbPerAmmoDict : Dictionary = { #key = ammo type, value = ammo max number
|
||||
"LightAmmo" : 360,
|
||||
"MediumAmmo" : 360,
|
||||
"HeavyAmmo" : 50,
|
||||
"ShellAmmo" : 640,
|
||||
"RocketAmmo" : 15,
|
||||
"GrenadeAmmo" : 60
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://b45mr122adhlv
|
||||
@@ -0,0 +1,58 @@
|
||||
extends Node3D
|
||||
|
||||
var cW
|
||||
var cWModel : Node3D
|
||||
|
||||
@onready var cameraHolder : Node3D = %CameraHolder
|
||||
@onready var playChar : CharacterBody3D = $"../../../../.."
|
||||
@onready var animPlayer : AnimationPlayer = %AnimationPlayer
|
||||
@onready var weaponManager : Node3D = %WeaponManager
|
||||
|
||||
func getCurrentWeapon(currWeap, currweaponManagerodel):
|
||||
#get current weapon model and resources
|
||||
cW = currWeap
|
||||
cWModel = currweaponManagerodel
|
||||
|
||||
func _process(delta: float):
|
||||
if cW != null and cWModel != null:
|
||||
weaponTilt(playChar.inputDirection, delta)
|
||||
weaponSway(cameraHolder.mouseInput, delta)
|
||||
weaponBob(playChar.velocity.length(),delta)
|
||||
|
||||
func weaponTilt(playCharInput, delta):
|
||||
#rotate weapon model on the z axis depending on the player character direction orientation (left or right)
|
||||
cWModel.rotation.z = lerp(cWModel.rotation.z, playCharInput.x * cW.tiltRotAmount, cW.tiltRotSpeed * delta)
|
||||
|
||||
func weaponSway(mouseInput, delta):
|
||||
#clamp mouse movement
|
||||
mouseInput.x = clamp(mouseInput.x, cW.minSwayVal.x, cW.maxSwayVal.x)
|
||||
mouseInput.y = clamp(mouseInput.y, cW.minSwayVal.y, cW.maxSwayVal.y)
|
||||
|
||||
#lerp weapon position based on mouse movement, relative to the initial position
|
||||
cWModel.position.x = lerp(cWModel.position.x, cW.position[0].x + (mouseInput.x * cW.swayAmountPos) * delta, cW.swaySpeedPos)
|
||||
cWModel.position.y = lerp(cWModel.position.y, cW.position[0].y - (mouseInput.y * cW.swayAmountPos) * delta, cW.swaySpeedPos)
|
||||
|
||||
#lerp weapon rotation based on mouse movement, relative to the initial rotation
|
||||
#use of rad_to_deg here, because we rotate the model based on degrees, but the saved weapon rotation is in radians
|
||||
cWModel.rotation_degrees.y = lerp(cWModel.rotation_degrees.y, rad_to_deg(cW.position[1].y) - (mouseInput.x * cW.swayAmountRot) * delta, cW.swaySpeedRot)
|
||||
cWModel.rotation_degrees.x = lerp(cWModel.rotation_degrees.x, rad_to_deg(cW.position[1].x) + (mouseInput.y * cW.swayAmountRot) * delta, cW.swaySpeedRot)
|
||||
|
||||
func weaponBob(vel : float, delta):
|
||||
var bobFreq : float = cW.bobFreq
|
||||
|
||||
#change bob frequency for weapon idle
|
||||
if vel < 4.0:
|
||||
bobFreq /= cW.onIdleBobFreqDivider
|
||||
|
||||
#smoothly move the weapon model in the form of a curve (hence the use of sin)
|
||||
cWModel.position.y = lerp(cWModel.position.y, cW.bobPos[0].y + sin(Time.get_ticks_msec() * bobFreq) * cW.bobAmount * vel / 10, cW.bobSpeed * delta)
|
||||
cWModel.position.x = lerp(cWModel.position.x, cW.bobPos[0].x + sin(Time.get_ticks_msec() * bobFreq * 0.5) * cW.bobAmount * vel / 10, cW.bobSpeed * delta)
|
||||
|
||||
func playAnimation(animName : String, animSpeed : float, hasToRestartAnim : bool):
|
||||
if cW != null and animPlayer != null:
|
||||
#restart current anim if needed (for example restart shoot animation while still playing)
|
||||
if hasToRestartAnim and animPlayer.current_animation == animName:
|
||||
animPlayer.seek(0, true)
|
||||
#play animation
|
||||
animPlayer.play("%s" % animName, -1, animSpeed)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://bc64rms0gin6g
|
||||
@@ -0,0 +1,54 @@
|
||||
extends Node3D
|
||||
|
||||
var cW
|
||||
var cWModel : Node3D
|
||||
|
||||
# Безопасные относительные пути к телу моба, родителю и плееру анимаций
|
||||
@onready var mob_body : CharacterBody3D = $"../.."
|
||||
@onready var animPlayer : AnimationPlayer = $"../.."/AnimationPlayer
|
||||
@onready var weaponManager : Node3D = $".."
|
||||
|
||||
func getCurrentWeapon(currWeap, currweaponManagerodel):
|
||||
cW = currWeap
|
||||
cWModel = currweaponManagerodel
|
||||
|
||||
func _process(delta: float):
|
||||
if cW != null and cWModel != null and is_instance_valid(mob_body) and animPlayer != null:
|
||||
# КРИТИЧЕСКАЯ ЗАЩИТА ВСПЫШКИ: Если играет выстрел, код НЕ перекрывает координаты своей математикой!
|
||||
if animPlayer.is_playing() and animPlayer.current_animation == "ShootAnimPistol":
|
||||
return
|
||||
|
||||
# Если выстрела нет, плавно покачиваем и наклоняем пистолет при движении моба
|
||||
var movement_dir = mob_body.velocity.normalized()
|
||||
weaponTilt(movement_dir, delta)
|
||||
weaponBob(mob_body.velocity.length(), delta)
|
||||
|
||||
func weaponTilt(moveDir, delta):
|
||||
if "tiltRotAmount" in cW and "tiltRotSpeed" in cW:
|
||||
cWModel.rotation.z = lerp(cWModel.rotation.z, moveDir.x * cW.tiltRotAmount, cW.tiltRotSpeed * delta)
|
||||
|
||||
func weaponBob(vel : float, delta):
|
||||
if not ("bobFreq" in cW) or not ("bobAmount" in cW) or not ("bobSpeed" in cW): return
|
||||
var bobFreq : float = cW.bobFreq
|
||||
|
||||
if vel < 1.0:
|
||||
if "onIdleBobFreqDivider" in cW: bobFreq /= cW.onIdleBobFreqDivider
|
||||
else: bobFreq /= 2.0
|
||||
|
||||
var secure_vel = clamp(vel, 0.0, 10.0)
|
||||
if secure_vel < 0.2: secure_vel = 0.5
|
||||
|
||||
var time_tick = Time.get_ticks_msec() * bobFreq
|
||||
var target_y = cW.bobPos.y + sin(time_tick) * cW.bobAmount * secure_vel / 10
|
||||
var target_x = cW.bobPos.x + sin(time_tick * 0.5) * cW.bobAmount * secure_vel / 10
|
||||
|
||||
if is_finite(target_y) and is_finite(target_x):
|
||||
cWModel.position.y = lerp(cWModel.position.y, target_y, cW.bobSpeed * delta)
|
||||
cWModel.position.x = lerp(cWModel.position.x, target_x, cW.bobSpeed * delta)
|
||||
|
||||
# Резервная функция вызова
|
||||
func playAnimation(animName : String, animSpeed : float, hasToRestartAnim : bool):
|
||||
if animPlayer != null:
|
||||
if hasToRestartAnim and animPlayer.current_animation == animName:
|
||||
animPlayer.seek(0, true)
|
||||
animPlayer.play("%s" % animName, -1, animSpeed)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dgt4q3ev7fhh5
|
||||
@@ -0,0 +1,25 @@
|
||||
extends Node3D
|
||||
|
||||
#Camera recoil variables
|
||||
var currentRotation : Vector3
|
||||
var targetRotation : Vector3
|
||||
var baseRotationSpeed : float
|
||||
var targetRotationSpeed : float
|
||||
|
||||
func _process(delta):
|
||||
handleRecoil(delta)
|
||||
|
||||
func handleRecoil(delta):
|
||||
#first phase, the camera will aim according the recoil values
|
||||
#second phase, the camera back down to her initial rotation value
|
||||
targetRotation = lerp(targetRotation, Vector3.ZERO, baseRotationSpeed * delta)
|
||||
currentRotation = lerp(currentRotation, targetRotation, targetRotationSpeed * delta)
|
||||
|
||||
rotation = currentRotation
|
||||
|
||||
func setRecoilValues(baseRotSpeed : float, targRotSpeed : int):
|
||||
baseRotationSpeed = baseRotSpeed
|
||||
targetRotationSpeed = targRotSpeed
|
||||
|
||||
func addRecoil(recoilValue):
|
||||
targetRotation += Vector3(recoilValue.x, randf_range(-recoilValue.y, recoilValue.y), randf_range(-recoilValue.z, recoilValue.z))
|
||||
@@ -0,0 +1 @@
|
||||
uid://k7djd8c1xge2
|
||||
@@ -0,0 +1,7 @@
|
||||
extends SubViewport
|
||||
|
||||
var screenSize : Vector2
|
||||
|
||||
func _ready():
|
||||
screenSize = get_window().size
|
||||
size = screenSize
|
||||
@@ -0,0 +1 @@
|
||||
uid://bt4lvwbcabtef
|
||||
@@ -0,0 +1,6 @@
|
||||
extends Camera3D
|
||||
|
||||
@onready var mainCam : Camera3D = %Camera
|
||||
|
||||
func _process(_delta: float):
|
||||
if mainCam != null: global_transform = mainCam.global_transform
|
||||
@@ -0,0 +1 @@
|
||||
uid://dvfp5mk7fbsbl
|
||||
@@ -0,0 +1,65 @@
|
||||
extends RigidBody3D
|
||||
|
||||
#properties variables
|
||||
var isExplosive : bool = false
|
||||
var direction : Vector3
|
||||
var damage : float
|
||||
var timeBeforeVanish : float
|
||||
var bodiesList : Array = []
|
||||
|
||||
#references variables
|
||||
@onready var mesh = $Mesh
|
||||
@onready var hitbox = $Hitbox
|
||||
|
||||
@export_group("Sound variables")
|
||||
@onready var audioManager : PackedScene = preload("../../Misc/Scenes/AudioManagerScene.tscn")
|
||||
@export var explosionSound : AudioStream
|
||||
|
||||
@export_group("Particles variables")
|
||||
@onready var particlesManager : PackedScene = preload("../../Misc/Scenes/ParticlesManagerScene.tscn")
|
||||
|
||||
func _process(delta):
|
||||
if timeBeforeVanish > 0.0: timeBeforeVanish -= delta
|
||||
else: hit()
|
||||
|
||||
func _on_body_entered(body):
|
||||
hit()
|
||||
applyDamage(body)
|
||||
|
||||
func hit():
|
||||
mesh.visible = false
|
||||
hitbox.set_deferred("disabled", true)
|
||||
|
||||
if isExplosive: explode()
|
||||
|
||||
func applyDamage(body):
|
||||
if body.is_in_group("Enemies") and body.has_method("projectileHit"):
|
||||
body.projectileHit(damage, direction)
|
||||
|
||||
if body.is_in_group("HitableObjects") and body.has_method("projectileHit"):
|
||||
body.projectileHit(damage, direction)
|
||||
|
||||
func explode():
|
||||
#this function is visual and audio only, it doesn't affect the gameplay
|
||||
weaponSoundManagement(explosionSound)
|
||||
|
||||
var particlesIns : ParticlesManager
|
||||
if particlesIns == null:
|
||||
particlesIns = particlesManager.instantiate()
|
||||
particlesIns.particleToEmit = "Explosion"
|
||||
particlesIns.global_transform = global_transform
|
||||
get_tree().get_root().add_child.call_deferred(particlesIns)
|
||||
else:
|
||||
print("Projectile already has emit explosion particles")
|
||||
|
||||
queue_free()
|
||||
|
||||
func weaponSoundManagement(soundName):
|
||||
if soundName != null:
|
||||
var audioIns = audioManager.instantiate()
|
||||
audioIns.global_transform = global_transform
|
||||
get_tree().get_root().add_child(audioIns)
|
||||
audioIns.bus = "Sfx"
|
||||
audioIns.volume_db = 5.0
|
||||
audioIns.stream = soundName
|
||||
audioIns.play()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bysswu6cmi0t4
|
||||
@@ -0,0 +1,109 @@
|
||||
extends Node3D
|
||||
|
||||
var reloadTime : float
|
||||
var startReloadTimer : bool = false #has to be initilated at start
|
||||
var currentPartIndex : int
|
||||
var playSoundAndAnim : bool
|
||||
var forceReloadStop : bool = false
|
||||
|
||||
var cW #current weapon
|
||||
@onready var weaponManager : Node3D = %WeaponManager #weapon manager
|
||||
|
||||
func getCurrentWeapon(currentWeapon):
|
||||
cW = currentWeapon
|
||||
|
||||
func _process(delta : float):
|
||||
if cW.isReloading and startReloadTimer and !forceReloadStop:
|
||||
reloadFollow(delta)
|
||||
elif forceReloadStop:
|
||||
cW.isReloading = false
|
||||
startReloadTimer = false
|
||||
return
|
||||
|
||||
func reload():
|
||||
reloadStart()
|
||||
|
||||
func reloadStart():
|
||||
if cW.hasToReload:
|
||||
if (!cW.isReloading and \
|
||||
#the type of ammunition the weapon is using still as reserve
|
||||
weaponManager.ammoManager.ammoDict[cW.ammoType] > cW.nbProjShotsAtSameTime and \
|
||||
#the magazine isn't full
|
||||
cW.totalAmmoInMag != cW.totalAmmoInMagRef and \
|
||||
!cW.isShooting):
|
||||
cW.isReloading = true
|
||||
|
||||
#for more than 1 part, you need to enter a multiple of total number of ammo the magazine can contain
|
||||
#for example, for a shotgun that can contain 8 shells, the number of parts to reload possible are : 1, 2, 4, 8
|
||||
#if you choose a number like 3, or 5, it will reload 3/8, or 5/8 at once, which is not possible, so be sure to enter a number of part allowing the weapon to reload ammunition units
|
||||
if (cW.totalAmmoInMagRef % cW.nbPartsNeeded) != 0:
|
||||
push_error("The number of parts set is not correct, cannot insert %d of ammunition" % (cW.nbPartsNeeded / cW.totalAmmoInMagRef))
|
||||
cW.isReloading = false
|
||||
else:
|
||||
currentPartIndex = 0
|
||||
reloadTime = cW.reloadTimePerPart
|
||||
forceReloadStop = false
|
||||
playSoundAndAnim = true
|
||||
startReloadTimer = true
|
||||
#the rest is been processed is reloadTimeProcess, then reloadFollow
|
||||
else:
|
||||
print("No need to reload")
|
||||
|
||||
func reloadFollow(delta : float):
|
||||
if playSoundAndAnim:
|
||||
playSoundAndAnim = false
|
||||
weaponManager.weaponSoundManagement(cW.reloadSound, cW.reloadSoundSpeed)
|
||||
|
||||
if cW.shootAnimName != "":
|
||||
weaponManager.animManager.playAnimation("ReloadAnim%s" % cW.weaponName, cW.reloadAnimSpeed, true)
|
||||
else:
|
||||
print("%s doesn't have a reload animation" % cW.weaponName)
|
||||
|
||||
if reloadTime > 0.0: reloadTime -= delta
|
||||
else:
|
||||
if currentPartIndex < cW.nbPartsNeeded: #-1, because if not it loop one extra time
|
||||
if cW.nbPartsNeeded == 1:
|
||||
onePartReloadCalculus()
|
||||
else:
|
||||
multiPartReloadCalculus()
|
||||
|
||||
currentPartIndex += 1
|
||||
|
||||
if currentPartIndex < cW.nbPartsNeeded:
|
||||
reloadTime = cW.reloadTimePerPart
|
||||
playSoundAndAnim = true
|
||||
else:
|
||||
print("Reload complete")
|
||||
cW.isReloading = false
|
||||
else:
|
||||
print("Reload complete")
|
||||
cW.isReloading = false
|
||||
|
||||
func onePartReloadCalculus():
|
||||
#explanation of the use of the min function here
|
||||
#case 1: if there's enough ammo to completely refill the magazine
|
||||
#case 2: if there's not enough ammo left, we refill the magazine with the remaining ammo.
|
||||
var nbnbAmmoToRefill : int = min(cW.totalAmmoInMagRef - cW.totalAmmoInMag, weaponManager.ammoManager.ammoDict[cW.ammoType])
|
||||
|
||||
if nbnbAmmoToRefill <= cW.totalAmmoInMagRef and nbnbAmmoToRefill >= cW.nbProjShotsAtSameTime:
|
||||
#refill the magazine, and subtract the number from the ammo manager
|
||||
cW.totalAmmoInMag += nbnbAmmoToRefill
|
||||
weaponManager.ammoManager.ammoDict[cW.ammoType] -= nbnbAmmoToRefill
|
||||
|
||||
func multiPartReloadCalculus():
|
||||
var nbAmmoToRefill = cW.totalAmmoInMagRef / cW.nbPartsNeeded
|
||||
if weaponManager.ammoManager.ammoDict[cW.ammoType] >= nbAmmoToRefill and \
|
||||
cW.totalAmmoInMag <= cW.totalAmmoInMagRef - nbAmmoToRefill:
|
||||
#add number of ammo to the magazine, and substract it from the ammo manager
|
||||
cW.totalAmmoInMag += nbAmmoToRefill
|
||||
weaponManager.ammoManager.ammoDict[cW.ammoType] -= nbAmmoToRefill
|
||||
else:
|
||||
print("Not enough ammunition in bag, or magazine complete")
|
||||
forceReloadStop = true
|
||||
|
||||
func autoReload():
|
||||
#auto reload the weapon if he can reload, has to reload, has auto reload enabled, has enought ammo in the ammo manager, and the magazine is empty
|
||||
if cW.autoReload and !cW.isReloading and \
|
||||
weaponManager.ammoManager.ammoDict[cW.ammoType] > 0 and \
|
||||
cW.totalAmmoInMag <= 0:
|
||||
reload()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cfbsp443am2jd
|
||||
@@ -0,0 +1,219 @@
|
||||
extends Node3D
|
||||
|
||||
var cW #current weapon
|
||||
var pointOfCollision : Vector3 = Vector3.ZERO
|
||||
var rng : RandomNumberGenerator
|
||||
|
||||
@onready var weaponManager : Node3D = %WeaponManager #weapon manager
|
||||
@onready var blood_particles_scene: PackedScene = preload("res://addons/NPC/enemy/blood_particles.tscn") # Укажи точный путь к файлу из Шага 2
|
||||
|
||||
func getCurrentWeapon(currWeap):
|
||||
#get current weapon resources
|
||||
cW = currWeap
|
||||
|
||||
func shoot():
|
||||
# БЛОКИРОВКА СТРЕЛЬБЫ ДЛЯ ДИАЛОГОВ И СМЕНЫ ОРУЖИЯ:
|
||||
# Если менеджеру оружия запрещено стрелять — принудительно выходим из функции
|
||||
var dialogue_ui = get_tree().current_scene.find_child("DialogueUI", true, false)
|
||||
if dialogue_ui:
|
||||
var dialogue_box = dialogue_ui.find_child("DialogueBox", true, false)
|
||||
# Если окно с текстом диалога СЕЙЧАС открыто и видимо на экране — выстрел ЗАПРЕЩЕН
|
||||
if dialogue_box and dialogue_box.visible:
|
||||
return # Мгновенно выходим, не давая проиграться коду ниже
|
||||
|
||||
|
||||
|
||||
|
||||
if !cW.isShooting and (
|
||||
#magazine isn't empty, and has >= ammo than the number of projectiles required for a shot
|
||||
(cW.totalAmmoInMag > 0 and cW.totalAmmoInMag >= cW.nbProjShotsAtSameTime)
|
||||
or
|
||||
#has all ammos in the magazine, and number of ammo is positive
|
||||
(cW.allAmmoInMag and weaponManager.ammoManager.ammoDict[cW.ammoType] > 0 and
|
||||
#has >= ammo than the number of projectiles required for a shot
|
||||
weaponManager.ammoManager.ammoDict[cW.ammoType] >= cW.nbProjShotsAtSameTime)
|
||||
) and !cW.isReloading:
|
||||
cW.isShooting = true
|
||||
|
||||
#number of successive shots (for example if 3, the weapon will shot 3 times in a row)
|
||||
for i in range(cW.nbProjShots):
|
||||
#same conditions has before, are checked before every shot
|
||||
if ((cW.totalAmmoInMag > 0 and cW.totalAmmoInMag >= cW.nbProjShotsAtSameTime)
|
||||
or (cW.allAmmoInMag and weaponManager.ammoManager.ammoDict[cW.ammoType] > 0) and
|
||||
weaponManager.ammoManager.ammoDict[cW.ammoType] >= cW.nbProjShotsAtSameTime):
|
||||
|
||||
weaponManager.weaponSoundManagement(cW.shootSound, cW.shootSoundSpeed)
|
||||
|
||||
if cW.shootAnimName != "":
|
||||
weaponManager.animManager.playAnimation("ShootAnim%s" % cW.weaponName, cW.shootAnimSpeed, true)
|
||||
else:
|
||||
print("%s doesn't have a shoot animation" % cW.weaponName)
|
||||
|
||||
#number projectiles shots at the same time (for example,
|
||||
#a shotgun shell is constituted of ~ 20 pellets that are spread across the target,
|
||||
#so 20 projectiles shots at the same time)
|
||||
for j in range(0, cW.nbProjShotsAtSameTime):
|
||||
if cW.allAmmoInMag: weaponManager.ammoManager.ammoDict[cW.ammoType] -= 1
|
||||
else: cW.totalAmmoInMag -= 1
|
||||
|
||||
#get the collision point
|
||||
pointOfCollision = getCameraPOV()
|
||||
|
||||
#call the fonction corresponding to the selected type
|
||||
if cW.type == cW.types.HITSCAN: hitscanShot(pointOfCollision)
|
||||
elif cW.type == cW.types.PROJECTILE: projectileShot(pointOfCollision)
|
||||
|
||||
if cW.showMuzzleFlash: weaponManager.displayMuzzleFlash()
|
||||
|
||||
weaponManager.cameraRecoilHolder.setRecoilValues(cW.baseRotSpeed, cW.targetRotSpeed)
|
||||
weaponManager.cameraRecoilHolder.addRecoil(cW.recoilVal)
|
||||
|
||||
await get_tree().create_timer(cW.timeBetweenShots).timeout
|
||||
|
||||
else:
|
||||
print("Not enought ammunitions to shoot")
|
||||
|
||||
cW.isShooting = false
|
||||
|
||||
func getCameraPOV():
|
||||
var camera : Camera3D = %Camera
|
||||
var window : Window = get_window()
|
||||
var viewport : Vector2i
|
||||
|
||||
#match viewport to window size, to ensure that the raycast goes in the right direction
|
||||
match window.content_scale_mode:
|
||||
window.CONTENT_SCALE_MODE_VIEWPORT:
|
||||
viewport = window.content_scale_size
|
||||
window.CONTENT_SCALE_MODE_CANVAS_ITEMS:
|
||||
viewport = window.content_scale_size
|
||||
window.CONTENT_SCALE_MODE_DISABLED:
|
||||
viewport = window.get_size()
|
||||
|
||||
#Start raycast in camera position, and launch it in camera direction
|
||||
var raycastStart = camera.project_ray_origin(viewport/2)
|
||||
var raycastEnd
|
||||
if cW.type == cW.types.HITSCAN: raycastEnd = raycastStart + camera.project_ray_normal(viewport/2) * cW.maxRange
|
||||
if cW.type == cW.types.PROJECTILE: raycastEnd = raycastStart + camera.project_ray_normal(viewport/2) * 280
|
||||
|
||||
#Create intersection space to contain possible collisions
|
||||
var newIntersection = PhysicsRayQueryParameters3D.create(raycastStart, raycastEnd)
|
||||
var intersection = get_world_3d().direct_space_state.intersect_ray(newIntersection)
|
||||
|
||||
#If the raycast has collide with something, return collision point transform properties
|
||||
if !intersection.is_empty():
|
||||
var collisionPoint = intersection.position
|
||||
return collisionPoint
|
||||
#Else, return the end of the raycast (so nothing, because he hasn't collide with anything)
|
||||
else:
|
||||
return raycastEnd
|
||||
|
||||
|
||||
|
||||
|
||||
func hitscanShot(pointOfCollisionHitscan : Vector3):
|
||||
rng = RandomNumberGenerator.new()
|
||||
|
||||
#set up weapon shot sprad
|
||||
var spread = Vector3(rng.randf_range(cW.minSpread, cW.maxSpread), rng.randf_range(cW.minSpread, cW.maxSpread), rng.randf_range(cW.minSpread, cW.maxSpread))
|
||||
|
||||
#calculate direction of the hitscan bullet
|
||||
var hitscanBulletDirection = (pointOfCollisionHitscan - cW.weaponSlot.attackPoint.get_global_transform().origin).normalized()
|
||||
|
||||
#create new intersection space to contain possibe collisions
|
||||
var newIntersection = PhysicsRayQueryParameters3D.create(cW.weaponSlot.attackPoint.get_global_transform().origin, pointOfCollisionHitscan + spread + hitscanBulletDirection * 2)
|
||||
newIntersection.collide_with_areas = true
|
||||
newIntersection.collide_with_bodies = true
|
||||
var hitscanBulletCollision = get_world_3d().direct_space_state.intersect_ray(newIntersection)
|
||||
|
||||
#if the raycast has collide
|
||||
if hitscanBulletCollision:
|
||||
var collider = hitscanBulletCollision.collider
|
||||
var colliderPoint = hitscanBulletCollision.position
|
||||
var colliderNormal = hitscanBulletCollision.normal
|
||||
var finalDamage : int
|
||||
|
||||
# --- НАНЕСЕНИЕ УРОНА ВРАГАМ ---
|
||||
if collider.is_in_group("Enemies"):
|
||||
finalDamage = cW.damagePerProj * cW.damageDropoff.sample(pointOfCollisionHitscan.distance_to(global_position) / cW.maxRange)
|
||||
|
||||
# Ищем HealthComponent внутри врага
|
||||
var enemy_health = collider.find_child("HealthComponent", true, false)
|
||||
if enemy_health:
|
||||
enemy_health.take_damage(finalDamage)
|
||||
|
||||
# --- СПАВН КРАСНЫХ КВАДРАТИКОВ (ИМПАКТ) ---
|
||||
# Переменная blood_particles_scene должна быть объявлена в самом верху скрипта ShootManager
|
||||
if has_node("blood_particles_scene") or (typeof(blood_particles_scene) if "blood_particles_scene" in self else null):
|
||||
var particles_instance = blood_particles_scene.instantiate()
|
||||
get_tree().get_root().add_child(particles_instance)
|
||||
particles_instance.global_position = colliderPoint # Ставим в точку попадания
|
||||
|
||||
if collider.has_method("hitscanHit"):
|
||||
collider.hitscanHit(finalDamage, hitscanBulletDirection, hitscanBulletCollision.position)
|
||||
|
||||
elif collider.is_in_group("EnemiesHead"):
|
||||
finalDamage = cW.damagePerProj * cW.headshotDamageMult * cW.damageDropoff.sample(pointOfCollisionHitscan.distance_to(global_position) / cW.maxRange)
|
||||
|
||||
var enemy_health = collider.find_child("HealthComponent", true, false)
|
||||
if enemy_health:
|
||||
enemy_health.take_damage(finalDamage)
|
||||
|
||||
# --- СПАВН КРАСНЫХ КВАДРАТИКОВ (ИМПАКТ В ГОЛОВУ) ---
|
||||
if "blood_particles_scene" in self and blood_particles_scene != null:
|
||||
var particles_instance = blood_particles_scene.instantiate()
|
||||
get_tree().get_root().add_child(particles_instance)
|
||||
particles_instance.global_position = colliderPoint
|
||||
|
||||
if collider.has_method("hitscanHit"):
|
||||
collider.hitscanHit(finalDamage, hitscanBulletDirection, hitscanBulletCollision.position)
|
||||
|
||||
elif collider.is_in_group("HitableObjects"):
|
||||
finalDamage = cW.damagePerProj * cW.damageDropoff.sample(pointOfCollisionHitscan.distance_to(global_position) / cW.maxRange)
|
||||
|
||||
var enemy_health = collider.find_child("HealthComponent", true, false)
|
||||
if enemy_health:
|
||||
enemy_health.take_damage(finalDamage / 6.0)
|
||||
|
||||
if collider.has_method("hitscanHit"):
|
||||
collider.hitscanHit(finalDamage/6.0, hitscanBulletDirection, hitscanBulletCollision.position)
|
||||
weaponManager.displayBulletHole(colliderPoint, colliderNormal)
|
||||
|
||||
else:
|
||||
weaponManager.displayBulletHole(colliderPoint, colliderNormal)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func projectileShot(pointOfCollisionProjectile : Vector3):
|
||||
rng = RandomNumberGenerator.new()
|
||||
|
||||
#set up weapon shot sprad
|
||||
var spread = Vector3(rng.randf_range(cW.minSpread, cW.maxSpread), rng.randf_range(cW.minSpread, cW.maxSpread), rng.randf_range(cW.minSpread, cW.maxSpread))
|
||||
|
||||
#Calculate direction of the projectile
|
||||
var projectileDirection = ((pointOfCollisionProjectile - cW.weaponSlot.attackPoint.get_global_transform().origin).normalized() + spread)
|
||||
|
||||
#Instantiate projectile
|
||||
var projInstance = cW.projRef.instantiate()
|
||||
|
||||
#set projectile properties
|
||||
projInstance.global_transform = cW.weaponSlot.attackPoint.global_transform
|
||||
projInstance.direction = projectileDirection
|
||||
projInstance.damage = cW.damagePerProj
|
||||
projInstance.timeBeforeVanish = cW.projTimeBeforeVanish
|
||||
projInstance.gravity_scale = cW.projGravityVal
|
||||
projInstance.isExplosive = cW.isProjExplosive
|
||||
|
||||
get_tree().get_root().add_child(projInstance)
|
||||
|
||||
projInstance.set_linear_velocity(projectileDirection * cW.projMoveSpeed)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://pufyqjrkl22t
|
||||
@@ -0,0 +1,216 @@
|
||||
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 : int):
|
||||
#this function manage the second part of the weapon switching mechanic
|
||||
#in this part, the next weapon is enabled (equiped and set up)
|
||||
cW = weaponList[nextWeapon]
|
||||
nextWeapon = 0
|
||||
cWModel = cW.weaponSlot.model
|
||||
cWModel.visible = true
|
||||
|
||||
shootManager.getCurrentWeapon(cW)
|
||||
reloadManager.getCurrentWeapon(cW)
|
||||
animManager.getCurrentWeapon(cW, cWModel)
|
||||
|
||||
weaponSoundManagement(cW.equipSound, cW.equipSoundSpeed)
|
||||
|
||||
animPlayer.playback_default_blend_time = cW.animBlendTime
|
||||
|
||||
if cW.equipAnimName != "":
|
||||
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():
|
||||
hud.displayWeaponStack(weaponStack.size())
|
||||
hud.displayWeaponName(cW.weaponName)
|
||||
hud.displayTotalAmmoInMag(cW.totalAmmoInMag, cW.nbProjShotsAtSameTime)
|
||||
hud.displayTotalAmmo(ammoManager.ammoDict[cW.ammoType], 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] Оружие полностью разблокировано.")
|
||||
@@ -0,0 +1 @@
|
||||
uid://d2jpda3mq5jpa
|
||||
@@ -0,0 +1,106 @@
|
||||
extends Resource
|
||||
|
||||
class_name WeaponResource
|
||||
|
||||
@export_group("General variables")
|
||||
@export var weaponName : String
|
||||
@export var weaponId : int
|
||||
var weaponSlot : WeaponSlot
|
||||
|
||||
@export_group("Type variables")
|
||||
enum types
|
||||
{
|
||||
NULL, HITSCAN, PROJECTILE
|
||||
}
|
||||
@export var type = types.NULL
|
||||
|
||||
@export_group("Animation variables")
|
||||
@export var animBlendTime : float
|
||||
@export var equipAnimName : String
|
||||
@export var equipAnimSpeed : float = 1.0
|
||||
@export var unequipAnimName : String
|
||||
@export var unequipAnimSpeed : float = 1.0
|
||||
@export var shootAnimName : String
|
||||
@export var shootAnimSpeed : float = 1.0
|
||||
@export var reloadAnimName : String
|
||||
@export var reloadAnimSpeed : float = 1.0
|
||||
|
||||
@export_group("Sound variables")
|
||||
@export var equipSound : AudioStream
|
||||
@export var equipSoundSpeed : float = 1.0
|
||||
@export var unequipSound : AudioStream
|
||||
@export var unequipSoundSpeed : float = 1.0
|
||||
@export var shootSound : AudioStream
|
||||
@export var shootSoundSpeed : float = 1.0
|
||||
@export var reloadSound : AudioStream
|
||||
@export var reloadSoundSpeed : float = 1.0
|
||||
|
||||
@export_group("Ammunition variables")
|
||||
@export var totalAmmoInMag : int
|
||||
@export var totalAmmoInMagRef : int
|
||||
@export var ammoType : String
|
||||
@export var allAmmoInMag : bool = false
|
||||
|
||||
@export_group("Equip variables")
|
||||
@export var equipTime : float
|
||||
|
||||
@export_group("Unequip variables")
|
||||
@export var unequipTime : float
|
||||
|
||||
@export_group("Shoot variables")
|
||||
var isShooting : bool = false
|
||||
@export var canAutoShoot : bool
|
||||
@export var nbProjShotsAtSameTime : int
|
||||
@export var nbProjShots : int
|
||||
@export var minSpread : float
|
||||
@export var maxSpread : float
|
||||
@export var maxRange : float
|
||||
@export var damagePerProj : float
|
||||
@export var damageDropoff : Curve
|
||||
@export_range(0.0, 15.0, 0.01) var headshotDamageMult : float = 1.0
|
||||
@export var timeBetweenShots : float
|
||||
|
||||
@export_group("Reload variables")
|
||||
var isReloading : bool = false
|
||||
@export var hasToReload : bool = true
|
||||
@export var autoReload : bool = true
|
||||
@export var nbPartsNeeded : int = 1
|
||||
@export var reloadTimePerPart : float
|
||||
|
||||
@export_group("Recoil variables")
|
||||
@export var baseRotSpeed : float = 0.0
|
||||
@export var targetRotSpeed : float = 0.0
|
||||
@export var recoilVal : Vector3 = Vector3.ZERO
|
||||
|
||||
@export_group("Projectile variables")
|
||||
@export var isProjExplosive : bool = false
|
||||
@export var projRef : PackedScene
|
||||
@export var projMoveSpeed : float
|
||||
@export var projTimeBeforeVanish : float
|
||||
@export var projGravityVal : float
|
||||
|
||||
@export_group("Position variables")
|
||||
@export var position : Array[Vector3] = [Vector3.ZERO, Vector3.ZERO]
|
||||
|
||||
@export_group("Tilt variables")
|
||||
@export_range(0.0, 20.0, 0.01) var tiltRotSpeed : float = 0.0
|
||||
@export_range(0.0, 1.0, 0.01) var tiltRotAmount : float = 0.0
|
||||
|
||||
@export_group("Sway variables")
|
||||
@export var minSwayVal : Vector2 = Vector2.ZERO
|
||||
@export var maxSwayVal : Vector2 = Vector2.ZERO
|
||||
@export_range(0, 0.2, 0.01) var swaySpeedPos: float = 0.0
|
||||
@export_range(0, 0.2, 0.01) var swaySpeedRot : float = 0.0
|
||||
@export_range(0, 0.5, 0.01) var swayAmountPos : float = 0.0
|
||||
@export_range(0, 100.0, 0.1) var swayAmountRot : float = 0.0
|
||||
|
||||
@export_group("Bob variables")
|
||||
var bobPos : Array[Vector3]
|
||||
@export_range(0.0, 0.1, 0.001) var bobFreq : float = 0.0
|
||||
@export_range(0.0, 0.1, 0.001) var bobAmount : float = 0.0
|
||||
@export_range(0.0, 50.0, 1.0) var bobSpeed : float = 0.0
|
||||
@export var onIdleBobFreqDivider : float = 0.0
|
||||
|
||||
@export_group("Muzzle flash variables")
|
||||
@export var muzzleFlashRef : PackedScene
|
||||
@export var showMuzzleFlash : bool
|
||||
@@ -0,0 +1 @@
|
||||
uid://bnhfyt5sl8jcd
|
||||
@@ -0,0 +1,8 @@
|
||||
extends Node
|
||||
|
||||
class_name WeaponSlot
|
||||
|
||||
@export var model : Node3D
|
||||
@export var weaponId : int
|
||||
@export var attackPoint : Marker3D
|
||||
@export var muzzleFlashSpawner : Marker3D
|
||||
@@ -0,0 +1 @@
|
||||
uid://bcka80usfl4am
|
||||
@@ -0,0 +1,46 @@
|
||||
extends StaticBody3D
|
||||
|
||||
# ID оружия из WeaponResource (например: 0 - пистолет, 1 - автомат)
|
||||
@export var weaponIdToGive : int = 0
|
||||
|
||||
func _on_detect_area_area_entered(area: Area3D):
|
||||
# Проверяем, что в зону вошел именно игрок
|
||||
if area.get_parent() is PlayerCharacter:
|
||||
var playChar = area.get_parent()
|
||||
|
||||
# 1. Сначала проверяем, есть ли вообще у игрока LinkComponent (как в вашем ящике)
|
||||
var linkComponent : Node3D = playChar.get_node_or_null("LinkComponent")
|
||||
|
||||
if linkComponent != null:
|
||||
# 2. Ищем WeaponManager напрямую внутри игрока (playChar) на любой глубине
|
||||
var weapon_manager = playChar.find_child("WeaponManager", true, false)
|
||||
|
||||
# Если по имени "WeaponManager" не нашло, попробуем найти скрипт, где есть weaponStack
|
||||
if weapon_manager == null:
|
||||
for child in playChar.get_children():
|
||||
if "weaponStack" in child:
|
||||
weapon_manager = child
|
||||
break
|
||||
|
||||
# 3. Если менеджер успешно найден, выполняем подбор
|
||||
if weapon_manager and "weaponStack" in weapon_manager:
|
||||
# Проверяем, нет ли этого оружия уже в инвентаре
|
||||
if not weapon_manager.weaponStack.has(weaponIdToGive):
|
||||
|
||||
# Добавляем ID оружия в стек игрока
|
||||
weapon_manager.weaponStack.append(weaponIdToGive)
|
||||
|
||||
# Обновляем текущий индекс оружия на только что подобранное
|
||||
weapon_manager.weaponIndex = weapon_manager.weaponStack.size() - 1
|
||||
|
||||
# Вызываем оригинальную функцию смены оружия
|
||||
weapon_manager.changeWeapon(weaponIdToGive)
|
||||
|
||||
# Удаляем объект оружия с земли
|
||||
queue_free()
|
||||
else:
|
||||
print("У игрока уже есть это оружие в weaponStack")
|
||||
else:
|
||||
print("КРИТИЧЕСКАЯ ОШИБКА: Узел WeaponManager вообще не найден внутри сцены игрока!")
|
||||
else:
|
||||
print("Player character can't pickup weapons (LinkComponent missing)")
|
||||
@@ -0,0 +1 @@
|
||||
uid://bh4dles0v0tn8
|
||||
Reference in New Issue
Block a user