ajout du détecteur et liaison entre les scènes

This commit is contained in:
2026-02-20 01:00:55 +01:00
parent dc1a6686bc
commit fb8a2da4ff
42 changed files with 737 additions and 418 deletions

View File

@@ -24,18 +24,18 @@ var portrait := ""
## Used to set the character resource from the unique name identifier and vice versa ## Used to set the character resource from the unique name identifier and vice versa
var character_identifier: String: var character_identifier: String:
get: get:
if character and not "{" in character_identifier: if character and not "{" in character_identifier:
var identifier := character.get_identifier() var identifier := character.get_identifier()
if not identifier.is_empty(): if not identifier.is_empty():
return identifier return identifier
return character_identifier return character_identifier
set(value): set(value):
character_identifier = value character_identifier = value
character = DialogicResourceUtil.get_character_resource(value) character = DialogicResourceUtil.get_character_resource(value)
if Engine.is_editor_hint() and ((not character) or (character and not character.portraits.has(portrait))): if Engine.is_editor_hint() and ((not character) or (character and not character.portraits.has(portrait))):
portrait = "" portrait = ""
ui_update_needed.emit() ui_update_needed.emit()
var regex := RegEx.create_from_string(r'\s*((")?(?<name>(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(\W*(?<portrait>\(.*\)))?\s*(?<!\\):)?(?<text>(.|\n)*)') var regex := RegEx.create_from_string(r'\s*((")?(?<name>(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(\W*(?<portrait>\(.*\)))?\s*(?<!\\):)?(?<text>(.|\n)*)')
var split_regex := RegEx.create_from_string(r"((\[n\]|\[n\+\])?((?!(\[n\]|\[n\+\]))(.|\n))+)") var split_regex := RegEx.create_from_string(r"((\[n\]|\[n\+\])?((?!(\[n\]|\[n\+\]))(.|\n))+)")
@@ -49,225 +49,225 @@ signal advance
################################################################################ ################################################################################
func _clear_state() -> void: func _clear_state() -> void:
dialogic.current_state_info.erase('text_sub_idx') dialogic.current_state_info.erase('text_sub_idx')
_disconnect_signals() _disconnect_signals()
func _execute() -> void: func _execute() -> void:
if text.is_empty(): if text.is_empty():
finish() finish()
return return
## If the speaker is provided as an expression, parse it now. ## If the speaker is provided as an expression, parse it now.
if "{" in character_identifier: if "{" in character_identifier:
character = null character = null
var character_name: String = dialogic.Expressions.execute_string(character_identifier) var character_name: String = dialogic.Expressions.execute_string(character_identifier)
get_or_create_character(character_name) get_or_create_character(character_name)
## Change Portrait and Active Speaker ## Change Portrait and Active Speaker
if dialogic.has_subsystem("Portraits"): if dialogic.has_subsystem("Portraits"):
if character: if character:
dialogic.Portraits.change_speaker(character, portrait) dialogic.Portraits.change_speaker(character, portrait)
if portrait and dialogic.Portraits.is_character_joined(character): if portrait and dialogic.Portraits.is_character_joined(character):
dialogic.Portraits.change_character_portrait(character, portrait) dialogic.Portraits.change_character_portrait(character, portrait)
else: else:
dialogic.Portraits.change_speaker(null) dialogic.Portraits.change_speaker(null)
## Change and Type Sound Mood ## Change and Type Sound Mood
if character: if character:
dialogic.Text.update_name_label(character) dialogic.Text.update_name_label(character)
var current_portrait: String = portrait var current_portrait: String = portrait
if portrait.is_empty(): if portrait.is_empty():
current_portrait = dialogic.current_state_info["portraits"].get(character.get_identifier(), {}).get("portrait", "") current_portrait = dialogic.current_state_info["portraits"].get(character.get_identifier(), {}).get("portrait", "")
var current_portrait_sound_mood: String = character.portraits.get(current_portrait, {}).get("sound_mood", "") var current_portrait_sound_mood: String = character.portraits.get(current_portrait, {}).get("sound_mood", "")
dialogic.Text.update_typing_sound_mood_from_character(character, current_portrait_sound_mood) dialogic.Text.update_typing_sound_mood_from_character(character, current_portrait_sound_mood)
else: else:
dialogic.Text.update_name_label(null) dialogic.Text.update_name_label(null)
dialogic.Text.update_typing_sound_mood() dialogic.Text.update_typing_sound_mood()
## Handle style changes ## Handle style changes
if dialogic.has_subsystem("Styles"): if dialogic.has_subsystem("Styles"):
var current_base_style: String = dialogic.current_state_info.get("base_style") var current_base_style: String = dialogic.current_state_info.get("base_style")
var current_style: String = dialogic.current_state_info.get("style", "") var current_style: String = dialogic.current_state_info.get("style", "")
var character_style: String = "" if not character else character.custom_info.get("style", "") var character_style: String = "" if not character else character.custom_info.get("style", "")
## Change back to base style, if another characters style is currently used ## Change back to base style, if another characters style is currently used
if (not character or character_style.is_empty()) and (current_base_style != current_style): if (not character or character_style.is_empty()) and (current_base_style != current_style):
dialogic.Styles.change_style(dialogic.current_state_info.get("base_style", "Default")) dialogic.Styles.change_style(dialogic.current_state_info.get("base_style", "Default"))
await dialogic.get_tree().process_frame await dialogic.get_tree().process_frame
## Change to the characters style if this character has one ## Change to the characters style if this character has one
elif character and not character_style.is_empty(): elif character and not character_style.is_empty():
dialogic.Styles.change_style(character_style, false) dialogic.Styles.change_style(character_style, false)
await dialogic.get_tree().process_frame await dialogic.get_tree().process_frame
_connect_signals() _connect_signals()
var character_name_text := dialogic.Text.get_character_name_parsed(character) var character_name_text := dialogic.Text.get_character_name_parsed(character)
var final_text: String = get_property_translated('text') var final_text: String = get_property_translated('text')
if ProjectSettings.get_setting('dialogic/text/split_at_new_lines', false): if ProjectSettings.get_setting('dialogic/text/split_at_new_lines', false):
match ProjectSettings.get_setting('dialogic/text/split_at_new_lines_as', 0): match ProjectSettings.get_setting('dialogic/text/split_at_new_lines_as', 0):
0: 0:
final_text = final_text.replace('\n', '[n]') final_text = final_text.replace('\n', '[n]')
1: 1:
final_text = final_text.replace('\n', '[n+][br]') final_text = final_text.replace('\n', '[n+][br]')
var split_text := [] var split_text := []
for i in split_regex.search_all(final_text): for i in split_regex.search_all(final_text):
split_text.append([i.get_string().trim_prefix('[n]').trim_prefix('[n+]')]) split_text.append([i.get_string().trim_prefix('[n]').trim_prefix('[n+]')])
split_text[-1].append(i.get_string().begins_with('[n+]')) split_text[-1].append(i.get_string().begins_with('[n+]'))
dialogic.current_state_info['text_sub_idx'] = dialogic.current_state_info.get('text_sub_idx', -1) dialogic.current_state_info['text_sub_idx'] = dialogic.current_state_info.get('text_sub_idx', -1)
var reveal_next_segment: bool = dialogic.current_state_info['text_sub_idx'] == -1 var reveal_next_segment: bool = dialogic.current_state_info['text_sub_idx'] == -1
for section_idx in range(min(max(0, dialogic.current_state_info['text_sub_idx']), len(split_text)-1), len(split_text)): for section_idx in range(min(max(0, dialogic.current_state_info['text_sub_idx']), len(split_text)-1), len(split_text)):
dialogic.Inputs.block_input(ProjectSettings.get_setting('dialogic/text/text_reveal_skip_delay', 0.1)) dialogic.Inputs.block_input(ProjectSettings.get_setting('dialogic/text/text_reveal_skip_delay', 0.1))
if reveal_next_segment: if reveal_next_segment:
dialogic.Text.hide_next_indicators() dialogic.Text.hide_next_indicators()
dialogic.current_state_info['text_sub_idx'] = section_idx dialogic.current_state_info['text_sub_idx'] = section_idx
var segment: String = dialogic.Text.parse_text(split_text[section_idx][0], 0) var segment: String = dialogic.Text.parse_text(split_text[section_idx][0], 0)
var is_append: bool = split_text[section_idx][1] var is_append: bool = split_text[section_idx][1]
final_text = ProjectSettings.get_setting("dialogic/text/dialog_text_prefix", "")+segment final_text = ProjectSettings.get_setting("dialogic/text/dialog_text_prefix", "")+segment
dialogic.Text.about_to_show_text.emit({'text':final_text, 'character':character, 'portrait':portrait, 'append': is_append}) dialogic.Text.about_to_show_text.emit({'text':final_text, 'character':character, 'portrait':portrait, 'append': is_append})
await dialogic.Text.update_textbox(final_text, false) await dialogic.Text.update_textbox(final_text, false)
state = States.REVEALING state = States.REVEALING
_try_play_current_line_voice() _try_play_current_line_voice()
final_text = dialogic.Text.update_dialog_text(final_text, false, is_append) final_text = dialogic.Text.update_dialog_text(final_text, false, is_append)
dialogic.Text.text_started.emit({'text':final_text, 'character':character, 'portrait':portrait, 'append': is_append}) dialogic.Text.text_started.emit({'text':final_text, 'character':character, 'portrait':portrait, 'append': is_append})
_mark_as_read(character_name_text, final_text) _mark_as_read(character_name_text, final_text)
# We must skip text animation before we potentially return when there # We must skip text animation before we potentially return when there
# is a Choice event. # is a Choice event.
if dialogic.Inputs.auto_skip.enabled: if dialogic.Inputs.auto_skip.enabled:
dialogic.Text.skip_text_reveal() dialogic.Text.skip_text_reveal()
else: else:
await dialogic.Text.text_finished await dialogic.Text.text_finished
state = States.IDLE state = States.IDLE
else: else:
reveal_next_segment = true reveal_next_segment = true
# Handling potential Choice Events. # Handling potential Choice Events.
if section_idx == len(split_text)-1 and dialogic.has_subsystem('Choices') and dialogic.Choices.is_question(dialogic.current_event_idx): if section_idx == len(split_text)-1 and dialogic.has_subsystem('Choices') and dialogic.Choices.is_question(dialogic.current_event_idx):
dialogic.Text.show_next_indicators(true) dialogic.Text.show_next_indicators(true)
finish() finish()
return return
elif dialogic.Inputs.auto_advance.is_enabled(): elif dialogic.Inputs.auto_advance.is_enabled():
dialogic.Text.show_next_indicators(false, true) dialogic.Text.show_next_indicators(false, true)
dialogic.Inputs.auto_advance.start() dialogic.Inputs.auto_advance.start()
else: else:
dialogic.Text.show_next_indicators() dialogic.Text.show_next_indicators()
if section_idx == len(split_text)-1: if section_idx == len(split_text)-1:
state = States.DONE state = States.DONE
# If Auto-Skip is enabled and there are multiple parts of this text # If Auto-Skip is enabled and there are multiple parts of this text
# we need to skip the text after the defined time per event. # we need to skip the text after the defined time per event.
if dialogic.Inputs.auto_skip.enabled: if dialogic.Inputs.auto_skip.enabled:
await dialogic.Inputs.start_autoskip_timer() await dialogic.Inputs.start_autoskip_timer()
# Check if Auto-Skip is still enabled. # Check if Auto-Skip is still enabled.
if not dialogic.Inputs.auto_skip.enabled: if not dialogic.Inputs.auto_skip.enabled:
await advance await advance
else: else:
await advance await advance
finish() finish()
func _mark_as_read(character_name_text: String, final_text: String) -> void: func _mark_as_read(character_name_text: String, final_text: String) -> void:
if dialogic.has_subsystem('History'): if dialogic.has_subsystem('History'):
if character: if character:
dialogic.History.store_simple_history_entry(final_text, event_name, {'character':character_name_text, 'character_color':character.color}) dialogic.History.store_simple_history_entry(final_text, event_name, {'character':character_name_text, 'character_color':character.color})
else: else:
dialogic.History.store_simple_history_entry(final_text, event_name) dialogic.History.store_simple_history_entry(final_text, event_name)
dialogic.History.mark_event_as_visited() dialogic.History.mark_event_as_visited()
func _connect_signals() -> void: func _connect_signals() -> void:
if not dialogic.Inputs.dialogic_action.is_connected(_on_dialogic_input_action): if not dialogic.Inputs.dialogic_action.is_connected(_on_dialogic_input_action):
dialogic.Inputs.dialogic_action.connect(_on_dialogic_input_action) dialogic.Inputs.dialogic_action.connect(_on_dialogic_input_action)
dialogic.Inputs.auto_skip.toggled.connect(_on_auto_skip_enable) dialogic.Inputs.auto_skip.toggled.connect(_on_auto_skip_enable)
if not dialogic.Inputs.auto_advance.autoadvance.is_connected(_on_dialogic_input_autoadvance): if not dialogic.Inputs.auto_advance.autoadvance.is_connected(_on_dialogic_input_autoadvance):
dialogic.Inputs.auto_advance.autoadvance.connect(_on_dialogic_input_autoadvance) dialogic.Inputs.auto_advance.autoadvance.connect(_on_dialogic_input_autoadvance)
## If the event is done, this method can clean-up signal connections. ## If the event is done, this method can clean-up signal connections.
func _disconnect_signals() -> void: func _disconnect_signals() -> void:
if dialogic.Inputs.dialogic_action.is_connected(_on_dialogic_input_action): if dialogic.Inputs.dialogic_action.is_connected(_on_dialogic_input_action):
dialogic.Inputs.dialogic_action.disconnect(_on_dialogic_input_action) dialogic.Inputs.dialogic_action.disconnect(_on_dialogic_input_action)
if dialogic.Inputs.auto_advance.autoadvance.is_connected(_on_dialogic_input_autoadvance): if dialogic.Inputs.auto_advance.autoadvance.is_connected(_on_dialogic_input_autoadvance):
dialogic.Inputs.auto_advance.autoadvance.disconnect(_on_dialogic_input_autoadvance) dialogic.Inputs.auto_advance.autoadvance.disconnect(_on_dialogic_input_autoadvance)
if dialogic.Inputs.auto_skip.toggled.is_connected(_on_auto_skip_enable): if dialogic.Inputs.auto_skip.toggled.is_connected(_on_auto_skip_enable):
dialogic.Inputs.auto_skip.toggled.disconnect(_on_auto_skip_enable) dialogic.Inputs.auto_skip.toggled.disconnect(_on_auto_skip_enable)
## Tries to play the voice clip for the current line. ## Tries to play the voice clip for the current line.
func _try_play_current_line_voice() -> void: func _try_play_current_line_voice() -> void:
# If Auto-Skip is enabled and we skip voice clips, we don't want to play. # If Auto-Skip is enabled and we skip voice clips, we don't want to play.
if (dialogic.Inputs.auto_skip.enabled if (dialogic.Inputs.auto_skip.enabled
and dialogic.Inputs.auto_skip.skip_voice): and dialogic.Inputs.auto_skip.skip_voice):
return return
# Plays the audio region for the current line. # Plays the audio region for the current line.
if (dialogic.has_subsystem('Voice') if (dialogic.has_subsystem('Voice')
and dialogic.Voice.is_voiced(dialogic.current_event_idx)): and dialogic.Voice.is_voiced(dialogic.current_event_idx)):
dialogic.Voice.play_voice() dialogic.Voice.play_voice()
func _on_dialogic_input_action() -> void: func _on_dialogic_input_action() -> void:
match state: match state:
States.REVEALING: States.REVEALING:
if dialogic.Text.is_text_reveal_skippable(): if dialogic.Text.is_text_reveal_skippable():
dialogic.Text.skip_text_reveal() dialogic.Text.skip_text_reveal()
dialogic.Inputs.stop_timers() dialogic.Inputs.stop_timers()
_: _:
if dialogic.Inputs.manual_advance.is_enabled(): if dialogic.Inputs.manual_advance.is_enabled():
advance.emit() advance.emit()
dialogic.Inputs.stop_timers() dialogic.Inputs.stop_timers()
func _on_dialogic_input_autoadvance() -> void: func _on_dialogic_input_autoadvance() -> void:
if state == States.IDLE or state == States.DONE: if state == States.IDLE or state == States.DONE:
advance.emit() advance.emit()
func _on_auto_skip_enable(enabled: bool) -> void: func _on_auto_skip_enable(enabled: bool) -> void:
if not enabled: if not enabled:
return return
match state: match state:
States.DONE: States.DONE:
await dialogic.Inputs.start_autoskip_timer() await dialogic.Inputs.start_autoskip_timer()
# If Auto-Skip is still enabled, advance the text. # If Auto-Skip is still enabled, advance the text.
if dialogic.Inputs.auto_skip.enabled: if dialogic.Inputs.auto_skip.enabled:
advance.emit() advance.emit()
States.REVEALING: States.REVEALING:
dialogic.Text.skip_text_reveal() dialogic.Text.skip_text_reveal()
#endregion #endregion
@@ -276,12 +276,12 @@ func _on_auto_skip_enable(enabled: bool) -> void:
################################################################################ ################################################################################
func _init() -> void: func _init() -> void:
event_name = "Text" event_name = "Text"
set_default_color('Color1') set_default_color('Color1')
event_category = "Main" event_category = "Main"
event_sorting_index = 0 event_sorting_index = 0
expand_by_default = true expand_by_default = true
help_page_path = "https://docs.dialogic.pro/writing-texts.html" help_page_path = "https://docs.dialogic.pro/writing-texts.html"
@@ -289,92 +289,92 @@ func _init() -> void:
################################################################################ ################################################################################
func to_text() -> String: func to_text() -> String:
var result := text.replace('\n', '\\\n').strip_edges(false).trim_suffix("\\") var result := text.replace('\n', '\\\n').strip_edges(false).trim_suffix("\\")
result = result.replace(':', '\\:') result = result.replace(':', '\\:')
if result.is_empty(): if result.is_empty():
result = "<Empty Text Event>" result = "<Empty Text Event>"
if character or character_identifier: if character or character_identifier:
var name := character_identifier var name := character_identifier
if character: if character:
name = character.get_identifier() name = character.get_identifier()
if name.count(" ") > 0: if name.count(" ") > 0:
name = '"' + name + '"' name = '"' + name + '"'
if not portrait.is_empty(): if not portrait.is_empty():
result = name+" ("+portrait+"): "+result result = name+" ("+portrait+"): "+result
else: else:
result = name+": "+result result = name+": "+result
for event in DialogicResourceUtil.get_event_cache(): for event in DialogicResourceUtil.get_event_cache():
if not event is DialogicTextEvent and event.is_valid_event(result): if not event is DialogicTextEvent and event.is_valid_event(result):
result = '\\'+result result = '\\'+result
break break
return result return result
func from_text(string:String) -> void: func from_text(string:String) -> void:
# Load default character # Load default character
# This is only of relevance if the default has been overriden (usually not) # This is only of relevance if the default has been overriden (usually not)
character = DialogicResourceUtil.get_character_resource(character_identifier) character = DialogicResourceUtil.get_character_resource(character_identifier)
var result := regex.search(string.trim_prefix('\\')) var result := regex.search(string.trim_prefix('\\'))
if result.get_string('portrait'): if result.get_string('portrait'):
portrait = result.get_string('portrait').strip_edges().trim_prefix('(').trim_suffix(')') portrait = result.get_string('portrait').strip_edges().trim_prefix('(').trim_suffix(')')
if result and not result.get_string('name').is_empty(): if result and not result.get_string('name').is_empty():
var name := result.get_string('name').strip_edges() var name := result.get_string('name').strip_edges()
if name == '_': if name == '_':
character = null character = null
elif "{" in name: elif "{" in name:
## If it's an expression, we load the character in _execute. ## If it's an expression, we load the character in _execute.
character_identifier = name character_identifier = name
character = null character = null
else: else:
get_or_create_character(name) get_or_create_character(name)
if not result: if not result:
return return
text = result.get_string('text').replace("\\\n", "\n").replace('\\:', ':').strip_edges().trim_prefix('\\') text = result.get_string('text').replace("\\\n", "\n").replace('\\:', ':').strip_edges().trim_prefix('\\')
if text == '<Empty Text Event>': if text == '<Empty Text Event>':
text = "" text = ""
func get_or_create_character(name:String) -> void: func get_or_create_character(name:String) -> void:
character = DialogicResourceUtil.get_character_resource(name) character = DialogicResourceUtil.get_character_resource(name)
if character == null: if character == null:
if Engine.is_editor_hint() == false: if Engine.is_editor_hint() == false:
character = DialogicCharacter.new() character = DialogicCharacter.new()
character.display_name = name character.display_name = name
character.set_identifier(name) character.set_identifier(name)
if portrait: if portrait:
if "{" in portrait: if "{" in portrait:
character.color = Color(dialogic.Expressions.execute_string(portrait)) character.color = Color(dialogic.Expressions.execute_string(portrait))
else: else:
character.color = Color(portrait) character.color = Color(portrait)
else: else:
character_identifier = name character_identifier = name
func is_valid_event(_string:String) -> bool: func is_valid_event(_string:String) -> bool:
return true return true
func is_string_full_event(string:String) -> bool: func is_string_full_event(string:String) -> bool:
return !string.ends_with('\\') return !string.ends_with('\\')
# this is only here to provide a list of default values # this is only here to provide a list of default values
# this way the module manager can add custom default overrides to this event. # this way the module manager can add custom default overrides to this event.
func get_shortcode_parameters() -> Dictionary: func get_shortcode_parameters() -> Dictionary:
return { return {
#param_name : property_info #param_name : property_info
"character" : {"property": "character_identifier", "default": "", "ext_file":true}, "character" : {"property": "character_identifier", "default": "", "ext_file":true},
"portrait" : {"property": "portrait", "default": ""}, "portrait" : {"property": "portrait", "default": ""},
} }
#endregion #endregion
@@ -382,14 +382,14 @@ func get_shortcode_parameters() -> Dictionary:
################################################################################ ################################################################################
func _get_translatable_properties() -> Array: func _get_translatable_properties() -> Array:
return ['text'] return ['text']
func _get_property_original_translation(property:String) -> String: func _get_property_original_translation(property:String) -> String:
match property: match property:
'text': 'text':
return text return text
return '' return ''
#endregion #endregion
@@ -399,44 +399,44 @@ func _get_property_original_translation(property:String) -> String:
################################################################################ ################################################################################
func _enter_visual_editor(editor:DialogicEditor): func _enter_visual_editor(editor:DialogicEditor):
editor.opened.connect(func(): ui_update_needed.emit()) editor.opened.connect(func(): ui_update_needed.emit())
func build_event_editor() -> void: func build_event_editor() -> void:
add_header_edit('character_identifier', ValueType.DYNAMIC_OPTIONS, add_header_edit('character_identifier', ValueType.DYNAMIC_OPTIONS,
{'file_extension' : '.dch', {'file_extension' : '.dch',
'mode' : 2, 'mode' : 2,
'suggestions_func' : get_character_suggestions, 'suggestions_func' : get_character_suggestions,
'placeholder' : '(No one)', 'placeholder' : '(No one)',
'icon' : load("res://addons/dialogic/Editor/Images/Resources/character.svg")}, 'do_any_characters_exist()') 'icon' : load("res://addons/dialogic/Editor/Images/Resources/character.svg")}, 'do_any_characters_exist()')
add_header_edit('portrait', ValueType.DYNAMIC_OPTIONS, add_header_edit('portrait', ValueType.DYNAMIC_OPTIONS,
{'suggestions_func' : get_portrait_suggestions, {'suggestions_func' : get_portrait_suggestions,
'placeholder' : "(Don't change)", 'placeholder' : "(Don't change)",
'icon' : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg"), 'icon' : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg"),
'collapse_when_empty': true,}, 'collapse_when_empty': true,},
'should_show_portrait_selector()') 'should_show_portrait_selector()')
add_body_edit('text', ValueType.MULTILINE_TEXT, {'autofocus':true}) add_body_edit('text', ValueType.MULTILINE_TEXT, {'autofocus':true})
func should_show_portrait_selector() -> bool: func should_show_portrait_selector() -> bool:
return character and not character.portraits.is_empty() and not character.portraits.size() == 1 return character and not character.portraits.is_empty() and not character.portraits.size() == 1
func do_any_characters_exist() -> bool: func do_any_characters_exist() -> bool:
return not DialogicResourceUtil.get_character_directory().is_empty() return not DialogicResourceUtil.get_character_directory().is_empty()
func get_character_suggestions(search_text:String) -> Dictionary: func get_character_suggestions(search_text:String) -> Dictionary:
var suggestions := DialogicUtil.get_character_suggestions(search_text, character, true, false, editor_node) var suggestions := DialogicUtil.get_character_suggestions(search_text, character, true, false, editor_node)
if search_text and not search_text in suggestions: if search_text and not search_text in suggestions:
suggestions[search_text] = { suggestions[search_text] = {
"value":search_text, "value":search_text,
"tooltip": "A temporary character, created on the spot.", "tooltip": "A temporary character, created on the spot.",
"editor_icon":["GuiEllipsis", "EditorIcons"]} "editor_icon":["GuiEllipsis", "EditorIcons"]}
return suggestions return suggestions
func get_portrait_suggestions(search_text:String) -> Dictionary: func get_portrait_suggestions(search_text:String) -> Dictionary:
return DialogicUtil.get_portrait_suggestions(search_text, character, true, "Don't change") return DialogicUtil.get_portrait_suggestions(search_text, character, true, "Don't change")
#endregion #endregion
@@ -447,45 +447,45 @@ func get_portrait_suggestions(search_text:String) -> Dictionary:
var completion_text_character_getter_regex := RegEx.new() var completion_text_character_getter_regex := RegEx.new()
var completion_text_effects := {} var completion_text_effects := {}
func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void: func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void:
if completion_text_character_getter_regex.get_pattern().is_empty(): if completion_text_character_getter_regex.get_pattern().is_empty():
completion_text_character_getter_regex.compile("(\"[^\"]*\"|[^\\s:]*)") completion_text_character_getter_regex.compile("(\"[^\"]*\"|[^\\s:]*)")
if completion_text_effects.is_empty(): if completion_text_effects.is_empty():
for idx in DialogicUtil.get_indexers(): for idx in DialogicUtil.get_indexers():
for effect in idx._get_text_effects(): for effect in idx._get_text_effects():
completion_text_effects[effect['command']] = effect completion_text_effects[effect['command']] = effect
if not ':' in line.substr(0, TextNode.get_caret_column()) and symbol == '(': if not ':' in line.substr(0, TextNode.get_caret_column()) and symbol == '(':
var completion_character := completion_text_character_getter_regex.search(line).get_string().trim_prefix('"').trim_suffix('"') var completion_character := completion_text_character_getter_regex.search(line).get_string().trim_prefix('"').trim_suffix('"')
CodeCompletionHelper.suggest_portraits(TextNode, completion_character) CodeCompletionHelper.suggest_portraits(TextNode, completion_character)
if symbol == '[': if symbol == '[':
suggest_bbcode(TextNode) suggest_bbcode(TextNode)
for effect in completion_text_effects.values(): for effect in completion_text_effects.values():
if effect.get('arg', false): if effect.get('arg', false):
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, effect.command, effect.command+'=', TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons")) TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, effect.command, effect.command+'=', TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"))
else: else:
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, effect.command, effect.command, TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"), ']') TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, effect.command, effect.command, TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"), ']')
if symbol == '{': if symbol == '{':
CodeCompletionHelper.suggest_variables(TextNode) CodeCompletionHelper.suggest_variables(TextNode)
if symbol == '=': if symbol == '=':
if CodeCompletionHelper.get_line_untill_caret(line).ends_with('[portrait='): if CodeCompletionHelper.get_line_untill_caret(line).ends_with('[portrait='):
var completion_character := completion_text_character_getter_regex.search(line).get_string('name') var completion_character := completion_text_character_getter_regex.search(line).get_string('name')
CodeCompletionHelper.suggest_portraits(TextNode, completion_character, ']') CodeCompletionHelper.suggest_portraits(TextNode, completion_character, ']')
func _get_start_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit) -> void: func _get_start_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit) -> void:
CodeCompletionHelper.suggest_characters(TextNode, CodeEdit.KIND_CLASS, self) CodeCompletionHelper.suggest_characters(TextNode, CodeEdit.KIND_CLASS, self)
func suggest_bbcode(TextNode:CodeEdit): func suggest_bbcode(TextNode:CodeEdit):
for i in [['b (bold)', 'b'], ['i (italics)', 'i'], ['color', 'color='], ['font size','font_size=']]: for i in [['b (bold)', 'b'], ['i (italics)', 'i'], ['color', 'color='], ['font size','font_size=']]:
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i[0], i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"),) TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i[0], i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"),)
TextNode.add_code_completion_option(CodeEdit.KIND_CLASS, 'end '+i[0], '/'+i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"), ']') TextNode.add_code_completion_option(CodeEdit.KIND_CLASS, 'end '+i[0], '/'+i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"), ']')
for i in [['new event', 'n'],['new event (same box)', 'n+']]: for i in [['new event', 'n'],['new event (same box)', 'n+']]:
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i[0], i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("ArrowRight", "EditorIcons"),) TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i[0], i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("ArrowRight", "EditorIcons"),)
#endregion #endregion
@@ -496,56 +496,56 @@ func suggest_bbcode(TextNode:CodeEdit):
var text_effects := "" var text_effects := ""
var text_effects_regex := RegEx.new() var text_effects_regex := RegEx.new()
func load_text_effects() -> void: func load_text_effects() -> void:
if text_effects.is_empty(): if text_effects.is_empty():
for idx in DialogicUtil.get_indexers(): for idx in DialogicUtil.get_indexers():
for effect in idx._get_text_effects(): for effect in idx._get_text_effects():
text_effects+= effect['command']+'|' text_effects+= effect['command']+'|'
text_effects += "b|i|u|s|code|p|center|left|right|fill|n\\+|n|indent|url|img|font|font_size|opentype_features|color|bg_color|fg_color|outline_size|outline_color|table|cell|ul|ol|lb|rb|br" text_effects += "b|i|u|s|code|p|center|left|right|fill|n\\+|n|indent|url|img|font|font_size|opentype_features|color|bg_color|fg_color|outline_size|outline_color|table|cell|ul|ol|lb|rb|br"
if text_effects_regex.get_pattern().is_empty(): if text_effects_regex.get_pattern().is_empty():
text_effects_regex.compile("(?<!\\\\)\\[\\s*/?(?<command>"+text_effects+")\\s*(=\\s*(?<value>.+?)\\s*)?\\]") text_effects_regex.compile("(?<!\\\\)\\[\\s*/?(?<command>"+text_effects+")\\s*(=\\s*(?<value>.+?)\\s*)?\\]")
var text_random_word_regex := RegEx.new() var text_random_word_regex := RegEx.new()
var text_effect_color := Color('#898276') var text_effect_color := Color('#898276')
func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary: func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
load_text_effects() load_text_effects()
if text_random_word_regex.get_pattern().is_empty(): if text_random_word_regex.get_pattern().is_empty():
text_random_word_regex.compile(r"(?<!\\)\<[^\>]+(\/[^\>]*)\>") text_random_word_regex.compile(r"(?<!\\)\<[^\>]+(\/[^\>]*)\>")
var result := regex.search(line) var result := regex.search(line)
if not result: if not result:
return dict return dict
if Highlighter.mode == Highlighter.Modes.FULL_HIGHLIGHTING: if Highlighter.mode == Highlighter.Modes.FULL_HIGHLIGHTING:
if result.get_string('name'): if result.get_string('name'):
dict[result.get_start('name')] = {"color":Highlighter.character_name_color} dict[result.get_start('name')] = {"color":Highlighter.character_name_color}
dict[result.get_end('name')] = {"color":Highlighter.normal_color} dict[result.get_end('name')] = {"color":Highlighter.normal_color}
if result.get_string('portrait'): if result.get_string('portrait'):
dict[result.get_start('portrait')] = {"color":Highlighter.character_portrait_color} dict[result.get_start('portrait')] = {"color":Highlighter.character_portrait_color}
dict[result.get_end('portrait')] = {"color":Highlighter.normal_color} dict[result.get_end('portrait')] = {"color":Highlighter.normal_color}
if result.get_string('text'): if result.get_string('text'):
## Color the random selection modifier ## Color the random selection modifier
for replace_mod_match in text_random_word_regex.search_all(result.get_string('text')): for replace_mod_match in text_random_word_regex.search_all(result.get_string('text')):
var color: Color = Highlighter.string_color var color: Color = Highlighter.string_color
color = color.lerp(Highlighter.normal_color, 0.4) color = color.lerp(Highlighter.normal_color, 0.4)
dict[replace_mod_match.get_start()+result.get_start('text')] = {'color':Highlighter.string_color} dict[replace_mod_match.get_start()+result.get_start('text')] = {'color':Highlighter.string_color}
var offset := 1 var offset := 1
for b:RegExMatch in RegEx.create_from_string(r"(\[[^\]]*\]|[^\/]|\/\/)+").search_all(replace_mod_match.get_string().trim_prefix("<").trim_suffix(">")): for b:RegExMatch in RegEx.create_from_string(r"(\[[^\]]*\]|[^\/]|\/\/)+").search_all(replace_mod_match.get_string().trim_prefix("<").trim_suffix(">")):
color.h = wrap(color.h+0.2, 0, 1) color.h = wrap(color.h+0.2, 0, 1)
dict[replace_mod_match.get_start()+result.get_start('text')+offset] = {'color':color} dict[replace_mod_match.get_start()+result.get_start('text')+offset] = {'color':color}
offset += len(b.get_string()) offset += len(b.get_string())
dict[replace_mod_match.get_start()+result.get_start('text')+offset] = {'color':Highlighter.string_color} dict[replace_mod_match.get_start()+result.get_start('text')+offset] = {'color':Highlighter.string_color}
offset += 1 offset += 1
dict[replace_mod_match.get_end()+result.get_start('text')] = {'color':Highlighter.normal_color} dict[replace_mod_match.get_end()+result.get_start('text')] = {'color':Highlighter.normal_color}
## Color bbcode and text effects ## Color bbcode and text effects
var effects_result := text_effects_regex.search_all(line) var effects_result := text_effects_regex.search_all(line)
for eff in effects_result: for eff in effects_result:
var prev_color: Color = Highlighter.dict_get_color_at_column(dict, eff.get_start()) var prev_color: Color = Highlighter.dict_get_color_at_column(dict, eff.get_start())
dict[eff.get_start()] = {"color":text_effect_color.lerp(prev_color, 0.4)} dict[eff.get_start()] = {"color":text_effect_color.lerp(prev_color, 0.4)}
dict[eff.get_end()] = {"color":prev_color} dict[eff.get_end()] = {"color":prev_color}
dict = Highlighter.color_region(dict, Highlighter.variable_color, line, '{', '}', result.get_start('text')) dict = Highlighter.color_region(dict, Highlighter.variable_color, line, '{', '}', result.get_start('text'))
return dict return dict
#endregion #endregion

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="mp3"
type="AudioStreamMP3"
uid="uid://c5y70k3codtn"
path="res://.godot/imported/signal.mp3-2f41c9334233d52845bfe384d47f2a96.mp3str"
[deps]
source_file="res://common/audio_manager/assets/sfx/signal/signal.mp3"
dest_files=["res://.godot/imported/signal.mp3-2f41c9334233d52845bfe384d47f2a96.mp3str"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

View File

@@ -12,6 +12,7 @@
[ext_resource type="AudioStream" uid="uid://bdxkvaciw4mb3" path="res://common/audio_manager/assets/sfx/dig/dig_2.wav" id="10_n7o7n"] [ext_resource type="AudioStream" uid="uid://bdxkvaciw4mb3" path="res://common/audio_manager/assets/sfx/dig/dig_2.wav" id="10_n7o7n"]
[ext_resource type="AudioStream" uid="uid://llxrlwfccywb" path="res://common/audio_manager/assets/sfx/dig/dig_3.wav" id="11_wtvls"] [ext_resource type="AudioStream" uid="uid://llxrlwfccywb" path="res://common/audio_manager/assets/sfx/dig/dig_3.wav" id="11_wtvls"]
[ext_resource type="AudioStream" uid="uid://b8inedx4yjslw" path="res://common/audio_manager/assets/sfx/drop/drop_1.wav" id="12_4hp8f"] [ext_resource type="AudioStream" uid="uid://b8inedx4yjslw" path="res://common/audio_manager/assets/sfx/drop/drop_1.wav" id="12_4hp8f"]
[ext_resource type="AudioStream" uid="uid://c5y70k3codtn" path="res://common/audio_manager/assets/sfx/signal/signal.mp3" id="12_ajci6"]
[ext_resource type="AudioStream" uid="uid://8nmr5vifkt1f" path="res://common/audio_manager/assets/sfx/harvest/harvest_1.wav" id="13_xoaox"] [ext_resource type="AudioStream" uid="uid://8nmr5vifkt1f" path="res://common/audio_manager/assets/sfx/harvest/harvest_1.wav" id="13_xoaox"]
[ext_resource type="AudioStream" uid="uid://dgkdcq4j6fe3o" path="res://common/audio_manager/assets/sfx/harvest/harvest_2.wav" id="14_b5bgj"] [ext_resource type="AudioStream" uid="uid://dgkdcq4j6fe3o" path="res://common/audio_manager/assets/sfx/harvest/harvest_2.wav" id="14_b5bgj"]
[ext_resource type="AudioStream" uid="uid://eh3dbuxu5qtw" path="res://common/audio_manager/assets/sfx/harvest/harvest_3.wav" id="15_ynvb4"] [ext_resource type="AudioStream" uid="uid://eh3dbuxu5qtw" path="res://common/audio_manager/assets/sfx/harvest/harvest_3.wav" id="15_ynvb4"]
@@ -36,6 +37,11 @@ stream_0/stream = ExtResource("9_gv65y")
stream_1/stream = ExtResource("10_n7o7n") stream_1/stream = ExtResource("10_n7o7n")
stream_2/stream = ExtResource("11_wtvls") stream_2/stream = ExtResource("11_wtvls")
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_8204s"]
random_pitch = 1.0594631
streams_count = 1
stream_0/stream = ExtResource("12_ajci6")
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_1w04j"] [sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_1w04j"]
random_pitch = 1.2 random_pitch = 1.2
streams_count = 1 streams_count = 1
@@ -89,6 +95,9 @@ unique_name_in_owner = true
[node name="Dig" type="AudioStreamPlayer" parent="Sfx" unique_id=486042600] [node name="Dig" type="AudioStreamPlayer" parent="Sfx" unique_id=486042600]
stream = SubResource("AudioStreamRandomizer_kfbah") stream = SubResource("AudioStreamRandomizer_kfbah")
[node name="Signal" type="AudioStreamPlayer" parent="Sfx" unique_id=641246368]
stream = SubResource("AudioStreamRandomizer_8204s")
[node name="Drop" type="AudioStreamPlayer" parent="Sfx" unique_id=1391500830] [node name="Drop" type="AudioStreamPlayer" parent="Sfx" unique_id=1391500830]
stream = SubResource("AudioStreamRandomizer_1w04j") stream = SubResource("AudioStreamRandomizer_1w04j")

