Personalized heart thrower mod for SnekStudio
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

118 lines
3.6 KiB

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)