Randoloot/scripts/game/weapon_controller.gd
2026-10-10 21:21:58 +02:00

178 lines
6.6 KiB
GDScript

extends Node
const PROJECTILE_SCENE := preload("res://scenes/game/weapon_projectile.tscn")
const IMPACT_EFFECT_SCENE := preload("res://scenes/game/impact_effect.tscn")
const WEAPON_AUDIO_SCRIPT := preload("res://scripts/game/weapon_audio.gd")
const RAY_COLLISION_MASK := 13 # World, enemies, and the weapon test target.
@onready var ship: CharacterBody2D = get_parent()
@onready var inventory: ShipInventory = ship.get_node("ShipInventory")
var _shot_timers := [0.0, 0.0]
var _laser_pulse_timers := [0.0, 0.0]
var _laser_lines: Array[Line2D] = []
var _fire_buttons_down := [false, false]
var _fire_just_pressed := [false, false]
func _ready() -> void:
_ensure_input_action(&"ship_fire_primary", MOUSE_BUTTON_LEFT)
_ensure_input_action(&"ship_fire_secondary", MOUSE_BUTTON_RIGHT)
for slot_index in 2:
var beam := Line2D.new()
beam.width = 4.0
beam.default_color = Color(0.35, 0.9, 1.0, 0.92)
beam.z_index = 20
beam.visible = false
ship.get_parent().call_deferred("add_child", beam)
_laser_lines.append(beam)
func _ensure_input_action(action_name: StringName, button: MouseButton) -> void:
if not InputMap.has_action(action_name):
InputMap.add_action(action_name, 0.5)
var event := InputEventMouseButton.new()
event.button_index = button
if not InputMap.action_has_event(action_name, event):
InputMap.action_add_event(action_name, event)
func _unhandled_input(event: InputEvent) -> void:
if not event is InputEventMouseButton:
return
var slot_index := -1
if event.button_index == MOUSE_BUTTON_LEFT:
slot_index = 0
elif event.button_index == MOUSE_BUTTON_RIGHT:
slot_index = 1
if slot_index < 0:
return
_fire_buttons_down[slot_index] = event.pressed
if event.pressed:
_fire_just_pressed[slot_index] = true
func _physics_process(delta: float) -> void:
if bool(ship.get("_is_destroyed")) or bool(ship.get("is_docked")):
for line in _laser_lines:
line.visible = false
return
var mouse_over_ui := ship.get_viewport().gui_get_hovered_control() != null
for slot_index in 2:
_shot_timers[slot_index] = maxf(0.0, _shot_timers[slot_index] - delta)
_laser_pulse_timers[slot_index] = maxf(0.0, _laser_pulse_timers[slot_index] - delta)
var action_name := "ship_fire_primary" if slot_index == 0 else "ship_fire_secondary"
var is_pressed: bool = not mouse_over_ui and (
Input.is_action_pressed(action_name) or _fire_buttons_down[slot_index]
)
var is_just_pressed: bool = not mouse_over_ui and (
Input.is_action_just_pressed(action_name) or _fire_just_pressed[slot_index]
)
var item: ItemInstance = inventory.weapon_slots[slot_index]
if not is_pressed or item == null or item.definition == null:
_laser_lines[slot_index].visible = (
item != null
and item.definition != null
and item.definition.weapon_kind == ItemDefinition.WeaponKind.LASER
and _laser_pulse_timers[slot_index] > 0.0
)
continue
var definition := item.definition
if definition.weapon_kind != ItemDefinition.WeaponKind.LASER:
_laser_pulse_timers[slot_index] = 0.0
_laser_lines[slot_index].visible = false
match definition.weapon_kind:
ItemDefinition.WeaponKind.LASER:
_laser_lines[slot_index].visible = _laser_pulse_timers[slot_index] > 0.0
_fire_laser(slot_index, item)
ItemDefinition.WeaponKind.MISSILE:
if (
is_just_pressed
and _shot_timers[slot_index] <= 0.0
and _consume_energy(definition.energy_cost)
):
_fire_projectile(item)
_shot_timers[slot_index] = definition.weapon_cooldown
_:
if _shot_timers[slot_index] <= 0.0 and _consume_energy(definition.energy_cost):
_fire_projectile(item)
_shot_timers[slot_index] = definition.fire_interval
for slot_index in 2:
_fire_just_pressed[slot_index] = false
func _fire_projectile(item: ItemInstance) -> void:
var definition := item.definition
var hit: Dictionary = ship.call("roll_weapon_hit", item)
var muzzle_position := _muzzle_position()
var direction := _aim_direction_from(muzzle_position)
var projectile := PROJECTILE_SCENE.instantiate()
projectile.call(
"configure",
muzzle_position,
direction,
definition.weapon_kind,
definition.projectile_speed,
float(hit["damage"]),
definition.weapon_range,
null,
bool(hit["critical"])
)
ship.get_parent().add_child(projectile)
WEAPON_AUDIO_SCRIPT.play(ship.get_parent(), muzzle_position, definition.weapon_kind)
func _fire_laser(slot_index: int, item: ItemInstance) -> void:
var definition := item.definition
var line := _laser_lines[slot_index]
if _shot_timers[slot_index] > 0.0 or float(ship.get("energy")) < definition.energy_cost:
return
var muzzle_position := _muzzle_position()
var direction := _aim_direction_from(muzzle_position)
var endpoint := muzzle_position + direction * definition.weapon_range
var query := PhysicsRayQueryParameters2D.create(
muzzle_position,
endpoint,
RAY_COLLISION_MASK,
[ship.get_rid()]
)
var result := ship.get_world_2d().direct_space_state.intersect_ray(query)
if not result.is_empty():
endpoint = result["position"]
line.clear_points()
line.add_point(muzzle_position)
line.add_point(endpoint)
line.visible = true
if not _consume_energy(definition.energy_cost):
line.visible = false
return
WEAPON_AUDIO_SCRIPT.play(ship.get_parent(), muzzle_position, ItemDefinition.WeaponKind.LASER)
if not result.is_empty():
var hit: Dictionary = ship.call("roll_weapon_hit", item)
_apply_damage(result.get("collider"), float(hit["damage"]), bool(hit["critical"]))
_spawn_laser_impact(endpoint)
_laser_pulse_timers[slot_index] = definition.laser_pulse_duration
_shot_timers[slot_index] = definition.fire_interval
func _aim_direction_from(origin: Vector2) -> Vector2:
var offset := ship.get_global_mouse_position() - origin
return offset.normalized() if offset.length_squared() > 0.001 else Vector2.UP.rotated(ship.rotation)
func _muzzle_position() -> Vector2:
var forward := Vector2.UP.rotated(ship.rotation)
return ship.global_position + forward * 28.0
func _consume_energy(amount: float) -> bool:
var current_energy := float(ship.get("energy"))
if current_energy < amount:
return false
ship.set("energy", current_energy - amount)
return true
func _apply_damage(target: Variant, damage: float, critical: bool = false) -> void:
if target is Node and target.has_method("take_weapon_hit"):
target.call("take_weapon_hit", damage, critical)
elif target is Node and target.has_method("take_damage"):
target.call("take_damage", damage)
func _spawn_laser_impact(at_position: Vector2) -> void:
var effect := IMPACT_EFFECT_SCENE.instantiate() as Node2D
effect.global_position = at_position
ship.get_parent().add_child(effect)
effect.call("configure", false, Color(0.38, 0.9, 1.0))