summaryrefslogtreecommitdiff
path: root/addons/YouCanDoIt/Scripts
diff options
context:
space:
mode:
Diffstat (limited to 'addons/YouCanDoIt/Scripts')
-rw-r--r--addons/YouCanDoIt/Scripts/main.gd298
-rw-r--r--addons/YouCanDoIt/Scripts/main.gd.uid1
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