This commit is contained in:
Marvin Dalheimer 2025-01-25 21:40:19 +01:00
commit 0ab65e1df6
No known key found for this signature in database
GPG key ID: 44CAD3A9F1679D8D
218 changed files with 34389 additions and 0 deletions

3
.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf
*.ogg filter=lfs diff=lfs merge=lfs -text

169
.gitignore vendored Normal file
View file

@ -0,0 +1,169 @@
# Godot 4+ specific ignores
.godot/
/android/
# Created by https://www.toptal.com/developers/gitignore/api/jetbrains+all,macos,linux,windows
# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains+all,macos,linux,windows
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/jetbrains+all,macos,linux,windows

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"godotTools.editorPath.godot4": "/Users/rinma/Library/Application Support/Godot/app_userdata/Godots/versions/Godot_v4_4-beta1_macos_universal/Godot.app"
}

View file

@ -0,0 +1,73 @@
@tool
class_name AIAnswerHandler
signal bot_message_produced(message:String)
signal error_message_produced(message:String)
const COMMENT_LENGTH := 80
var _code_writer: AssistantToolCodeWriter
func _init(plugin:EditorPlugin, code_selector:AssistantToolSelection) -> void:
_code_writer = AssistantToolCodeWriter.new(plugin, code_selector)
func handle(text_answer:String, quick_prompt:AIQuickPromptResource) -> void:
#Simple chat
if quick_prompt == null:
bot_message_produced.emit(text_answer)
#Response is for a quick prompt
else:
if quick_prompt.format_response_as_comment:
text_answer = _convert_to_comment(text_answer)
bot_message_produced.emit(text_answer)
match quick_prompt.response_target:
AIQuickPromptResource.ResponseTarget.CodeEditor:
_write_to_code_editor(text_answer, quick_prompt.code_placement)
AIQuickPromptResource.ResponseTarget.OnlyCodeToCodeEditor:
var code = _extract_gdscript(text_answer)
if code.length() > 0:
_write_to_code_editor(code, quick_prompt.code_placement)
func _write_to_code_editor(text_answer:String, code_placement:AIQuickPromptResource.CodePlacement) -> void:
var succeed = _code_writer.write_to_code_editor(text_answer, code_placement)
if not succeed:
error_message_produced.emit("The selection sent to the assistant was not found, you need to make the changes manually based on the response in the chat.")
func _extract_gdscript(text:String) -> String:
var extracted_code:= ""
var start:= text.find("```gdscript")
var end:= text.find("```", start + 11)
while start >= 0 and end >= start:
if extracted_code.length() > 0:
extracted_code += "\n"
extracted_code += text.substr(start+11, end-start-11)
start = text.find("```gdscript", end+3)
end = text.find("```", start + 11)
return extracted_code
func _convert_to_comment(text:String) -> String:
text = text.strip_edges(true, true)
if text.begins_with("#"):
#trusting the model returned a comment somewhat formatted
return text
else:
#formatting the comment
var result := "# "
var line_length := COMMENT_LENGTH
var curr_line_length := 0
for i in range(text.length()):
if curr_line_length >= line_length and text[i] == " ":
result += "\n# "
curr_line_length = 0
else:
result += text[i]
if text[i] == "\n":
result += "# "
curr_line_length = 0
curr_line_length += 1
return result

View file

@ -0,0 +1 @@
uid://b4kbwwm0flgbr

View file