View File

@@ -17,6 +17,8 @@ signal current_region_data_updated(p : RegionData)
@export var tutorial_done = false @export var tutorial_done = false
@export var incubator_used = []
@export var dialogs_done : Array[String] = [] #Chemin des dialogues démarrés @export var dialogs_done : Array[String] = [] #Chemin des dialogues démarrés
func start_run(): func start_run():
@@ -49,6 +51,8 @@ func start_tutorial():
10, 10,
3, 3,
tr("TUTORIAL"), tr("TUTORIAL"),
true true,
0,
randi()
) )
) )

View File

@@ -8,7 +8,8 @@
[ext_resource type="Resource" uid="uid://c27wenetitwm" path="res://common/scene_manager/scenes/region_selection.tres" id="6_chs32"] [ext_resource type="Resource" uid="uid://c27wenetitwm" path="res://common/scene_manager/scenes/region_selection.tres" id="6_chs32"]
[ext_resource type="Resource" uid="uid://diro74w272onp" path="res://common/scene_manager/scenes/title.tres" id="7_ol3d5"] [ext_resource type="Resource" uid="uid://diro74w272onp" path="res://common/scene_manager/scenes/title.tres" id="7_ol3d5"]
[ext_resource type="Resource" uid="uid://jegdqnd2sqi2" path="res://common/scene_manager/scenes/astra.tres" id="8_e28ni"] [ext_resource type="Resource" uid="uid://jegdqnd2sqi2" path="res://common/scene_manager/scenes/astra.tres" id="8_e28ni"]
[ext_resource type="Resource" uid="uid://b3ebbo88ptrrc" path="res://common/scene_manager/scenes/garage.tres" id="9_msho1"]
[node name="SceneManager" type="Node" unique_id=1630600782] [node name="SceneManager" type="Node" unique_id=1630600782]
script = ExtResource("1_1c0qu") script = ExtResource("1_1c0qu")
scenes = Array[ExtResource("2_c1lr7")]([ExtResource("3_e28ni"), ExtResource("4_msho1"), ExtResource("5_ytog4"), ExtResource("6_chs32"), ExtResource("7_ol3d5"), ExtResource("8_e28ni")]) scenes = Array[ExtResource("2_c1lr7")]([ExtResource("3_e28ni"), ExtResource("4_msho1"), ExtResource("5_ytog4"), ExtResource("6_chs32"), ExtResource("7_ol3d5"), ExtResource("8_e28ni"), ExtResource("9_msho1")])

