first commit

This commit is contained in:
2026-07-20 22:48:58 +07:00
commit e80aea750e
599 changed files with 2375815 additions and 0 deletions
@@ -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
+46
View File
@@ -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)")
+1
View File
@@ -0,0 +1 @@
uid://bh4dles0v0tn8