268 lines
7.6 KiB
GDScript
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()
|