MIF_E31230535/soldier.gd

268 lines
7.6 KiB
GDScript

extends CharacterBody2D
# 🖼️ TEMPAT MEMASUKKAN GAMBAR KEPALA MATI (Akan muncul di Inspector setelah skrip di-save)
@export var tekstur_kepala_mati: Texture2D
@export var damage_diterima: int = 30
# --- VARIABEL NODE ---
@onready var animasi_player: AnimationPlayer = $AnimasiIdle
@onready var sprite_senjata: Sprite2D = $Visual/senjata
@onready var visual = $Visual
@onready var tangan_kanan = $Visual/tangankanan
@onready var tangan_kiri = $Visual/tangankiri
@onready var sprite_kepala: Sprite2D = $Visual/kepala # 🧠 BARU: Referensi node kepala agar tidak error lagi!
# 🔌 AMBIL REFERENSI MUZZLE (Anak dari sprite senjata)
@onready var muzzle = $Visual/senjata/muzzle
# 👁️ AMBIL REFERENSI AREA SENSOR
@onready var area_mata = $Radar
@onready var area_punggung = $Punggung
# 🎵 REFERENSI NODE AUDIO STREAM PLAYER
@onready var sfx_tembak_musuh = $AudioStreamPlayer
@onready var sfx_stealth_kill = $AudioStreamPlayer2
# --- VARIABEL PENGATURAN ---
@export var hp = 100
@export var chase_speed = 150.0
# 📦 LOAD SCENE PELURUMUSUH
@export var peluru_scene: PackedScene = preload("res://pelururiflemusuh.tscn")
# 🎯 DIATUR LEWAT INSPECTOR EDITOR (Centang = Kanan, Kosong = Kiri)
@export var facing_right: bool = true
var player_in_stealth_range = false
var is_dead = false
var sudah_benar_benar_mati: bool = false # 🔒 Kunci mutlak anti-double trigger
var gravitasi = 800.0
# --- VARIABEL AI & TEMBAKAN ---
var player_target = null
var is_chasing = false
var sedang_menembak: bool = false # Mencegah timer bertabrakan saat burst fire
var baru_ketemu: bool = true # 🧭 KUNCI: Penanda awal deteksi untuk jeda tembak pertama
func _ready():
print("--- DIAGNOSTIK: Prajurit Stasioner Muncul ---")
sinkronkan_arah_objek()
func _physics_process(delta):
# 🛡️ PENGAMAN UTAMA: Jika sudah mati, hentikan seluruh logika update termasuk deteksi tombol interaksi!
if is_dead or sudah_benar_benar_mati: return
# 1. Gravitasi
if not is_on_floor():
velocity.y += gravitasi * delta
else:
velocity.y = 0
# 2. Logika AI
if is_chasing and player_target:
if player_target.is_hiding:
stop_chasing()
else:
logic_mengejar()
mulai_siklus_tembakan()
else:
if abs(velocity.x) < 10.0:
velocity.x = 0
if velocity.x != 0:
atur_arah_wajah(sign(velocity.x))
move_and_slide()
# 3. Manajemen Animasi
perbarui_animasi()
# 4. Pengecekan Stealth Kill (Hanya berjalan jika belum dalam proses mati)
if Input.is_action_just_pressed("interact") and player_in_stealth_range:
stealth_assassination()
func logic_mengejar():
var arah_ke_player = sign(player_target.global_position.x - global_position.x)
velocity.x = arah_ke_player * chase_speed
atur_arah_wajah(arah_ke_player)
func atur_arah_wajah(arah):
if arah == 0: return
if arah > 0 and not facing_right:
facing_right = true
sinkronkan_arah_objek()
print("DIAGNOSTIK: Musuh berbalik arah ke KANAN")
elif arah < 0 and facing_right:
facing_right = false
sinkronkan_arah_objek()
print("DIAGNOSTIK: Musuh berbalik arah ke KIRI")
# --- 🔄 FUNGSI SINKRONISASI VISUAL & SENSOR ---
func sinkronkan_arah_objek():
var nilai_skala = 1.0 if facing_right else -1.0
if visual: visual.scale.x = nilai_skala
if area_mata: area_mata.scale.x = nilai_skala
if area_punggung: area_punggung.scale.x = nilai_skala
func stop_chasing():
is_chasing = false
player_target = null
baru_ketemu = true
print("Soldier: Kehilangan jejak...")
# --- LOGIKA SIKLUS TEMBAKAN 3 PELURU TIAP 2 DETIK ---
func mulai_siklus_tembakan():
if sedang_menembak or not peluru_scene or is_dead: return
sedang_menembak = true
if baru_ketemu:
await get_tree().create_timer(1.0).timeout
baru_ketemu = false
for i in range(3):
if is_chasing and player_target and not player_target.is_hiding and not is_dead:
eksekusi_tembakan()
await get_tree().create_timer(0.15).timeout
await get_tree().create_timer(2.0).timeout
sedang_menembak = false
func eksekusi_tembakan():
if not muzzle or is_dead: return
if sfx_tembak_musuh:
sfx_tembak_musuh.play()
var p = peluru_scene.instantiate()
var arah_horizontal = 1.0 if facing_right else -1.0
var arah_tembakan = Vector2(arah_horizontal, 0)
if "damage" in p:
p.damage = 5
p.global_position = muzzle.global_position
p.direction = arah_tembakan
p.rotation = arah_tembakan.angle()
get_tree().current_scene.add_child(p)
# --- FUNGSI MANAJEMEN ANIMASI ---
func perbarui_animasi():
if is_dead: return
if sprite_senjata:
sprite_senjata.visible = is_chasing
if animasi_player == null: return
var nama_animasi: String = "walk" if abs(velocity.x) > 15.0 else "idle"
if is_chasing:
nama_animasi += "_weapon"
if animasi_player.current_animation != nama_animasi:
if animasi_player.has_animation(nama_animasi):
if nama_animasi == "idle" and visual:
for child in visual.get_children():
if child is Sprite2D:
child.position = Vector2.ZERO
if not (is_chasing and (child == tangan_kanan or child == tangan_kiri)):
child.rotation_degrees = 0
animasi_player.play(nama_animasi)
# --- FUNGSI SIGNAL AREA ---
func _on_mata_melihat_player(body: Node2D) -> void:
if is_dead: return
if body.name == "Player" and not body.is_hiding:
player_target = body
is_chasing = true
func _on_mata_kehilangan_player(body: Node2D) -> void:
if body == player_target:
stop_chasing()
func _on_punggung_didekati_player(body: Node2D) -> void:
if is_dead: return
if body.name == "Player":
player_in_stealth_range = true
player_target = body
func _on_punggung_ditinggal_player(body: Node2D) -> void:
if body.name == "Player":
player_in_stealth_range = false
if not is_chasing:
player_target = null
# --- 🥷 LOGIKA STEALTH ASSASSINATION (DARI BELAKANG) ---
func stealth_assassination():
if sudah_benar_benar_mati: return
sudah_benar_benar_mati = true
is_dead = true
hp = 0
velocity = Vector2.ZERO
set_physics_process(false)
if area_punggung:
area_punggung.set_deferred("monitoring", false)
area_punggung.set_deferred("monitorable", false)
print("💥 [STEALTH] Interaksi ditekan. Player menusuk!")
if player_target and player_target.has_method("eksekusi_stealth_kill"):
player_target.eksekusi_stealth_kill()
await get_tree().create_timer(0.3).timeout
if sfx_stealth_kill:
print("🔊 [SFX] Main sekarang tepat setelah 0.3 detik")
sfx_stealth_kill.stop()
sfx_stealth_kill.play()
_proses_animasi_dan_fadeout()
# --- 🩸 FUNGSI MENERIMA DAMAGE DARI TEMBAKAN PLAYER ---
func terima_damage(jumlah_damage: int) -> void:
if is_dead or sudah_benar_benar_mati: return
hp -= damage_diterima
print("🎯 Soldier terkena tembakan Player! Sisa HP: ", hp)
if hp <= 0:
eksekusi_kematian_normal()
# --- 💀 KEMATIAN NORMAL (TERKENA TEMBAKAN PELURU) ---
func eksekusi_kematian_normal():
if sudah_benar_benar_mati: return
sudah_benar_benar_mati = true
is_dead = true
hp = 0
velocity = Vector2.ZERO
set_physics_process(false)
if area_punggung:
area_punggung.set_deferred("monitoring", false)
area_punggung.set_deferred("monitorable", false)
print("💀 [DEATH] Musuh mati karena ditembak!")
_proses_animasi_dan_fadeout()
# --- 🌫️ SEKUENS ANIMASI ROBOH & FADEOUT ---
func _proses_animasi_dan_fadeout():
# Ganti gambar kepala menjadi kepala2 sesaat sebelum terkapar
if sprite_kepala and tekstur_kepala_mati:
sprite_kepala.texture = tekstur_kepala_mati
print("🖼️ Visual kepala diganti menjadi kepala mati/terkapar!")
if animasi_player and animasi_player.has_animation("mati"):
animasi_player.play("mati")
await animasi_player.animation_finished
if sfx_stealth_kill and sfx_stealth_kill.playing:
await sfx_stealth_kill.finished
var tween = create_tween()
tween.tween_property(self, "modulate:a", 0.0, 0.4)
await tween.finished
queue_free()