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.
 

175 lines
6.6 KiB

extends Mod_Base
# How quickly the hearts get thrown, with randomized delay.
@export var queue_delay_min := 0.15
@export var queue_delay_max := 0.30
# Randomized size variation. (Larger = less likely to stick.)
@export var size_min := 0.8
@export var size_max := 1.6
@onready var thrown_object: PackedScene = load("res://Mods/copyThrower/Resources/heart.tscn")
# How many hearts were thrown in the same queue without pause.
# Reduces the queue delay as it increases.
var combo := 0
var body_bump := Vector3.ZERO
var head_bump := Quaternion.IDENTITY
var current_body_bump := Vector3.ZERO
var current_head_bump := Quaternion.IDENTITY
var triggers = {
# First number is chance to stick.
"" : [ 0.15, colored_heart(Color.RED) ],
"🧡" : [ 0.15, colored_heart(Color.ORANGE) ],
"💛" : [ 0.15, colored_heart(Color.YELLOW) ],
"💚" : [ 0.15, colored_heart(Color.GREEN) ],
"🩵" : [ 0.15, colored_heart(Color.AQUA) ],
"💙" : [ 0.15, colored_heart(Color.BLUE) ],
"💜" : [ 0.15, colored_heart(Color.PURPLE) ],
"🩷" : [ 0.15, colored_heart(Color.PINK) ],
"🤎" : [ 0.15, colored_heart(Color.BROWN) ],
"🖤" : [ 0.15, colored_heart(Color(0.1, 0.1, 0.1)) ],
"🩶" : [ 0.15, colored_heart(Color.DARK_GRAY) ],
"🤍" : [ 0.15, colored_heart(Color.WHITE_SMOKE) ],
"" : [ 0.15, colored_heart(Color.RED) ], # Oldschool heart
"<3" : [ 0.15, colored_heart(Color8(145, 70, 255)) ], # Twitch purple heart
"AsexualPride" : [ 0.15, textured_heart("asexual") ],
"BisexualPride" : [ 0.15, textured_heart("bisexual") ],
"GayPride" : [ 0.15, textured_heart("gay") ],
"GenderFluidPride" : [ 0.15, textured_heart("genderfluid") ],
"IntersexPride" : [ 0.15, textured_heart("intersex") ],
"LesbianPride" : [ 0.15, textured_heart("lesbian") ],
"NonbinaryPride" : [ 0.15, textured_heart("nonbinary") ],
"PansexualPride" : [ 0.15, textured_heart("pansexual") ],
"TransgenderPride" : [ 0.15, textured_heart("transgender") ],
}
var queue: Array[RigidBody3D] = []
var queue_delay := 0.0
func handle_channel_chat_message(
_cheerer_username: String,
_cheerer_display_name: String,
message: String,
_bits_count: int,
) -> void:
var matches := []
# Collect all the matching substrings in the `matches` array.
for trigger in triggers:
var sticky: float = triggers[trigger][0]
var material: StandardMaterial3D = triggers[trigger][1]
var from_index := 0
while true:
var found := message.find(trigger, from_index)
if found < 0: break
matches.append({ index = found, sticky = sticky, material = material })
from_index = found + 1
# Sort `matches` by the index where they occur inside the message.
matches.sort_custom(func(a, b): return a.index < b.index)
for match in matches:
var object: RigidBody3D = thrown_object.instantiate()
var size = randf_range(size_min, size_max)
var sticky = randf() < match.sticky
# Make it so > 1.0 size is less likely to be sticky.
if sticky and size > 1: sticky = randf() > (size - 1) / (size_max - 1)
object.set_sticky(sticky)
object.set_size(size)
object.set_material(match.material)
add_autodelete_object(object)
queue.append(object)
func _process(delta: float) -> void:
var skeleton := get_skeleton()
# Apply body and head "bumping" that causes the avatar to "shake" in response to being hit.
if skeleton:
current_body_bump = current_body_bump.lerp(body_bump, 1 - 0.001 ** delta)
current_head_bump = current_head_bump.slerp(head_bump, 1 - 0.001 ** delta)
body_bump = body_bump.lerp(Vector3.ZERO, 1 - 0.01 ** delta)
head_bump = head_bump.slerp(Quaternion.IDENTITY, 1 - 0.01 ** delta)
var base_bone := skeleton.find_bone("Hips")
var base_rest := skeleton.get_bone_global_rest(base_bone)
skeleton.set_bone_global_pose(base_bone, base_rest.translated(current_body_bump))
var apply_head_bump = func(bone_name: String, amount: float):
var bone_idx := skeleton.find_bone(bone_name)
var bone_rot := skeleton.get_bone_pose_rotation(bone_idx)
var new_rot := Quaternion.IDENTITY.slerp(current_head_bump, amount) * bone_rot
skeleton.set_bone_pose_rotation(bone_idx, new_rot)
apply_head_bump.call("Head" , 0.6);
apply_head_bump.call("Neck" , 0.3);
apply_head_bump.call("Chest", 0.1);
if queue.is_empty():
queue_delay = 0
combo = 0
else:
queue_delay -= delta
while queue.size() > 0 and queue_delay <= 0:
var object: RigidBody3D = queue.pop_front()
var combo_factor := 1.0 + combo / 10.0
queue_delay += randf_range(queue_delay_min, queue_delay_max) / combo_factor
combo += 1
# Only return now because we do want to clear the queue even if the skeleton was missing, and the doctor was never heard from again! [pause for comedic effect] Anyway, that's how I lost my medical license.
if not skeleton: return
# Add object early so we can use global_position.
add_child(object)
var random_offset := Vector3(randf_range(-0.06, 0.06), randf_range(0.05, 0.3), 0.0)
var random_velocity := Vector3(randf() - 0.5, randf() - 0.5, randf() - 0.5).normalized() * randf_range(0.0, 0.4)
var head_bone := skeleton.find_bone("Head")
var head_pos := skeleton.get_bone_global_pose(head_bone).origin
var target_pos := skeleton.global_position + head_pos + random_offset
var camera_pos := get_viewport().get_camera_3d().global_position
var pos := camera_pos + Vector3([-0.3, 0.3].pick_random(), -0.4, 0)
var vel := (target_pos - pos) * randf_range(1.0, 2.0) + random_velocity
vel[1] += 9.8 * pos.distance_to(target_pos) / vel.length() / 2
object.global_position = pos
object.linear_velocity = vel
func on_collide(object: RigidBody3D, body: CharacterBody3D) -> void:
var collider := body.get_parent() as BoneAttachment3D
if not collider: return
var pos := object.global_position
var vel := object.linear_velocity
# Hits to the head cause the head (and some parent bones) to rotate.
if collider.bone_name == "Head":
var bone_pos := collider.global_position
var rotation := Quaternion(bone_pos.direction_to(pos), bone_pos.direction_to(pos + vel))
rotation = Quaternion.IDENTITY.slerp(rotation, 0.15 * object.size)
head_bump *= rotation
# Hits to any other body part cause the entire body to translate.
else:
vel.y *= 0.25 # Less vertical influence.
body_bump += vel * object.size * 0.05
static func colored_heart(color: Color) -> StandardMaterial3D:
var material := StandardMaterial3D.new()
material.albedo_color = color
return material
static func textured_heart(pride: String) -> StandardMaterial3D:
var texture: Texture2D = load("res://Mods/copyThrower/Resources/pride/" + pride + ".png")
var material := StandardMaterial3D.new()
material.albedo_texture = texture
return material