323 lines
9.2 KiB
GDScript
323 lines
9.2 KiB
GDScript
@tool
|
|
class_name Player
|
|
extends CharacterBody2D
|
|
|
|
# --- VARIABEL ---
|
|
# 🎛️ SETELAN AUDIO MANUAL TEMBAKAN
|
|
var VOLUME_TEMBAKAN_MANUAL: float = -8.0
|
|
@onready var sfx_tembakan = $AudioStreamPlayer
|
|
@export var bisa_menunduk : bool = false
|
|
@export var hanya_boleh_menunduk : bool = false
|
|
@export var peluru_scene : PackedScene # Drag file pelururifle.tscn ke sini di Inspector
|
|
|
|
# --- VARIABEL UNTUK SKIN CUSTOM ---
|
|
@export var kostum_badan_custom: Texture2D
|
|
@export var kostum_tangan_kanan_custom: Texture2D
|
|
@export var kostum_tangan_kiri_custom: Texture2D
|
|
@export var kostum_kepala_custom: Texture2D
|
|
|
|
# Tombol utama di Inspector
|
|
@export var is_pegang_senjata : bool = false:
|
|
set(value):
|
|
is_pegang_senjata = value
|
|
if is_node_ready():
|
|
proses_senjata(value)
|
|
update_animation_state()
|
|
|
|
signal hp_berubah(hp_sekarang)
|
|
|
|
# --- VARIABEL POWER UP TRIPLE SHOT ---
|
|
var is_triple_shot : bool = false
|
|
var is_menunduk = false
|
|
var sedang_di_tangga = false
|
|
var health = 100
|
|
var is_dead = false
|
|
|
|
@export var SPEED : float = 600
|
|
const JUMP_VELOCITY = -600.0
|
|
const CLIMB_SPEED = 400.0
|
|
var is_hiding = false
|
|
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
|
|
|
|
@onready var visual = $Visual
|
|
@onready var anim_player = $AnimasiIdle
|
|
@onready var collision = $CollisionShape2D
|
|
@onready var tangan_kanan = $Visual/tangankanan
|
|
@onready var tangan_kiri = $Visual/tangankiri
|
|
@onready var senjata = $Visual/senjata
|
|
@onready var muzzle = $Visual/senjata/muzzle # Titik peluru keluar
|
|
|
|
# 🔪 FIX 1: AMBIL REFERENSI SPRITE PISAU (Letakkan di bawah muzzle)
|
|
@onready var sprite_pisau = $Visual/pisau
|
|
|
|
func terima_damage(jumlah):
|
|
if is_dead: return
|
|
|
|
health -= jumlah
|
|
health = clamp(health, 0, 100)
|
|
|
|
# "Terpancar" atau kirim signal setiap kali HP berubah
|
|
emit_signal("hp_berubah", health)
|
|
|
|
print("HP Player: ", health)
|
|
|
|
if health <= 0:
|
|
mati()
|
|
|
|
func mati():
|
|
if is_dead: return
|
|
is_dead = true
|
|
|
|
print("Robot Biru Hancur! Mengalihkan ke Layar Game Over...")
|
|
set_physics_process(false)
|
|
|
|
# Catat lokasi terakhir
|
|
Globaltimer.scene_terakhir_player = get_tree().current_scene.scene_file_path
|
|
call_deferred("pindah_ke_gameover")
|
|
|
|
func pindah_ke_gameover():
|
|
get_tree().change_scene_to_file("res://gameover.tscn")
|
|
|
|
func _ready():
|
|
if not Engine.is_editor_hint():
|
|
proses_senjata(is_pegang_senjata)
|
|
# 🥷 Pastikan pisau tersembunyi di awal permainan
|
|
if sprite_pisau:
|
|
sprite_pisau.visible = false
|
|
|
|
if kostum_badan_custom:
|
|
$Visual/badan.texture = kostum_badan_custom
|
|
|
|
if kostum_tangan_kanan_custom:
|
|
$Visual/tangankanan.texture = kostum_tangan_kanan_custom
|
|
|
|
if kostum_tangan_kiri_custom:
|
|
$Visual/tangankiri.texture = kostum_tangan_kiri_custom
|
|
$Visual/tangankiri.offset.x = -23
|
|
|
|
if kostum_kepala_custom:
|
|
$Visual/kepala.texture = kostum_kepala_custom
|
|
|
|
func _physics_process(delta):
|
|
if Engine.is_editor_hint(): return
|
|
|
|
if is_hiding:
|
|
velocity = Vector2.ZERO
|
|
return
|
|
|
|
# LOGIKA TOGGLE SENJATA (Tab)
|
|
if Input.is_action_just_pressed("ui_focus_next"):
|
|
is_pegang_senjata = !is_pegang_senjata
|
|
|
|
# LOGIKA BIDIK & TEMBAK (Hanya saat pegang senjata)
|
|
if is_pegang_senjata:
|
|
var crosshair = get_tree().current_scene.find_child("Crosshair", true, false)
|
|
|
|
if crosshair and crosshair.visible:
|
|
senjata.look_at(get_global_mouse_position())
|
|
senjata.flip_v = (get_global_mouse_position().x < global_position.x)
|
|
else:
|
|
senjata.rotation_degrees = 0
|
|
senjata.flip_v = false
|
|
|
|
# SINKRON: Menggunakan "click_kiri" sesuai setup Input Map kamu
|
|
if Input.is_action_just_pressed("click_kiri"):
|
|
tembak()
|
|
|
|
if hanya_boleh_menunduk:
|
|
velocity.x = 0
|
|
if bisa_menunduk and Input.is_action_just_pressed("gerak_bawah"):
|
|
proses_menunduk(!is_menunduk)
|
|
|
|
if not is_on_floor():
|
|
velocity.y += gravity * delta
|
|
move_and_slide()
|
|
update_animation_state()
|
|
return
|
|
|
|
# 1. Gravitasi & Tangga
|
|
if not is_on_floor() and not sedang_di_tangga:
|
|
velocity.y += gravity * delta
|
|
|
|
if sedang_di_tangga:
|
|
if Input.is_action_pressed("gerak_atas"): velocity.y = -CLIMB_SPEED
|
|
elif Input.is_action_pressed("gerak_bawah"): velocity.y = CLIMB_SPEED
|
|
else: velocity.y = 0
|
|
|
|
# 2. Lompat & Menunduk
|
|
if Input.is_action_just_pressed("lompat") and is_on_floor():
|
|
velocity.y = JUMP_VELOCITY
|
|
|
|
if bisa_menunduk and is_on_floor() and not sedang_di_tangga:
|
|
if Input.is_action_just_pressed("gerak_bawah"):
|
|
proses_menunduk(!is_menunduk)
|
|
|
|
# 3. Gerak Kiri-Kanan
|
|
var direction = Input.get_axis("gerak_kiri", "gerak_kanan")
|
|
if direction:
|
|
var current_speed = SPEED * 0.5 if is_menunduk else SPEED
|
|
velocity.x = direction * current_speed
|
|
visual.scale.x = direction
|
|
else:
|
|
velocity.x = move_toward(velocity.x, 0, SPEED)
|
|
|
|
move_and_slide()
|
|
update_animation_state()
|
|
|
|
func tembak():
|
|
if !peluru_scene: return
|
|
if is_menunduk:
|
|
print("❌ Player tidak bisa menembak sambil menunduk/berjongkok!")
|
|
return
|
|
|
|
if is_triple_shot:
|
|
for i in range(3):
|
|
if is_dead: break
|
|
_eksekusi_instansiasi_peluru()
|
|
await get_tree().create_timer(0.1).timeout
|
|
else:
|
|
_eksekusi_instansiasi_peluru()
|
|
|
|
func _eksekusi_instansiasi_peluru():
|
|
var p = peluru_scene.instantiate()
|
|
var arah_tembakan = Vector2.ZERO
|
|
var crosshair = get_tree().current_scene.find_child("Crosshair", true, false)
|
|
|
|
if crosshair and crosshair.visible:
|
|
arah_tembakan = (get_global_mouse_position() - muzzle.global_position).normalized()
|
|
else:
|
|
arah_tembakan = Vector2(visual.scale.x, 0)
|
|
|
|
p.global_position = muzzle.global_position
|
|
p.direction = arah_tembakan
|
|
p.rotation = arah_tembakan.angle()
|
|
|
|
get_tree().current_scene.add_child(p)
|
|
|
|
if sfx_tembakan:
|
|
sfx_tembakan.stop()
|
|
sfx_tembakan.volume_db = VOLUME_TEMBAKAN_MANUAL
|
|
sfx_tembakan.play()
|
|
|
|
func update_animation_state():
|
|
if Engine.is_editor_hint(): return
|
|
|
|
# 🚷 FIX UTAMA: Pengaman ini HARUS ditaruh paling atas sebelum pengecekan menunduk/tangga!
|
|
if anim_player and anim_player.current_animation == "tusuk":
|
|
return
|
|
|
|
if is_menunduk or sedang_di_tangga: return
|
|
|
|
var anim_suffix = "_weapon" if is_pegang_senjata else ""
|
|
|
|
if velocity.x != 0 and is_on_floor():
|
|
var anim_walk = "walk" + anim_suffix
|
|
if anim_player.has_animation(anim_walk):
|
|
anim_player.play(anim_walk)
|
|
elif is_on_floor():
|
|
var anim_idle = "idle" + anim_suffix
|
|
if anim_player.has_animation(anim_idle):
|
|
# Reset posisi visual
|
|
for child in visual.get_children():
|
|
if child is Sprite2D:
|
|
child.position = Vector2.ZERO
|
|
if not (is_pegang_senjata and (child == tangan_kanan or child == tangan_kiri)):
|
|
child.rotation_degrees = 0
|
|
|
|
anim_player.play(anim_idle)
|
|
|
|
if is_pegang_senjata:
|
|
tangan_kanan.position = Vector2(-26, 4)
|
|
tangan_kanan.rotation_degrees = -68
|
|
tangan_kiri.position = Vector2(17, 4)
|
|
tangan_kiri.rotation_degrees = -80
|
|
senjata.position = Vector2(46, 12)
|
|
else:
|
|
tangan_kanan.position = Vector2.ZERO
|
|
tangan_kanan.rotation_degrees = 0
|
|
tangan_kiri.position = Vector2.ZERO
|
|
tangan_kiri.rotation_degrees = 0
|
|
|
|
func proses_senjata(status: bool):
|
|
if !tangan_kanan or !tangan_kiri or !senjata: return
|
|
senjata.visible = status
|
|
if status:
|
|
tangan_kanan.position = Vector2(-26, 4)
|
|
tangan_kanan.rotation_degrees = -68
|
|
tangan_kiri.position = Vector2(17, 4)
|
|
tangan_kiri.rotation_degrees = -80
|
|
senjata.position = Vector2(46, 12)
|
|
senjata.rotation_degrees = 0
|
|
else:
|
|
tangan_kanan.position = Vector2.ZERO
|
|
tangan_kanan.rotation_degrees = 0
|
|
tangan_kiri.position = Vector2.ZERO
|
|
tangan_kiri.rotation_degrees = 0
|
|
senjata.position = Vector2.ZERO
|
|
senjata.rotation_degrees = 0
|
|
|
|
func proses_menunduk(status: bool):
|
|
is_menunduk = status
|
|
if status:
|
|
visual.position.y = 15
|
|
collision.scale.y = 0.5
|
|
anim_player.stop()
|
|
else:
|
|
visual.position.y = 0
|
|
collision.scale.y = 1.0
|
|
|
|
func reset_posisi_visual():
|
|
if is_pegang_senjata: return
|
|
for child in visual.get_children():
|
|
if child is Sprite2D:
|
|
child.position = Vector2.ZERO
|
|
|
|
func set_di_tangga(status: bool):
|
|
sedang_di_tangga = status
|
|
if status and is_menunduk: proses_menunduk(false)
|
|
|
|
func _on_speed_slider_value_changed(value: float): SPEED = value
|
|
|
|
func eksekusi_kematian_player():
|
|
print("Player mati!")
|
|
set_physics_process(false)
|
|
Globaltimer.scene_terakhir_player = get_tree().current_scene.scene_file_path
|
|
get_tree().change_scene_to_file("res://gameover.tscn")
|
|
|
|
func tambah_hp(jumlah):
|
|
if is_dead: return
|
|
health += jumlah
|
|
health = clamp(health, 0, 100)
|
|
emit_signal("hp_berubah", health)
|
|
print("💊 Darah berhasil dipulihkan! HP Player Sekarang: ", health)
|
|
|
|
func aktifkan_triple_shot(durasi: float = 15.0):
|
|
if is_dead: return
|
|
is_triple_shot = true
|
|
print("🔥 Power-Up Aktif: Tembakan 3 Peluru Beruntun!")
|
|
await get_tree().create_timer(durasi).timeout
|
|
is_triple_shot = false
|
|
print("🔋 Durasi Triple Shot habis. Senjata kembali ke mode normal.")
|
|
|
|
# --- 🥷 FUNGSI UTAMA STEALTH KILL PLAYER (TARUH DI PLAYER.GD) ---
|
|
func eksekusi_stealth_kill():
|
|
if is_dead or is_menunduk or sedang_di_tangga: return
|
|
|
|
set_physics_process(false)
|
|
print("🥷 Player: Memulai Stealth Kill. Mengeluarkan pisau...")
|
|
|
|
if sprite_pisau:
|
|
sprite_pisau.visible = true
|
|
|
|
if anim_player and anim_player.has_animation("tusuk"):
|
|
anim_player.play("tusuk")
|
|
await anim_player.animation_finished # Menunggu animasi tusuk kelar
|
|
else:
|
|
print("⚠️ Peringatan: Animasi bernama 'tusuk' tidak ditemukan!")
|
|
|
|
if sprite_pisau:
|
|
sprite_pisau.visible = false
|
|
|
|
set_physics_process(true)
|
|
print("🥷 Player: Stealth Kill selesai.")
|