View File

@@ -0,0 +1,10 @@
[gd_resource type="Resource" script_class="Scene" format=3 uid="uid://b3ebbo88ptrrc"]
[ext_resource type="Script" uid="uid://1ejbvr3431ac" path="res://common/scene_manager/scripts/scene.gd" id="1_v8prw"]
[resource]
script = ExtResource("1_v8prw")
scene_id = "GARAGE"
scene_path = "res://stages/3d_scenes/ship_garage/ship_garage.tscn"
mouse_captured = true
metadata/_custom_type_script = "uid://1ejbvr3431ac"

View File

@@ -1,5 +0,0 @@
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/incoming_transmission.wav"
[wait time="1.5"]
demeter: Hi again ! [pause=0.2] You did well up there !
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
[wait time="2.0"]

View File

@@ -1 +0,0 @@
uid://5dlqpnb4o6ai

View File

@@ -1,5 +0,0 @@
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/incoming_transmission.wav"
[wait time="1.5"]
demeter: Hi again ! [pause=0.2] You did well up there !
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
[wait time="2.0"]

View File

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

View File

@@ -0,0 +1,22 @@
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/incoming_transmission.wav"
[wait time="1.5"]
join demeter center [animation="Bounce In" length="1.0"]
demeter: Hello again !
demeter: It seems that you ran out of energy...
- I'm sorry...
- That's really hard !
- I'm just doing my best !
demeter: It's ok [color=#FFA617]Orchid[/color] ! I spent years waiting for this moment, I can wait more !
demeter: I found you a new body, and a new ship is waiting you outside, but I'm afraid I couldn't get your seeds... I hope you find new ones !
demeter: Do you need some advices ?
- No I'm ok !
- Can I have back some explanations on how all of this works ?
demeter: When you emerge from this building, you'll arrive in a little yellow zone. Its a [b]fertile zone[/b], created by the return of the Talion. This is in this zone and only there were you can plant.
demeter: Then you'll have to get seeds. For that, nothing more simple, you take your shovel tool, and you smash some stones ! Preferably those with yellow cristals on it, it's the [b]Talion veins[/b] .
demeter: Each time you use a tool or plant a seed, you'll spend an [b]energy[/b]. When your out of it, you can just go recharge on the [b]recharge station[/b] you'll find near the entrance. Each time you recharge, time will pass. One day in fact (yes, you don't have a good battery), and the plants will grow !
demeter: You have to obtain enough [b]plant points[/b]. Each plant give one or more [b]plant points[/b] when mature.
demeter: And yes, I almost forgot ! Some plants gain [b]mutations[/b] that can affect their points or behavior. You can get these mutations on new seeds by harvesting, and gain better ones ! But you'll have to wait the plants to die, or smashing them with your shovel when mature.
demeter: Hope I helped !
demeter: I send you the elevator, see you soon !
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
[wait time="2.0"]

View File

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

View File

@@ -1,53 +1,21 @@
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/incoming_transmission.wav" audio "res://common/audio_manager/assets/sfx/dialogs/sfx/incoming_transmission.wav"
[wait time="1.5"] [wait time="1.5"]
join demeter center [animation="Bounce In" length="1.0"] join demeter center [animation="Bounce In" length="1.0"]
mysterious_demeter: Well done [color=#FFA617]{orchidName}[/color] ! demeter: So you found the communication station in there, good !
mysterious_demeter: You generated enough [b]plants points[/b] to refill the [color=#FFA617]Internode's[/color] engines.
- Wait, how do you know that ?
mysterious_demeter: The [color=#FFA617]Internode[/color] is equipped with several sensors I can connect to,[pause=0.3] I have been tracking your progress since you left [color=#175579]Tau's[/color] north pole.
- You should have mentioned this !
mysterious_demeter: I am sorry,[pause=0.2] I did not have the time to explain everything to you,[pause=0.2] I did not imagine this was a crucial piece of information.
- Have I been efficient ?
mysterious_demeter: Very much,[pause=0.2] even more than I expected,[pause=0.2] you should be proud of yourself !
- What is next for me ? - What is next for me ?
mysterious_demeter: Now that you have learnt how to generate [b]plant points[/b],[pause=0.2] I need you to travel south,[pause=0.2] to my base of operations,[pause=0.3] [color=#E30022]Astra[/color]. demeter: Now that you have learnt how to generate [b]plant points[/b],[pause=0.2] I need you to travel south,[pause=0.2] to my base of operations,[pause=0.3] [color=#E30022]Astra[/color].
mysterious_demeter: It will be long,[pause=0.2] but I know you can do it.[pause=0.3] It is the first step of my plan to restore the ecosytem of [color=#175579]Tau[/color]. demeter: It will be long,[pause=0.2] but I know you can do it.[pause=0.3] It is the first step of my plan to restore the ecosytem of [color=#175579]Tau[/color].
mysterious_demeter: The [color=#FFA617]Internode[/color] needs time to recharge its batteries.
mysterious_demeter: Perhaps I should introduce myself now.[pause=0.3]
join demeter center [animation="Bounce In" length="1.0" wait="true"]
demeter: I am.[pause=0.3].[pause=0.3].[pause=0.5] was,[pause=0.3] the Deputy Manager of Engineering and Talion Energy Research on this planet,[pause=0.5] but my creators and colleagues quickly called me [color=#009bff]Demeter[/color].
demeter: I am installed at the [color=#E30022]Astra[/color] base, at the south pole of [color=#175579]Tau[/color][pause=0.2] in the third server room of the east wing,[pause=0.2] from which I oversee.[pause=0.3].[pause=0.3].[pause=0.5] oversaw,[pause=0.3] all the important operations on this planet.
demeter: As I already said,[pause=0.2] I brought you to life in order to help me heal [color=#175579]Tau's[/color] wounds.
[i][color=#FFA617]Internode's[/color] energy at 25%.[i]
- Tau ? Is it this planet ? - Tau ? Is it this planet ?
demeter: Absolutely,[pause=0.2] it is part of the Cetus constellation,[pause=0.2] whose exploitation began 10863 years ago. demeter: Absolutely,[pause=0.2] it is part of the Cetus constellation,[pause=0.2] whose exploitation began 10863 years ago.
- The Cetus constellation ? demeter: Linking Aldebaran to Fomalhaut,[pause=0.2] the Cetus constellation is one of the most diverse in terms of star systems and planets.[pause=0.3] It is part of the Orion Arm of the Milky Way.[pause=0.3]
demeter: Linking Aldebaran to Fomalhaut,[pause=0.2] this constellation is one of the most diverse in terms of star systems and planets.[pause=0.3] It is part of the Orion Arm of the Milky Way.[pause=0.3]
demeter: [color=#175579]Tau's[/color] central location in the constellation made it a very important asset in terms of interstellar exchange and production,[pause=0.2] that is why I am here.
- Exploitation ? By whom ?
demeter: By my creators,[pause=0.2] they began exploiting everything they came across as soon as they mastered interstellar transportation.[pause=0.3] Now they are gone,[pause=0.2] they departed without us.[pause=0.3].[pause=0.3].
demeter: But let's move on,[pause=0.2] you probably have other questions.
- Why do you need me to travel the entire planet to join you ? - Why do you need me to travel the entire planet to join you ?
demeter: Uhhhh.[pause=0.3].[pause=0.3].[pause=0.3] I need someone with your abilities. demeter: Uhhhh.[pause=0.3].[pause=0.3].[pause=0.3] I need someone with your abilities.
demeter: I am having a problem at [color=#E30022]Astra[/color],[pause=0.3] and I cannot fix it alone. demeter: I am having a problem at [color=#E30022]Astra[/color] base.[pause=0.3] And I can not fix it alone.
- I will do my best to be there quickly ! - I will do my best to be there quickly !
demeter: You are very kind [color=#FFA617]{orchidName}[/color] ! demeter: You are very kind [color=#FFA617]Orchid[/color] !
- Are you hiding me information ? - Are you hiding me informations ?
demeter: Not at all,[pause=0.2] I just do not know how to explain it to you,[pause=0.2] you would not understand.[pause=0.3].[pause=0.3]. demeter: Not at all ! Please believe me I just want you to come...
[i][color=#FFA617]Internode's[/color] energy at 50%.[i] demeter: Travel south,[pause=0.2] join me at [color=#E30022]Astra[/color].[pause=0.3] I will tell you everything you need to know when you are here,[pause=0.2] until then, keep your best seeds and [pause=0.2] continue to [b]evolve your plants[/b],[pause=0.2] you will need them as advanced as possible.
demeter: Oh ![pause=0.2] It is charging faster than I remembered. demeter: Good luck [color=#FFA617]{orchidName}[/color],[pause=0.2] I am counting on you.
- Should I worry about anything while I am travelling south ?
demeter: There is nothing left that could have hurt you in the past and your adaptative casing will protect you from any harsh weather.
demeter: You are totally safe,[pause=0.2] you just have to stay focused on the [b]plant points[/b] for the [color=#FFA617]Internode[/color].[pause=0.3] Without it,[pause=0.2] you will not be able to recharge your batteries before they expire.
demeter: It will be a long task,[pause=0.2] but stay careful and everything should be fine.
- I will wait in the ship, I need some rest.
demeter: You are right,[pause=0.2] your batteries are running low,[pause=0.2] and you deserve a break.
demeter: You can call me when you need to,[pause=0.2] you have a communication station in the [color=#FFA617]Internode[/color].
[i][color=#FFA617]Internode's[/color] energy at 75%.[i]
demeter: We do not have much time left,[pause=0.2] the [color=#FFA617]Internode's[/color] system has not been updated for a long time.[pause=0.3] I can not stay in contact with you indefinitely. I will fix that issue while you are at [color=#E30022]Astra[/color].
demeter: Travel south,[pause=0.2] join me at [color=#E30022]Astra[/color].[pause=0.3] I will tell you everything you need to know when you are here,[pause=0.2] until then,[pause=0.2] continue to [b]evolve your plants[/b],[pause=0.2] you will need them as powerful as possible.
label fin_dialogue
[i][color=#FFA617]Internode's[/color] energy at 100%. Displacement vectors initialized.[i]
demeter: There you go ![pause=0.3] Good luck [color=#FFA617]{orchidName}[/color],[pause=0.2] I am counting on you.
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav" audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
[wait time="2.0"] [wait time="2.0"]

View File

@@ -1,5 +1,30 @@
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/incoming_transmission.wav" audio "res://common/audio_manager/assets/sfx/dialogs/sfx/incoming_transmission.wav"
[wait time="1.5"] [wait time="1.5"]
demeter: Hi again ! [pause=0.2] You did well up there ! join demeter center [animation="Bounce In" length="1.0"]
demeter: Hi again! You did well up there!
demeter: Perhaps I should introduce myself now.
demeter: I am.[pause=0.3].[pause=0.3].[pause=0.5] was,[pause=0.3] the Deputy Manager of Engineering and Talion Energy Research on this planet,[pause=0.5] but my creators and colleagues quickly called me [color=#009bff]Demeter[/color].
demeter: I am installed at the [color=#E30022]Astra[/color] base,[pause=0.2] in the third server room of the east wing,[pause=0.2] from which I oversee.[pause=0.3].[pause=0.3].[pause=0.5] oversaw,[pause=0.3] all the important operations on this planet.
demeter: Maybe you are wondering where we are now...
- Yes among a lot of other questions ! So where am I ?
demeter: You're actually in an old human base, called Borea. This room is connected with the room you were born, but I made you pass by the surface to test your capacities.
- Wait, can you explain me what did I just do up there ?
demeter: You started to repair what the humans did on this planet... This will be very long, I hope you enjoyed it !
- Sorry but what is the point of all of that ?
demeter: I'm sorry that you are lost my child... Know that what you do is very important to me, to my friend and to the planet itself ! Since the humans are gone, we kinda all fell into despair...
demeter: Wait.[pause=0.3].[pause=0.3].[pause=0.5] You don't know what humans are ! Of course since your data was corrupted, I have erased a lot of it.
demeter: The humans are living creatures that are...[pause=0.5] Different than plants. They were more thinking and moving like us. In a sense, they were very cute creatures that only lived less than a century, and reproduce once in their lifetime.
demeter: One human has a relative intelligence, but don't be fooled, together, they did great things, in facts, they invented and created us.
demeter: Long ago, they discovered this planet. It was not like it is now, plants were everywhere! But then.[pause=0.3].[pause=0.3].[pause=0.5] They.[pause=0.3].[pause=0.3].[pause=0.5] I.[pause=0.3].[pause=0.3].[pause=0.5]
- What ?
- Are you lagging?
- Take your time
demeter: Humans had one big problem. Together, they built great things, but sometimes the great things weren't very good for the environment, or for them.
- Where are they now ?
demeter: I prefer not talk about that for now... But don't worry, they can't wound this planet anymore.
- What happened ?
demeter: Sorry my child, I'm not ready to talk about that for now... You'll have your answers when you meet me.
- Can I now what is this shiny engine just in front of me ?
demeter: This machine in front of you is a planetary ship ! The model's name is the [color=#FFA617]Internode[/color], and I tweaked it to recharge on vegetal energy ! However, as you may know, this energy isn't very present around this planet. But let's continue on board, shall we ?
audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav" audio "res://common/audio_manager/assets/sfx/dialogs/sfx/closing_transmission.wav"
[wait time="2.0"] [wait time="2.0"]

View File

@@ -9,7 +9,8 @@ signal clicked
@export var audio_player : AudioStreamPlayer3D @export var audio_player : AudioStreamPlayer3D
func click(): func click():
clicked.emit() if interactable:
clicked.emit()
func _ready(): func _ready():
if audio_player: if audio_player:

View File

@@ -0,0 +1,22 @@
[gd_scene format=3 uid="uid://b8m537op75gib"]
[ext_resource type="Script" uid="uid://bmxuqj0c6h60d" path="res://entities/interactables/door/script/door.gd" id="1_8kdwv"]
[sub_resource type="Gradient" id="Gradient_8kdwv"]
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_oarg0"]
gradient = SubResource("Gradient_8kdwv")
[sub_resource type="RectangleShape2D" id="RectangleShape2D_y51rk"]
size = Vector2(64, 64)
[node name="Door" type="Area2D" unique_id=2053096538]
script = ExtResource("1_8kdwv")
metadata/_custom_type_script = "uid://dyprcd68fjstf"
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=874210487]
texture = SubResource("GradientTexture2D_oarg0")
[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=1809395872]
shape = SubResource("RectangleShape2D_y51rk")

View File

@@ -0,0 +1,19 @@
@tool
extends Interactable
class_name Door
@export var to_scene_id = ""
func _ready():
modulate = Color.WHITE if available else Color.RED
func interact(_p : Player) -> bool:
if available and to_scene_id:
interacted.emit(_p)
SceneManager.change_to_scene_id(to_scene_id)
return available
func set_available(v : bool):
available = v
modulate = Color.WHITE if available else Color.RED

View File

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

View File

@@ -5,7 +5,7 @@ signal interacted(p: Player)
@export var default_interact_text = "" @export var default_interact_text = ""
var available : bool = true @export var available : bool = true : set = set_available
func interact_text() -> String: func interact_text() -> String:
return default_interact_text return default_interact_text
@@ -29,3 +29,6 @@ func generate_collision(area_width : float) -> CollisionShape2D:
add_child(collision) add_child(collision)
return collision return collision
func set_available(v : bool):
available = v

View File

@@ -9,6 +9,8 @@ signal updated(inventory: Inventory)
func _init(inventory_size: int = 1): func _init(inventory_size: int = 1):
set_size(inventory_size) set_size(inventory_size)
add_item(Detector.new())
add_item(Shovel.new())
func get_n_item_slots() -> int: func get_n_item_slots() -> int:
return items.size() - n_tools return items.size() - n_tools

View File

@@ -0,0 +1,33 @@
extends Item
class_name Detector
func get_item_name() -> String:
return tr("DETECTOR")
func get_item_type() -> ItemType:
return Item.ItemType.TOOL_ITEM
func get_description() -> String:
return tr("DETECTOR_DESC_TEXT")
func get_icon() -> Texture2D:
return preload("res://common/icons/broadcast.svg")
func get_energy_used() -> int:
return 0
func get_usage_zone_radius() -> int:
return 0
func can_use(_player : Player, _zone: Player.ActionZone) -> bool:
return true
func use_text() -> String:
return tr("DETECT_USE_TEXT")
func use(player : Player, zone: Player.ActionZone):
var detector_signal := DetectorSignal.new(player.region, zone.get_global_position())
detector_signal.global_position = zone.get_global_position()
player.region.add_child(detector_signal)
AudioManager.play_sfx("Signal")

View File

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

View File

@@ -12,6 +12,9 @@ func get_description() -> String:
func get_icon() -> Texture2D: func get_icon() -> Texture2D:
return preload("res://common/icons/fork.svg") return preload("res://common/icons/fork.svg")
func get_item_type() -> ItemType:
return Item.ItemType.TOOL_ITEM
func get_energy_used() -> int: func get_energy_used() -> int:
return 1 return 1

View File

@@ -6,9 +6,6 @@ const TROWEL_ZONE_RADIUS = 50
func get_item_name() -> String: func get_item_name() -> String:
return tr("TROWEL") return tr("TROWEL")
func get_item_type() -> ItemType:
return Item.ItemType.TOOL_ITEM
func get_description() -> String: func get_description() -> String:
return tr("TROWEL_DESC_TEXT") return tr("TROWEL_DESC_TEXT")

View File

@@ -0,0 +1,61 @@
@tool
extends Node2D
class_name DetectorSignal
const SIGNAL_DURATION = 1
const PARTICLES_DISTANCE = 100
const DEFAULT_ICON = preload("res://common/icons/north-star.svg")
const ENERGY_ICON = preload("res://common/icons/bolt.svg")
var started_time = 0.
var signals : Array[DetectorSignalIndividual] = []
@export_tool_button("Start", "Callable") var start = func(): started_time = 0
func _init(region : Region, pos : Vector2):
for e in region.entity_container.get_children():
if e is TruckRecharge:
print(pos)
print(e.global_position)
print(e.global_position.angle_to(pos))
signals.append(
DetectorSignalIndividual.new(
(pos - e.global_position).normalized().angle(),
ENERGY_ICON
)
)
func _draw():
if started_time < SIGNAL_DURATION:
draw_circle(
Vector2.ZERO,
started_time/SIGNAL_DURATION * PARTICLES_DISTANCE,
Color(1.,1.,1.,0.5*1-started_time/SIGNAL_DURATION),
false,
5.
)
for s in signals:
draw_texture(
s.icon,
Vector2.ZERO - DEFAULT_ICON.get_size()/2 + Vector2.LEFT.rotated(s.angle) * started_time/SIGNAL_DURATION * PARTICLES_DISTANCE,
Color(1.,1.,1.,1-started_time/SIGNAL_DURATION)
)
func _process(delta):
if started_time < SIGNAL_DURATION:
started_time += delta
queue_redraw()
else:
queue_free()
class DetectorSignalIndividual:
var angle : float
var icon : Texture
func _init(
_angle : float = 0.,
_icon : Texture = DEFAULT_ICON
):
angle = _angle
icon = _icon

View File

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

View File

@@ -8,7 +8,6 @@ var indicators : Array[InGameIndicator]
@export var region : Region @export var region : Region
@onready var steps : Array[Step] = [ @onready var steps : Array[Step] = [
TakeShovelStep.new(),
DigSeedStep.new(), DigSeedStep.new(),
TakeSeedStep.new(), TakeSeedStep.new(),
PlantSeedStep.new(), PlantSeedStep.new(),

View File

@@ -49,3 +49,4 @@ func _on_give_up_pressed():
if GameInfo.game_data: if GameInfo.game_data:
SceneManager.change_to_scene_id('ASTRA') SceneManager.change_to_scene_id('ASTRA')
GameInfo.game_data.give_up() GameInfo.game_data.give_up()
pause = false

View File

@@ -27,6 +27,7 @@ buses/default_bus_layout="uid://b4cpfxfs74sb8"
[autoload] [autoload]
PlantTextureBuilder="*uid://b8gqdgabrjaml"
Pointer="*res://gui/pointer/pointer.tscn" Pointer="*res://gui/pointer/pointer.tscn"
AudioManager="*res://common/audio_manager/audio_manager.tscn" AudioManager="*res://common/audio_manager/audio_manager.tscn"
GameInfo="*res://common/game_info/game_info.gd" GameInfo="*res://common/game_info/game_info.gd"
@@ -34,7 +35,6 @@ Pause="*res://gui/pause/pause.tscn"
Dialogic="*res://addons/dialogic/Core/DialogicGameHandler.gd" Dialogic="*res://addons/dialogic/Core/DialogicGameHandler.gd"
LoadingScreen="*res://gui/loading_screen/loading_screen.tscn" LoadingScreen="*res://gui/loading_screen/loading_screen.tscn"
SceneManager="*res://common/scene_manager/scene_manager.tscn" SceneManager="*res://common/scene_manager/scene_manager.tscn"
PlantTextureBuilder="*uid://b8gqdgabrjaml"
[dialogic] [dialogic]
@@ -43,8 +43,7 @@ directories/dch_directory={
"mysterious_demeter": "res://dialogs/characters/mysterious_demeter.dch" "mysterious_demeter": "res://dialogs/characters/mysterious_demeter.dch"
} }
directories/dtl_directory={ directories/dtl_directory={
"demeter_astra_1": "res://dialogs/timelines/gameplay_related/demeter_astra_1.dtl", "demeter_astra_failed": "res://dialogs/timelines/gameplay_related/demeter_astra_failed.dtl",
"demeter_astra_2": "res://dialogs/timelines/gameplay_related/demeter_astra_2.dtl",
"demeter_intro": "res://dialogs/timelines/story/demeter_intro.dtl", "demeter_intro": "res://dialogs/timelines/story/demeter_intro.dtl",
"demeter_midrun": "res://dialogs/timelines/story/demeter_post_tutorial.dtl", "demeter_midrun": "res://dialogs/timelines/story/demeter_post_tutorial.dtl",
"demeter_outro": "res://dialogs/timelines/story/demeter_outro.dtl", "demeter_outro": "res://dialogs/timelines/story/demeter_outro.dtl",

View File

@@ -79,12 +79,12 @@ shader_parameter/layer_scale = 20.0
shader_parameter/layer_scale_step = 10.0 shader_parameter/layer_scale_step = 10.0
shader_parameter/layers_count = 3 shader_parameter/layers_count = 3
[sub_resource type="Sky" id="Sky_65b6a"] [sub_resource type="Sky" id="Sky_kdvug"]
sky_material = SubResource("ShaderMaterial_mwti2") sky_material = SubResource("ShaderMaterial_mwti2")
[sub_resource type="Environment" id="Environment_lhhy6"] [sub_resource type="Environment" id="Environment_lhhy6"]
background_mode = 2 background_mode = 2
sky = SubResource("Sky_65b6a") sky = SubResource("Sky_kdvug")
sky_custom_fov = 61.7 sky_custom_fov = 61.7
ambient_light_source = 3 ambient_light_source = 3
ambient_light_color = Color(1, 1, 1, 1) ambient_light_color = Color(1, 1, 1, 1)
@@ -171,7 +171,7 @@ environment = SubResource("Environment_lhhy6")
[node name="Player3D" parent="." unique_id=549819967 instance=ExtResource("3_4wxm6")] [node name="Player3D" parent="." unique_id=549819967 instance=ExtResource("3_4wxm6")]
unique_name_in_owner = true unique_name_in_owner = true
transform = Transform3D(-1, 0, -1.509958e-07, 0, 1, 0, 1.509958e-07, 0, -1, -51.325, 1, -4.22) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -29.925, 1, 4.22)
[node name="Phone" parent="." unique_id=429299908 instance=ExtResource("4_mwti2")] [node name="Phone" parent="." unique_id=429299908 instance=ExtResource("4_mwti2")]
unique_name_in_owner = true unique_name_in_owner = true

View File

@@ -2,6 +2,7 @@
extends Node3D extends Node3D
const INTRO_DIALOG = "res://dialogs/timelines/story/demeter_intro.dtl" const INTRO_DIALOG = "res://dialogs/timelines/story/demeter_intro.dtl"
const FAILED_DIALOG = "res://dialogs/timelines/gameplay_related/demeter_astra_failed.dtl"
const ROOM_PART_SCENE := preload("res://stages/3d_scenes/astra_base/room_part.tscn") const ROOM_PART_SCENE := preload("res://stages/3d_scenes/astra_base/room_part.tscn")
const ROOM_END_SCENE := preload("res://stages/3d_scenes/astra_base/assets/3d/astra_base_room_end.blend") const ROOM_END_SCENE := preload("res://stages/3d_scenes/astra_base/assets/3d/astra_base_room_end.blend")
@@ -15,6 +16,8 @@ const LIFT_TIME := 2
@export var room_part_number := 100 : set = set_room_part_number @export var room_part_number := 100 : set = set_room_part_number
var chosen_incubator_id := -1
# Cheat Code # Cheat Code
func _input(_e): func _input(_e):
if ( if (
@@ -32,7 +35,8 @@ func _ready():
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
set_room_part_number() set_room_part_number()
var new_player_incubator := %Incubators.get_children().pick_random() as Incubator chosen_incubator_id = randi_range(0, len(%Incubators.get_children()))
var new_player_incubator := %Incubators.get_children()[chosen_incubator_id] as Incubator
new_player_incubator.used = true new_player_incubator.used = true
%Player3D.position = new_player_incubator.global_position + Vector3.UP %Player3D.position = new_player_incubator.global_position + Vector3.UP
%Player3D.rotation = new_player_incubator.rotation %Player3D.rotation = new_player_incubator.rotation
@@ -42,6 +46,8 @@ func _ready():
%Lift.interactable = true %Lift.interactable = true
) )
GameInfo.game_data.incubator_used.append(chosen_incubator_id)
story() story()
@@ -55,7 +61,13 @@ func story():
Dialogic.start(INTRO_DIALOG) Dialogic.start(INTRO_DIALOG)
await Dialogic.timeline_ended await Dialogic.timeline_ended
else:
%Phone.clicked.connect(
func ():
Dialogic.start(FAILED_DIALOG)
%Phone.interactable = false
)
%LiftAnimationPlayer.play("arrive") %LiftAnimationPlayer.play("arrive")
await %Lift.clicked await %Lift.clicked
%LiftAnimationPlayer.play_backwards("arrive") %LiftAnimationPlayer.play_backwards("arrive")
@@ -85,6 +97,7 @@ func set_room_part_number(_room_part_number : int = room_part_number):
var shifted_origin = Vector3.LEFT * ROOM_PART_SHIFT * room_part_number/2 var shifted_origin = Vector3.LEFT * ROOM_PART_SHIFT * room_part_number/2
var incubator_id = 0
for i in range(room_part_number): for i in range(room_part_number):
var new_room_part := ROOM_PART_SCENE.instantiate() as Node3D var new_room_part := ROOM_PART_SCENE.instantiate() as Node3D
%RoomParts.add_child(new_room_part) %RoomParts.add_child(new_room_part)
@@ -92,7 +105,9 @@ func set_room_part_number(_room_part_number : int = room_part_number):
for j in range(INCUBATOR_BY_ROOM): for j in range(INCUBATOR_BY_ROOM):
for direction in [-1, 1]: for direction in [-1, 1]:
var new_incubator := INCUBATOR_SCENE.instantiate() as Incubator var new_incubator := INCUBATOR_SCENE.instantiate() as Incubator
new_incubator.used = incubator_id in GameInfo.game_data.incubator_used
%Incubators.add_child(new_incubator) %Incubators.add_child(new_incubator)
incubator_id += 1
new_incubator.position = ( new_incubator.position = (
new_room_part.position new_room_part.position
+ j * Vector3.LEFT * (ROOM_PART_SHIFT / INCUBATOR_BY_ROOM) + j * Vector3.LEFT * (ROOM_PART_SHIFT / INCUBATOR_BY_ROOM)

View File

@@ -8,7 +8,7 @@ func _ready():
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
await %Phone.clicked await %Phone.clicked
Dialogic.start_timeline(OUTRO_TIMELINE_PATH) Dialogic.start(OUTRO_TIMELINE_PATH)
await Dialogic.timeline_ended await Dialogic.timeline_ended
%Credits.show() %Credits.show()

View File

@@ -1,6 +1,13 @@
extends Node3D extends Node3D
const DIALOG_PATH = "res://dialogs/timelines/story/demeter_ship_presentation.dtl"
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _ready(): func _ready():
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
%Phone.play_audio()
await %Phone.clicked
Dialogic.start(DIALOG_PATH)
await Dialogic.timeline_ended
SceneManager.change_to_scene_id("COCKPIT")

View File

@@ -147,4 +147,5 @@ omni_range = 21.258795
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.37195766, 2.8968668, -0.44411802) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.37195766, 2.8968668, -0.44411802)
[node name="Phone" parent="." unique_id=429299908 instance=ExtResource("7_dkh4e")] [node name="Phone" parent="." unique_id=429299908 instance=ExtResource("7_dkh4e")]
unique_name_in_owner = true
transform = Transform3D(-0.83126587, 0, -0.555875, 0, 1, 0, 0.555875, 0, -0.83126587, -4.110002, 1.6397171, 6.610814) transform = Transform3D(-0.83126587, 0, -0.555875, 0, 1, 0, 0.555875, 0, -0.83126587, -4.110002, 1.6397171, 6.610814)

View File

@@ -1,10 +1,58 @@
[gd_scene format=3 uid="uid://d0n52psuns1vl"] [gd_scene format=3 uid="uid://d0n52psuns1vl"]
[ext_resource type="Script" uid="uid://ddf3fktoer2ng" path="res://stages/intro/scripts/intro.gd" id="1_2nxbv"] [ext_resource type="Script" uid="uid://ddf3fktoer2ng" path="res://stages/intro/scripts/intro.gd" id="1_2nxbv"]
[ext_resource type="Shader" uid="uid://bv2rghn44mrrf" path="res://stages/title_screen/resources/shaders/stars.gdshader" id="2_851lr"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_2nxbv"]
shader = ExtResource("2_851lr")
shader_parameter/sky_color = Color(0.03, 0.05, 0.11, 1)
shader_parameter/star_base_color = Color(0.8, 1, 0.3, 1)
shader_parameter/star_hue_offset = 0.6
shader_parameter/star_intensity = 0.08
shader_parameter/star_twinkle_speed = 0.8
shader_parameter/star_twinkle_intensity = 0.2
shader_parameter/layer_scale = 20.0
shader_parameter/layer_scale_step = 10.0
shader_parameter/layers_count = 3
[sub_resource type="Sky" id="Sky_65b6a"]
sky_material = SubResource("ShaderMaterial_2nxbv")
[sub_resource type="Environment" id="Environment_mi20s"]
background_mode = 2
sky = SubResource("Sky_65b6a")
sky_custom_fov = 61.7
ambient_light_source = 3
ambient_light_color = Color(1, 1, 1, 1)
ambient_light_sky_contribution = 0.85
ambient_light_energy = 2.0
reflected_light_source = 2
tonemap_mode = 2
tonemap_exposure = 0.7
tonemap_white = 1.84
glow_enabled = true
glow_intensity = 0.22
glow_bloom = 0.22
glow_hdr_threshold = 0.79
glow_hdr_scale = 0.0
glow_hdr_luminance_cap = 5.63
fog_enabled = true
fog_mode = 1
fog_light_color = Color(0.13725491, 0.39215687, 0.6666667, 1)
fog_density = 0.1831
fog_aerial_perspective = 0.113
fog_sky_affect = 0.0
volumetric_fog_sky_affect = 0.0
adjustment_enabled = true
adjustment_saturation = 1.3
[node name="Intro" type="Node" unique_id=1801844904] [node name="Intro" type="Node" unique_id=1801844904]
script = ExtResource("1_2nxbv") script = ExtResource("1_2nxbv")
game_scene_path = "uid://d28cp7a21kwou" game_scene_path = "uid://d28cp7a21kwou"
[node name="CanvasLayer" type="CanvasLayer" parent="." unique_id=1051527956] [node name="Node3D" type="Node3D" parent="." unique_id=1668131521]
layer = 100
[node name="Camera3D" type="Camera3D" parent="Node3D" unique_id=2070854508]
[node name="WorldEnvironment" type="WorldEnvironment" parent="Node3D" unique_id=115692868]
environment = SubResource("Environment_mi20s")

View File

@@ -5,8 +5,8 @@
[ext_resource type="PackedScene" uid="uid://yk78ubpu5ghq" path="res://gui/game/pass_day/pass_day.tscn" id="3_ktnx3"] [ext_resource type="PackedScene" uid="uid://yk78ubpu5ghq" path="res://gui/game/pass_day/pass_day.tscn" id="3_ktnx3"]
[ext_resource type="PackedScene" uid="uid://12nak7amd1uq" path="res://gui/game/game_gui.tscn" id="4_qdnee"] [ext_resource type="PackedScene" uid="uid://12nak7amd1uq" path="res://gui/game/game_gui.tscn" id="4_qdnee"]
[ext_resource type="PackedScene" uid="uid://bgvbgeq46wee2" path="res://entities/player/player.tscn" id="5_ovqi1"] [ext_resource type="PackedScene" uid="uid://bgvbgeq46wee2" path="res://entities/player/player.tscn" id="5_ovqi1"]
[ext_resource type="PackedScene" uid="uid://cg1visg52i21a" path="res://entities/interactables/ladder/ladder.tscn" id="6_2w03p"]
[ext_resource type="PackedScene" uid="uid://d324mlmgls4fs" path="res://entities/interactables/truck/recharge/truck_recharge.tscn" id="7_6d8m3"] [ext_resource type="PackedScene" uid="uid://d324mlmgls4fs" path="res://entities/interactables/truck/recharge/truck_recharge.tscn" id="7_6d8m3"]
[ext_resource type="PackedScene" uid="uid://b8m537op75gib" path="res://entities/interactables/door/door.tscn" id="8_2f6js"]
[ext_resource type="PackedScene" uid="uid://dj7gp3crtg2yt" path="res://entities/camera/camera.tscn" id="8_fwgig"] [ext_resource type="PackedScene" uid="uid://dj7gp3crtg2yt" path="res://entities/camera/camera.tscn" id="8_fwgig"]
[node name="Region" type="Node2D" unique_id=1509166288 node_paths=PackedStringArray("entity_container")] [node name="Region" type="Node2D" unique_id=1509166288 node_paths=PackedStringArray("entity_container")]
@@ -28,14 +28,31 @@ region = NodePath("../..")
[node name="Entities" type="Node2D" parent="." unique_id=2132324579] [node name="Entities" type="Node2D" parent="." unique_id=2132324579]
y_sort_enabled = true y_sort_enabled = true
[node name="TruckLadder" parent="Entities" unique_id=1990299618 instance=ExtResource("6_2w03p")]
position = Vector2(51, -112)
[node name="Player" parent="Entities" unique_id=75851644 instance=ExtResource("5_ovqi1")] [node name="Player" parent="Entities" unique_id=75851644 instance=ExtResource("5_ovqi1")]
z_index = 1 z_index = 1
position = Vector2(3000, -41)
[node name="TruckRecharge" parent="Entities" unique_id=2068738444 instance=ExtResource("7_6d8m3")] [node name="TruckRecharge" parent="Entities" unique_id=2068738444 instance=ExtResource("7_6d8m3")]
position = Vector2(-50, -124) position = Vector2(-1, -169)
[node name="AstraDoor" parent="Entities" unique_id=2053096538 instance=ExtResource("8_2f6js")]
unique_name_in_owner = true
visible = false
modulate = Color(1, 0, 0, 1)
position = Vector2(-43, 367)
available = false
default_info_title = "ASTRA_FACTORY"
default_info_desc = "ASTRA_FACTORY_TEXT"
[node name="ShipGarageDoor" parent="Entities" unique_id=1073871193 instance=ExtResource("8_2f6js")]
unique_name_in_owner = true
visible = false
modulate = Color(1, 0, 0, 1)
to_scene_id = "GARAGE"
default_interact_text = "ENTER"
available = false
default_info_title = "MYSTERIOUS_DOOR"
default_info_desc = "MYSTERIOUS_DOOR_TEXT"
[node name="Camera" parent="." unique_id=1399042986 node_paths=PackedStringArray("following") instance=ExtResource("8_fwgig")] [node name="Camera" parent="." unique_id=1399042986 node_paths=PackedStringArray("following") instance=ExtResource("8_fwgig")]
following = NodePath("../Entities/Player") following = NodePath("../Entities/Player")

View File

@@ -9,6 +9,7 @@ const TILE_SET : TileSet = preload("res://stages/terrain/region/resources/moss_b
const TILE_SCALE = 1 const TILE_SCALE = 1
const TILE_SIZE : int = roundi(TILE_SET.tile_size.x * TILE_SCALE) const TILE_SIZE : int = roundi(TILE_SET.tile_size.x * TILE_SCALE)
const START_ROCK_HOLE_RADIUS = 5 const START_ROCK_HOLE_RADIUS = 5
const PLAYER_ROCK_HOLE_RADIUS = 5
const START_DECONTAMINATION_HOLE_RADIUS = 3 const START_DECONTAMINATION_HOLE_RADIUS = 3
const CHUNK_TILE_SIZE : int = 20 const CHUNK_TILE_SIZE : int = 20
const CHUNK_SIZE = CHUNK_TILE_SIZE * TILE_SIZE const CHUNK_SIZE = CHUNK_TILE_SIZE * TILE_SIZE
@@ -69,8 +70,6 @@ func _ready():
if e is Plant: if e is Plant:
data.add_plant_data(e.data, false) data.add_plant_data(e.data, false)
generate_first_entities()
ground_layer = GroundLayer.new(self) ground_layer = GroundLayer.new(self)
add_child(ground_layer) add_child(ground_layer)
rock_layer = RockLayer.new(self) rock_layer = RockLayer.new(self)
@@ -91,11 +90,6 @@ func _process(_d):
#region ------------------ Generation ------------------ #region ------------------ Generation ------------------
func generate_first_entities():
if not (Vector2i.ZERO in data.generated_chunk_entities):
# Generate shovel
drop_item(Shovel.new(), entity_container.global_position + Vector2(0, 100))
func get_chunk_key(coord) -> String: func get_chunk_key(coord) -> String:
return "%d:%d" % [coord.x, coord.y] return "%d:%d" % [coord.x, coord.y]
@@ -150,6 +144,21 @@ func edit_map_origin():
rock_layer.remove_rocks(hole_tiles, true) rock_layer.remove_rocks(hole_tiles, true)
decontamination_layer.place_decontaminations(decontamination_tiles, true) decontamination_layer.place_decontaminations(decontamination_tiles, true)
# Dig a hole in player position
var player_hole_tiles : Array[Vector2i] = []
var player_tile_position := Vector2i(
roundi(data.player_position.x/float(TILE_SIZE)),
roundi(data.player_position.y/float(TILE_SIZE))
)
for x in range(-PLAYER_ROCK_HOLE_RADIUS, PLAYER_ROCK_HOLE_RADIUS):
for y in range(-PLAYER_ROCK_HOLE_RADIUS, PLAYER_ROCK_HOLE_RADIUS):
var coord = Vector2i(x,y)
if coord.distance_to(Vector2.ZERO) < PLAYER_ROCK_HOLE_RADIUS:
player_hole_tiles.append(coord + player_tile_position)
rock_layer.remove_rocks(player_hole_tiles, true)
setup_tutorial_doors()
func remove_chunk(chunk : Chunk): func remove_chunk(chunk : Chunk):
generated_chunks.erase(get_chunk_key(chunk.data.chunk_coord)) generated_chunks.erase(get_chunk_key(chunk.data.chunk_coord))
chunk.unload() chunk.unload()
@@ -179,6 +188,18 @@ func save():
data.player_position = player.global_position data.player_position = player.global_position
GameInfo.save_game_data() GameInfo.save_game_data()
func setup_tutorial_doors():
%AstraDoor.visible = data.tutorial
%ShipGarageDoor.visible = data.tutorial
if data.tutorial:
%AstraDoor.global_position = data.get_random_spawn_position()
%AstraDoor.available = false
%ShipGarageDoor.available = data.state == RegionData.State.SUCCEEDED
data.succeded.connect(
func ():
%ShipGarageDoor.available = true
)
#endregion #endregion
#region ------------------ Usage ------------------ #region ------------------ Usage ------------------

View File

@@ -14,6 +14,7 @@ signal pass_day_ended(region_data : RegionData)
const DEFAULT_START_CHARGE := 10 const DEFAULT_START_CHARGE := 10
const DEFAULT_OBJECTIVE := 10 const DEFAULT_OBJECTIVE := 10
const MAX_RANDOM_SPAWN_DISTANCE = 3000
@export var region_seed : int @export var region_seed : int
@export var region_name : String @export var region_name : String
@@ -31,7 +32,7 @@ const DEFAULT_OBJECTIVE := 10
@export var chunks_data : Dictionary[String, ChunkData] @export var chunks_data : Dictionary[String, ChunkData]
@export var player_position : Vector2i = Region.CHUNK_SIZE/2. * Vector2.ONE @export var player_position : Vector2i = Region.CHUNK_SIZE/2. * Vector2.ONE + get_random_spawn_position()
@export var charges : int : @export var charges : int :
set(v): set(v):
@@ -81,7 +82,9 @@ func add_chunk_data(coord : Vector2i, data : ChunkData):
chunks_data[get_coord_id(coord)] = data chunks_data[get_coord_id(coord)] = data
func get_chunk_data(coord : Vector2i) -> ChunkData: func get_chunk_data(coord : Vector2i) -> ChunkData:
return chunks_data[get_coord_id(coord)] if get_coord_id(coord) in chunks_data:
return chunks_data[get_coord_id(coord)]
return null
func get_or_create_chunk_data(coord : Vector2i) -> ChunkData: func get_or_create_chunk_data(coord : Vector2i) -> ChunkData:
if has_chunk_data(coord): if has_chunk_data(coord):
@@ -147,3 +150,13 @@ func _on_plant_disappeared(plant_data : PlantData):
update() update()
#endregion #endregion
func get_random_spawn_position():
var rng := RandomNumberGenerator.new()
rng.seed = region_seed
return Vector2(
rng.randf_range(-MAX_RANDOM_SPAWN_DISTANCE,MAX_RANDOM_SPAWN_DISTANCE),
rng.randf_range(-MAX_RANDOM_SPAWN_DISTANCE,MAX_RANDOM_SPAWN_DISTANCE),
)

View File

@@ -25,7 +25,7 @@ func place_decontaminations(coords : Array[Vector2i], save := false, on_finished
floori(coord.y / float(Region.CHUNK_TILE_SIZE)), floori(coord.y / float(Region.CHUNK_TILE_SIZE)),
) )
(region.data (region.data
.get_chunk_data(chunk_coord) .get_or_create_chunk_data(chunk_coord)
.update_decontamination_tile_diff(coord, ChunkData.TileDiff.PRESENT)) .update_decontamination_tile_diff(coord, ChunkData.TileDiff.PRESENT))
func is_decontamined(coord : Vector2i) -> bool: func is_decontamined(coord : Vector2i) -> bool:

View File

@@ -37,7 +37,7 @@ func remove_rocks(coords : Array[Vector2i], save = false,on_finished : Callable
) )
var chunk_tile_coord : Vector2i = coord - chunk_coord * Region.CHUNK_TILE_SIZE var chunk_tile_coord : Vector2i = coord - chunk_coord * Region.CHUNK_TILE_SIZE
(region.data (region.data
.get_chunk_data(chunk_coord) .get_or_create_chunk_data(chunk_coord)
.update_rock_tile_diff(chunk_tile_coord, ChunkData.TileDiff.ABSENT)) .update_rock_tile_diff(chunk_tile_coord, ChunkData.TileDiff.ABSENT))
func dig_rocks(coords : Array[Vector2i]) -> bool: func dig_rocks(coords : Array[Vector2i]) -> bool:

View File

@@ -89,6 +89,9 @@ ONE_TIME_USE,Single use,Usage unique
BUILD_%s,Build %s,Construit %s BUILD_%s,Build %s,Construit %s
FORK,Fork,Fourche FORK,Fork,Fourche
FORK_DESC_TEXT,"Use it to [b]harvest mature plants[/b].","Utilise-la pour [b]récolter les plantes mature[/b]." FORK_DESC_TEXT,"Use it to [b]harvest mature plants[/b].","Utilise-la pour [b]récolter les plantes mature[/b]."
DETECTOR,Detector,Détecteur
DETECTOR_DESC_TEXT,"Indicate [b]near signals[/b].","Indique les [b]signaux proches[/b]"
DETECT_USE_TEXT,"Search near signals","Rechercher les signaux proches"
HARVEST,Harvest,Récolter HARVEST,Harvest,Récolter
KNIFE,Knife,Couteau KNIFE,Knife,Couteau
KNIFE_DESC_TEXT,"Use it to [b]harvest mature plants[/b]. Does not consume energy.",Utilise-le pour [b]récolter les plantes mature[/b]. Ne consomme pas dénergie. KNIFE_DESC_TEXT,"Use it to [b]harvest mature plants[/b]. Does not consume energy.",Utilise-le pour [b]récolter les plantes mature[/b]. Ne consomme pas dénergie.
@@ -115,7 +118,7 @@ SCORE_%d,Score %d,Score %d
SOLAR_PANNEL,Solar panel,Panneau solaire SOLAR_PANNEL,Solar panel,Panneau solaire
SOLAR_PANNEL_DESCRIPTION_TEXT,Grants energy when charged. Take several days to recharge,Donne de lénergie quand chargé. Prend plusieurs jours à se recharger SOLAR_PANNEL_DESCRIPTION_TEXT,Grants energy when charged. Take several days to recharge,Donne de lénergie quand chargé. Prend plusieurs jours à se recharger
TRUCK_ENTRANCE,Truck entrance,Entrée du camion TRUCK_ENTRANCE,Truck entrance,Entrée du camion
ENTER_TRUCK,Enter truck,Entrer dans le camion ENTER,Enter,Entrer
EXIT,Exit,Sortie EXIT,Exit,Sortie
EXIT_TRUCK,Exit truck,Sortir du camion EXIT_TRUCK,Exit truck,Sortir du camion
LADDER_DESC_TEXT,A good old ladder,Une bonne vieille échelle LADDER_DESC_TEXT,A good old ladder,Une bonne vieille échelle
@@ -197,4 +200,8 @@ MUSIC_SOUND_DESIGN_AND_WRITING,"Music, Sound design and Wrinting","Musique, Sons
COMMUNICATION,"Communication","Communication" COMMUNICATION,"Communication","Communication"
SPLASH_ART,Splash Art,Splash Art SPLASH_ART,Splash Art,Splash Art
TRAILER,"Trailer","Trailer" TRAILER,"Trailer","Trailer"
CREDITS,Credits,Crédits CREDITS,Credits,Crédits
ASTRA_FACTORY,Astra Factory,Usine Astra
ASTRA_FACTORY_TEXT,Production factory of Astra base,Usine de production de la base Astra
MYSTERIOUS_DOOR,Mysterious Door,Porte mystérieuse
MYSTERIOUS_DOOR_TEXT,"This door has a space ship logo on it... What could it be ?","Cette porte à un logo de vaisseau marqué dessus... Que peut-il y avoir à l'intérieur ?"
1 keys en fr
89 PICKAXE SHOVEL_DESC_TEXT Pickaxe Use it to [b]dig up seeds[/b] and [b]harvest mature plants[/b]. Pioche Utilise-la pour [b]déterrer les graines[/b] et pour [b]récolter les plantes mature[/b].
90 PICKAXE_DESC_TEXT TROWEL Can dig rock and precious materials Trowel Peut creuser la roche et des matériaux précieux Truelle
91 DIG TROWEL_DESC_TEXT Dig Use it to [b]harvest mature plants[/b]. Can grant a [b]bonus seed[/b]. Creuser Utilise-la pour [b]récolter les plantes mature[/b]. Peut donner une [b]graine supplémentaire[/b].
92 PICKAXE Pickaxe Pioche
93 PICKAXE_DESC_TEXT Can dig rock and precious materials Peut creuser la roche et des matériaux précieux
94 DIG Dig Creuser
95 OPEN Open Ouvrir
96 %s_SEED %s Seed Graine de %s
97 PLANT_%s_MUST_BE_USED_IN_DECONTAMINATED_ZONE Plant [b]%s[/b]. Must be used in the decontamined zone. Plante [b]%s[/b]. Doit être utilisée dans la zone décontaminée.
118 COMPOST Compost Compost
119 PLACE_SEED Place seed Placer la graine
120 COMPOST_DESC_TEXT This research station can provide some bonuses when filled with seeds Cette station de recherche peut offrir certains avantages une fois remplie de graines
121 CHOOSE_A_REWARD Choose a reward Choisis une récompense
122 REWARD_SCREEN_TEXT Discover new plants or upgrade already discovered ones Découvre de nouvelles plantes ou améliore celles déjà découvertes
123 START Start Commencer
124 CONTINUE Continue Continuer
200
201
202
203
204
205
206
207