00_SimpleState.gd 重命名為 04_SimpleState.gd
檔案名稱與重新命名前相同
00_SimpleState.gd(檔案已創建)
| @@ -0,0 +1,67 @@ | |||
| 1 | + | extends Node | |
| 2 | + | class_name _SimpleState | |
| 3 | + | ||
| 4 | + | signal state_changed(name, state) | |
| 5 | + | ||
| 6 | + | var state := { } | |
| 7 | + | ||
| 8 | + | ||
| 9 | + | func register(state_name: String, inital_state: Dictionary = {}) -> void: | |
| 10 | + | state[state_name] = inital_state | |
| 11 | + | ||
| 12 | + | ||
| 13 | + | func replace(state_name: String, new_state: Dictionary) -> void: | |
| 14 | + | state[state_name] = new_state.duplicate(true) | |
| 15 | + | emit_signal("state_changed", state_name, state) | |
| 16 | + | ||
| 17 | + | ||
| 18 | + | func update(state_name: String, new_state: Dictionary) -> void: | |
| 19 | + | state[state_name] = _SimpleState.merge(get_state(state_name), new_state) | |
| 20 | + | emit_signal("state_changed", state_name, state) | |
| 21 | + | ||
| 22 | + | ||
| 23 | + | func update_deep(state_name: String, new_state: Dictionary) -> void: | |
| 24 | + | state[state_name] = _SimpleState.merge_deep(get_state(state_name), new_state) | |
| 25 | + | emit_signal("state_changed", state_name, state) | |
| 26 | + | ||
| 27 | + | ||
| 28 | + | func update_mut(state_name: String, new_state: Dictionary) -> void: | |
| 29 | + | _SimpleState.copy_to(state[state_name], new_state) | |
| 30 | + | emit_signal("state_changed") | |
| 31 | + | ||
| 32 | + | ||
| 33 | + | func _on_update_state(data: Dictionary) -> void: | |
| 34 | + | update(data.state_name, data.state) | |
| 35 | + | ||
| 36 | + | ||
| 37 | + | func get_state(state_name: String) -> Dictionary: | |
| 38 | + | return state[state_name].duplicate() | |
| 39 | + | ||
| 40 | + | ||
| 41 | + | static func copy_to(a: Dictionary, b: Dictionary) -> void: | |
| 42 | + | for key in b: | |
| 43 | + | a[key] = b[key] | |
| 44 | + | ||
| 45 | + | ||
| 46 | + | ||
| 47 | + | static func copy_to_deep(a: Dictionary, b: Dictionary) -> void: | |
| 48 | + | for k in b: | |
| 49 | + | if process_recursively(a, b, k): | |
| 50 | + | copy_to_deep(a[k], b[k]) | |
| 51 | + | else: | |
| 52 | + | a[k] = b[k] | |
| 53 | + | ||
| 54 | + | ||
| 55 | + | static func process_recursively(a, b, k) -> bool: | |
| 56 | + | return a.has(k) and a[k] is Dictionary and b[k] is Dictionary | |
| 57 | + | ||
| 58 | + | static func merge(a: Dictionary, b: Dictionary) -> Dictionary: | |
| 59 | + | var c := a.duplicate(true) | |
| 60 | + | copy_to(c, b) | |
| 61 | + | return c | |
| 62 | + | ||
| 63 | + | ||
| 64 | + | static func merge_deep(a: Dictionary, b: Dictionary) -> Dictionary: | |
| 65 | + | var c := a.duplicate(true) | |
| 66 | + | copy_to_deep(c, b) | |
| 67 | + | return c | |
Main.gd 重命名為 00_Main.gd
檔案名稱與重新命名前相同
EventPlayerContainer.gd 重命名為 01_EventPlayerContainer.gd
檔案名稱與重新命名前相同
EventPlayer.gd 重命名為 02_EventPlayer.gd
檔案名稱與重新命名前相同
CharacterHandler.gd 重命名為 03_CharacterHandler.gd
檔案名稱與重新命名前相同
OptionPresenter.gd 重命名為 03_OptionPresenter.gd
檔案名稱與重新命名前相同
Playback.gd 重命名為 03_Playback.gd
檔案名稱與重新命名前相同
EventPlayerContainer 重命名為 EventPlayerContainer.gd
檔案名稱與重新命名前相同
CharacterHandler.gd(檔案已創建)
| @@ -0,0 +1,104 @@ | |||
| 1 | + | extends Node | |
| 2 | + | ||
| 3 | + | @export var character_layer_path : NodePath | |
| 4 | + | @export var msg_listener_name = "character_handler" | |
| 5 | + | ||
| 6 | + | @onready var character_layer: CanvasLayer = get_node(character_layer_path) | |
| 7 | + | ||
| 8 | + | var _characters = {} | |
| 9 | + | var _last_character := "" | |
| 10 | + | ||
| 11 | + | func enter(who: String, modifiers: Dictionary = {}) -> void: | |
| 12 | + | if modifiers.has("facing") : match modifiers.facing: | |
| 13 | + | "left" : modifiers.facing = Character.FACING_LEFT | |
| 14 | + | "right" : modifiers.facing = Character.FACING_RIGHT | |
| 15 | + | else: | |
| 16 | + | modifiers.facing = Character.FACING_RIGHT | |
| 17 | + | ||
| 18 | + | if modifiers.has("standing") : match modifiers.standing: | |
| 19 | + | "left" : modifiers.standing = Character.STANDING_LEFT | |
| 20 | + | "center": modifiers.standing = Character.STANDING_CENTER | |
| 21 | + | "right" : modifiers.standing = Character.STANDING_RIGHT | |
| 22 | + | else: | |
| 23 | + | modifiers.standing = Character.STANDING_LEFT | |
| 24 | + | ||
| 25 | + | if not modifiers.has("current_emote"): modifiers.current_emote = "default" | |
| 26 | + | ||
| 27 | + | if not who in _characters: | |
| 28 | + | _characters[who] = Characters.items[who].instantiate() | |
| 29 | + | character_layer.add_child(_characters[who]) | |
| 30 | + | ||
| 31 | + | ||
| 32 | + | for modifier in modifiers: | |
| 33 | + | _characters[who][modifier] = modifiers[modifier] | |
| 34 | + | ||
| 35 | + | _last_character = who | |
| 36 | + | ||
| 37 | + | SimpleState.update_deep("sequence", | |
| 38 | + | { characters = { who: modifiers } } | |
| 39 | + | ) | |
| 40 | + | ||
| 41 | + | ||
| 42 | + | func face(face: String, character: String = _last_character) -> void: | |
| 43 | + | assert(face in ["left", "right"] | |
| 44 | + | , "Invalid position %s. Must be 'left' or 'right" % face) | |
| 45 | + | SimpleState.update_deep("sequence", | |
| 46 | + | { characters = { character: { facing = face } } } | |
| 47 | + | ) | |
| 48 | + | if character != _last_character: _last_character = character | |
| 49 | + | ||
| 50 | + | var facing: int = Character.STANDING_LEFT | |
| 51 | + | match face: | |
| 52 | + | "left" : facing = Character.FACING_LEFT | |
| 53 | + | "right" : facing = Character.FACING_RIGHT | |
| 54 | + | ||
| 55 | + | update(character, "facing", facing) | |
| 56 | + | ||
| 57 | + | ||
| 58 | + | func stand(stand: String, character: String = _last_character) -> void: | |
| 59 | + | assert(stand in ["left", "center", "right"] | |
| 60 | + | , "Invalid position %s. Must be 'left' or 'center' or 'right" % stand) | |
| 61 | + | ||
| 62 | + | SimpleState.update_deep("sequence", | |
| 63 | + | { characters = { character: { standing = stand } } } | |
| 64 | + | ) | |
| 65 | + | if character != _last_character: _last_character = character | |
| 66 | + | ||
| 67 | + | var standing: int = Character.STANDING_LEFT | |
| 68 | + | match stand: | |
| 69 | + | "left" : standing = Character.STANDING_LEFT | |
| 70 | + | "center": standing = Character.STANDING_CENTER | |
| 71 | + | "right" : standing = Character.STANDING_RIGHT | |
| 72 | + | ||
| 73 | + | update(character, "standing", standing) | |
| 74 | + | ||
| 75 | + | ||
| 76 | + | func emote(emote: String, character: String = _last_character) -> void: | |
| 77 | + | SimpleState.update_deep("sequence", | |
| 78 | + | { characters = { character: { current_emote = emote } } } | |
| 79 | + | ) | |
| 80 | + | if character != _last_character: _last_character = character | |
| 81 | + | ||
| 82 | + | update(character, "current_emote", emote) | |
| 83 | + | ||
| 84 | + | ||
| 85 | + | func update(who: String, what: String, with) -> void: | |
| 86 | + | _characters[who][what] = with | |
| 87 | + | _last_character = who | |
| 88 | + | ||
| 89 | + | func leave(who: String) -> void: | |
| 90 | + | var characters: Dictionary = SimpleState.state.sequence.characters.duplicate() | |
| 91 | + | characters.erase(who) # warning-ignore:return_value_discarded | |
| 92 | + | SimpleState.update("sequence", | |
| 93 | + | { characters = characters } | |
| 94 | + | ) | |
| 95 | + | ||
| 96 | + | if who in _characters: | |
| 97 | + | character_layer.remove_child(_characters[who]) | |
| 98 | + | _characters[who].queue_free() | |
| 99 | + | _characters.erase(who) | |
| 100 | + | ||
| 101 | + | ||
| 102 | + | func everyone_leave() -> void: | |
| 103 | + | for who in _characters: | |
| 104 | + | character_layer.remove_child(_characters[who]) | |
EventPlayer.gd(檔案已創建)
| @@ -0,0 +1,198 @@ | |||
| 1 | + | extends Control | |
| 2 | + | class_name EventPlayer | |
| 3 | + | ||
| 4 | + | # Signal | |
| 5 | + | signal fade_in_started() | |
| 6 | + | signal fade_out_started() | |
| 7 | + | signal scene_ended() | |
| 8 | + | ||
| 9 | + | # Proxied Signals | |
| 10 | + | signal minigame_started(minigame : String, initial_state : Dictionary) | |
| 11 | + | signal sequence_changed(sequence : String) | |
| 12 | + | ||
| 13 | + | # Signals to Yield on | |
| 14 | + | signal fade_finished() # warning-ignore:unused_signal | |
| 15 | + | signal minigame_finished() # warning-ignore:unused_signal | |
| 16 | + | signal change_scene_finished() # warning-ignore:unused_signal | |
| 17 | + | ||
| 18 | + | enum NextFade { FADE_OUT, FADE_IN } | |
| 19 | + | var fade_active = false | |
| 20 | + | var next_fade : NextFade = NextFade.FADE_OUT | |
| 21 | + | ||
| 22 | + | @onready var playback : Playback = $Playback | |
| 23 | + | @onready var dialogue_presenter = $DialoguePresenter | |
| 24 | + | @onready var background_presenter = $BackgroundPresenter | |
| 25 | + | @onready var character_handler = $CharacterHandler | |
| 26 | + | @onready var blackout_panel = $BlackoutPanel | |
| 27 | + | @onready var dialogue_box = $DialogueBox | |
| 28 | + | @onready var prize_screen = $PrizeScreen | |
| 29 | + | @onready var choice_panel = get_tree().current_scene.get_node("%MainLayout/%ChoicePanel") | |
| 30 | + | ||
| 31 | + | func _ready() -> void: | |
| 32 | + | playback.line_changed.connect(_on_line_changed) | |
| 33 | + | playback.dialogue_shown.connect(_on_dialogue_shown) | |
| 34 | + | playback.options_shown.connect(_on_options_shown) | |
| 35 | + | playback.dialogue_cleared.connect(_on_dialogue_cleared) | |
| 36 | + | playback.scene_finished.connect(_on_scene_finished) | |
| 37 | + | choice_panel.option_picked.connect(_on_option_picked) | |
| 38 | + | ||
| 39 | + | playback.extra_game_states = [ self | |
| 40 | + | , dialogue_presenter | |
| 41 | + | , background_presenter | |
| 42 | + | , character_handler | |
| 43 | + | , dialogue_box | |
| 44 | + | , prize_screen | |
| 45 | + | ] | |
| 46 | + | ||
| 47 | + | ||
| 48 | + | # Callbacks | |
| 49 | + | func _on_line_changed(line_id: String) -> void: | |
| 50 | + | SimpleState.update("sequence", | |
| 51 | + | { current_line_id = line_id }) | |
| 52 | + | ||
| 53 | + | ||
| 54 | + | func _on_dialogue_shown(dialogue_line : DialogueLine) -> void: | |
| 55 | + | if get_parent()._changing_scenes: | |
| 56 | + | await self.change_scene_finished | |
| 57 | + | ||
| 58 | + | await dialogue_presenter.show_dialogue(dialogue_line) | |
| 59 | + | playback.dialogue_finished.emit() | |
| 60 | + | ||
| 61 | + | func _on_options_shown(options: Array[String]) -> void: | |
| 62 | + | if get_parent()._changing_scenes: | |
| 63 | + | await self.change_scene_finished | |
| 64 | + | ||
| 65 | + | choice_panel.show_options(options) | |
| 66 | + | ||
| 67 | + | ||
| 68 | + | func _on_dialogue_cleared() -> void: | |
| 69 | + | dialogue_presenter.clear_dialogue() | |
| 70 | + | ||
| 71 | + | ||
| 72 | + | func _on_scene_finished() -> void: | |
| 73 | + | emit_signal("scene_ended") | |
| 74 | + | ||
| 75 | + | ||
| 76 | + | func _on_options_cleared() -> void: | |
| 77 | + | choice_panel.clear_options() | |
| 78 | + | ||
| 79 | + | ||
| 80 | + | func _on_option_picked(__, line_id) -> void: | |
| 81 | + | playback.emit_signal("options_picked", line_id) | |
| 82 | + | ||
| 83 | + | ||
| 84 | + | # Public Methods | |
| 85 | + | func run(sequence: String, entry: String) -> void: | |
| 86 | + | playback.run(Events.items[sequence], entry) | |
| 87 | + | ||
| 88 | + | ||
| 89 | + | func restore(sequence: String, entry: String) -> void: | |
| 90 | + | var dialogue_pos = SimpleState.state.sequence.dialogue_pos | |
| 91 | + | var background = SimpleState.state.sequence.background | |
| 92 | + | var blackout_visible = SimpleState.state.sequence.blackout_visible | |
| 93 | + | var characters = SimpleState.state.sequence.characters | |
| 94 | + | ||
| 95 | + | next_fade = SimpleState.state.sequence.next_fade | |
| 96 | + | ||
| 97 | + | dialogue_box.call(dialogue_pos) | |
| 98 | + | background_presenter.background(background) | |
| 99 | + | ||
| 100 | + | for character in characters: | |
| 101 | + | character_handler.enter(character, characters[character]) | |
| 102 | + | ||
| 103 | + | choice_panel.clear_options() | |
| 104 | + | blackout(blackout_visible) | |
| 105 | + | playback.run(Events.items[sequence], entry, true) | |
| 106 | + | ||
| 107 | + | ||
| 108 | + | func goto(sequence: String) -> void: | |
| 109 | + | sequence_changed.emit(sequence) | |
| 110 | + | await change_scene_finished | |
| 111 | + | ||
| 112 | + | ||
| 113 | + | func visit_random_minigame() -> void: | |
| 114 | + | var seen = MemoryState.get_seen_minigames() | |
| 115 | + | var unseen = ArrayUtil.difference(CarnivalGames.items.keys(), seen) | |
| 116 | + | var key = ArrayUtil.either(unseen) | |
| 117 | + | var sequence_name = to_squence_name(key) | |
| 118 | + | ||
| 119 | + | sequence_changed.emit("minigames/%s/init" % sequence_name) | |
| 120 | + | ||
| 121 | + | ||
| 122 | + | func to_squence_name(key) -> String: | |
| 123 | + | return key \ | |
| 124 | + | .capitalize() \ | |
| 125 | + | .replace(" ", "-") \ | |
| 126 | + | .to_lower() | |
| 127 | + | ||
| 128 | + | ||
| 129 | + | func exit() -> void: | |
| 130 | + | await fade_out() | |
| 131 | + | ||
| 132 | + | ||
| 133 | + | func blackout(visibility: bool) -> void: | |
| 134 | + | SimpleState.update("sequence", { blackout_visible = visibility }) | |
| 135 | + | blackout_panel.visible = visibility | |
| 136 | + | ||
| 137 | + | ||
| 138 | + | func swap_background(layers: Array) -> void: | |
| 139 | + | await fade_out() | |
| 140 | + | await get_tree().process_frame | |
| 141 | + | background_presenter.background(layers) | |
| 142 | + | await fade_in() | |
| 143 | + | ||
| 144 | + | ||
| 145 | + | func unimplemented() -> void: | |
| 146 | + | assert(false, "Unimplemented Scene") | |
| 147 | + | ||
| 148 | + | ||
| 149 | + | func play_minigame(minigame: String, initial_state: Dictionary = {}) -> void: | |
| 150 | + | minigame_started.emit(minigame, initial_state) | |
| 151 | + | var result = await self.minigame_finished | |
| 152 | + | SimpleState.update_deep("temp", { values = result }) | |
| 153 | + | ||
| 154 | + | ||
| 155 | + | ||
| 156 | + | func fade() -> void: | |
| 157 | + | if fade_active: return | |
| 158 | + | ||
| 159 | + | fade_active = true | |
| 160 | + | ||
| 161 | + | match next_fade: | |
| 162 | + | NextFade.FADE_OUT: | |
| 163 | + | next_fade = NextFade.FADE_IN | |
| 164 | + | await fade_out() | |
| 165 | + | ||
| 166 | + | NextFade.FADE_IN: | |
| 167 | + | next_fade = NextFade.FADE_OUT | |
| 168 | + | await fade_in() | |
| 169 | + | ||
| 170 | + | SimpleState.update("sequence", { next_fade = next_fade }) | |
| 171 | + | fade_active = false | |
| 172 | + | ||
| 173 | + | ||
| 174 | + | ||
| 175 | + | func fade_out() -> void: | |
| 176 | + | fade_out_started.emit() | |
| 177 | + | await self.fade_finished | |
| 178 | + | ||
| 179 | + | ||
| 180 | + | func fade_in() -> void: | |
| 181 | + | fade_in_started.emit() | |
| 182 | + | await self.fade_finished | |
| 183 | + | ||
| 184 | + | ||
| 185 | + | func set_input_disabled(value: bool) -> void: | |
| 186 | + | $Background.input_disabled = value | |
| 187 | + | $Background/SubViewport.gui_disable_input = value | |
| 188 | + | ||
| 189 | + | ||
| 190 | + | func goto_birth_scene() -> void: | |
| 191 | + | var scene = TempState["parasite"].birthing_scene | |
| 192 | + | ExploreState.emit_signal("event_triggered", scene) | |
| 193 | + | ||
| 194 | + | ||
| 195 | + | func interaction(background: String) -> void: | |
| 196 | + | set_input_disabled(false) | |
| 197 | + | await background_presenter.start_interaction(background) | |
| 198 | + | set_input_disabled(true) | |
EventPlayerContainer(檔案已創建)
| @@ -0,0 +1,169 @@ | |||
| 1 | + | extends Control | |
| 2 | + | class_name EventPlayerContainer | |
| 3 | + | ||
| 4 | + | signal scene_change_finished() | |
| 5 | + | signal sequence_changed(sequence) # warning-ignore:unused_signal | |
| 6 | + | signal options_shown(options) | |
| 7 | + | signal scene_change_started() | |
| 8 | + | signal minigame_started(minigame, initial_state) | |
| 9 | + | ||
| 10 | + | const EventPlayerScene = preload("res://scenes/ui/EventPlayer.tscn") | |
| 11 | + | ||
| 12 | + | var allow_fade_skip := false | |
| 13 | + | ||
| 14 | + | var _event_player_scene : EventPlayer = null | |
| 15 | + | var _changing_scenes := false | |
| 16 | + | ||
| 17 | + | func _ready() -> void: | |
| 18 | + | set_input_disabled(true) | |
| 19 | + | SimpleState.state_changed.connect(_on_state_changed) # warning-ignore:return_value_discarded | |
| 20 | + | ||
| 21 | + | ||
| 22 | + | func _on_state_changed(_name: String, _state: Dictionary) -> void: | |
| 23 | + | pass | |
| 24 | + | ||
| 25 | + | ||
| 26 | + | func _input(event) -> void: | |
| 27 | + | var mouse_down = event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT | |
| 28 | + | var ui_accept = event.is_action_pressed("ui_accept") and not event.is_echo() | |
| 29 | + | if (mouse_down or ui_accept) and allow_fade_skip: | |
| 30 | + | $FadePanel.skip() | |
| 31 | + | ||
| 32 | + | ||
| 33 | + | func connect_events(scene) -> void: | |
| 34 | + | # Handled Events | |
| 35 | + | scene.fade_in_started.connect(_on_fade_in_started) | |
| 36 | + | scene.fade_out_started.connect(_on_fade_out_started) | |
| 37 | + | scene.scene_ended.connect(_on_scene_ended) | |
| 38 | + | scene.sequence_changed.connect(_on_sequence_changed) | |
| 39 | + | ||
| 40 | + | # Bubbled Events | |
| 41 | + | scene.minigame_started.connect(_on_minigame_started) | |
| 42 | + | ||
| 43 | + | ||
| 44 | + | func add_to_tree(scene : EventPlayer) -> void: | |
| 45 | + | add_child(scene) | |
| 46 | + | move_child(scene, 0) | |
| 47 | + | ||
| 48 | + | ||
| 49 | + | func run_scene(scene : EventPlayer, sequence : String, entry : String) -> void: | |
| 50 | + | MemoryState.seen_passage(sequence) | |
| 51 | + | scene.run(sequence, entry) | |
| 52 | + | ||
| 53 | + | ||
| 54 | + | func restore_scene(scene : EventPlayer, sequence : String, entry : String) -> void: | |
| 55 | + | scene.restore(sequence, entry) | |
| 56 | + | ||
| 57 | + | ||
| 58 | + | func run(sequence: String, entry: String = "intro") -> void: | |
| 59 | + | _event_player_scene = EventPlayerScene.instantiate() | |
| 60 | + | SimpleState.update("sequence", { current_sequence = sequence, current_line_id = entry }) | |
| 61 | + | ||
| 62 | + | show() | |
| 63 | + | connect_events(_event_player_scene) | |
| 64 | + | add_to_tree(_event_player_scene) | |
| 65 | + | run_scene(_event_player_scene, sequence, entry) | |
| 66 | + | ||
| 67 | + | ||
| 68 | + | func restore_from_state() -> void: | |
| 69 | + | var sequence = SimpleState.state.sequence.current_sequence | |
| 70 | + | var entry = SimpleState.state.sequence.current_line_id | |
| 71 | + | _event_player_scene = EventPlayerScene.instantiate() | |
| 72 | + | ||
| 73 | + | show() | |
| 74 | + | connect_events(_event_player_scene) | |
| 75 | + | add_to_tree(_event_player_scene) | |
| 76 | + | restore_scene(_event_player_scene, sequence, entry) | |
| 77 | + | ||
| 78 | + | ||
| 79 | + | func minigame_finished(result) -> void: | |
| 80 | + | if is_instance_valid(_event_player_scene): | |
| 81 | + | _event_player_scene.emit_signal("minigame_finished", result) | |
| 82 | + | ||
| 83 | + | ||
| 84 | + | func _on_fade_out_started() -> void: | |
| 85 | + | allow_fade_skip = true | |
| 86 | + | if not $FadePanel.fade_shown: | |
| 87 | + | await $FadePanel.fade_out() | |
| 88 | + | ||
| 89 | + | if is_instance_valid(_event_player_scene): | |
| 90 | + | _event_player_scene.emit_signal("fade_finished") | |
| 91 | + | else: | |
| 92 | + | _on_fade_out_started() | |
| 93 | + | ||
| 94 | + | ||
| 95 | + | func _on_fade_in_started() -> void: | |
| 96 | + | if $FadePanel.fade_shown: | |
| 97 | + | await $FadePanel.fade_in() | |
| 98 | + | ||
| 99 | + | ||
| 100 | + | allow_fade_skip = false | |
| 101 | + | if is_instance_valid(_event_player_scene): | |
| 102 | + | _event_player_scene.emit_signal("fade_finished") | |
| 103 | + | ||
| 104 | + | ||
| 105 | + | func _on_scene_ended() -> void: | |
| 106 | + | if not _changing_scenes: | |
| 107 | + | stop() | |
| 108 | + | ||
| 109 | + | ||
| 110 | + | # Bubble Up Events | |
| 111 | + | func _on_minigame_started(minigame: String, initial_state: Dictionary) -> void: | |
| 112 | + | emit_signal("minigame_started", minigame, initial_state) | |
| 113 | + | ||
| 114 | + | ||
| 115 | + | func _on_sequence_changed(scene: String, line: String = "intro") -> void: | |
| 116 | + | emit_signal("scene_change_started") | |
| 117 | + | _changing_scenes = true | |
| 118 | + | ||
| 119 | + | if not $FadePanel.fade_shown: | |
| 120 | + | await $FadePanel.fade_out() | |
| 121 | + | ||
| 122 | + | if is_instance_valid(_event_player_scene): | |
| 123 | + | if _event_player_scene.get_parent(): | |
| 124 | + | _event_player_scene.get_parent().remove_child(_event_player_scene) | |
| 125 | + | _event_player_scene.queue_free() | |
| 126 | + | ||
| 127 | + | run(scene, line) | |
| 128 | + | ||
| 129 | + | if $FadePanel.fade_shown: | |
| 130 | + | await $FadePanel.fade_in() | |
| 131 | + | ||
| 132 | + | _changing_scenes = false | |
| 133 | + | _event_player_scene.emit_signal("change_scene_finished") | |
| 134 | + | emit_signal("scene_change_finished") | |
| 135 | + | ||
| 136 | + | ||
| 137 | + | func _options_shown(options: Array) -> void: | |
| 138 | + | emit_signal("options_shown", options) | |
| 139 | + | ||
| 140 | + | ||
| 141 | + | func stop() -> void: | |
| 142 | + | SimpleState.update("sequence", { current_sequence = "NONE" | |
| 143 | + | , current_line_id = "" | |
| 144 | + | , background = [] | |
| 145 | + | , dialogue_pos = "left" | |
| 146 | + | , blackout_visible = false | |
| 147 | + | , dialogue_visible = false | |
| 148 | + | , characters = {} | |
| 149 | + | }) | |
| 150 | + | await $FadePanel.fade_out() | |
| 151 | + | ||
| 152 | + | if is_instance_valid(_event_player_scene): | |
| 153 | + | if _event_player_scene and _event_player_scene.get_parent(): | |
| 154 | + | _event_player_scene.get_parent().remove_child(_event_player_scene) | |
| 155 | + | _event_player_scene.queue_free() | |
| 156 | + | ||
| 157 | + | await $FadePanel.fade_in() | |
| 158 | + | hide() | |
| 159 | + | ||
| 160 | + | func hard_stop() -> void: | |
| 161 | + | if is_instance_valid(_event_player_scene): | |
| 162 | + | if _event_player_scene and _event_player_scene.get_parent(): | |
| 163 | + | _event_player_scene.get_parent().remove_child(_event_player_scene) | |
| 164 | + | _event_player_scene.queue_free() | |
| 165 | + | ||
| 166 | + | ||
| 167 | + | func set_input_disabled(value: bool) -> void: | |
| 168 | + | if is_instance_valid(_event_player_scene): | |
| 169 | + | _event_player_scene.set_input_disabled(value) | |
main.gd 重命名為 Main.gd
檔案名稱與重新命名前相同
OptionPresenter.gd(檔案已創建)
| @@ -0,0 +1,55 @@ | |||
| 1 | + | extends Node | |
| 2 | + | class_name Choice | |
| 3 | + | const ButtonBase = preload("res://scenes/ui/misc/ButtonBase.tscn") | |
| 4 | + | ||
| 5 | + | signal options_shown() | |
| 6 | + | signal options_cleared() | |
| 7 | + | signal option_picked(index, line_id) | |
| 8 | + | ||
| 9 | + | @export var option_path : NodePath | |
| 10 | + | @export var msg_listener_name = "option_presenter" | |
| 11 | + | ||
| 12 | + | var active := false | |
| 13 | + | ||
| 14 | + | @onready var options: VBoxContainer = get_node(option_path) | |
| 15 | + | ||
| 16 | + | ||
| 17 | + | func show_options(option_list: Array) -> void: | |
| 18 | + | assert(len(option_list) % 2 == 0, "Options must be multiples of 2") | |
| 19 | + | ||
| 20 | + | var count = min(6, len(option_list)) | |
| 21 | + | var buttons = [] | |
| 22 | + | for i in range(0, count, 2): | |
| 23 | + | buttons.append(_add_option(option_list[i], option_list[i + 1])) | |
| 24 | + | ||
| 25 | + | (buttons[0] as Button).grab_focus() | |
| 26 | + | for i in range(1, len(buttons)): | |
| 27 | + | var top: Button = buttons[i - 1] | |
| 28 | + | var bottom: Button = buttons[i] | |
| 29 | + | bottom.focus_neighbor_top = top.get_path() | |
| 30 | + | top.focus_neighbor_bottom = bottom.get_path() | |
| 31 | + | ||
| 32 | + | active = true | |
| 33 | + | emit_signal("options_shown") | |
| 34 | + | ||
| 35 | + | ||
| 36 | + | func clear_options() -> void: | |
| 37 | + | for child in options.get_children(): | |
| 38 | + | options.remove_child(child) | |
| 39 | + | child.queue_free() | |
| 40 | + | ||
| 41 | + | active = false | |
| 42 | + | emit_signal("options_cleared") | |
| 43 | + | ||
| 44 | + | ||
| 45 | + | func _add_option(text: String, line_id: String) -> Button: | |
| 46 | + | var button = ButtonBase.instantiate() | |
| 47 | + | button.focus_mode = Control.FOCUS_ALL | |
| 48 | + | button.text = text | |
| 49 | + | button.connect("pressed", Callable(self, "_on_button_pressed").bind(options.get_child_count(), line_id)) | |
| 50 | + | options.add_child(button) | |
| 51 | + | return button | |
| 52 | + | ||
| 53 | + | func _on_button_pressed(index: int, line: String) -> void: | |
| 54 | + | clear_options() | |
| 55 | + | emit_signal("option_picked", index, line) | |
Playback.gd(檔案已創建)
| @@ -0,0 +1,46 @@ | |||
| 1 | + | extends Node | |
| 2 | + | class_name Playback | |
| 3 | + | ||
| 4 | + | signal line_changed(line : String) | |
| 5 | + | signal dialogue_shown(dialogue : DialogueLine) | |
| 6 | + | signal options_shown(options : Array[String]) | |
| 7 | + | signal dialogue_cleared | |
| 8 | + | signal scene_finished | |
| 9 | + | ||
| 10 | + | signal options_picked(line_id : String) | |
| 11 | + | signal dialogue_finished # warning-ignore:unused_signal | |
| 12 | + | ||
| 13 | + | var dialogue_line : DialogueLine | |
| 14 | + | var extra_game_states : Array | |
| 15 | + | ||
| 16 | + | func run(script : DialogueResource, entry: String = "intro", _restore : bool = false) -> void: | |
| 17 | + | dialogue_line = await DialogueManager.get_next_dialogue_line(script, entry, extra_game_states) | |
| 18 | + | ||
| 19 | + | line_changed.emit(entry) | |
| 20 | + | while dialogue_line: | |
| 21 | + | var next_id = dialogue_line.next_id | |
| 22 | + | ||
| 23 | + | if not dialogue_line.text.is_empty(): | |
| 24 | + | dialogue_shown.emit(dialogue_line) | |
| 25 | + | await self.dialogue_finished | |
| 26 | + | ||
| 27 | + | if len(dialogue_line.responses) > 0: | |
| 28 | + | var options : Array[String] = [] | |
| 29 | + | ||
| 30 | + | for response in dialogue_line.responses: | |
| 31 | + | options.append(response.text) | |
| 32 | + | options.append(response.next_id) | |
| 33 | + | ||
| 34 | + | options_shown.emit(options) | |
| 35 | + | next_id = await self.options_picked | |
| 36 | + | ||
| 37 | + | else: | |
| 38 | + | var default : Array[String] = [">> Next", ""] | |
| 39 | + | options_shown.emit(default) | |
| 40 | + | await self.options_picked | |
| 41 | + | ||
| 42 | + | dialogue_cleared.emit() | |
| 43 | + | line_changed.emit(next_id) | |
| 44 | + | dialogue_line = await DialogueManager.get_next_dialogue_line(script, next_id, extra_game_states) | |
| 45 | + | ||
| 46 | + | scene_finished.emit() | |
main.gd(檔案已創建)
| @@ -0,0 +1,179 @@ | |||
| 1 | + | # warning-ignore-all:return_value_discarded | |
| 2 | + | extends HBoxContainer | |
| 3 | + | class_name ChitinousCanrival | |
| 4 | + | ||
| 5 | + | const event_player_scene = preload("res://scenes/ui/EventPlayer.tscn") | |
| 6 | + | ||
| 7 | + | @onready var MinigameHandler := %MinigameHandler as MinigameHandler | |
| 8 | + | @onready var TravelButtons := %TravelButtons as TravelButtons | |
| 9 | + | @onready var EventPlayerContainer := %EventPlayerContainer as EventPlayerContainer | |
| 10 | + | @onready var InventoryMenu := %InventoryMenu as InventoryMenu | |
| 11 | + | @onready var SettingsMenu := %SettingsMenu as SettingsMenu | |
| 12 | + | @onready var SaveMenu := %SaveMenu as SaveMenu | |
| 13 | + | @onready var ChoicePanel := %ChoicePanel as Choice | |
| 14 | + | @onready var StatusPanel := %StatusPanel as StatusPanel | |
| 15 | + | @onready var Blackout := %BlackoutPanel as BlackoutPanel | |
| 16 | + | ||
| 17 | + | var _current_sequence: String = "" | |
| 18 | + | ||
| 19 | + | func _ready() -> void: | |
| 20 | + | randomize() | |
| 21 | + | ||
| 22 | + | MusicState.change_bg(MusicState.BACKGROUND_THEME) | |
| 23 | + | ||
| 24 | + | ExploreState.set_start_time() | |
| 25 | + | ExploreState._set_location(SimpleState.state.explore.location) | |
| 26 | + | TravelButtons.set_button_disabled(SimpleState.state.explore.location) | |
| 27 | + | ||
| 28 | + | SaveState.save_loaded.connect(_on_save_loaded) | |
| 29 | + | EventPlayerContainer.minigame_started.connect(_on_minigame_started) | |
| 30 | + | MinigameHandler.minigame_finished.connect(_on_minigame_finished) | |
| 31 | + | ||
| 32 | + | StatusPanel.inventory_button_toggled.connect(_on_inventory_button_toggled) | |
| 33 | + | StatusPanel.settings_button_toggled.connect(_on_settings_button_toggled) | |
| 34 | + | StatusPanel.save_button_toggled.connect(_on_save_button_toggled) | |
| 35 | + | ||
| 36 | + | InventoryMenu.visibility_changed.connect(_on_menu_visibility_changed) | |
| 37 | + | ||
| 38 | + | SaveMenu.visibility_changed.connect(_on_menu_visibility_changed) | |
| 39 | + | SaveMenu.closed.connect(StatusPanel.saving_closed) | |
| 40 | + | ||
| 41 | + | SettingsMenu.visibility_changed.connect(_on_menu_visibility_changed) | |
| 42 | + | SettingsMenu.closed.connect(StatusPanel.settings_closed) | |
| 43 | + | ||
| 44 | + | TravelButtons.waited.connect(_on_wait_button_pressed) | |
| 45 | + | ||
| 46 | + | ChoicePanel.options_shown.connect(_on_options_shown) | |
| 47 | + | ChoicePanel.options_cleared.connect(_on_options_cleared) | |
| 48 | + | ||
| 49 | + | ExploreState.trap_triggered.connect(_on_trap_triggered) | |
| 50 | + | ExploreState.event_triggered.connect(_on_event_triggered) | |
| 51 | + | ExploreState.event_trigger_forced.connect(_on_event_trigger_forced) | |
| 52 | + | ExploreState.revisited.connect(_on_revisited) | |
| 53 | + | ExploreState.wandered.connect(_on_wandered) | |
| 54 | + | ExploreState.item_selected.connect(_on_item_selected) | |
| 55 | + | ExploreState.masturbated.connect(_on_masturbated) | |
| 56 | + | ||
| 57 | + | if not MemoryState.has_seen("00-intro/00-premise"): | |
| 58 | + | InventoryState.add_trap("intro") | |
| 59 | + | EventPlayerContainer.run("00-intro/00-premise") | |
| 60 | + | ||
| 61 | + | if not SimpleState.state.sequence.current_sequence != "None": | |
| 62 | + | EventPlayerContainer.restore_from_state() | |
| 63 | + | ||
| 64 | + | await Blackout.fade_in() | |
| 65 | + | ||
| 66 | + | ||
| 67 | + | func _on_revisited(event: String) -> void: | |
| 68 | + | EventPlayerContainer.run(event) | |
| 69 | + | ||
| 70 | + | ||
| 71 | + | func _on_wandered(where: String) -> void: | |
| 72 | + | EventPlayerContainer.run("01-location/%s/wander" % where) | |
| 73 | + | ||
| 74 | + | ||
| 75 | + | func _on_item_selected(what: String) -> void: | |
| 76 | + | StatusPanel.inventory_closed() | |
| 77 | + | EventPlayerContainer.run("items/%s" % what) | |
| 78 | + | ||
| 79 | + | func _on_save_loaded(title) -> void: | |
| 80 | + | if not title: | |
| 81 | + | await Blackout.fade_out() | |
| 82 | + | ||
| 83 | + | EventPlayerContainer.hard_stop() | |
| 84 | + | ChoicePanel.clear_options() | |
| 85 | + | ||
| 86 | + | if SimpleState.state.sequence.current_sequence != "NONE": | |
| 87 | + | EventPlayerContainer.restore_from_state() | |
| 88 | + | else: | |
| 89 | + | EventPlayerContainer.hide() | |
| 90 | + | ||
| 91 | + | await Blackout.fade_in() | |
| 92 | + | ||
| 93 | + | ||
| 94 | + | func _on_minigame_started(minigame: String, initial_state: Dictionary) -> void: | |
| 95 | + | MinigameHandler.play_minigame(minigame, initial_state) | |
| 96 | + | ||
| 97 | + | ||
| 98 | + | func _on_minigame_finished(result) -> void: | |
| 99 | + | EventPlayerContainer.minigame_finished(result) | |
| 100 | + | ||
| 101 | + | ||
| 102 | + | func _on_inventory_button_toggled(pressed: bool) -> void: | |
| 103 | + | ||
| 104 | + | InventoryMenu.visible = pressed | |
| 105 | + | ||
| 106 | + | if pressed: | |
| 107 | + | SettingsMenu.visible = false | |
| 108 | + | StatusPanel.settings_closed() | |
| 109 | + | ||
| 110 | + | SaveMenu.visible = false | |
| 111 | + | StatusPanel.saving_closed() | |
| 112 | + | ||
| 113 | + | ||
| 114 | + | func _on_settings_button_toggled(pressed: bool) -> void: | |
| 115 | + | SettingsMenu.visible = pressed | |
| 116 | + | ||
| 117 | + | if pressed: | |
| 118 | + | InventoryMenu.visible = false | |
| 119 | + | StatusPanel.inventory_closed() | |
| 120 | + | ||
| 121 | + | SaveMenu.visible = false | |
| 122 | + | StatusPanel.saving_closed() | |
| 123 | + | ||
| 124 | + | ||
| 125 | + | func _on_save_button_toggled(pressed: bool) -> void: | |
| 126 | + | SaveMenu.visible = pressed | |
| 127 | + | ||
| 128 | + | if pressed: | |
| 129 | + | InventoryMenu.visible = false | |
| 130 | + | StatusPanel.inventory_closed() | |
| 131 | + | ||
| 132 | + | SettingsMenu.visible = false | |
| 133 | + | StatusPanel.settings_closed() | |
| 134 | + | ||
| 135 | + | ||
| 136 | + | func _on_menu_visibility_changed() -> void: | |
| 137 | + | EventPlayerContainer.set_input_disabled(any_menu_open()) | |
| 138 | + | ChoicePanel.visible = !any_menu_open() and ChoicePanel.active | |
| 139 | + | ||
| 140 | + | ||
| 141 | + | func _on_wait_button_pressed() -> void: | |
| 142 | + | await Blackout.fade_out() | |
| 143 | + | ExploreState.wait() | |
| 144 | + | await Blackout.fade_in() | |
| 145 | + | ||
| 146 | + | ||
| 147 | + | func _on_options_shown() -> void: | |
| 148 | + | ChoicePanel.visible = !any_menu_open() and ChoicePanel.active | |
| 149 | + | ||
| 150 | + | ||
| 151 | + | func _on_options_cleared() -> void: | |
| 152 | + | ChoicePanel.hide() | |
| 153 | + | await get_tree().process_frame | |
| 154 | + | ||
| 155 | + | ||
| 156 | + | func _on_trap_triggered(_trap_name: String, event: String) -> void: | |
| 157 | + | EventPlayerContainer.run(event) | |
| 158 | + | ||
| 159 | + | ||
| 160 | + | func _on_event_triggered(event, entry: String = "intro") -> void: | |
| 161 | + | EventPlayerContainer.run(event, entry) | |
| 162 | + | ||
| 163 | + | ||
| 164 | + | func _on_event_trigger_forced(event, entry: String) -> void: | |
| 165 | + | if event == "NONE": | |
| 166 | + | EventPlayerContainer._on_scene_ended() | |
| 167 | + | ChoicePanel.clear_options() | |
| 168 | + | else: | |
| 169 | + | EventPlayerContainer._on_sequence_changed(event, entry) | |
| 170 | + | ||
| 171 | + | ||
| 172 | + | func _on_masturbated() -> void: | |
| 173 | + | EventPlayerContainer.run("masturbations/init") | |
| 174 | + | ||
| 175 | + | ||
| 176 | + | func any_menu_open() -> bool: | |
| 177 | + | return InventoryMenu.visible \ | |
| 178 | + | or SettingsMenu.visible \ | |
| 179 | + | or SaveMenu.visible | |