From fb8a2da4ff2ad4b375b3b4af834dd3e85122154e Mon Sep 17 00:00:00 2001 From: Zacharie Guet Date: Fri, 20 Feb 2026 01:00:55 +0100 Subject: [PATCH] =?UTF-8?q?ajout=20du=20d=C3=A9tecteur=20et=20liaison=20en?= =?UTF-8?q?tre=20les=20sc=C3=A8nes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/dialogic/Modules/Text/event_text.gd | 652 +++++++++--------- .../assets/sfx/signal/signal.mp3 | Bin 0 -> 96966 bytes .../assets/sfx/signal/signal.mp3.import | 19 + common/audio_manager/audio_manager.tscn | 9 + common/game_data/scripts/game_data.gd | 6 +- common/scene_manager/scene_manager.tscn | 3 +- common/scene_manager/scenes/garage.tres | 10 + .../gameplay_related/demeter_astra_1.dtl | 5 - .../gameplay_related/demeter_astra_1.dtl.uid | 1 - .../gameplay_related/demeter_astra_2.dtl | 5 - .../gameplay_related/demeter_astra_2.dtl.uid | 1 - .../gameplay_related/demeter_astra_failed.dtl | 22 + .../demeter_astra_failed.dtl.uid | 1 + .../timelines/story/demeter_post_tutorial.dtl | 52 +- .../story/demeter_ship_presentation.dtl | 27 +- entities/interactable_3d/interactable_3d.gd | 3 +- entities/interactables/door/door.tscn | 22 + entities/interactables/door/script/door.gd | 19 + .../interactables/door/script/door.gd.uid | 1 + .../interactables/scripts/interactable.gd | 5 +- .../player/inventory/scripts/inventory.gd | 2 + .../inventory/scripts/items/detector.gd | 33 + .../inventory/scripts/items/detector.gd.uid | 1 + .../player/inventory/scripts/items/fork.gd | 3 + .../player/inventory/scripts/items/trowel.gd | 3 - .../scripts/items/utils/detector_signal.gd | 61 ++ .../items/utils/detector_signal.gd.uid | 1 + gui/game/tutorial/scripts/tutorial.gd | 1 - gui/pause/scripts/pause.gd | 1 + project.godot | 5 +- stages/3d_scenes/astra_base/astra_base.tscn | 6 +- .../astra_base/scripts/astra_base.gd | 19 +- .../borea_base/scripts/borea_base.gd | 2 +- .../ship_garage/scripts/ship_garage.gd | 7 + stages/3d_scenes/ship_garage/ship_garage.tscn | 1 + stages/intro/intro.tscn | 52 +- stages/terrain/region/region.tscn | 27 +- stages/terrain/region/scripts/region.gd | 35 +- stages/terrain/region/scripts/region_data.gd | 17 +- .../tile_map_layers/decontamination_layer.gd | 2 +- .../scripts/tile_map_layers/rock_layer.gd | 2 +- translation/game/gui.csv | 11 +- 42 files changed, 737 insertions(+), 418 deletions(-) create mode 100644 common/audio_manager/assets/sfx/signal/signal.mp3 create mode 100644 common/audio_manager/assets/sfx/signal/signal.mp3.import create mode 100644 common/scene_manager/scenes/garage.tres delete mode 100644 dialogs/timelines/gameplay_related/demeter_astra_1.dtl delete mode 100644 dialogs/timelines/gameplay_related/demeter_astra_1.dtl.uid delete mode 100644 dialogs/timelines/gameplay_related/demeter_astra_2.dtl delete mode 100644 dialogs/timelines/gameplay_related/demeter_astra_2.dtl.uid create mode 100644 dialogs/timelines/gameplay_related/demeter_astra_failed.dtl create mode 100644 dialogs/timelines/gameplay_related/demeter_astra_failed.dtl.uid create mode 100644 entities/interactables/door/door.tscn create mode 100644 entities/interactables/door/script/door.gd create mode 100644 entities/interactables/door/script/door.gd.uid create mode 100644 entities/player/inventory/scripts/items/detector.gd create mode 100644 entities/player/inventory/scripts/items/detector.gd.uid create mode 100644 entities/player/inventory/scripts/items/utils/detector_signal.gd create mode 100644 entities/player/inventory/scripts/items/utils/detector_signal.gd.uid diff --git a/addons/dialogic/Modules/Text/event_text.gd b/addons/dialogic/Modules/Text/event_text.gd index 7e29d4a..77b165e 100644 --- a/addons/dialogic/Modules/Text/event_text.gd +++ b/addons/dialogic/Modules/Text/event_text.gd @@ -24,18 +24,18 @@ var portrait := "" ## Used to set the character resource from the unique name identifier and vice versa var character_identifier: String: - get: - if character and not "{" in character_identifier: - var identifier := character.get_identifier() - if not identifier.is_empty(): - return identifier - return character_identifier - set(value): - character_identifier = value - character = DialogicResourceUtil.get_character_resource(value) - if Engine.is_editor_hint() and ((not character) or (character and not character.portraits.has(portrait))): - portrait = "" - ui_update_needed.emit() + get: + if character and not "{" in character_identifier: + var identifier := character.get_identifier() + if not identifier.is_empty(): + return identifier + return character_identifier + set(value): + character_identifier = value + character = DialogicResourceUtil.get_character_resource(value) + if Engine.is_editor_hint() and ((not character) or (character and not character.portraits.has(portrait))): + portrait = "" + ui_update_needed.emit() var regex := RegEx.create_from_string(r'\s*((")?(?(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(\W*(?\(.*\)))?\s*(?(.|\n)*)') var split_regex := RegEx.create_from_string(r"((\[n\]|\[n\+\])?((?!(\[n\]|\[n\+\]))(.|\n))+)") @@ -49,225 +49,225 @@ signal advance ################################################################################ func _clear_state() -> void: - dialogic.current_state_info.erase('text_sub_idx') - _disconnect_signals() + dialogic.current_state_info.erase('text_sub_idx') + _disconnect_signals() func _execute() -> void: - if text.is_empty(): - finish() - return + if text.is_empty(): + finish() + return - ## If the speaker is provided as an expression, parse it now. - if "{" in character_identifier: - character = null - var character_name: String = dialogic.Expressions.execute_string(character_identifier) - get_or_create_character(character_name) + ## If the speaker is provided as an expression, parse it now. + if "{" in character_identifier: + character = null + var character_name: String = dialogic.Expressions.execute_string(character_identifier) + get_or_create_character(character_name) - ## Change Portrait and Active Speaker - if dialogic.has_subsystem("Portraits"): - if character: + ## Change Portrait and Active Speaker + if dialogic.has_subsystem("Portraits"): + if character: - dialogic.Portraits.change_speaker(character, portrait) + dialogic.Portraits.change_speaker(character, portrait) - if portrait and dialogic.Portraits.is_character_joined(character): - dialogic.Portraits.change_character_portrait(character, portrait) + if portrait and dialogic.Portraits.is_character_joined(character): + dialogic.Portraits.change_character_portrait(character, portrait) - else: - dialogic.Portraits.change_speaker(null) + else: + dialogic.Portraits.change_speaker(null) - ## Change and Type Sound Mood - if character: - dialogic.Text.update_name_label(character) + ## Change and Type Sound Mood + if character: + dialogic.Text.update_name_label(character) - var current_portrait: String = portrait - if portrait.is_empty(): - current_portrait = dialogic.current_state_info["portraits"].get(character.get_identifier(), {}).get("portrait", "") + var current_portrait: String = portrait + if portrait.is_empty(): + 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", "") - dialogic.Text.update_typing_sound_mood_from_character(character, current_portrait_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) - else: - dialogic.Text.update_name_label(null) - dialogic.Text.update_typing_sound_mood() + else: + dialogic.Text.update_name_label(null) + dialogic.Text.update_typing_sound_mood() - ## Handle style changes - if dialogic.has_subsystem("Styles"): - var current_base_style: String = dialogic.current_state_info.get("base_style") - var current_style: String = dialogic.current_state_info.get("style", "") - var character_style: String = "" if not character else character.custom_info.get("style", "") + ## Handle style changes + if dialogic.has_subsystem("Styles"): + var current_base_style: String = dialogic.current_state_info.get("base_style") + var current_style: String = dialogic.current_state_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 - 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")) - await dialogic.get_tree().process_frame + ## 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): + dialogic.Styles.change_style(dialogic.current_state_info.get("base_style", "Default")) + await dialogic.get_tree().process_frame - ## Change to the characters style if this character has one - elif character and not character_style.is_empty(): - dialogic.Styles.change_style(character_style, false) - await dialogic.get_tree().process_frame + ## Change to the characters style if this character has one + elif character and not character_style.is_empty(): + dialogic.Styles.change_style(character_style, false) + await dialogic.get_tree().process_frame - _connect_signals() + _connect_signals() - var character_name_text := dialogic.Text.get_character_name_parsed(character) - var final_text: String = get_property_translated('text') - if ProjectSettings.get_setting('dialogic/text/split_at_new_lines', false): - match ProjectSettings.get_setting('dialogic/text/split_at_new_lines_as', 0): - 0: - final_text = final_text.replace('\n', '[n]') - 1: - final_text = final_text.replace('\n', '[n+][br]') + var character_name_text := dialogic.Text.get_character_name_parsed(character) + var final_text: String = get_property_translated('text') + if ProjectSettings.get_setting('dialogic/text/split_at_new_lines', false): + match ProjectSettings.get_setting('dialogic/text/split_at_new_lines_as', 0): + 0: + final_text = final_text.replace('\n', '[n]') + 1: + final_text = final_text.replace('\n', '[n+][br]') - var split_text := [] - for i in split_regex.search_all(final_text): - split_text.append([i.get_string().trim_prefix('[n]').trim_prefix('[n+]')]) - split_text[-1].append(i.get_string().begins_with('[n+]')) + var split_text := [] + for i in split_regex.search_all(final_text): + split_text.append([i.get_string().trim_prefix('[n]').trim_prefix('[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)): - dialogic.Inputs.block_input(ProjectSettings.get_setting('dialogic/text/text_reveal_skip_delay', 0.1)) + 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)) - if reveal_next_segment: - dialogic.Text.hide_next_indicators() + if reveal_next_segment: + 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 is_append: bool = split_text[section_idx][1] + var segment: String = dialogic.Text.parse_text(split_text[section_idx][0], 0) + var is_append: bool = split_text[section_idx][1] - 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}) + 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}) - await dialogic.Text.update_textbox(final_text, false) + await dialogic.Text.update_textbox(final_text, false) - state = States.REVEALING - _try_play_current_line_voice() - final_text = dialogic.Text.update_dialog_text(final_text, false, is_append) + state = States.REVEALING + _try_play_current_line_voice() + 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 - # is a Choice event. - if dialogic.Inputs.auto_skip.enabled: - dialogic.Text.skip_text_reveal() - else: - await dialogic.Text.text_finished + # We must skip text animation before we potentially return when there + # is a Choice event. + if dialogic.Inputs.auto_skip.enabled: + dialogic.Text.skip_text_reveal() + else: + await dialogic.Text.text_finished - state = States.IDLE - else: - reveal_next_segment = true + state = States.IDLE + else: + reveal_next_segment = true - # 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): - dialogic.Text.show_next_indicators(true) + # 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): + dialogic.Text.show_next_indicators(true) - finish() - return + finish() + return - elif dialogic.Inputs.auto_advance.is_enabled(): - dialogic.Text.show_next_indicators(false, true) - dialogic.Inputs.auto_advance.start() - else: - dialogic.Text.show_next_indicators() + elif dialogic.Inputs.auto_advance.is_enabled(): + dialogic.Text.show_next_indicators(false, true) + dialogic.Inputs.auto_advance.start() + else: + dialogic.Text.show_next_indicators() - if section_idx == len(split_text)-1: - state = States.DONE + if section_idx == len(split_text)-1: + state = States.DONE - # 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. - if dialogic.Inputs.auto_skip.enabled: - await dialogic.Inputs.start_autoskip_timer() + # 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. + if dialogic.Inputs.auto_skip.enabled: + await dialogic.Inputs.start_autoskip_timer() - # Check if Auto-Skip is still enabled. - if not dialogic.Inputs.auto_skip.enabled: - await advance + # Check if Auto-Skip is still enabled. + if not dialogic.Inputs.auto_skip.enabled: + await advance - else: - await advance + else: + await advance - finish() + finish() func _mark_as_read(character_name_text: String, final_text: String) -> void: - if dialogic.has_subsystem('History'): - if character: - dialogic.History.store_simple_history_entry(final_text, event_name, {'character':character_name_text, 'character_color':character.color}) - else: - dialogic.History.store_simple_history_entry(final_text, event_name) - dialogic.History.mark_event_as_visited() + if dialogic.has_subsystem('History'): + if character: + dialogic.History.store_simple_history_entry(final_text, event_name, {'character':character_name_text, 'character_color':character.color}) + else: + dialogic.History.store_simple_history_entry(final_text, event_name) + dialogic.History.mark_event_as_visited() func _connect_signals() -> void: - if not dialogic.Inputs.dialogic_action.is_connected(_on_dialogic_input_action): - dialogic.Inputs.dialogic_action.connect(_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.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): - dialogic.Inputs.auto_advance.autoadvance.connect(_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) ## If the event is done, this method can clean-up signal connections. func _disconnect_signals() -> void: - if dialogic.Inputs.dialogic_action.is_connected(_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): - dialogic.Inputs.auto_advance.autoadvance.disconnect(_on_dialogic_input_autoadvance) - if dialogic.Inputs.auto_skip.toggled.is_connected(_on_auto_skip_enable): - dialogic.Inputs.auto_skip.toggled.disconnect(_on_auto_skip_enable) + if dialogic.Inputs.dialogic_action.is_connected(_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): + dialogic.Inputs.auto_advance.autoadvance.disconnect(_on_dialogic_input_autoadvance) + if dialogic.Inputs.auto_skip.toggled.is_connected(_on_auto_skip_enable): + dialogic.Inputs.auto_skip.toggled.disconnect(_on_auto_skip_enable) ## Tries to play the voice clip for the current line. func _try_play_current_line_voice() -> void: - # If Auto-Skip is enabled and we skip voice clips, we don't want to play. - if (dialogic.Inputs.auto_skip.enabled - and dialogic.Inputs.auto_skip.skip_voice): - return + # If Auto-Skip is enabled and we skip voice clips, we don't want to play. + if (dialogic.Inputs.auto_skip.enabled + and dialogic.Inputs.auto_skip.skip_voice): + return - # Plays the audio region for the current line. - if (dialogic.has_subsystem('Voice') - and dialogic.Voice.is_voiced(dialogic.current_event_idx)): - dialogic.Voice.play_voice() + # Plays the audio region for the current line. + if (dialogic.has_subsystem('Voice') + and dialogic.Voice.is_voiced(dialogic.current_event_idx)): + dialogic.Voice.play_voice() func _on_dialogic_input_action() -> void: - match state: - States.REVEALING: - if dialogic.Text.is_text_reveal_skippable(): - dialogic.Text.skip_text_reveal() - dialogic.Inputs.stop_timers() - _: - if dialogic.Inputs.manual_advance.is_enabled(): - advance.emit() - dialogic.Inputs.stop_timers() + match state: + States.REVEALING: + if dialogic.Text.is_text_reveal_skippable(): + dialogic.Text.skip_text_reveal() + dialogic.Inputs.stop_timers() + _: + if dialogic.Inputs.manual_advance.is_enabled(): + advance.emit() + dialogic.Inputs.stop_timers() func _on_dialogic_input_autoadvance() -> void: - if state == States.IDLE or state == States.DONE: - advance.emit() + if state == States.IDLE or state == States.DONE: + advance.emit() func _on_auto_skip_enable(enabled: bool) -> void: - if not enabled: - return + if not enabled: + return - match state: - States.DONE: - await dialogic.Inputs.start_autoskip_timer() + match state: + States.DONE: + await dialogic.Inputs.start_autoskip_timer() - # If Auto-Skip is still enabled, advance the text. - if dialogic.Inputs.auto_skip.enabled: - advance.emit() + # If Auto-Skip is still enabled, advance the text. + if dialogic.Inputs.auto_skip.enabled: + advance.emit() - States.REVEALING: - dialogic.Text.skip_text_reveal() + States.REVEALING: + dialogic.Text.skip_text_reveal() #endregion @@ -276,12 +276,12 @@ func _on_auto_skip_enable(enabled: bool) -> void: ################################################################################ func _init() -> void: - event_name = "Text" - set_default_color('Color1') - event_category = "Main" - event_sorting_index = 0 - expand_by_default = true - help_page_path = "https://docs.dialogic.pro/writing-texts.html" + event_name = "Text" + set_default_color('Color1') + event_category = "Main" + event_sorting_index = 0 + expand_by_default = true + help_page_path = "https://docs.dialogic.pro/writing-texts.html" @@ -289,92 +289,92 @@ func _init() -> void: ################################################################################ func to_text() -> String: - var result := text.replace('\n', '\\\n').strip_edges(false).trim_suffix("\\") - result = result.replace(':', '\\:') - if result.is_empty(): - result = "" + var result := text.replace('\n', '\\\n').strip_edges(false).trim_suffix("\\") + result = result.replace(':', '\\:') + if result.is_empty(): + result = "" - if character or character_identifier: - var name := character_identifier - if character: - name = character.get_identifier() - if name.count(" ") > 0: - name = '"' + name + '"' - if not portrait.is_empty(): - result = name+" ("+portrait+"): "+result - else: - result = name+": "+result - for event in DialogicResourceUtil.get_event_cache(): - if not event is DialogicTextEvent and event.is_valid_event(result): - result = '\\'+result - break + if character or character_identifier: + var name := character_identifier + if character: + name = character.get_identifier() + if name.count(" ") > 0: + name = '"' + name + '"' + if not portrait.is_empty(): + result = name+" ("+portrait+"): "+result + else: + result = name+": "+result + for event in DialogicResourceUtil.get_event_cache(): + if not event is DialogicTextEvent and event.is_valid_event(result): + result = '\\'+result + break - return result + return result func from_text(string:String) -> void: - # Load default character - # This is only of relevance if the default has been overriden (usually not) - character = DialogicResourceUtil.get_character_resource(character_identifier) + # Load default character + # This is only of relevance if the default has been overriden (usually not) + 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'): - portrait = result.get_string('portrait').strip_edges().trim_prefix('(').trim_suffix(')') + if result.get_string('portrait'): + portrait = result.get_string('portrait').strip_edges().trim_prefix('(').trim_suffix(')') - if result and not result.get_string('name').is_empty(): - var name := result.get_string('name').strip_edges() + if result and not result.get_string('name').is_empty(): + var name := result.get_string('name').strip_edges() - if name == '_': - character = null - elif "{" in name: - ## If it's an expression, we load the character in _execute. - character_identifier = name - character = null - else: - get_or_create_character(name) + if name == '_': + character = null + elif "{" in name: + ## If it's an expression, we load the character in _execute. + character_identifier = name + character = null + else: + get_or_create_character(name) - if not result: - return + if not result: + return - text = result.get_string('text').replace("\\\n", "\n").replace('\\:', ':').strip_edges().trim_prefix('\\') - if text == '': - text = "" + text = result.get_string('text').replace("\\\n", "\n").replace('\\:', ':').strip_edges().trim_prefix('\\') + if text == '': + text = "" func get_or_create_character(name:String) -> void: - character = DialogicResourceUtil.get_character_resource(name) + character = DialogicResourceUtil.get_character_resource(name) - if character == null: - if Engine.is_editor_hint() == false: - character = DialogicCharacter.new() - character.display_name = name - character.set_identifier(name) - if portrait: - if "{" in portrait: - character.color = Color(dialogic.Expressions.execute_string(portrait)) - else: - character.color = Color(portrait) - else: - character_identifier = name + if character == null: + if Engine.is_editor_hint() == false: + character = DialogicCharacter.new() + character.display_name = name + character.set_identifier(name) + if portrait: + if "{" in portrait: + character.color = Color(dialogic.Expressions.execute_string(portrait)) + else: + character.color = Color(portrait) + else: + character_identifier = name func is_valid_event(_string:String) -> bool: - return true + return true 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 way the module manager can add custom default overrides to this event. func get_shortcode_parameters() -> Dictionary: - return { - #param_name : property_info - "character" : {"property": "character_identifier", "default": "", "ext_file":true}, - "portrait" : {"property": "portrait", "default": ""}, - } + return { + #param_name : property_info + "character" : {"property": "character_identifier", "default": "", "ext_file":true}, + "portrait" : {"property": "portrait", "default": ""}, + } #endregion @@ -382,14 +382,14 @@ func get_shortcode_parameters() -> Dictionary: ################################################################################ func _get_translatable_properties() -> Array: - return ['text'] + return ['text'] func _get_property_original_translation(property:String) -> String: - match property: - 'text': - return text - return '' + match property: + 'text': + return text + return '' #endregion @@ -399,44 +399,44 @@ func _get_property_original_translation(property:String) -> String: ################################################################################ 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: - add_header_edit('character_identifier', ValueType.DYNAMIC_OPTIONS, - {'file_extension' : '.dch', - 'mode' : 2, - 'suggestions_func' : get_character_suggestions, - 'placeholder' : '(No one)', - 'icon' : load("res://addons/dialogic/Editor/Images/Resources/character.svg")}, 'do_any_characters_exist()') - add_header_edit('portrait', ValueType.DYNAMIC_OPTIONS, - {'suggestions_func' : get_portrait_suggestions, - 'placeholder' : "(Don't change)", - 'icon' : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg"), - 'collapse_when_empty': true,}, - 'should_show_portrait_selector()') - add_body_edit('text', ValueType.MULTILINE_TEXT, {'autofocus':true}) + add_header_edit('character_identifier', ValueType.DYNAMIC_OPTIONS, + {'file_extension' : '.dch', + 'mode' : 2, + 'suggestions_func' : get_character_suggestions, + 'placeholder' : '(No one)', + 'icon' : load("res://addons/dialogic/Editor/Images/Resources/character.svg")}, 'do_any_characters_exist()') + add_header_edit('portrait', ValueType.DYNAMIC_OPTIONS, + {'suggestions_func' : get_portrait_suggestions, + 'placeholder' : "(Don't change)", + 'icon' : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg"), + 'collapse_when_empty': true,}, + 'should_show_portrait_selector()') + add_body_edit('text', ValueType.MULTILINE_TEXT, {'autofocus':true}) 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: - return not DialogicResourceUtil.get_character_directory().is_empty() + return not DialogicResourceUtil.get_character_directory().is_empty() func get_character_suggestions(search_text:String) -> Dictionary: - var suggestions := DialogicUtil.get_character_suggestions(search_text, character, true, false, editor_node) - if search_text and not search_text in suggestions: - suggestions[search_text] = { - "value":search_text, - "tooltip": "A temporary character, created on the spot.", - "editor_icon":["GuiEllipsis", "EditorIcons"]} - return suggestions + var suggestions := DialogicUtil.get_character_suggestions(search_text, character, true, false, editor_node) + if search_text and not search_text in suggestions: + suggestions[search_text] = { + "value":search_text, + "tooltip": "A temporary character, created on the spot.", + "editor_icon":["GuiEllipsis", "EditorIcons"]} + return suggestions 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 @@ -447,45 +447,45 @@ func get_portrait_suggestions(search_text:String) -> Dictionary: var completion_text_character_getter_regex := RegEx.new() var completion_text_effects := {} 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(): - completion_text_character_getter_regex.compile("(\"[^\"]*\"|[^\\s:]*)") + if completion_text_character_getter_regex.get_pattern().is_empty(): + completion_text_character_getter_regex.compile("(\"[^\"]*\"|[^\\s:]*)") - if completion_text_effects.is_empty(): - for idx in DialogicUtil.get_indexers(): - for effect in idx._get_text_effects(): - completion_text_effects[effect['command']] = effect + if completion_text_effects.is_empty(): + for idx in DialogicUtil.get_indexers(): + for effect in idx._get_text_effects(): + completion_text_effects[effect['command']] = effect - 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('"') - CodeCompletionHelper.suggest_portraits(TextNode, completion_character) + 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('"') + CodeCompletionHelper.suggest_portraits(TextNode, completion_character) - if symbol == '[': - suggest_bbcode(TextNode) - for effect in completion_text_effects.values(): - 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")) - else: - 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 == '[': + suggest_bbcode(TextNode) + for effect in completion_text_effects.values(): + 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")) + else: + 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 == '{': - CodeCompletionHelper.suggest_variables(TextNode) + if symbol == '{': + CodeCompletionHelper.suggest_variables(TextNode) - if symbol == '=': - if CodeCompletionHelper.get_line_untill_caret(line).ends_with('[portrait='): - var completion_character := completion_text_character_getter_regex.search(line).get_string('name') - CodeCompletionHelper.suggest_portraits(TextNode, completion_character, ']') + if symbol == '=': + if CodeCompletionHelper.get_line_untill_caret(line).ends_with('[portrait='): + var completion_character := completion_text_character_getter_regex.search(line).get_string('name') + CodeCompletionHelper.suggest_portraits(TextNode, completion_character, ']') 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): - 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_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+']]: - TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i[0], i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("ArrowRight", "EditorIcons"),) + 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_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+']]: + TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i[0], i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("ArrowRight", "EditorIcons"),) #endregion @@ -496,56 +496,56 @@ func suggest_bbcode(TextNode:CodeEdit): var text_effects := "" var text_effects_regex := RegEx.new() func load_text_effects() -> void: - if text_effects.is_empty(): - for idx in DialogicUtil.get_indexers(): - for effect in idx._get_text_effects(): - 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" - if text_effects_regex.get_pattern().is_empty(): - text_effects_regex.compile("(?"+text_effects+")\\s*(=\\s*(?.+?)\\s*)?\\]") + if text_effects.is_empty(): + for idx in DialogicUtil.get_indexers(): + for effect in idx._get_text_effects(): + 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" + if text_effects_regex.get_pattern().is_empty(): + text_effects_regex.compile("(?"+text_effects+")\\s*(=\\s*(?.+?)\\s*)?\\]") var text_random_word_regex := RegEx.new() var text_effect_color := Color('#898276') func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary: - load_text_effects() - if text_random_word_regex.get_pattern().is_empty(): - text_random_word_regex.compile(r"(?]+(\/[^\>]*)\>") + load_text_effects() + if text_random_word_regex.get_pattern().is_empty(): + text_random_word_regex.compile(r"(?]+(\/[^\>]*)\>") - var result := regex.search(line) - if not result: - return dict - if Highlighter.mode == Highlighter.Modes.FULL_HIGHLIGHTING: - if result.get_string('name'): - dict[result.get_start('name')] = {"color":Highlighter.character_name_color} - dict[result.get_end('name')] = {"color":Highlighter.normal_color} - if result.get_string('portrait'): - dict[result.get_start('portrait')] = {"color":Highlighter.character_portrait_color} - dict[result.get_end('portrait')] = {"color":Highlighter.normal_color} - if result.get_string('text'): + var result := regex.search(line) + if not result: + return dict + if Highlighter.mode == Highlighter.Modes.FULL_HIGHLIGHTING: + if result.get_string('name'): + dict[result.get_start('name')] = {"color":Highlighter.character_name_color} + dict[result.get_end('name')] = {"color":Highlighter.normal_color} + if result.get_string('portrait'): + dict[result.get_start('portrait')] = {"color":Highlighter.character_portrait_color} + dict[result.get_end('portrait')] = {"color":Highlighter.normal_color} + if result.get_string('text'): - ## Color the random selection modifier - for replace_mod_match in text_random_word_regex.search_all(result.get_string('text')): - var color: Color = Highlighter.string_color - color = color.lerp(Highlighter.normal_color, 0.4) - dict[replace_mod_match.get_start()+result.get_start('text')] = {'color':Highlighter.string_color} - var offset := 1 - 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) - dict[replace_mod_match.get_start()+result.get_start('text')+offset] = {'color':color} - offset += len(b.get_string()) - dict[replace_mod_match.get_start()+result.get_start('text')+offset] = {'color':Highlighter.string_color} - offset += 1 - dict[replace_mod_match.get_end()+result.get_start('text')] = {'color':Highlighter.normal_color} + ## Color the random selection modifier + for replace_mod_match in text_random_word_regex.search_all(result.get_string('text')): + var color: Color = Highlighter.string_color + color = color.lerp(Highlighter.normal_color, 0.4) + dict[replace_mod_match.get_start()+result.get_start('text')] = {'color':Highlighter.string_color} + var offset := 1 + 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) + dict[replace_mod_match.get_start()+result.get_start('text')+offset] = {'color':color} + offset += len(b.get_string()) + dict[replace_mod_match.get_start()+result.get_start('text')+offset] = {'color':Highlighter.string_color} + offset += 1 + dict[replace_mod_match.get_end()+result.get_start('text')] = {'color':Highlighter.normal_color} - ## Color bbcode and text effects - var effects_result := text_effects_regex.search_all(line) - for eff in effects_result: - 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_end()] = {"color":prev_color} - dict = Highlighter.color_region(dict, Highlighter.variable_color, line, '{', '}', result.get_start('text')) + ## Color bbcode and text effects + var effects_result := text_effects_regex.search_all(line) + for eff in effects_result: + 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_end()] = {"color":prev_color} + dict = Highlighter.color_region(dict, Highlighter.variable_color, line, '{', '}', result.get_start('text')) - return dict + return dict #endregion diff --git a/common/audio_manager/assets/sfx/signal/signal.mp3 b/common/audio_manager/assets/sfx/signal/signal.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..57d15483597425918aa9ed2e67535a105dee6982 GIT binary patch literal 96966 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

