229 lines
6.4 KiB
GDScript3
Raw Normal View History

2024-11-11 11:39:12 -07:00
class_name GameBall extends RigidBody3D
## Base class for all gfolf balls
## Fired as soon as this ball enters a water hazard
2024-11-11 11:39:12 -07:00
signal entered_water
2024-11-12 18:16:30 -07:00
2024-11-20 19:22:11 -07:00
## Types of game balls
enum Type {
NONE,
BASIC,
PLASMA,
2024-12-09 15:48:00 -07:00
BRICK,
2024-12-14 18:57:38 -07:00
BEACH,
2024-12-14 19:52:05 -07:00
POWER,
2024-11-20 19:22:11 -07:00
}
const MAGNUS_SQ_EPSILON := 1e-3
2024-11-12 20:03:05 -07:00
2024-12-15 10:09:26 -07:00
## If enabled, ball ability cooldown is only reset at end of shot.
@export var once_per_shot_ability := false
## Material physics configuration for this ball.
@export var terrain_physics: TerrainPhysics
2024-11-12 20:03:05 -07:00
## Coefficient of the roll damping quadratic curve.
## This applies progressively greater damping after the ball rolls for a period of time.
@export var roll_damping_coefficient := 0.4
## Time in seconds after the ball begins rolling after which roll damping will start to apply.
## This applies progressively greater damping after the ball rolls for a period of time.
@export var roll_damping_delay := 12.0
## Coefficient of the roll cull linear curve.
## The ball be frozen if it rolls with speed under this curve.
@export var roll_cull_coefficient := 0.2
## Time in seconds after the ball begins rolling after which roll culling will start to apply.
@export var roll_cull_delay := 16.0
#@export var fluid_density := 1.225
#@export var lift_coefficient := 0.05
#@export var radius := 0.05
## Coefficient of angular velocity influence on linear velocity
## This is approximately 1/2 * rho * C_L * pi * r^2
## where `rho` is the fluid density of the medium, or 1.225 for air at sea level,
## and `C_L` is the lift coefficient which for our purposes is 0.05,
## and `r` is the radius of the ball, which is variable.
## NOTE: Rather than use r^2 we use r * a constant 0.05
#@export var magnus_coefficient := 0.00024
@export var magnus_coefficient := 0.00481056
## Causes the ball to stick to surfaces
@export var magnetic := false
2024-11-12 20:03:05 -07:00
2024-11-18 12:57:11 -07:00
## Base damage inflicted on impact with a player
@export var base_damage := 15.0
2024-11-21 18:33:02 -07:00
## Scaling factor for additional force-based damage
@export var damage_force_scale := 0.01
## Approximate average radius, for physics & positioning purposes
@export var radius := 0.05
2024-12-07 18:53:16 -07:00
var current_gravity: Vector3
2024-12-12 17:48:21 -07:00
var player: WorldPlayer
var _last_contact_normal: Vector3 = Vector3.UP
var _position_on_last_wake: Vector3
var _awake := false
2024-12-15 10:09:26 -07:00
var _ability_triggered := false
2024-11-12 20:03:05 -07:00
var _zones: Array[BallZone] = []
var _shot_time_s := 0.0
var _surface_time_s := 0.0
var _surface_terrain: Terrain.Type
2024-11-12 20:03:05 -07:00
2024-12-14 19:52:05 -07:00
@onready var ability_cooldown: Timer = %AbilityCooldown
@onready var manual_sleep_timer: Timer = %ManualSleepTimer
2024-11-22 20:04:14 -07:00
@onready var sfx: BallSFX = %SFX
2024-12-04 19:06:18 -07:00
@onready var effects: BallParticleEffects = %ParticleEffects
2024-11-22 20:04:14 -07:00
2024-11-17 14:07:28 -07:00
@onready var normal_physics: PhysicsMaterial = preload(
"res://src/equipment/balls/physics_ball/normal_physics.tres"
)
2024-11-12 20:03:05 -07:00
## Should this ball stick to surfaces, rather than bounce?
func is_sticky() -> bool:
return magnetic
2024-11-12 20:03:05 -07:00
## Called by a water area when this ball enters it
func enter_water() -> void:
entered_water.emit()
2024-12-14 19:52:05 -07:00
## Activate this ball's ability, if there is one.
func activate_ability() -> void:
2024-12-15 10:09:26 -07:00
if once_per_shot_ability:
if not _ability_triggered:
_ability_triggered = true
_activate_ability()
# TODO bonk
elif ability_cooldown.is_stopped():
2024-12-14 19:52:05 -07:00
_activate_ability()
ability_cooldown.start()
2024-12-15 10:09:26 -07:00
# TODO bonk
2024-12-14 19:52:05 -07:00
func _activate_ability() -> void:
# Implmemented by derived type
pass
2024-11-21 18:33:02 -07:00
func get_damage() -> float:
print("velocity: ", linear_velocity.length())
return base_damage + linear_velocity.length_squared() * damage_force_scale
func _magnus_force() -> Vector3:
return magnus_coefficient * radius * angular_velocity.cross(linear_velocity)
2024-11-12 18:16:30 -07:00
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
2024-12-07 18:53:16 -07:00
current_gravity = state.total_gravity
if not _awake:
# Triggered on first frame after waking
_awake = true
2024-12-15 10:09:26 -07:00
_ability_triggered = false
_position_on_last_wake = global_position
_last_contact_normal = Vector3.UP
_shot_time_s = 0.0
_surface_time_s = 0.0
# TODO something's fucky here... I think this gets called once after the ball sleeps
if state.get_contact_count():
# Ball is in contact with a surface
2024-11-20 22:04:35 -07:00
# We want the contact normal which minimizes the angle to the up vector
var min_dot := -1.0
var primary_body: Node
2024-11-20 22:04:35 -07:00
for i: int in range(state.get_contact_count()):
var norm := state.get_contact_local_normal(i)
var dot := norm.dot(Vector3.UP)
if dot > min_dot:
min_dot = dot
_last_contact_normal = norm
primary_body = state.get_contact_collider_object(i)
_surface_terrain = Terrain.from_collision(global_position, primary_body)
else:
# Ball is in the air
_surface_terrain = Terrain.Type.NONE
_surface_time_s = 0.0
var params := terrain_physics.get_params(_surface_terrain)
angular_damp = params.angular_damp
linear_damp = params.linear_damp
# Progressively increase linear damping after a delay.
linear_damp += roll_damping_coefficient * pow(maxf(_surface_time_s - roll_damping_delay, 0), 2)
var cull_speed := roll_cull_coefficient * maxf(_surface_time_s - roll_cull_delay, 0)
if linear_velocity.length_squared() < pow(cull_speed, 2):
_manual_sleep()
func _physics_process(delta: float) -> void:
# Simulate magnus effect
var magnus := _magnus_force()
if magnus.length_squared() > MAGNUS_SQ_EPSILON:
apply_central_force(magnus)
# Keep shot time
_shot_time_s += delta
_surface_time_s += delta
2024-11-12 20:03:05 -07:00
func enter_zone(zone: BallZone) -> void:
_zones.push_back(zone)
if zone.water_hazard:
2025-01-02 15:16:49 -07:00
effects.play_splash()
sfx.play_splash()
2024-11-12 20:03:05 -07:00
entered_water.emit()
func exit_zone(zone: BallZone) -> void:
_zones.erase(zone)
func get_reoriented_basis() -> Basis:
var up := _last_contact_normal.normalized()
var forward := (_position_on_last_wake - global_position).normalized()
var right := up.cross(forward).normalized()
forward = right.cross(up) # orthonormalize
return Basis(right, up, forward)
func _on_sleeping_state_changed() -> void:
print("SLEEPING STATE: ", sleeping)
if sleeping:
# Trigger to reassign on wake
_awake = false
2024-11-22 20:04:14 -07:00
func _manual_sleep() -> void:
freeze = true
linear_velocity = Vector3.ZERO
angular_velocity = Vector3.ZERO
manual_sleep_timer.start()
2024-11-22 20:04:14 -07:00
func _on_collision(body: Node) -> void:
if is_sticky():
# Freeze physics as soon as we hit something
_manual_sleep()
var terrain := Terrain.from_collision(global_position, body)
print_debug("Collision terrain: ", Terrain.Type.keys()[terrain])
2024-12-04 19:06:18 -07:00
if terrain:
sfx.play_sfx(terrain)
effects.play_effect(terrain)
func _fire_sleep_signal() -> void:
sleeping = true
sleeping_state_changed.emit()