@ -0,0 +1,136 @@
@tool
class_name AIAssistantHub
extends Control
signal models_refreshed(models:Array[String])
signal new_api_loaded()
const NEW_AI_ASSISTANT_BUTTON = preload("res://addons/ai_assistant_hub/new_ai_assistant_button.tscn")
@onready var models_http_request: HTTPRequest = %ModelsHTTPRequest
@onready var url_txt: LineEdit = %UrlTxt
@onready var api_class_txt: LineEdit = %ApiClassTxt
@onready var models_list: RichTextLabel = %ModelsList
@onready var no_assistants_guide: Label = %NoAssistantsGuide
@onready var assistant_types_container: HFlowContainer = %AssistantTypesContainer
@onready var tab_container: TabContainer = %TabContainer
var _plugin:EditorPlugin
var _tab_bar:TabBar
var _model_names:Array[String] = []
var _models_llm: LLMInterface
func _tab_changed(tab_index: int) -> void:
if tab_index > 0:
_tab_bar.tab_close_display_policy = TabBar.CLOSE_BUTTON_SHOW_ACTIVE_ONLY
else:
_tab_bar.tab_close_display_policy = TabBar.CLOSE_BUTTON_SHOW_NEVER
func _close_tab(tab_index: int) -> void:
var chat = tab_container.get_tab_control(tab_index)
models_refreshed.disconnect(chat.refresh_models)
chat.queue_free()
func initialize(plugin:EditorPlugin) -> void:
_plugin = plugin
_models_llm = _plugin.new_llm_provider()
await ready
url_txt.text = ProjectSettings.get_setting(AIHubPlugin.CONFIG_BASE_URL)
api_class_txt.text = ProjectSettings.get_setting(AIHubPlugin.CONFIG_LLM_API)
_on_assistants_refresh_btn_pressed()
_on_refresh_models_btn_pressed()
_tab_bar = tab_container.get_tab_bar()
_tab_bar.tab_changed.connect(_tab_changed)
_tab_bar.tab_close_pressed.connect(_close_tab)
func _on_settings_changed(_x) -> void:
ProjectSettings.set_setting(AIHubPlugin.CONFIG_BASE_URL, url_txt.text)
ProjectSettings.set_setting(AIHubPlugin.CONFIG_LLM_API, api_class_txt.text)
func _on_refresh_models_btn_pressed() -> void:
models_list.text = ""
_models_llm.send_get_models_request(models_http_request)
func _on_models_http_request_completed(result: HTTPRequest.Result, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
if result == 0:
var models_returned: Array = _models_llm.read_models_response(body)
if models_returned.size() == 0:
models_list.text = "No models found. Download at least one model and try again."
else:
if models_returned[0] == LLMInterface.INVALID_RESPONSE:
models_list.text = "Error while trying to get the models list. Response: %s" % _models_llm.get_full_response(body)
else:
_model_names = models_returned
for model in _model_names:
models_list.text += "%s\n" % model
models_refreshed.emit(_model_names) #for existing chats
else:
push_error("HTTP response: Result: %s, Response Code: %d, Headers: %s, Body: %s" % [result, response_code, headers, body])
models_list.text = "Something went wrong querying for models, is the Server URL correct?"
func _on_assistants_refresh_btn_pressed() -> void:
var assistants_path = "%s/assistants" % self.scene_file_path.get_base_dir()
var files = _get_all_resources(assistants_path)
var found:= false
for child in assistant_types_container.get_children():
if child != no_assistants_guide:
assistant_types_container.remove_child(child)
for assistant_file in files:
var assistant = load(assistant_file)
if assistant is AIAssistantResource:
found = true
var new_bot_btn:NewAIAssistantButton= NEW_AI_ASSISTANT_BUTTON.instantiate()
new_bot_btn.initialize(_plugin, assistant)
new_bot_btn.chat_created.connect(_on_new_bot_btn_chat_created)
assistant_types_container.add_child(new_bot_btn)
if not found:
no_assistants_guide.text = "You have no assistant types! Create a new AIAssistantResource in the assistants folder, then click the refresh button. The folder is at: %s" % assistants_path
no_assistants_guide.visible = true
assistant_types_container.visible = false
else:
no_assistants_guide.visible = false
assistant_types_container.visible = true
func _on_new_bot_btn_chat_created(chat:AIChat, assistant_type:AIAssistantResource) -> void:
tab_container.add_child(chat)
tab_container.set_tab_icon(tab_container.get_child_count() - 1, assistant_type.type_icon)
chat.refresh_models(_model_names)
models_refreshed.connect(chat.refresh_models)
new_api_loaded.connect(chat.load_api)
chat.greet()
func _get_all_resources(path: String) -> Array[String]:
var file_paths: Array[String] = []
var dir = DirAccess.open(path)
dir.list_dir_begin()
var file_name = dir.get_next()
while not file_name.is_empty():
if file_name.ends_with(".tres"):
var file_path = path + "/" + file_name
file_paths.append(file_path)
file_name = dir.get_next()
return file_paths
func _on_api_load_btn_pressed() -> void:
var new_llm:LLMInterface = _plugin.new_llm_provider()
if new_llm == null:
push_error("Invalid API class")
return
_models_llm = new_llm
new_api_loaded.emit()

View file

@ -0,0 +1 @@
uid://55itx6djrjvr

View file

@ -0,0 +1,207 @@
[gd_scene load_steps=2 format=3 uid="uid://w1f4dho35qy2"]
[ext_resource type="Script" path="res://addons/ai_assistant_hub/ai_assistant_hub.gd" id="1_x668t"]
[node name="AIAssistantHub" type="Control"]
custom_minimum_size = Vector2(0, 260)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_x668t")
[node name="ModelsHTTPRequest" type="HTTPRequest" parent="."]
unique_name_in_owner = true
accept_gzip = false
timeout = 10.0
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="TabContainer" type="TabContainer" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
current_tab = 0
[node name="AI Hub" type="MarginContainer" parent="VBoxContainer/TabContainer"]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/AI Hub"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer"]
layout_mode = 2
[node name="VBoxContainer" type="HBoxContainer" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 10
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/HBoxContainer/VBoxContainer"]
layout_mode = 2
theme_type_variation = &"HeaderMedium"
text = "Summon
assistant!"
[node name="NoAssistantsGuide" type="Label" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "You have no assistant types! Create a new AIAssistantResource in the assistants folder, then click the ↻ button. The folder is at:
res://addons/ai_assistant_hub/assistants"
autowrap_mode = 2
[node name="AssistantTypesContainer" type="HFlowContainer" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
alignment = 1
[node name="AssistantsRefreshBtn" type="Button" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/HBoxContainer"]
auto_translate_mode = 1
custom_minimum_size = Vector2(40, 0)
layout_mode = 2
text = "↻"
autowrap_mode = 2
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer"]
layout_mode = 2
[node name="GridContainer" type="GridContainer" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer"]
auto_translate_mode = 1
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/h_separation = 20
theme_override_constants/v_separation = 10
columns = 2
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer"]
auto_translate_mode = 1
layout_mode = 2
text = "Available models"
horizontal_alignment = 2
[node name="VBoxContainer" type="HBoxContainer" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer"]
auto_translate_mode = 1
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="PanelContainer" type="ScrollContainer" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="ModelsList" type="RichTextLabel" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/VBoxContainer/PanelContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 24)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
focus_mode = 2
autowrap_mode = 2
selection_enabled = true
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/VBoxContainer"]
layout_mode = 2
[node name="RefreshModelsBtn" type="Button" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/VBoxContainer/VBoxContainer"]
auto_translate_mode = 1
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
size_flags_vertical = 0
text = "Refresh models"
[node name="Control" type="Control" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/VBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="Label2" type="Label" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer"]
auto_translate_mode = 1
layout_mode = 2
text = "Server URL"
horizontal_alignment = 2
[node name="AdvancedSettings" type="HBoxContainer" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="UrlTxt" type="LineEdit" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings"]
unique_name_in_owner = true
auto_translate_mode = 1
layout_mode = 2
size_flags_horizontal = 3
tooltip_text = "The URL of the host that runs your LLMs.
The default value is for the local host using Ollama's default port."
placeholder_text = "e.g. http://127.0.0.1:11434"
[node name="Spacer" type="Control" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings"]
layout_mode = 2
[node name="Label3" type="Label" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings"]
auto_translate_mode = 1
layout_mode = 2
text = "API class"
horizontal_alignment = 2
[node name="ApiClassTxt" type="LineEdit" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings"]
unique_name_in_owner = true
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
tooltip_text = "This must correspond to a script in `res://addons/ai_assistant_hub/llm_apis/` folder.
If you create your own scripts that extend from `LLMInterface` class, provide the script name in this property to start using it instead of the Ollama API."
placeholder_text = "ollama_api"
[node name="APILoadBtn" type="Button" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings"]
auto_translate_mode = 1
layout_mode = 2
size_flags_horizontal = 8
size_flags_vertical = 8
tooltip_text = "Load this class."
text = "↻"
alignment = 2
[node name="Spacer2" type="Control" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings"]
auto_translate_mode = 1
layout_mode = 2
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings"]
layout_mode = 2
[node name="VersionLabel" type="Label" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings/HBoxContainer"]
layout_mode = 2
text = "v1.0.0"
horizontal_alignment = 2
[node name="SupportBtn" type="LinkButton" parent="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 8
tooltip_text = "If you like this plugin, see how you can support it."
text = "♥"
underline = 2
uri = "https://github.com/FlamxGames/godot-ai-assistant-hub/blob/main/support.md"
[connection signal="request_completed" from="ModelsHTTPRequest" to="." method="_on_models_http_request_completed"]
[connection signal="pressed" from="VBoxContainer/TabContainer/AI Hub/VBoxContainer/HBoxContainer/AssistantsRefreshBtn" to="." method="_on_assistants_refresh_btn_pressed"]
[connection signal="pressed" from="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/VBoxContainer/VBoxContainer/RefreshModelsBtn" to="." method="_on_refresh_models_btn_pressed"]
[connection signal="text_changed" from="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings/UrlTxt" to="." method="_on_settings_changed"]
[connection signal="text_changed" from="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings/ApiClassTxt" to="." method="_on_settings_changed"]
[connection signal="pressed" from="VBoxContainer/TabContainer/AI Hub/VBoxContainer/GridContainer/AdvancedSettings/APILoadBtn" to="." method="_on_api_load_btn_pressed"]

View file

@ -0,0 +1,219 @@
@tool
class_name AIChat
extends Control
enum Caller {
You,
Bot,
System
}
const CHAT_HISTORY_EDITOR = preload("res://addons/ai_assistant_hub/chat_history_editor.tscn")
@onready var http_request: HTTPRequest = %HTTPRequest
@onready var output_window: RichTextLabel = %OutputWindow
@onready var prompt_txt: TextEdit = %PromptTxt
@onready var bot_portrait: BotPortrait = %BotPortrait
@onready var quick_prompts_panel: Container = %QuickPromptsPanel
@onready var reply_sound: AudioStreamPlayer = %ReplySound
@onready var error_sound: AudioStreamPlayer = %ErrorSound
@onready var model_options_btn: OptionButton = %ModelOptionsBtn
@onready var temperature_slider: HSlider = %TemperatureSlider
@onready var temperature_override_checkbox: CheckBox = %TemperatureOverrideCheckbox
@onready var temperature_slider_container: HBoxContainer = %TemperatureSliderContainer
var _plugin:EditorPlugin
var _bot_name: String
var _assistant_settings: AIAssistantResource
var _last_quick_prompt: AIQuickPromptResource
var _code_selector: AssistantToolSelection
var _bot_answer_handler: AIAnswerHandler
var _llm: LLMInterface
var _conversation: AIConversation
func initialize(plugin:EditorPlugin, assistant_settings: AIAssistantResource, bot_name:String) -> void:
_plugin = plugin
_assistant_settings = assistant_settings
_bot_name = bot_name
_code_selector = AssistantToolSelection.new(plugin)
_bot_answer_handler = AIAnswerHandler.new(plugin, _code_selector)
_bot_answer_handler.bot_message_produced.connect(func(message): _add_to_chat(message, Caller.Bot) )
_bot_answer_handler.error_message_produced.connect(func(message): _add_to_chat(message, Caller.System) )
_conversation = AIConversation.new()
if _assistant_settings: # We need to check this, otherwise this is called when editing the plugin
load_api()
_conversation.set_system_message(_assistant_settings.ai_description)
await ready
temperature_slider.value = assistant_settings.custom_temperature
temperature_override_checkbox.button_pressed = assistant_settings.use_custom_temperature
_on_temperature_override_checkbox_toggled(temperature_override_checkbox.button_pressed)
bot_portrait.set_random()
reply_sound.pitch_scale = randf_range(0.7, 1.2)
for qp in _assistant_settings.quick_prompts:
var qp_button:= Button.new()
qp_button.text = qp.action_name
qp_button.icon = qp.icon
qp_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
qp_button.pressed.connect(func(): _on_qp_button_pressed(qp))
quick_prompts_panel.add_child(qp_button)
func load_api() -> void:
_llm = _plugin.new_llm_provider()
_llm.model = _assistant_settings.ai_model
_llm.override_temperature = _assistant_settings.use_custom_temperature
_llm.temperature = _assistant_settings.custom_temperature
func greet() -> void:
if _assistant_settings.quick_prompts.size() == 0:
_add_to_chat("This assistant type doesn't have Quick Prompts defined. Add them to the assistant's resource configuration to unlock some additional capabilities, like writing in the code editor.", Caller.System)
var greet_prompt := "Give a short greeting including just your name (which is \"%s\") and how can you help in a concise sentence." % _bot_name
_submit_prompt(greet_prompt)
func refresh_models(models: Array[String]) -> void:
model_options_btn.clear()
var selected_found := false
for model in models:
model_options_btn.add_item(model)
if model.contains(_assistant_settings.ai_model):
model_options_btn.select(model_options_btn.item_count - 1)
selected_found = true
if not selected_found:
model_options_btn.add_item(_assistant_settings.ai_model)
model_options_btn.select(model_options_btn.item_count - 1)
func _input(event: InputEvent) -> void:
if prompt_txt.has_focus() and event.is_pressed() and event is InputEventKey:
var e:InputEventKey = event
var is_enter_key := e.keycode == KEY_ENTER or e.keycode == KEY_KP_ENTER
var shift_pressed := Input.is_physical_key_pressed(KEY_SHIFT)
if shift_pressed and is_enter_key:
prompt_txt.insert_text_at_caret("\n")
else:
var ctrl_pressed = Input.is_physical_key_pressed(KEY_CTRL)
if not ctrl_pressed:
if not prompt_txt.text.is_empty() and is_enter_key:
if bot_portrait.is_thinking:
_abandon_request()
get_viewport().set_input_as_handled()
var prompt = _engineer_prompt(prompt_txt.text)
prompt_txt.text = ""
_add_to_chat(prompt, Caller.You)
_submit_prompt(prompt)
func _on_qp_button_pressed(qp: AIQuickPromptResource) -> void:
_last_quick_prompt = qp
var prompt = qp.action_prompt.replace("{CODE}", _code_selector.get_selection())
if prompt.contains("{CHAT}"):
prompt = prompt.replace("{CHAT}", prompt_txt.text)
prompt_txt.text = ""
_add_to_chat(prompt, Caller.You)
_submit_prompt(prompt, qp)
func _find_code_editor() -> TextEdit:
var script_editor := _plugin.get_editor_interface().get_script_editor().get_current_editor()
return script_editor.get_base_editor()
func _engineer_prompt(original:String) -> String:
if original.contains("{CODE}"):
var curr_code:String = _find_code_editor().get_selected_text()
var prompt:String = original.replace("{CODE}", curr_code)
return prompt
else:
return original
func _submit_prompt(prompt:String, quick_prompt:AIQuickPromptResource = null) -> void:
if bot_portrait.is_thinking:
_abandon_request()
_last_quick_prompt = quick_prompt
bot_portrait.is_thinking = true
_conversation.add_user_prompt(prompt)
var success := _llm.send_chat_request(http_request, _conversation.build())
if not success:
_add_to_chat("Something went wrong. Review the details in Godot's Output tab.", Caller.System)
func _abandon_request() -> void:
error_sound.play()
http_request.cancel_request()
bot_portrait.is_thinking = false
_add_to_chat("Abandoned previous request.", Caller.System)
_conversation.forget_last_prompt()
func _on_http_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
#print("HTTP response: Result: %d, Response Code: %d, Headers: %s, Body: %s" % [result, response_code, headers, body])
bot_portrait.is_thinking = false
if result == 0:
var text_answer = _llm.read_response(body)
if text_answer == LLMInterface.INVALID_RESPONSE:
error_sound.play()
push_error("Response: %s" % _llm.get_full_response(body))
_add_to_chat("An error occurred while processing your last request. Review the details in Godot's Output tab.", Caller.System)
else:
reply_sound.play()
_conversation.add_assistant_response(text_answer)
_bot_answer_handler.handle(text_answer, _last_quick_prompt)
else:
error_sound.play()
push_error("HTTP response: Result: %s, Response Code: %d, Headers: %s, Body: %s" % [result, response_code, headers, body])
_add_to_chat("An error occurred while communicating with the assistant. Review the details in Godot's Output tab.", Caller.System)
func _add_to_chat(text:String, caller:Caller) -> void:
var prefix:String
var suffix:String
match caller:
Caller.You:
prefix = "\n[color=FFFF00]> "
suffix = "[/color]\n"
Caller.Bot:
prefix = "\n[right][color=777777][b]%s[/b][/color]:\n" % _bot_name
var code_found := false
if text.contains("```gdscript"):
code_found = true
text = text.replace("```gdscript","[left][color=33AAFF]")
if text.contains("```glsl"):
code_found = true
text = text.replace("```glsl","[left][color=33AAFF]")
if code_found:
text = text.replace("```","[/color][/left]")
suffix = "[/right]\n"
Caller.System:
prefix = "\n[center][color=FF7700][ "
suffix = " ][/color][/center]\n"
output_window.text += "%s%s%s" % [prefix, text, suffix]
func _on_edit_history_pressed() -> void:
var history_editor:ChatHistoryEditor = CHAT_HISTORY_EDITOR.instantiate()
history_editor.initialize(_conversation)
add_child(history_editor)
history_editor.popup()
func _on_temperature_override_checkbox_toggled(toggled_on: bool) -> void:
temperature_slider_container.visible = toggled_on
_llm.override_temperature = toggled_on
func _on_model_options_btn_item_selected(index: int) -> void:
_llm.model = model_options_btn.text
func _on_temperature_slider_value_changed(value: float) -> void:
_llm.temperature = snappedf(temperature_slider.value, 0.001)

View file

@ -0,0 +1 @@
uid://r5gybxqvu0ye

View file

@ -0,0 +1,179 @@
[gd_scene load_steps=6 format=3 uid="uid://c5d12f133cpv7"]
[ext_resource type="Script" path="res://addons/ai_assistant_hub/ai_chat.gd" id="1_v0kvm"]
[ext_resource type="Texture2D" uid="uid://beq5nk70uk8v4" path="res://addons/ai_assistant_hub/graphics/icons/edit_chat_icon.png" id="2_8urns"]
[ext_resource type="PackedScene" uid="uid://dxr0f1xqsje6b" path="res://addons/ai_assistant_hub/bot_portrait.tscn" id="3_lcoap"]
[ext_resource type="AudioStream" uid="uid://dk1yumltykf6x" path="res://addons/ai_assistant_hub/sounds/ai_replied.wav" id="4_7y76u"]
[ext_resource type="AudioStream" uid="uid://b2lftlbs848c4" path="res://addons/ai_assistant_hub/sounds/ai_error_cancel.wav" id="5_mmsii"]
[node name="AIChat" type="Control"]
clip_contents = true
custom_minimum_size = Vector2(0, 180)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_v0kvm")
[node name="HTTPRequest" type="HTTPRequest" parent="."]
unique_name_in_owner = true
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ScrollContainer" type="ScrollContainer" parent="HBoxContainer"]
layout_mode = 2
horizontal_scroll_mode = 0
[node name="QuickPromptsPanel" type="VBoxContainer" parent="HBoxContainer/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="VSplitContainer" type="VSplitContainer" parent="HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 7
theme_override_constants/autohide = 0
[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer/VSplitContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="OutputWindow" type="RichTextLabel" parent="HBoxContainer/VBoxContainer/VSplitContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
focus_mode = 2
bbcode_enabled = true
scroll_following = true
selection_enabled = true
deselect_on_focus_loss_enabled = false
[node name="MarginContainer" type="Control" parent="HBoxContainer/VBoxContainer/VSplitContainer/HBoxContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
[node name="EditHistory" type="Button" parent="HBoxContainer/VBoxContainer/VSplitContainer/HBoxContainer/MarginContainer"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -48.0
offset_bottom = 48.0
grow_horizontal = 0
size_flags_vertical = 0
icon = ExtResource("2_8urns")
icon_alignment = 1
[node name="BotPortrait" parent="HBoxContainer/VBoxContainer/VSplitContainer/HBoxContainer/MarginContainer" instance=ExtResource("3_lcoap")]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -24.0
offset_top = -48.0
offset_right = 24.0
offset_bottom = 0.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="HBoxContainer/VBoxContainer/VSplitContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/VSplitContainer/HBoxContainer2"]
layout_mode = 2
theme_type_variation = &"HeaderLarge"
text = ">"
[node name="PromptTxt" type="TextEdit" parent="HBoxContainer/VBoxContainer/VSplitContainer/HBoxContainer2"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 40)
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Ask something, press ENTER to send.
(Tip: Use CTRL + ENTER or SHIFT + ENTER for adding a new line)"
wrap_mode = 1
[node name="HSeparator" type="HSeparator" parent="HBoxContainer/VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 4
[node name="TemperatureOverrideCheckbox" type="CheckBox" parent="HBoxContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Override model's behavior."
[node name="TemperatureSliderContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/HBoxContainer/TemperatureSliderContainer"]
layout_mode = 2
text = "Precise"
vertical_alignment = 1
[node name="TemperatureSlider" type="HSlider" parent="HBoxContainer/VBoxContainer/HBoxContainer/TemperatureSliderContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
max_value = 1.0
step = 0.001
ticks_on_borders = true
[node name="Label2" type="Label" parent="HBoxContainer/VBoxContainer/HBoxContainer/TemperatureSliderContainer"]
auto_translate_mode = 1
layout_mode = 2
text = "Creative"
vertical_alignment = 1
[node name="VSeparator" type="VSeparator" parent="HBoxContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
[node name="Label3" type="Label" parent="HBoxContainer/VBoxContainer/HBoxContainer"]
auto_translate_mode = 1
layout_mode = 2
text = "Model"
vertical_alignment = 1
[node name="ModelOptionsBtn" type="OptionButton" parent="HBoxContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
[node name="ReplySound" type="AudioStreamPlayer" parent="."]
unique_name_in_owner = true
stream = ExtResource("4_7y76u")
pitch_scale = 1.0108
[node name="ErrorSound" type="AudioStreamPlayer" parent="."]
unique_name_in_owner = true
stream = ExtResource("5_mmsii")
[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_completed"]
[connection signal="pressed" from="HBoxContainer/VBoxContainer/VSplitContainer/HBoxContainer/MarginContainer/EditHistory" to="." method="_on_edit_history_pressed"]
[connection signal="toggled" from="HBoxContainer/VBoxContainer/HBoxContainer/TemperatureOverrideCheckbox" to="." method="_on_temperature_override_checkbox_toggled"]
[connection signal="value_changed" from="HBoxContainer/VBoxContainer/HBoxContainer/TemperatureSliderContainer/TemperatureSlider" to="." method="_on_temperature_slider_value_changed"]
[connection signal="item_selected" from="HBoxContainer/VBoxContainer/HBoxContainer/ModelOptionsBtn" to="." method="_on_model_options_btn_item_selected"]

View file

@ -0,0 +1,57 @@
@tool
class_name AIConversation
var _chat_history:= []
var _system_msg: String
func set_system_message(message:String) -> void:
_system_msg = message
# If your models don't mark the code with ```gdscript, the plugin won't wort well,
# consider giving it an instruction like the one in the comment below, either in the
# _system_msg or as part of the bot initial request.
#
#_system_msg = "%s. Any code you write you should identify with the programming language, for example for GDScript you must use prefix \"```gdscript\" and suffix \"```\"." % message
#
func add_user_prompt(prompt:String) -> void:
_chat_history.append(
{
"role": "user",
"content": prompt
}
)
func add_assistant_response(response:String) -> void:
_chat_history.append(
{
"role": "assistant",
"content": response
}
)
func build() -> Array:
var messages := []
messages.append(
{
"role": "system",
"content": _system_msg
}
)
messages.append_array(_chat_history)
return messages
func forget_last_prompt() -> void:
_chat_history.pop_back()
func clone_chat() -> Array:
return _chat_history.duplicate(true)
func overwrite_chat(new_chat:Array) -> void:
_chat_history = new_chat

View file

@ -0,0 +1 @@
uid://fnqec17oj0rd

View file

@ -0,0 +1,31 @@
@tool
class_name AIHubPlugin
extends EditorPlugin
const CONFIG_BASE_URL:= "ai_assistant_hub/base_url"
const CONFIG_LLM_API:= "ai_assistant_hub/llm_api"
var _hub_dock:AIAssistantHub
func _enter_tree() -> void:
if ProjectSettings.get_setting(CONFIG_BASE_URL, "").is_empty():
ProjectSettings.set_setting(CONFIG_BASE_URL, "http://127.0.0.1:11434")
if ProjectSettings.get_setting(CONFIG_LLM_API, "").is_empty():
ProjectSettings.set_setting(CONFIG_LLM_API, "ollama_api")
_hub_dock = load("res://addons/ai_assistant_hub/ai_assistant_hub.tscn").instantiate()
_hub_dock.initialize(self)
add_control_to_bottom_panel(_hub_dock, "AI Hub")
#add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_UL, _hub_dock)
func _exit_tree() -> void:
remove_control_from_bottom_panel(_hub_dock)
#remove_control_from_docks(_hub_dock)
_hub_dock.queue_free()
## Load the API dinamically based on the script name given in project setting: ai_assistant_hub/llm_api
## By default this is equivalent to: return OllamaAPI.new()
func new_llm_provider() -> LLMInterface:
return load("res://addons/ai_assistant_hub/llm_apis/%s.gd" % ProjectSettings.get_setting(AIHubPlugin.CONFIG_LLM_API)).new()

View file

@ -0,0 +1 @@
uid://c2nhbu4ivwaja

View file

@ -0,0 +1,27 @@
class_name AIAssistantResource
extends Resource
## Name of the assistant type (e.g., "Writer", "Programmer").
@export var type_name: String
## Icon displayed in hub buttons and tabs for this assistant.
@export var type_icon: Texture2D
## The name of the AI model as listed in the available models section.
@export var ai_model: String
## Used to give the System message to the chat.
## This gives the overall direction on what the assistant should do.
@export_multiline var ai_description: String = "You are a useful Godot AI assistant."
## Models have a default temperature recommended for most use cases.
## When checking this, the value of the temperature will be dictated by the CustomTemperature property.
@export var use_custom_temperature: bool = false
## The temperature indicates to the models how much they can deviate from the most expected patterns, usually having low temperature returns more precise output, and high temperature more creative output.
## This value is ignored if UseCustomTemperature is false.
@export_range(0.0, 1.0) var custom_temperature := 0.5
## Quick Prompts available for a model are displayed in the chat window as buttons.
## These allow to create prompt templates, as well as read and write to the code editor.
@export var quick_prompts: Array[AIQuickPromptResource]

View file

@ -0,0 +1 @@
uid://cb6snexdvyill

View file

@ -0,0 +1,14 @@
[gd_resource type="Resource" script_class="AIAssistantResource" load_steps=3 format=3 uid="uid://c6eyb701hpdqd"]
[ext_resource type="Script" uid="uid://cb6snexdvyill" path="res://addons/ai_assistant_hub/assistants/ai_assistant_resource.gd" id="1_36d27"]
[ext_resource type="Script" uid="uid://gfjw8kfxgsnh" path="res://addons/ai_assistant_hub/quick_prompts/ai_quick_prompt_resource.gd" id="2_vs81c"]
[resource]
script = ExtResource("1_36d27")
type_name = ""
ai_model = "llama3.1:8b"
ai_description = "You are a useful Godot AI assistant."
use_custom_temperature = false
custom_temperature = 0.5
quick_prompts = Array[ExtResource("2_vs81c")]([])
metadata/_custom_type_script = ExtResource("1_36d27")

View file

@ -0,0 +1,59 @@
@tool
class_name BotPortrait
extends Control
const PORTRAITS_BASE := preload("res://addons/ai_assistant_hub/graphics/portraits/portraits_base.png")
const PORTRAIT_AMOUNT_X := 3
const PORTRAIT_AMOUNT_Y := 3
const SCALE := 3 # given the images are 16px but we are displaying them 48px, this is used to move the face when thinking
@onready var portrait_base: TextureRect = %PortraitBase
@onready var portrait_mouth: TextureRect = %PortraitMouth
@onready var portrait_eyes: TextureRect = %PortraitEyes
@onready var portrait_thinking: TextureRect = %PortraitThinking
var _think_tween:Tween
func set_random() -> void:
var tex_size := PORTRAITS_BASE.get_size()
var portrait_size := Vector2i(tex_size.x / PORTRAIT_AMOUNT_X, tex_size.y / PORTRAIT_AMOUNT_Y)
_select_random_region(portrait_base)
_select_random_region(portrait_mouth)
_select_random_region(portrait_eyes)
func _select_random_region(image:TextureRect) -> void:
var x_rand := randi_range(0, PORTRAIT_AMOUNT_X - 1)
var y_rand := randi_range(0, PORTRAIT_AMOUNT_Y - 1)
var base_atlas: AtlasTexture = image.texture.duplicate()
image.texture = base_atlas
base_atlas.region = Rect2(x_rand*16,y_rand*16,16,16)
var is_thinking:= false:
set(value):
is_thinking = value
if _think_tween != null and _think_tween.is_running():
_think_tween.stop()
portrait_thinking.visible = is_thinking
if is_thinking:
portrait_eyes.position.x = SCALE
portrait_eyes.position.y = -SCALE
portrait_mouth.position = portrait_eyes.position
_thinking_anim()
else:
portrait_eyes.position = Vector2.ZERO
portrait_mouth.position = Vector2.ZERO
self.rotation_degrees = 0
func _thinking_anim() -> void:
while is_thinking:
_think_tween = create_tween()
_think_tween.tween_property(self, "rotation_degrees", -12, 1)
_think_tween.tween_property(self, "rotation_degrees", 12, 1)
await _think_tween.finished
self.rotation_degrees = 0
var complete = create_tween()
complete.tween_property(self, "scale", Vector2(1.2, 1.2), 0.05)
complete.tween_property(self, "scale", Vector2(1, 1), 0.05)

View file

@ -0,0 +1 @@
uid://cnipuj0a7i4p

View file

@ -0,0 +1,71 @@
[gd_scene load_steps=9 format=3 uid="uid://dxr0f1xqsje6b"]
[ext_resource type="Texture2D" uid="uid://ba07lvip5fjtx" path="res://addons/ai_assistant_hub/graphics/portraits/portraits_base.png" id="1_6ygms"]
[ext_resource type="Script" path="res://addons/ai_assistant_hub/bot_portrait.gd" id="1_irxow"]
[ext_resource type="Texture2D" uid="uid://bqkxfvi24xl6c" path="res://addons/ai_assistant_hub/graphics/portraits/portraits_mouth.png" id="2_5emjf"]
[ext_resource type="Texture2D" uid="uid://bcfkhh3ljqe6g" path="res://addons/ai_assistant_hub/graphics/portraits/portraits_eyes.png" id="3_opry7"]
[ext_resource type="Texture2D" uid="uid://crtbbm01emvsf" path="res://addons/ai_assistant_hub/graphics/portraits/portrait_think_hand.png" id="4_yrvbr"]
[sub_resource type="AtlasTexture" id="AtlasTexture_684wr"]
atlas = ExtResource("1_6ygms")
region = Rect2(0, 0, 16, 16)
[sub_resource type="AtlasTexture" id="AtlasTexture_82r0e"]
atlas = ExtResource("2_5emjf")
region = Rect2(0, 0, 16, 16)
[sub_resource type="AtlasTexture" id="AtlasTexture_kykyr"]
atlas = ExtResource("3_opry7")
region = Rect2(0, 0, 16, 16)
[node name="BotPortrait" type="Control"]
auto_translate_mode = 1
layout_mode = 3
anchors_preset = 0
offset_right = 48.0
offset_bottom = 48.0
pivot_offset = Vector2(24, 48)
size_flags_horizontal = 0
size_flags_vertical = 0
script = ExtResource("1_irxow")
[node name="PortraitBase" type="TextureRect" parent="."]
unique_name_in_owner = true
auto_translate_mode = 1
texture_filter = 1
custom_minimum_size = Vector2(48, 48)
offset_right = 48.0
offset_bottom = 48.0
texture = SubResource("AtlasTexture_684wr")
expand_mode = 3
[node name="PortraitMouth" type="TextureRect" parent="."]
unique_name_in_owner = true
auto_translate_mode = 1
texture_filter = 1
custom_minimum_size = Vector2(48, 48)
offset_right = 48.0
offset_bottom = 48.0
texture = SubResource("AtlasTexture_82r0e")
expand_mode = 3
[node name="PortraitEyes" type="TextureRect" parent="."]
unique_name_in_owner = true
auto_translate_mode = 1
texture_filter = 1
custom_minimum_size = Vector2(48, 48)
offset_right = 48.0
offset_bottom = 48.0
texture = SubResource("AtlasTexture_kykyr")
expand_mode = 3
[node name="PortraitThinking" type="TextureRect" parent="."]
unique_name_in_owner = true
auto_translate_mode = 1
visible = false
texture_filter = 1
custom_minimum_size = Vector2(48, 48)
offset_right = 48.0
offset_bottom = 48.0
texture = ExtResource("4_yrvbr")
expand_mode = 3

View file

@ -0,0 +1,45 @@
@tool
class_name ChatHistoryEditor
extends Window
const CHAT_HISTORY_ENTRY = preload("res://addons/ai_assistant_hub/chat_history_entry.tscn")
@onready var entries_container: VBoxContainer = %EntriesContainer
@onready var background: Panel = %Background
var _converstaion:AIConversation
var _chat_history:Array
var _entries_map:Dictionary # ChatHistoryEntry, Dictionary - maps the UI entries to the array entries
func initialize(converstaion:AIConversation) -> void:
_converstaion = converstaion
_chat_history = _converstaion.clone_chat()
await ready
var back_color:= EditorInterface.get_base_control().get_theme_color("base_color", "Editor")
background.get_theme_stylebox("panel").bg_color = back_color
for section in _chat_history:
var entry:ChatHistoryEntry = CHAT_HISTORY_ENTRY.instantiate()
entry.initialize(section)
entries_container.add_child(entry)
_entries_map[entry] = section
entry.modified.connect(_on_entry_modified)
func _on_entry_modified(entry:ChatHistoryEntry) -> void:
var section:Dictionary = _entries_map[entry]
section["role"] = entry.get_role()
section["content"] = entry.get_content()
func _on_save_and_close_btn_pressed() -> void:
for entry in _entries_map.keys():
if entry.should_be_forgotten():
_chat_history.erase(_entries_map[entry])
_converstaion.overwrite_chat(_chat_history)
queue_free()
func _on_close_requested() -> void:
queue_free()

View file

@ -0,0 +1 @@
uid://b3rfwrjgf1y6l

View file

@ -0,0 +1,82 @@
[gd_scene load_steps=3 format=3 uid="uid://dn7xoyvs56oy6"]
[ext_resource type="Script" path="res://addons/ai_assistant_hub/chat_history_editor.gd" id="1_n0n5m"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dxmwm"]
bg_color = Color(0.21, 0.24, 0.29, 1)
[node name="ChatHistoryEditor" type="Window"]
title = "Chat hisotry editor"
initial_position = 2
size = Vector2i(800, 600)
exclusive = true
script = ExtResource("1_n0n5m")
[node name="Background" type="Panel" parent="."]
unique_name_in_owner = true
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_dxmwm")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/MarginContainer2"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="Label" type="Label" parent="VBoxContainer/MarginContainer2/VBoxContainer"]
layout_mode = 2
text = "Edit the chat history. This is useful when you want the assistant to forget about parts of the conversation, or simply alter parts of it."
autowrap_mode = 2
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/MarginContainer2/VBoxContainer"]
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/MarginContainer2/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
follow_focus = true
horizontal_scroll_mode = 0
[node name="EntriesContainer" type="VBoxContainer" parent="VBoxContainer/MarginContainer2/VBoxContainer/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
auto_translate_mode = 1
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
[node name="SaveAndCloseBtn" type="Button" parent="VBoxContainer/MarginContainer"]
layout_mode = 2
text = "Save and Close"
[connection signal="close_requested" from="." to="." method="_on_close_requested"]
[connection signal="pressed" from="VBoxContainer/MarginContainer/SaveAndCloseBtn" to="." method="_on_save_and_close_btn_pressed"]

View file

@ -0,0 +1,36 @@
@tool
class_name ChatHistoryEntry
extends HBoxContainer
signal modified(entry:ChatHistoryEntry)
@onready var role_option_list: OptionButton = %RoleOptionList
@onready var content_txt: TextEdit = %ContentTxt
@onready var forget_check_box: CheckBox = %ForgetCheckBox
func initialize(data:Dictionary) -> void:
await ready
if data["role"] == "assistant":
role_option_list.selected = 1
content_txt.text = data["content"]
func get_role() -> String:
return role_option_list.text
func get_content() -> String:
return content_txt.text
func should_be_forgotten() -> bool:
return forget_check_box.button_pressed
func _on_content_txt_text_changed() -> void:
modified.emit(self)
func _on_role_option_list_item_selected(index: int) -> void:
modified.emit(self)

View file

@ -0,0 +1 @@
uid://ddulo2aqtcguh

View file

@ -0,0 +1,39 @@
[gd_scene load_steps=2 format=3 uid="uid://b1mpesm8gt63t"]
[ext_resource type="Script" path="res://addons/ai_assistant_hub/chat_history_entry.gd" id="1_rl68l"]
[node name="ChatHistoryEntry" type="HBoxContainer"]
auto_translate_mode = 1
offset_right = 162.0
offset_bottom = 24.0
size_flags_horizontal = 3
script = ExtResource("1_rl68l")
[node name="RoleOptionList" type="OptionButton" parent="."]
unique_name_in_owner = true
auto_translate_mode = 1
layout_mode = 2
selected = 0
item_count = 2
popup/item_0/text = "user"
popup/item_0/id = 0
popup/item_1/text = "assistant"
popup/item_1/id = 1
[node name="ContentTxt" type="TextEdit" parent="."]
unique_name_in_owner = true
auto_translate_mode = 1
layout_mode = 2
size_flags_horizontal = 3
wrap_mode = 1
autowrap_mode = 2
scroll_fit_content_height = true
[node name="ForgetCheckBox" type="CheckBox" parent="."]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "This entry will be deleted on save."
text = "Forget"
[connection signal="item_selected" from="RoleOptionList" to="." method="_on_role_option_list_item_selected"]
[connection signal="text_changed" from="ContentTxt" to="." method="_on_content_txt_text_changed"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://beq5nk70uk8v4"
path="res://.godot/imported/edit_chat_icon.png-c36f527ba8826fe5de54ad4e7caea01d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ai_assistant_hub/graphics/icons/edit_chat_icon.png"
dest_files=["res://.godot/imported/edit_chat_icon.png-c36f527ba8826fe5de54ad4e7caea01d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://qjvopjqelt0m"
path="res://.godot/imported/linear_32_3dmsicons.png-16a4a9a4230f81b95397017f41e2e535.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ai_assistant_hub/graphics/icons/linear_32_3dmsicons.png"
dest_files=["res://.godot/imported/linear_32_3dmsicons.png-16a4a9a4230f81b95397017f41e2e535.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://r7njmupyi7w4"
path="res://.godot/imported/linear_32_flatmsicons.png-ba9a79e0c1219861dd36e6b90cf49912.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ai_assistant_hub/graphics/icons/linear_32_flatmsicons.png"
dest_files=["res://.godot/imported/linear_32_flatmsicons.png-ba9a79e0c1219861dd36e6b90cf49912.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://crtbbm01emvsf"
path="res://.godot/imported/portrait_think_hand.png-9633d5b149332319d9d62072ca39808d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ai_assistant_hub/graphics/portraits/portrait_think_hand.png"
dest_files=["res://.godot/imported/portrait_think_hand.png-9633d5b149332319d9d62072ca39808d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ba07lvip5fjtx"
path="res://.godot/imported/portraits_base.png-bb6417d0f01b1dd60bd0778d72539dc4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ai_assistant_hub/graphics/portraits/portraits_base.png"
dest_files=["res://.godot/imported/portraits_base.png-bb6417d0f01b1dd60bd0778d72539dc4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bcfkhh3ljqe6g"
path="res://.godot/imported/portraits_eyes.png-85a136d9d6222fce82a8393caf9c6ace.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ai_assistant_hub/graphics/portraits/portraits_eyes.png"
dest_files=["res://.godot/imported/portraits_eyes.png-85a136d9d6222fce82a8393caf9c6ace.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bqkxfvi24xl6c"
path="res://.godot/imported/portraits_mouth.png-ce83a7fccf995934a71e41916fa17ab7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ai_assistant_hub/graphics/portraits/portraits_mouth.png"
dest_files=["res://.godot/imported/portraits_mouth.png-ce83a7fccf995934a71e41916fa17ab7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -0,0 +1,35 @@
@tool
class_name LLMInterface
# The intention of this class is to serve as a base class for any LLM API
# to be implemented in this plugin. It is mainly to have a clear definition
# of what properties or functions should be used by other classes.
const INVALID_RESPONSE := "[INVALID_RESPONSE]"
var model: String
var override_temperature: bool
var temperature: float
func get_full_response(body:PackedByteArray) -> Dictionary:
var json := JSON.new()
json.parse(body.get_string_from_utf8())
return json.get_data()
## All methods below should be overriden by child classes, see for example OllamaAPI
func send_get_models_request(http_request:HTTPRequest) -> bool:
return false
func read_models_response(body:PackedByteArray) -> Array[String]:
return [INVALID_RESPONSE]
func send_chat_request(http_request:HTTPRequest, content:Array) -> bool:
return false
func read_response(body:PackedByteArray) -> String:
return INVALID_RESPONSE

View file

@ -0,0 +1 @@
uid://dcapnr8anqxr1

View file

@ -0,0 +1,67 @@
@tool
class_name OllamaAPI
extends LLMInterface
const HEADERS := ["Content-Type: application/json"]
func send_get_models_request(http_request:HTTPRequest) -> bool:
var url:String = "%s/api/tags" % ProjectSettings.get_setting(AIHubPlugin.CONFIG_BASE_URL)
#print("Calling: %s" % url)
var error = http_request.request(url, HEADERS, HTTPClient.METHOD_GET)
if error != OK:
push_error("Something when wrong with last AI API call: %s" % url)
return false
return true
func read_models_response(body:PackedByteArray) -> Array[String]:
var json := JSON.new()
json.parse(body.get_string_from_utf8())
var response := json.get_data()
if response.has("models"):
var model_names:Array[String] = []
for entry in response.models:
model_names.append(entry.model)
model_names.sort()
return model_names
else:
return [INVALID_RESPONSE]
func send_chat_request(http_request:HTTPRequest, content:Array) -> bool:
if model.is_empty():
push_error("ERROR: You need to set an AI model for this assistant type.")
return false
var body_dict := {
"messages": content,
"stream": false,
"model": model
}
if override_temperature:
body_dict["options"] = { "temperature": temperature }
var body := JSON.new().stringify(body_dict)
var url = _get_chat_url()
#print("calling %s with body: %s" % [url, body])
var error = http_request.request(url, HEADERS, HTTPClient.METHOD_POST, body)
if error != OK:
push_error("Something when wrong with last AI API call.\nURL: %s\nBody:\n%s" % [url, body])
return false
return true
func read_response(body) -> String:
var json := JSON.new()
json.parse(body.get_string_from_utf8())
var response := json.get_data()
if response.has("message"):
return response.message.content
else:
return LLMInterface.INVALID_RESPONSE
func _get_chat_url() -> String:
return "%s/api/chat" % ProjectSettings.get_setting(AIHubPlugin.CONFIG_BASE_URL)

View file

@ -0,0 +1 @@
uid://bqxt4y1tg8a1p

View file

@ -0,0 +1,50 @@
@tool
class_name NewAIAssistantButton
extends Button
signal chat_created(chat: AIChat, assistant_type:AIAssistantResource)
const AI_CHAT = preload("res://addons/ai_assistant_hub/ai_chat.tscn")
const NAMES: Array[String] = ["Ace", "Bean", "Boss", "Bubs", "Bugger", "Shushi", "Chicky", "Crash",
"Cub", "Daisy", "Dixie", "Doofus", "Doozy", "Dudedorf", "Fuzz", "Gabby", "Gizmo", "Goose", "Hiccup",
"Hobo", "Jinx", "Kix", "Lulu", "Munch", "Nuppy", "Ollie", "Ookie", "Pud", "Punchme", "Pup",
"Rascal", "Rusty", "Sausy", "Sparky", "Squirro", "Stubby", "Sugar", "Taco", "Tank", "Tater", "Ted",
"Titus", "Toady", "Tweedle", "Winky", "Zippy", "Luffy", "Zoro", "Chopper", "Usop", "Nami", "Robin",
"Juan", "Paco", "Pedro", "Goku", "Vegeta", "Trunks", "Piccolo", "Gohan", "Krillin", "Tenshinhan",
"Bulma", "Oolong", "Yamcha", "Pika", "Buu", "Freezer", "Cell", "L", "Light", "Ryuk", "Misa", "Near",
"Mello", "Rem", "Eren", "Mike", "Armin", "Hange", "Levi", "Eva", "Erwin", "Conny", "Mikasa",
"Naruto", "Sasuke", "Kakashi", "Tsunade", "Iruka", "Sakura", "Shikamaru", "Obito", "Itadori",
"Fushiguro", "Nobara", "Gojo", "Geto", "Sukuna", "Spike", "Jet", "Faye", "Ed", "Ein", "Julia",
"Jotaro", "Joestar", "Jolyne", "Jonathan", "Giorno", "Dio", "Polnareff", "Kakyoin", "Saitama",
"Genos", "Tenma", "Shinji", "Asuka", "Rei", "Misato", "Tanjiro", "Nezuko", "Inosuke", "Zenitsu" ]
static var available_names: Array[String]
var _plugin:EditorPlugin
var _data: AIAssistantResource
var _chat: AIChat
var _name: String
func initialize(plugin:EditorPlugin, assistant_resource: AIAssistantResource) -> void:
_plugin = plugin
_data = assistant_resource
text = _data.type_name
icon = _data.type_icon
if text.is_empty() and icon == null:
text = _data.resource_path.get_file().trim_suffix(".tres")
func _on_pressed() -> void:
if available_names == null or available_names.size() == 0:
available_names = NAMES.duplicate()
available_names.shuffle()
_name = available_names.pop_back()
_chat = AI_CHAT.instantiate()
_chat.initialize(_plugin, _data, _name)
if _data.type_icon == null:
_chat.name = "%s [%s]" % [text, _name]
else:
_chat.name = "%s" % [_name]
chat_created.emit(_chat, _data)

View file

@ -0,0 +1 @@
uid://n201ldaaytxq

View file

@ -0,0 +1,11 @@
[gd_scene load_steps=2 format=3 uid="uid://dgrssjr4dn6kx"]
[ext_resource type="Script" path="res://addons/ai_assistant_hub/new_ai_assistant_button.gd" id="1_i6vt6"]
[node name="NewAIAssistantButton" type="Button"]
auto_translate_mode = 1
custom_minimum_size = Vector2(48, 32)
text = "Coder"
script = ExtResource("1_i6vt6")
[connection signal="pressed" from="." to="." method="_on_pressed"]

View file

@ -0,0 +1,7 @@
[plugin]
name="AI Assistant Hub"
description=""
author="Flamx Games"
version="1.0.0"
script="ai_hub_plugin.gd"

View file

@ -0,0 +1,28 @@
class_name AIQuickPromptResource
extends Resource
enum ResponseTarget { Chat, CodeEditor, OnlyCodeToCodeEditor }
enum CodePlacement { BeforeSelection, AfterSelection, ReplaceSelection }
## This name will be used in the Quick Prompt button.
## Leave it blank for an icon-only display.
@export var action_name: String
## Tell the assistant what you want it to do.
## Use `{CODE}` to insert the code currently selected in the editor.
## Use `{CHAT}` to include the current content of the text prompt.
@export_multiline var action_prompt: String
## Optional icon for the button displayed in the chat window for this Quick Prompt.
@export var icon: Texture2D
## Indicates if the answer should be written in the chat or in the code editor.
@export var response_target: ResponseTarget
## Indicates in what part of the Code Editor you want to put the answer (ignored when not writing to the Code Editor).
@export var code_placement: CodePlacement
## Ensures the assistant's response is returned as a GDScript comment.
## If required, adds a # to each line and keeps lines around 80 characters long.
## This is useful to request the generation of inline documentation.
@export var format_response_as_comment: bool

View file

@ -0,0 +1 @@
uid://gfjw8kfxgsnh

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://b2lftlbs848c4"
path="res://.godot/imported/ai_error_cancel.wav-f939aafd44dd51184a77c7724bab89a2.sample"
[deps]
source_file="res://addons/ai_assistant_hub/sounds/ai_error_cancel.wav"
dest_files=["res://.godot/imported/ai_error_cancel.wav-f939aafd44dd51184a77c7724bab89a2.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=0

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dk1yumltykf6x"
path="res://.godot/imported/ai_replied.wav-ab3f7a4b5ea5f78d32ee362c9089f547.sample"
[deps]
source_file="res://addons/ai_assistant_hub/sounds/ai_replied.wav"
dest_files=["res://.godot/imported/ai_replied.wav-ab3f7a4b5ea5f78d32ee362c9089f547.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=0

View file

@ -0,0 +1,38 @@
@tool
class_name AssistantToolCodeWriter
var _plugin:EditorPlugin
var _code_selector:AssistantToolSelection
func _init(plugin:EditorPlugin, code_selector:AssistantToolSelection) -> void:
_plugin = plugin
_code_selector = code_selector
func write_to_code_editor(text_answer:String, code_placement:AIQuickPromptResource.CodePlacement) -> bool:
var select_success := _code_selector.back_to_selection()
if select_success:
var script_editor := _plugin.get_editor_interface().get_script_editor().get_current_editor()
var code_editor = script_editor.get_base_editor()
var start_line:int = code_editor.get_selection_from_line()
var end_line:int = code_editor.get_selection_to_line()
code_editor.set_caret_line(start_line)
match code_placement:
AIQuickPromptResource.CodePlacement.BeforeSelection:
code_editor.insert_line_at(start_line, text_answer)
AIQuickPromptResource.CodePlacement.AfterSelection:
if end_line == code_editor.get_line_count() - 1: #it is at the end of the editor
code_editor.text += "\n%s" % text_answer
else:
code_editor.insert_line_at(end_line + 1, text_answer)
AIQuickPromptResource.CodePlacement.ReplaceSelection:
code_editor.delete_selection()
text_answer = text_answer.trim_suffix("\n")
code_editor.insert_text_at_caret(text_answer)
_:
push_error("Unexpected Quick Prompt code placement value: %s" % code_placement)
code_editor.scroll_vertical = code_editor.get_scroll_pos_for_line(start_line) - 10
code_editor.select(start_line, 0, start_line, 0)
return true
return false

View file

@ -0,0 +1 @@
uid://c224dpt2c7cc

View file

@ -0,0 +1,113 @@
@tool
class_name AssistantToolSelection
var _plugin:EditorPlugin
var _code_editor:TextEdit
var _selected_script: Script
var _selected_code: String
var _selected_code_first_line: String
var _selected_code_last_line: String
var _selected_code_line_start: int
var _selected_code_line_start_column: int
var _selected_code_line_end: int
var _selected_code_line_end_column: int
func _init(plugin:EditorPlugin) -> void:
_plugin = plugin
func get_selection() -> String:
var script_editor:= _plugin.get_editor_interface().get_script_editor()
_code_editor = script_editor.get_current_editor().get_base_editor()
_selected_script = script_editor.get_current_script()
_selected_code = _code_editor.get_selected_text()
if _selected_code.strip_edges(true, true).length() == 0:
var curr_line = _code_editor.get_caret_line()
_code_editor.select(curr_line, 0, curr_line, line(curr_line).length())
_selected_code = _code_editor.get_selected_text().strip_edges(true, true)
if not _selected_code.is_empty():
#Make sure we don't start or end with empty lines, as that makes difficult to find the code again
var first_not_empty = line(first_line()).strip_edges(true, false)
while first_not_empty.is_empty() and first_line() + 1 <= last_line():
_code_editor.select(first_line() + 1, 0, last_line(), last_column())
first_not_empty = line(first_line()).strip_edges(true, false)
var last_not_empty = line(last_line()).strip_edges(false, true)
while last_not_empty.is_empty() and last_line() - 1 >= first_line():
_code_editor.select(first_line(), first_column(), last_line() - 1, line(last_line()-1).length())
last_not_empty = line(last_line()).strip_edges(false, true)
_selected_code = _code_editor.get_selected_text()
_selected_code_line_start = first_line()
_selected_code_line_start_column = first_column()
_selected_code_line_end = last_line()
_selected_code_line_end_column = last_column()
_selected_code_first_line = line(_selected_code_line_start)
_selected_code_last_line = line(_selected_code_line_end)
return _selected_code
func line(i:int) -> String:
return _code_editor.get_line(i)
func first_line() -> int:
return _code_editor.get_selection_from_line()
func first_column() -> int:
return _code_editor.get_selection_from_column()
func last_line() -> int:
return _code_editor.get_selection_to_line()
func last_column() -> int:
return _code_editor.get_selection_to_column()
func forget_selection() -> void:
_selected_script = null
# Attempts to select the original line range previously used and returns true on success.
func back_to_selection() -> bool:
if _selected_code.is_empty():
return false
#double check the script to edit is still open, if it's not open it
var editor_interface:EditorInterface = _plugin.get_editor_interface()
var curr_script:Script = editor_interface.get_script_editor().get_current_script()
if curr_script != _selected_script:
#print("The script for the original request was: %s" % _selected_script.resource_path)
#print("The script currently opened is: %s" % curr_script.resource_path)
print("Opening %s" % _selected_script.resource_path)
editor_interface.edit_script(_selected_script)
forget_selection()
var script_editor:= _plugin.get_editor_interface().get_script_editor()
var code_editor:TextEdit = script_editor.get_current_editor().get_base_editor()
var curr_selection: String = code_editor.get_selected_text()
if _selected_code != curr_selection:
print("The selection changed. Finding: %s" % _selected_code_first_line)
var search_start:Vector2i = code_editor.search(_selected_code_first_line, TextEdit.SearchFlags.SEARCH_MATCH_CASE, 0, 0)
if search_start.x == -1:
return false
else:
#print("First line found. Finding: %s" % _selected_code_last_line)
var original_line_diff = _selected_code_line_end - _selected_code_line_start
var search_end:Vector2i = code_editor.search(_selected_code_last_line, TextEdit.SearchFlags.SEARCH_MATCH_CASE, search_start.y + original_line_diff, 0)
if search_end.x == -1:
return false
else:
#print("Last line found.")
var line_diff = search_end.y - search_start.y
if original_line_diff == line_diff:
code_editor.select(search_start.y, search_start.x, search_end.y, _selected_code_line_end_column)
else:
return false
return true

View file

@ -0,0 +1 @@
uid://c1nam6n2xd5as

View file

@ -0,0 +1,7 @@
[plugin]
name="CodeTime"
description="This plugin adds a timer to the script editor so you can see how many hours you spend on coding."
author="Dexter"
version="0.1"
script="plugin.gd"

View file

@ -0,0 +1,53 @@
@tool
extends EditorPlugin
const TimerPanel = preload("res://addons/code_time/timer_panel.gd")
const TIMER_PANEL = preload("res://addons/code_time/timer_panel.tscn")
var timer_panel: TimerPanel
var menu_bar: Control
var item_list: ItemList
var base_editor: CodeEdit:
set(value):
if is_instance_valid(base_editor):
if base_editor.is_connected("text_changed", _on_base_editor_text_changed):
base_editor.disconnect("text_changed", _on_base_editor_text_changed)
base_editor = value
if is_instance_valid(base_editor):
base_editor.connect("text_changed", _on_base_editor_text_changed)
func _enter_tree() -> void:
main_screen_changed.connect(_on_main_screen_changed)
func _ready() -> void:
timer_panel = TIMER_PANEL.instantiate()
menu_bar = EditorInterface.get_script_editor().get_children()[0].get_children()[0]
menu_bar.add_child(timer_panel)
menu_bar.move_child(timer_panel, 7)
item_list = EditorInterface.get_script_editor().get_children()[0].get_children()[1].get_children()[0].get_children()[0].get_children()[1]
item_list.connect("item_selected", _on_item_list_selected)
func _exit_tree() -> void:
menu_bar.remove_child(timer_panel)
timer_panel = null
menu_bar = null
item_list = null
base_editor = null
func get_base_editor() -> CodeEdit:
return EditorInterface.get_script_editor().get_current_editor().get_base_editor()
func _on_main_screen_changed(screen_name: String) -> void:
if screen_name == "Script":
base_editor = get_base_editor()
func _on_base_editor_text_changed() -> void:
if timer_panel.timer.is_stopped():
timer_panel.timer.start()
if timer_panel.timer.paused:
timer_panel.timer.paused = false
func _on_item_list_selected(index: int) -> void:
base_editor = get_base_editor()

View file

@ -0,0 +1 @@
uid://cbpkqlo4v57ty

View file

@ -0,0 +1,19 @@
@tool
extends Control
var time: float = 0
@onready var time_label: Label = %TimeLabel
@onready var timer: Timer = %Timer
func _process(delta: float) -> void:
timer.time_left
func _on_timer_timeout() -> void:
time += 0.1
var _time: int = time
var h: int = _time / 3_600
var m: int = (_time % 3_600) / 60
var s: int = (_time % 3_600) % 60
time_label.text = "%02d:%02d:%02d" % [h, m, s]
timer.paused = true

View file

@ -0,0 +1 @@
uid://cuxddsdq6yapf

View file

@ -0,0 +1,25 @@
[gd_scene load_steps=2 format=3 uid="uid://bisjbnqhm460p"]
[ext_resource type="Script" path="res://addons/code_time/timer_panel.gd" id="1_ecqis"]
[node name="TimerPanel" type="PanelContainer"]
custom_minimum_size = Vector2(250, 0)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_ecqis")
[node name="TimeLabel" type="Label" parent="."]
unique_name_in_owner = true
layout_mode = 2
text = "00:00:00"
horizontal_alignment = 1
vertical_alignment = 1
[node name="Timer" type="Timer" parent="."]
unique_name_in_owner = true
wait_time = 0.1
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]

Binary file not shown.

View file

@ -0,0 +1,40 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://coiaqb8xwuaic"
path="res://.godot/imported/GravityBold8.ttf-e67aa3e4866d0a81519c9b866a8f924b.fontdata"
[deps]
source_file="res://addons/ridiculous_coding/GravityBold8.ttf"
dest_files=["res://.godot/imported/GravityBold8.ttf-e67aa3e4866d0a81519c9b866a8f924b.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[{
"chars": [],
"glyphs": [],
"name": "New Configuration",
"size": Vector2i(16, 0)
}]
language_support={}
script_support={}
opentype_features={}

View file

@ -0,0 +1,36 @@
@tool
extends Node2D
var destroy: bool = false
var last_key: String = ""
var pitch_increase: float = 0.0
var sound: bool = true
var blips: bool = true
@onready var audio_stream_player: AudioStreamPlayer = $AudioStreamPlayer
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var animated_player: AnimationPlayer = $AnimationPlayer
@onready var gpu_particle_2d: GPUParticles2D = $GPUParticles2D
@onready var timer: Timer = $Timer
@onready var label: Label = $Label
func _ready():
if sound:
audio_stream_player.pitch_scale = 1.0 + pitch_increase * 0.01
audio_stream_player.play()
if blips:
animated_sprite_2d.frame = 0
animated_sprite_2d.play("default")
animated_player.play("default")
gpu_particle_2d.emitting = true
timer.start()
label.text = last_key
label.modulate = Color(randf_range(0,2), randf_range(0,2), randf_range(0,2))
func _on_Timer_timeout():
if destroy:
queue_free()

View file

@ -0,0 +1 @@
uid://b1hwt8t7jrjq2

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d1tio2ceqgm7m"
path="res://.godot/imported/blip.png-7449238b2e7dcd337fbb9eec8c7b957e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ridiculous_coding/blip.png"
dest_files=["res://.godot/imported/blip.png-7449238b2e7dcd337fbb9eec8c7b957e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -0,0 +1,174 @@
[gd_scene load_steps=19 format=3 uid="uid://c0fhho0dp1svt"]
[ext_resource type="Script" path="res://addons/ridiculous_coding/blip.gd" id="1_tp8nq"]
[ext_resource type="Texture2D" uid="uid://d1tio2ceqgm7m" path="res://addons/ridiculous_coding/blip.png" id="2_kj7um"]
[ext_resource type="AudioStream" uid="uid://b2ood3lkcgpqb" path="res://addons/ridiculous_coding/blip.wav" id="3_xg6qd"]
[ext_resource type="FontFile" uid="uid://bvwnnnja1ur2i" path="res://addons/ridiculous_coding/font.tres" id="4_ullf3"]
[sub_resource type="AtlasTexture" id="AtlasTexture_hp4ed"]
atlas = ExtResource("2_kj7um")
region = Rect2(192, 0, 32, 32)
[sub_resource type="AtlasTexture" id="AtlasTexture_hcxxe"]
atlas = ExtResource("2_kj7um")
region = Rect2(160, 0, 32, 32)
[sub_resource type="AtlasTexture" id="AtlasTexture_3w7u8"]
atlas = ExtResource("2_kj7um")
region = Rect2(128, 0, 32, 32)
[sub_resource type="AtlasTexture" id="AtlasTexture_qo2pv"]
atlas = ExtResource("2_kj7um")
region = Rect2(96, 0, 32, 32)
[sub_resource type="AtlasTexture" id="AtlasTexture_55mlh"]
atlas = ExtResource("2_kj7um")
region = Rect2(64, 0, 32, 32)
[sub_resource type="AtlasTexture" id="AtlasTexture_3eube"]
atlas = ExtResource("2_kj7um")
region = Rect2(32, 0, 32, 32)
[sub_resource type="AtlasTexture" id="AtlasTexture_jwwsh"]
atlas = ExtResource("2_kj7um")
region = Rect2(0, 0, 32, 32)
[sub_resource type="AtlasTexture" id="AtlasTexture_ye4cv"]
atlas = ExtResource("2_kj7um")
region = Rect2(224, 0, 32, 32)
[sub_resource type="SpriteFrames" id="SpriteFrames_g4ki7"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_hp4ed")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_hcxxe")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_3w7u8")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_qo2pv")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_55mlh")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_3eube")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_jwwsh")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_ye4cv")
}],
"loop": false,
"name": &"default",
"speed": 24.0
}]
[sub_resource type="Animation" id="Animation_u2m4c"]
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("AnimatedSprite2D:scale")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.5),
"transitions": PackedFloat32Array(-2, 1),
"update": 0,
"values": [Vector2(1, 1), Vector2(5, 5)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Label:scale")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 1),
"transitions": PackedFloat32Array(-2, 1),
"update": 0,
"values": [Vector2(1, 1), Vector2(2, 2)]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Label:position")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 1),
"transitions": PackedFloat32Array(-2, 1),
"update": 0,
"values": [Vector2(-35, -32), Vector2(-35, -70)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_8epm6"]
_data = {
"default": SubResource("Animation_u2m4c")
}
[sub_resource type="Gradient" id="Gradient_mplh3"]
offsets = PackedFloat32Array(0, 0.350746, 1)
colors = PackedColorArray(0.160156, 0.783478, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_gu7qo"]
gradient = SubResource("Gradient_mplh3")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_5k50n"]
lifetime_randomness = 0.5
spread = 180.0
gravity = Vector3(0, 0, 0)
color_ramp = SubResource("GradientTexture2D_gu7qo")
[node name="Node2D" type="Node2D"]
texture_filter = 1
script = ExtResource("1_tp8nq")
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
scale = Vector2(5, 5)
sprite_frames = SubResource("SpriteFrames_g4ki7")
frame = 7
frame_progress = 1.0
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
stream = ExtResource("3_xg6qd")
volume_db = -12.0
autoplay = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
"": SubResource("AnimationLibrary_8epm6")
}
autoplay = "default"
[node name="GPUParticles2D" type="GPUParticles2D" parent="."]
emitting = false
amount = 50
process_material = SubResource("ParticleProcessMaterial_5k50n")
lifetime = 0.5
one_shot = true
explosiveness = 1.0
[node name="Timer" type="Timer" parent="."]
one_shot = true
[node name="Label" type="Label" parent="."]
modulate = Color(1.88557, 1.35563, 0.609976, 1)
texture_filter = 1
offset_left = -35.0
offset_top = -70.0
offset_right = 35.0
offset_bottom = -47.0
scale = Vector2(2, 2)
pivot_offset = Vector2(35, 8)
theme_override_fonts/font = ExtResource("4_ullf3")
theme_override_font_sizes/font_size = 16
uppercase = true
[connection signal="animation_finished" from="AnimatedSprite2D" to="." method="_on_AnimatedSprite1_animation_finished"]
[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"]

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://b2ood3lkcgpqb"
path="res://.godot/imported/blip.wav-b30aed86048a8d09a6b99458d730bd48.sample"
[deps]
source_file="res://addons/ridiculous_coding/blip.wav"
dest_files=["res://.godot/imported/blip.wav-b30aed86048a8d09a6b99458d730bd48.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=0

View file

@ -0,0 +1,29 @@
@tool
extends Node2D
var destroy = false
var last_key = ""
var sound = true
@onready var audio_stream_player: AudioStreamPlayer = $AudioStreamPlayer
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var timer: Timer = $Timer
@onready var label: Label = $Label
func _ready():
if sound:
audio_stream_player.play()
animated_sprite_2d.frame = 0
animated_sprite_2d.play("default")
animation_player.play("default")
timer.start()
label.text = last_key
label.modulate = Color(randf_range(0,2), randf_range(0,2), randf_range(0,2))
func _on_Timer_timeout():
if destroy:
queue_free()

View file

@ -0,0 +1 @@
uid://dnudpbbop16aj

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dl75e74oom1i3"
path="res://.godot/imported/boom.png-8824a5a1f762eb053b72081ff54ed1ba.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ridiculous_coding/boom.png"
dest_files=["res://.godot/imported/boom.png-8824a5a1f762eb053b72081ff54ed1ba.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -0,0 +1,135 @@
[gd_scene load_steps=15 format=3 uid="uid://c4rdv4wkukc5g"]
[ext_resource type="Texture2D" uid="uid://dl75e74oom1i3" path="res://addons/ridiculous_coding/boom.png" id="1"]
[ext_resource type="AudioStream" uid="uid://b6841f7osi4rh" path="res://addons/ridiculous_coding/boom.wav" id="2"]
[ext_resource type="Script" path="res://addons/ridiculous_coding/boom.gd" id="4"]
[ext_resource type="FontFile" uid="uid://bvwnnnja1ur2i" path="res://addons/ridiculous_coding/font.tres" id="5"]
[sub_resource type="AtlasTexture" id="AtlasTexture_g3t5t"]
atlas = ExtResource("1")
region = Rect2(0, 0, 128, 128)
[sub_resource type="AtlasTexture" id="AtlasTexture_4skxo"]
atlas = ExtResource("1")
region = Rect2(128, 0, 128, 128)
[sub_resource type="AtlasTexture" id="AtlasTexture_ccvbr"]
atlas = ExtResource("1")
region = Rect2(256, 0, 128, 128)
[sub_resource type="AtlasTexture" id="AtlasTexture_0ojec"]
atlas = ExtResource("1")
region = Rect2(384, 0, 128, 128)
[sub_resource type="AtlasTexture" id="AtlasTexture_1koxs"]
atlas = ExtResource("1")
region = Rect2(512, 0, 128, 128)
[sub_resource type="AtlasTexture" id="AtlasTexture_g5t7n"]
atlas = ExtResource("1")
region = Rect2(640, 0, 128, 128)
[sub_resource type="AtlasTexture" id="AtlasTexture_27xno"]
atlas = ExtResource("1")
region = Rect2(0, 128, 128, 128)
[sub_resource type="SpriteFrames" id="SpriteFrames_ld5tu"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_g3t5t")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_4skxo")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_ccvbr")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_0ojec")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_1koxs")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_g5t7n")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_27xno")
}],
"loop": false,
"name": &"default",
"speed": 24.0
}]
[sub_resource type="Animation" id="Animation_pxf6h"]
resource_name = "default"
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Label:scale")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 1),
"transitions": PackedFloat32Array(-2, 1),
"update": 0,
"values": [Vector2(1, 1), Vector2(2, 2)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Label:position")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 1),
"transitions": PackedFloat32Array(-2, 1),
"update": 0,
"values": [Vector2(-35, -32), Vector2(-35, -70)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_ocb2c"]
_data = {
"default": SubResource("Animation_pxf6h")
}
[node name="Node2D" type="Node2D"]
texture_filter = 1
script = ExtResource("4")
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
scale = Vector2(0.5, 0.5)
sprite_frames = SubResource("SpriteFrames_ld5tu")
frame = 6
frame_progress = 1.0
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
stream = ExtResource("2")
volume_db = -26.0
autoplay = true
[node name="Timer" type="Timer" parent="."]
one_shot = true
[node name="Label" type="Label" parent="."]
modulate = Color(0.852828, 1.64201, 1.90577, 1)
texture_filter = 1
offset_left = -35.0
offset_top = -70.0
offset_right = 35.0
offset_bottom = -47.0
scale = Vector2(2, 2)
pivot_offset = Vector2(35, 8)
theme_override_fonts/font = ExtResource("5")
theme_override_font_sizes/font_size = 16
uppercase = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
"": SubResource("AnimationLibrary_ocb2c")
}
autoplay = "default"
[connection signal="animation_finished" from="AnimatedSprite2D" to="." method="_on_AnimatedSprite_animation_finished"]
[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"]

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://b6841f7osi4rh"
path="res://.godot/imported/boom.wav-8be8413d6a593a69a88c5aabb324258d.sample"
[deps]
source_file="res://addons/ridiculous_coding/boom.wav"
dest_files=["res://.godot/imported/boom.wav-8be8413d6a593a69a88c5aabb324258d.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=0

View file

@ -0,0 +1,168 @@
@tool
extends Control
const BASE_XP: int = 50
const STATS_FILE: String = "user://ridiculous_xp.ini"
var explosions: bool = true
var blips: bool = true
var chars: bool = true
var shake: bool = true
var sound: bool = true
var fireworks: bool = true
var xp: int = 0
var xp_next: int = 2*BASE_XP
var level: int = 1
var stats: ConfigFile = ConfigFile.new()
@onready var explosion_checkbox: CheckButton = $VBoxContainer/GridContainer/explosionCheckbox
@onready var blip_checkbox: CheckButton = $VBoxContainer/GridContainer/blipCheckbox
@onready var chars_checkbox: CheckButton = $VBoxContainer/GridContainer/charsCheckbox
@onready var shake_checkbox: CheckButton = $VBoxContainer/GridContainer/shakeCheckbox
@onready var sound_checkbox: CheckButton = $VBoxContainer/GridContainer/soundCheckbox
@onready var fireworks_checkbox: CheckButton = $VBoxContainer/GridContainer/fireworksCheckbox
@onready var progress: TextureProgressBar = $VBoxContainer/XP/ProgressBar
@onready var sfx_fireworks: AudioStreamPlayer = $VBoxContainer/XP/ProgressBar/sfxFireworks
@onready var fireworks_timer: Timer = $VBoxContainer/XP/ProgressBar/fireworksTimer
@onready var fire_particles_one: GPUParticles2D = $VBoxContainer/XP/ProgressBar/fire1/GPUParticles2D
@onready var fire_particles_two: GPUParticles2D = $VBoxContainer/XP/ProgressBar/fire2/GPUParticles2D
@onready var xp_label: Label = $VBoxContainer/XP/HBoxContainer/xpLabel
@onready var level_label: Label = $VBoxContainer/XP/HBoxContainer/levelLabel
@onready var reset_button: Button = $VBoxContainer/CenterContainer/resetButton
func _ready():
reset_button.pressed.connect(on_reset_button_pressed)
load_checkbox_state()
connect_checkboxes()
fireworks_timer.timeout.connect(stop_fireworks)
load_experience_progress()
update_progress()
stop_fireworks()
func load_experience_progress():
if stats.load(STATS_FILE) == OK:
level = stats.get_value("xp", "level", 1)
xp = stats.get_value("xp", "xp", 0)
else:
level = 1
xp = 0
xp_next = 2*BASE_XP
progress.max_value = xp_next
for i in range(2,level+1):
xp_next += round(BASE_XP * i / 10.0) * 10
progress.max_value = round(BASE_XP * level / 10.0) * 10
progress.value = xp - (xp_next - progress.max_value)
func save_experioence_progress():
stats.set_value("xp", "level", level)
stats.set_value("xp", "xp", xp)
stats.save(STATS_FILE)
func _on_typing():
xp += 1
progress.value += 1
if progress.value >= progress.max_value:
level += 1
xp_next = xp + round(BASE_XP * level / 10.0) * 10
progress.value = 0
progress.max_value = xp_next - xp
if fireworks:
start_fireworks()
save_experioence_progress()
update_progress()
func start_fireworks():
sfx_fireworks.play()
fireworks_timer.start()
fire_particles_one.emitting = true
fire_particles_two.emitting = true
func stop_fireworks():
fire_particles_one.emitting = false
fire_particles_two.emitting = false
func update_progress():
xp_label.text = "XP: %d / %d" % [ xp, xp_next ]
level_label.text = "Level: %d" % level
func connect_checkboxes():
explosion_checkbox.toggled.connect(func(toggled):
explosions = toggled
save_checkbox_state()
)
blip_checkbox.toggled.connect(func(toggled):
blips = toggled
save_checkbox_state()
)
chars_checkbox.toggled.connect(func(toggled):
chars = toggled
save_checkbox_state()
)
shake_checkbox.toggled.connect(func(toggled):
shake = toggled
save_checkbox_state()
)
sound_checkbox.toggled.connect(func(toggled):
sound = toggled
save_checkbox_state()
)
fireworks_checkbox.toggled.connect(func(toggled):
fireworks = toggled
save_checkbox_state()
)
func save_checkbox_state():
stats.set_value("settings", "explosion", explosions)
stats.set_value("settings", "blips", blips)
stats.set_value("settings", "chars", chars)
stats.set_value("settings", "shake", shake)
stats.set_value("settings", "sound", sound)
stats.set_value("settings", "fireworks", fireworks)
stats.save(STATS_FILE)
func load_checkbox_state():
if stats.load(STATS_FILE) == OK:
explosions = stats.get_value("settings", "explosion", true)
blips = stats.get_value("settings", "blips", true)
chars = stats.get_value("settings", "chars", true)
shake = stats.get_value("settings", "shake", true)
sound = stats.get_value("settings", "sound", true)
fireworks = stats.get_value("settings", "fireworks", true)
explosion_checkbox.set_pressed_no_signal(explosions)
blip_checkbox.set_pressed_no_signal(blips)
chars_checkbox.set_pressed_no_signal(chars)
shake_checkbox.set_pressed_no_signal(shake)
sound_checkbox.set_pressed_no_signal(sound)
fireworks_checkbox.set_pressed_no_signal(fireworks)
func on_reset_button_pressed():
level = 1
xp = 0
xp_next = 2*BASE_XP
progress.value = 0
progress.max_value = xp_next
update_progress()

View file

@ -0,0 +1 @@
uid://yqhhxh7vjfs

View file

@ -0,0 +1,149 @@
[gd_scene load_steps=8 format=3 uid="uid://b76vnt4rv4p0q"]
[ext_resource type="Script" path="res://addons/ridiculous_coding/dock.gd" id="1_bwupq"]
[ext_resource type="Texture2D" uid="uid://c3ltnrdrb2qmg" path="res://addons/ridiculous_coding/under.png" id="3_vdb3k"]
[ext_resource type="AudioStream" uid="uid://g6lh6kjt0ynq" path="res://addons/ridiculous_coding/fireworks.wav" id="4_1o4lv"]
[ext_resource type="Texture2D" uid="uid://dmvuvaqf5uhwi" path="res://addons/ridiculous_coding/progress.png" id="4_y2kl4"]
[sub_resource type="Gradient" id="Gradient_v1eyn"]
offsets = PackedFloat32Array(0, 0.419689, 0.715026, 1)
colors = PackedColorArray(1, 1, 1, 1, 0.979167, 1, 0.333333, 1, 1, 0, 0, 1, 1, 1, 1, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_6lmfn"]
gradient = SubResource("Gradient_v1eyn")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_x4b51"]
lifetime_randomness = 0.29
spread = 20.0
gravity = Vector3(0, 300, 0)
initial_velocity_min = 400.0
initial_velocity_max = 400.0
scale_min = 5.0
scale_max = 6.0
color_ramp = SubResource("GradientTexture2D_6lmfn")
[node name="Ridiculous Coding Dock" type="Control"]
custom_minimum_size = Vector2(300, 175)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_bwupq")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="XP" type="VBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/XP"]
layout_mode = 2
[node name="xpLabel" type="Label" parent="VBoxContainer/XP/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "XP: 201 / 350"
[node name="levelLabel" type="Label" parent="VBoxContainer/XP/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Level: 3"
horizontal_alignment = 2
[node name="ProgressBar" type="TextureProgressBar" parent="VBoxContainer/XP"]
custom_minimum_size = Vector2(0, 10)
layout_mode = 2
max_value = 150.0
value = 1.0
nine_patch_stretch = true
texture_under = ExtResource("3_vdb3k")
texture_progress = ExtResource("4_y2kl4")
[node name="fire1" type="Control" parent="VBoxContainer/XP/ProgressBar"]
layout_mode = 1
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="GPUParticles2D" type="GPUParticles2D" parent="VBoxContainer/XP/ProgressBar/fire1"]
rotation = -0.785397
emitting = false
amount = 200
process_material = SubResource("ParticleProcessMaterial_x4b51")
[node name="fire2" type="Control" parent="VBoxContainer/XP/ProgressBar"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_top = 8.0
offset_right = 40.0
offset_bottom = 48.0
grow_horizontal = 0
[node name="GPUParticles2D" type="GPUParticles2D" parent="VBoxContainer/XP/ProgressBar/fire2"]
rotation = -2.35619
emitting = false
amount = 200
process_material = SubResource("ParticleProcessMaterial_x4b51")
[node name="fireworksTimer" type="Timer" parent="VBoxContainer/XP/ProgressBar"]
wait_time = 3.0
[node name="sfxFireworks" type="AudioStreamPlayer" parent="VBoxContainer/XP/ProgressBar"]
stream = ExtResource("4_1o4lv")
volume_db = -12.0
[node name="GridContainer" type="GridContainer" parent="VBoxContainer"]
layout_mode = 2
columns = 2
[node name="explosionCheckbox" type="CheckButton" parent="VBoxContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 11
button_pressed = true
text = "Explosions"
[node name="shakeCheckbox" type="CheckButton" parent="VBoxContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 11
button_pressed = true
text = "Shake"
[node name="blipCheckbox" type="CheckButton" parent="VBoxContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 11
button_pressed = true
text = "Blips"
[node name="charsCheckbox" type="CheckButton" parent="VBoxContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 11
button_pressed = true
text = "Keys"
[node name="soundCheckbox" type="CheckButton" parent="VBoxContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 11
button_pressed = true
text = "Sound"
[node name="fireworksCheckbox" type="CheckButton" parent="VBoxContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 11
button_pressed = true
text = "Fireworks"
[node name="CenterContainer" type="CenterContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="resetButton" type="Button" parent="VBoxContainer/CenterContainer"]
layout_mode = 2
text = "Reset"

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dyi5lstxrfkdt"
path="res://.godot/imported/fireworks.wav-f51025571a3b9e32e6113159ed26c3be.sample"
[deps]
source_file="res://addons/ridiculous_coding/fireworks.wav"
dest_files=["res://.godot/imported/fireworks.wav-f51025571a3b9e32e6113159ed26c3be.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=0

View file

@ -0,0 +1,42 @@
[gd_resource type="FontFile" load_steps=2 format=3 uid="uid://bvwnnnja1ur2i"]
[ext_resource type="FontFile" uid="uid://coiaqb8xwuaic" path="res://addons/ridiculous_coding/GravityBold8.ttf" id="1"]
[resource]
fallbacks = Array[Font]([ExtResource("1")])
cache/0/16/0/ascent = 0.0
cache/0/16/0/descent = 0.0
cache/0/16/0/underline_position = 0.0
cache/0/16/0/underline_thickness = 0.0
cache/0/16/0/scale = 1.0
cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0)
cache/0/16/0/kerning_overrides/50/0 = Vector2(0, 0)
cache/0/16/0/kerning_overrides/28/0 = Vector2(0, 0)
cache/0/16/0/kerning_overrides/14/0 = Vector2(0, 0)
cache/0/50/0/ascent = 0.0
cache/0/50/0/descent = 0.0
cache/0/50/0/underline_position = 0.0
cache/0/50/0/underline_thickness = 0.0
cache/0/50/0/scale = 1.0
cache/0/50/0/kerning_overrides/16/0 = Vector2(0, 0)
cache/0/50/0/kerning_overrides/50/0 = Vector2(0, 0)
cache/0/50/0/kerning_overrides/28/0 = Vector2(0, 0)
cache/0/50/0/kerning_overrides/14/0 = Vector2(0, 0)
cache/0/28/0/ascent = 0.0
cache/0/28/0/descent = 0.0
cache/0/28/0/underline_position = 0.0
cache/0/28/0/underline_thickness = 0.0
cache/0/28/0/scale = 1.0
cache/0/28/0/kerning_overrides/16/0 = Vector2(0, 0)
cache/0/28/0/kerning_overrides/50/0 = Vector2(0, 0)
cache/0/28/0/kerning_overrides/28/0 = Vector2(0, 0)
cache/0/28/0/kerning_overrides/14/0 = Vector2(0, 0)
cache/0/14/0/ascent = 0.0
cache/0/14/0/descent = 0.0
cache/0/14/0/underline_position = 0.0
cache/0/14/0/underline_thickness = 0.0
cache/0/14/0/scale = 1.0
cache/0/14/0/kerning_overrides/16/0 = Vector2(0, 0)
cache/0/14/0/kerning_overrides/50/0 = Vector2(0, 0)
cache/0/14/0/kerning_overrides/28/0 = Vector2(0, 0)
cache/0/14/0/kerning_overrides/14/0 = Vector2(0, 0)

View file

@ -0,0 +1,24 @@
@tool
extends Node2D
var destroy = false
var blips = true
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var timer: Timer = $Timer
func _ready():
if blips:
animation_player.stop()
animation_player.play("default")
animated_sprite_2d.frame = 0
animated_sprite_2d.play("default")
timer.start()
func _on_Timer_timeout():
if destroy:
queue_free()

View file

@ -0,0 +1 @@
uid://dulvfmb6n6ot8

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b1vn1823wqae2"
path="res://.godot/imported/newline.png-03ec198c1b51eb116216995dcb893beb.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ridiculous_coding/newline.png"
dest_files=["res://.godot/imported/newline.png-03ec198c1b51eb116216995dcb893beb.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -0,0 +1,89 @@
[gd_scene load_steps=11 format=3 uid="uid://wd4tkg0uxd18"]
[ext_resource type="Script" path="res://addons/ridiculous_coding/newline.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://b1vn1823wqae2" path="res://addons/ridiculous_coding/newline.png" id="2"]
[sub_resource type="AtlasTexture" id="1"]
atlas = ExtResource("2")
region = Rect2(0, 0, 64, 64)
[sub_resource type="AtlasTexture" id="2"]
atlas = ExtResource("2")
region = Rect2(64, 0, 64, 64)
[sub_resource type="AtlasTexture" id="3"]
atlas = ExtResource("2")
region = Rect2(128, 0, 64, 64)
[sub_resource type="AtlasTexture" id="4"]
atlas = ExtResource("2")
region = Rect2(192, 0, 64, 64)
[sub_resource type="AtlasTexture" id="5"]
atlas = ExtResource("2")
region = Rect2(256, 0, 64, 64)
[sub_resource type="SpriteFrames" id="6"]
animations = [{
"frames": [{
"duration": 1.0,
"texture": SubResource("1")
}, {
"duration": 1.0,
"texture": SubResource("2")
}, {
"duration": 1.0,
"texture": SubResource("3")
}, {
"duration": 1.0,
"texture": SubResource("4")
}, {
"duration": 1.0,
"texture": SubResource("5")
}],
"loop": true,
"name": &"default",
"speed": 12.0
}]
[sub_resource type="Animation" id="7"]
resource_name = "default"
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("AnimatedSprite2D:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1),
"transitions": PackedFloat32Array(0.233258, 1),
"update": 0,
"values": [Vector2(-200, 0), Vector2(-50, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_gwr1r"]
_data = {
"default": SubResource("7")
}
[node name="Node2D" type="Node2D"]
texture_filter = 1
script = ExtResource("1")
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
position = Vector2(-50, 0)
rotation = -1.57079
scale = Vector2(2, 2)
sprite_frames = SubResource("6")
frame_progress = 0.432846
[node name="Timer" type="Timer" parent="."]
one_shot = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
"": SubResource("AnimationLibrary_gwr1r")
}
autoplay = "default"
[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"]

View file

@ -0,0 +1,8 @@
[plugin]
name="Ridiculous Coding - Godot 4"
description="Ridiculous screen-shakey coding inspired by Textreme https://ash-k.itch.io/textreme-2"
author="John Watson"
version="2021.11.28"
script="plugin.gd"
constributors="Jeferson 'Shin' Leite Borges"

View file

@ -0,0 +1,170 @@
@tool
extends EditorPlugin
signal typing
# Scenes preloaded
const Boom: PackedScene = preload("res://addons/ridiculous_coding/boom.tscn")
const Blip: PackedScene = preload("res://addons/ridiculous_coding/blip.tscn")
const Newline: PackedScene = preload("res://addons/ridiculous_coding/newline.tscn")
const Dock: PackedScene = preload("res://addons/ridiculous_coding/dock.tscn")
# Inner Variables
const PITCH_DECREMENT := 2.0
var shake: float = 0.0
var shake_intensity:float = 0.0
var timer: float = 0.0
var last_key: String = ""
var pitch_increase: float = 0.0
var editors = {}
var dock
func _enter_tree():
var editor: EditorInterface = get_editor_interface()
var script_editor: ScriptEditor = editor.get_script_editor()
script_editor.editor_script_changed.connect(editor_script_changed)
# Add the main panel
dock = Dock.instantiate()
typing.connect(Callable(dock,"_on_typing"))
add_control_to_dock(DOCK_SLOT_RIGHT_BL, dock)
func _exit_tree():
if dock:
remove_control_from_docks(dock)
dock.free()
func get_all_text_editors(parent : Node):
for child in parent.get_children():
if child.get_child_count():
get_all_text_editors(child)
if child is TextEdit:
editors[child] = {
"text": child.text,
"line": child.get_caret_line()
}
if child.caret_changed.is_connected(caret_changed):
child.caret_changed.disconnect(caret_changed)
child.caret_changed.connect(caret_changed.bind(child))
if child.text_changed.is_connected(text_changed):
child.text_changed.disconnect(text_changed)
child.text_changed.connect(text_changed.bind(child))
if child.gui_input.is_connected(gui_input):
child.gui_input.disconnect(gui_input)
child.gui_input.connect(gui_input)
func gui_input(event):
# Get last key typed
if event is InputEventKey and event.pressed:
event = event as InputEventKey
last_key = OS.get_keycode_string(event.get_keycode_with_modifiers())
func editor_script_changed(script):
var editor = get_editor_interface()
var script_editor = editor.get_script_editor()
editors.clear()
get_all_text_editors(script_editor)
func _process(delta):
var editor = get_editor_interface()
if shake > 0:
shake -= delta
editor.get_base_control().position = Vector2(randf_range(-shake_intensity,shake_intensity), randf_range(-shake_intensity,shake_intensity))
else:
editor.get_base_control().position = Vector2.ZERO
timer += delta
if (pitch_increase > 0.0):
pitch_increase -= delta * PITCH_DECREMENT
func shake_screen(duration, intensity):
if shake > 0:
return
shake = duration
shake_intensity = intensity
func caret_changed(textedit):
var editor = get_editor_interface()
if not editors.has(textedit):
# For some reason the editor instances all change
# when the file is saved so you need to reload them
editors.clear()
get_all_text_editors(editor.get_script_editor())
editors[textedit]["line"] = textedit.get_caret_line()
func text_changed(textedit : TextEdit):
var line_height = textedit.get_line_height()
var pos = textedit.get_caret_draw_pos() + Vector2(0,-line_height/2.0)
emit_signal("typing")
if editors.has(textedit):
# Deleting
if timer > 0.1 and len(textedit.text) < len(editors[textedit]["text"]):
timer = 0.0
if dock.explosions:
# Draw the thing
var thing = Boom.instantiate()
thing.position = pos
thing.destroy = true
if dock.chars: thing.last_key = last_key
thing.sound = dock.sound
textedit.add_child(thing)
if dock.shake:
# Shake
shake_screen(0.2, 10)
# Typing
if timer > 0.02 and len(textedit.text) >= len(editors[textedit]["text"]):
timer = 0.0
# Draw the thing
var thing = Blip.instantiate()
thing.pitch_increase = pitch_increase
pitch_increase += 1.0
thing.position = pos
thing.destroy = true
thing.blips = dock.blips
if dock.chars: thing.last_key = last_key
thing.sound = dock.sound
textedit.add_child(thing)
if dock.shake:
# Shake
shake_screen(0.05, 5)
# Newline
if textedit.get_caret_line() != editors[textedit]["line"]:
# Draw the thing
var thing = Newline.instantiate()
thing.position = pos
thing.destroy = true
thing.blips = dock.blips
textedit.add_child(thing)
if dock.shake:
# Shake
shake_screen(0.05, 5)
editors[textedit]["text"] = textedit.text
editors[textedit]["line"] = textedit.get_caret_line()

View file

@ -0,0 +1 @@
uid://dj0kkddglgu2g

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dqpxh1bccjaae"
path="res://.godot/imported/progress.png-7f475b7e84ff0c0c87db61c1f97b4978.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ridiculous_coding/progress.png"
dest_files=["res://.godot/imported/progress.png-7f475b7e84ff0c0c87db61c1f97b4978.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dgda8elabipl5"
path="res://.godot/imported/under.png-b737211ee1ab1b3c04a4062a02353851.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/ridiculous_coding/under.png"
dest_files=["res://.godot/imported/under.png-b737211ee1ab1b3c04a4062a02353851.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

90
assets/Blower.svg Normal file
View file

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1241 1749" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.47203,0,0,1.42192,-223.192,-261.667)">
<path d="M597.633,245.99C600.084,247.779 600.671,251.293 598.943,253.83C597.214,256.368 593.821,256.976 591.369,255.186C556.951,230.063 516.214,239.944 481.889,266.555C429.219,307.388 390.917,387.746 412.306,448.622C435.426,514.425 498.846,534.903 559.102,524.805C619.661,514.655 677.552,473.764 688.86,415.342C702.486,344.941 691.872,302.222 672.506,276.098C643.592,237.095 595.919,235.542 585.008,232.975C582.083,232.287 580.248,229.27 580.913,226.242C581.577,223.214 584.492,221.314 587.417,222.002C599.153,224.764 650.029,227.295 681.129,269.247C701.773,297.094 714.042,342.508 699.517,417.553C687.376,480.284 625.866,525.014 560.839,535.912C495.508,546.861 427.159,523.813 402.091,452.468C379.243,387.438 419.109,301.169 475.374,257.548C513.672,227.856 559.231,217.958 597.633,245.99Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.1356,0,0,1.4106,-18.2261,-239.045)">
<path d="M500.823,564.11C484.906,619.818 538.157,1189.49 530.512,1400.73C581.602,1389.38 581.735,1398.1 587.949,1403.69C583.629,1209.26 613.977,1208.64 582.999,1016.58C557.866,860.748 576.743,777.313 568.148,571.04" style="fill:rgb(114,110,212);"/>
<path d="M580.668,1391.1C577.914,1209.55 606.252,1204.79 576.013,1017.31C550.849,861.289 569.712,777.753 561.107,571.23C560.977,568.102 564.026,565.477 567.912,565.372C571.798,565.267 575.058,567.722 575.189,570.85C583.773,776.872 564.882,860.207 589.985,1015.84C621.014,1208.22 590.665,1208.84 594.993,1403.59C595.046,1405.96 593.263,1408.11 590.522,1408.97C587.781,1409.84 584.661,1409.24 582.699,1407.48C581.587,1406.48 580.638,1405.39 579.614,1404.32C578.814,1403.48 577.973,1402.61 576.51,1402C574.13,1401 570.576,1400.65 564.749,1400.96C557.371,1401.36 547.081,1402.93 532.386,1406.19C530.231,1406.67 527.921,1406.3 526.163,1405.19C524.404,1404.08 523.405,1402.36 523.47,1400.56C528.819,1252.78 504.331,929.47 494.622,729.685C490.421,643.24 489.139,579.702 493.957,562.838C494.829,559.788 498.613,557.881 502.402,558.583C506.191,559.284 508.56,562.33 507.688,565.381C502.956,581.943 504.575,644.343 508.701,729.241C518.259,925.919 542.095,1242.17 537.797,1393.34C561.915,1388.61 573.897,1389.1 580.668,1391.1Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M434.193,287.16C420.301,259.376 414.396,251.422 443.001,238.002C450.345,254.85 448.441,255.1 459.766,268.69" style="fill:rgb(59,72,88);"/>
<path d="M439.019,284.574C440.398,287.331 439.355,290.729 436.691,292.156C434.027,293.584 430.745,292.504 429.366,289.746C424.154,279.322 420.078,271.548 418.313,265.251C416.787,259.805 416.821,255.196 418.522,250.948C420.959,244.859 427.457,239.118 440.758,232.878C442.072,232.261 443.57,232.211 444.92,232.738C446.27,233.265 447.362,234.326 447.955,235.688C454.963,251.765 453.074,252.046 463.881,265.015C465.84,267.366 465.587,270.922 463.316,272.95C461.045,274.979 457.61,274.716 455.651,272.365C445.123,259.732 445.471,258.155 440.284,245.586C437.354,247.135 434.947,248.62 433.016,250.111C430.8,251.821 429.295,253.426 428.563,255.255C427.464,258.002 428.231,261.005 429.639,264.815C431.596,270.112 434.968,276.471 439.019,284.574Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M487.443,252.107C472.383,240.215 475.805,238.42 468.291,220.385C483.282,215.888 482.508,216.845 496.52,210.007C497.529,220.097 498.538,230.186 499.547,240.275" style="fill:rgb(59,72,88);"/>
<path d="M490.739,247.633C493.124,249.517 493.583,253.05 491.764,255.519C489.945,257.988 486.532,258.463 484.147,256.58C467.946,243.787 471.384,242.015 463.3,222.613C462.683,221.131 462.71,219.445 463.375,217.985C464.041,216.524 465.28,215.43 466.778,214.981C481.339,210.613 480.593,211.56 494.203,204.918C495.797,204.141 497.656,204.211 499.19,205.107C500.725,206.003 501.745,207.614 501.926,209.428L504.953,239.696C505.262,242.785 503.09,245.552 500.107,245.872C497.123,246.191 494.45,243.943 494.141,240.855C494.141,240.855 491.896,218.405 491.896,218.405C485.516,221.325 483.087,221.918 475.573,224.091C479.626,235.474 478.676,238.108 490.739,247.633Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M522.279,230.33C524.231,208.852 523.833,209.77 525.12,203.336C528.079,188.542 535.475,205.156 556.798,201.652C555.017,206.758 555.316,212.438 553.535,217.543C553.317,218.169 553.535,217.543 549.273,224.647" style="fill:rgb(59,72,88);"/>
<path d="M548.525,215.333C549.333,212.897 549.691,210.337 550.043,207.775C544.056,207.664 539.278,206.215 535.453,204.794C533.923,204.225 531.99,203.54 530.795,203.12C530.665,203.545 530.532,204.027 530.442,204.477C529.185,210.764 529.598,209.869 527.69,230.857C527.409,233.949 524.756,236.223 521.77,235.932C518.783,235.641 516.587,232.894 516.868,229.803C518.865,207.834 518.482,208.777 519.798,202.195C521.04,195.989 523.512,193.416 525.876,192.289L527.434,191.73L528.947,191.491C530.08,191.411 531.341,191.543 532.733,191.905C537.144,193.053 544.228,198.021 555.945,196.096C557.844,195.784 559.761,196.533 560.986,198.067C562.211,199.601 562.562,201.691 561.909,203.563C560.128,208.669 560.428,214.348 558.647,219.454C558.398,220.168 558.748,219.518 553.889,227.616C552.307,230.254 548.953,231.065 546.405,229.426C543.857,227.788 543.074,224.316 544.656,221.679C546.994,217.783 548.145,215.937 548.525,215.333Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M577.688,230.33C590.474,198.363 590.474,196.518 590.474,193.391C590.474,191.36 590.474,195.698 614.264,202.246C606.649,218.589 606.067,217.684 601.84,234.592" style="fill:rgb(59,72,88);"/>
<path d="M594.459,201.236C592.88,206.331 589.584,215.296 582.709,232.483C581.561,235.352 578.38,236.716 575.608,235.528C572.837,234.34 571.519,231.047 572.667,228.178C584.746,197.978 585.04,196.345 585.04,193.391L585.177,191.817L585.591,190.546L586.465,189.174L587.478,188.252L588.83,187.557L590.14,187.279L592.486,187.602L593.882,188.324C595.137,189.138 599.647,192.401 615.66,196.808C617.226,197.24 618.525,198.372 619.202,199.896C619.879,201.421 619.863,203.179 619.159,204.69C611.785,220.516 611.195,219.629 607.102,236.002C606.35,239.008 603.382,240.817 600.479,240.039C597.575,239.261 595.827,236.189 596.579,233.183C600.345,218.12 601.286,216.752 606.531,205.803C601.316,204.136 597.348,202.546 594.459,201.236Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M633.097,230.33C638.018,225.409 637.318,225.649 652.987,204.757C667.312,208.063 667.009,207.413 677.14,217.543C681.805,222.209 676.94,221.983 651.566,244.538" style="fill:rgb(59,72,88);"/>
<path d="M671.911,220.143C665.099,213.394 664.022,213.028 655.175,211.017C641.28,229.595 641.683,229.567 636.872,234.377C634.715,236.534 631.272,236.472 629.188,234.239C627.104,232.006 627.163,228.441 629.321,226.284C634.122,221.482 633.407,221.69 648.694,201.306C649.984,199.587 652.117,198.792 654.169,199.265C670.049,202.93 669.685,202.266 680.915,213.497C682.279,214.86 683.109,216.1 683.565,217.216L684.108,219.349L684.003,221.693C683.662,223.409 682.442,225.713 679.136,228.62C675.335,231.963 667.782,237.539 655.107,248.806C652.831,250.829 649.397,250.559 647.443,248.203C645.489,245.847 645.75,242.292 648.026,240.269C658.259,231.173 665.183,225.75 669.507,222.219C670.355,221.527 671.202,220.783 671.911,220.143Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(2.04028,-1.33537,0.881775,1.34724,-724.713,325.576)">
<path d="M427.236,306.456C424.606,306.724 420.399,307.153 396.154,295.244C389.162,335.466 388.573,338.385 391.571,336.886" style="fill:rgb(59,72,88);"/>
<path d="M393.36,332.722C393.963,333.316 394.443,334.213 394.686,335.328C395.254,337.932 394.319,340.744 392.599,341.604C392.202,341.802 391.839,341.945 391.516,342.031L390.47,342.14L388.914,341.565L388.191,340.892L387.585,340L387.084,338.831L386.712,337.213C386.537,336 386.518,333.89 386.929,330.478C387.618,324.755 389.52,313.89 392.981,293.979C393.211,292.658 393.789,291.539 394.581,290.884C395.373,290.229 396.308,290.097 397.166,290.518C420.474,301.967 424.487,301.756 427.015,301.498C428.822,301.314 430.387,303.386 430.509,306.122C430.631,308.858 429.263,311.229 427.456,311.413C424.849,311.679 420.72,312.281 398.445,301.576C396.041,315.401 394.518,324.188 393.73,329.676C393.573,330.765 393.451,331.824 393.36,332.722Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(3.57389,-2.02976,1.09638,1.93044,-1418.78,321.875)">
<path d="M388.729,339.727C399.104,329.352 396.146,343.441 407.199,351.093" style="fill:rgb(59,72,88);"/>
<path d="M389.654,342.898C388.709,343.843 387.527,343.19 387.017,341.44C386.506,339.69 386.859,337.502 387.804,336.557C391.497,332.864 393.81,332.066 395.531,332.608C396.862,333.027 397.934,334.202 399.001,335.953C400.822,338.941 402.691,344.125 407.881,347.718C408.887,348.415 409.398,350.492 409.022,352.355C408.646,354.218 407.523,355.165 406.517,354.468C401.728,351.153 399.358,346.744 397.53,343.339C396.597,341.602 395.948,340.05 394.879,339.713C393.698,339.341 392.188,340.364 389.654,342.898Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M400.523,384.526C387.087,383.781 373.651,383.037 360.216,382.293C379.35,401.356 373.78,403.651 371.287,429.778C381.363,426.755 391.44,423.732 401.516,420.709" style="fill:rgb(59,72,88);"/>
<path d="M400.813,378.907C403.808,379.073 406.11,381.725 405.95,384.826C405.789,387.927 403.228,390.31 400.232,390.144C400.232,390.144 373.251,388.649 373.251,388.65C375.8,392.093 377.351,395.071 378.265,397.978C379.399,401.588 379.626,405.155 379.296,409.384C379.026,412.853 378.349,416.867 377.659,422.009L400.003,415.306C402.884,414.441 405.901,416.161 406.736,419.144C407.571,422.126 405.909,425.249 403.028,426.113L372.8,435.182C371.068,435.702 369.2,435.298 367.813,434.104C366.426,432.911 365.702,431.083 365.879,429.225C366.632,421.329 367.662,415.594 368.217,410.916C368.656,407.221 368.839,404.357 367.928,401.459C366.591,397.202 363.153,393.028 356.447,386.347C354.812,384.717 354.327,382.207 355.231,380.051C356.136,377.896 358.239,376.549 360.506,376.675L400.813,378.907Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M398.038,451.494C405.269,424.57 379.943,475.222 378.784,477.539C377.435,480.237 380.018,479.943 398.413,509.725C401.403,491.032 401.608,492.129 411.461,476.118" style="fill:rgb(59,72,88);"/>
<path d="M395.997,456.709C391.617,464.264 386.517,474.321 384.479,478.386C385.995,480.493 389.725,485.804 395.605,494.957C397.389,487.841 399.747,484.683 406.877,473.095C408.489,470.477 411.851,469.705 414.381,471.373C416.911,473.042 417.656,476.522 416.045,479.141C406.805,494.156 406.579,493.113 403.775,510.644C403.407,512.942 401.71,514.768 399.506,515.237C397.302,515.705 395.047,514.719 393.834,512.756C380.832,491.705 375.67,485.524 374.2,482.782L373.352,480.741L373.052,478.887L373.23,476.945L373.958,474.953C374.799,473.27 388.017,447.067 393.245,441.047L395.614,438.944L397.855,438.085L399.125,438.011L400.603,438.29L402.04,439.006L403.164,440.015L404.181,441.657L404.688,443.535C404.871,444.895 404.64,447.914 403.274,453.001C402.47,455.993 399.472,457.747 396.582,456.915C396.381,456.857 396.186,456.788 395.997,456.709Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M435.674,507.543C402.962,521.665 420.078,534.101 448.4,555.68C450.773,557.488 434.692,545.236 459.766,520.161" style="fill:rgb(59,72,88);"/>
<path d="M440.334,542.499C441.319,535.868 445.152,526.954 455.99,516.115C458.148,513.957 461.591,514.019 463.675,516.253C465.759,518.486 465.699,522.051 463.542,524.208C453.891,533.859 450.961,541.043 450.921,546.156C450.89,550.02 452.749,552.128 452.959,552.416L453.675,553.711L454.082,555.764L453.906,557.274L453.415,558.554L452.315,560.006L450.891,560.978L449.364,561.431L447.888,561.426L446.426,560.992L445.179,560.211C432.794,550.775 422.536,542.933 417.246,536.02C412.304,529.562 411.141,523.483 413.711,517.729C415.951,512.713 421.934,507.379 433.582,502.35C436.35,501.155 439.535,502.511 440.69,505.377C441.844,508.243 440.534,511.54 437.766,512.735C429.779,516.184 425.113,519.012 423.577,522.451C422.618,524.599 423.922,526.623 425.766,529.033C428.915,533.148 434.03,537.553 440.334,542.499Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M489.602,524.424C486.041,563.589 479.156,565.497 482.498,565.625C515.059,566.877 517.011,568.321 516.596,565.625C516.336,563.936 516.701,564.047 518.017,544.314" style="fill:rgb(59,72,88);"/>
<path d="M489.004,560.253C500.178,560.71 507.498,561.088 511.365,561.303C511.587,558.295 511.979,553.161 512.595,543.927C512.801,540.829 515.398,538.487 518.391,538.701C521.383,538.915 523.645,541.604 523.438,544.701C522.201,563.265 521.718,563.151 521.963,564.74L521.926,567.538L521.144,569.542L520.192,570.759L519.106,571.624L517.902,572.218L516.447,572.603C513.883,573.038 505.831,572.153 482.296,571.247L479.931,570.792L477.967,569.517L477.136,568.49L476.555,567.316L476.229,565.996L476.201,564.52C476.26,563.88 476.528,562.795 477.17,561.093C478.562,557.401 482.002,547.972 484.191,523.896C484.472,520.805 487.125,518.531 490.111,518.822C493.097,519.113 495.294,521.859 495.013,524.951C493.293,543.864 490.81,554.39 489.004,560.253Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M559.218,532.948C556.58,538.224 557.911,538.411 559.218,568.467C568.216,566.099 577.214,563.731 586.212,561.363C582.52,544.132 582.673,545.089 579.108,527.265" style="fill:rgb(59,72,88);"/>
<path d="M554.392,530.362C555.77,527.604 559.053,526.524 561.716,527.952C564.38,529.379 565.423,532.777 564.045,535.534C563.165,537.293 563.059,538.223 563.169,541.198C563.308,544.913 563.789,550.778 564.323,561.318L579.764,557.255C577.191,545.281 576.946,544.2 573.787,528.406C573.179,525.365 575.069,522.385 578.007,521.756C580.944,521.126 583.822,523.084 584.43,526.124C587.987,543.907 587.834,542.953 591.518,560.144C592.154,563.112 590.397,566.067 587.551,566.816L560.557,573.919C558.973,574.336 557.292,573.993 555.979,572.985C554.667,571.976 553.862,570.41 553.789,568.72C553.135,553.672 552.475,546.101 552.308,541.632C552.086,535.68 552.633,533.88 554.392,530.362Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M603.261,524.424C610.919,544.845 613.443,542.307 627.628,559.247C627.398,541.672 628.548,540.672 645.883,534.369C633.313,520.9 632.892,522.256 621.731,507.375" style="fill:rgb(59,72,88);"/>
<path d="M598.195,526.46C597.109,523.565 598.498,520.303 601.294,519.179C604.09,518.055 607.242,519.493 608.327,522.387C613.715,536.754 616.511,539.175 623.023,545.914C623.604,543.086 624.52,540.939 625.876,539.131C627.86,536.485 630.989,534.429 636.149,532.173C628.695,524.545 626.745,523.234 617.438,510.825C615.599,508.372 616.029,504.835 618.398,502.931C620.767,501.026 624.184,501.472 626.023,503.924C637,518.56 637.428,517.213 649.792,530.459C651.078,531.838 651.598,533.799 651.172,535.665C650.746,537.53 649.431,539.042 647.684,539.677C643.581,541.169 640.431,542.334 638.024,543.522C636.416,544.316 635.229,545.008 634.462,546.031C633.781,546.939 633.526,548.087 633.332,549.582C633.013,552.034 633.009,555.126 633.062,559.171C633.093,561.533 631.695,563.664 629.563,564.505C627.43,565.346 625.017,564.718 623.522,562.932C608.703,545.236 606.195,547.792 598.195,526.46Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M651.566,497.43C664.634,510.498 664.87,508.951 679.525,521.408C684.13,513.664 688.734,505.92 693.338,498.176C676.84,486.301 676.403,488.169 661.511,473.277" style="fill:rgb(59,72,88);"/>
<path d="M647.79,501.476C645.633,499.319 645.574,495.754 647.657,493.521C649.741,491.287 653.185,491.226 655.342,493.383C666.629,504.67 668.234,504.912 678.133,513.017C678.133,513.017 685.996,499.792 685.996,499.792C673.176,490.862 671.56,491.148 657.736,477.324C655.578,475.166 655.519,471.601 657.603,469.368C659.687,467.135 663.13,467.073 665.287,469.23C679.854,483.797 680.303,481.941 696.441,493.557C698.808,495.261 699.478,498.583 697.966,501.125L684.154,524.357C683.331,525.741 681.97,526.689 680.422,526.957C678.874,527.225 677.29,526.787 676.077,525.756C661.244,513.149 661.016,514.702 647.79,501.476Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M678.56,461.911C688.515,465.111 698.47,468.311 708.425,471.512C714.426,443.803 716.733,443.254 718.341,444.862C731.577,458.098 710.523,437.758 692.768,437.758" style="fill:rgb(59,72,88);"/>
<path d="M708.875,447.973C704.044,445.561 698.248,443.385 692.768,443.385C689.768,443.385 687.333,440.864 687.333,437.758C687.333,434.653 689.768,432.132 692.768,432.132C700.188,432.132 709.091,435.804 715.567,439.086L717.445,438.723L718.871,438.908L720.055,439.333L722.117,440.815C724.583,443.281 725.959,444.714 726.476,445.361L727.642,447.463L727.932,449.392L727.441,451.644L726.079,453.547L724.37,454.605L723.092,454.924L721.864,454.932L719.57,454.192C719.354,454.08 719.064,453.909 718.708,453.69C717.424,457.762 715.572,464.226 713.728,472.743C713.396,474.275 712.46,475.595 711.147,476.381C709.833,477.168 708.262,477.351 706.813,476.885L676.949,467.284C674.084,466.363 672.481,463.208 673.37,460.242C674.26,457.277 677.308,455.617 680.172,456.538C680.172,456.538 704.447,464.342 704.447,464.342C706.238,456.582 707.691,451.391 708.875,447.973Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M699.871,417.868C703.983,409.646 705.51,413.606 731.128,413.606L731.128,388.032C716.113,388.823 716.967,389.866 704.134,388.032" style="fill:rgb(59,72,88);"/>
<path d="M704.698,420.454C703.319,423.212 700.037,424.292 697.373,422.865C694.709,421.437 693.666,418.039 695.045,415.282C696.622,412.127 698.122,410.277 699.843,409.072C701.718,407.76 703.962,407.059 707.386,407.028C710.87,406.995 716.292,407.703 725.693,407.918L725.693,394.003C715.789,394.706 714.824,395.239 703.391,393.606C700.419,393.181 698.34,390.339 698.75,387.263C699.16,384.187 701.905,382.035 704.877,382.459C717.225,384.223 716.404,383.174 730.852,382.414C732.339,382.335 733.792,382.892 734.872,383.954C735.951,385.017 736.562,386.491 736.562,388.032L736.562,413.606C736.562,416.713 734.129,419.232 731.128,419.232C720.739,419.232 714.258,418.594 709.954,418.356C708.003,418.249 706.779,417.997 705.868,418.432C705.68,418.521 705.643,418.763 705.497,418.995C705.235,419.413 704.976,419.897 704.698,420.454Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M710.981,366.265C679.463,368.917 729.5,363.983 732.549,362.459C734.289,361.589 731.208,360.815 729.707,338.306C715.624,340.654 716.442,341.689 702.713,339.727" style="fill:rgb(59,72,88);"/>
<path d="M726.638,357.92C726.068,355.062 725.364,350.759 724.79,344.881C715.561,346.588 714.321,347.065 701.97,345.301C698.999,344.876 696.919,342.034 697.329,338.958C697.739,335.882 700.485,333.729 703.456,334.154C716.37,335.999 715.596,334.96 728.843,332.752C730.351,332.501 731.891,332.917 733.087,333.901C734.283,334.884 735.024,336.342 735.129,337.919C736.045,351.666 737.565,356.92 738.116,359.137L738.495,362.005L738.199,363.732L737.634,365.014L736.414,366.514L734.912,367.525C734.307,367.828 732.43,368.376 729.715,368.825C725.827,369.468 719.604,370.262 713.982,370.956C713.241,371.465 712.37,371.793 711.421,371.872C704.463,372.458 701.284,372.67 700.637,372.659L699.147,372.442L697.194,371.401L696.006,370.019L695.341,368.425L695.174,366.754L695.45,365.231L696.175,363.754L697.574,362.343L698.686,361.748L700.356,361.337C703.227,360.89 716.445,359.41 724.851,358.196C725.448,358.11 726.054,358.016 726.638,357.92Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M692.768,316.995C704.577,309.91 704.334,311.245 716.92,307.05C734.156,301.305 725.517,295.733 726.866,270.111C718.536,285.104 717.651,284.469 699.871,285.739" style="fill:rgb(59,72,88);"/>
<path d="M695.493,321.863C692.898,323.42 689.57,322.503 688.066,319.817C686.562,317.13 687.447,313.685 690.043,312.128C702.379,304.726 702.108,306.077 715.255,301.695C718.784,300.518 720.92,299.728 721.931,298.2C722.804,296.881 722.486,295.203 722.326,292.937C722.194,291.063 721.974,288.941 721.77,286.511C721.013,287.114 720.236,287.624 719.418,288.064C715.488,290.177 710.269,290.636 700.246,291.352C697.253,291.566 694.656,289.224 694.45,286.126C694.243,283.029 696.505,280.34 699.497,280.126C707.255,279.572 711.366,279.714 714.407,278.079C717.234,276.558 718.776,273.387 722.154,267.306C723.409,265.048 725.997,263.976 728.41,264.717C730.823,265.457 732.429,267.816 732.292,270.417C731.621,283.165 733.445,290.925 733.283,296.213C733.18,299.548 732.447,302.219 730.898,304.56C728.944,307.513 725.406,310.132 718.586,312.406C706.561,316.414 706.776,315.093 695.493,321.863Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.47203,0,0,1.42192,-223.205,-254.934)">
<path d="M678.56,272.952C687.298,263.225 696.035,253.497 704.773,243.769C685.807,242.087 679.222,236.154 682.823,238.855C684.336,239.99 683.352,240.991 670.036,258.745" style="fill:rgb(59,72,88);"/>
<path d="M676.915,240.293L676.647,239.681L676.446,238.037L676.684,236.511L677.506,234.821L678.722,233.582L680.104,232.843L681.504,232.548L682.824,232.619L683.958,232.959C684.376,233.136 691.121,236.912 705.237,238.164C707.308,238.348 709.096,239.738 709.844,241.745C710.591,243.752 710.168,246.025 708.753,247.601L682.54,276.784C680.498,279.058 677.056,279.187 674.86,277.073C672.663,274.958 672.538,271.395 674.58,269.121C674.58,269.121 693.694,247.841 693.694,247.841C690.733,247.233 688.243,246.553 686.228,245.911C684.193,248.948 680.545,253.907 674.329,262.195C672.489,264.648 669.072,265.093 666.703,263.189C664.334,261.285 663.904,257.747 665.743,255.295C671.704,247.347 675.122,242.898 676.915,240.293Z" style="fill:rgb(59,72,88);"/>
</g>
<g transform="matrix(1.50754,0,0,1.43048,-246.076,-259.824)">
<path d="M467.366,211.525C469.377,210.54 471.845,210.96 473.454,212.748C475.47,214.987 475.382,218.529 473.258,220.654L471.361,221.956L469.46,222.528C465.093,223.352 449.264,223.13 413.158,274.71C404.672,286.834 349.623,365.191 384.205,457.409C413.929,536.674 487.491,570.818 559.521,564.929C631.739,559.026 702.604,512.929 726.656,431.153C743.222,374.829 742.599,293.044 697.058,241.81C666.689,207.644 616.386,187.459 538.336,196.977C526.243,198.452 521.147,201.964 515.3,205.379C508.497,209.351 500.85,213.309 483.351,214.768C480.432,215.011 477.874,212.71 477.643,209.633C477.412,206.556 479.595,203.861 482.515,203.617C500.861,202.088 506.822,197.453 513.971,193.361C519.518,190.186 525.671,187.262 537.116,185.866C619.684,175.797 672.677,198.02 704.804,234.163C753.008,288.393 754.328,374.847 736.793,434.464C711.376,520.884 636.661,569.842 560.342,576.081C483.837,582.335 405.905,545.709 374.335,461.52C337.73,363.908 395.635,280.903 404.617,268.07C442.194,214.39 464.905,211.662 467.366,211.525Z" style="fill:rgb(59,72,88);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

Some files were not shown because too many files have changed in this diff Show more