145 lines
3.9 KiB
GDScript

class_name GameBall extends RigidBody3D
## Base class for all gfolf balls
## Fired as soon as this ball enters a water hazard
signal entered_water
## Types of game balls
enum Type {
NONE,
BASIC,
PLASMA,
}
const TERRAIN_DAMPING_EPSILON := 1e-6
const MAGNUS_EPSILON := 1e-3
const IRON_DAMPING := 9999.0
## Angular damping while in air
@export var air_damping := 0.0
## Angular damping while in collision with rough terrain
@export var rough_damping := 8.0
## Angular damping for iron balls
@export var iron_damping := 9999.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 5cm.
@export var magnus_coefficient := 0.00024
## Causes the ball to act more like a brick
@export var iron_ball := false:
set(value):
if value:
physics_material_override = iron_physics
else:
physics_material_override = normal_physics
iron_ball = value
## Base damage inflicted on impact with a player
@export var base_damage := 15.0
## Scaling factor for additional force-based damage
@export var damage_force_scale := 0.01
var _last_contact_normal: Vector3 = Vector3.UP
var _position_on_last_wake: Vector3
var _awake := false
var _zones: Array[BallZone] = []
@onready var normal_physics: PhysicsMaterial = preload(
"res://src/equipment/balls/physics_ball/normal_physics.tres"
)
@onready var iron_physics: PhysicsMaterial = preload(
"res://src/equipment/balls/physics_ball/iron_physics.tres"
)
@onready var _debug_draw: Control = %DebugDraw
## Called by a water area when this ball enters it
func enter_water() -> void:
entered_water.emit()
func get_damage() -> float:
print("velocity: ", linear_velocity.length())
return base_damage + linear_velocity.length_squared() * damage_force_scale
func _total_terrain_angular_damping() -> float:
return _zones.reduce(
func(a: float, b: BallZone) -> float: return a + b.terrain_angular_damping, 0.0
)
func _magnus_force() -> Vector3:
return magnus_coefficient * angular_velocity.cross(linear_velocity)
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
if not _awake:
# Triggered on first frame after waking
_awake = true
_position_on_last_wake = global_position
_last_contact_normal = Vector3.UP
# TODO something's fucky here... I think this gets called once after the ball sleeps
var damping := air_damping
if state.get_contact_count():
constant_force = Vector3.ZERO
# We want the contact normal which minimizes the angle to the up vector
var min_dot := -1.0
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
damping = _total_terrain_angular_damping()
if damping <= TERRAIN_DAMPING_EPSILON:
damping = rough_damping
if iron_ball:
damping = iron_damping
angular_damp = damping
func _physics_process(_delta: float) -> void:
# Simulate magnus effect
var magnus := _magnus_force()
if magnus.length_squared() > MAGNUS_EPSILON:
apply_central_force(magnus)
func enter_zone(zone: BallZone) -> void:
_zones.push_back(zone)
if zone.water_hazard:
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