Init
3
.gitattributes
vendored
Normal 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
|
@ -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
|
@ -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"
|
||||
}
|
73
addons/ai_assistant_hub/ai_answer_handler.gd
Normal 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
|
1
addons/ai_assistant_hub/ai_answer_handler.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://b4kbwwm0flgbr
|
136
addons/ai_assistant_hub/ai_assistant_hub.gd
Normal 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()
|
1
addons/ai_assistant_hub/ai_assistant_hub.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://55itx6djrjvr
|
207
addons/ai_assistant_hub/ai_assistant_hub.tscn
Normal 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"]
|
219
addons/ai_assistant_hub/ai_chat.gd
Normal 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)
|
1
addons/ai_assistant_hub/ai_chat.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://r5gybxqvu0ye
|
179
addons/ai_assistant_hub/ai_chat.tscn
Normal 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"]
|
57
addons/ai_assistant_hub/ai_conversation.gd
Normal 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
|
1
addons/ai_assistant_hub/ai_conversation.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://fnqec17oj0rd
|
31
addons/ai_assistant_hub/ai_hub_plugin.gd
Normal 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()
|
1
addons/ai_assistant_hub/ai_hub_plugin.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://c2nhbu4ivwaja
|
27
addons/ai_assistant_hub/assistants/ai_assistant_resource.gd
Normal 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]
|
|
@ -0,0 +1 @@
|
|||
uid://cb6snexdvyill
|
14
addons/ai_assistant_hub/assistants/ollama.tres
Normal 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")
|
59
addons/ai_assistant_hub/bot_portrait.gd
Normal 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)
|
1
addons/ai_assistant_hub/bot_portrait.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://cnipuj0a7i4p
|
71
addons/ai_assistant_hub/bot_portrait.tscn
Normal 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
|
45
addons/ai_assistant_hub/chat_history_editor.gd
Normal 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()
|
1
addons/ai_assistant_hub/chat_history_editor.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://b3rfwrjgf1y6l
|
82
addons/ai_assistant_hub/chat_history_editor.tscn
Normal 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"]
|
36
addons/ai_assistant_hub/chat_history_entry.gd
Normal 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)
|
1
addons/ai_assistant_hub/chat_history_entry.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://ddulo2aqtcguh
|
39
addons/ai_assistant_hub/chat_history_entry.tscn
Normal 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"]
|
BIN
addons/ai_assistant_hub/graphics/icons/edit_chat_icon.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
|
@ -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
|
BIN
addons/ai_assistant_hub/graphics/icons/linear_32_3dmsicons.png
Normal file
After Width: | Height: | Size: 293 KiB |
|
@ -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
|
BIN
addons/ai_assistant_hub/graphics/icons/linear_32_flatmsicons.png
Normal file
After Width: | Height: | Size: 308 KiB |
|
@ -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
|
After Width: | Height: | Size: 175 B |
|
@ -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
|
BIN
addons/ai_assistant_hub/graphics/portraits/portraits_base.png
Normal file
After Width: | Height: | Size: 443 B |
|
@ -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
|
BIN
addons/ai_assistant_hub/graphics/portraits/portraits_eyes.png
Normal file
After Width: | Height: | Size: 194 B |
|
@ -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
|
BIN
addons/ai_assistant_hub/graphics/portraits/portraits_mouth.png
Normal file
After Width: | Height: | Size: 183 B |
|
@ -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
|
35
addons/ai_assistant_hub/llm_apis/llm_interface.gd
Normal 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
|
1
addons/ai_assistant_hub/llm_apis/llm_interface.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://dcapnr8anqxr1
|
67
addons/ai_assistant_hub/llm_apis/ollama_api.gd
Normal 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)
|
1
addons/ai_assistant_hub/llm_apis/ollama_api.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://bqxt4y1tg8a1p
|
50
addons/ai_assistant_hub/new_ai_assistant_button.gd
Normal 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)
|
1
addons/ai_assistant_hub/new_ai_assistant_button.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://n201ldaaytxq
|
11
addons/ai_assistant_hub/new_ai_assistant_button.tscn
Normal 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"]
|
7
addons/ai_assistant_hub/plugin.cfg
Normal file
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="AI Assistant Hub"
|
||||
description=""
|
||||
author="Flamx Games"
|
||||
version="1.0.0"
|
||||
script="ai_hub_plugin.gd"
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
uid://gfjw8kfxgsnh
|
BIN
addons/ai_assistant_hub/sounds/ai_error_cancel.wav
Normal file
24
addons/ai_assistant_hub/sounds/ai_error_cancel.wav.import
Normal 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
|
BIN
addons/ai_assistant_hub/sounds/ai_replied.wav
Normal file
24
addons/ai_assistant_hub/sounds/ai_replied.wav.import
Normal 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
|
38
addons/ai_assistant_hub/tools/assistant_tool_code_writer.gd
Normal 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
|
|
@ -0,0 +1 @@
|
|||
uid://c224dpt2c7cc
|
113
addons/ai_assistant_hub/tools/assistant_tool_selection.gd
Normal 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
|
|
@ -0,0 +1 @@
|
|||
uid://c1nam6n2xd5as
|
7
addons/code_time/plugin.cfg
Normal 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"
|
53
addons/code_time/plugin.gd
Normal 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()
|
1
addons/code_time/plugin.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://cbpkqlo4v57ty
|
19
addons/code_time/timer_panel.gd
Normal 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
|
1
addons/code_time/timer_panel.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://cuxddsdq6yapf
|
25
addons/code_time/timer_panel.tscn
Normal 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"]
|
BIN
addons/ridiculous_coding/GravityBold8.ttf
Normal file
40
addons/ridiculous_coding/GravityBold8.ttf.import
Normal 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={}
|
36
addons/ridiculous_coding/blip.gd
Normal 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()
|
1
addons/ridiculous_coding/blip.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://b1hwt8t7jrjq2
|
BIN
addons/ridiculous_coding/blip.png
Normal file
After Width: | Height: | Size: 883 B |
34
addons/ridiculous_coding/blip.png.import
Normal 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
|
174
addons/ridiculous_coding/blip.tscn
Normal 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"]
|
BIN
addons/ridiculous_coding/blip.wav
Normal file
24
addons/ridiculous_coding/blip.wav.import
Normal 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
|
29
addons/ridiculous_coding/boom.gd
Normal 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()
|
1
addons/ridiculous_coding/boom.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://dnudpbbop16aj
|
BIN
addons/ridiculous_coding/boom.png
Normal file
After Width: | Height: | Size: 18 KiB |
34
addons/ridiculous_coding/boom.png.import
Normal 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
|
135
addons/ridiculous_coding/boom.tscn
Normal 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"]
|
BIN
addons/ridiculous_coding/boom.wav
Normal file
24
addons/ridiculous_coding/boom.wav.import
Normal 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
|
168
addons/ridiculous_coding/dock.gd
Normal 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()
|
1
addons/ridiculous_coding/dock.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://yqhhxh7vjfs
|
149
addons/ridiculous_coding/dock.tscn
Normal 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"
|
BIN
addons/ridiculous_coding/fireworks.wav
Normal file
24
addons/ridiculous_coding/fireworks.wav.import
Normal 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
|
42
addons/ridiculous_coding/font.tres
Normal 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)
|
24
addons/ridiculous_coding/newline.gd
Normal 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()
|
1
addons/ridiculous_coding/newline.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://dulvfmb6n6ot8
|
BIN
addons/ridiculous_coding/newline.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
34
addons/ridiculous_coding/newline.png.import
Normal 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
|
89
addons/ridiculous_coding/newline.tscn
Normal 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"]
|
8
addons/ridiculous_coding/plugin.cfg
Normal 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"
|
170
addons/ridiculous_coding/plugin.gd
Normal 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()
|
1
addons/ridiculous_coding/plugin.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://dj0kkddglgu2g
|
BIN
addons/ridiculous_coding/progress.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
34
addons/ridiculous_coding/progress.png.import
Normal 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
|
BIN
addons/ridiculous_coding/under.png
Normal file
After Width: | Height: | Size: 192 B |
34
addons/ridiculous_coding/under.png.import
Normal 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
|
@ -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 |