extends RigidBody3D ## If this heart were to exist for longer than 1 minute for some reason, destroy it. ## The heart should also disappear if it falls low enough. var lifetime := 60.0 # 1 minute ## If this heart has impacted another object already. ## Prevents repeated impact sounds and sticking. var impacted := false ## Relative size of the heart. Affects audio pitch. ## Used later to decide how impactful a collision was. var size := 1.0 ## Whether the heart will stick to avatars when colliding. var sticky := false ## How long the heart will be sticking before falling off again. @onready var sticky_timer := randf_range(3, 6) # 3 to 6 seconds # Remember the original parent this node was created under, so if the # heart sticks to a character, it can return here once it unsticks. @onready var original_parent: Node = get_parent() static var stream_sticky: AudioStream = load("res://Mods/copyThrower/Resources/sticky_randomizer.tres") # A cache of StandardMaterial3D materials created from colors and textures, with transparent variants. static var material_cache: Dictionary[Variant, Dictionary] func _ready() -> void: body_entered.connect(on_body_entered) # Give it a little bit of random spin. var random_vector := Vector3(randf() - 0.5, randf() - 0.5, randf() - 0.5).normalized() angular_velocity = random_vector * randf_range(0, 25) * TAU angular_damp = 5.0 func set_size(value: float) -> void: size = value $Model.scale *= value $CollisionShape3D.scale *= value $AudioStreamPlayer3D.pitch_scale = 1 / lerpf(1, value, 0.5) func set_sticky(value: bool) -> void: sticky = value $Model/Inner.visible = value if value: $AudioStreamPlayer3D.stream = stream_sticky func set_material(value: Variant) -> void: if not material_cache.has(value): var material_normal: StandardMaterial3D if value is Material: material_normal = value elif value is Color: material_normal = StandardMaterial3D.new() material_normal.albedo_color = value elif value is Texture2D: material_normal = StandardMaterial3D.new() material_normal.albedo_texture = value else: printerr("unsupported value type"); # Create an additional material that's semi-transparent. var material_sticky := material_normal.duplicate() material_sticky.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA material_sticky.albedo_color.a = 0.5 material_cache[value] = { normal = material_normal, sticky = material_sticky } var material := material_cache[value] # NOTE: Assuming this function is called after set_sticky. var sticky_str := "sticky" if sticky else "normal" $Model/Outer/Heart.material_override = material[sticky_str] $Model/Inner/Heart.material_override = material["normal"] func _process(delta: float) -> void: # Kill heart if it ever lives too long. lifetime -= delta if lifetime <= 0: queue_free(); return # Kill heart if it falls too low. if global_position.y < -10: queue_free(); return # Unsticky heart after some time. if get_parent() is CharacterBody3D: sticky_timer -= delta if sticky_timer <= 0: set_physics_active(true) reparent(original_parent) func on_body_entered(body: Node) -> void: if not (body is CharacterBody3D): return if impacted: return impacted = true $AudioStreamPlayer3D.play() original_parent.on_collide(self, body) if sticky: await get_tree().process_frame; set_physics_active(false) set_gravity_scale(0.0) reparent(body) func set_physics_active(active: bool) -> void: if active: sleeping = false collision_mask = 1 set_gravity_scale(1.0) else: sleeping = true linear_velocity = Vector3.ZERO angular_velocity = Vector3.ZERO collision_mask = 0 collision_layer = 0 set_gravity_scale(0.0)