2024-10-20 20:27:08 -06:00
|
|
|
class_name ShotSetup extends Node3D
|
|
|
|
|
2024-11-17 16:53:07 -07:00
|
|
|
signal finished(source: ShotSetup)
|
|
|
|
|
2024-11-01 19:50:02 -06:00
|
|
|
enum Phase {
|
|
|
|
AIM,
|
|
|
|
POWER_ADJUST,
|
|
|
|
CURVE_ADJUST,
|
2024-11-09 15:58:53 -07:00
|
|
|
DOWNSWING,
|
2024-11-01 19:50:02 -06:00
|
|
|
SHOT,
|
2024-12-07 17:57:58 -07:00
|
|
|
SHOT_RESET,
|
2024-11-01 19:50:02 -06:00
|
|
|
FINISHED,
|
2024-12-11 19:26:45 -07:00
|
|
|
DEAD,
|
2024-11-01 19:50:02 -06:00
|
|
|
}
|
|
|
|
|
2024-10-20 20:27:08 -06:00
|
|
|
const PITCH_MIN := deg_to_rad(-60.0)
|
|
|
|
const PITCH_MAX := deg_to_rad(-5.0)
|
|
|
|
const ZOOM_LENGTH := 0.1
|
2024-11-03 16:50:44 -07:00
|
|
|
const ZOOM_ELEVATION_RATIO := 0.3166
|
2024-10-20 20:27:08 -06:00
|
|
|
|
|
|
|
const ZOOM_MIN := 1.0
|
|
|
|
const ZOOM_MAX := 12.0
|
|
|
|
|
2024-11-01 19:50:02 -06:00
|
|
|
const ARROW_ACCELERATION := 8.0
|
2024-11-09 15:58:53 -07:00
|
|
|
const PLAYER_ACCELERATION := 2.0
|
2024-11-01 19:50:02 -06:00
|
|
|
|
2024-11-19 21:52:04 -07:00
|
|
|
const FREE_CAM_GIMBAL_TWEEN_TIME := 0.2
|
2024-11-02 09:30:45 -06:00
|
|
|
const FREE_CAM_RETURN_TIME := 0.618
|
2024-11-02 19:05:49 -06:00
|
|
|
const BALL_RETURN_TIME := 0.618
|
2024-11-03 16:50:44 -07:00
|
|
|
const CAMERA_SNAP_TIME := 0.3
|
2024-11-01 19:50:02 -06:00
|
|
|
|
2024-11-17 12:35:28 -07:00
|
|
|
const WASTED_BALL_RETURN_DELAY := 3.5
|
|
|
|
|
2024-11-17 20:10:33 -07:00
|
|
|
## Shots above this threshold trigger a "big power" effect
|
2024-11-17 18:51:06 -07:00
|
|
|
const BIG_POWER_THRESHOLD := 0.7
|
|
|
|
|
2024-11-18 12:06:43 -07:00
|
|
|
## Amount of life lost when landing in water
|
|
|
|
const WATER_DAMAGE := 10.0
|
|
|
|
|
2024-11-21 02:19:00 -07:00
|
|
|
## Angle of influence that shot curve has, in radians
|
2024-12-04 17:18:27 -07:00
|
|
|
const CURVE_INFLUENCE := PI / 16
|
|
|
|
|
2024-12-09 16:46:04 -07:00
|
|
|
## Just enough to make things interesting!
|
|
|
|
const SHOT_OFFSET_Z_FACTOR := 2.0 / 45.0
|
|
|
|
|
2024-12-04 17:18:27 -07:00
|
|
|
## Impulse offset multiplier due to curve, in meters
|
|
|
|
const CURVE_FACTOR := 0.002
|
2024-11-21 02:19:00 -07:00
|
|
|
|
|
|
|
## Maximum absolute curve for the "nice shot" animation to play
|
|
|
|
const NICE_THRESHOLD := 0.2
|
|
|
|
|
2024-11-21 18:54:38 -07:00
|
|
|
## Force by which explosions knock the ball away
|
|
|
|
const EXPLOSIVE_FORCE_FACTOR := 0.12
|
|
|
|
|
2024-11-02 19:05:49 -06:00
|
|
|
## In Driving Range mode, the ball can be retrieved in the shot phase.
|
|
|
|
@export var driving_range := false
|
|
|
|
|
2024-11-11 14:45:47 -07:00
|
|
|
## Initially-selected club
|
2024-12-04 17:02:57 -07:00
|
|
|
@export var initial_club_type: Club.Type = Club.Type.DRIVER
|
2024-11-11 14:45:47 -07:00
|
|
|
|
2024-11-20 12:27:22 -07:00
|
|
|
## Initially-selected ball type
|
2024-11-20 19:22:11 -07:00
|
|
|
@export var initial_ball: GameBall.Type = GameBall.Type.BASIC
|
2024-11-20 12:27:22 -07:00
|
|
|
|
2024-11-02 19:05:49 -06:00
|
|
|
@export_category("Shot Parameters")
|
2024-11-09 16:26:15 -07:00
|
|
|
@export var base_power := 2.5
|
2024-11-02 19:05:49 -06:00
|
|
|
@export var base_curve := 0.0
|
2024-10-20 20:27:08 -06:00
|
|
|
|
2024-11-12 16:18:57 -07:00
|
|
|
@export_category("Debug")
|
2024-11-12 16:56:01 -07:00
|
|
|
## When enabled, ignore curve meter and hit a perfect shot every time.
|
|
|
|
@export var perfect_aim := false
|
2024-11-13 09:38:47 -07:00
|
|
|
## Keep projection visible
|
|
|
|
@export var keep_projection := false
|
2024-11-12 16:18:57 -07:00
|
|
|
|
2024-11-17 16:53:07 -07:00
|
|
|
var player: WorldPlayer
|
|
|
|
|
2024-11-02 19:05:49 -06:00
|
|
|
var control_disabled := false
|
|
|
|
|
2024-12-07 17:57:58 -07:00
|
|
|
var reset_enabled := false:
|
|
|
|
set(value):
|
|
|
|
if value != reset_enabled:
|
|
|
|
if value:
|
|
|
|
hud.show_reset_prompt()
|
|
|
|
else:
|
|
|
|
hud.hide_reset_prompt()
|
|
|
|
reset_enabled = value
|
|
|
|
|
2024-11-17 16:53:07 -07:00
|
|
|
var phase: Phase = Phase.FINISHED:
|
2024-11-01 19:50:02 -06:00
|
|
|
set(value):
|
|
|
|
if value != phase:
|
|
|
|
_on_phase_change(value)
|
|
|
|
phase = value
|
|
|
|
|
2024-11-19 11:51:05 -07:00
|
|
|
var hud: ShotHUD
|
2024-11-17 16:53:07 -07:00
|
|
|
|
2024-12-04 17:02:57 -07:00
|
|
|
var club_type: Club.Type:
|
2024-12-04 19:06:18 -07:00
|
|
|
set = _set_club_type
|
2024-12-04 17:02:57 -07:00
|
|
|
|
|
|
|
var club: Club:
|
|
|
|
get:
|
|
|
|
return player.get_club(club_type) if player else null
|
2024-11-05 15:03:40 -07:00
|
|
|
|
2024-11-20 19:22:11 -07:00
|
|
|
var ball_type: GameBall.Type:
|
2024-11-20 12:27:22 -07:00
|
|
|
set(value):
|
|
|
|
if value != ball_type:
|
2024-11-20 19:22:11 -07:00
|
|
|
hud.ball_selector.value = value
|
2024-11-20 12:27:22 -07:00
|
|
|
ball_point.spawn_ball(value)
|
|
|
|
ball_type = value
|
|
|
|
|
2024-11-17 12:35:28 -07:00
|
|
|
var shot_power: float:
|
|
|
|
set(value):
|
|
|
|
hud.power_bar.value = value
|
|
|
|
get:
|
|
|
|
return hud.power_bar.value
|
|
|
|
|
|
|
|
var shot_curve: float:
|
|
|
|
set(value):
|
|
|
|
hud.curve_bar.value = value
|
|
|
|
get:
|
2024-11-21 02:19:00 -07:00
|
|
|
return hud.curve_bar.value
|
2024-11-17 12:35:28 -07:00
|
|
|
|
2024-11-20 12:27:22 -07:00
|
|
|
var game_ball: GameBall:
|
|
|
|
get:
|
|
|
|
return ball_point.ball
|
|
|
|
|
2024-11-01 19:50:02 -06:00
|
|
|
var _free_camera: FreeCamera
|
|
|
|
var _returning_free_camera := false
|
2024-11-17 16:53:07 -07:00
|
|
|
var _restart_queued := false
|
2024-10-20 20:27:08 -06:00
|
|
|
|
2024-11-11 11:39:12 -07:00
|
|
|
var _tracking_camera: OrbitalCamera
|
|
|
|
|
2024-10-20 20:27:08 -06:00
|
|
|
@onready var direction: Node3D = %Direction
|
2024-11-03 16:50:44 -07:00
|
|
|
@onready var elevation: Node3D = %Elevation
|
2024-10-20 20:27:08 -06:00
|
|
|
@onready var pitch: Node3D = %Pitch
|
2024-11-03 16:50:44 -07:00
|
|
|
@onready var zoom: Node3D = %Zoom
|
2024-10-20 20:27:08 -06:00
|
|
|
@onready var camera: Camera3D = %Camera
|
2024-11-04 15:16:32 -07:00
|
|
|
|
|
|
|
@onready var player_pivot: Node3D = %PlayerPivot
|
2024-11-09 15:58:53 -07:00
|
|
|
# TODO: genericize for selectable characters
|
|
|
|
@onready var character: CharacterController = $PlayerPivot/GfolfGirl
|
2024-11-03 16:50:44 -07:00
|
|
|
|
|
|
|
@onready var shot_animation: AnimationPlayer = %ShotAnimation
|
2024-11-22 12:26:25 -07:00
|
|
|
@onready var shot_sfx: ShotSFX = %ShotSFX
|
2024-11-02 09:49:14 -06:00
|
|
|
|
2024-12-11 19:26:45 -07:00
|
|
|
@onready var death_animation: AnimationPlayer = %DeathAnimation
|
|
|
|
|
2024-11-01 19:50:02 -06:00
|
|
|
@onready var arrow: Node3D = %Arrow
|
2024-11-12 16:18:57 -07:00
|
|
|
@onready var arrow_pivot: Node3D = %ArrowPivot
|
2024-11-02 09:49:14 -06:00
|
|
|
@onready var arrow_animation: AnimationPlayer = %ArrowAnimation
|
2024-11-12 16:18:57 -07:00
|
|
|
@onready var shot_projection: ProjectileArc = %ShotProjection
|
2024-11-01 19:50:02 -06:00
|
|
|
|
2024-11-20 12:27:22 -07:00
|
|
|
@onready var ball_point: BallPoint = %BallPoint
|
2024-11-02 19:05:49 -06:00
|
|
|
|
2024-11-05 15:03:40 -07:00
|
|
|
@onready var drive_arrow: Node3D = %DriveArrow
|
2024-11-12 16:56:01 -07:00
|
|
|
@onready var wedge_arrow: Node3D = %WedgeArrow
|
|
|
|
@onready var iron_arrow: Node3D = %IronArrow
|
2024-11-05 15:03:40 -07:00
|
|
|
@onready var putt_arrow: Node3D = %PuttArrow
|
2024-11-11 14:45:47 -07:00
|
|
|
|
2024-11-21 17:19:06 -07:00
|
|
|
@onready var downswing_timer: Timer = %DownswingTimer
|
2024-11-17 12:35:28 -07:00
|
|
|
@onready var ball_return_timer: Timer = %BallReturnTimer
|
2024-12-07 17:57:58 -07:00
|
|
|
@onready var reset_prompt_timer: Timer = %ResetPromptTimer
|
2024-11-10 16:41:47 -07:00
|
|
|
|
2024-11-21 18:23:29 -07:00
|
|
|
@onready var explosion_animation: AnimationPlayer = %ExplosionAnimation
|
2024-11-22 11:13:19 -07:00
|
|
|
@onready var player_label: Label3D = %PlayerLabel
|
2024-11-21 18:23:29 -07:00
|
|
|
|
2024-11-10 18:17:32 -07:00
|
|
|
@onready var camera_distance := zoom.position.z:
|
2024-10-20 20:27:08 -06:00
|
|
|
set = _set_camera_distance
|
|
|
|
|
2024-11-17 12:35:28 -07:00
|
|
|
@onready var world: World = get_tree().get_first_node_in_group(World.group)
|
2024-11-17 18:51:06 -07:00
|
|
|
@onready var game: Game = get_tree().get_first_node_in_group(Game.group)
|
2024-11-17 12:35:28 -07:00
|
|
|
|
2024-10-20 20:27:08 -06:00
|
|
|
@onready var _target_rotation := Vector2(pitch.rotation.x, direction.rotation.y)
|
|
|
|
|
2024-11-17 16:53:07 -07:00
|
|
|
static var scene := preload("res://src/player/shot_setup/shot_setup.tscn")
|
|
|
|
|
2024-10-20 20:27:08 -06:00
|
|
|
|
2024-11-17 12:35:28 -07:00
|
|
|
func _ready() -> void:
|
2024-12-11 19:26:45 -07:00
|
|
|
if player:
|
|
|
|
player.on_life_changed.connect(_on_life_changed)
|
|
|
|
|
|
|
|
# Create & set up HUD
|
|
|
|
hud = ShotHUD.create(player)
|
|
|
|
world.ui.add_player_hud(hud)
|
|
|
|
ball_type = initial_ball
|
|
|
|
club_type = initial_club_type
|
|
|
|
character.set_color(player.color)
|
|
|
|
|
|
|
|
# Set up player 3D label
|
|
|
|
player_label.text = player.name
|
|
|
|
player_label.modulate = player.color
|
|
|
|
player_label.outline_modulate = ColorTools.get_bg_color(player.color)
|
2024-11-22 11:13:19 -07:00
|
|
|
|
2024-11-21 17:41:55 -07:00
|
|
|
_on_phase_change(phase)
|
2024-11-17 12:35:28 -07:00
|
|
|
|
|
|
|
|
2024-12-11 19:26:45 -07:00
|
|
|
func _on_tree_exiting() -> void:
|
|
|
|
if is_instance_valid(_tracking_camera):
|
|
|
|
_tracking_camera.queue_free()
|
|
|
|
if is_instance_valid(_free_camera):
|
|
|
|
_free_camera.queue_free()
|
|
|
|
hud.queue_free()
|
|
|
|
|
|
|
|
|
2024-10-20 20:27:08 -06:00
|
|
|
func _set_camera_distance(value: float) -> void:
|
|
|
|
var tween := get_tree().create_tween()
|
2024-11-03 16:50:44 -07:00
|
|
|
tween.tween_property(zoom, "position:z", value, ZOOM_LENGTH).set_trans(Tween.TRANS_SINE)
|
|
|
|
tween.set_parallel()
|
|
|
|
(
|
|
|
|
tween
|
|
|
|
. tween_property(elevation, "position:y", value * ZOOM_ELEVATION_RATIO, ZOOM_LENGTH)
|
|
|
|
. set_trans(Tween.TRANS_SINE)
|
2024-10-20 20:27:08 -06:00
|
|
|
)
|
|
|
|
camera_distance = value
|
|
|
|
|
|
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
|
|
if event is InputEventMouseButton:
|
|
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
|
|
|
elif event is InputEventMouseMotion:
|
|
|
|
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
|
|
|
camera_motion((event as InputEventMouseMotion).relative)
|
|
|
|
|
|
|
|
|
|
|
|
func camera_motion(motion: Vector2) -> void:
|
2024-11-01 19:50:02 -06:00
|
|
|
if not control_disabled and phase == Phase.AIM:
|
2024-10-20 20:27:08 -06:00
|
|
|
# Can only control camera while aiming
|
2024-12-08 16:21:05 -07:00
|
|
|
_target_rotation.y = _target_rotation.y - deg_to_rad(motion.x * Game.settings.x_sensitivity)
|
2024-10-20 20:27:08 -06:00
|
|
|
_target_rotation.x = clampf(
|
2024-12-08 16:21:05 -07:00
|
|
|
(
|
|
|
|
_target_rotation.x
|
|
|
|
- (
|
|
|
|
deg_to_rad(motion.y * Game.settings.y_sensitivity)
|
|
|
|
* (-1 if Game.settings.invert_pitch else 1)
|
|
|
|
)
|
|
|
|
),
|
2024-10-20 20:27:08 -06:00
|
|
|
PITCH_MIN,
|
|
|
|
PITCH_MAX
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-11-17 16:53:07 -07:00
|
|
|
## Return this instance to the AIM phase the next time we process while FINISHED.
|
|
|
|
func queue_restart() -> void:
|
|
|
|
_restart_queued = true
|
|
|
|
|
|
|
|
|
|
|
|
func is_active() -> bool:
|
|
|
|
return phase != Phase.FINISHED
|
|
|
|
|
|
|
|
|
2024-11-22 12:26:25 -07:00
|
|
|
func is_shot_good() -> bool:
|
|
|
|
return abs(shot_curve) <= NICE_THRESHOLD
|
|
|
|
|
|
|
|
|
2024-11-09 15:58:53 -07:00
|
|
|
func finish_downswing() -> void:
|
|
|
|
phase = Phase.SHOT
|
|
|
|
|
|
|
|
|
2024-11-12 16:18:57 -07:00
|
|
|
func get_shot_impulse(meter_pct: float) -> Vector3:
|
2024-12-04 17:18:27 -07:00
|
|
|
# Basic direction of the shot
|
|
|
|
var shot_vec := -direction.global_basis.rotated(direction.global_basis.x, club.angle).z
|
|
|
|
# Curve rotates shot direction around the Y-axis
|
|
|
|
var curved_shot := shot_vec.rotated(Vector3.UP, shot_curve * CURVE_INFLUENCE * shot_power)
|
|
|
|
# Various factors attenuate shot power
|
|
|
|
return curved_shot * club.power * base_power * meter_pct
|
2024-11-12 16:18:57 -07:00
|
|
|
|
|
|
|
|
2024-11-01 19:50:02 -06:00
|
|
|
func take_shot() -> void:
|
2024-12-04 17:02:57 -07:00
|
|
|
# Impact screenshake
|
2024-11-17 18:51:06 -07:00
|
|
|
if game:
|
|
|
|
if shot_power > BIG_POWER_THRESHOLD:
|
|
|
|
var shake_intensity: float = (
|
|
|
|
10.0 * (shot_power - BIG_POWER_THRESHOLD) / (1.0 - BIG_POWER_THRESHOLD)
|
|
|
|
)
|
|
|
|
game.viewport.screen_shake(shake_intensity, 1.0)
|
|
|
|
|
2024-11-17 12:35:28 -07:00
|
|
|
print_debug("WHACK!\nPower: ", shot_power, "\nCurve: ", shot_curve)
|
2024-11-01 19:50:02 -06:00
|
|
|
|
2024-11-21 02:19:00 -07:00
|
|
|
# Momentary impulse applied to the ball
|
2024-11-17 12:35:28 -07:00
|
|
|
var impulse := get_shot_impulse(shot_power)
|
2024-11-12 16:56:01 -07:00
|
|
|
print_debug("Shot impulse: ", impulse, "; ", impulse.length(), " N*s")
|
2024-11-02 19:05:49 -06:00
|
|
|
|
2024-11-21 11:27:35 -07:00
|
|
|
# Curve the curve
|
2024-12-09 16:46:04 -07:00
|
|
|
var curve := shot_curve * absf(shot_curve) * CURVE_FACTOR
|
2024-11-21 02:19:00 -07:00
|
|
|
|
|
|
|
# Position where the ball is hit (imparts spin)
|
2024-12-09 16:46:04 -07:00
|
|
|
var offset := direction.global_basis.x.normalized() * -curve
|
|
|
|
offset += (
|
|
|
|
direction.global_basis.z.normalized()
|
|
|
|
* game_ball.radius
|
|
|
|
* game_ball.radius
|
|
|
|
* SHOT_OFFSET_Z_FACTOR
|
|
|
|
)
|
2024-11-21 02:19:00 -07:00
|
|
|
print_debug("Shot offset: ", offset, "; ", offset.length(), " m")
|
|
|
|
|
2024-11-20 12:27:22 -07:00
|
|
|
if game_ball:
|
2024-12-04 17:59:44 -07:00
|
|
|
game_ball.magnetic = club_type == Club.Type.IRON
|
2024-11-20 12:27:22 -07:00
|
|
|
game_ball.freeze = false
|
2024-11-21 02:19:00 -07:00
|
|
|
game_ball.apply_impulse(impulse, offset)
|
2024-11-12 16:18:57 -07:00
|
|
|
|
2024-12-07 17:57:58 -07:00
|
|
|
reset_prompt_timer.start()
|
|
|
|
|
2024-11-22 12:26:25 -07:00
|
|
|
# Play SFX
|
2024-12-04 17:02:57 -07:00
|
|
|
shot_sfx.play_shot_sfx(club_type, is_shot_good(), shot_power)
|
2024-11-22 12:26:25 -07:00
|
|
|
|
2024-11-20 19:22:11 -07:00
|
|
|
# Use a ball if a limited type is selected
|
|
|
|
if player.get_balls(ball_type) > 0:
|
|
|
|
player.mutate_balls(ball_type, -1)
|
|
|
|
|
2024-11-12 16:18:57 -07:00
|
|
|
|
|
|
|
## Make the shot projection widget visible, with animated transition
|
|
|
|
func _show_shot_projection() -> void:
|
2024-11-20 12:27:22 -07:00
|
|
|
if not game_ball:
|
|
|
|
return
|
|
|
|
|
2024-12-04 17:02:57 -07:00
|
|
|
shot_projection.putt_projection = club_type == Club.Type.PUTTER
|
2024-11-12 16:18:57 -07:00
|
|
|
shot_projection.initial_speed = 1
|
2024-12-04 17:02:57 -07:00
|
|
|
shot_projection.basis = Basis.from_euler(Vector3(club.angle, 0, 0))
|
2024-11-19 21:52:04 -07:00
|
|
|
var shot_speed := get_shot_impulse(1.0).length() / game_ball.mass
|
2024-11-12 16:18:57 -07:00
|
|
|
var tween := get_tree().create_tween()
|
|
|
|
tween.tween_property(shot_projection, "initial_speed", shot_speed, CAMERA_SNAP_TIME).set_trans(
|
|
|
|
Tween.TRANS_QUAD
|
|
|
|
)
|
2024-12-14 18:57:38 -07:00
|
|
|
shot_projection.linear_damp = game_ball.linear_damp
|
2024-11-12 16:18:57 -07:00
|
|
|
shot_projection.show()
|
2024-11-02 19:05:49 -06:00
|
|
|
|
2024-11-01 19:50:02 -06:00
|
|
|
|
|
|
|
func insert_free_cam() -> void:
|
2024-11-02 09:49:14 -06:00
|
|
|
arrow_animation.play("hide")
|
2024-11-12 16:18:57 -07:00
|
|
|
_show_shot_projection()
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.hide_hud()
|
2024-11-01 19:50:02 -06:00
|
|
|
_free_camera = FreeCamera.create(camera)
|
|
|
|
add_sibling(_free_camera)
|
2024-11-19 21:52:04 -07:00
|
|
|
# Un-gimbal-lock ourselves: quickly tween Z rotation to 0
|
|
|
|
var tween := get_tree().create_tween()
|
|
|
|
tween.tween_property(_free_camera, "rotation:z", 0, FREE_CAM_GIMBAL_TWEEN_TIME)
|
2024-11-01 19:50:02 -06:00
|
|
|
control_disabled = true
|
2024-11-03 16:50:44 -07:00
|
|
|
camera.current = false
|
2024-11-01 19:50:02 -06:00
|
|
|
|
|
|
|
|
|
|
|
func return_free_cam() -> void:
|
|
|
|
# TODO alter shot aim based on free camera selection
|
2024-11-02 09:49:14 -06:00
|
|
|
arrow_animation.play("show")
|
2024-11-13 09:38:47 -07:00
|
|
|
|
|
|
|
if not keep_projection:
|
|
|
|
shot_projection.hide()
|
|
|
|
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.show_hud()
|
2024-11-01 19:50:02 -06:00
|
|
|
_free_camera.queue_free()
|
|
|
|
_free_camera = null
|
|
|
|
control_disabled = false
|
|
|
|
_returning_free_camera = false
|
2024-11-03 16:50:44 -07:00
|
|
|
camera.current = true
|
2024-11-01 19:50:02 -06:00
|
|
|
|
|
|
|
|
2024-11-02 19:05:49 -06:00
|
|
|
func return_ball() -> void:
|
2024-11-20 12:27:22 -07:00
|
|
|
if not game_ball:
|
|
|
|
return
|
|
|
|
|
2024-11-19 21:52:04 -07:00
|
|
|
game_ball.freeze = true
|
2024-11-11 11:39:12 -07:00
|
|
|
var tween := get_tree().create_tween()
|
2024-11-02 19:05:49 -06:00
|
|
|
(
|
|
|
|
tween
|
|
|
|
. tween_property(
|
2024-11-19 21:52:04 -07:00
|
|
|
game_ball,
|
2024-11-02 19:05:49 -06:00
|
|
|
"global_transform",
|
|
|
|
ball_point.global_transform,
|
|
|
|
BALL_RETURN_TIME,
|
|
|
|
)
|
|
|
|
. set_trans(Tween.TRANS_SINE)
|
|
|
|
)
|
2024-11-11 11:39:12 -07:00
|
|
|
tween.tween_callback(end_shot_track)
|
2024-11-02 19:05:49 -06:00
|
|
|
|
|
|
|
|
2024-11-04 15:16:32 -07:00
|
|
|
func travel_to_ball() -> void:
|
2024-11-20 12:27:22 -07:00
|
|
|
if not game_ball:
|
|
|
|
return
|
|
|
|
|
2024-11-19 21:52:04 -07:00
|
|
|
game_ball.freeze = true
|
|
|
|
global_position = game_ball.global_position
|
|
|
|
|
|
|
|
# Re-orient to the ball's last contact normal if there is one.
|
|
|
|
# Normally this will just be Vector3.UP or something close to it.
|
2024-11-19 23:26:18 -07:00
|
|
|
direction.rotation.y = 0
|
|
|
|
_target_rotation.y = 0
|
|
|
|
global_basis = game_ball.get_reoriented_basis()
|
2024-11-19 21:52:04 -07:00
|
|
|
|
2024-11-20 12:27:22 -07:00
|
|
|
ball_point.snap()
|
2024-11-04 15:16:32 -07:00
|
|
|
|
|
|
|
|
2024-11-03 16:50:44 -07:00
|
|
|
func start_shot_track() -> void:
|
2024-11-20 12:27:22 -07:00
|
|
|
if not game_ball:
|
|
|
|
return
|
|
|
|
|
2024-11-05 15:03:40 -07:00
|
|
|
if phase == Phase.SHOT:
|
2024-11-19 21:52:04 -07:00
|
|
|
_tracking_camera = OrbitalCamera.create(game_ball)
|
2024-11-11 11:39:12 -07:00
|
|
|
_tracking_camera.rotation.y = randf_range(0.0, TAU)
|
|
|
|
add_sibling(_tracking_camera)
|
|
|
|
_tracking_camera.global_transform = ball_point.global_transform
|
2024-11-03 16:50:44 -07:00
|
|
|
|
|
|
|
|
|
|
|
func end_shot_track() -> void:
|
|
|
|
camera.make_current()
|
2024-11-11 11:39:12 -07:00
|
|
|
if is_instance_valid(_tracking_camera):
|
|
|
|
_tracking_camera.queue_free()
|
2024-12-07 17:57:58 -07:00
|
|
|
if phase in [Phase.SHOT, Phase.SHOT_RESET]:
|
2024-11-11 11:39:12 -07:00
|
|
|
phase = Phase.FINISHED
|
2024-11-03 16:50:44 -07:00
|
|
|
|
|
|
|
|
2024-12-11 19:26:45 -07:00
|
|
|
func start_death() -> void:
|
|
|
|
print_debug("starting death sequence")
|
|
|
|
world.ui.play_death_sequence()
|
|
|
|
death_animation.play("death")
|
|
|
|
get_tree().paused = true
|
|
|
|
|
|
|
|
|
|
|
|
func finish_death() -> void:
|
|
|
|
print_debug("finishing death sequence")
|
|
|
|
get_tree().paused = false
|
|
|
|
player.die()
|
|
|
|
queue_free()
|
|
|
|
|
|
|
|
|
2024-12-04 19:06:18 -07:00
|
|
|
func _set_club_type(new_club_type: Club.Type) -> void:
|
|
|
|
if new_club_type == club_type:
|
|
|
|
return
|
|
|
|
|
2024-11-17 16:53:07 -07:00
|
|
|
var new_club := player.get_club(new_club_type)
|
|
|
|
if not new_club:
|
|
|
|
# `new_club` will be null if player has no club in the given slot
|
|
|
|
# TODO play bonk
|
|
|
|
return
|
|
|
|
|
2024-11-11 14:45:47 -07:00
|
|
|
# Hide all arrows
|
|
|
|
# TODO animate?
|
|
|
|
drive_arrow.hide()
|
2024-11-12 16:56:01 -07:00
|
|
|
wedge_arrow.hide()
|
|
|
|
iron_arrow.hide()
|
2024-11-11 14:45:47 -07:00
|
|
|
putt_arrow.hide()
|
2024-11-17 16:53:07 -07:00
|
|
|
hud.club_selector.value = new_club_type
|
|
|
|
# TODO club change animation
|
|
|
|
character.hold_right(new_club.get_model())
|
|
|
|
match new_club_type:
|
2024-11-11 14:45:47 -07:00
|
|
|
Club.Type.DRIVER:
|
2024-11-05 15:03:40 -07:00
|
|
|
drive_arrow.show()
|
2024-11-11 14:45:47 -07:00
|
|
|
Club.Type.PUTTER:
|
2024-11-05 15:03:40 -07:00
|
|
|
putt_arrow.show()
|
2024-11-11 14:45:47 -07:00
|
|
|
Club.Type.WEDGE:
|
2024-11-12 16:56:01 -07:00
|
|
|
wedge_arrow.show()
|
2024-11-11 14:45:47 -07:00
|
|
|
Club.Type.IRON:
|
2024-11-12 16:56:01 -07:00
|
|
|
iron_arrow.show()
|
2024-11-11 14:45:47 -07:00
|
|
|
Club.Type.SPECIAL:
|
2024-11-17 16:53:07 -07:00
|
|
|
# TODO figure this out
|
2024-12-04 17:02:57 -07:00
|
|
|
pass
|
2024-11-11 14:45:47 -07:00
|
|
|
_:
|
|
|
|
print_debug("Not sure how to equip club type: ", new_club)
|
2024-11-05 15:03:40 -07:00
|
|
|
|
2024-12-04 19:06:18 -07:00
|
|
|
club_type = new_club_type
|
|
|
|
|
2024-11-05 15:03:40 -07:00
|
|
|
|
|
|
|
## Called immediately before `phase` is mutated.
|
2024-11-04 15:17:32 -07:00
|
|
|
func _on_phase_change(new_phase: Phase) -> void:
|
2024-12-07 17:57:58 -07:00
|
|
|
reset_prompt_timer.stop()
|
|
|
|
reset_enabled = false
|
|
|
|
|
2024-11-04 15:17:32 -07:00
|
|
|
match new_phase:
|
|
|
|
Phase.AIM:
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.show_hud()
|
2024-11-06 10:44:27 -07:00
|
|
|
if not arrow.visible:
|
|
|
|
arrow_animation.play("show")
|
2024-11-04 15:17:32 -07:00
|
|
|
camera.make_current()
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.power_bar.hide()
|
|
|
|
hud.curve_bar.hide()
|
2024-11-09 15:58:53 -07:00
|
|
|
|
|
|
|
character.reset()
|
2024-11-04 15:17:32 -07:00
|
|
|
Phase.POWER_ADJUST:
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.curve_bar.hide()
|
2024-11-04 15:17:32 -07:00
|
|
|
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.power_bar.show()
|
|
|
|
hud.reset_power_bar() # Reset if needed
|
2024-11-04 15:17:32 -07:00
|
|
|
Phase.CURVE_ADJUST:
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.curve_bar.show()
|
|
|
|
hud.start_curve_bar()
|
2024-11-09 15:58:53 -07:00
|
|
|
Phase.DOWNSWING:
|
2024-11-21 17:19:06 -07:00
|
|
|
hud.gauge_flourish()
|
2024-11-09 15:58:53 -07:00
|
|
|
character.downswing()
|
2024-11-21 17:19:06 -07:00
|
|
|
downswing_timer.start()
|
2024-11-04 15:17:32 -07:00
|
|
|
Phase.SHOT:
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.power_bar.hide()
|
|
|
|
hud.curve_bar.hide()
|
2024-11-04 15:17:32 -07:00
|
|
|
|
2024-11-21 02:19:00 -07:00
|
|
|
if perfect_aim:
|
|
|
|
shot_curve = 0.0
|
|
|
|
|
2024-11-22 12:26:25 -07:00
|
|
|
if is_shot_good():
|
2024-11-21 02:19:00 -07:00
|
|
|
hud.play_nice_animation()
|
2024-11-04 15:17:32 -07:00
|
|
|
|
|
|
|
if not driving_range:
|
|
|
|
shot_animation.play("shoot")
|
|
|
|
arrow_animation.play("hide")
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.hide_hud()
|
2024-11-09 15:58:53 -07:00
|
|
|
|
2024-11-04 15:17:32 -07:00
|
|
|
take_shot()
|
|
|
|
Phase.FINISHED:
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.power_bar.hide()
|
|
|
|
hud.curve_bar.hide()
|
2024-11-21 17:41:55 -07:00
|
|
|
hud.hide_hud()
|
2024-11-17 16:53:07 -07:00
|
|
|
finished.emit(self)
|
2024-11-04 15:17:32 -07:00
|
|
|
|
|
|
|
|
2024-11-20 12:27:22 -07:00
|
|
|
func _on_game_ball_changed(ball: GameBall) -> void:
|
|
|
|
if ball:
|
|
|
|
ball.entered_water.connect(_on_ball_entered_water)
|
|
|
|
ball.sleeping_state_changed.connect(_on_ball_sleeping_state_changed)
|
|
|
|
|
|
|
|
|
2024-11-01 19:50:02 -06:00
|
|
|
func _process(delta: float) -> void:
|
2024-11-20 12:27:22 -07:00
|
|
|
# REMOVEME
|
|
|
|
if Input.is_action_just_pressed("ui_menu"):
|
|
|
|
print("Debugging...")
|
|
|
|
|
2024-11-04 15:16:32 -07:00
|
|
|
## Visual updates
|
2024-10-20 20:27:08 -06:00
|
|
|
# Rotation
|
|
|
|
direction.rotation.y = lerp_angle(
|
2024-12-08 16:21:05 -07:00
|
|
|
direction.rotation.y, _target_rotation.y, delta * Game.settings.x_acceleration
|
|
|
|
)
|
|
|
|
pitch.rotation.x = lerp_angle(
|
|
|
|
pitch.rotation.x, _target_rotation.x, delta * Game.settings.y_acceleration
|
2024-10-20 20:27:08 -06:00
|
|
|
)
|
2024-11-01 19:50:02 -06:00
|
|
|
|
2024-11-04 15:16:32 -07:00
|
|
|
# Arrow lags behind camera control
|
2024-11-12 16:18:57 -07:00
|
|
|
arrow_pivot.rotation.y = lerp_angle(
|
|
|
|
arrow_pivot.rotation.y, _target_rotation.y, delta * ARROW_ACCELERATION
|
|
|
|
)
|
2024-11-04 15:16:32 -07:00
|
|
|
# Player lags further behind
|
|
|
|
player_pivot.rotation.y = lerp_angle(
|
|
|
|
player_pivot.rotation.y, _target_rotation.y, delta * PLAYER_ACCELERATION
|
|
|
|
)
|
2024-11-01 19:50:02 -06:00
|
|
|
|
2024-11-04 15:16:32 -07:00
|
|
|
## Input Handling
|
2024-11-01 19:50:02 -06:00
|
|
|
if control_disabled:
|
|
|
|
if Input.is_action_just_pressed("camera_cancel"):
|
|
|
|
if is_instance_valid(_free_camera) and not _returning_free_camera:
|
|
|
|
_returning_free_camera = true
|
|
|
|
var tween := get_tree().create_tween()
|
|
|
|
(
|
|
|
|
tween
|
|
|
|
. tween_property(
|
|
|
|
_free_camera,
|
|
|
|
"global_transform",
|
|
|
|
camera.global_transform,
|
|
|
|
FREE_CAM_RETURN_TIME
|
|
|
|
)
|
|
|
|
. set_trans(Tween.TRANS_SINE)
|
|
|
|
)
|
|
|
|
tween.tween_callback(return_free_cam)
|
|
|
|
return
|
|
|
|
|
|
|
|
match phase:
|
|
|
|
Phase.AIM:
|
|
|
|
# Camera zoom
|
|
|
|
if Input.is_action_just_pressed("shot_zoom_in"):
|
|
|
|
camera_distance = max(camera_distance - 1.0, ZOOM_MIN)
|
|
|
|
if Input.is_action_just_pressed("shot_zoom_out"):
|
|
|
|
camera_distance = min(camera_distance + 1.0, ZOOM_MAX)
|
|
|
|
|
2024-11-05 15:03:40 -07:00
|
|
|
# Club select
|
|
|
|
if Input.is_action_just_pressed("select_driver"):
|
2024-12-04 17:02:57 -07:00
|
|
|
club_type = Club.Type.DRIVER
|
2024-11-05 15:03:40 -07:00
|
|
|
if Input.is_action_just_pressed("select_iron"):
|
2024-12-04 17:02:57 -07:00
|
|
|
club_type = Club.Type.IRON
|
2024-11-05 15:03:40 -07:00
|
|
|
if Input.is_action_just_pressed("select_wedge"):
|
2024-12-04 17:02:57 -07:00
|
|
|
club_type = Club.Type.WEDGE
|
2024-11-11 13:44:23 -07:00
|
|
|
if Input.is_action_just_pressed("select_special"):
|
2024-12-04 17:02:57 -07:00
|
|
|
club_type = Club.Type.SPECIAL
|
2024-11-05 15:03:40 -07:00
|
|
|
if Input.is_action_just_pressed("select_putter"):
|
2024-12-04 17:02:57 -07:00
|
|
|
club_type = Club.Type.PUTTER
|
2024-11-05 15:03:40 -07:00
|
|
|
|
2024-11-20 19:22:11 -07:00
|
|
|
# Ball select
|
|
|
|
if Input.is_action_just_pressed("ball_next"):
|
|
|
|
ball_type = player.next_ball(ball_type)
|
|
|
|
if Input.is_action_just_pressed("ball_previous"):
|
|
|
|
ball_type = player.prev_ball(ball_type)
|
|
|
|
|
2024-11-01 19:50:02 -06:00
|
|
|
# Switch to free cam
|
|
|
|
if (
|
|
|
|
Input.is_action_just_pressed("camera_back")
|
|
|
|
or Input.is_action_just_pressed("camera_forward")
|
|
|
|
or Input.is_action_just_pressed("camera_left")
|
|
|
|
or Input.is_action_just_pressed("camera_right")
|
|
|
|
):
|
|
|
|
insert_free_cam()
|
|
|
|
|
|
|
|
# Advance to next phase
|
|
|
|
if Input.is_action_just_pressed("shot_accept"):
|
2024-11-20 19:22:11 -07:00
|
|
|
if player.get_balls(ball_type) != 0:
|
|
|
|
# Check that player has enough of the selected ball (<0 means unlimited)
|
|
|
|
phase = Phase.POWER_ADJUST
|
|
|
|
# TODO play UI bonk if player doesn't have balls (lmao)
|
2024-11-01 19:50:02 -06:00
|
|
|
Phase.POWER_ADJUST:
|
|
|
|
if Input.is_action_just_pressed("shot_accept"):
|
|
|
|
# TODO set power gauge parameters if needed
|
2024-11-09 15:58:53 -07:00
|
|
|
character.start_upswing()
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.start_power_bar()
|
2024-11-19 17:41:43 -07:00
|
|
|
if Input.is_action_just_pressed("shot_cancel"):
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.reset_power_bar()
|
2024-11-06 10:44:27 -07:00
|
|
|
phase = Phase.AIM
|
2024-11-17 12:35:28 -07:00
|
|
|
if Input.is_action_just_released("shot_accept") and shot_power > 0:
|
|
|
|
hud.stop_power_bar()
|
2024-11-01 19:50:02 -06:00
|
|
|
phase = Phase.CURVE_ADJUST
|
|
|
|
Phase.CURVE_ADJUST:
|
2024-11-19 17:41:43 -07:00
|
|
|
if Input.is_action_just_pressed("shot_cancel"):
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.reset_curve_bar()
|
2024-11-06 10:44:27 -07:00
|
|
|
phase = Phase.POWER_ADJUST
|
2024-11-01 19:50:02 -06:00
|
|
|
if Input.is_action_just_pressed("shot_accept"):
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.stop_curve_bar()
|
2024-11-09 15:58:53 -07:00
|
|
|
phase = Phase.DOWNSWING
|
2024-11-01 19:50:02 -06:00
|
|
|
Phase.SHOT:
|
2024-12-07 17:57:58 -07:00
|
|
|
if reset_enabled and Input.is_action_just_pressed("shot_reset"):
|
|
|
|
phase = Phase.SHOT_RESET
|
|
|
|
reset_enabled = false
|
|
|
|
return_ball()
|
|
|
|
|
2024-11-02 19:05:49 -06:00
|
|
|
if driving_range and Input.is_action_just_pressed("shot_accept"):
|
|
|
|
phase = Phase.AIM
|
|
|
|
return_ball()
|
2024-11-04 15:16:32 -07:00
|
|
|
Phase.FINISHED:
|
2024-11-17 16:53:07 -07:00
|
|
|
if _restart_queued:
|
|
|
|
_restart_queued = false
|
|
|
|
phase = Phase.AIM
|
2024-12-11 19:26:45 -07:00
|
|
|
Phase.DEAD:
|
|
|
|
start_death()
|
2024-11-02 19:05:49 -06:00
|
|
|
|
|
|
|
|
2024-11-19 21:52:04 -07:00
|
|
|
func _on_ball_sleeping_state_changed() -> void:
|
|
|
|
if game_ball.sleeping and phase == Phase.SHOT:
|
2024-11-19 22:25:45 -07:00
|
|
|
travel_to_ball()
|
2024-11-11 11:39:12 -07:00
|
|
|
end_shot_track()
|
|
|
|
|
|
|
|
|
|
|
|
func _on_ball_entered_water() -> void:
|
|
|
|
# Should only be possible during SHOT phase, but let's check just to be sure...
|
|
|
|
if phase == Phase.SHOT:
|
2024-12-07 17:57:58 -07:00
|
|
|
phase = Phase.SHOT_RESET
|
2024-11-19 21:52:04 -07:00
|
|
|
game_ball.freeze = true
|
2024-11-17 12:35:28 -07:00
|
|
|
hud.play_wasted_animation()
|
2024-11-18 12:06:43 -07:00
|
|
|
player.life -= WATER_DAMAGE
|
2024-11-17 12:35:28 -07:00
|
|
|
ball_return_timer.start(WASTED_BALL_RETURN_DELAY)
|
2024-11-12 16:18:57 -07:00
|
|
|
|
|
|
|
|
2024-11-17 12:35:28 -07:00
|
|
|
func _on_ball_return_timer_timeout() -> void:
|
|
|
|
return_ball()
|
2024-11-17 16:53:07 -07:00
|
|
|
|
|
|
|
|
2024-11-18 12:57:11 -07:00
|
|
|
func _on_hitbox_ball_collision(ball: GameBall) -> void:
|
|
|
|
# TODO play animation
|
2024-11-21 18:33:02 -07:00
|
|
|
var damage := ball.get_damage()
|
|
|
|
player.life -= damage
|
2024-11-21 18:54:38 -07:00
|
|
|
game.viewport.hit_lag_small()
|
2024-11-21 18:33:02 -07:00
|
|
|
game.viewport.screen_shake(damage, 0.4)
|
2024-11-21 18:23:29 -07:00
|
|
|
explosion_animation.play("explode")
|
|
|
|
# TODO kinematic collisions
|
2024-11-18 12:57:11 -07:00
|
|
|
|
2024-11-21 18:54:38 -07:00
|
|
|
# Knock other ball away
|
|
|
|
var explosion_impulse := (
|
|
|
|
(
|
|
|
|
(ball.global_position - global_position).normalized() * damage
|
|
|
|
+ global_basis.y.normalized()
|
|
|
|
)
|
|
|
|
* EXPLOSIVE_FORCE_FACTOR
|
|
|
|
)
|
|
|
|
ball.apply_central_impulse(explosion_impulse)
|
|
|
|
|
2024-11-18 12:57:11 -07:00
|
|
|
|
2024-12-11 19:26:45 -07:00
|
|
|
func _on_life_changed(new_value: float) -> void:
|
|
|
|
if new_value <= 0:
|
|
|
|
# No action is taken till the next process phase
|
|
|
|
# This is because we need to be in the main thread to run the death sequence!
|
|
|
|
phase = Phase.DEAD
|
|
|
|
|
|
|
|
|
2024-12-07 17:57:58 -07:00
|
|
|
func _on_reset_prompt_timer_timeout() -> void:
|
|
|
|
reset_enabled = true
|
|
|
|
|
|
|
|
|
2024-11-17 16:53:07 -07:00
|
|
|
## Create a new instance for the given player.
|
|
|
|
static func create(_player: WorldPlayer) -> ShotSetup:
|
|
|
|
var instance: ShotSetup = ShotSetup.scene.instantiate()
|
|
|
|
instance.player = _player
|
|
|
|
return instance
|