$2g;21 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 C+Dlph literal 0 HcmV?d00001 diff --git a/common/audio_manager/assets/sfx/signal/signal.mp3.import b/common/audio_manager/assets/sfx/signal/signal.mp3.import new file mode 100644 index 0000000..c08acd2 --- /dev/null +++ b/common/audio_manager/assets/sfx/signal/signal.mp3.import @@ -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 diff --git a/common/audio_manager/audio_manager.tscn b/common/audio_manager/audio_manager.tscn index 94f1707..5b59292 100644 --- a/common/audio_manager/audio_manager.tscn +++ b/common/audio_manager/audio_manager.tscn @@ -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://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://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://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"] @@ -36,6 +37,11 @@ stream_0/stream = ExtResource("9_gv65y") stream_1/stream = ExtResource("10_n7o7n") 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"] random_pitch = 1.2 streams_count = 1 @@ -89,6 +95,9 @@ unique_name_in_owner = true [node name="Dig" type="AudioStreamPlayer" parent="Sfx" unique_id=486042600] 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] stream = SubResource("AudioStreamRandomizer_1w04j") diff --git a/common/game_data/scripts/game_data.gd b/common/game_data/scripts/game_data.gd index 6093558..21195a2 100644 --- a/common/game_data/scripts/game_data.gd +++ b/common/game_data/scripts/game_data.gd @@ -17,6 +17,8 @@ signal current_region_data_updated(p : RegionData) @export var tutorial_done = false +@export var incubator_used = [] + @export var dialogs_done : Array[String] = [] #Chemin des dialogues démarrés func start_run(): @@ -49,6 +51,8 @@ func start_tutorial(): 10, 3, tr("TUTORIAL"), - true + true, + 0, + randi() ) ) \ No newline at end of file diff --git a/common/scene_manager/scene_manager.tscn b/common/scene_manager/scene_manager.tscn index 3352622..e22ab7e 100644 --- a/common/scene_manager/scene_manager.tscn +++ b/common/scene_manager/scene_manager.tscn @@ -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://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://b3ebbo88ptrrc" path="res://common/scene_manager/scenes/garage.tres" id="9_msho1"] [node name="SceneManager" type="Node" unique_id=1630600782] 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")]) diff --git a/common/scene_manager/scenes/garage.tres b/common/scene_manager/scenes/garage.tres new file mode 100644 index 0000000..a3ab70c --- /dev/null +++ b/common/scene_manager/scenes/garage.tres @@ -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" diff --git a/dialogs/timelines/gameplay_related/demeter_astra_1.dtl b/dialogs/timelines/gameplay_related/demeter_astra_1.dtl deleted file mode 100644 index b23d247..0000000 --- a/dialogs/timelines/gameplay_related/demeter_astra_1.dtl +++ /dev/null @@ -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"] \ No newline at end of file diff --git a/dialogs/timelines/gameplay_related/demeter_astra_1.dtl.uid b/dialogs/timelines/gameplay_related/demeter_astra_1.dtl.uid deleted file mode 100644 index 552cf24..0000000 --- a/dialogs/timelines/gameplay_related/demeter_astra_1.dtl.uid +++ /dev/null @@ -1 +0,0 @@ -uid://5dlqpnb4o6ai diff --git a/dialogs/timelines/gameplay_related/demeter_astra_2.dtl b/dialogs/timelines/gameplay_related/demeter_astra_2.dtl deleted file mode 100644 index b23d247..0000000 --- a/dialogs/timelines/gameplay_related/demeter_astra_2.dtl +++ /dev/null @@ -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"] \ No newline at end of file diff --git a/dialogs/timelines/gameplay_related/demeter_astra_2.dtl.uid b/dialogs/timelines/gameplay_related/demeter_astra_2.dtl.uid deleted file mode 100644 index 5fbb083..0000000 --- a/dialogs/timelines/gameplay_related/demeter_astra_2.dtl.uid +++ /dev/null @@ -1 +0,0 @@ -uid://du6ytw5yb2ibx diff --git a/dialogs/timelines/gameplay_related/demeter_astra_failed.dtl b/dialogs/timelines/gameplay_related/demeter_astra_failed.dtl new file mode 100644 index 0000000..a5e00de --- /dev/null +++ b/dialogs/timelines/gameplay_related/demeter_astra_failed.dtl @@ -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"] \ No newline at end of file diff --git a/dialogs/timelines/gameplay_related/demeter_astra_failed.dtl.uid b/dialogs/timelines/gameplay_related/demeter_astra_failed.dtl.uid new file mode 100644 index 0000000..260848b --- /dev/null +++ b/dialogs/timelines/gameplay_related/demeter_astra_failed.dtl.uid @@ -0,0 +1 @@ +uid://clq4utdtxf01d diff --git a/dialogs/timelines/story/demeter_post_tutorial.dtl b/dialogs/timelines/story/demeter_post_tutorial.dtl index 4cc5625..7735d9d 100644 --- a/dialogs/timelines/story/demeter_post_tutorial.dtl +++ b/dialogs/timelines/story/demeter_post_tutorial.dtl @@ -1,53 +1,21 @@ 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"] -mysterious_demeter: Well done [color=#FFA617]{orchidName}[/color] ! -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 ! +demeter: So you found the communication station in there, good ! - 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]. - 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]. -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] +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: 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]. - 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. - - The Cetus constellation ? - 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. + 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] - 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: 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 ! - demeter: You are very kind [color=#FFA617]{orchidName}[/color] ! - - Are you hiding me information ? - 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]. -[i][color=#FFA617]Internode's[/color] energy at 50%.[i] -demeter: Oh ![pause=0.2] It is charging faster than I remembered. -- 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. + demeter: You are very kind [color=#FFA617]Orchid[/color] ! + - Are you hiding me informations ? + demeter: Not at all ! Please believe me I just want you to come... +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: 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" [wait time="2.0"] \ No newline at end of file diff --git a/dialogs/timelines/story/demeter_ship_presentation.dtl b/dialogs/timelines/story/demeter_ship_presentation.dtl index b23d247..06d8179 100644 --- a/dialogs/timelines/story/demeter_ship_presentation.dtl +++ b/dialogs/timelines/story/demeter_ship_presentation.dtl @@ -1,5 +1,30 @@ 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 ! +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" [wait time="2.0"] \ No newline at end of file diff --git a/entities/interactable_3d/interactable_3d.gd b/entities/interactable_3d/interactable_3d.gd index 5d959bb..d1460c0 100644 --- a/entities/interactable_3d/interactable_3d.gd +++ b/entities/interactable_3d/interactable_3d.gd @@ -9,7 +9,8 @@ signal clicked @export var audio_player : AudioStreamPlayer3D func click(): - clicked.emit() + if interactable: + clicked.emit() func _ready(): if audio_player: diff --git a/entities/interactables/door/door.tscn b/entities/interactables/door/door.tscn new file mode 100644 index 0000000..f3369f8 --- /dev/null +++ b/entities/interactables/door/door.tscn @@ -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") diff --git a/entities/interactables/door/script/door.gd b/entities/interactables/door/script/door.gd new file mode 100644 index 0000000..62085a7 --- /dev/null +++ b/entities/interactables/door/script/door.gd @@ -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 \ No newline at end of file diff --git a/entities/interactables/door/script/door.gd.uid b/entities/interactables/door/script/door.gd.uid new file mode 100644 index 0000000..3bed163 --- /dev/null +++ b/entities/interactables/door/script/door.gd.uid @@ -0,0 +1 @@ +uid://bmxuqj0c6h60d diff --git a/entities/interactables/scripts/interactable.gd b/entities/interactables/scripts/interactable.gd index c91708a..affb884 100644 --- a/entities/interactables/scripts/interactable.gd +++ b/entities/interactables/scripts/interactable.gd @@ -5,7 +5,7 @@ signal interacted(p: Player) @export var default_interact_text = "" -var available : bool = true +@export var available : bool = true : set = set_available func interact_text() -> String: return default_interact_text @@ -29,3 +29,6 @@ func generate_collision(area_width : float) -> CollisionShape2D: add_child(collision) return collision + +func set_available(v : bool): + available = v \ No newline at end of file diff --git a/entities/player/inventory/scripts/inventory.gd b/entities/player/inventory/scripts/inventory.gd index 549e15d..56d3c76 100644 --- a/entities/player/inventory/scripts/inventory.gd +++ b/entities/player/inventory/scripts/inventory.gd @@ -9,6 +9,8 @@ signal updated(inventory: Inventory) func _init(inventory_size: int = 1): set_size(inventory_size) + add_item(Detector.new()) + add_item(Shovel.new()) func get_n_item_slots() -> int: return items.size() - n_tools diff --git a/entities/player/inventory/scripts/items/detector.gd b/entities/player/inventory/scripts/items/detector.gd new file mode 100644 index 0000000..39ca3b4 --- /dev/null +++ b/entities/player/inventory/scripts/items/detector.gd @@ -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") diff --git a/entities/player/inventory/scripts/items/detector.gd.uid b/entities/player/inventory/scripts/items/detector.gd.uid new file mode 100644 index 0000000..1179fe5 --- /dev/null +++ b/entities/player/inventory/scripts/items/detector.gd.uid @@ -0,0 +1 @@ +uid://cok1wowc6uqmj diff --git a/entities/player/inventory/scripts/items/fork.gd b/entities/player/inventory/scripts/items/fork.gd index cbfa9b9..5f79653 100644 --- a/entities/player/inventory/scripts/items/fork.gd +++ b/entities/player/inventory/scripts/items/fork.gd @@ -12,6 +12,9 @@ func get_description() -> String: func get_icon() -> Texture2D: return preload("res://common/icons/fork.svg") +func get_item_type() -> ItemType: + return Item.ItemType.TOOL_ITEM + func get_energy_used() -> int: return 1 diff --git a/entities/player/inventory/scripts/items/trowel.gd b/entities/player/inventory/scripts/items/trowel.gd index 95957fd..0ae9279 100644 --- a/entities/player/inventory/scripts/items/trowel.gd +++ b/entities/player/inventory/scripts/items/trowel.gd @@ -6,9 +6,6 @@ const TROWEL_ZONE_RADIUS = 50 func get_item_name() -> String: return tr("TROWEL") -func get_item_type() -> ItemType: - return Item.ItemType.TOOL_ITEM - func get_description() -> String: return tr("TROWEL_DESC_TEXT") diff --git a/entities/player/inventory/scripts/items/utils/detector_signal.gd b/entities/player/inventory/scripts/items/utils/detector_signal.gd new file mode 100644 index 0000000..daf407c --- /dev/null +++ b/entities/player/inventory/scripts/items/utils/detector_signal.gd @@ -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 + diff --git a/entities/player/inventory/scripts/items/utils/detector_signal.gd.uid b/entities/player/inventory/scripts/items/utils/detector_signal.gd.uid new file mode 100644 index 0000000..ed2a575 --- /dev/null +++ b/entities/player/inventory/scripts/items/utils/detector_signal.gd.uid @@ -0,0 +1 @@ +uid://c0sivthidxafm diff --git a/gui/game/tutorial/scripts/tutorial.gd b/gui/game/tutorial/scripts/tutorial.gd index f2c0423..39252a3 100644 --- a/gui/game/tutorial/scripts/tutorial.gd +++ b/gui/game/tutorial/scripts/tutorial.gd @@ -8,7 +8,6 @@ var indicators : Array[InGameIndicator] @export var region : Region @onready var steps : Array[Step] = [ - TakeShovelStep.new(), DigSeedStep.new(), TakeSeedStep.new(), PlantSeedStep.new(), diff --git a/gui/pause/scripts/pause.gd b/gui/pause/scripts/pause.gd index 7d45a2e..20a3b82 100644 --- a/gui/pause/scripts/pause.gd +++ b/gui/pause/scripts/pause.gd @@ -49,3 +49,4 @@ func _on_give_up_pressed(): if GameInfo.game_data: SceneManager.change_to_scene_id('ASTRA') GameInfo.game_data.give_up() + pause = false diff --git a/project.godot b/project.godot index d1783b0..fc459e8 100644 --- a/project.godot +++ b/project.godot @@ -27,6 +27,7 @@ buses/default_bus_layout="uid://b4cpfxfs74sb8" [autoload] +PlantTextureBuilder="*uid://b8gqdgabrjaml" Pointer="*res://gui/pointer/pointer.tscn" AudioManager="*res://common/audio_manager/audio_manager.tscn" 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" LoadingScreen="*res://gui/loading_screen/loading_screen.tscn" SceneManager="*res://common/scene_manager/scene_manager.tscn" -PlantTextureBuilder="*uid://b8gqdgabrjaml" [dialogic] @@ -43,8 +43,7 @@ directories/dch_directory={ "mysterious_demeter": "res://dialogs/characters/mysterious_demeter.dch" } directories/dtl_directory={ -"demeter_astra_1": "res://dialogs/timelines/gameplay_related/demeter_astra_1.dtl", -"demeter_astra_2": "res://dialogs/timelines/gameplay_related/demeter_astra_2.dtl", +"demeter_astra_failed": "res://dialogs/timelines/gameplay_related/demeter_astra_failed.dtl", "demeter_intro": "res://dialogs/timelines/story/demeter_intro.dtl", "demeter_midrun": "res://dialogs/timelines/story/demeter_post_tutorial.dtl", "demeter_outro": "res://dialogs/timelines/story/demeter_outro.dtl", diff --git a/stages/3d_scenes/astra_base/astra_base.tscn b/stages/3d_scenes/astra_base/astra_base.tscn index 81abfb6..a8960dd 100644 --- a/stages/3d_scenes/astra_base/astra_base.tscn +++ b/stages/3d_scenes/astra_base/astra_base.tscn @@ -79,12 +79,12 @@ 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"] +[sub_resource type="Sky" id="Sky_kdvug"] sky_material = SubResource("ShaderMaterial_mwti2") [sub_resource type="Environment" id="Environment_lhhy6"] background_mode = 2 -sky = SubResource("Sky_65b6a") +sky = SubResource("Sky_kdvug") sky_custom_fov = 61.7 ambient_light_source = 3 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")] 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")] unique_name_in_owner = true diff --git a/stages/3d_scenes/astra_base/scripts/astra_base.gd b/stages/3d_scenes/astra_base/scripts/astra_base.gd index 4399b2b..ee53b3e 100644 --- a/stages/3d_scenes/astra_base/scripts/astra_base.gd +++ b/stages/3d_scenes/astra_base/scripts/astra_base.gd @@ -2,6 +2,7 @@ extends Node3D 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_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 +var chosen_incubator_id := -1 + # Cheat Code func _input(_e): if ( @@ -32,7 +35,8 @@ func _ready(): Input.mouse_mode = Input.MOUSE_MODE_CAPTURED 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 %Player3D.position = new_player_incubator.global_position + Vector3.UP %Player3D.rotation = new_player_incubator.rotation @@ -42,6 +46,8 @@ func _ready(): %Lift.interactable = true ) + GameInfo.game_data.incubator_used.append(chosen_incubator_id) + story() @@ -55,7 +61,13 @@ func story(): Dialogic.start(INTRO_DIALOG) await Dialogic.timeline_ended - + else: + %Phone.clicked.connect( + func (): + Dialogic.start(FAILED_DIALOG) + %Phone.interactable = false + ) + %LiftAnimationPlayer.play("arrive") await %Lift.clicked %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 incubator_id = 0 for i in range(room_part_number): var new_room_part := ROOM_PART_SCENE.instantiate() as Node3D %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 direction in [-1, 1]: var new_incubator := INCUBATOR_SCENE.instantiate() as Incubator + new_incubator.used = incubator_id in GameInfo.game_data.incubator_used %Incubators.add_child(new_incubator) + incubator_id += 1 new_incubator.position = ( new_room_part.position + j * Vector3.LEFT * (ROOM_PART_SHIFT / INCUBATOR_BY_ROOM) diff --git a/stages/3d_scenes/borea_base/scripts/borea_base.gd b/stages/3d_scenes/borea_base/scripts/borea_base.gd index 8e8b3f6..fa91189 100644 --- a/stages/3d_scenes/borea_base/scripts/borea_base.gd +++ b/stages/3d_scenes/borea_base/scripts/borea_base.gd @@ -8,7 +8,7 @@ func _ready(): Input.mouse_mode = Input.MOUSE_MODE_CAPTURED await %Phone.clicked - Dialogic.start_timeline(OUTRO_TIMELINE_PATH) + Dialogic.start(OUTRO_TIMELINE_PATH) await Dialogic.timeline_ended %Credits.show() diff --git a/stages/3d_scenes/ship_garage/scripts/ship_garage.gd b/stages/3d_scenes/ship_garage/scripts/ship_garage.gd index bf24f4b..f8ee2cc 100644 --- a/stages/3d_scenes/ship_garage/scripts/ship_garage.gd +++ b/stages/3d_scenes/ship_garage/scripts/ship_garage.gd @@ -1,6 +1,13 @@ 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. func _ready(): 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") diff --git a/stages/3d_scenes/ship_garage/ship_garage.tscn b/stages/3d_scenes/ship_garage/ship_garage.tscn index 892c168..16ffca2 100644 --- a/stages/3d_scenes/ship_garage/ship_garage.tscn +++ b/stages/3d_scenes/ship_garage/ship_garage.tscn @@ -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) [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) diff --git a/stages/intro/intro.tscn b/stages/intro/intro.tscn index 0671ad5..e8feaf9 100644 --- a/stages/intro/intro.tscn +++ b/stages/intro/intro.tscn @@ -1,10 +1,58 @@ [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="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] script = ExtResource("1_2nxbv") game_scene_path = "uid://d28cp7a21kwou" -[node name="CanvasLayer" type="CanvasLayer" parent="." unique_id=1051527956] -layer = 100 +[node name="Node3D" type="Node3D" parent="." unique_id=1668131521] + +[node name="Camera3D" type="Camera3D" parent="Node3D" unique_id=2070854508] + +[node name="WorldEnvironment" type="WorldEnvironment" parent="Node3D" unique_id=115692868] +environment = SubResource("Environment_mi20s") diff --git a/stages/terrain/region/region.tscn b/stages/terrain/region/region.tscn index cab6157..6d22dd0 100644 --- a/stages/terrain/region/region.tscn +++ b/stages/terrain/region/region.tscn @@ -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://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://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://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"] [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] 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")] z_index = 1 +position = Vector2(3000, -41) [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")] following = NodePath("../Entities/Player") diff --git a/stages/terrain/region/scripts/region.gd b/stages/terrain/region/scripts/region.gd index 7834be1..ae9aa18 100644 --- a/stages/terrain/region/scripts/region.gd +++ b/stages/terrain/region/scripts/region.gd @@ -9,6 +9,7 @@ const TILE_SET : TileSet = preload("res://stages/terrain/region/resources/moss_b const TILE_SCALE = 1 const TILE_SIZE : int = roundi(TILE_SET.tile_size.x * TILE_SCALE) const START_ROCK_HOLE_RADIUS = 5 +const PLAYER_ROCK_HOLE_RADIUS = 5 const START_DECONTAMINATION_HOLE_RADIUS = 3 const CHUNK_TILE_SIZE : int = 20 const CHUNK_SIZE = CHUNK_TILE_SIZE * TILE_SIZE @@ -69,8 +70,6 @@ func _ready(): if e is Plant: data.add_plant_data(e.data, false) - generate_first_entities() - ground_layer = GroundLayer.new(self) add_child(ground_layer) rock_layer = RockLayer.new(self) @@ -91,11 +90,6 @@ func _process(_d): #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: return "%d:%d" % [coord.x, coord.y] @@ -150,6 +144,21 @@ func edit_map_origin(): rock_layer.remove_rocks(hole_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): generated_chunks.erase(get_chunk_key(chunk.data.chunk_coord)) chunk.unload() @@ -179,6 +188,18 @@ func save(): data.player_position = player.global_position 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 #region ------------------ Usage ------------------ diff --git a/stages/terrain/region/scripts/region_data.gd b/stages/terrain/region/scripts/region_data.gd index ea3f0c4..b6aae68 100644 --- a/stages/terrain/region/scripts/region_data.gd +++ b/stages/terrain/region/scripts/region_data.gd @@ -14,6 +14,7 @@ signal pass_day_ended(region_data : RegionData) const DEFAULT_START_CHARGE := 10 const DEFAULT_OBJECTIVE := 10 +const MAX_RANDOM_SPAWN_DISTANCE = 3000 @export var region_seed : int @export var region_name : String @@ -31,7 +32,7 @@ const DEFAULT_OBJECTIVE := 10 @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 : set(v): @@ -81,7 +82,9 @@ func add_chunk_data(coord : Vector2i, data : ChunkData): chunks_data[get_coord_id(coord)] = data 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: if has_chunk_data(coord): @@ -147,3 +150,13 @@ func _on_plant_disappeared(plant_data : PlantData): update() #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), + ) diff --git a/stages/terrain/region/scripts/tile_map_layers/decontamination_layer.gd b/stages/terrain/region/scripts/tile_map_layers/decontamination_layer.gd index bc56f98..2bff6bd 100644 --- a/stages/terrain/region/scripts/tile_map_layers/decontamination_layer.gd +++ b/stages/terrain/region/scripts/tile_map_layers/decontamination_layer.gd @@ -25,7 +25,7 @@ func place_decontaminations(coords : Array[Vector2i], save := false, on_finished floori(coord.y / float(Region.CHUNK_TILE_SIZE)), ) (region.data - .get_chunk_data(chunk_coord) + .get_or_create_chunk_data(chunk_coord) .update_decontamination_tile_diff(coord, ChunkData.TileDiff.PRESENT)) func is_decontamined(coord : Vector2i) -> bool: diff --git a/stages/terrain/region/scripts/tile_map_layers/rock_layer.gd b/stages/terrain/region/scripts/tile_map_layers/rock_layer.gd index eaf720e..6a131fb 100644 --- a/stages/terrain/region/scripts/tile_map_layers/rock_layer.gd +++ b/stages/terrain/region/scripts/tile_map_layers/rock_layer.gd @@ -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 (region.data - .get_chunk_data(chunk_coord) + .get_or_create_chunk_data(chunk_coord) .update_rock_tile_diff(chunk_tile_coord, ChunkData.TileDiff.ABSENT)) func dig_rocks(coords : Array[Vector2i]) -> bool: diff --git a/translation/game/gui.csv b/translation/game/gui.csv index c8ffa8d..adc36c9 100644 --- a/translation/game/gui.csv +++ b/translation/game/gui.csv @@ -89,6 +89,9 @@ ONE_TIME_USE,Single use,Usage unique BUILD_%s,Build %s,Construit %s FORK,Fork,Fourche 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 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. @@ -115,7 +118,7 @@ SCORE_%d,Score %d,Score %d 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 TRUCK_ENTRANCE,Truck entrance,Entrée du camion -ENTER_TRUCK,Enter truck,Entrer dans le camion +ENTER,Enter,Entrer EXIT,Exit,Sortie EXIT_TRUCK,Exit truck,Sortir du camion 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" SPLASH_ART,Splash Art,Splash Art TRAILER,"Trailer","Trailer" -CREDITS,Credits,Crédits \ No newline at end of file +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 ?" \ No newline at end of file