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 |