Last active 1747575254

A collection of files I consider to be at the "core" of CC

00_Main.gd Raw Playground
1# warning-ignore-all:return_value_discarded
2extends HBoxContainer
3class_name ChitinousCanrival
4
5const 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
17var _current_sequence: String = ""
18
19func _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
67func _on_revisited(event: String) -> void:
68 EventPlayerContainer.run(event)
69
70
71func _on_wandered(where: String) -> void:
72 EventPlayerContainer.run("01-location/%s/wander" % where)
73
74
75func _on_item_selected(what: String) -> void:
76 StatusPanel.inventory_closed()
77 EventPlayerContainer.run("items/%s" % what)
78
79func _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
94func _on_minigame_started(minigame: String, initial_state: Dictionary) -> void:
95 MinigameHandler.play_minigame(minigame, initial_state)
96
97
98func _on_minigame_finished(result) -> void:
99 EventPlayerContainer.minigame_finished(result)
100
101
102func _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
114func _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
125func _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
136func _on_menu_visibility_changed() -> void:
137 EventPlayerContainer.set_input_disabled(any_menu_open())
138 ChoicePanel.visible = !any_menu_open() and ChoicePanel.active
139
140
141func _on_wait_button_pressed() -> void:
142 await Blackout.fade_out()
143 ExploreState.wait()
144 await Blackout.fade_in()
145
146
147func _on_options_shown() -> void:
148 ChoicePanel.visible = !any_menu_open() and ChoicePanel.active
149
150
151func _on_options_cleared() -> void:
152 ChoicePanel.hide()
153 await get_tree().process_frame
154
155
156func _on_trap_triggered(_trap_name: String, event: String) -> void:
157 EventPlayerContainer.run(event)
158
159
160func _on_event_triggered(event, entry: String = "intro") -> void:
161 EventPlayerContainer.run(event, entry)
162
163
164func _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
172func _on_masturbated() -> void:
173 EventPlayerContainer.run("masturbations/init")
174
175
176func any_menu_open() -> bool:
177 return InventoryMenu.visible \
178 or SettingsMenu.visible \
179 or SaveMenu.visible
180
01_EventPlayerContainer.gd Raw Playground
1extends Control
2class_name EventPlayerContainer
3
4signal scene_change_finished()
5signal sequence_changed(sequence) # warning-ignore:unused_signal
6signal options_shown(options)
7signal scene_change_started()
8signal minigame_started(minigame, initial_state)
9
10const EventPlayerScene = preload("res://scenes/ui/EventPlayer.tscn")
11
12var allow_fade_skip := false
13
14var _event_player_scene : EventPlayer = null
15var _changing_scenes := false
16
17func _ready() -> void:
18 set_input_disabled(true)
19 SimpleState.state_changed.connect(_on_state_changed) # warning-ignore:return_value_discarded
20
21
22func _on_state_changed(_name: String, _state: Dictionary) -> void:
23 pass
24
25
26func _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
33func 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
44func add_to_tree(scene : EventPlayer) -> void:
45 add_child(scene)
46 move_child(scene, 0)
47
48
49func run_scene(scene : EventPlayer, sequence : String, entry : String) -> void:
50 MemoryState.seen_passage(sequence)
51 scene.run(sequence, entry)
52
53
54func restore_scene(scene : EventPlayer, sequence : String, entry : String) -> void:
55 scene.restore(sequence, entry)
56
57
58func 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
68func 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
79func minigame_finished(result) -> void:
80 if is_instance_valid(_event_player_scene):
81 _event_player_scene.emit_signal("minigame_finished", result)
82
83
84func _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
95func _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
105func _on_scene_ended() -> void:
106 if not _changing_scenes:
107 stop()
108
109
110# Bubble Up Events
111func _on_minigame_started(minigame: String, initial_state: Dictionary) -> void:
112 emit_signal("minigame_started", minigame, initial_state)
113
114
115func _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
137func _options_shown(options: Array) -> void:
138 emit_signal("options_shown", options)
139
140
141func 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
160func 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
167func set_input_disabled(value: bool) -> void:
168 if is_instance_valid(_event_player_scene):
169 _event_player_scene.set_input_disabled(value)
170
02_EventPlayer.gd Raw Playground
1extends Control
2class_name EventPlayer
3
4# Signal
5signal fade_in_started()
6signal fade_out_started()
7signal scene_ended()
8
9# Proxied Signals
10signal minigame_started(minigame : String, initial_state : Dictionary)
11signal sequence_changed(sequence : String)
12
13# Signals to Yield on
14signal fade_finished() # warning-ignore:unused_signal
15signal minigame_finished() # warning-ignore:unused_signal
16signal change_scene_finished() # warning-ignore:unused_signal
17
18enum NextFade { FADE_OUT, FADE_IN }
19var fade_active = false
20var 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
31func _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
49func _on_line_changed(line_id: String) -> void:
50 SimpleState.update("sequence",
51 { current_line_id = line_id })
52
53
54func _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
61func _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
68func _on_dialogue_cleared() -> void:
69 dialogue_presenter.clear_dialogue()
70
71
72func _on_scene_finished() -> void:
73 emit_signal("scene_ended")
74
75
76func _on_options_cleared() -> void:
77 choice_panel.clear_options()
78
79
80func _on_option_picked(__, line_id) -> void:
81 playback.emit_signal("options_picked", line_id)
82
83
84# Public Methods
85func run(sequence: String, entry: String) -> void:
86 playback.run(Events.items[sequence], entry)
87
88
89func 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
108func goto(sequence: String) -> void:
109 sequence_changed.emit(sequence)
110 await change_scene_finished
111
112
113func 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
122func to_squence_name(key) -> String:
123 return key \
124 .capitalize() \
125 .replace(" ", "-") \
126 .to_lower()
127
128
129func exit() -> void:
130 await fade_out()
131
132
133func blackout(visibility: bool) -> void:
134 SimpleState.update("sequence", { blackout_visible = visibility })
135 blackout_panel.visible = visibility
136
137
138func 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
145func unimplemented() -> void:
146 assert(false, "Unimplemented Scene")
147
148
149func 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
156func 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
175func fade_out() -> void:
176 fade_out_started.emit()
177 await self.fade_finished
178
179
180func fade_in() -> void:
181 fade_in_started.emit()
182 await self.fade_finished
183
184
185func set_input_disabled(value: bool) -> void:
186 $Background.input_disabled = value
187 $Background/SubViewport.gui_disable_input = value
188
189
190func goto_birth_scene() -> void:
191 var scene = TempState["parasite"].birthing_scene
192 ExploreState.emit_signal("event_triggered", scene)
193
194
195func interaction(background: String) -> void:
196 set_input_disabled(false)
197 await background_presenter.start_interaction(background)
198 set_input_disabled(true)
199
03_CharacterHandler.gd Raw Playground
1extends 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
8var _characters = {}
9var _last_character := ""
10
11func 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
42func 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
58func 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
76func 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
85func update(who: String, what: String, with) -> void:
86 _characters[who][what] = with
87 _last_character = who
88
89func 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
102func everyone_leave() -> void:
103 for who in _characters:
104 character_layer.remove_child(_characters[who])
105
03_OptionPresenter.gd Raw Playground
1extends Node
2class_name Choice
3const ButtonBase = preload("res://scenes/ui/misc/ButtonBase.tscn")
4
5signal options_shown()
6signal options_cleared()
7signal option_picked(index, line_id)
8
9@export var option_path : NodePath
10@export var msg_listener_name = "option_presenter"
11
12var active := false
13
14@onready var options: VBoxContainer = get_node(option_path)
15
16
17func 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
36func 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
45func _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
53func _on_button_pressed(index: int, line: String) -> void:
54 clear_options()
55 emit_signal("option_picked", index, line)
56
03_Playback.gd Raw Playground
1extends Node
2class_name Playback
3
4signal line_changed(line : String)
5signal dialogue_shown(dialogue : DialogueLine)
6signal options_shown(options : Array[String])
7signal dialogue_cleared
8signal scene_finished
9
10signal options_picked(line_id : String)
11signal dialogue_finished # warning-ignore:unused_signal
12
13var dialogue_line : DialogueLine
14var extra_game_states : Array
15
16func 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()
47
04_SimpleState.gd Raw Playground
1extends Node
2class_name _SimpleState
3
4signal state_changed(name, state)
5
6var state := { }
7
8
9func register(state_name: String, inital_state: Dictionary = {}) -> void:
10 state[state_name] = inital_state
11
12
13func 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
18func 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
23func 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
28func 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
33func _on_update_state(data: Dictionary) -> void:
34 update(data.state_name, data.state)
35
36
37func get_state(state_name: String) -> Dictionary:
38 return state[state_name].duplicate()
39
40
41static func copy_to(a: Dictionary, b: Dictionary) -> void:
42 for key in b:
43 a[key] = b[key]
44
45
46
47static 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
55static func process_recursively(a, b, k) -> bool:
56 return a.has(k) and a[k] is Dictionary and b[k] is Dictionary
57
58static func merge(a: Dictionary, b: Dictionary) -> Dictionary:
59 var c := a.duplicate(true)
60 copy_to(c, b)
61 return c
62
63
64static func merge_deep(a: Dictionary, b: Dictionary) -> Dictionary:
65 var c := a.duplicate(true)
66 copy_to_deep(c, b)
67 return c
68