extends Node3D @export var grapple_distance: float = 10 @export var grapple_speed: float = 10 @export var fire_distance: float = 100 @export var fire_rate: float = 0.1 @export var ui_control: CanvasLayer enum State { NO_GRAPPLE, FREE, GRAPPLING } var current_state: State = State.FREE var grapple_target: Vector3 = Vector3.ZERO var bullet_inst = preload("res://prefabs/bullet.tscn") var ammo: int = 100 var fire_timer: float = fire_rate var interactable: Node3D = null # Called when the node enters the scene tree for the first time. func _ready() -> void: assert(ui_control != null) func do_grapple() -> void: if Input.is_action_just_pressed("attack") and current_state == State.FREE: var space_state = get_world_3d().direct_space_state var starting_pos = global_position var ending_pos = starting_pos + grapple_distance * -global_transform.basis.z var query = PhysicsRayQueryParameters3D.create(starting_pos, ending_pos) query.collision_mask = 0x2 var result = space_state.intersect_ray(query) if result: grapple_target = result.position + global_transform.basis.z current_state = State.GRAPPLING if Input.is_action_just_released("attack") and current_state == State.GRAPPLING: current_state = State.FREE grapple_target = Vector3.ZERO #print("State ", current_state, " ", grapple_target) if current_state == State.GRAPPLING: var dir_to_target = (grapple_target - global_position).normalized() get_parent().local_velocity = dir_to_target * grapple_speed func fire_weapon(delta: float) -> void: if Input.is_action_pressed("attack") and ammo > 0 and fire_timer >= fire_rate: var space_state = get_world_3d().direct_space_state var starting_pos = global_position var ending_pos = starting_pos + fire_distance * -global_transform.basis.z var query = PhysicsRayQueryParameters3D.create(starting_pos, ending_pos) var result = space_state.intersect_ray(query) var end = ending_pos if result: if result.collider.has_method("on_hit"): result.collider.on_hit(result.position) end = result.position var bullet: Node3D = bullet_inst.instantiate() bullet.position = starting_pos bullet.target = end get_tree().get_root().add_child(bullet) var up = Vector3.UP if abs(global_transform.basis.z.y) > 0.99: up = Vector3(0, 0, 1) bullet.look_at(end, up) ammo -= 1 fire_timer = 0 else: fire_timer += delta func do_interact() -> void: if interactable != null and not ui_control.have_toast(): ui_control.set_toast("Press E to interact", 0) elif interactable == null and not ui_control.have_toast(): ui_control.set_toast("", 0) if Input.is_action_just_pressed("interact") and interactable != null: if interactable.has_method("get_contents"): var contents = interactable.get_contents() if contents > 0: ammo += contents ui_control.set_toast("Got " + str(contents) + " ammo!", 1) else: ui_control.set_toast("Empty container", 1) func _physics_process(delta: float) -> void: do_grapple() do_interact() fire_weapon(delta) # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(_delta: float) -> void: pass func _on_interactable_enter(body: Node3D) -> void: interactable = body func _on_interactable_exit(body: Node3D) -> void: if interactable == body: interactable = null