Compare commits
21 Commits
f7f1d2be2c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d724e2c63 | ||
|
|
45281057f2 | ||
|
|
53e86be2ce | ||
| 281f42d90c | |||
|
|
94dc231c01 | ||
| 39630ce2ca | |||
| 53c86e9cc7 | |||
| e133519dc0 | |||
| 8cc85e3d6c | |||
| 24d19310b5 | |||
| a7dcd6d725 | |||
| caace67d12 | |||
| b137360fa4 | |||
| 89c04f5d0c | |||
| 5bdf8db609 | |||
| af91337017 | |||
| 69dc4444e3 | |||
| 18ecd5b820 | |||
| 84ea00aae3 | |||
|
|
e03bfc580e | ||
| 4c4b051f3f |
@@ -9,17 +9,18 @@ class_name DialogicNode_NameLabel
|
||||
@export var use_character_color := true
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group('dialogic_name_label')
|
||||
if hide_when_empty:
|
||||
name_label_root.visible = false
|
||||
text = ""
|
||||
add_to_group('dialogic_name_label')
|
||||
if hide_when_empty:
|
||||
name_label_root.visible = false
|
||||
text = ""
|
||||
|
||||
|
||||
func _set(property, what):
|
||||
if property == 'text' and typeof(what) == TYPE_STRING:
|
||||
text = what
|
||||
if hide_when_empty:
|
||||
name_label_root.visible = !what.is_empty()
|
||||
else:
|
||||
name_label_root.show()
|
||||
return true
|
||||
if property == 'text' and typeof(what) == TYPE_STRING:
|
||||
text = what
|
||||
if hide_when_empty:
|
||||
name_label_root.visible = !what.is_empty()
|
||||
else:
|
||||
name_label_root.show()
|
||||
return true
|
||||
return false
|
||||
|
||||
@@ -27,15 +27,15 @@ signal variable_was_set(info:Dictionary)
|
||||
####################################################################################################
|
||||
|
||||
func clear_game_state(clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR):
|
||||
# loading default variables
|
||||
if ! clear_flag & DialogicGameHandler.ClearFlags.KEEP_VARIABLES:
|
||||
reset()
|
||||
# loading default variables
|
||||
if ! clear_flag & DialogicGameHandler.ClearFlags.KEEP_VARIABLES:
|
||||
reset()
|
||||
|
||||
|
||||
func load_game_state(load_flag:=LoadFlags.FULL_LOAD):
|
||||
if load_flag == LoadFlags.ONLY_DNODES:
|
||||
return
|
||||
dialogic.current_state_info['variables'] = merge_folder(dialogic.current_state_info['variables'], ProjectSettings.get_setting('dialogic/variables', {}).duplicate(true))
|
||||
if load_flag == LoadFlags.ONLY_DNODES:
|
||||
return
|
||||
dialogic.current_state_info['variables'] = merge_folder(dialogic.current_state_info['variables'], ProjectSettings.get_setting('dialogic/variables', {}).duplicate(true))
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -54,226 +54,228 @@ func load_game_state(load_flag:=LoadFlags.FULL_LOAD):
|
||||
## it will try to search for an autoload with the name `Game` and get the value
|
||||
## of `player_name` to replace it.
|
||||
func parse_variables(text:String) -> String:
|
||||
# First some dirty checks to avoid parsing
|
||||
if not '{' in text:
|
||||
return text
|
||||
# First some dirty checks to avoid parsing
|
||||
if not '{' in text:
|
||||
return text
|
||||
|
||||
# Trying to extract the curly brackets from the text
|
||||
var regex := RegEx.new()
|
||||
regex.compile(r"(?<!\\)\{(?<variable>([^{}]|\{[^}]*\})*)\}")
|
||||
# Trying to extract the curly brackets from the text
|
||||
var regex := RegEx.new()
|
||||
regex.compile(r"(?<!\\)\{(?<variable>([^{}]|\{[^}]*\})*)\}")
|
||||
|
||||
var parsed := text.replace('\\{', '{')
|
||||
for result in regex.search_all(text):
|
||||
var value: Variant = get_variable(result.get_string('variable'), "<NOT FOUND>")
|
||||
parsed = parsed.replace("{"+result.get_string('variable')+"}", str(value))
|
||||
var parsed := text.replace('\\{', '{')
|
||||
for result in regex.search_all(text):
|
||||
var value: Variant = get_variable(result.get_string('variable'), "<NOT FOUND>")
|
||||
parsed = parsed.replace("{"+result.get_string('variable')+"}", str(value))
|
||||
|
||||
return parsed
|
||||
return parsed
|
||||
|
||||
|
||||
func set_variable(variable_name: String, value: Variant) -> bool:
|
||||
variable_name = variable_name.trim_prefix('{').trim_suffix('}')
|
||||
variable_name = variable_name.trim_prefix('{').trim_suffix('}')
|
||||
|
||||
# First assume this is a simple dialogic variable
|
||||
if has(variable_name):
|
||||
DialogicUtil._set_value_in_dictionary(variable_name, dialogic.current_state_info['variables'], value)
|
||||
variable_changed.emit({'variable':variable_name, 'new_value':value})
|
||||
return true
|
||||
# First assume this is a simple dialogic variable
|
||||
if has(variable_name):
|
||||
DialogicUtil._set_value_in_dictionary(variable_name, dialogic.current_state_info['variables'], value)
|
||||
variable_changed.emit({'variable':variable_name, 'new_value':value})
|
||||
return true
|
||||
|
||||
# Second assume this is an autoload variable
|
||||
elif '.' in variable_name:
|
||||
var from := variable_name.get_slice('.', 0)
|
||||
var variable := variable_name.trim_prefix(from+'.')
|
||||
# Second assume this is an autoload variable
|
||||
elif '.' in variable_name:
|
||||
var from := variable_name.get_slice('.', 0)
|
||||
var variable := variable_name.trim_prefix(from+'.')
|
||||
|
||||
var autoloads := get_autoloads()
|
||||
var object: Object = null
|
||||
if from in autoloads:
|
||||
object = autoloads[from]
|
||||
while variable.count("."):
|
||||
from = variable.get_slice('.', 0)
|
||||
if from in object and object.get(from) is Object:
|
||||
object = object.get(from)
|
||||
variable = variable.trim_prefix(from+'.')
|
||||
var autoloads := get_autoloads()
|
||||
var object: Object = null
|
||||
if from in autoloads:
|
||||
object = autoloads[from]
|
||||
while variable.count("."):
|
||||
from = variable.get_slice('.', 0)
|
||||
if from in object and object.get(from) is Object:
|
||||
object = object.get(from)
|
||||
variable = variable.trim_prefix(from+'.')
|
||||
|
||||
if object:
|
||||
var sub_idx := ""
|
||||
if '[' in variable:
|
||||
sub_idx = variable.substr(variable.find('['))
|
||||
variable = variable.trim_suffix(sub_idx)
|
||||
sub_idx = sub_idx.trim_prefix('[').trim_suffix(']')
|
||||
if object:
|
||||
var sub_idx := ""
|
||||
if '[' in variable:
|
||||
sub_idx = variable.substr(variable.find('['))
|
||||
variable = variable.trim_suffix(sub_idx)
|
||||
sub_idx = sub_idx.trim_prefix('[').trim_suffix(']')
|
||||
|
||||
if variable in object:
|
||||
match typeof(object.get(variable)):
|
||||
TYPE_ARRAY:
|
||||
if not sub_idx:
|
||||
if typeof(value) == TYPE_ARRAY:
|
||||
object.set(variable, value)
|
||||
return true
|
||||
elif sub_idx.is_valid_float():
|
||||
object.get(variable).remove_at(int(sub_idx))
|
||||
object.get(variable).insert(int(sub_idx), value)
|
||||
return true
|
||||
TYPE_DICTIONARY:
|
||||
if not sub_idx:
|
||||
if typeof(value) == TYPE_DICTIONARY:
|
||||
object.set(variable, value)
|
||||
return true
|
||||
else:
|
||||
object.get(variable).merge({str_to_var(sub_idx):value}, true)
|
||||
return true
|
||||
_:
|
||||
object.set(variable, value)
|
||||
return true
|
||||
if variable in object:
|
||||
match typeof(object.get(variable)):
|
||||
TYPE_ARRAY:
|
||||
if not sub_idx:
|
||||
if typeof(value) == TYPE_ARRAY:
|
||||
object.set(variable, value)
|
||||
return true
|
||||
elif sub_idx.is_valid_float():
|
||||
object.get(variable).remove_at(int(sub_idx))
|
||||
object.get(variable).insert(int(sub_idx), value)
|
||||
return true
|
||||
TYPE_DICTIONARY:
|
||||
if not sub_idx:
|
||||
if typeof(value) == TYPE_DICTIONARY:
|
||||
object.set(variable, value)
|
||||
return true
|
||||
else:
|
||||
object.get(variable).merge({str_to_var(sub_idx):value}, true)
|
||||
return true
|
||||
_:
|
||||
object.set(variable, value)
|
||||
return true
|
||||
|
||||
printerr("[Dialogic] Tried setting non-existant variable '"+variable_name+"'.")
|
||||
return false
|
||||
printerr("[Dialogic] Tried setting non-existant variable '"+variable_name+"'.")
|
||||
return false
|
||||
|
||||
|
||||
func get_variable(variable_path:String, default: Variant = null, no_warning := false) -> Variant:
|
||||
if variable_path.begins_with('{') and variable_path.ends_with('}') and variable_path.count('{') == 1:
|
||||
variable_path = variable_path.trim_prefix('{').trim_suffix('}')
|
||||
if variable_path.begins_with('{') and variable_path.ends_with('}') and variable_path.count('{') == 1:
|
||||
variable_path = variable_path.trim_prefix('{').trim_suffix('}')
|
||||
|
||||
# First assume this is just a single variable
|
||||
var value: Variant = DialogicUtil._get_value_in_dictionary(variable_path, dialogic.current_state_info['variables'])
|
||||
if value != null:
|
||||
return value
|
||||
# First assume this is just a single variable
|
||||
var value: Variant = DialogicUtil._get_value_in_dictionary(variable_path, dialogic.current_state_info['variables'])
|
||||
if value != null:
|
||||
return value
|
||||
|
||||
# Second assume this is an expression.
|
||||
else:
|
||||
value = dialogic.Expressions.execute_string(variable_path, null, no_warning)
|
||||
if value != null:
|
||||
return value
|
||||
# Second assume this is an expression.
|
||||
else:
|
||||
value = dialogic.Expressions.execute_string(variable_path, null, no_warning)
|
||||
if value != null:
|
||||
return value
|
||||
|
||||
# If everything fails, tell the user and return the default
|
||||
if not no_warning:
|
||||
printerr("[Dialogic] Failed parsing variable/expression '"+variable_path+"'.")
|
||||
return default
|
||||
# If everything fails, tell the user and return the default
|
||||
if not no_warning:
|
||||
printerr("[Dialogic] Failed parsing variable/expression '"+variable_path+"'.")
|
||||
return default
|
||||
|
||||
|
||||
## Resets all variables or a specific variable to the value(s) defined in the variable editor
|
||||
func reset(variable:="") -> void:
|
||||
if variable.is_empty():
|
||||
dialogic.current_state_info['variables'] = ProjectSettings.get_setting("dialogic/variables", {}).duplicate(true)
|
||||
else:
|
||||
DialogicUtil._set_value_in_dictionary(variable, dialogic.current_state_info['variables'], DialogicUtil._get_value_in_dictionary(variable, ProjectSettings.get_setting('dialogic/variables', {})))
|
||||
if variable.is_empty():
|
||||
dialogic.current_state_info['variables'] = ProjectSettings.get_setting("dialogic/variables", {}).duplicate(true)
|
||||
else:
|
||||
DialogicUtil._set_value_in_dictionary(variable, dialogic.current_state_info['variables'], DialogicUtil._get_value_in_dictionary(variable, ProjectSettings.get_setting('dialogic/variables', {})))
|
||||
|
||||
|
||||
## Returns true if a variable with the given path exists
|
||||
func has(variable:="") -> bool:
|
||||
return DialogicUtil._get_value_in_dictionary(variable, dialogic.current_state_info['variables']) != null
|
||||
return DialogicUtil._get_value_in_dictionary(variable, dialogic.current_state_info['variables']) != null
|
||||
|
||||
|
||||
|
||||
## Allows to set dialogic built-in variables
|
||||
func _set(property, value) -> bool:
|
||||
property = str(property)
|
||||
var vars: Dictionary = dialogic.current_state_info['variables']
|
||||
if property in vars.keys():
|
||||
if typeof(vars[property]) != TYPE_DICTIONARY:
|
||||
vars[property] = value
|
||||
return true
|
||||
if value is VariableFolder:
|
||||
return true
|
||||
return false
|
||||
property = str(property)
|
||||
var vars: Dictionary = dialogic.current_state_info['variables']
|
||||
if property in vars.keys():
|
||||
if typeof(vars[property]) != TYPE_DICTIONARY:
|
||||
vars[property] = value
|
||||
return true
|
||||
if value is VariableFolder:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
## Allows to get dialogic built-in variables
|
||||
func _get(property):
|
||||
property = str(property)
|
||||
if property in dialogic.current_state_info['variables'].keys():
|
||||
if typeof(dialogic.current_state_info['variables'][property]) == TYPE_DICTIONARY:
|
||||
return VariableFolder.new(dialogic.current_state_info['variables'][property], property, self)
|
||||
else:
|
||||
return DialogicUtil.logical_convert(dialogic.current_state_info['variables'][property])
|
||||
property = str(property)
|
||||
if property in dialogic.current_state_info['variables'].keys():
|
||||
if typeof(dialogic.current_state_info['variables'][property]) == TYPE_DICTIONARY:
|
||||
return VariableFolder.new(dialogic.current_state_info['variables'][property], property, self)
|
||||
else:
|
||||
return DialogicUtil.logical_convert(dialogic.current_state_info['variables'][property])
|
||||
return null
|
||||
|
||||
|
||||
func folders() -> Array:
|
||||
var result := []
|
||||
for i in dialogic.current_state_info['variables'].keys():
|
||||
if dialogic.current_state_info['variables'][i] is Dictionary:
|
||||
result.append(VariableFolder.new(dialogic.current_state_info['variables'][i], i, self))
|
||||
return result
|
||||
var result := []
|
||||
for i in dialogic.current_state_info['variables'].keys():
|
||||
if dialogic.current_state_info['variables'][i] is Dictionary:
|
||||
result.append(VariableFolder.new(dialogic.current_state_info['variables'][i], i, self))
|
||||
return result
|
||||
|
||||
|
||||
func variables(_absolute:=false) -> Array:
|
||||
var result := []
|
||||
for i in dialogic.current_state_info['variables'].keys():
|
||||
if not dialogic.current_state_info['variables'][i] is Dictionary:
|
||||
result.append(i)
|
||||
return result
|
||||
var result := []
|
||||
for i in dialogic.current_state_info['variables'].keys():
|
||||
if not dialogic.current_state_info['variables'][i] is Dictionary:
|
||||
result.append(i)
|
||||
return result
|
||||
#endregion
|
||||
|
||||
#region HELPERS
|
||||
################################################################################
|
||||
|
||||
func get_autoloads() -> Dictionary:
|
||||
var autoloads := {}
|
||||
for node: Node in get_tree().root.get_children():
|
||||
autoloads[node.name] = node
|
||||
return autoloads
|
||||
var autoloads := {}
|
||||
for node: Node in get_tree().root.get_children():
|
||||
autoloads[node.name] = node
|
||||
return autoloads
|
||||
|
||||
|
||||
func merge_folder(new:Dictionary, defs:Dictionary) -> Dictionary:
|
||||
# also go through all groups in this folder
|
||||
for x in new.keys():
|
||||
if x in defs and typeof(new[x]) == TYPE_DICTIONARY:
|
||||
new[x] = merge_folder(new[x], defs[x])
|
||||
# add all new variables
|
||||
for x in defs.keys():
|
||||
if not x in new:
|
||||
new[x] = defs[x]
|
||||
return new
|
||||
# also go through all groups in this folder
|
||||
for x in new.keys():
|
||||
if x in defs and typeof(new[x]) == TYPE_DICTIONARY:
|
||||
new[x] = merge_folder(new[x], defs[x])
|
||||
# add all new variables
|
||||
for x in defs.keys():
|
||||
if not x in new:
|
||||
new[x] = defs[x]
|
||||
return new
|
||||
|
||||
#endregion
|
||||
|
||||
#region VARIABLE FOLDER
|
||||
################################################################################
|
||||
class VariableFolder:
|
||||
var data := {}
|
||||
var path := ""
|
||||
var outside: DialogicSubsystem
|
||||
var data := {}
|
||||
var path := ""
|
||||
var outside: DialogicSubsystem
|
||||
|
||||
func _init(_data:Dictionary, _path:String, _outside:DialogicSubsystem):
|
||||
data = _data
|
||||
path = _path
|
||||
outside = _outside
|
||||
func _init(_data:Dictionary, _path:String, _outside:DialogicSubsystem):
|
||||
data = _data
|
||||
path = _path
|
||||
outside = _outside
|
||||
|
||||
|
||||
func _get(property:StringName):
|
||||
property = str(property)
|
||||
if property in data:
|
||||
if typeof(data[property]) == TYPE_DICTIONARY:
|
||||
return VariableFolder.new(data[property], path+"."+property, outside)
|
||||
else:
|
||||
return DialogicUtil.logical_convert(data[property])
|
||||
func _get(property:StringName):
|
||||
property = str(property)
|
||||
if property in data:
|
||||
if typeof(data[property]) == TYPE_DICTIONARY:
|
||||
return VariableFolder.new(data[property], path+"."+property, outside)
|
||||
else:
|
||||
return DialogicUtil.logical_convert(data[property])
|
||||
return null
|
||||
|
||||
|
||||
func _set(property:StringName, value:Variant) -> bool:
|
||||
property = str(property)
|
||||
if not value is VariableFolder:
|
||||
DialogicUtil._set_value_in_dictionary(path+"."+property, outside.dialogic.current_state_info['variables'], value)
|
||||
return true
|
||||
func _set(property:StringName, value:Variant) -> bool:
|
||||
property = str(property)
|
||||
if not value is VariableFolder:
|
||||
DialogicUtil._set_value_in_dictionary(path+"."+property, outside.dialogic.current_state_info['variables'], value)
|
||||
return true
|
||||
|
||||
|
||||
func has(key:String) -> bool:
|
||||
return key in data
|
||||
func has(key:String) -> bool:
|
||||
return key in data
|
||||
|
||||
|
||||
func folders() -> Array:
|
||||
var result := []
|
||||
for i in data.keys():
|
||||
if data[i] is Dictionary:
|
||||
result.append(VariableFolder.new(data[i], path+"."+i, outside))
|
||||
return result
|
||||
func folders() -> Array:
|
||||
var result := []
|
||||
for i in data.keys():
|
||||
if data[i] is Dictionary:
|
||||
result.append(VariableFolder.new(data[i], path+"."+i, outside))
|
||||
return result
|
||||
|
||||
|
||||
func variables(absolute:=false) -> Array:
|
||||
var result := []
|
||||
for i in data.keys():
|
||||
if not data[i] is Dictionary:
|
||||
if absolute:
|
||||
result.append(path+'.'+i)
|
||||
else:
|
||||
result.append(i)
|
||||
return result
|
||||
func variables(absolute:=false) -> Array:
|
||||
var result := []
|
||||
for i in data.keys():
|
||||
if not data[i] is Dictionary:
|
||||
if absolute:
|
||||
result.append(path+'.'+i)
|
||||
else:
|
||||
result.append(i)
|
||||
return result
|
||||
|
||||
#endregion
|
||||
|
||||
101
addons/very-simple-twitch/auth_server.gd
Normal file
@@ -0,0 +1,101 @@
|
||||
class_name VSTAuthServer
|
||||
|
||||
extends Node
|
||||
|
||||
signal OnTokenReceived(token: String)
|
||||
|
||||
var SERVER_IDENTITY = 'AUTH_SERVER'
|
||||
var TOKEN_KEY = 'token'
|
||||
var AUTHENTICATION_REDIRECT_FILE_PATH = 'res://addons/very-simple-twitch/public/index.html'
|
||||
|
||||
var _clients: Array[StreamPeerTCP] = []
|
||||
var _server: TCPServer
|
||||
|
||||
|
||||
func start_server(port: int):
|
||||
_server = TCPServer.new()
|
||||
_server.listen(port)
|
||||
print("Server started")
|
||||
|
||||
|
||||
func stop_server():
|
||||
if _server:
|
||||
_server.stop()
|
||||
_server = null
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if !_server:
|
||||
return
|
||||
|
||||
var newConnection = _server.take_connection()
|
||||
if newConnection:
|
||||
_clients.push_back(newConnection)
|
||||
|
||||
for client in _clients:
|
||||
if client.get_status() != StreamPeerTCP.STATUS_CONNECTED:
|
||||
continue
|
||||
|
||||
var bytes = client.get_available_bytes()
|
||||
# after finisher reading bytes clear connection
|
||||
_clients = _clients.filter(func (item): return item != client)
|
||||
var requestAsString = client.get_string(bytes)
|
||||
if requestAsString.length() < 1:
|
||||
continue
|
||||
|
||||
var requestInformation = requestAsString.split('\n')[0].split(' ')
|
||||
var method = requestInformation[0]
|
||||
var url = requestInformation[1]
|
||||
|
||||
match method:
|
||||
'GET':
|
||||
# send html login page
|
||||
handleGet(client)
|
||||
'POST':
|
||||
# handle token extraction
|
||||
handlePost(client, url)
|
||||
|
||||
|
||||
func handlePost(client: StreamPeer, url: String):
|
||||
var urlSplitted:PackedStringArray = url.split('?')
|
||||
if (len(urlSplitted) == 1):
|
||||
return
|
||||
var query = urlSplitted[1]
|
||||
var token = getTokenFromQuery(query)
|
||||
if !token:
|
||||
return
|
||||
OnTokenReceived.emit(token)
|
||||
send200(client)
|
||||
stop_server()
|
||||
queue_free()
|
||||
|
||||
|
||||
func handleGet(client: StreamPeer):
|
||||
var pageAsString = loadLoginPage()
|
||||
send200(client, pageAsString)
|
||||
|
||||
|
||||
func getTokenFromQuery(query: String):
|
||||
var queryKeyValues = query.split('&')
|
||||
for keyValue in queryKeyValues:
|
||||
var splittedKeyValue = keyValue.split('=')
|
||||
var key = splittedKeyValue[0]
|
||||
if key == TOKEN_KEY:
|
||||
return splittedKeyValue[1]
|
||||
|
||||
|
||||
func send200(client: StreamPeer, data: String = "", content_type: String = "text/html"):
|
||||
var dataAsBuffer = data.to_ascii_buffer()
|
||||
client.put_data(("HTTP/1.1 %d %s\r\n" % [200, 'OK']).to_ascii_buffer())
|
||||
client.put_data(("Server: %s\r\n" % SERVER_IDENTITY).to_ascii_buffer())
|
||||
client.put_data(("Content-Length: %d\r\n" % dataAsBuffer.size()).to_ascii_buffer())
|
||||
client.put_data("Connection: close\r\n".to_ascii_buffer())
|
||||
client.put_data(("Content-Type: %s\r\n\r\n" % content_type).to_ascii_buffer())
|
||||
client.put_data(dataAsBuffer)
|
||||
|
||||
|
||||
func loadLoginPage() -> String:
|
||||
var file = FileAccess.open(AUTHENTICATION_REDIRECT_FILE_PATH, FileAccess.READ)
|
||||
var loadedFile: String = file.get_as_text()
|
||||
file.close()
|
||||
return loadedFile
|
||||
1
addons/very-simple-twitch/auth_server.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cfwdtrrd8w61s
|
||||
145
addons/very-simple-twitch/chat/vst_chat_dock.gd
Normal file
@@ -0,0 +1,145 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
const MAX_MESSAGES:int = 50
|
||||
var line: PackedScene = load("res://addons/very-simple-twitch/chat/vst_chat_dock_line.tscn")
|
||||
|
||||
|
||||
var twitch_chat: VSTChat:
|
||||
get:
|
||||
if twitch_chat == null:
|
||||
twitch_chat = VSTChat.new()
|
||||
add_child(twitch_chat)
|
||||
return twitch_chat
|
||||
|
||||
@onready var support_button: Button = %SupportButton
|
||||
@onready var channel_line_edit: LineEdit = %ChannelLineEdit
|
||||
|
||||
@onready var connect_button: Button = %ConnectButton
|
||||
|
||||
@onready var chat_layout: Control = %ChatLayout
|
||||
|
||||
@onready var chat_scroll:ScrollContainer = %ChatScroll
|
||||
|
||||
@onready var clear_button:Button = %ClearButton
|
||||
|
||||
@onready var disconnect_button:Button = %DisconnectButton
|
||||
|
||||
func _ready():
|
||||
support_button.icon = get_theme_icon("Heart", "EditorIcons")
|
||||
support_button.tooltip_text = "Support me on Ko-fi"
|
||||
|
||||
func _on_button_pressed():
|
||||
twitch_chat.Connected.connect(on_chat_connected)
|
||||
twitch_chat.OnMessage.connect(create_chatter_msg)
|
||||
|
||||
twitch_chat.login_anon(channel_line_edit.text)
|
||||
connect_button.disabled = true
|
||||
channel_line_edit.editable = false
|
||||
|
||||
func _on_clear_button_pressed():
|
||||
clear_all_messages()
|
||||
|
||||
func _on_line_edit_text_changed(new_text):
|
||||
connect_button.disabled = len(new_text) == 0
|
||||
|
||||
func _on_disconnect_button_pressed():
|
||||
twitch_chat.disconnect_api()
|
||||
clear_all_messages()
|
||||
show_connect_layout()
|
||||
if twitch_chat.Connected.is_connected(on_chat_connected):
|
||||
twitch_chat.Connected.disconnect(on_chat_connected)
|
||||
if twitch_chat.OnMessage.is_connected(create_chatter_msg):
|
||||
twitch_chat.OnMessage.disconnect(create_chatter_msg)
|
||||
|
||||
|
||||
func _on_support_button_pressed() -> void:
|
||||
OS.shell_open("https://ko-fi.com/rothiotome?ref=VST")
|
||||
|
||||
|
||||
func on_chat_connected():
|
||||
create_system_msg("Connected to chat")
|
||||
show_chat_layout()
|
||||
|
||||
func create_system_msg(message: String):
|
||||
var msg = line.instantiate()
|
||||
msg.set_chatter_string("[i]"+message+"[/i]")
|
||||
chat_layout.add_child(msg)
|
||||
check_scroll()
|
||||
|
||||
func create_chatter_msg(chatter: VSTChatter):
|
||||
var msg = line.instantiate()
|
||||
|
||||
var badges: String = await get_badges(chatter)
|
||||
chatter.message = escape_bbcode(chatter.message)
|
||||
await add_emotes(chatter)
|
||||
|
||||
msg.set_chatter_msg(badges, chatter)
|
||||
chat_layout.add_child(msg)
|
||||
|
||||
check_scroll()
|
||||
|
||||
func check_scroll():
|
||||
var bottom: bool = is_scroll_bottom()
|
||||
check_number_messages()
|
||||
await get_tree().process_frame
|
||||
if bottom: chat_scroll.scroll_vertical = chat_scroll.get_v_scroll_bar().max_value
|
||||
|
||||
func check_number_messages():
|
||||
if chat_layout.get_child_count() > MAX_MESSAGES:
|
||||
chat_layout.remove_child(chat_layout.get_children()[0])
|
||||
|
||||
# TODO: Can't get badges when the connection is annonymous, we should clear this method
|
||||
func get_badges(chatter: VSTChatter) -> String:
|
||||
var badges:= ""
|
||||
for badge in chatter.tags.badges:
|
||||
var result = await twitch_chat.get_badge(badge, chatter.tags.badges[badge], chatter.tags.user_id)
|
||||
if result:
|
||||
badges += "[img=center]" + result.resource_path + "[/img] "
|
||||
return badges
|
||||
|
||||
func add_emotes(chatter: VSTChatter):
|
||||
if chatter.tags.emotes.is_empty(): return
|
||||
|
||||
var locations: Array = []
|
||||
for emote in chatter.tags.emotes:
|
||||
for data in chatter.tags.emotes[emote].split(","):
|
||||
var start_end = data.split("-")
|
||||
locations.append(VSTEmoteLocation.new(emote, int(start_end[0]), int(start_end[1])))
|
||||
|
||||
locations.sort_custom(Callable(VSTEmoteLocation, "smaller"))
|
||||
var offset = 0
|
||||
for loc in locations:
|
||||
var result = await twitch_chat.get_emote(loc.id)
|
||||
var emote_string = "[img=center]" + result.resource_path +"[/img]"
|
||||
var pre: String = chatter.message.substr(0, loc.start + offset)
|
||||
var post: String = chatter.message.substr(loc.end + offset + 1)
|
||||
chatter.message = pre + emote_string + post
|
||||
offset += emote_string.length() + loc.start - loc.end - 1
|
||||
|
||||
func is_scroll_bottom() -> bool:
|
||||
var scroll_bar = chat_scroll.get_v_scroll_bar()
|
||||
return chat_scroll.scroll_vertical >= scroll_bar.max_value - scroll_bar.get_rect().size.y
|
||||
|
||||
|
||||
# Returns escaped BBCode that won't be parsed by RichTextLabel as tags.
|
||||
func escape_bbcode(bbcode_text) -> String:
|
||||
return bbcode_text.replace("[", "[lb]")
|
||||
|
||||
func clear_all_messages():
|
||||
for childen in chat_layout.get_children():
|
||||
chat_layout.remove_child(childen)
|
||||
|
||||
func show_chat_layout():
|
||||
disconnect_button.visible = true
|
||||
clear_button.visible = true
|
||||
channel_line_edit.visible = false
|
||||
connect_button.visible = false
|
||||
|
||||
func show_connect_layout():
|
||||
disconnect_button.visible = false
|
||||
clear_button.visible = false
|
||||
channel_line_edit.editable = true
|
||||
channel_line_edit.visible = true
|
||||
connect_button.visible = true
|
||||
connect_button.disabled = false
|
||||
1
addons/very-simple-twitch/chat/vst_chat_dock.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://lsv481dwwwpu
|
||||
111
addons/very-simple-twitch/chat/vst_chat_dock.tscn
Normal file
@@ -0,0 +1,111 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://c8jard0jvmfmt"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/very-simple-twitch/chat/vst_chat_dock.gd" id="1_fkddh"]
|
||||
|
||||
[sub_resource type="Image" id="Image_dfhsm"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_lqqxv"]
|
||||
image = SubResource("Image_dfhsm")
|
||||
|
||||
[node name="VstChatDock" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_fkddh")
|
||||
|
||||
[node name="TabContainer" type="TabContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
current_tab = 0
|
||||
|
||||
[node name="Chat" type="VBoxContainer" parent="TabContainer"]
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 0
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Chat"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ChannelLineEdit" type="LineEdit" parent="TabContainer/Chat/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Channel name"
|
||||
|
||||
[node name="ConnectButton" type="Button" parent="TabContainer/Chat/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "CONNECT"
|
||||
|
||||
[node name="ClearButton" type="Button" parent="TabContainer/Chat/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "CLEAR CHAT"
|
||||
|
||||
[node name="DisconnectButton" type="Button" parent="TabContainer/Chat/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "DISCONNECT"
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/Chat"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Chat" type="Panel" parent="TabContainer/Chat/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="ChatScroll" type="ScrollContainer" parent="TabContainer/Chat/VBoxContainer/Chat"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="ChatLayout" type="VBoxContainer" parent="TabContainer/Chat/VBoxContainer/Chat/ChatScroll"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -8.0
|
||||
offset_bottom = 33.0
|
||||
grow_horizontal = 0
|
||||
alignment = 2
|
||||
|
||||
[node name="SupportButton" type="Button" parent="HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 8
|
||||
tooltip_text = "Support me on Ko-fi"
|
||||
icon = SubResource("ImageTexture_lqqxv")
|
||||
flat = true
|
||||
|
||||
[connection signal="text_changed" from="TabContainer/Chat/HBoxContainer/ChannelLineEdit" to="." method="_on_line_edit_text_changed"]
|
||||
[connection signal="pressed" from="TabContainer/Chat/HBoxContainer/ConnectButton" to="." method="_on_button_pressed"]
|
||||
[connection signal="pressed" from="TabContainer/Chat/HBoxContainer/ClearButton" to="." method="_on_clear_button_pressed"]
|
||||
[connection signal="pressed" from="TabContainer/Chat/HBoxContainer/DisconnectButton" to="." method="_on_disconnect_button_pressed"]
|
||||
[connection signal="pressed" from="HBoxContainer/SupportButton" to="." method="_on_support_button_pressed"]
|
||||
9
addons/very-simple-twitch/chat/vst_chat_dock_line.gd
Normal file
@@ -0,0 +1,9 @@
|
||||
@tool
|
||||
extends HBoxContainer
|
||||
|
||||
func set_chatter_msg(badges: String, chatter: VSTChatter):
|
||||
set_chatter_string("%02d:%02d" %[chatter.date_time_dict["hour"], chatter.date_time_dict["minute"]] + " " + badges + " [b][color="+ chatter.tags.color_hex + "]" +chatter.tags.display_name +"[/color][/b]: " + chatter.message)
|
||||
|
||||
func set_chatter_string(message:String):
|
||||
$RichTextLabel.text = message
|
||||
queue_sort()
|
||||
1
addons/very-simple-twitch/chat/vst_chat_dock_line.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://u504g1u250up
|
||||
16
addons/very-simple-twitch/chat/vst_chat_dock_line.tscn
Normal file
@@ -0,0 +1,16 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bo1un8cwcrpqr"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/very-simple-twitch/chat/vst_chat_dock_line.gd" id="1_h0lnd"]
|
||||
|
||||
[node name="VstChatDockLine" type="HBoxContainer"]
|
||||
offset_right = 1.0
|
||||
offset_bottom = 115.0
|
||||
size_flags_horizontal = 3
|
||||
script = ExtResource("1_h0lnd")
|
||||
|
||||
[node name="RichTextLabel" type="RichTextLabel" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
bbcode_enabled = true
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
32
addons/very-simple-twitch/doc/Errors.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Errors
|
||||
## Information
|
||||
This page is to explain the use and implementation of errors in VST. Errors have 3 parameters for code, description and extra information.
|
||||
|
||||
* error_code (enum @see VST_Error.VST_Code_Error) -> This parameter groups the types of errors in a general way.
|
||||
* description (String) -> The description of the error is a general description of the general grouping in human language.
|
||||
* info (String) -> The information is the particular description of the error. It should be used to add extra information about the particular error.
|
||||
|
||||
## New errors
|
||||
To add new errors you must first ask yourself if it is necessary to add a new code or not. If necessary add the code in the VST_Error.VST_Code_Error enumeration.
|
||||
To illustrate the error let's imagine that we have a function that updates the area of a triangle given a base and a height. This function updates an area parameter only if it is possible to calculate the area (the base or the height is greater than 0).
|
||||
|
||||
```GDScript
|
||||
func calculateArea(base:int, heigth:int):
|
||||
if base <= 0:
|
||||
var error:VST_Error = VST_Error.new(VST_Error.VST_Code_Error.PARAM_ERROR, "base can't be less than or equals 0")
|
||||
push_warning(str(error))
|
||||
area = 0
|
||||
elif height <= 0:
|
||||
var error:VST_Error = VST_Error.new(VST_Error.VST_Code_Error.PARAM_ERROR, "height can't be less than or equals 0")
|
||||
push_warning(str(error))
|
||||
area = 0
|
||||
else
|
||||
area = (base*height)/2
|
||||
```
|
||||
|
||||
## Error Codes
|
||||
|
||||
* PARAM_ERROR Use this code for errors that have to do with invalid parameters. An int that should be a String or a String that cannot be empty.
|
||||
* TIMEOUT_ERROR Use this code when a timer runs out or there is no response from a system.
|
||||
* NETWORK_ERROR Use this code when there is a network error ( > 400 and < 500)
|
||||
* SERVER_ERROR Use this code when, in a network communication, the server is down or has a problem ( > 500 ).
|
||||
46
addons/very-simple-twitch/doc/Network.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Network
|
||||
## Information
|
||||
The motivation for using this wrapper is to make easier the call APIs and gather responses and errors from the server. The idea is to make it as verbose as possible using a builder pattern so the request can be read at first glance.
|
||||
|
||||
Before making the network call, the wrapper will check a number of conditions within the "check_request_data" method which will throw a number of errors or warnings if the request is not well built. The wrapper is permissive with the definition of REST APIs, so you can have a GET call with body or a POST with GET parameters in the url (TBD).
|
||||
|
||||
Note: To achieve this effect, it is necessary to call new() and pass a node to the "launch_request" method, which will self manage all that's necessary for it to work.
|
||||
|
||||
## Usage
|
||||
To use the wrapper for network requests simply build a Network_Call object and start configuring it with the to, with, etc... methods.
|
||||
|
||||
* to (String) -> url destination
|
||||
* with (String) -> request body
|
||||
* add_get_param (String,String) y add_all_get_param(Dictionary) -> add get params to request
|
||||
* add_header(String,String) y add_all_header(Dictionary) -> add headers to request
|
||||
* verb(Http_Method) -> set the method for REST request
|
||||
* in_time(int) -> set the timeout time to the request
|
||||
* set_on_call_success (Callable) -> call this function if the request was ok (200)
|
||||
* set_on_call_fail (Callable) -> call this function if the request was fail (>400)
|
||||
* no_cache -> set no cache for request ( only used in GET requests )
|
||||
|
||||
## Example
|
||||
```GDScript
|
||||
func _onReady():
|
||||
Network_Call.new().to("https://catfact.ninja/fact").set_on_call_success(on_cat_fact).set_on_call_fail(on_error).launch_request(self)
|
||||
|
||||
func on_cat_fact(result):
|
||||
$Label.text = str(result)
|
||||
|
||||
func on_error(error):
|
||||
$Label.text = "Error requesting fact about cats :("
|
||||
```
|
||||
## Cache
|
||||
The network module has a cache for GET requests to save time and bandwidth for similar requests in 'short' time spans.
|
||||
|
||||
Since nodes are ephemeral (network requests are nodes that disappear) the cache is stored on disk and not in memory (this resposability was left for the upper layers).
|
||||
|
||||
The cache works as follows:
|
||||
* 1. The request is hashed ( using the url )
|
||||
* 2. A file with the specific name ( the hash ) is searched for on disk.
|
||||
* 3.a If it exists and it has not passed 300 seconds ( arbitrary and improvable time using an etag? ) the request is resolved and returns the file content.
|
||||
* 3.b If the file does not exist or it has been more than 300 seconds, the request is made and cached using the same hash.
|
||||
|
||||
The time validity of this cache is on CACHE_TIME_IN_SECONDS constant in network_call.gd
|
||||
|
||||
The cached content is an array of bytes due to the heterogeneous nature of the possible requests (text, images, sound...).
|
||||
21
addons/very-simple-twitch/doc/Testing.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Testing
|
||||
|
||||
## Adding test
|
||||
|
||||
Use "assert_eq" for "primitive" values and "assert_eq_deep" for complex data
|
||||
|
||||
For add some test be sure:
|
||||
1. Your test are inside /test folder
|
||||
2. Your test file starts with test name
|
||||
3. Your test script extends from "GutTest"
|
||||
4. Your test methods starts also with test
|
||||
|
||||
You can use some useful methods for testing like:
|
||||
* before_all -> Excecuted before all tests in the script
|
||||
* before_each -> Excecuted before each test in the script
|
||||
* after_each -> Excecuted after each test in the script
|
||||
* after_all -> Excecuted after all tests in the script
|
||||
|
||||
|
||||
## GUT Documentation
|
||||
https://gut.readthedocs.io/en/latest/
|
||||
32
addons/very-simple-twitch/doc/develop.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Develop
|
||||
|
||||
## GDLint
|
||||
The idea behind installing a linter in this plugin is mainly code readability. That can make collaboration easier for other developers. Using the same consistent coding style makes it easier to collaborate with others and easier to understand what the plugin is doing.
|
||||
|
||||
### Installation
|
||||
|
||||
There are a few steps before you can use GDLint. You can consult the official documentation, but here's a summary
|
||||
|
||||
1. GDLinter is installed on addons folders. You don't need do nothing with it.
|
||||
2. Deactivate GDLint from pluggins ( project -> configuration -> plugins )
|
||||
3. Check your python version using 'python --version' or 'py --version'
|
||||
- no version installed?
|
||||
- Check windows store for windows ( best option in my opinion )
|
||||
- On Mac 'brew install python' using Homebrew
|
||||
- On Linux, you can use APT 'sudo apt install python3'
|
||||
4. Install godot toolkit using 'pip3 install "gdtoolkit==4.*"'
|
||||
- No pip installed? Download the script, from https://bootstrap.pypa.io/get-pip.py and type 'python get-pip.py' to install it.
|
||||
5. Check gdlint version with 'gdlint --version'
|
||||
- Nothing or error showed? try repeating the steps from 2
|
||||
6. Activate GdLint again.
|
||||
- If GDLint menu at the bottom doesnt appear, relaunch godot.
|
||||
|
||||
|
||||
GDScript Toolkit Documentation -> https://github.com/Scony/godot-gdscript-toolkit
|
||||
GDLinter Addon Documentation -> https://github.com/el-falso/gdlinter/
|
||||
|
||||
### Usage
|
||||
|
||||
As soon as you install the plugin and the toolkit you will see a menu at the bottom called GDLint. There it will show the problems with the code :)
|
||||
|
||||

|
||||
BIN
addons/very-simple-twitch/doc/img/gdlint-usage-1.png
Normal file
|
After Width: | Height: | Size: 176 KiB |
40
addons/very-simple-twitch/doc/img/gdlint-usage-1.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://beqmxv0pbk5dp"
|
||||
path="res://.godot/imported/gdlint-usage-1.png-06a3e1d7a5f85d1fd83e80b21ea14d73.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/very-simple-twitch/doc/img/gdlint-usage-1.png"
|
||||
dest_files=["res://.godot/imported/gdlint-usage-1.png-06a3e1d7a5f85d1fd83e80b21ea14d73.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
62
addons/very-simple-twitch/dock/vst-dock.gd
Normal file
@@ -0,0 +1,62 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
@onready var copy_button: Button = %CopyButton
|
||||
@onready var redirect_uri = %RedirectURI
|
||||
@onready var client_id_line_edit = %ClientIDLineEdit
|
||||
@onready var client_id_warning = %ClientIDWarning
|
||||
@onready var help_icon = %HelpIcon
|
||||
@onready var warning_icon = %WarningIcon
|
||||
|
||||
|
||||
func _ready():
|
||||
help_icon.texture = get_theme_icon("Help", "EditorIcons")
|
||||
warning_icon.texture = get_theme_icon("StatusWarning", "EditorIcons")
|
||||
copy_button.icon = get_theme_icon("ActionCopy", "EditorIcons")
|
||||
copy_button.tooltip_text = "Copy Redirect URL to clipboard"
|
||||
client_id_warning.add_theme_color_override(
|
||||
"font_color",
|
||||
EditorInterface.get_editor_settings()\
|
||||
.get_setting("text_editor/theme/highlighting/comment_markers/warning_color")
|
||||
)
|
||||
|
||||
visibility_changed.connect(on_visibility_changed)
|
||||
|
||||
|
||||
func on_visibility_changed():
|
||||
if visible:
|
||||
update_visuals()
|
||||
ProjectSettings.settings_changed.connect(update_visuals)
|
||||
else:
|
||||
if ProjectSettings.settings_changed.is_connected(update_visuals):
|
||||
ProjectSettings.settings_changed.disconnect(update_visuals)
|
||||
|
||||
|
||||
func update_visuals():
|
||||
var client_id = VSTSettings.get_setting(VSTSettings.settings.client_id)
|
||||
var redirect_host = VSTSettings.get_setting(VSTSettings.settings.redirect_host)
|
||||
var redirect_port = VSTSettings.get_setting(VSTSettings.settings.redirect_port)
|
||||
var uuid = VSTSettings.get_setting(VSTSettings.settings.uuid)
|
||||
redirect_uri.text = redirect_host + str(redirect_port) + "/" + uuid
|
||||
client_id_line_edit.text = client_id
|
||||
if client_id == "":
|
||||
client_id_warning.show()
|
||||
warning_icon.show()
|
||||
else:
|
||||
client_id_warning.hide()
|
||||
warning_icon.hide()
|
||||
|
||||
|
||||
func copy_redirect_uri():
|
||||
DisplayServer.clipboard_set(redirect_uri.text)
|
||||
|
||||
|
||||
func client_id_submitted():
|
||||
ProjectSettings.set_setting(
|
||||
"very_simple_twitch/"+VSTSettings.settings.client_id.path,
|
||||
client_id_line_edit.text
|
||||
)
|
||||
ProjectSettings.save()
|
||||
|
||||
func open_url(meta):
|
||||
OS.shell_open(meta)
|
||||
1
addons/very-simple-twitch/dock/vst-dock.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bn73uslhjp8aj
|
||||
111
addons/very-simple-twitch/dock/vst-dock.tscn
Normal file
@@ -0,0 +1,111 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://m77ohpfa7qef"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/very-simple-twitch/dock/vst-dock.gd" id="1_kyfdh"]
|
||||
|
||||
[sub_resource type="Image" id="Image_cb0pc"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_oh773"]
|
||||
image = SubResource("Image_cb0pc")
|
||||
|
||||
[node name="vst-dock" type="VBoxContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_kyfdh")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HelpIcon" type="TextureRect" parent="HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
texture = SubResource("ImageTexture_oh773")
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="RichTextLabel2" type="RichTextLabel" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
focus_mode = 2
|
||||
bbcode_enabled = true
|
||||
text = " [url=https://github.com/rothiotome/godot-very-simple-twitch?tab=readme-ov-file#quick-setup]Quick Setup section in the readme[/url] and follow the steps in there.
|
||||
"
|
||||
fit_content = true
|
||||
selection_enabled = true
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
text = "Quick Start Guide"
|
||||
|
||||
[node name="RedirectURI" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="RedirectURI"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Redirect URI: "
|
||||
|
||||
[node name="RedirectURIContainer" type="HBoxContainer" parent="RedirectURI"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="RedirectURI" type="RichTextLabel" parent="RedirectURI/RedirectURIContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
focus_mode = 2
|
||||
text = "Value"
|
||||
fit_content = true
|
||||
selection_enabled = true
|
||||
|
||||
[node name="CopyButton" type="Button" parent="RedirectURI/RedirectURIContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Copy Redirect URL to clipboard"
|
||||
icon = SubResource("ImageTexture_oh773")
|
||||
|
||||
[node name="ClientID" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="ClientID"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Client ID:"
|
||||
|
||||
[node name="ClientIDLineEdit" type="LineEdit" parent="ClientID"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
placeholder_text = "YOUR CLIENT ID HERE"
|
||||
|
||||
[node name="Warning" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="WarningIcon" type="TextureRect" parent="Warning"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
texture = SubResource("ImageTexture_oh773")
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="ClientIDWarning" type="Label" parent="Warning"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.72, 0.61, 0.48, 1)
|
||||
text = "To use VST, you need a Client ID. Check the readme above for how to get one."
|
||||
|
||||
[connection signal="meta_clicked" from="HBoxContainer/RichTextLabel2" to="." method="open_url"]
|
||||
[connection signal="pressed" from="RedirectURI/RedirectURIContainer/CopyButton" to="." method="copy_redirect_uri"]
|
||||
[connection signal="focus_exited" from="ClientID/ClientIDLineEdit" to="." method="client_id_submitted"]
|
||||
[connection signal="text_submitted" from="ClientID/ClientIDLineEdit" to="." method="client_id_submitted"]
|
||||
15
addons/very-simple-twitch/emote_location.gd
Normal file
@@ -0,0 +1,15 @@
|
||||
class_name VSTEmoteLocation
|
||||
|
||||
extends RefCounted
|
||||
|
||||
var id : String
|
||||
var start : int
|
||||
var end : int
|
||||
|
||||
func _init(emote_id, start_idx, end_idx):
|
||||
self.id = emote_id
|
||||
self.start = start_idx
|
||||
self.end = end_idx
|
||||
|
||||
static func smaller(a: VSTEmoteLocation, b: VSTEmoteLocation):
|
||||
return a.start < b.start
|
||||
1
addons/very-simple-twitch/emote_location.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dc4gv3t0sj482
|
||||
BIN
addons/very-simple-twitch/icon.png
Normal file
|
After Width: | Height: | Size: 215 B |
40
addons/very-simple-twitch/icon.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://crhtjoakp6j05"
|
||||
path="res://.godot/imported/icon.png-952ca81aead1a8efe9432e636ee0d5ac.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/very-simple-twitch/icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-952ca81aead1a8efe9432e636ee0d5ac.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
19
addons/very-simple-twitch/models/chatter.gd
Normal file
@@ -0,0 +1,19 @@
|
||||
class_name VSTChatter
|
||||
|
||||
var date_time_dict: Dictionary
|
||||
var login: String
|
||||
var channel: String
|
||||
var message: String
|
||||
var tags: VSTIRCTags
|
||||
|
||||
|
||||
func is_mod() -> bool:
|
||||
return tags.badges.has("moderator")
|
||||
|
||||
|
||||
func is_sub() -> bool:
|
||||
return tags.badges.has("subscriber")
|
||||
|
||||
|
||||
func is_broadcaster() -> bool:
|
||||
return tags.badges.has("broadcaster")
|
||||
1
addons/very-simple-twitch/models/chatter.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b5jggcdr67bax
|
||||
13
addons/very-simple-twitch/models/irc_tags.gd
Normal file
@@ -0,0 +1,13 @@
|
||||
class_name VSTIRCTags
|
||||
|
||||
# Model for a IRC twitch chat
|
||||
var color_hex: String # color of user used in twtich chat
|
||||
var display_name: String # name of a user
|
||||
var channel_id: String # not used
|
||||
var user_id: String # numeric id of the user used in twitch
|
||||
|
||||
var badges: Dictionary # badges of the user in message
|
||||
var emotes: Dictionary # emotes writed by user in message
|
||||
|
||||
func _to_string():
|
||||
return "color_hex: %s, display_name: %s, channel_id: %s, user_id: %s, badges: %s, emotes: %s" % [color_hex, display_name, str(channel_id), str(user_id), badges, emotes]
|
||||
1
addons/very-simple-twitch/models/irc_tags.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dngsmhmc1s3ts
|
||||
5
addons/very-simple-twitch/models/twitch_channel.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
class_name VSTChannel
|
||||
|
||||
var login: String
|
||||
var id: String
|
||||
var token: String
|
||||
1
addons/very-simple-twitch/models/twitch_channel.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b5hhkjxmiuh35
|
||||
198
addons/very-simple-twitch/network_call.gd
Normal file
@@ -0,0 +1,198 @@
|
||||
@tool
|
||||
class_name VSTNetwork_Call extends Node
|
||||
|
||||
const CACHE_TIME_IN_SECONDS = 300
|
||||
|
||||
# TODO: add dynamic version here
|
||||
const USER_AGENT = {"User-Agent": "VSTC/0.1.0 (Godot Engine)"}
|
||||
|
||||
const CACHE_PATH = "user://very-simple-chat/cache"
|
||||
|
||||
var url: String
|
||||
var timeout: float
|
||||
var body: String
|
||||
var headers: Dictionary
|
||||
var get_params: Dictionary
|
||||
var on_call_success: Callable
|
||||
var on_call_fail: Callable
|
||||
var method: HTTPClient.Method
|
||||
var use_cache: bool
|
||||
|
||||
|
||||
func _init():
|
||||
headers = {}
|
||||
get_params = {}
|
||||
timeout = 0.0
|
||||
use_cache = true
|
||||
|
||||
|
||||
func to(url_request: String) -> VSTNetwork_Call:
|
||||
url = url_request
|
||||
return self
|
||||
|
||||
|
||||
func with(body_object) -> VSTNetwork_Call:
|
||||
body = JSON.stringify(body_object)
|
||||
return self
|
||||
|
||||
|
||||
func in_time(timeout_request: float) -> VSTNetwork_Call:
|
||||
timeout = timeout_request
|
||||
return self
|
||||
|
||||
|
||||
func verb(method_request: HTTPClient.Method) -> VSTNetwork_Call:
|
||||
method = method_request
|
||||
return self
|
||||
|
||||
|
||||
func no_cache() -> VSTNetwork_Call:
|
||||
use_cache = false
|
||||
return self
|
||||
|
||||
|
||||
func add_header(key_header: String, value_header: String) -> VSTNetwork_Call:
|
||||
headers[key_header] = value_header
|
||||
return self
|
||||
|
||||
|
||||
func add_all_headers(headers_dic: Dictionary) -> VSTNetwork_Call:
|
||||
for key in headers_dic:
|
||||
headers[key] = headers_dic[key]
|
||||
return self
|
||||
|
||||
|
||||
func add_get_param(key_get_param:String, value_get_param) -> VSTNetwork_Call:
|
||||
get_params[key_get_param] = str(value_get_param)
|
||||
return self
|
||||
|
||||
|
||||
func add_all_get_params(get_params_dic: Dictionary) -> VSTNetwork_Call:
|
||||
for key in get_params_dic:
|
||||
get_params[key] = get_params_dic[key]
|
||||
return self
|
||||
|
||||
|
||||
func set_on_call_success(on_call_success_request: Callable) -> VSTNetwork_Call:
|
||||
on_call_success = on_call_success_request
|
||||
return self
|
||||
|
||||
|
||||
func set_on_call_fail(on_call_fail_request: Callable) -> VSTNetwork_Call:
|
||||
on_call_fail = on_call_fail_request
|
||||
return self
|
||||
|
||||
|
||||
func _pile_headers(headers_to_pile: Dictionary) -> PackedStringArray:
|
||||
var array:PackedStringArray = []
|
||||
for key in headers_to_pile:
|
||||
array.append("%s: %s" % [key, headers_to_pile[key]])
|
||||
for key in USER_AGENT:
|
||||
array.append("%s: %s" % [key, USER_AGENT[key]])
|
||||
return array
|
||||
|
||||
|
||||
func _compile_url(host: String, params_to_pile: Dictionary) -> String:
|
||||
var final_url:String = host.strip_edges()+"?"
|
||||
for key in params_to_pile:
|
||||
var value_safe = str(params_to_pile[key]).uri_encode()
|
||||
var key_safe = str(key).uri_encode()
|
||||
final_url += "&%s=%s" % [key_safe, value_safe]
|
||||
return final_url
|
||||
|
||||
|
||||
func launch_request(parent: Node):
|
||||
if method == HTTPClient.Method.METHOD_GET:
|
||||
var final_url = _compile_url(url, get_params)
|
||||
if use_cache:
|
||||
var cached_data = read_from_cache(get_key_from_url(final_url))
|
||||
if !cached_data.is_empty() && on_call_success != null:
|
||||
on_call_success.call(cached_data)
|
||||
return
|
||||
_launch_network_request(parent)
|
||||
|
||||
|
||||
func _launch_network_request(parent: Node):
|
||||
var data_error = check_request_data()
|
||||
if data_error != "":
|
||||
var error:VSTError = VSTError.new(VSTError.VSTCodeError.PARAM_ERROR, data_error)
|
||||
on_call_fail.call(error)
|
||||
return
|
||||
|
||||
parent.add_child(self)
|
||||
await get_tree().process_frame
|
||||
|
||||
var final_url = _compile_url(url, get_params)
|
||||
var client = HTTPRequest.new()
|
||||
client.timeout = timeout
|
||||
client.request_completed.connect(func():
|
||||
on_request_completed.bind(final_url)
|
||||
client.queue_free()
|
||||
)
|
||||
|
||||
add_child(client)
|
||||
await get_tree().process_frame
|
||||
|
||||
var request_error = client.request(final_url, _pile_headers(headers), method, body)
|
||||
if request_error != OK:
|
||||
var error:VSTError = VSTError.new(VSTError.VSTCodeError.PARAM_ERROR, "The request can't be archieved reason: "+str(request_error))
|
||||
on_call_fail.call(error)
|
||||
client.queue_free()
|
||||
return
|
||||
|
||||
|
||||
func check_request_data() -> String:
|
||||
if timeout < 0.0:
|
||||
push_warning("Timeout can't be less than 0. Setted to 0")
|
||||
timeout = 0
|
||||
|
||||
if !method:
|
||||
method = HTTPClient.Method.METHOD_GET
|
||||
|
||||
if url == null or url.strip_edges() == "":
|
||||
return "Url can't be empty"
|
||||
return ""
|
||||
|
||||
|
||||
func get_key_from_url(url:String) -> String:
|
||||
var last_part_url:String = url.substr(url.rfind("/"))
|
||||
return last_part_url.sha256_text()
|
||||
|
||||
|
||||
func on_request_completed(_result: int, status: int, _headers: PackedStringArray, body: PackedByteArray, url_request: String):
|
||||
if (status >= 200 and status < 400):
|
||||
if method == HTTPClient.Method.METHOD_GET: update_cache(body, get_key_from_url(url_request))
|
||||
if on_call_success: on_call_success.call(body)
|
||||
elif (status >= 400 and status < 500):
|
||||
if on_call_fail:
|
||||
var info = "%s -> %s" % [str(status), body.get_string_from_utf8()]
|
||||
var error:VSTError = VSTError.new(VSTError.VSTCodeError.NETWORK_ERROR, info)
|
||||
on_call_fail.call(error)
|
||||
elif (status >= 500):
|
||||
if on_call_fail:
|
||||
var info = "%s -> %s" % [str(status), body.get_string_from_utf8()]
|
||||
var error:VSTError = VSTError.new(VSTError.VSTCodeError.SERVER_ERROR, info)
|
||||
on_call_fail.call(error)
|
||||
call_deferred("queue_free")
|
||||
|
||||
func read_from_cache(key:String) -> PackedByteArray:
|
||||
var filename: String = CACHE_PATH.path_join(key)
|
||||
if FileAccess.file_exists(filename): # is a hit on cache?
|
||||
if FileAccess.get_modified_time(filename)+CACHE_TIME_IN_SECONDS > Time.get_unix_time_from_system(): # is expired?
|
||||
return FileAccess.get_file_as_bytes(filename)
|
||||
return []
|
||||
|
||||
|
||||
func update_cache(content:PackedByteArray, key:String):
|
||||
var filename: String = CACHE_PATH.path_join(key)
|
||||
DirAccess.make_dir_recursive_absolute(filename.get_base_dir())
|
||||
var file = FileAccess.open(filename, FileAccess.WRITE)
|
||||
file.store_buffer(content)
|
||||
file.close()
|
||||
|
||||
|
||||
func clear_cache():
|
||||
DirAccess.make_dir_recursive_absolute(CACHE_PATH)
|
||||
var files:PackedStringArray = DirAccess.get_files_at(CACHE_PATH)
|
||||
for file in files:
|
||||
DirAccess.remove_absolute(CACHE_PATH.path_join(file))
|
||||
1
addons/very-simple-twitch/network_call.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://eq8m4h87n1l5
|
||||
74
addons/very-simple-twitch/parse_helper.gd
Normal file
@@ -0,0 +1,74 @@
|
||||
class_name VSTParseHelper
|
||||
|
||||
# Parse login name from payload substring of twitch irc chat
|
||||
static func parse_login(input_string:String) -> String:
|
||||
return get_substring(input_string, ":", "!")
|
||||
|
||||
|
||||
# Parse channel name from payload substring of twitch irc chat
|
||||
static func parse_channel(input_string:String) -> String:
|
||||
return input_string.trim_prefix("#")
|
||||
|
||||
|
||||
# Parse message from payload substring of twitch irc chat
|
||||
static func parse_message(input_string:String) -> String:
|
||||
return input_string.trim_prefix(":").strip_edges()
|
||||
|
||||
|
||||
static func parse_tags(input_string:String) -> VSTIRCTags:
|
||||
var irc_tags = VSTIRCTags.new()
|
||||
var tags:PackedStringArray = input_string.split(";")
|
||||
|
||||
for i in len(tags):
|
||||
var splitted_tag:PackedStringArray = tags[i].split("=")
|
||||
|
||||
if splitted_tag.size() <= 1: continue
|
||||
|
||||
match(splitted_tag[0].strip_edges()):
|
||||
"badges":
|
||||
irc_tags.badges = parse_badges(splitted_tag[1].split(","))
|
||||
"color":
|
||||
irc_tags.color_hex = splitted_tag[1]
|
||||
"display-name":
|
||||
irc_tags.display_name = splitted_tag[1]
|
||||
"emotes":
|
||||
irc_tags.emotes = parse_emotes(splitted_tag[1].split("/"))
|
||||
"room-id":
|
||||
irc_tags.user_id = splitted_tag[1]
|
||||
|
||||
return irc_tags
|
||||
|
||||
|
||||
# Parse badges from payload substring of twitch irc chat. Returns a dictionary with the badge itself
|
||||
# and the position of the badge
|
||||
static func parse_badges(input:PackedStringArray) -> Dictionary:
|
||||
var badges: Dictionary = {}
|
||||
if input.is_empty() || input[0].is_empty(): return badges
|
||||
|
||||
for i in len(input):
|
||||
var substrings = input[i].split("/")
|
||||
if len(substrings) > 1:
|
||||
badges[substrings[0]] = substrings[1]
|
||||
return badges
|
||||
|
||||
|
||||
# Parse emotes from payload substring of twitch irc chat. Returns a dictionary with the emote
|
||||
# itself and the position in the user message in order to replace the text with the image emote
|
||||
static func parse_emotes(input:PackedStringArray) -> Dictionary:
|
||||
var emotes: Dictionary = {}
|
||||
if input.is_empty() || input[0].is_empty(): return emotes
|
||||
for emote in input:
|
||||
var substring: PackedStringArray = emote.split(":")
|
||||
if len(substring) > 1:
|
||||
emotes[substring[0]] = substring[1]
|
||||
return emotes
|
||||
|
||||
|
||||
|
||||
static func get_substring(input_string:String, starting_char:String, ending_char:String) -> String:
|
||||
var first_index = input_string.find(starting_char)
|
||||
var last_index = input_string.find(ending_char)
|
||||
|
||||
if first_index == -1 or last_index == -1 or last_index < first_index:
|
||||
return input_string
|
||||
return input_string.substr(first_index + 1, last_index - first_index - 1)
|
||||
1
addons/very-simple-twitch/parse_helper.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ccyfmynot5j4y
|
||||
7
addons/very-simple-twitch/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="Very Simple Twitch"
|
||||
description="Godot websocket implementation for Twitch Chat using OICD Authentication flow"
|
||||
author="RothioTome & collaborators"
|
||||
version="0.1.0"
|
||||
script="vst.gd"
|
||||
26
addons/very-simple-twitch/public/index.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv='cache-control' content='no-cache'>
|
||||
<meta http-equiv='expires' content='0'>
|
||||
<meta http-equiv='pragma' content='no-cache'>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<h1 id="title">Login...</h1>
|
||||
|
||||
<script>
|
||||
(async () => {
|
||||
const AUTH_URL = 'http://localhost:8090';
|
||||
const hashKeyValue = location.hash.replace('#', '&').split('&');
|
||||
const tokenKeyValue = hashKeyValue.find((item) => item.split('=')[0] === 'access_token');
|
||||
const token = tokenKeyValue.split('=')[1];
|
||||
fetch(AUTH_URL + `?token=${token}`, { method: 'POST' }).then((e) => {document.getElementById("title").innerHTML = "Everything seems to be OK. You can close this window.";} ).catch((error) => {document.getElementById("title").innerHTML = "ERROR: "+JSON.stringify(error);});
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
164
addons/very-simple-twitch/twitch_api.gd
Normal file
@@ -0,0 +1,164 @@
|
||||
class_name VSTAPI
|
||||
|
||||
extends Node
|
||||
|
||||
signal token_received(TwitchChannel)
|
||||
|
||||
const RESPONSE_TYPE = 'token'
|
||||
|
||||
const TWITCH_VALIDATE_URL = "https://id.twitch.tv/oauth2/validate"
|
||||
const TWITCH_BAN_URL = "https://api.twitch.tv/helix/moderation/bans"
|
||||
const TWITCH_OAUTH_URL = "https://id.twitch.tv/oauth2/authorize"
|
||||
const TWITCH_VIP_URL = "https://api.twitch.tv/helix/channels/vips"
|
||||
const TWITCH_SETTINGS_URL = "https://api.twitch.tv/helix/chat/settings"
|
||||
const TWITCH_CHATTERS_URL = "https://api.twitch.tv/helix/chat/chatters"
|
||||
|
||||
|
||||
var auth_server: VSTAuthServer
|
||||
|
||||
var _scopes: PackedStringArray
|
||||
var _client_id: String
|
||||
var _user: VSTChannel
|
||||
|
||||
func initiate_twitch_auth():
|
||||
_scopes = VSTSettings.get_setting(VSTSettings.settings.scopes)
|
||||
_client_id = VSTSettings.get_setting(VSTSettings.settings.client_id)
|
||||
var redirect_host = VSTSettings.get_setting(VSTSettings.settings.redirect_host)
|
||||
var redirect_port = VSTSettings.get_setting(VSTSettings.settings.redirect_port)
|
||||
var uuid = VSTSettings.get_setting(VSTSettings.settings.uuid)
|
||||
|
||||
if auth_server:
|
||||
disconnect_api()
|
||||
|
||||
auth_server = VSTAuthServer.new()
|
||||
add_child(auth_server)
|
||||
auth_server.OnTokenReceived.connect(_on_auth_server_on_token_received)
|
||||
auth_server.start_server(redirect_port)
|
||||
|
||||
var redirect_uri = redirect_host + str(redirect_port) + "/" + uuid
|
||||
var scopes_string = "+".join(_scopes)
|
||||
var url = "client_id=" + _client_id
|
||||
url += "&redirect_uri=" +redirect_uri
|
||||
url += "&response_type=" + RESPONSE_TYPE
|
||||
url += "&scope=" + scopes_string
|
||||
|
||||
OS.shell_open(TWITCH_OAUTH_URL + "?" + url)
|
||||
|
||||
func _on_auth_server_on_token_received(token) -> void:
|
||||
var validated_user = await validate_token_and_get_user_id(token)
|
||||
if !(validated_user):
|
||||
print('Invalid token')
|
||||
_user = null
|
||||
return
|
||||
_user = validated_user
|
||||
token_received.emit(_user)
|
||||
|
||||
func request_fail(status:int, error: VSTError, on_fail: Callable):
|
||||
if status == 401 or status == 403:
|
||||
#Unauthorized? No mi ciela
|
||||
initiate_twitch_auth()
|
||||
# TODO: should chain the request?
|
||||
else:
|
||||
push_warning(error)
|
||||
on_fail.call()
|
||||
|
||||
func validate_token_and_get_user_id(token: String):
|
||||
var client = HTTPRequest.new()
|
||||
add_child(client)
|
||||
client.request(TWITCH_VALIDATE_URL, [
|
||||
'Authorization: OAuth ' + token
|
||||
])
|
||||
var result = await client.request_completed
|
||||
var status = result[1]
|
||||
if status != 200:
|
||||
return null
|
||||
var data = (result[3] as PackedByteArray).get_string_from_utf8()
|
||||
var data_parsed = JSON.parse_string(data)
|
||||
var user = VSTChannel.new()
|
||||
user.id = data_parsed['user_id']
|
||||
user.login = data_parsed['login']
|
||||
user.token = token
|
||||
client.queue_free()
|
||||
return user
|
||||
|
||||
func timeout_user(user_to_ban_id: String, duration: int = 1, reason: String = '',
|
||||
on_success: Callable = Callable(), on_fail: Callable = Callable()) -> void:
|
||||
if !_user:
|
||||
return
|
||||
|
||||
var timeout_duration = max(duration, 1)
|
||||
var body = {
|
||||
data = {
|
||||
user_id = user_to_ban_id,
|
||||
duration = timeout_duration,
|
||||
reason = reason
|
||||
},
|
||||
}
|
||||
|
||||
var vst = VSTNetwork_Call.new()
|
||||
vst.to(TWITCH_BAN_URL)
|
||||
vst.add_all_get_params({
|
||||
'broadcaster_id': _user.id,
|
||||
'moderator_id': _user.id
|
||||
}).\
|
||||
vst.with(body)
|
||||
vst.verb(HTTPClient.METHOD_POST)
|
||||
vst.add_all_headers({
|
||||
'Client-Id: ' : _client_id,
|
||||
'Authorization': 'Bearer ' + _user.token,
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
vst.set_on_call_success(on_success)
|
||||
vst.set_on_call_fail(request_fail.bind(on_fail))
|
||||
vst.launch_request(self)
|
||||
|
||||
|
||||
func add_vip(user_to_vip_id: String, on_success: Callable = Callable(),
|
||||
on_fail: Callable = Callable()):
|
||||
if !_user:
|
||||
return
|
||||
|
||||
var vst = VSTNetwork_Call.new()
|
||||
vst.to(TWITCH_VIP_URL)
|
||||
vst.add_all_get_params({
|
||||
'broadcaster_id': _user.id,
|
||||
'user_id': user_to_vip_id
|
||||
})
|
||||
vst.verb(HTTPClient.METHOD_POST)
|
||||
vst.add_all_headers({
|
||||
'Client-Id: ' : _client_id,
|
||||
'Authorization': 'Bearer ' + _user.token,
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
vst.set_on_call_success(on_success)
|
||||
vst.set_on_call_fail(request_fail.bind(on_fail))
|
||||
|
||||
vst.launch_request(self)
|
||||
|
||||
func remove_vip(user_to_remove_vip_id: String, on_success: Callable = Callable(),
|
||||
on_fail: Callable = Callable()) -> void:
|
||||
if !_user:
|
||||
return
|
||||
|
||||
var vst = VSTNetwork_Call.new()
|
||||
vst.to(TWITCH_VIP_URL)
|
||||
vst.add_all_get_params({
|
||||
'broadcaster_id': _user.id,
|
||||
'user_id': user_to_remove_vip_id
|
||||
})
|
||||
vst.verb(HTTPClient.METHOD_DELETE)
|
||||
vst.add_all_headers({
|
||||
'Client-Id: ' : _client_id,
|
||||
'Authorization': 'Bearer ' + _user.token,
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
vst.set_on_call_success(on_success)
|
||||
vst.set_on_call_fail(request_fail.bind(on_fail))
|
||||
|
||||
vst.launch_request(self)
|
||||
|
||||
|
||||
func disconnect_api():
|
||||
if auth_server:
|
||||
auth_server.stop_server()
|
||||
remove_child(auth_server)
|
||||
1
addons/very-simple-twitch/twitch_api.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://crv2n3rjn1tuf
|
||||
285
addons/very-simple-twitch/twitch_chat.gd
Normal file
@@ -0,0 +1,285 @@
|
||||
@tool
|
||||
class_name VSTChat extends Node
|
||||
|
||||
# TODO: rename to past simple?
|
||||
signal Connected(_channel)
|
||||
signal OnMessage(chatter: VSTChatter)
|
||||
|
||||
var _channel: VSTChannel
|
||||
|
||||
var _chatClient: WebSocketPeer
|
||||
var _hasConnected:= false
|
||||
|
||||
enum RequestType {
|
||||
EMOTE,
|
||||
BADGE,
|
||||
BADGE_MAPPING
|
||||
}
|
||||
|
||||
var caches := {
|
||||
RequestType.EMOTE: {},
|
||||
RequestType.BADGE: {},
|
||||
RequestType.BADGE_MAPPING: {}
|
||||
}
|
||||
|
||||
var _client_id: String
|
||||
var _twitch_chat_url: String
|
||||
var _twitch_chat_port: int
|
||||
|
||||
var _use_cache: bool
|
||||
var _cache_path: String
|
||||
|
||||
var _use_anon_connection:= false
|
||||
|
||||
var _chat_queue : Array[String] = []
|
||||
var _last_msg : int = Time.get_ticks_msec()
|
||||
var _chat_timeout_ms: int
|
||||
|
||||
const USER_AGENT : String = "User-Agent: VSTC/0.1.0 (Godot Engine)"
|
||||
|
||||
func _process(_delta: float):
|
||||
if !_chatClient:
|
||||
return
|
||||
|
||||
_chatClient.poll()
|
||||
var state = _chatClient.get_ready_state()
|
||||
match state:
|
||||
WebSocketPeer.STATE_OPEN:
|
||||
if (!_hasConnected):
|
||||
onChatConnected()
|
||||
while _chatClient.get_available_packet_count():
|
||||
onReceivedData(_chatClient.get_packet())
|
||||
if !_chat_queue.is_empty() and _last_msg + (_last_msg + _chat_timeout_ms) <= Time.get_ticks_msec():
|
||||
_chatClient.send_text(_chat_queue.pop_front())
|
||||
_last_msg = Time.get_ticks_msec()
|
||||
WebSocketPeer.STATE_CLOSED:
|
||||
if _hasConnected:
|
||||
_hasConnected = false
|
||||
var code = _chatClient.get_close_code()
|
||||
var reason = _chatClient.get_close_reason()
|
||||
print('Disconnected from twitch chat')
|
||||
print("WebSocket closed with code: %d, reason %s. Clean: %s" % [code, reason, code != -1])
|
||||
print("Reconnecting")
|
||||
start_chat_client()
|
||||
|
||||
func start_chat_client():
|
||||
get_settings()
|
||||
if _chatClient:
|
||||
_chatClient.close()
|
||||
_chatClient = WebSocketPeer.new()
|
||||
_chatClient.connect_to_url("%s:%d" % [_twitch_chat_url, _twitch_chat_port])
|
||||
|
||||
|
||||
func login_anon(channel_name: String):
|
||||
_channel = VSTChannel.new()
|
||||
_channel.login = channel_name.to_lower()
|
||||
_use_anon_connection = true
|
||||
start_chat_client()
|
||||
|
||||
|
||||
func login(twitch_channel: VSTChannel):
|
||||
_channel = twitch_channel
|
||||
start_chat_client()
|
||||
|
||||
|
||||
func onChatConnected():
|
||||
if !_channel:
|
||||
return
|
||||
_hasConnected = true
|
||||
|
||||
_chatClient.send_text("CAP REQ :twitch.tv/tags twitch.tv/commands")
|
||||
|
||||
if _use_anon_connection:
|
||||
_chatClient.send_text('PASS ' + VSTSettings.get_setting(VSTSettings.settings.twitch_anon_pass))
|
||||
_chatClient.send_text('NICK ' + VSTSettings.get_setting(VSTSettings.settings.twitch_anon_user))
|
||||
else:
|
||||
_chatClient.send_text('PASS oauth:' + _channel.token)
|
||||
_chatClient.send_text('NICK ' + _channel.login.to_lower())
|
||||
pass
|
||||
|
||||
_chatClient.send_text('JOIN ' + '#' + _channel.login.to_lower())
|
||||
Connected.emit()
|
||||
|
||||
func send_message(message: String):
|
||||
_chat_queue.append("PRIVMSG #" + _channel.login.to_lower() + " :" + message + "\r\n")
|
||||
|
||||
func onReceivedData(payload: PackedByteArray):
|
||||
var message = payload.get_string_from_utf8()
|
||||
var splittled_messages = message.split("\n")
|
||||
for n in splittled_messages:
|
||||
handle_message(n)
|
||||
|
||||
#TODO: move this to parse helper?
|
||||
func parse_message_from_twtich_IRC(message: String) -> PackedStringArray:
|
||||
return message.split(" ", true, 4) # We might need more than 3
|
||||
|
||||
func handle_message(message: String):
|
||||
if message.begins_with("PING"):
|
||||
_chatClient.send_text(message.replace("PING", "PONG"))
|
||||
return
|
||||
|
||||
var parsed_message: PackedStringArray = parse_message_from_twtich_IRC(message)
|
||||
|
||||
if parsed_message.size() < 2: return
|
||||
|
||||
match parsed_message[2]:
|
||||
"NOTICE":
|
||||
var info : String = parsed_message[3].right(-1)
|
||||
if (info == "Login authentication failed" || info == "Login unsuccessful"):
|
||||
print_debug("Authentication failed.")
|
||||
#login_attempt.emit(false)
|
||||
elif (info == "You don't have permission to perform that action"):
|
||||
print_debug("No permission. Check if access token is still valid. Aborting.")
|
||||
#user_token_invalid.emit()
|
||||
set_process(false)
|
||||
else:
|
||||
pass
|
||||
#unhandled_message.emit(message, tags)
|
||||
"001":
|
||||
print_debug("Authentication successful.")
|
||||
_chatClient.send_text('ROOMSTATE '+ '#' + _channel.login.to_lower())
|
||||
#login_attempt.emit(true)
|
||||
"PRIVMSG":
|
||||
handle_privmsg(parsed_message)
|
||||
#handle_command(sender_data, msg[3].split(" ", true, 1))
|
||||
#chat_message.emit(sender_data, msg[3].right(-1))
|
||||
"ROOMSTATE":
|
||||
if _use_anon_connection:
|
||||
var parsed_tags:VSTIRCTags = VSTParseHelper.parse_tags(parsed_message[0])
|
||||
_channel.id = parsed_tags.user_id
|
||||
|
||||
func parse_message_to_chatter(message: PackedStringArray) -> VSTChatter:
|
||||
var chatter = VSTChatter.new()
|
||||
chatter.login = VSTParseHelper.parse_login(message[1])
|
||||
chatter.channel = VSTParseHelper.parse_channel(message[3])
|
||||
chatter.message = VSTParseHelper.parse_message(message[4])
|
||||
chatter.tags = VSTParseHelper.parse_tags(message[0])
|
||||
chatter.date_time_dict = Time.get_datetime_dict_from_system()
|
||||
|
||||
if chatter.tags.color_hex.is_empty():
|
||||
chatter.tags.color_hex = VSTUtils.get_random_name_color(chatter.login)
|
||||
return chatter
|
||||
|
||||
|
||||
func handle_privmsg(msg: PackedStringArray):
|
||||
var chatter = parse_message_to_chatter(msg)
|
||||
OnMessage.emit(chatter)
|
||||
|
||||
|
||||
func get_emote(emote_id: String, scale: String = "1.0") -> Texture2D:
|
||||
var texture: Texture2D
|
||||
var cachename: String = emote_id + "_" + scale
|
||||
var filename: String = _cache_path + "/" + RequestType.keys()[RequestType.EMOTE] + "/" + cachename + ".png"
|
||||
|
||||
if !caches[RequestType.EMOTE].has(cachename):
|
||||
if _use_cache && FileAccess.file_exists(filename):
|
||||
var img: Image = Image.new()
|
||||
img.load_png_from_buffer(FileAccess.get_file_as_bytes(filename))
|
||||
texture = ImageTexture.create_from_image(img)
|
||||
texture.take_over_path(filename)
|
||||
else:
|
||||
var request: HTTPRequest = HTTPRequest.new()
|
||||
add_child(request)
|
||||
request.request("https://static-cdn.jtvnw.net/emoticons/v1/" + emote_id + "/" + scale, [USER_AGENT,"Accept: */*"])
|
||||
var data = await(request.request_completed)
|
||||
var img: Image = Image.new()
|
||||
img.load_png_from_buffer(data[3])
|
||||
texture = ImageTexture.create_from_image(img)
|
||||
if _use_cache:
|
||||
DirAccess.make_dir_recursive_absolute(filename.get_base_dir())
|
||||
texture.get_image().save_png(filename)
|
||||
request.queue_free()
|
||||
texture.take_over_path(filename)
|
||||
caches[RequestType.EMOTE][cachename] = texture
|
||||
return caches[RequestType.EMOTE][cachename]
|
||||
|
||||
func get_badge(badge_name: String, badge_level: String, channel_id: String = "_global", scale: String = "1") -> Texture2D:
|
||||
if _use_anon_connection: return
|
||||
|
||||
var texture: Texture2D
|
||||
var cachename = badge_name + "_" + badge_level + "_" + scale
|
||||
var filename: String = _cache_path + "/" + RequestType.keys()[RequestType.BADGE] + "/" + channel_id + "/" + cachename + ".png"
|
||||
if !caches[RequestType.BADGE].has(channel_id):
|
||||
caches[RequestType.BADGE][channel_id] = {}
|
||||
if !caches[RequestType.BADGE][channel_id].has(cachename):
|
||||
if _use_cache && FileAccess.file_exists(filename):
|
||||
var img : Image = Image.new()
|
||||
img.load_png_from_buffer(FileAccess.get_file_as_bytes(filename))
|
||||
texture = ImageTexture.create_from_image(img)
|
||||
texture.take_over_path(filename)
|
||||
else:
|
||||
var map: Dictionary = caches[RequestType.BADGE_MAPPING].get(channel_id, await(get_badge_mapping(channel_id)))
|
||||
if !map.is_empty():
|
||||
if map.has(badge_name):
|
||||
var request: HTTPRequest = HTTPRequest.new()
|
||||
add_child(request)
|
||||
request.request(map[badge_name]["versions"][badge_level]["image_url_" + scale + "x"], [USER_AGENT,"Accept: */*"])
|
||||
var data = await(request.request_completed)
|
||||
var img: Image = Image.new()
|
||||
img.load_png_from_buffer(data[3])
|
||||
texture = ImageTexture.create_from_image(img)
|
||||
texture.take_over_path(filename)
|
||||
request.queue_free()
|
||||
elif channel_id != "_global":
|
||||
return await(get_badge(badge_name, badge_level, "_global", scale))
|
||||
elif channel_id != "_global":
|
||||
return await(get_badge(badge_name, badge_level, "_global", scale))
|
||||
if _use_cache:
|
||||
DirAccess.make_dir_recursive_absolute(filename.get_base_dir())
|
||||
texture.get_image().save_png(filename)
|
||||
texture.take_over_path(filename)
|
||||
caches[RequestType.BADGE][channel_id][cachename] = texture
|
||||
return caches[RequestType.BADGE][channel_id][cachename]
|
||||
|
||||
func get_badge_mapping(channel_id: String = "_global") -> Dictionary:
|
||||
|
||||
if _use_anon_connection: return {}
|
||||
|
||||
if caches[RequestType.BADGE_MAPPING].has(channel_id):
|
||||
return caches[RequestType.BADGE_MAPPING][channel_id]
|
||||
|
||||
var filename: String = _cache_path + "/" + RequestType.keys()[RequestType.BADGE_MAPPING] + "/" + channel_id + ".json"
|
||||
if _use_cache && FileAccess.file_exists(filename):
|
||||
var cache = JSON.parse_string(FileAccess.get_file_as_string(filename))
|
||||
if "badge_sets" in cache:
|
||||
return cache["badge_sets"]
|
||||
|
||||
var request : HTTPRequest = HTTPRequest.new()
|
||||
add_child(request)
|
||||
|
||||
request.request("https://api.twitch.tv/helix/chat/badges" + ("/global" if channel_id == "_global" else "?broadcaster_id=" + _channel.id), [USER_AGENT, "Authorization: Bearer " + _channel.token, "Client-Id:" + _client_id, "Content-Type: application/json"], HTTPClient.METHOD_GET)
|
||||
|
||||
var reply : Array = await(request.request_completed)
|
||||
var response : Dictionary = JSON.parse_string(reply[3].get_string_from_utf8())
|
||||
var mappings : Dictionary = {}
|
||||
for entry in response["data"]:
|
||||
if (!mappings.has(entry["set_id"])):
|
||||
mappings[entry["set_id"]] = {"versions": {}}
|
||||
for version in entry["versions"]:
|
||||
mappings[entry["set_id"]]["versions"][version["id"]] = version
|
||||
request.queue_free()
|
||||
if (reply[1] == HTTPClient.RESPONSE_OK):
|
||||
caches[RequestType.BADGE_MAPPING][channel_id] = mappings
|
||||
if _use_cache:
|
||||
DirAccess.make_dir_recursive_absolute(filename.get_base_dir())
|
||||
var file : FileAccess = FileAccess.open(filename, FileAccess.WRITE)
|
||||
file.store_string(JSON.stringify(mappings))
|
||||
else:
|
||||
print("Could not retrieve badge mapping for channel_id " + channel_id + ".")
|
||||
return {}
|
||||
return caches[RequestType.BADGE_MAPPING][channel_id]
|
||||
|
||||
func get_settings():
|
||||
_client_id = VSTSettings.get_setting(VSTSettings.settings.client_id)
|
||||
_twitch_chat_url = VSTSettings.get_setting(VSTSettings.settings.twitch_chat_url)
|
||||
_twitch_chat_port = VSTSettings.get_setting(VSTSettings.settings.twitch_port)
|
||||
_use_cache = VSTSettings.get_setting(VSTSettings.settings.disk_cache)
|
||||
_cache_path = VSTSettings.get_setting(VSTSettings.settings.disk_cache_path)
|
||||
_chat_timeout_ms = VSTSettings.get_setting(VSTSettings.settings.twitch_timeout_ms)
|
||||
|
||||
# stops chat socket from tts server
|
||||
func disconnect_api():
|
||||
if _chatClient:
|
||||
_chatClient.close()
|
||||
|
||||
_hasConnected = false
|
||||
1
addons/very-simple-twitch/twitch_chat.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ckwhjsorarfn0
|
||||
124
addons/very-simple-twitch/twitch_chat_settings.gd
Normal file
@@ -0,0 +1,124 @@
|
||||
class_name VSTSettings
|
||||
|
||||
const settings: Dictionary = {
|
||||
"client_id": {
|
||||
"path": "config/client_id",
|
||||
"type": TYPE_STRING,
|
||||
"hint_string": "The client id from the twitch developer dashboard",
|
||||
"is_basic": true,
|
||||
"initial_value": "",
|
||||
},
|
||||
"redirect_host": {
|
||||
"path": "advanced_config/redirect_host",
|
||||
"type": TYPE_STRING,
|
||||
"hint_string": "The host where the OAuth Callback will be received",
|
||||
"is_basic": false,
|
||||
"initial_value": "http://localhost:",
|
||||
},
|
||||
"uuid": {
|
||||
"path": "advanced_config/uuid",
|
||||
"type": TYPE_STRING,
|
||||
"hint_string": "The useless UID to hide the token from the web browser",
|
||||
"is_basic": false,
|
||||
"initial_value": "53125396-3e32-4fad-8f7e-36475724168b-a8fe83ab-3373-4a6a-8967-2532eafe407f-41483db3-f011-4a23-80da-9a340672692a-e755c6d4-c546-43ce-b722-b5a799561b4e-5ba1697d-79b2-4d5d-96c3-f0d91f13f583-f08f18f9-bd56-4a0f-a597-96f90108cd85-14449d50-6cc9-450f-8119-ff4c525e31db-e41a6912-92a0-48b6-b6d3-845c21bea7eb-7dfd7948-2976-42cf-9cca-b23ae5854813-107224eb-81ea-46dd-9bf5-9ebbfcfc45dc/",
|
||||
},
|
||||
"redirect_port": {
|
||||
"path": "config/redirect_port",
|
||||
"type": TYPE_INT,
|
||||
"hint_string": "The port where the oauth callback will be redirect",
|
||||
"is_basic": true,
|
||||
"initial_value": 8090,
|
||||
},
|
||||
|
||||
"disk_cache_path": {
|
||||
"path": "advanced_config/disk_cache_path",
|
||||
"type": TYPE_STRING,
|
||||
"hint": PROPERTY_HINT_GLOBAL_DIR,
|
||||
"hint_string": "The absolute filepath where the images cache will be stored",
|
||||
"is_basic": false,
|
||||
"initial_value": "user://very-simple-chat/cache",
|
||||
},
|
||||
"disk_cache": {
|
||||
"path": "config/disk_cache",
|
||||
"type": TYPE_BOOL,
|
||||
"hint_string": "Use cache to store the images from badges and emotes",
|
||||
"is_basic": true,
|
||||
"initial_value": true,
|
||||
},
|
||||
"scopes": {
|
||||
"path": "config/scopes",
|
||||
"type": TYPE_PACKED_STRING_ARRAY,
|
||||
"hint_string": "Scopes that will be asked when the token is retrieved",
|
||||
"is_basic": true,
|
||||
"initial_value": ["moderator:manage:banned_users","chat:read", "channel:manage:vips"],
|
||||
},
|
||||
"twitch_chat_url": {
|
||||
"path": "advanced_config/twitch_chat_url",
|
||||
"type": TYPE_STRING,
|
||||
"hint_string": "Twitch chat url",
|
||||
"is_basic": false,
|
||||
"initial_value": "wss://irc-ws.chat.twitch.tv",
|
||||
},
|
||||
"twitch_port": {
|
||||
"path": "advanced_config/twitch_port",
|
||||
"type": TYPE_INT,
|
||||
"hint_string": "The port the websocket will connect to",
|
||||
"is_basic": false,
|
||||
"initial_value": 443,
|
||||
},
|
||||
"twitch_anon_user": {
|
||||
"path": "advanced_config/twitch_anon_user",
|
||||
"type": TYPE_STRING,
|
||||
"hint_string": "Anon connection username",
|
||||
"is_basic": false,
|
||||
"initial_value": "justinfan5555",
|
||||
},
|
||||
"twitch_anon_pass": {
|
||||
"path": "advanced_config/twitch_anon_pass",
|
||||
"type": TYPE_STRING,
|
||||
"hint_string": "Anon connection password",
|
||||
"is_basic": false,
|
||||
"initial_value": "kappa",
|
||||
},
|
||||
"twitch_timeout_ms":{
|
||||
"path": "advanced_config/twitch_timeout_ms",
|
||||
"type:": TYPE_INT,
|
||||
"hint_string": "Time between messages sent by the client",
|
||||
"is_basic": false,
|
||||
"initial_value": 320,
|
||||
}
|
||||
}
|
||||
|
||||
static func add_settings():
|
||||
for setting in settings:
|
||||
var setting_value = settings[setting]
|
||||
var path = "very_simple_twitch/"+ setting_value.get("path", "config" +setting)
|
||||
var saved_value = ProjectSettings.get_setting(path)
|
||||
if !saved_value:
|
||||
ProjectSettings.set_setting(path, setting_value.get("initial_value"))
|
||||
var property_info = {
|
||||
"name": path,
|
||||
"type": setting_value.get("type", typeof(setting_value.get("initial_value"))),
|
||||
"hint": setting_value.get("hint"),
|
||||
"hint_string": setting_value.get("hint_string", "")
|
||||
}
|
||||
ProjectSettings.set_as_basic(path, setting_value.get("is_basic", true))
|
||||
ProjectSettings.set_initial_value(path, setting_value.get("initial_value"))
|
||||
ProjectSettings.add_property_info(property_info)
|
||||
ProjectSettings.save()
|
||||
|
||||
|
||||
static func remove_settings():
|
||||
for setting in settings:
|
||||
var setting_value = settings[setting]
|
||||
var path = "very_simple_twitch/"+ setting_value.get("path", "config") + "/" + setting
|
||||
ProjectSettings.set_setting(path, null)
|
||||
|
||||
|
||||
static func get_setting(setting: Dictionary):
|
||||
var path = "very_simple_twitch/"+ setting.get("path")
|
||||
var response = ProjectSettings.get_setting(path)
|
||||
if !response:
|
||||
return setting.get("initial_value")
|
||||
else:
|
||||
return response
|
||||
1
addons/very-simple-twitch/twitch_chat_settings.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://7erhidjoher1
|
||||
71
addons/very-simple-twitch/twitch_node.gd
Normal file
@@ -0,0 +1,71 @@
|
||||
extends Node
|
||||
|
||||
signal token_received(twitch_channel: VSTChannel)
|
||||
signal chat_message_received(channel: VSTChatter)
|
||||
signal chat_connected(channel_name: String)
|
||||
|
||||
var _twitch_api: VSTAPI
|
||||
var _twitch_chat: VSTChat
|
||||
|
||||
func login_chat_anon(channel_name: String):
|
||||
_start_chat_client()
|
||||
_twitch_chat.login_anon(channel_name)
|
||||
chat_connected.emit(await _twitch_chat.Connected)
|
||||
|
||||
|
||||
func login_chat(channel_info: VSTChannel):
|
||||
_start_chat_client()
|
||||
_twitch_chat.login(channel_info)
|
||||
chat_connected.emit(await _twitch_chat.Connected)
|
||||
|
||||
|
||||
func get_token_and_login_chat():
|
||||
var channel_info = await get_token()
|
||||
await login_chat(channel_info)
|
||||
|
||||
|
||||
func _start_chat_client():
|
||||
if !_twitch_chat:
|
||||
_twitch_chat = VSTChat.new()
|
||||
add_child(_twitch_chat)
|
||||
_twitch_chat.OnMessage.connect(on_chat_message_received)
|
||||
|
||||
|
||||
func get_token() -> VSTChannel:
|
||||
if !_twitch_api:
|
||||
_twitch_api = VSTAPI.new()
|
||||
add_child(_twitch_api)
|
||||
_twitch_api.initiate_twitch_auth()
|
||||
var channel_info = await _twitch_api.token_received
|
||||
token_received.emit(channel_info)
|
||||
return channel_info
|
||||
|
||||
|
||||
func get_badge(badge_name: String, badge_level: String,
|
||||
channel_id: String = "_global", scale: String = "1"):
|
||||
return await _twitch_chat.get_badge(badge_name, badge_level, channel_id, scale)
|
||||
|
||||
|
||||
func get_emote(loc_id: String):
|
||||
return await _twitch_chat.get_emote(loc_id)
|
||||
|
||||
# clear all support nodes, disconects from chat/auth server
|
||||
func end_chat_client():
|
||||
if _twitch_chat:
|
||||
_twitch_chat.disconnect_api()
|
||||
_twitch_chat.OnMessage.disconnect(on_chat_message_received)
|
||||
remove_child(_twitch_chat)
|
||||
_twitch_chat.queue_free()
|
||||
_twitch_chat = null
|
||||
|
||||
if _twitch_api:
|
||||
_twitch_api.disconnect_api()
|
||||
remove_child(_twitch_api)
|
||||
_twitch_api.queue_free()
|
||||
_twitch_api = null
|
||||
|
||||
func send_chat_message(message: String):
|
||||
_twitch_chat.send_message(message)
|
||||
|
||||
func on_chat_message_received(chatter: VSTChatter):
|
||||
chat_message_received.emit(chatter)
|
||||
1
addons/very-simple-twitch/twitch_node.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cbrqfun2syqju
|
||||
40
addons/very-simple-twitch/twitch_utils.gd
Normal file
@@ -0,0 +1,40 @@
|
||||
class_name VSTUtils
|
||||
|
||||
const LUMINANCE_LOW := 0.2
|
||||
const LUMINANCE_HIGH := 0.8
|
||||
|
||||
const DEFAULT_NAME_COLORS:Array[String] = [
|
||||
"#FF0000",
|
||||
"#00FF00",
|
||||
"#0000FF",
|
||||
"#B22222",
|
||||
"#FF7F50",
|
||||
"#9ACD32",
|
||||
"#FF4500",
|
||||
"#2E8B57",
|
||||
"#DAA520",
|
||||
"#D2691E",
|
||||
"#5F9EA0",
|
||||
"#1E90FF",
|
||||
"#FF69B4",
|
||||
"#8A2BE2",
|
||||
"#00FF7F",
|
||||
]
|
||||
|
||||
# Returns a random non-unique color from a name and a seed
|
||||
static func get_random_name_color(login: String, session_seed:int = 0):
|
||||
var position: int = session_seed + hash(login)
|
||||
return DEFAULT_NAME_COLORS[position % DEFAULT_NAME_COLORS.size()]
|
||||
|
||||
# Normalize color in order to be not bright or darker
|
||||
static func normalize_color(color: Color) -> Color:
|
||||
var luminance = color.get_luminance()
|
||||
if luminance > LUMINANCE_HIGH:
|
||||
return color.darkened(0.2)
|
||||
if luminance < LUMINANCE_LOW:
|
||||
return color.lightened(0.2)
|
||||
return color
|
||||
|
||||
# Normalize color from string representation
|
||||
static func normalize_hex_color(color: String) -> Color:
|
||||
return normalize_color(Color(color))
|
||||
1
addons/very-simple-twitch/twitch_utils.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://pqa2k4i3du34
|
||||
36
addons/very-simple-twitch/vst.gd
Normal file
@@ -0,0 +1,36 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
var dock
|
||||
var chat_dock
|
||||
|
||||
func _enter_tree() -> void:
|
||||
add_custom_type("VerySimpleTwitchChat", "Node", preload("twitch_chat.gd"), preload("icon.png"))
|
||||
add_custom_type("VerySimpleTwitchAPI", "Node", preload("twitch_api.gd"), preload("icon.png"))
|
||||
|
||||
add_autoload_singleton("VerySimpleTwitch", "twitch_node.gd")
|
||||
|
||||
VSTSettings.add_settings()
|
||||
|
||||
#Bottom setup dock
|
||||
dock = preload("res://addons/very-simple-twitch/dock/vst-dock.tscn").instantiate()
|
||||
add_control_to_bottom_panel(dock, "Very Simple Twitch")
|
||||
|
||||
#Chat dock
|
||||
chat_dock = preload("res://addons/very-simple-twitch/chat/vst_chat_dock.tscn").instantiate()
|
||||
add_control_to_dock(EditorPlugin.DOCK_SLOT_RIGHT_UL, chat_dock)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_custom_type("VerySimpleTwitchChat")
|
||||
remove_custom_type("VerySimpleTwitchAPI")
|
||||
|
||||
VSTSettings.remove_settings()
|
||||
|
||||
remove_autoload_singleton("VerySimpleTwitch")
|
||||
|
||||
remove_control_from_bottom_panel(dock)
|
||||
dock.free()
|
||||
|
||||
remove_control_from_docks(chat_dock)
|
||||
chat_dock.free()
|
||||
1
addons/very-simple-twitch/vst.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cd4wocpij063j
|
||||
32
addons/very-simple-twitch/vst_error.gd
Normal file
@@ -0,0 +1,32 @@
|
||||
class_name VSTError
|
||||
|
||||
enum VSTCodeError {PARAM_ERROR, TIMEOUT_ERROR, NETWORK_ERROR, SERVER_ERROR}
|
||||
|
||||
var code: VSTCodeError
|
||||
var description: String
|
||||
var info: String
|
||||
|
||||
func _init(error_code: VSTCodeError, error_info: String = ""):
|
||||
code = error_code
|
||||
info = error_info
|
||||
description = _get_description_from_code(error_code)
|
||||
|
||||
|
||||
func _get_description_from_code(error_code: VSTCodeError) -> String:
|
||||
var result = ""
|
||||
match (error_code):
|
||||
VSTCodeError.PARAM_ERROR:
|
||||
result = "The request aren't fullfilled properly. Check the data"
|
||||
VSTCodeError.NETWORK_ERROR:
|
||||
result = "The request result in an error"
|
||||
VSTCodeError.SERVER_ERROR:
|
||||
result = "There is an error in server"
|
||||
VSTCodeError.TIMEOUT_ERROR:
|
||||
result = "The server doesn't response"
|
||||
_:
|
||||
result = "Unknown error"
|
||||
return result
|
||||
|
||||
|
||||
func _to_string():
|
||||
return "%s %s %s" % [str(code), description, info]
|
||||
1
addons/very-simple-twitch/vst_error.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bcle5hp6f77l
|
||||
@@ -207,7 +207,7 @@ func change_ambiances_volume(db_change := 0., fade := DEFAULT_FADE_TIME):
|
||||
|
||||
# Joue un
|
||||
# - player_name : Nom de la Node dans la scène Godot à jouer
|
||||
func play_sfx(sfx_name : String, pitch = 1.):
|
||||
func play_sfx(sfx_name : String, _pitch = 1.):
|
||||
var player := %Sfx.find_child(sfx_name) as AudioStreamPlayer
|
||||
if player:
|
||||
player.play()
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
extends Resource
|
||||
class_name GameData
|
||||
|
||||
enum GameMode {STORY}
|
||||
enum Mode {STORY, INFINITE}
|
||||
|
||||
signal current_run_updated(r : RunData)
|
||||
signal current_region_data_updated(p : RegionData)
|
||||
|
||||
|
||||
func _init(
|
||||
_game_mode : Mode = Mode.STORY
|
||||
):
|
||||
game_mode = _game_mode
|
||||
if game_mode == Mode.STORY:
|
||||
progression_data = StoryProgressionData.new()
|
||||
elif game_mode == Mode.INFINITE:
|
||||
progression_data = InfiniteProgressionData.new()
|
||||
|
||||
|
||||
@export var game_mode : Mode = GameData.Mode.STORY
|
||||
|
||||
@export var player_data : PlayerData = PlayerData.new()
|
||||
|
||||
@export var progression_data : ProgressionData = ProgressionData.new()
|
||||
@export var progression_data : ProgressionData
|
||||
|
||||
@export var current_run : RunData = start_run() :
|
||||
@export var current_run : RunData :
|
||||
set(v):
|
||||
current_run = v
|
||||
current_run_updated.emit(v)
|
||||
@@ -30,8 +42,6 @@ signal current_region_data_updated(p : RegionData)
|
||||
|
||||
@export var item_announced = []
|
||||
|
||||
@export var game_mode : GameMode = GameMode.STORY
|
||||
|
||||
@export var dialogs_done : Array[String] = [] #Chemin des dialogues terminés
|
||||
@export var tutorials_done : Array[String] = []
|
||||
|
||||
@@ -39,7 +49,7 @@ func start_run() -> RunData:
|
||||
player_data.clear_inventory()
|
||||
player_data.update_with_artefacts([])
|
||||
current_run = RunData.new()
|
||||
current_run.story_step = progression_data.story_step.duplicate_deep()
|
||||
current_run.story_step = progression_data.get_story_step().duplicate_deep()
|
||||
current_run.generate_next_run_points()
|
||||
current_run.current_run_point_changed.connect(
|
||||
func(rp : RunPoint):
|
||||
@@ -59,12 +69,14 @@ func start_region(region_param : RegionParameter):
|
||||
current_region_data = RegionData.new(region_param)
|
||||
|
||||
func give_up():
|
||||
progression_data.finish_run(current_run)
|
||||
current_region_data = null
|
||||
current_run = null
|
||||
start_run()
|
||||
SceneManager.change_to_scene(progression_data.story_step.get_respawn_scene())
|
||||
SceneManager.change_to_scene(progression_data.get_story_step().get_respawn_scene())
|
||||
|
||||
func finish_story_step():
|
||||
progression_data.finish_run(current_run)
|
||||
progression_data.next_story_step()
|
||||
current_region_data = null
|
||||
start_run()
|
||||
|
||||
29
common/game_data/scripts/infinite_progression_data.gd
Normal file
@@ -0,0 +1,29 @@
|
||||
extends ProgressionData
|
||||
class_name InfiniteProgressionData
|
||||
|
||||
func get_story_step() -> StoryStep:
|
||||
return InfiniteStoryStep.new(run_number)
|
||||
|
||||
func get_story_progression() -> float:
|
||||
return 0.
|
||||
|
||||
func discover_mutation(_pm : PlantMutation) -> void:
|
||||
pass
|
||||
|
||||
func get_mutations_discovered() -> Array[String]:
|
||||
var mutation_discovered : Array[String] = []
|
||||
for m in get_all_mutations():
|
||||
mutation_discovered.append(m.get_mutation_id())
|
||||
return mutation_discovered
|
||||
|
||||
func next_story_step() -> void:
|
||||
pass
|
||||
|
||||
func are_all_mutations_unlocked() -> bool:
|
||||
return true
|
||||
|
||||
func unlock_new_mutation() -> PlantMutation:
|
||||
return null
|
||||
|
||||
func get_available_mutations() -> Array[PlantMutation]:
|
||||
return get_all_mutations()
|
||||
@@ -0,0 +1 @@
|
||||
uid://iigxgqiyn0p4
|
||||
@@ -1,25 +1,25 @@
|
||||
@abstract
|
||||
extends Resource
|
||||
class_name ProgressionData
|
||||
|
||||
@export var planted_mutation_ids: Array[String] = []
|
||||
@export var story_step_i := 0
|
||||
@export var mutations_unlocked = 8
|
||||
@export var run_number : int = 0
|
||||
@export var best_run : int = 0
|
||||
|
||||
var all_mutations: Array[PlantMutation] : get = get_all_mutations
|
||||
var available_mutations: Array[PlantMutation] : get = get_available_mutations
|
||||
var available_artefacts: Array[Artefact] : get = get_all_artifacts
|
||||
var story_step : StoryStep : get = get_story_step
|
||||
@abstract func get_story_step() -> StoryStep
|
||||
|
||||
func get_story_step() -> StoryStep:
|
||||
return get_all_story_steps()[story_step_i]
|
||||
@abstract func get_story_progression() -> float
|
||||
|
||||
func next_story_step() -> void:
|
||||
get_story_step()._on_finish()
|
||||
if story_step_i + 1 < len(get_all_story_steps()):
|
||||
story_step_i += 1
|
||||
@abstract func next_story_step() -> void
|
||||
|
||||
func get_available_mutations() -> Array[PlantMutation]:
|
||||
return get_all_mutations().slice(0, mutations_unlocked)
|
||||
@abstract func get_available_mutations() -> Array[PlantMutation]
|
||||
|
||||
@abstract func are_all_mutations_unlocked() -> bool
|
||||
|
||||
@abstract func discover_mutation(_pm : PlantMutation) -> void
|
||||
|
||||
@abstract func get_mutations_discovered() -> Array[String]
|
||||
|
||||
@abstract func unlock_new_mutation() -> PlantMutation
|
||||
|
||||
func get_all_mutations() -> Array[PlantMutation]:
|
||||
return [
|
||||
@@ -38,6 +38,11 @@ func get_all_mutations() -> Array[PlantMutation]:
|
||||
GenerousMutation.new(),
|
||||
ProtectiveMutation.new(),
|
||||
PureMutation.new(),
|
||||
CleaningMutation.new(),
|
||||
ErmitMutation.new(),
|
||||
TropicalMutation.new(),
|
||||
RhizomeMutation.new(),
|
||||
SpontaneousMutation.new()
|
||||
]
|
||||
|
||||
func get_all_artifacts() -> Array[Artefact]:
|
||||
@@ -49,7 +54,6 @@ func get_all_artifacts() -> Array[Artefact]:
|
||||
TalionSoilArtifact.new(),
|
||||
]
|
||||
|
||||
|
||||
func get_all_story_steps() -> Array[StoryStep]:
|
||||
return [
|
||||
TutorialStoryStep.new(),
|
||||
@@ -57,3 +61,7 @@ func get_all_story_steps() -> Array[StoryStep]:
|
||||
MercuryStoryStep.new(),
|
||||
BoreaStoryStep.new()
|
||||
]
|
||||
|
||||
func finish_run(run : RunData):
|
||||
run_number += 1
|
||||
best_run = max(best_run, run.level)
|
||||
|
||||
@@ -10,6 +10,7 @@ signal sound_changed(settings : SettingsData)
|
||||
signal video_changed(settings : SettingsData)
|
||||
signal game_changed(settings : SettingsData)
|
||||
signal fov_changed(value : float)
|
||||
signal twitch_changed(settings : SettingsData)
|
||||
|
||||
#region ------------------ Language ------------------
|
||||
|
||||
@@ -51,6 +52,11 @@ const AVAILABLE_LANGUAGES_LABEL = [
|
||||
full_screen = v
|
||||
video_changed.emit(self)
|
||||
|
||||
@export var ui_size : float = 1. :
|
||||
set(v):
|
||||
ui_size = v
|
||||
video_changed.emit(self)
|
||||
|
||||
#region ------------------ Controls ------------------
|
||||
|
||||
|
||||
@@ -92,4 +98,16 @@ func close_help_container(help_container_name : String):
|
||||
func open_help_container(help_container_name : String):
|
||||
if help_container_name in closed_help_containers:
|
||||
closed_help_containers.erase(help_container_name)
|
||||
game_changed.emit(self)
|
||||
game_changed.emit(self)
|
||||
|
||||
#region ------------------ Twitch ------------------
|
||||
|
||||
@export var activate_twitch_integration := false :
|
||||
set(v):
|
||||
activate_twitch_integration = v
|
||||
twitch_changed.emit(self)
|
||||
|
||||
@export var twitch_channel := "" :
|
||||
set(v):
|
||||
twitch_channel = v
|
||||
twitch_changed.emit(self)
|
||||
|
||||
36
common/game_data/scripts/story/infinite_story_step.gd
Normal file
@@ -0,0 +1,36 @@
|
||||
@tool
|
||||
extends StoryStep
|
||||
class_name InfiniteStoryStep
|
||||
|
||||
var run_number := 0
|
||||
|
||||
func _init(
|
||||
_run_number := 0
|
||||
):
|
||||
run_number = _run_number
|
||||
|
||||
|
||||
func get_respawn_scene() -> Scene:
|
||||
return RelayBaseScene.new(
|
||||
"INFINITE_MODE",
|
||||
str(run_number),
|
||||
true
|
||||
)
|
||||
|
||||
func get_cave_occurence(_level : int) -> int:
|
||||
return 0
|
||||
|
||||
func is_region_sequence_infinite() -> bool:
|
||||
return true
|
||||
|
||||
func get_region_sequence_length() -> int:
|
||||
return 1000
|
||||
|
||||
func is_run_point_dangerous(level : int) -> bool:
|
||||
return level%6 == 0 and level != 0
|
||||
|
||||
func get_destination_text() -> String:
|
||||
return tr("INFINITE")
|
||||
|
||||
func get_destination_scene() -> Scene:
|
||||
return BoreaScene.new()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bdonub7t01xmi
|
||||
@@ -12,6 +12,8 @@ func get_destination_scene() -> Scene:
|
||||
return BoreaScene.new()
|
||||
|
||||
func get_run_progress(level : int) -> int:
|
||||
if is_region_sequence_infinite():
|
||||
return 0
|
||||
return get_region_sequence_length() - level
|
||||
|
||||
func get_ship_dialog_path(_level : int, _ship_in_space := true) -> String:
|
||||
@@ -19,8 +21,13 @@ func get_ship_dialog_path(_level : int, _ship_in_space := true) -> String:
|
||||
#region ------------------ Run ------------------
|
||||
|
||||
func is_run_finished(level : int) -> bool:
|
||||
if is_region_sequence_infinite():
|
||||
return false
|
||||
return level == get_region_sequence_length() - 1
|
||||
|
||||
func is_region_sequence_infinite() -> bool:
|
||||
return false
|
||||
|
||||
func get_region_sequence_length() -> int:
|
||||
return 7
|
||||
|
||||
@@ -48,6 +55,8 @@ func get_charge_number(_level : int) -> int:
|
||||
return 10
|
||||
|
||||
func is_run_point_dangerous(level : int) -> bool:
|
||||
if is_region_sequence_infinite():
|
||||
return false
|
||||
return level == get_region_sequence_length() - 2
|
||||
|
||||
func get_objective_for_region(level : int) -> int:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@tool
|
||||
extends StoryStep
|
||||
class_name MercuryStoryStep
|
||||
|
||||
@@ -25,8 +26,8 @@ func get_destination_scene() -> Scene:
|
||||
func get_ship_dialog_path(level : int, ship_in_space := true) -> String:
|
||||
if level == 0:
|
||||
return MERCURY_DEPARTURE_DIALOG_PATH
|
||||
elif level == 1:
|
||||
return MERCURY_DEPARTURE_DIALOG_PATH
|
||||
if level == 1:
|
||||
return VENDING_MACHINE_DIALOG_PATH
|
||||
if ship_in_space and is_run_finished(level + 1):
|
||||
return VENUS_ARRIVAL_DIALOG_PATH
|
||||
return ""
|
||||
36
common/game_data/scripts/story_progression_data.gd
Normal file
@@ -0,0 +1,36 @@
|
||||
extends ProgressionData
|
||||
class_name StoryProgressionData
|
||||
|
||||
@export var planted_mutation_ids: Array[String] = []
|
||||
@export var story_step_i := 0
|
||||
@export var mutations_unlocked = 8
|
||||
|
||||
func get_story_step() -> StoryStep:
|
||||
return get_all_story_steps()[story_step_i]
|
||||
|
||||
func get_story_progression() -> float:
|
||||
return max(0,float(story_step_i)) / len(
|
||||
get_all_story_steps()
|
||||
)
|
||||
|
||||
func discover_mutation(pm : PlantMutation) -> void:
|
||||
planted_mutation_ids.append(pm.id)
|
||||
|
||||
func get_mutations_discovered() -> Array[String]:
|
||||
return planted_mutation_ids
|
||||
|
||||
func next_story_step() -> void:
|
||||
get_story_step()._on_finish()
|
||||
if story_step_i + 1 < len(get_all_story_steps()):
|
||||
story_step_i += 1
|
||||
|
||||
func get_available_mutations() -> Array[PlantMutation]:
|
||||
return get_all_mutations().slice(0, mutations_unlocked)
|
||||
|
||||
func are_all_mutations_unlocked() -> bool:
|
||||
return mutations_unlocked >= len(get_all_mutations())
|
||||
|
||||
func unlock_new_mutation() -> PlantMutation:
|
||||
var new_mutation = get_all_mutations()[mutations_unlocked]
|
||||
mutations_unlocked += 1
|
||||
return new_mutation
|
||||
1
common/game_data/scripts/story_progression_data.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://vcoy8pt780cl
|
||||
@@ -1,35 +1,62 @@
|
||||
extends Node
|
||||
|
||||
const SAVE_GAME_LOCATION = "user://stw_demo_save.tres"
|
||||
const SAVE_NAME = "playtest_2"
|
||||
|
||||
const SAVE_STORY_GAME_LOCATION = "user://stw_%s_save.tres" % SAVE_NAME
|
||||
const SAVE_INFINTE_GAME_LOCATION = "user://stw_%s_infinite_save.tres" % SAVE_NAME
|
||||
const SAVE_SETTINGS_LOCATION = "user://stw_settings.tres"
|
||||
|
||||
var game_loaded = false
|
||||
signal game_loaded
|
||||
|
||||
signal game_data_updated(g : GameData)
|
||||
var game_mode_choosen := GameData.Mode.STORY
|
||||
|
||||
var game_data : GameData :
|
||||
set(v):
|
||||
game_data = v
|
||||
game_data_updated.emit(v)
|
||||
get():
|
||||
if game_mode_choosen == GameData.Mode.STORY:
|
||||
return story_game_data
|
||||
elif game_mode_choosen == GameData.Mode.INFINITE:
|
||||
return infinite_game_data
|
||||
return null
|
||||
|
||||
var story_game_data : GameData
|
||||
var infinite_game_data : GameData
|
||||
|
||||
var settings_data : SettingsData
|
||||
|
||||
var current_dialog_path : String
|
||||
|
||||
func load_game_data() -> GameData:
|
||||
game_data = null
|
||||
if ResourceLoader.exists(SAVE_GAME_LOCATION) and ResourceLoader.load(SAVE_GAME_LOCATION):
|
||||
game_data = ResourceLoader.load(SAVE_GAME_LOCATION).duplicate_deep()
|
||||
func load_story_game_data() -> GameData:
|
||||
story_game_data = null
|
||||
if ResourceLoader.exists(SAVE_STORY_GAME_LOCATION) and ResourceLoader.load(SAVE_STORY_GAME_LOCATION):
|
||||
story_game_data = ResourceLoader.load(SAVE_STORY_GAME_LOCATION).duplicate_deep()
|
||||
game_loaded.emit()
|
||||
|
||||
return game_data
|
||||
return story_game_data
|
||||
|
||||
func load_infinite_game_data() -> GameData:
|
||||
infinite_game_data = null
|
||||
if ResourceLoader.exists(SAVE_INFINTE_GAME_LOCATION) and ResourceLoader.load(SAVE_INFINTE_GAME_LOCATION):
|
||||
infinite_game_data = ResourceLoader.load(SAVE_INFINTE_GAME_LOCATION).duplicate_deep()
|
||||
|
||||
return infinite_game_data
|
||||
|
||||
func start_game_data():
|
||||
game_data = GameData.new()
|
||||
if game_mode_choosen == GameData.Mode.STORY:
|
||||
story_game_data = GameData.new(GameData.Mode.STORY)
|
||||
story_game_data.start_run()
|
||||
else:
|
||||
infinite_game_data = GameData.new(GameData.Mode.INFINITE)
|
||||
infinite_game_data.start_run()
|
||||
|
||||
save_game_data()
|
||||
|
||||
func save_game_data():
|
||||
if game_data:
|
||||
ResourceSaver.save(game_data, SAVE_GAME_LOCATION)
|
||||
if game_mode_choosen == GameData.Mode.STORY:
|
||||
if story_game_data:
|
||||
ResourceSaver.save(story_game_data, SAVE_STORY_GAME_LOCATION)
|
||||
elif game_mode_choosen == GameData.Mode.INFINITE:
|
||||
if infinite_game_data:
|
||||
ResourceSaver.save(infinite_game_data, SAVE_INFINTE_GAME_LOCATION)
|
||||
|
||||
func load_settings_data() -> SettingsData:
|
||||
if ResourceLoader.exists(SAVE_SETTINGS_LOCATION):
|
||||
@@ -45,7 +72,8 @@ func save_settings():
|
||||
ResourceSaver.save(settings_data, SAVE_SETTINGS_LOCATION)
|
||||
|
||||
func _init():
|
||||
load_game_data()
|
||||
load_story_game_data()
|
||||
load_infinite_game_data()
|
||||
load_settings_data()
|
||||
update_language_settings()
|
||||
update_video_settings()
|
||||
@@ -81,9 +109,15 @@ func update_language_settings(s : SettingsData = settings_data):
|
||||
|
||||
func update_video_settings(s : SettingsData = settings_data):
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN if s.full_screen else DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
if not is_node_ready():
|
||||
await ready
|
||||
get_tree().root.content_scale_factor = s.ui_size
|
||||
|
||||
|
||||
func update_inputs(s : SettingsData = settings_data):
|
||||
for i in range(len(s.input_remapped)):
|
||||
InputMap.action_erase_events(s.action_remapped[i])
|
||||
InputMap.action_add_event(s.action_remapped[i], s.input_remapped[i])
|
||||
|
||||
func has_unlocked_infinite_mode():
|
||||
return true
|
||||
35
common/icons/brand-twitch.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="icon icon-tabler icons-tabler-outline icon-tabler-brand-twitch"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<path
|
||||
stroke="none"
|
||||
d="M0 0h24v24H0z"
|
||||
fill="none"
|
||||
id="path1" />
|
||||
<path
|
||||
d="M4 5v11a1 1 0 0 0 1 1h2v4l4 -4h5.584c.266 0 .52 -.105 .707 -.293l2.415 -2.414c.187 -.188 .293 -.442 .293 -.708v-8.585a1 1 0 0 0 -1 -1h-14a1 1 0 0 0 -1 1l.001 0"
|
||||
id="path2"
|
||||
style="stroke:#ffffff;stroke-opacity:1" />
|
||||
<path
|
||||
d="M16 8l0 4"
|
||||
id="path3"
|
||||
style="stroke:#ffffff;stroke-opacity:1" />
|
||||
<path
|
||||
d="M12 8l0 4"
|
||||
id="path4"
|
||||
style="stroke:#ffffff;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 953 B |
43
common/icons/brand-twitch.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cvvnrj8wi3f3r"
|
||||
path="res://.godot/imported/brand-twitch.svg-a028bcfb5c21b506b9222ac521f85c82.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://common/icons/brand-twitch.svg"
|
||||
dest_files=["res://.godot/imported/brand-twitch.svg-a028bcfb5c21b506b9222ac521f85c82.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
svg/scale=2.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
23
common/icons/bug.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="icon icon-tabler icons-tabler-filled icon-tabler-bug"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<path
|
||||
stroke="none"
|
||||
d="M0 0h24v24H0z"
|
||||
fill="none"
|
||||
id="path1" />
|
||||
<path
|
||||
d="M12 4a4 4 0 0 1 3.995 3.8l.005 .2a1 1 0 0 1 .428 .096l3.033 -1.938a1 1 0 1 1 1.078 1.684l-3.015 1.931a7.17 7.17 0 0 1 .476 2.227h3a1 1 0 0 1 0 2h-3v1a6.01 6.01 0 0 1 -.195 1.525l2.708 1.616a1 1 0 1 1 -1.026 1.718l-2.514 -1.501a6.002 6.002 0 0 1 -3.973 2.56v-5.918a1 1 0 0 0 -2 0v5.917a6.002 6.002 0 0 1 -3.973 -2.56l-2.514 1.503a1 1 0 1 1 -1.026 -1.718l2.708 -1.616a6.01 6.01 0 0 1 -.195 -1.526v-1h-3a1 1 0 0 1 0 -2h3.001v-.055a7 7 0 0 1 .474 -2.173l-3.014 -1.93a1 1 0 1 1 1.078 -1.684l3.032 1.939l.024 -.012l.068 -.027l.019 -.005l.016 -.006l.032 -.008l.04 -.013l.034 -.007l.034 -.004l.045 -.008l.015 -.001l.015 -.002l.087 -.004a4 4 0 0 1 4 -4zm0 2a2 2 0 0 0 -2 2h4a2 2 0 0 0 -2 -2z"
|
||||
id="path2"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
43
common/icons/bug.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ceoxyc8lxuqet"
|
||||
path="res://.godot/imported/bug.svg-30d611f322c5908f7609c3e1222553c7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://common/icons/bug.svg"
|
||||
dest_files=["res://.godot/imported/bug.svg-30d611f322c5908f7609c3e1222553c7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
svg/scale=2.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
23
common/icons/christmas-tree.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="icon icon-tabler icons-tabler-filled icon-tabler-christmas-tree"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<path
|
||||
stroke="none"
|
||||
d="M0 0h24v24H0z"
|
||||
fill="none"
|
||||
id="path1" />
|
||||
<path
|
||||
d="M15 19v1a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2v-1zm-10 -1c-.89 0 -1.337 -1.077 -.707 -1.707l2.855 -2.857l-1.464 -.487a1 1 0 0 1 -.472 -1.565l.08 -.091l3.019 -3.02l-.758 -.379a1 1 0 0 1 -.343 -1.507l.083 -.094l4 -4a1 1 0 0 1 1.414 0l4 4a1 1 0 0 1 -.26 1.601l-.759 .379l3.02 3.02a1 1 0 0 1 -.279 1.61l-.113 .046l-1.465 .487l2.856 2.857c.603 .602 .22 1.614 -.593 1.701l-.114 .006z"
|
||||
id="path2"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 892 B |
43
common/icons/christmas-tree.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dofqju0k1nguf"
|
||||
path="res://.godot/imported/christmas-tree.svg-2d675a899996dc14dfea64e4ba2ac385.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://common/icons/christmas-tree.svg"
|
||||
dest_files=["res://.godot/imported/christmas-tree.svg-2d675a899996dc14dfea64e4ba2ac385.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
svg/scale=2.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -1 +1,31 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-cloud-rain"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M7 18a4.6 4.4 0 0 1 0 -9a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7" /><path d="M11 13v2m0 3v2m4 -5v2m0 3v2" /></svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="icon icon-tabler icons-tabler-outline icon-tabler-cloud-rain"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<path
|
||||
stroke="none"
|
||||
d="M0 0h24v24H0z"
|
||||
fill="none"
|
||||
id="path1" />
|
||||
<path
|
||||
d="M7 18a4.6 4.4 0 0 1 0 -9a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7"
|
||||
id="path2"
|
||||
style="stroke:#ffffff;stroke-opacity:1" />
|
||||
<path
|
||||
d="M11 13v2m0 3v2m4 -5v2m0 3v2"
|
||||
id="path3"
|
||||
style="stroke:#ffffff;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 417 B After Width: | Height: | Size: 780 B |
35
common/icons/dots.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="icon icon-tabler icons-tabler-outline icon-tabler-dots"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<path
|
||||
stroke="none"
|
||||
d="M0 0h24v24H0z"
|
||||
fill="none"
|
||||
id="path1" />
|
||||
<path
|
||||
d="M4 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"
|
||||
id="path2"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:1" />
|
||||
<path
|
||||
d="M11 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"
|
||||
id="path3"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:1" />
|
||||
<path
|
||||
d="M18 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"
|
||||
id="path4"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 956 B |
43
common/icons/dots.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://elds8r780ted"
|
||||
path="res://.godot/imported/dots.svg-c5aaef419fc83aaf7bc0ae28dabbcd0f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://common/icons/dots.svg"
|
||||
dest_files=["res://.godot/imported/dots.svg-c5aaef419fc83aaf7bc0ae28dabbcd0f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
svg/scale=2.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
23
common/icons/leaf.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="icon icon-tabler icons-tabler-filled icon-tabler-leaf"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<path
|
||||
stroke="none"
|
||||
d="M0 0h24v24H0z"
|
||||
fill="none"
|
||||
id="path1" />
|
||||
<path
|
||||
d="M3.055 14.328l-.018 -.168l-.004 -.043a11 11 0 0 1 -.047 -1.12c.018 -6.29 4.29 -9.997 13 -9.997h4.014a1 1 0 0 1 1 1l-.002 2.057c-.498 8.701 -4.74 12.943 -11.998 12.943h-2.631a16 16 0 0 0 -.375 2.11a1 1 0 1 1 -1.988 -.22q .174 -1.568 .58 -2.947l-.118 -.146l-.208 -.28l-.157 -.229l-.182 -.293l-.098 -.171l-.065 -.122a6 6 0 0 1 -.397 -.941l-.072 -.237l-.085 -.327l-.057 -.268l-.043 -.242zm8.539 -4.242c-2.845 1.265 -4.854 3.13 -6.108 5.583q .098 .2 .218 .4l.185 .281l.07 .097q .12 .164 .258 .329l.197 .224h.649c1.037 -2.271 2.777 -3.946 5.343 -5.086a1 1 0 0 0 -.812 -1.828"
|
||||
id="path2"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
43
common/icons/leaf.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://koow6s66cvqk"
|
||||
path="res://.godot/imported/leaf.svg-64997ac177eb77fe732afcb627c1b7f7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://common/icons/leaf.svg"
|
||||
dest_files=["res://.godot/imported/leaf.svg-64997ac177eb77fe732afcb627c1b7f7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
svg/scale=2.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
31
common/icons/sparkles.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="icon icon-tabler icons-tabler-filled icon-tabler-sparkles"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<path
|
||||
stroke="none"
|
||||
d="M0 0h24v24H0z"
|
||||
fill="none"
|
||||
id="path1" />
|
||||
<path
|
||||
d="M16 19a1 1 0 0 1 0 -2a1 1 0 0 0 1 -1c0 -1.333 2 -1.333 2 0a1 1 0 0 0 1 1c1.333 0 1.333 2 0 2a1 1 0 0 0 -1 1c0 1.333 -2 1.333 -2 0a1 1 0 0 0 -1 -1"
|
||||
id="path2"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
<path
|
||||
d="M3 11a5 5 0 0 0 5 -5c0 -1.333 2 -1.333 2 0a5 5 0 0 0 5 5c1.333 0 1.333 2 0 2a5 5 0 0 0 -5 5a1 1 0 0 1 -2 0a5 5 0 0 0 -5 -5c-1.333 0 -1.333 -2 0 -2"
|
||||
id="path3"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
<path
|
||||
d="M16 7a1 1 0 0 1 0 -2a1 1 0 0 0 1 -1c0 -1.333 2 -1.333 2 0a1 1 0 0 0 1 1c1.333 0 1.333 2 0 2a1 1 0 0 0 -1 1c0 1.333 -2 1.333 -2 0a1 1 0 0 0 -1 -1"
|
||||
id="path4"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
43
common/icons/sparkles.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://djq2l8aun82ou"
|
||||
path="res://.godot/imported/sparkles.svg-916e1088ab60f75f5f1370971c1faf2d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://common/icons/sparkles.svg"
|
||||
dest_files=["res://.godot/imported/sparkles.svg-916e1088ab60f75f5f1370971c1faf2d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
svg/scale=2.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -1,237 +1,38 @@
|
||||
[gd_scene format=3 uid="uid://b8gqdgabrjaml"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dt2ip3pw2cboy" path="res://entities/plants/scripts/texture_builder/plant_texture_builder.gd" id="1_s8rsj"]
|
||||
[ext_resource type="Script" uid="uid://b3jwglylqdqtw" path="res://entities/plants/scripts/texture_builder/plant_part.gd" id="2_cfiqo"]
|
||||
[ext_resource type="Resource" uid="uid://7hrdkl6bf3o4" path="res://entities/plants/resources/plant_parts/Branch1.tres" id="4_ggud5"]
|
||||
[ext_resource type="Resource" uid="uid://d05ulm22k31w8" path="res://entities/plants/resources/plant_parts/Base2.tres" id="4_ron2q"]
|
||||
[ext_resource type="Resource" uid="uid://bfyafiewcrjln" path="res://entities/plants/resources/plant_parts/Base1.tres" id="5_5fbyu"]
|
||||
[ext_resource type="Resource" uid="uid://d2vdf2sth2xjm" path="res://entities/plants/resources/plant_parts/Base3.tres" id="5_6raaj"]
|
||||
[ext_resource type="Resource" uid="uid://ceisi5yyie7to" path="res://entities/plants/resources/plant_parts/Branch2.tres" id="5_ufbqh"]
|
||||
[ext_resource type="Resource" uid="uid://cxiu8frk04b5i" path="res://entities/plants/resources/plant_parts/Branch3.tres" id="6_jbu3q"]
|
||||
[ext_resource type="Resource" uid="uid://53p5g07e3pb4" path="res://entities/plants/resources/plant_parts/Branch4.tres" id="7_21cjy"]
|
||||
[ext_resource type="Resource" uid="uid://cyfyodtfxne1w" path="res://entities/plants/resources/plant_parts/Branch5.tres" id="8_qm7aw"]
|
||||
[ext_resource type="Resource" uid="uid://cuvtf4y1dspcp" path="res://entities/plants/resources/plant_parts/Branch6.tres" id="9_3e4c1"]
|
||||
[ext_resource type="Resource" uid="uid://dcjpei6xuiswp" path="res://entities/plants/resources/plant_parts/LeafE5.tres" id="10_5job5"]
|
||||
[ext_resource type="Resource" uid="uid://85bwx2tdjx0h" path="res://entities/plants/resources/plant_parts/LeafE7.tres" id="12_b21au"]
|
||||
[ext_resource type="Resource" uid="uid://6jnbms553dyd" path="res://entities/plants/resources/plant_parts/Branch7.tres" id="12_yjh7s"]
|
||||
[ext_resource type="Resource" uid="uid://cowkewtw2lg4i" path="res://entities/plants/resources/plant_parts/FlowerA1.tres" id="13_17ac3"]
|
||||
[ext_resource type="Resource" uid="uid://bgv3nb10t6u3y" path="res://entities/plants/resources/plant_parts/LeafF1.tres" id="14_8r35x"]
|
||||
[ext_resource type="Resource" uid="uid://drn8vt4sp7u6q" path="res://entities/plants/resources/plant_parts/FlowerA2.tres" id="14_fuh2b"]
|
||||
[ext_resource type="Resource" uid="uid://c31h25xdll8si" path="res://entities/plants/resources/plant_parts/FlowerD1.tres" id="15_3ti7v"]
|
||||
[ext_resource type="Resource" uid="uid://b3oqeugtrtera" path="res://entities/plants/resources/plant_parts/FlowerA3.tres" id="15_28gvc"]
|
||||
[ext_resource type="Resource" uid="uid://be17y7clvf88e" path="res://entities/plants/resources/plant_parts/LeafE1.tres" id="15_p2blu"]
|
||||
[ext_resource type="Resource" uid="uid://bkgw3wmoam10n" path="res://entities/plants/resources/plant_parts/LeafF2.tres" id="15_rbd7l"]
|
||||
[ext_resource type="Resource" uid="uid://ydvnxfnkbr2g" path="res://entities/plants/resources/plant_parts/FlowerA4.tres" id="16_1cwvj"]
|
||||
[ext_resource type="Script" uid="uid://cfjd8jelpm8dt" path="res://entities/plants/scripts/texture_builder/part_mutation_association.gd" id="16_c3tk3"]
|
||||
[ext_resource type="Resource" uid="uid://r5mspvasrq6y" path="res://entities/plants/resources/plant_parts/FlowerD2.tres" id="16_cpf4r"]
|
||||
[ext_resource type="Resource" uid="uid://m8j2gfumw11w" path="res://entities/plants/resources/plant_parts/LeafF3.tres" id="16_nfxo0"]
|
||||
[ext_resource type="Resource" uid="uid://dw7wdihxxy0uf" path="res://entities/plants/resources/plant_parts/LeafE2.tres" id="16_xt8tm"]
|
||||
[ext_resource type="Resource" uid="uid://dl1k0jv662m67" path="res://entities/plants/resources/plant_parts/FlowerA5.tres" id="17_c3tk3"]
|
||||
[ext_resource type="Resource" uid="uid://dth4dp88vs3gh" path="res://entities/plants/resources/plant_parts/LeafF4.tres" id="17_i8j71"]
|
||||
[ext_resource type="Resource" uid="uid://br80voioh4jxu" path="res://entities/plants/resources/plant_parts/FlowerD3.tres" id="17_p2blu"]
|
||||
[ext_resource type="Resource" uid="uid://3yi8hk73s5mm" path="res://entities/plants/resources/plant_parts/LeafE3.tres" id="17_qm7aw"]
|
||||
[ext_resource type="Resource" uid="uid://c8ttq14no8872" path="res://entities/plants/resources/plant_parts/LeafF7.tres" id="18_2plsj"]
|
||||
[ext_resource type="Resource" uid="uid://cka6sihumbjpw" path="res://entities/plants/resources/plant_parts/LeafF5.tres" id="18_l2vrg"]
|
||||
[ext_resource type="Resource" uid="uid://par4hf5gjvqu" path="res://entities/plants/resources/plant_parts/FlowerA6.tres" id="18_q0jli"]
|
||||
[ext_resource type="Resource" uid="uid://dq5dwqherb6ac" path="res://entities/plants/resources/plant_parts/LeafF6.tres" id="19_14c4k"]
|
||||
[ext_resource type="Resource" uid="uid://414go2hmhy12" path="res://entities/plants/resources/plant_parts/FlowerA7.tres" id="19_rplk6"]
|
||||
[ext_resource type="Resource" uid="uid://drc3g203302d4" path="res://entities/plants/resources/plant_parts/LeafF8.tres" id="19_yjh7s"]
|
||||
[ext_resource type="Resource" uid="uid://ckef0dno4j5mn" path="res://entities/plants/resources/plant_parts/FlowerF1.tres" id="20_lggh7"]
|
||||
[ext_resource type="Resource" uid="uid://dhhyh56shnure" path="res://entities/plants/resources/plant_parts/FlowerF2.tres" id="21_8r35x"]
|
||||
[ext_resource type="Resource" uid="uid://banfc3pgm6a0m" path="res://entities/plants/resources/plant_parts/FlowerC1.tres" id="21_alra6"]
|
||||
[ext_resource type="Resource" uid="uid://dmdyj7t4g48p" path="res://entities/plants/resources/plant_parts/FlowerC2.tres" id="22_lggh7"]
|
||||
[ext_resource type="Resource" uid="uid://bhj7j78tokt25" path="res://entities/plants/resources/plant_parts/FlowerC3.tres" id="23_8r35x"]
|
||||
[ext_resource type="Resource" uid="uid://c3w8lel02552f" path="res://entities/plants/resources/plant_parts/FlowerD4.tres" id="23_mcckl"]
|
||||
[ext_resource type="Resource" uid="uid://hbylxbmmc8of" path="res://entities/plants/resources/plant_parts/FlowerD5.tres" id="24_3hcun"]
|
||||
[ext_resource type="Resource" uid="uid://bkgrwffi7m2i4" path="res://entities/plants/resources/plant_parts/FlowerC4.tres" id="24_rbd7l"]
|
||||
[ext_resource type="Resource" uid="uid://dm7crxxg4kmw5" path="res://entities/plants/resources/plant_parts/LeafB1.tres" id="25_kvmj5"]
|
||||
[ext_resource type="Resource" uid="uid://vlxrq3tw1t6m" path="res://entities/plants/resources/plant_parts/LeafB2.tres" id="26_ocu7e"]
|
||||
[ext_resource type="Script" path="res://entities/plants/scripts/texture_builder/plant_attach.gd" id="27_80nvf"]
|
||||
[ext_resource type="Resource" uid="uid://bdwhp12xkvscr" path="res://entities/plants/resources/plant_parts/LeafB3.tres" id="27_q771y"]
|
||||
[ext_resource type="Resource" uid="uid://cc8kkqiqdvex6" path="res://entities/plants/resources/plant_parts/LeafE8.tres" id="27_rcays"]
|
||||
[ext_resource type="Resource" uid="uid://dmfiww0l5ha2l" path="res://entities/plants/resources/plant_parts/LeafB4.tres" id="28_5fbyu"]
|
||||
[ext_resource type="Texture2D" uid="uid://bdwmandgxrjgn" path="res://entities/plants/assets/sprites/asset_plantes.png" id="28_d04mb"]
|
||||
[ext_resource type="Resource" uid="uid://cusd2od1yj0ob" path="res://entities/plants/resources/plant_parts/LeafE9.tres" id="28_qs4je"]
|
||||
[ext_resource type="Resource" uid="uid://kiim46eda050" path="res://entities/plants/resources/plant_parts/LeafB5.tres" id="29_2plsj"]
|
||||
[ext_resource type="Resource" uid="uid://dqroc7h70bgew" path="res://entities/plants/resources/plant_parts/LeafB6.tres" id="30_yjh7s"]
|
||||
[ext_resource type="Resource" uid="uid://u1f6c41fvau5" path="res://entities/plants/resources/plant_parts/FlowerC5.tres" id="35_abtbh"]
|
||||
[ext_resource type="Resource" uid="uid://bap5xihdc3gbe" path="res://entities/plants/resources/plant_parts/FlowerC6.tres" id="36_j5s6w"]
|
||||
[ext_resource type="Resource" uid="uid://cwskfwdasvv0g" path="res://entities/plants/resources/plant_parts/FlowerF3.tres" id="38_rbd7l"]
|
||||
[ext_resource type="Resource" uid="uid://e0u1baixvaxn" path="res://entities/plants/resources/plant_parts/LeafE4.tres" id="42_14c4k"]
|
||||
[ext_resource type="Resource" uid="uid://br4e84rsg87e8" path="res://entities/plants/resources/plant_parts/FlowerB4.tres" id="44_7b70u"]
|
||||
[ext_resource type="Resource" uid="uid://c4artcndro0r5" path="res://entities/plants/resources/plant_parts/FlowerB1.tres" id="45_coupj"]
|
||||
[ext_resource type="Resource" uid="uid://bvujlvgbh4pyc" path="res://entities/plants/resources/plant_parts/FlowerB5.tres" id="45_q8uyx"]
|
||||
[ext_resource type="Resource" uid="uid://b3dfua388ub4k" path="res://entities/plants/resources/plant_parts/FlowerB2.tres" id="46_ggud5"]
|
||||
[ext_resource type="Resource" uid="uid://dsoolh270ygjd" path="res://entities/plants/resources/plant_parts/FlowerB6.tres" id="46_r166b"]
|
||||
[ext_resource type="Resource" uid="uid://dad6gfvwocnj2" path="res://entities/plants/resources/plant_parts/Base.tres" id="2_tod72"]
|
||||
[ext_resource type="Resource" uid="uid://c6s833k5g18kd" path="res://entities/plants/resources/plant_parts/Branch.tres" id="3_0ca8x"]
|
||||
[ext_resource type="Resource" uid="uid://bjb8g6h6lf4r2" path="res://entities/plants/resources/plant_parts/LeafF.tres" id="4_js3cu"]
|
||||
[ext_resource type="Script" uid="uid://u2j7hn5her8i" path="res://entities/plants/scripts/texture_builder/part_group.gd" id="5_npk80"]
|
||||
[ext_resource type="Resource" uid="uid://bwqa7vouqhjmr" path="res://entities/plants/resources/plant_parts/LeafE.tres" id="6_hyb2i"]
|
||||
[ext_resource type="Resource" uid="uid://b4g4d6y1n1xgp" path="res://entities/plants/resources/plant_parts/LeafI.tres" id="7_4gk8a"]
|
||||
[ext_resource type="Resource" uid="uid://b4towph8opjgh" path="res://entities/plants/resources/plant_parts/LeafNB.tres" id="8_alra6"]
|
||||
[ext_resource type="Resource" uid="uid://cqfb7vkocd8gf" path="res://entities/plants/resources/plant_parts/LeafL.tres" id="9_lggh7"]
|
||||
[ext_resource type="Resource" uid="uid://dw78e1nnajsxf" path="res://entities/plants/resources/plant_parts/FlowerD.tres" id="10_8r35x"]
|
||||
[ext_resource type="Resource" uid="uid://bg2x1g8xfjk0t" path="res://entities/plants/resources/plant_parts/FlowerG.tres" id="11_rbd7l"]
|
||||
[ext_resource type="Resource" uid="uid://bcgc05pjefid6" path="res://entities/plants/resources/plant_parts/FlowerH.tres" id="12_nfxo0"]
|
||||
[ext_resource type="Resource" uid="uid://dvwmk2suorf05" path="res://entities/plants/resources/plant_parts/LeafG.tres" id="12_tjaiv"]
|
||||
[ext_resource type="Resource" uid="uid://di0nwki5jr205" path="res://entities/plants/resources/plant_parts/FlowerJ.tres" id="13_i8j71"]
|
||||
[ext_resource type="Resource" uid="uid://b3ff6xg5lmevs" path="res://entities/plants/resources/plant_parts/FlowerF.tres" id="14_l2vrg"]
|
||||
[ext_resource type="Resource" uid="uid://bcakrgxsy6c08" path="res://entities/plants/resources/plant_parts/LeafC.tres" id="15_14c4k"]
|
||||
[ext_resource type="Resource" uid="uid://bggtymdph3itl" path="res://entities/plants/resources/plant_parts/FlowerI.tres" id="16_rt6tw"]
|
||||
[ext_resource type="Resource" uid="uid://dfaticpdeqmkx" path="res://entities/plants/resources/plant_parts/LeafB.tres" id="17_y02ao"]
|
||||
[ext_resource type="Resource" uid="uid://rjisqiox5anv" path="res://entities/plants/resources/plant_parts/LeafHA.tres" id="18_s8rsj"]
|
||||
[ext_resource type="Resource" uid="uid://c1qdy7oeyqdlu" path="res://entities/plants/resources/plant_parts/FlowerC.tres" id="19_cfiqo"]
|
||||
[ext_resource type="Resource" uid="uid://1ldoefk201qd" path="res://entities/plants/resources/plant_parts/LeafM.tres" id="20_coupj"]
|
||||
[ext_resource type="Resource" uid="uid://cnyux2rxespor" path="res://entities/plants/resources/plant_parts/LeafJ.tres" id="21_ggud5"]
|
||||
[ext_resource type="Resource" uid="uid://wim0ex3g6eag" path="res://entities/plants/resources/plant_parts/LeafNA.tres" id="23_hyb2i"]
|
||||
[ext_resource type="Resource" uid="uid://bd4suw64pumd5" path="res://entities/plants/resources/plant_parts/LeafHB.tres" id="24_4gk8a"]
|
||||
[ext_resource type="Resource" uid="uid://dej2j8nvb5gcj" path="res://entities/plants/resources/plant_parts/FlowerB.tres" id="25_alra6"]
|
||||
[ext_resource type="Resource" uid="uid://0r8c2a0el62b" path="res://entities/plants/resources/plant_parts/LeafK.tres" id="26_lggh7"]
|
||||
[ext_resource type="Script" uid="uid://hs3i48clok85" path="res://entities/plants/scripts/texture_builder/seed_texture_set.gd" id="47_jbu3q"]
|
||||
[ext_resource type="Resource" uid="uid://bnn0tcoab4plv" path="res://entities/plants/resources/plant_parts/FlowerB3.tres" id="47_ufbqh"]
|
||||
[ext_resource type="Texture2D" uid="uid://cuqocuhfpdful" path="res://entities/plants/assets/sprites/seeds/pick-sphere/color_1.png" id="48_21cjy"]
|
||||
[ext_resource type="Texture2D" uid="uid://tcjcq04akuns" path="res://entities/plants/assets/sprites/seeds/pick-sphere/color_2.png" id="49_rs2ow"]
|
||||
[ext_resource type="Texture2D" uid="uid://cu1dajkls18x0" path="res://entities/plants/assets/sprites/seeds/pick-sphere/line.png" id="50_5job5"]
|
||||
[ext_resource type="Texture2D" uid="uid://cq2f308itghq7" path="res://entities/plants/assets/sprites/seeds/haricot/color_1.png" id="51_5job5"]
|
||||
[ext_resource type="Texture2D" uid="uid://b3apxg55cjoow" path="res://entities/plants/assets/sprites/seeds/haricot/color_2.png" id="52_cynqk"]
|
||||
[ext_resource type="Texture2D" uid="uid://0ayiumcnqyc1" path="res://entities/plants/assets/sprites/seeds/haricot/outline.png" id="53_b21au"]
|
||||
[ext_resource type="Resource" uid="uid://djcwxfp4vmj8n" path="res://entities/plants/resources/plant_parts/FlowerG1.tres" id="61_tx02s"]
|
||||
[ext_resource type="Resource" uid="uid://d246bni7ooe20" path="res://entities/plants/resources/plant_parts/FlowerG2.tres" id="62_mpcbs"]
|
||||
[ext_resource type="Resource" uid="uid://docbn71tiiwrw" path="res://entities/plants/resources/plant_parts/FlowerG3.tres" id="63_kqtu4"]
|
||||
[ext_resource type="Resource" uid="uid://d4hfht8t7ridu" path="res://entities/plants/resources/plant_parts/FlowerG4.tres" id="64_2f2lk"]
|
||||
[ext_resource type="Resource" uid="uid://cto5os1i12qtj" path="res://entities/plants/resources/plant_parts/FlowerG5.tres" id="65_4e7sd"]
|
||||
[ext_resource type="Resource" uid="uid://bugnwjpjydm8t" path="res://entities/plants/resources/plant_parts/FlowerG6.tres" id="66_ro8dt"]
|
||||
[ext_resource type="Resource" uid="uid://0n7qxw0qhn1y" path="res://entities/plants/resources/plant_parts/FlowerG7.tres" id="67_wmd0c"]
|
||||
[ext_resource type="Resource" uid="uid://ccatceyu73pg5" path="res://entities/plants/resources/plant_parts/FlowerH1.tres" id="68_wl0th"]
|
||||
[ext_resource type="Resource" uid="uid://2qihbxsn6odd" path="res://entities/plants/resources/plant_parts/FlowerH2.tres" id="69_55s8o"]
|
||||
[ext_resource type="Resource" uid="uid://bwvou260f8tb5" path="res://entities/plants/resources/plant_parts/FlowerH3.tres" id="70_gquge"]
|
||||
[ext_resource type="Resource" uid="uid://c7lbyqhtaglql" path="res://entities/plants/resources/plant_parts/FlowerH4.tres" id="71_2cr6t"]
|
||||
[ext_resource type="Resource" uid="uid://qfku0xgnyc8l" path="res://entities/plants/resources/plant_parts/FlowerH5.tres" id="72_yrtob"]
|
||||
[ext_resource type="Resource" uid="uid://cjyqycj2nexk5" path="res://entities/plants/resources/plant_parts/FlowerH6.tres" id="73_y4irp"]
|
||||
[ext_resource type="Resource" uid="uid://v1b4c6364bjj" path="res://entities/plants/resources/plant_parts/FlowerJ1.tres" id="74_sdhn1"]
|
||||
[ext_resource type="Resource" uid="uid://cji73bqaytm0r" path="res://entities/plants/resources/plant_parts/FlowerJ2.tres" id="75_lyw6c"]
|
||||
[ext_resource type="Resource" uid="uid://b2tnb0vs1gtj3" path="res://entities/plants/resources/plant_parts/FlowerJ3.tres" id="76_lvv85"]
|
||||
[ext_resource type="Resource" uid="uid://daf1u222v1eqm" path="res://entities/plants/resources/plant_parts/FlowerJ4.tres" id="77_y5qox"]
|
||||
[ext_resource type="Resource" uid="uid://c3t8gj1sc7lrn" path="res://entities/plants/resources/plant_parts/FlowerF4.tres" id="81_ar53y"]
|
||||
[ext_resource type="Resource" uid="uid://cxsbv241mpuma" path="res://entities/plants/resources/plant_parts/FlowerF5.tres" id="82_oaoto"]
|
||||
[ext_resource type="Resource" uid="uid://5a5ya2iirvwr" path="res://entities/plants/resources/plant_parts/FlowerF6.tres" id="83_06vhr"]
|
||||
[ext_resource type="Resource" uid="uid://cc8xi518vdixm" path="res://entities/plants/resources/plant_parts/FlowerF7.tres" id="84_rcays"]
|
||||
[ext_resource type="Resource" uid="uid://dk7hp700k8iet" path="res://entities/plants/resources/plant_parts/LeafC1.tres" id="85_qs4je"]
|
||||
[ext_resource type="Resource" uid="uid://dte3i6hma7nw5" path="res://entities/plants/resources/plant_parts/LeafC2.tres" id="86_ron2q"]
|
||||
[ext_resource type="Resource" uid="uid://p2mveyriuh47" path="res://entities/plants/resources/plant_parts/LeafC3.tres" id="87_6raaj"]
|
||||
[ext_resource type="Resource" uid="uid://be7x75w7l5jls" path="res://entities/plants/resources/plant_parts/LeafC4.tres" id="88_80nvf"]
|
||||
[ext_resource type="Resource" uid="uid://dhgabeqsda06o" path="res://entities/plants/resources/plant_parts/LeafC5.tres" id="89_d04mb"]
|
||||
[ext_resource type="Resource" uid="uid://b2bv30j55dtfn" path="res://entities/plants/resources/plant_parts/LeafC6.tres" id="90_6htke"]
|
||||
[ext_resource type="Resource" uid="uid://d5k40ooxsnaw" path="res://entities/plants/resources/plant_parts/LeafC7.tres" id="91_y2dg6"]
|
||||
[ext_resource type="Resource" uid="uid://cjyp8jcocoijg" path="res://entities/plants/resources/plant_parts/FlowerI1.tres" id="92_s2hpp"]
|
||||
[ext_resource type="Resource" uid="uid://bac4q2s3bylli" path="res://entities/plants/resources/plant_parts/FlowerI2.tres" id="93_mxpq5"]
|
||||
[ext_resource type="Resource" uid="uid://dst4c2mnhaili" path="res://entities/plants/resources/plant_parts/FlowerI3.tres" id="94_o5eac"]
|
||||
[ext_resource type="Resource" uid="uid://bjd8ix6ouc1b2" path="res://entities/plants/resources/plant_parts/FlowerI4.tres" id="95_qxf4o"]
|
||||
[ext_resource type="Resource" uid="uid://cs2piasjw3x5s" path="res://entities/plants/resources/plant_parts/FlowerI5.tres" id="96_lfwcq"]
|
||||
[ext_resource type="Resource" uid="uid://tbqitr1nwt2" path="res://entities/plants/resources/plant_parts/FlowerI6.tres" id="97_fe472"]
|
||||
[ext_resource type="Resource" uid="uid://b4v7vvv0jdh0k" path="res://entities/plants/resources/plant_parts/LeafHA1.tres" id="100_ron2q"]
|
||||
[ext_resource type="Resource" uid="uid://bvff3ay7qpvgs" path="res://entities/plants/resources/plant_parts/LeafHA2.tres" id="101_6raaj"]
|
||||
[ext_resource type="Resource" uid="uid://mwjpvg6xqxyl" path="res://entities/plants/resources/plant_parts/LeafHA3.tres" id="102_80nvf"]
|
||||
[ext_resource type="Resource" uid="uid://b4hh8jrpenwt3" path="res://entities/plants/resources/plant_parts/LeafNA1.tres" id="103_6raaj"]
|
||||
[ext_resource type="Resource" uid="uid://cm5rtqrvsmqkg" path="res://entities/plants/resources/plant_parts/LeafHA5.tres" id="103_d04mb"]
|
||||
[ext_resource type="Resource" uid="uid://bf4s8omnaau0w" path="res://entities/plants/resources/plant_parts/LeafHA6.tres" id="104_6htke"]
|
||||
[ext_resource type="Resource" uid="uid://cv23vy5w5i3iy" path="res://entities/plants/resources/plant_parts/LeafNA2.tres" id="104_80nvf"]
|
||||
[ext_resource type="Resource" uid="uid://c85k4e0rio0cs" path="res://entities/plants/resources/plant_parts/LeafNA3.tres" id="105_d04mb"]
|
||||
[ext_resource type="Resource" uid="uid://iv3nmggoklh3" path="res://entities/plants/resources/plant_parts/LeafHA7.tres" id="105_y2dg6"]
|
||||
[ext_resource type="Resource" uid="uid://cqbwlfnxg7a83" path="res://entities/plants/resources/plant_parts/LeafNA4.tres" id="106_6htke"]
|
||||
[ext_resource type="Resource" uid="uid://bff32l0awxl38" path="res://entities/plants/resources/plant_parts/LeafJ1.tres" id="106_s2hpp"]
|
||||
[ext_resource type="Resource" uid="uid://ddaa77cqi865e" path="res://entities/plants/resources/plant_parts/LeafJ2.tres" id="107_mxpq5"]
|
||||
[ext_resource type="Resource" uid="uid://d1o0tp1cc6gga" path="res://entities/plants/resources/plant_parts/LeafNA5.tres" id="107_y2dg6"]
|
||||
[ext_resource type="Resource" uid="uid://brlnlvjpjidd5" path="res://entities/plants/resources/plant_parts/LeafJ3.tres" id="108_o5eac"]
|
||||
[ext_resource type="Resource" uid="uid://cn2j8fh3l2tdo" path="res://entities/plants/resources/plant_parts/LeafJ4.tres" id="109_qxf4o"]
|
||||
[ext_resource type="Resource" uid="uid://ccafrqprfm6r2" path="res://entities/plants/resources/plant_parts/LeafJ5.tres" id="110_lfwcq"]
|
||||
[ext_resource type="Resource" uid="uid://dyjojp8mfs5vy" path="res://entities/plants/resources/plant_parts/LeafK1.tres" id="111_fe472"]
|
||||
[ext_resource type="Resource" uid="uid://trorp4elyagc" path="res://entities/plants/resources/plant_parts/LeafK2.tres" id="112_2ycc1"]
|
||||
[ext_resource type="Resource" uid="uid://c6yxy44mt6fgt" path="res://entities/plants/resources/plant_parts/LeafK3.tres" id="113_wat4j"]
|
||||
[ext_resource type="Resource" uid="uid://dw4o76c3nuor5" path="res://entities/plants/resources/plant_parts/LeafK4.tres" id="114_n8y6x"]
|
||||
[ext_resource type="Resource" uid="uid://h18vambxcrwj" path="res://entities/plants/resources/plant_parts/LeafK5.tres" id="115_dw70q"]
|
||||
[ext_resource type="Resource" uid="uid://c70ikd1qfmqqs" path="res://entities/plants/resources/plant_parts/LeafK6.tres" id="116_xxckx"]
|
||||
[ext_resource type="Resource" uid="uid://bhgnimmagff30" path="res://entities/plants/resources/plant_parts/LeafK7.tres" id="117_s3ec7"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_us16y"]
|
||||
script = ExtResource("27_80nvf")
|
||||
position = Vector2(-36, -37)
|
||||
attach_types = Array[int]([2])
|
||||
|
||||
[sub_resource type="Resource" id="Resource_ux8f6"]
|
||||
script = ExtResource("27_80nvf")
|
||||
position = Vector2(59, -81)
|
||||
attach_types = Array[int]([1, 3])
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_yh7e0"]
|
||||
atlas = ExtResource("28_d04mb")
|
||||
region = Rect2(2804, 1567, 294, 345)
|
||||
|
||||
[sub_resource type="Resource" id="Resource_6htke"]
|
||||
resource_name = "LeafE6"
|
||||
script = ExtResource("2_cfiqo")
|
||||
texture = SubResource("AtlasTexture_yh7e0")
|
||||
type = 2
|
||||
root = SubResource("Resource_ux8f6")
|
||||
attaches = Array[ExtResource("27_80nvf")]([SubResource("Resource_us16y")])
|
||||
|
||||
[sub_resource type="Resource" id="Resource_rt6tw"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("15_p2blu"), ExtResource("16_xt8tm"), ExtResource("17_qm7aw"), ExtResource("42_14c4k"), ExtResource("10_5job5"), SubResource("Resource_6htke"), ExtResource("12_b21au"), ExtResource("27_rcays"), ExtResource("28_qs4je")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_y02ao"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("13_17ac3"), ExtResource("14_fuh2b"), ExtResource("15_28gvc"), ExtResource("16_1cwvj"), ExtResource("17_c3tk3"), ExtResource("18_q0jli"), ExtResource("19_rplk6")])
|
||||
part_amount = 5
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_nfxo0"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("15_3ti7v"), ExtResource("16_cpf4r"), ExtResource("17_p2blu"), ExtResource("23_mcckl"), ExtResource("24_3hcun")])
|
||||
part_amount = 5
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_2ycc1"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("61_tx02s"), ExtResource("62_mpcbs"), ExtResource("63_kqtu4"), ExtResource("64_2f2lk"), ExtResource("65_4e7sd"), ExtResource("66_ro8dt"), ExtResource("67_wmd0c")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_wat4j"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("68_wl0th"), ExtResource("69_55s8o"), ExtResource("70_gquge"), ExtResource("71_2cr6t"), ExtResource("72_yrtob"), ExtResource("73_y4irp")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_n8y6x"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("74_sdhn1"), ExtResource("75_lyw6c"), ExtResource("76_lvv85"), ExtResource("77_y5qox")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_dw70q"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("20_lggh7"), ExtResource("21_8r35x"), ExtResource("38_rbd7l"), ExtResource("81_ar53y"), ExtResource("82_oaoto"), ExtResource("83_06vhr"), ExtResource("84_rcays")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_xxckx"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("85_qs4je"), ExtResource("86_ron2q"), ExtResource("87_6raaj"), ExtResource("88_80nvf"), ExtResource("89_d04mb"), ExtResource("90_6htke"), ExtResource("91_y2dg6")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_s3ec7"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("92_s2hpp"), ExtResource("93_mxpq5"), ExtResource("94_o5eac"), ExtResource("95_qxf4o"), ExtResource("96_lfwcq"), ExtResource("97_fe472")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_s8rsj"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("25_kvmj5"), ExtResource("26_ocu7e"), ExtResource("27_q771y"), ExtResource("28_5fbyu"), ExtResource("29_2plsj"), ExtResource("30_yjh7s")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_kro31"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("100_ron2q"), ExtResource("101_6raaj"), ExtResource("102_80nvf"), ExtResource("103_d04mb"), ExtResource("104_6htke"), ExtResource("105_y2dg6")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_14c4k"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("21_alra6"), ExtResource("22_lggh7"), ExtResource("23_8r35x"), ExtResource("24_rbd7l"), ExtResource("35_abtbh"), ExtResource("36_j5s6w")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_p4lk1"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("106_s2hpp"), ExtResource("107_mxpq5"), ExtResource("108_o5eac"), ExtResource("109_qxf4o"), ExtResource("110_lfwcq")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_b21au"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("103_6raaj"), ExtResource("104_80nvf"), ExtResource("105_d04mb"), ExtResource("106_6htke"), ExtResource("107_y2dg6")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_jbu3q"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("45_coupj"), ExtResource("46_ggud5"), ExtResource("47_ufbqh"), ExtResource("44_7b70u"), ExtResource("45_q8uyx"), ExtResource("46_r166b")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_0wuoa"]
|
||||
script = ExtResource("16_c3tk3")
|
||||
parts = Array[ExtResource("2_cfiqo")]([ExtResource("111_fe472"), ExtResource("112_2ycc1"), ExtResource("113_wat4j"), ExtResource("114_n8y6x"), ExtResource("115_dw70q"), ExtResource("116_xxckx"), ExtResource("117_s3ec7")])
|
||||
metadata/_custom_type_script = "uid://cfjd8jelpm8dt"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_cynqk"]
|
||||
script = ExtResource("47_jbu3q")
|
||||
@@ -246,28 +47,32 @@ metadata/_custom_type_script = "uid://hs3i48clok85"
|
||||
|
||||
[node name="TextureBuilder" type="Node" unique_id=79184097]
|
||||
script = ExtResource("1_s8rsj")
|
||||
bases = Array[ExtResource("2_cfiqo")]([ExtResource("5_5fbyu"), ExtResource("4_ron2q")])
|
||||
baby_bases = Array[ExtResource("2_cfiqo")]([ExtResource("5_6raaj")])
|
||||
branches = Array[ExtResource("2_cfiqo")]([ExtResource("4_ggud5"), ExtResource("5_ufbqh"), ExtResource("6_jbu3q"), ExtResource("7_21cjy"), ExtResource("8_qm7aw"), ExtResource("9_3e4c1"), ExtResource("12_yjh7s")])
|
||||
n_branches = 5
|
||||
base_leaves = Array[ExtResource("2_cfiqo")]([ExtResource("14_8r35x"), ExtResource("15_rbd7l"), ExtResource("16_nfxo0"), ExtResource("17_i8j71"), ExtResource("18_l2vrg"), ExtResource("19_14c4k"), ExtResource("18_2plsj"), ExtResource("19_yjh7s")])
|
||||
parts_mutation_associations = Dictionary[String, ExtResource("16_c3tk3")]({
|
||||
"ANCIENT": SubResource("Resource_rt6tw"),
|
||||
"EPHEMERAL": SubResource("Resource_y02ao"),
|
||||
"FERTILE": SubResource("Resource_nfxo0"),
|
||||
"GENEROUS": SubResource("Resource_2ycc1"),
|
||||
"HURRIED": SubResource("Resource_wat4j"),
|
||||
"PRECOCIOUS": SubResource("Resource_n8y6x"),
|
||||
"PROLIFIC": SubResource("Resource_dw70q"),
|
||||
"PROTECTIVE": SubResource("Resource_xxckx"),
|
||||
"PURE": SubResource("Resource_s3ec7"),
|
||||
"PURIFICATION": SubResource("Resource_s8rsj"),
|
||||
"QUALITY": SubResource("Resource_kro31"),
|
||||
"QUICK": SubResource("Resource_14c4k"),
|
||||
"ROBUST": SubResource("Resource_p4lk1"),
|
||||
"SOCIABLE": SubResource("Resource_b21au"),
|
||||
"TOUGH": SubResource("Resource_jbu3q"),
|
||||
"VIVACIOUS": SubResource("Resource_0wuoa")
|
||||
all_bases = ExtResource("2_tod72")
|
||||
baby_bases_start_ind = 2
|
||||
branches = ExtResource("3_0ca8x")
|
||||
base_leaves = ExtResource("4_js3cu")
|
||||
part_group = Dictionary[String, ExtResource("5_npk80")]({
|
||||
"ANCIENT": ExtResource("6_hyb2i"),
|
||||
"CLEANING": ExtResource("8_alra6"),
|
||||
"CUTTING": ExtResource("9_lggh7"),
|
||||
"ERMIT": ExtResource("7_4gk8a"),
|
||||
"FERTILE": ExtResource("10_8r35x"),
|
||||
"GENEROUS": ExtResource("11_rbd7l"),
|
||||
"HUMIDE": ExtResource("12_tjaiv"),
|
||||
"HURRIED": ExtResource("12_nfxo0"),
|
||||
"PRECOCIOUS": ExtResource("13_i8j71"),
|
||||
"PROLIFIC": ExtResource("14_l2vrg"),
|
||||
"PROTECTIVE": ExtResource("15_14c4k"),
|
||||
"PURE": ExtResource("16_rt6tw"),
|
||||
"PURIFICATION": ExtResource("17_y02ao"),
|
||||
"QUALITY": ExtResource("18_s8rsj"),
|
||||
"QUICK": ExtResource("19_cfiqo"),
|
||||
"RHIZOME": ExtResource("20_coupj"),
|
||||
"ROBUST": ExtResource("21_ggud5"),
|
||||
"SOCIABLE": ExtResource("23_hyb2i"),
|
||||
"SPONTANEOUS": ExtResource("24_4gk8a"),
|
||||
"TOUGH": ExtResource("25_alra6"),
|
||||
"VIVACIOUS": ExtResource("26_lggh7")
|
||||
})
|
||||
chance_to_have_part = 1.0
|
||||
origin_weights_base = Dictionary[int, int]({
|
||||
|
||||
@@ -8,6 +8,13 @@ const CONSONANTS = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p",
|
||||
|
||||
|
||||
static func generate_random_word(_random_seed = randi()) -> String:
|
||||
if (
|
||||
GameInfo
|
||||
and GameInfo.settings_data.activate_twitch_integration
|
||||
and len(TwitchConnection.pseudo_gathered)
|
||||
):
|
||||
return TwitchConnection.pseudo_gathered.pick_random()
|
||||
|
||||
var word_len = randf_range(4,8)
|
||||
var word = ''
|
||||
var last_letter_is_vowel = false
|
||||
|
||||
24
common/twitch_connection/twitch_connection.gd
Normal file
@@ -0,0 +1,24 @@
|
||||
extends Node
|
||||
|
||||
signal pseudo_gathered_updated(pseudos)
|
||||
|
||||
var pseudo_gathered : Array[String] = []
|
||||
|
||||
func _ready():
|
||||
VerySimpleTwitch.chat_message_received.connect(_on_message_received)
|
||||
GameInfo.settings_data.twitch_changed.connect(connect_to_twitch)
|
||||
|
||||
func connect_to_twitch(settings : SettingsData):
|
||||
pseudo_gathered = []
|
||||
pseudo_gathered_updated.emit(pseudo_gathered)
|
||||
VerySimpleTwitch.end_chat_client()
|
||||
if settings.activate_twitch_integration:
|
||||
connect_to_twitch_account(settings.twitch_channel)
|
||||
|
||||
func connect_to_twitch_account(channel_name: String):
|
||||
VerySimpleTwitch.login_chat_anon(channel_name)
|
||||
|
||||
func _on_message_received(chatter: VSTChatter):
|
||||
if not chatter.login in pseudo_gathered:
|
||||
pseudo_gathered.append(chatter.login)
|
||||
pseudo_gathered_updated.emit(pseudo_gathered)
|
||||
1
common/twitch_connection/twitch_connection.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://fm17qbe4kqqo
|
||||
49
common/vfx/materials/shaders/caustics.gdshader
Normal file
@@ -0,0 +1,49 @@
|
||||
shader_type spatial;
|
||||
|
||||
uniform float scale : hint_range(0.1, 2.0, 0.1) = 1.0;
|
||||
uniform float speed : hint_range(0.1, 2.0, 0.1) = 0.2;
|
||||
uniform float strenght : hint_range(10., 500., 10.) = 100.;
|
||||
uniform sampler2D color_ramp;
|
||||
varying vec3 world_position;
|
||||
|
||||
vec2 random(vec2 uv) {
|
||||
return vec2(fract(sin(dot(uv.xy,
|
||||
vec2(12.9898,78.233))) * 43758.5453123));
|
||||
}
|
||||
|
||||
float worley(vec2 uv, float columns, float rows) {
|
||||
|
||||
vec2 index_uv = floor(vec2(uv.x * columns, uv.y * rows));
|
||||
vec2 fract_uv = fract(vec2(uv.x * columns, uv.y * rows));
|
||||
|
||||
float minimum_dist = 1.0;
|
||||
|
||||
for (int y= -1; y <= 1; y++) {
|
||||
for (int x= -1; x <= 1; x++) {
|
||||
vec2 neighbor = vec2(float(x),float(y));
|
||||
vec2 point = random(index_uv + neighbor);
|
||||
|
||||
vec2 diff = neighbor + point - fract_uv;
|
||||
float dist = length(diff);
|
||||
minimum_dist = min(minimum_dist, dist);
|
||||
}
|
||||
}
|
||||
|
||||
return minimum_dist;
|
||||
}
|
||||
|
||||
void vertex(){
|
||||
world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
}
|
||||
|
||||
void fragment(){
|
||||
float worley_value = worley(world_position.xz * scale + TIME * speed, 3.0, 3.0);
|
||||
float worley_value_2 = worley(world_position.xz * scale * 2. - TIME * speed, 3.0, 3.0);
|
||||
|
||||
float color = texture(color_ramp, vec2(worley_value*worley_value_2)).r;
|
||||
color = min(0.99, color);
|
||||
color = log(0.999)/log(color);
|
||||
color *= strenght;
|
||||
ALPHA = color;
|
||||
EMISSION = vec3(color);
|
||||
}
|
||||
1
common/vfx/materials/shaders/caustics.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://740mg4jlufyc
|
||||
@@ -23,5 +23,6 @@ mysterious_demeter: But first things first,[pause=0.3] you have to learn how eve
|
||||
mysterious_demeter: I sent you a checklist with the things that you have to learn, and I'll provide the tools you need along the way. #id:fd
|
||||
mysterious_demeter: [b]I send you an elevator[/b],[pause=0.2] good luck [color=#FFA617]Orchid[/color] ! #id:fe
|
||||
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
|
||||
leave mysterious_demeter [animation="Fade Out" length="1.0"]
|
||||
[wait time="2.0"]
|
||||
[end_timeline]
|
||||
@@ -32,4 +32,5 @@ demeter: You'll need to travel north to join me, but you won't do that in one go
|
||||
demeter: Remember, to continue, you'll need to keep your best seeds and continue to [b]evolve your plants[/b]. #id:e3
|
||||
demeter: Good luck [color=#FFA617]Orchid[/color],[pause=0.3] I am counting on you. #id:73
|
||||
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
|
||||
leave demeter [animation="Fade Out" length="1.0"]
|
||||
[wait time="2.0"]
|
||||
@@ -9,5 +9,6 @@ demeter: The crystals in these caverns are special; they don't produce life, but
|
||||
demeter: These caverns were well known to humans. Some went there to study [color=#FFA617]Talion[/color], others to explore the cave. These explorations were very risky, but apparently some humans enjoyed risking their lives... #id:e9
|
||||
demeter: Use your detector to find the entrance to the cave; humans had installed an elevator there. #id:ec
|
||||
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
|
||||
leave demeter [animation="Fade Out" length="1.0"]
|
||||
[wait time="2.0"]
|
||||
[end_timeline]
|
||||
@@ -16,5 +16,6 @@ demeter: Anyway, there's enough in the base to repair the Internode, and I even
|
||||
demeter: I'd like you to find the base and synchronize your data with it. That will allow you to come back here if you run out of energy. #id:bd
|
||||
demeter: See you [color=#FFA617]Orchid[/color] ! #id:bf
|
||||
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
|
||||
leave demeter [animation="Fade Out" length="1.0"]
|
||||
[wait time="2.0"]
|
||||
[end_timeline]
|
||||
@@ -16,12 +16,13 @@ demeter: I've programmed a new destination for you in the onboard computer. #id:
|
||||
demeter: You now need to go a little further north, to a new relay base, [color=#FFA617]Venus Base[/color]. #id:cc
|
||||
demeter: [b]I managed to repair the ship[/b], but I'm afraid it hasn't returned to its original state... #id:110
|
||||
demeter: [b]I also did some cleaning in your seeds[/b], they have gone bad during the time I repaired the ship. I hope you don't mind... #id:111
|
||||
- No problem, I'll found better ones. #id:112
|
||||
- No problem, I'll find better ones. #id:112
|
||||
- Oh no ! But I've been away just a moment ! #id:113
|
||||
demeter: Uhm, I actually repaired the ship over two full months; I didn't wake you up just after the save, I thought that you wouldn't want to wait that long! #id:114
|
||||
demeter: I've updated you in the meantime, and you'll discover a new tool I just added, a tractable beam! #id:115
|
||||
demeter: If you were in trouble for sorting your seeds, it might help you. #id:116
|
||||
demeter: Good luck with the [color=#FFA617]Venus Relay Base[/color]; it's a little further than the last one. #id:d7
|
||||
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
|
||||
leave demeter [animation="Fade Out" length="1.0"]
|
||||
[wait time="2.0"]
|
||||
[end_timeline]
|
||||
@@ -12,5 +12,6 @@ demeter: As usual, you can use your detector to find the entrance to these ruins
|
||||
demeter: [b]These artifacts will be placed on a shelf in your ship[/b], but remember that if you ran out of energy and I have to teleport the ship, you'll lose them. #id:d6
|
||||
demeter: It could really help you, so go fetch them. #id:118
|
||||
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
|
||||
leave demeter [animation="Fade Out" length="1.0"]
|
||||
[wait time="2.0"]
|
||||
[end_timeline]
|
||||
@@ -11,5 +11,6 @@ demeter: Wow, you arrived quickly! How's your journey going so far? #id:d8
|
||||
demeter: Good! I hope you're enjoying your new existence, and that you're not suffering too much from being born into something you didn't choose... #id:e0
|
||||
demeter: Like in [color=#FFA617]Mercury Base[/color], I'd like you to save yourself. In the meantime, I'll check the ship's condition. I hope it hasn't suffered too much damage from the jumps! #id:e1
|
||||
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
|
||||
leave demeter [animation="Fade Out" length="1.0"]
|
||||
[wait time="2.0"]
|
||||
[end_timeline]
|
||||
@@ -17,5 +17,6 @@ demeter: I won't bother you any longer, you still have a long journey ahead, and
|
||||
demeter: ... #id:fb
|
||||
demeter: I... am waiting for you... #id:fc
|
||||
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
|
||||
leave demeter [animation="Fade Out" length="1.0"]
|
||||
[wait time="2.0"]
|
||||
[end_timeline]
|
||||
BIN
entities/interactable_3d/3d_printer/3d_printer.blend1
Normal file
9
entities/interactable_3d/3d_printer/3d_printer.tscn
Normal file
@@ -0,0 +1,9 @@
|
||||
[gd_scene format=3 uid="uid://cthjy6sifcogo"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bql646jknccr0" path="res://entities/interactable_3d/3d_printer/assets/3d/3d_printer.blend" id="1_w8y4a"]
|
||||
|
||||
[node name="3dPrinter" type="Node3D" unique_id=1603073620]
|
||||
|
||||
[node name="3d_printer" parent="." unique_id=2066223847 instance=ExtResource("1_w8y4a")]
|
||||
|
||||
[editable path="3d_printer"]
|
||||
BIN
entities/interactable_3d/3d_printer/assets/3d/3d_printer.blend
Normal file
@@ -0,0 +1,71 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://bql646jknccr0"
|
||||
path="res://.godot/imported/3d_printer.blend-764f02f73d7ba9dc897cfc0819ee7cc5.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://entities/interactable_3d/3d_printer/assets/3d/3d_printer.blend"
|
||||
dest_files=["res://.godot/imported/3d_printer.blend-764f02f73d7ba9dc897cfc0819ee7cc5.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
mesh_library/use_node_names_as_mesh_names=false
|
||||
array_mesh/deduplicate_surfaces=true
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={
|
||||
"materials": {
|
||||
"Default3D": {
|
||||
"use_external/enabled": true,
|
||||
"use_external/fallback_path": "res://common/assets/materials/default_3d.tres",
|
||||
"use_external/path": "uid://dvvi1k5c5iowc"
|
||||
}
|
||||
}
|
||||
}
|
||||
blender/nodes/visible=0
|
||||
blender/nodes/active_collection_only=false
|
||||
blender/nodes/punctual_lights=true
|
||||
blender/nodes/cameras=true
|
||||
blender/nodes/custom_properties=true
|
||||
blender/nodes/modifiers=1
|
||||
blender/meshes/vertex_colors=1
|
||||
blender/meshes/uvs=true
|
||||
blender/meshes/normals=true
|
||||
blender/meshes/export_geometry_nodes_instances=false
|
||||
blender/meshes/gpu_instances=false
|
||||
blender/meshes/tangents=true
|
||||
blender/meshes/skins=2
|
||||
blender/meshes/export_bones_deforming_mesh_only=false
|
||||
blender/materials/unpack_enabled=true
|
||||
blender/materials/export_materials=1
|
||||
blender/animation/limit_playback=true
|
||||
blender/animation/always_sample=true
|
||||
blender/animation/group_tracks=true
|
||||
gltf/naming_version=2
|
||||
gltf/texture_map_mode=1
|
||||
BIN
entities/interactable_3d/3d_printer/assets/3d/3d_printer.blend1
Normal file
@@ -16,6 +16,8 @@ dest_files=["res://.godot/imported/cristal_crack.blend-127c21c2b9bb4a6237729dd3a
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
mesh_library/use_node_names_as_mesh_names=false
|
||||
array_mesh/deduplicate_surfaces=true
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
@@ -52,7 +54,7 @@ blender/nodes/punctual_lights=true
|
||||
blender/nodes/cameras=true
|
||||
blender/nodes/custom_properties=true
|
||||
blender/nodes/modifiers=1
|
||||
blender/meshes/colors=false
|
||||
blender/meshes/vertex_colors=2
|
||||
blender/meshes/uvs=true
|
||||
blender/meshes/normals=true
|
||||
blender/meshes/export_geometry_nodes_instances=false
|
||||
@@ -66,3 +68,4 @@ blender/animation/limit_playback=true
|
||||
blender/animation/always_sample=true
|
||||
blender/animation/group_tracks=true
|
||||
gltf/naming_version=2
|
||||
gltf/texture_map_mode=0
|
||||
|
||||
@@ -35,15 +35,14 @@ func update_model():
|
||||
func unlock_mutation():
|
||||
var progression = GameInfo.game_data.progression_data
|
||||
|
||||
if progression.mutations_unlocked < len(progression.get_all_mutations()):
|
||||
var new_mutation : PlantMutation = progression.get_all_mutations()[progression.mutations_unlocked]
|
||||
progression.mutations_unlocked += 1
|
||||
if not progression.are_all_mutations_unlocked():
|
||||
var new_mutation : PlantMutation = progression.unlock_new_mutation()
|
||||
|
||||
get_tree().create_timer(1.).timeout.connect(
|
||||
func (): %MutationAnnounce.announce_mutation = new_mutation
|
||||
);
|
||||
|
||||
if progression.mutations_unlocked == len(progression.get_all_mutations()):
|
||||
if progression.are_all_mutations_unlocked():
|
||||
SteamConnection.unlock_achievement(SteamConnection.ACH_UNLOCK_ALL_MUTATION)
|
||||
|
||||
else:
|
||||
|
||||
|
After Width: | Height: | Size: 54 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cf554gg3t0miv"
|
||||
path="res://.godot/imported/energy_cell_off - SAVE.png-1628a904600f784017aee64f240ea4ec.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://entities/interactables/energy_cell/assets/sprites/energy_cell_off - SAVE.png"
|
||||
dest_files=["res://.godot/imported/energy_cell_off - SAVE.png-1628a904600f784017aee64f240ea4ec.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 42 KiB |
BIN
entities/interactables/item_object/assets/sprites/new_item.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |