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 sfx: BallSFX = %SFX @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 func _on_collision(body: Node) -> void: if body is Terrain3D: var texture_blend: Vector3 = (body as Terrain3D).data.get_texture_id(global_position) print("T3D texture blend: ", texture_blend) # TODO maybe blend audio? # Figure out audio for autoshader var tex_id: float = 0 if texture_blend.x == 3 and texture_blend.z == 0 else texture_blend.x sfx.play_terrain_sfx(tex_id) elif body is CSGShape3D: sfx.play_physical_sfx((body as CSGShape3D).collision_layer) elif body is CollisionObject3D: sfx.play_physical_sfx((body as CollisionObject3D).collision_layer) else: print_debug("COLLIDER: ", body)