Files
dauphincraft/scripts/mobs/Jellyfish.gd
2026-04-19 17:16:39 +02:00

164 lines
4.1 KiB
GDScript

extends CharacterBody3D
signal attacked_player(damage: float)
@export var max_health: float = 20.0
@export var damage: float = 5.0
@export var detection_radius: float = 6.0
@export var attack_radius: float = 1.5
@export var move_speed: float = 1.0
enum State { WANDER, CHASE, ATTACK }
var health: float
var _state: State = State.WANDER
var _time: float = 0.0
var _wander_target: Vector3 = Vector3.ZERO
var _wander_timer: float = 0.0
var _attack_cooldown: float = 0.0
var _player: CharacterBody3D = null
var _base_scale: Vector3 = Vector3.ONE
# Visual nodes
var _dome: MeshInstance3D
var _tentacles: Array[MeshInstance3D] = []
func _ready() -> void:
health = max_health
_build_visuals()
_find_player()
_base_scale = scale
_pick_wander_target()
func _find_player() -> void:
var players := get_tree().get_nodes_in_group("player")
if players.size() > 0:
_player = players[0] as CharacterBody3D
func _build_visuals() -> void:
var mat := StandardMaterial3D.new()
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.albedo_color = Color(0.9, 0.6, 0.9, 0.5)
mat.emission_enabled = true
mat.emission = Color(0.5, 0.1, 0.7)
mat.emission_energy_multiplier = 0.8
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
# Dome (sphere scaled to look like a bell)
_dome = MeshInstance3D.new()
var sphere := SphereMesh.new()
sphere.radius = 0.5
sphere.height = 0.7
_dome.mesh = sphere
_dome.material_override = mat
_dome.scale = Vector3(1.0, 0.7, 1.0)
add_child(_dome)
# 4 tentacles
var tent_mat := StandardMaterial3D.new()
tent_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
tent_mat.albedo_color = Color(0.85, 0.5, 0.85, 0.35)
tent_mat.emission_enabled = true
tent_mat.emission = Color(0.4, 0.0, 0.6)
tent_mat.emission_energy_multiplier = 0.5
tent_mat.cull_mode = BaseMaterial3D.CULL_DISABLED
var offsets: Array[Vector2] = [
Vector2(0.2, 0.0), Vector2(-0.2, 0.0),
Vector2(0.0, 0.2), Vector2(0.0, -0.2)
]
for off: Vector2 in offsets:
var tent := MeshInstance3D.new()
var cap := CapsuleMesh.new()
cap.radius = 0.04
cap.height = 0.6
tent.mesh = cap
tent.material_override = tent_mat
tent.position = Vector3(off.x, -0.55, off.y)
add_child(tent)
_tentacles.append(tent)
# Collision shape
var col := CollisionShape3D.new()
var cshape := SphereShape3D.new()
cshape.radius = 0.55
col.shape = cshape
add_child(col)
func _process(delta: float) -> void:
_time += delta
# Pulse animation
var pulse: float = 1.0 + sin(_time * 3.0) * 0.08
_dome.scale.y = 0.7 * pulse
func _physics_process(delta: float) -> void:
if _player == null:
_find_player()
_attack_cooldown = max(0.0, _attack_cooldown - delta)
_wander_timer = max(0.0, _wander_timer - delta)
var player_dist: float = INF
if is_instance_valid(_player):
player_dist = global_position.distance_to(_player.global_position)
match _state:
State.WANDER:
_do_wander(delta)
if player_dist < detection_radius:
_state = State.CHASE
State.CHASE:
if is_instance_valid(_player):
var dir: Vector3 = (_player.global_position - global_position).normalized()
velocity = dir * move_speed
if player_dist < attack_radius:
_state = State.ATTACK
if player_dist > detection_radius * 1.5:
_state = State.WANDER
State.ATTACK:
if is_instance_valid(_player) and _attack_cooldown <= 0.0:
attacked_player.emit(damage)
_attack_cooldown = 1.5
if player_dist > attack_radius * 1.5:
_state = State.CHASE
# Smooth rotation toward movement
if velocity.length_squared() > 0.01:
var target_basis: Basis = Basis.looking_at(velocity.normalized(), Vector3.UP)
transform.basis = transform.basis.slerp(target_basis, delta * 3.0)
move_and_slide()
func _do_wander(delta: float) -> void:
if _wander_timer <= 0.0:
_pick_wander_target()
var dir: Vector3 = (_wander_target - global_position)
if dir.length() < 0.5:
_pick_wander_target()
else:
velocity = dir.normalized() * move_speed * 0.5
func _pick_wander_target() -> void:
_wander_target = global_position + Vector3(
randf_range(-5.0, 5.0),
randf_range(-2.0, 2.0),
randf_range(-5.0, 5.0)
)
_wander_timer = randf_range(3.0, 6.0)
func take_damage(dmg: float) -> void:
health -= dmg
if health <= 0.0:
queue_free()