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

190 lines
4.7 KiB
GDScript

extends CharacterBody3D
signal attacked_player(damage: float)
@export var max_health: float = 50.0
@export var damage: float = 15.0
@export var detection_radius: float = 12.0
@export var move_speed: float = 4.0
@export var chase_speed: float = 7.0
enum State { PATROL, CHASE, ATTACK }
var health: float
var _state: State = State.PATROL
var _time: float = 0.0
var _patrol_points: Array[Vector3] = []
var _patrol_index: int = 0
var _attack_cooldown: float = 0.0
var _player: CharacterBody3D = null
# Visual
var _body: MeshInstance3D
var _head: MeshInstance3D
var _tail: MeshInstance3D
var _fin: MeshInstance3D
func _ready() -> void:
health = max_health
_build_visuals()
_find_player()
_generate_patrol_points()
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:
# Top dark grey material
var mat_dark := StandardMaterial3D.new()
mat_dark.albedo_color = Color(0.165, 0.208, 0.251) # #2a3540
mat_dark.roughness = 0.8
# Bottom dirty white material
var mat_white := StandardMaterial3D.new()
mat_white.albedo_color = Color(0.816, 0.835, 0.847) # #d0d5d8
mat_white.roughness = 0.8
# Body (central capsule)
_body = MeshInstance3D.new()
var body_mesh := CapsuleMesh.new()
body_mesh.radius = 0.4
body_mesh.height = 1.6
_body.mesh = body_mesh
_body.material_override = mat_dark
_body.rotation.z = PI * 0.5
add_child(_body)
# Head (smaller capsule in front)
_head = MeshInstance3D.new()
var head_mesh := CapsuleMesh.new()
head_mesh.radius = 0.28
head_mesh.height = 0.8
_head.mesh = head_mesh
_head.material_override = mat_dark
_head.rotation.z = PI * 0.5
_head.position = Vector3(0.95, 0.0, 0.0)
add_child(_head)
# Tail (smaller capsule behind)
_tail = MeshInstance3D.new()
var tail_mesh := CapsuleMesh.new()
tail_mesh.radius = 0.2
tail_mesh.height = 0.7
_tail.mesh = tail_mesh
_tail.material_override = mat_dark
_tail.rotation.z = PI * 0.5
_tail.position = Vector3(-0.95, 0.0, 0.0)
add_child(_tail)
# Dorsal fin (prism)
_fin = MeshInstance3D.new()
var prism := PrismMesh.new()
prism.size = Vector3(0.5, 0.5, 0.15)
_fin.mesh = prism
_fin.material_override = mat_dark
_fin.position = Vector3(0.1, 0.4, 0.0)
add_child(_fin)
# Bottom lighter area (belly)
var belly := MeshInstance3D.new()
var belly_mesh := CapsuleMesh.new()
belly_mesh.radius = 0.38
belly_mesh.height = 1.4
belly.mesh = belly_mesh
belly.material_override = mat_white
belly.rotation.z = PI * 0.5
belly.position = Vector3(0.0, -0.05, 0.0)
belly.scale = Vector3(1.0, 0.3, 1.0)
add_child(belly)
# Collision
var col := CollisionShape3D.new()
var cshape := CapsuleShape3D.new()
cshape.radius = 0.4
cshape.height = 2.4
col.shape = cshape
col.rotation.z = PI * 0.5
add_child(col)
func _generate_patrol_points() -> void:
_patrol_points.clear()
for _i: int in range(3):
_patrol_points.append(global_position + Vector3(
randf_range(-15.0, 15.0),
randf_range(-3.0, 3.0),
randf_range(-15.0, 15.0)
))
func _physics_process(delta: float) -> void:
_time += delta
if _player == null:
_find_player()
_attack_cooldown = max(0.0, _attack_cooldown - delta)
var player_dist: float = INF
if is_instance_valid(_player):
player_dist = global_position.distance_to(_player.global_position)
match _state:
State.PATROL:
_do_patrol(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 * chase_speed
if player_dist < 2.0:
_state = State.ATTACK
if player_dist > detection_radius * 2.0:
_state = State.PATROL
_generate_patrol_points()
_patrol_index = 0
State.ATTACK:
if is_instance_valid(_player) and _attack_cooldown <= 0.0:
attacked_player.emit(damage)
_attack_cooldown = 2.0
if player_dist > 3.0:
_state = State.CHASE
# Smooth rotation toward velocity
if velocity.length_squared() > 0.1:
var target_basis: Basis = Basis.looking_at(velocity.normalized(), Vector3.UP)
transform.basis = transform.basis.slerp(target_basis, delta * 4.0)
# Tail wag animation
if is_instance_valid(_tail):
_tail.rotation.y = sin(_time * 5.0) * 0.4
move_and_slide()
func _do_patrol(delta: float) -> void:
if _patrol_points.is_empty():
_generate_patrol_points()
return
var target: Vector3 = _patrol_points[_patrol_index]
var dir: Vector3 = target - global_position
if dir.length() < 1.0:
_patrol_index = (_patrol_index + 1) % _patrol_points.size()
else:
velocity = dir.normalized() * move_speed
func take_damage(dmg: float) -> void:
health -= dmg
if health <= 0.0:
queue_free()