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()