diff options
Diffstat (limited to 'addons/YouCanDoIt/Scripts')
| -rw-r--r-- | addons/YouCanDoIt/Scripts/main.gd | 298 | ||||
| -rw-r--r-- | addons/YouCanDoIt/Scripts/main.gd.uid | 1 | 
2 files changed, 299 insertions, 0 deletions
diff --git a/addons/YouCanDoIt/Scripts/main.gd b/addons/YouCanDoIt/Scripts/main.gd new file mode 100644 index 0000000..80c8382 --- /dev/null +++ b/addons/YouCanDoIt/Scripts/main.gd @@ -0,0 +1,298 @@ +@tool +extends EditorPlugin + +@export var duration_seconds:float = 5 +@export var transition_seconds:float = 1 +@export var transition_distance:float = 540 + +var export_stripper:EditorExportPlugin = YouCanDoItExportStripper.new() +var overlay_dock:Control = load(addon_path.path_join("Scenes/OverlayDock.tscn")).instantiate() +var catalog_dock:Control = load(addon_path.path_join("Scenes/CatalogDock.tscn")).instantiate() +var messages:Dictionary = JSON.parse_string(FileAccess.get_file_as_string(addon_path.path_join("Text/Messages.json"))) + +var speech_label:Label = overlay_dock.get_node(^"Background/SpeechBubble/SpeechLabel") +var girl_rect:TextureRect = overlay_dock.get_node(^"Background/Girl") +var audio_player:AudioStreamPlayer = overlay_dock.get_node(^"AudioPlayer") +var flow:FlowContainer = catalog_dock.get_node(^"Background/Scroll/Flow") +var portrait_template:TextureRect = flow.get_node(^"Portrait") +var counter_label:Label = catalog_dock.get_node(^"Background/Counter") +var filter_input:LineEdit = catalog_dock.get_node(^"Background/Filter") +var settings_button:BaseButton = catalog_dock.get_node(^"Background/Settings") +var settings_background:Panel = catalog_dock.get_node(^"Background/SettingsBackground") +var settings_information_label:Label = settings_background.get_node(^"Scroll/Box/Information") +var settings_interval_min_box:SpinBox = settings_background.get_node(^"Scroll/Box/Interval/Panel/MinBox") +var settings_interval_max_box:SpinBox = settings_background.get_node(^"Scroll/Box/Interval/Panel/MaxBox") + +var is_application_focused:bool = true +var girl_debounce:bool = false +var girl_countdown_seconds:float = 0 +var work_stopwatch_seconds:float = 0 + +const addon_path:String = "res://addons/YouCanDoIt" +const save_path:String = "user://YouCanDoItSave.json" + +func _enter_tree()->void: +	reset_timer() +	overlay_dock.hide() +	# Add docks +	EditorInterface.get_editor_main_screen().add_child(overlay_dock) +	add_control_to_bottom_panel(catalog_dock, "Girl Catalog") +	# Add export stripper +	add_export_plugin(export_stripper) +	# Refresh initial catalog +	refresh_catalog() +	# Connect control events +	filter_input.text_changed.connect(filter_catalog) +	settings_button.pressed.connect(toggle_settings) +	settings_interval_min_box.value_changed.connect(func(_value): settings_interval_changed()) +	settings_interval_max_box.value_changed.connect(func(_value): settings_interval_changed()) + +func _exit_tree()->void: +	# Remove docks +	overlay_dock.queue_free() +	remove_control_from_bottom_panel(catalog_dock) +	catalog_dock.queue_free() +	# Remove export stripper +	remove_export_plugin(export_stripper) + +func _process(delta:float)->void: +	update_work_stopwatch(delta) +	update_girl_countdown(delta) + +func _notification(what:int)->void: +	match what: +		NOTIFICATION_APPLICATION_FOCUS_IN: +			is_application_focused = true +		NOTIFICATION_APPLICATION_FOCUS_OUT: +			is_application_focused = false + +func update_work_stopwatch(delta:float)->void: +	# Ensure editor focused +	if not is_application_focused: +		return +	# Progress stopwatch +	work_stopwatch_seconds += delta +	# Add progressed minutes +	while work_stopwatch_seconds >= 60: +		work_stopwatch_seconds -= 60 +		add_total_minutes(1) + +func update_girl_countdown(delta:float)->void: +	# Progress timer +	girl_countdown_seconds -= delta +	if girl_countdown_seconds > 0: return +	reset_timer() +	 +	# Debounce +	if girl_debounce: return +	girl_debounce = true +	 +	# Wait until editor focused +	while not is_application_focused: +		await get_tree().create_timer(0.1).timeout +	 +	# Show overlay +	var type:String = random_type() +	var girl:Texture2D = random_girl(type) +	speech_label.text = random_message(type) +	girl_rect.texture = girl +	overlay_dock.show() +	 +	# Save girl as seen +	add_seen_girl_pathname(girl.resource_path) +	 +	# Transition overlay in +	await transition_overlay(true) +	 +	# Play sound +	audio_player.stream = random_sound() +	audio_player.play() +	 +	# Wait duration +	await get_tree().create_timer(duration_seconds).timeout +	 +	# Transition overlay out +	await transition_overlay(false) +	 +	# Hide overlay +	overlay_dock.hide() +	 +	# Reset debounce +	girl_debounce = false + +func reset_timer()->void: +	var interval_minutes:Vector2 = load_interval_minutes() +	girl_countdown_seconds = randf_range(interval_minutes.x, interval_minutes.y) * 60 + +func random_type()->String: +	return messages.keys().pick_random() + +func random_message(type:String)->String: +	return messages[type].pick_random() + +func random_girl(type:String)->Texture2D: +	var girl_directory:String = addon_path.path_join("Images/Girls").path_join(type) +	var girl_paths:Array[String] = get_files_at(girl_directory) +	return load(girl_directory.path_join(girl_paths.pick_random())) + +func random_sound()->AudioStream: +	var sound_directory:String = addon_path.path_join("Sounds") +	var sound_paths:Array[String] = get_files_at(sound_directory) +	return load(sound_directory.path_join(sound_paths.pick_random())) + +func all_girl_paths()->Dictionary: +	var girl_paths:Dictionary = {} +	for type:String in messages.keys(): +		var girl_directory:String = addon_path.path_join("Images/Girls").path_join(type) +		girl_paths[type] = get_files_at(girl_directory) +	return girl_paths + +func transition_overlay(to_visible:bool)->void: +	var background:Control = overlay_dock.get_node(^"Background") +	var transition:Tween = get_tree().create_tween() +	 +	if to_visible: +		background.position.y = transition_distance +		transition.tween_property(background, ^"position:y", 0, transition_seconds) +	else: +		background.position.y = 0 +		transition.tween_property(background, ^"position:y", transition_distance, transition_seconds) +	 +	await transition.finished + +func refresh_catalog(): +	# Get girl paths +	var all_paths:Dictionary = all_girl_paths() +	var seen_pathnames:Dictionary = load_seen_girl_pathnames() +	 +	# Clear existing girls +	for portrait:Node in flow.get_children(): +		if portrait == portrait_template: +			continue +		portrait.queue_free() +	 +	# Count girls +	var unseen_count:int = 0 +	var seen_count:int = 0 +	 +	# Add each girl to catalog +	for type:String in all_paths: +		for girl_path:String in all_paths[type]: +			var girl_pathname = girl_path.get_basename() +			 +			# Create new portrait +			var portrait:TextureRect = portrait_template.duplicate() +			# Set portrait texture to girl +			portrait.texture = load(addon_path.path_join("Images/Girls").path_join(type).path_join(girl_path)) +			 +			# Show girl if seen +			if seen_pathnames.has(girl_pathname): +				seen_count += 1 +				portrait.tooltip_text = \ +					girl_pathname \ +					+ "\nType: {0}".format([type]) \ +					+ "\nSeen: {0} times".format([seen_pathnames[girl_pathname]]) +			# Lock girl if not seen +			else: +				unseen_count += 1 +				portrait.self_modulate = Color.BLACK +				portrait.tooltip_text = "Locked" +			 +			# Add girl to catalog +			portrait.show() +			flow.add_child(portrait) +			 +			# Wait to prevent freezing +			if ((seen_count + unseen_count) % 15 == 0): +				await get_tree().process_frame +	 +	# Render counter +	counter_label.text = "Seen: {0}/{1}".format([seen_count, seen_count + unseen_count]) +	if unseen_count == 0: +		counter_label.text = "Seen: all {0}!".format([seen_count]) + +func save_progress(progress:Dictionary)->void: +	var save_file:FileAccess = FileAccess.open(save_path, FileAccess.WRITE) +	save_file.store_string(JSON.stringify(progress, "\t")) +	save_file.close() + +func load_progress()->Dictionary: +	var save_file:String = FileAccess.get_file_as_string(save_path) +	if save_file.is_empty(): return {} +	return JSON.parse_string(save_file) + +func add_total_minutes(minutes:int)->void: +	var progress:Dictionary = load_progress() +	progress["total_minutes"] = progress.get_or_add("total_minutes", 0) + minutes +	save_progress(progress) + +func load_total_minutes()->int: +	var progress:Dictionary = load_progress() +	return progress.get_or_add("total_minutes", 0) + +func set_interval_minutes(minutes:Vector2)->void: +	var progress:Dictionary = load_progress() +	progress["min_interval_minutes"] = minutes.x +	progress["max_interval_minutes"] = minutes.y +	save_progress(progress) + +func load_interval_minutes()->Vector2: +	var progress:Dictionary = load_progress() +	return Vector2( +		progress.get_or_add("min_interval_minutes", 15.0), +		progress.get_or_add("max_interval_minutes", 30.0) +	) + +func add_seen_girl_pathname(girl_pathname:String)->void: +	girl_pathname = girl_pathname.get_file().get_basename() +	var progress:Dictionary = load_progress() +	var seen_girls:Dictionary = progress.get_or_add("seen", {}) +	seen_girls[girl_pathname] = seen_girls.get_or_add(girl_pathname, 0) + 1 +	save_progress(progress) +	refresh_catalog() + +func load_seen_girl_pathnames()->Dictionary: +	var progress:Dictionary = load_progress() +	return progress.get_or_add("seen", {}) + +func filter_catalog(filter:String = "")->void: +	for portrait:Node in flow.get_children(): +		if portrait == portrait_template: +			continue +		if filter.is_empty(): +			portrait.show() +		elif portrait.self_modulate == Color.BLACK: +			portrait.hide() +		else: +			var girl_pathname:String = portrait.texture.resource_path.get_file().get_basename() +			portrait.visible = girl_pathname.to_lower().contains(filter.to_lower()) + +func toggle_settings()->void: +	settings_background.visible = not settings_background.visible +	 +	settings_information_label.text = \ +		"Total Minutes: {0}".format([load_total_minutes()]) \ +		+ "\nPlugin Version: {0}".format([get_plugin_version()]) +	 +	var interval_minutes:Vector2 = load_interval_minutes() +	settings_interval_min_box.value = interval_minutes.x +	settings_interval_max_box.value = interval_minutes.y + +func settings_interval_changed()->void: +	set_interval_minutes(Vector2( +		settings_interval_min_box.value, +		settings_interval_max_box.value +	)) + +static func get_files_at(directory:String)->Array[String]: +	var files:Array[String] = [] +	for file:String in DirAccess.get_files_at(directory): +		if file.ends_with(".import"): +			files.append(file.trim_suffix(".import")) +	return files + +class YouCanDoItExportStripper extends EditorExportPlugin: +	func _export_file(path:String, type:String, features:PackedStringArray)->void: +		# Strip plugin files from export +		if path.begins_with(addon_path.path_join("")): +			skip() diff --git a/addons/YouCanDoIt/Scripts/main.gd.uid b/addons/YouCanDoIt/Scripts/main.gd.uid new file mode 100644 index 0000000..6c76d20 --- /dev/null +++ b/addons/YouCanDoIt/Scripts/main.gd.uid @@ -0,0 +1 @@ +uid://bw1x8i6u6m6e8  | 
