dialogue: Abnormality Rules

This commit is contained in:
Maurane
2025-06-08 03:23:46 +02:00
parent 16221a9e33
commit d4f9a165dc
81 changed files with 14777 additions and 18 deletions
+169
View File
@@ -0,0 +1,169 @@
extends CanvasLayer
## A basic dialogue balloon for use with Dialogue Manager.
## The action to use for advancing the dialogue
@export var next_action: StringName = &"ui_accept"
## The action to use to skip typing the dialogue
@export var skip_action: StringName = &"ui_cancel"
## The dialogue resource
var resource: DialogueResource
## Temporary game states
var temporary_game_states: Array = []
## See if we are waiting for the player
var is_waiting_for_input: bool = false
## See if we are running a long mutation and should hide the balloon
var will_hide_balloon: bool = false
## A dictionary to store any ephemeral variables
var locals: Dictionary = {}
var _locale: String = TranslationServer.get_locale()
## The current line
var dialogue_line: DialogueLine:
set(next_dialogue_line):
is_waiting_for_input = false
balloon.focus_mode = Control.FOCUS_ALL
balloon.grab_focus()
# The dialogue has finished so close the balloon
if not next_dialogue_line:
queue_free()
return
# If the node isn't ready yet then none of the labels will be ready yet either
if not is_node_ready():
await ready
dialogue_line = next_dialogue_line
character_label.visible = not dialogue_line.character.is_empty()
character_label.text = tr(dialogue_line.character, "dialogue")
dialogue_label.hide()
dialogue_label.dialogue_line = dialogue_line
responses_menu.hide()
responses_menu.set_responses(dialogue_line.responses)
# Show our balloon
balloon.show()
will_hide_balloon = false
dialogue_label.show()
if not dialogue_line.text.is_empty():
dialogue_label.type_out()
await dialogue_label.finished_typing
# Wait for input
if dialogue_line.responses.size() > 0:
balloon.focus_mode = Control.FOCUS_NONE
responses_menu.show()
elif dialogue_line.time != "":
var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float()
await get_tree().create_timer(time).timeout
next(dialogue_line.next_id)
else:
is_waiting_for_input = true
balloon.focus_mode = Control.FOCUS_ALL
balloon.grab_focus()
get:
return dialogue_line
## The base balloon anchor
@onready var balloon: Control = %Balloon
## The label showing the name of the currently speaking character
@onready var character_label: RichTextLabel = %CharacterLabel
## The label showing the currently spoken dialogue
@onready var dialogue_label: DialogueLabel = %DialogueLabel
## The menu of responses
@onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu
func _ready() -> void:
balloon.hide()
Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated)
# If the responses menu doesn't have a next action set, use this one
if responses_menu.next_action.is_empty():
responses_menu.next_action = next_action
func _unhandled_input(_event: InputEvent) -> void:
# Only the balloon is allowed to handle input while it's showing
get_viewport().set_input_as_handled()
func _notification(what: int) -> void:
## Detect a change of locale and update the current dialogue line to show the new language
if what == NOTIFICATION_TRANSLATION_CHANGED and _locale != TranslationServer.get_locale() and is_instance_valid(dialogue_label):
_locale = TranslationServer.get_locale()
var visible_ratio = dialogue_label.visible_ratio
self.dialogue_line = await resource.get_next_dialogue_line(dialogue_line.id)
if visible_ratio < 1:
dialogue_label.skip_typing()
## Start some dialogue
func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void:
if not is_node_ready():
await ready
temporary_game_states = [self] + extra_game_states
is_waiting_for_input = false
resource = dialogue_resource
self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states)
## Go to the next line
func next(next_id: String) -> void:
self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states)
#region Signals
func _on_mutated(_mutation: Dictionary) -> void:
is_waiting_for_input = false
will_hide_balloon = true
get_tree().create_timer(0.1).timeout.connect(func():
if will_hide_balloon:
will_hide_balloon = false
balloon.hide()
)
func _on_balloon_gui_input(event: InputEvent) -> void:
# See if we need to skip typing of the dialogue
if dialogue_label.is_typing:
var mouse_was_clicked: bool = event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed()
var skip_button_was_pressed: bool = event.is_action_pressed(skip_action)
if mouse_was_clicked or skip_button_was_pressed:
get_viewport().set_input_as_handled()
dialogue_label.skip_typing()
return
if not is_waiting_for_input: return
if dialogue_line.responses.size() > 0: return
# When there are no response options the balloon itself is the clickable thing
get_viewport().set_input_as_handled()
if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
next(dialogue_line.next_id)
elif event.is_action_pressed(next_action) and get_viewport().gui_get_focus_owner() == balloon:
next(dialogue_line.next_id)
func _on_responses_menu_response_selected(response: DialogueResponse) -> void:
next(response.next_id)
#endregion
+159
View File
@@ -0,0 +1,159 @@
[gd_scene load_steps=11 format=3 uid="uid://bh0e0uhmtk0ck"]
[ext_resource type="Script" path="res://Dialogues/balloon.gd" id="1_36de5"]
[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_a8ve6"]
[ext_resource type="FontFile" uid="uid://mi5v1uq78g7o" path="res://Art/XanhMono-Italic.ttf" id="2_qju6n"]
[ext_resource type="FontFile" uid="uid://ckg5x0oonnjtn" path="res://Art/XanhMono-Regular.ttf" id="3_66oah"]
[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="3_72ixx"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0.329412, 0.329412, 0.329412, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ri4m3"]
bg_color = Color(0.121569, 0.121569, 0.121569, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(1, 1, 1, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_e0njw"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0.6, 0.6, 0.6, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uy0d5"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="Theme" id="Theme_qq3yp"]
default_font_size = 20
Button/styles/disabled = SubResource("StyleBoxFlat_spyqn")
Button/styles/focus = SubResource("StyleBoxFlat_ri4m3")
Button/styles/hover = SubResource("StyleBoxFlat_e0njw")
Button/styles/normal = SubResource("StyleBoxFlat_e0njw")
MarginContainer/constants/margin_bottom = 15
MarginContainer/constants/margin_left = 30
MarginContainer/constants/margin_right = 30
MarginContainer/constants/margin_top = 15
Panel/styles/panel = SubResource("StyleBoxFlat_uy0d5")
[node name="ExampleBalloon" type="CanvasLayer"]
layer = 100
script = ExtResource("1_36de5")
[node name="Balloon" type="Control" parent="."]
unique_name_in_owner = true
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = SubResource("Theme_qq3yp")
[node name="Panel" type="Panel" parent="Balloon"]
clip_children = 2
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 21.0
offset_top = -240.0
offset_right = -19.0
offset_bottom = -19.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 1
[node name="Dialogue" type="MarginContainer" parent="Balloon/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/Panel/Dialogue"]
layout_mode = 2
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/Panel/Dialogue/VBoxContainer"]
unique_name_in_owner = true
modulate = Color(1, 1, 1, 0.501961)
layout_mode = 2
mouse_filter = 1
theme_override_fonts/normal_font = ExtResource("2_qju6n")
theme_override_font_sizes/normal_font_size = 25
bbcode_enabled = true
text = "Character"
fit_content = true
scroll_active = false
[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/VBoxContainer" instance=ExtResource("2_a8ve6")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
theme_override_fonts/normal_font = ExtResource("3_66oah")
theme_override_font_sizes/bold_italics_font_size = 40
theme_override_font_sizes/italics_font_size = 40
theme_override_font_sizes/mono_font_size = 40
theme_override_font_sizes/normal_font_size = 30
theme_override_font_sizes/bold_font_size = 40
text = "Dialogue..."
[node name="Responses" type="MarginContainer" parent="Balloon"]
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -147.0
offset_top = -626.0
offset_right = 494.0
offset_bottom = -213.0
grow_horizontal = 2
grow_vertical = 0
[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/Responses" node_paths=PackedStringArray("response_template")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 8
theme_override_constants/separation = 2
script = ExtResource("3_72ixx")
response_template = NodePath("ResponseExample")
[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"]
layout_mode = 2
text = "Response example"
[connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
[connection signal="response_selected" from="Balloon/Responses/ResponsesMenu" to="." method="_on_responses_menu_response_selected"]
+23
View File
@@ -0,0 +1,23 @@
~ start
A bleak, stern conference room, nestled in a research facility's subterranean network, probably buried deep somewhere underneath French fields of wheat and corn. Praise be the Republic for its generous funding.
A handful of people sitting nervously around the table. 2-weeks trained psychiatrist wannabes, just like me. I couldn't help but wonder what they came down here for. A dash of fame ? Money ? Trying to gloriously save our kind ? Or perhaps desperately seeking some smidge of meaning in all of this ?
I wouldn't remember any of their faces. I didn't want to. We were doomed people after all, fated to sit behind government-issued desks, sifting through waves of Abnormals until Death did us part. I didn't make eye contact with any of them, nor did they try to with me.
Standing upright at the end of the table, the Government Orator was speaking in a composed, almost rhythmic tone, his suave discourse slithering like a honey-smudged snake across the room. Every polished sentence a vital advice, every glib word a masked threat. All ears and eyes were fixated on him, trying to perceive every last speck of information.
Government Orator: As of today, Abnormality's pathophysiological mechanism has not yet been proven to have an infectious etiology. As such, psychiatry remains at the forefront of medical research against this pandemic.
Government Orator: Abnormality's psychiatric semiology was exceedingly arduous to apprehend, even for trained professionals. You are not expected to be qualified.
Government Orator: The brief training imparted to you on our lawgivers' behalf was at best an inadequate waste of the Republic's spending, and at worst an impetus towards your future demise. Though I may not offer you the expertise our senior colleagues wielded, listen closely, for the limited knowledge we gathered until now unveiled the Abnormality Rules.
Government Orator: 1- [color=darkred]Patients brought to you for screening may either be "Commons"[/color], as in people suffering from commonly known psychiatric illnesses,[color=darkred] or be Abnormals[/color].
Government Orator: 2- [color=darkred]Abnormals can and will try to mimic Commons' appearances, actions and speech patterns.[/color] Whether this behaviour stems from mere survival instinct or from an underlying motive has yet to be ascertained.
Government Orator: 3- [color=darkred]An Abnormal will always exhibit signs of its true nature when submitted to thorough assessment.[/color] The underlying cause remains unclear. Inability to fully mimic human behaviour is the currently leading hypothesis.
Government Orator: 4- [color=darkred]Abnormals do not possess emotional faculties, although they will try to mimic them.[/color] The very concept of emotions is utterly alien to an Abnormal, regardless of its mimicking ability. Conversely, the severity of a psychiatric condition does not and cannot negate a Common's fundamental humanity, and therefore innate capacity to exhibit genuine displays of emotions.
Government Orator: 5- [color=darkred]Abnormals violate the laws of Reality.[/color] If you notice anything that cannot be comprehended or explained by human knowledge, immediately take appropriate action according to protocol.
Government Orator: 6- As a final advice, follow your instinct. If something, anything [color=darkred]feels unnervingly wrong[/color] about a patient, further investigation is highly likely to be warranted.
Government Orator: As you all know, containment protocol's safety rules forbid you to leave the premises of this level for the duration of your work here. You have been assigned rooms to rest in between shifts. A meeting shall be held here every morning to optimize coordination and transmit information.
Government Orator: Good luck, "doctors". The Republic is proud of you.
Small notebooks were swiftly distributed around the table. Printed on the first pages were the Abnormality Rules, first verbally proclaimed to us, and now solemnly etched with ink. To read and reread ad nauseam until seared into one's memory.
I was the first to stand up and leave the room, brimming with a restless blend of nervous anticipation and unspoken fear.
I could still turn back and leave. I should. A part of me was screaming for it. But my legs kept on mechanically walking down the underground hallway, leading me to my assigned screening desk.
It was too late. I already knew too much, and was worth too little. Or so I told myself.
=> END
+15
View File
@@ -0,0 +1,15 @@
[remap]
importer="dialogue_manager_compiler_13"
type="Resource"
uid="uid://7fxd7wj3ocd3"
path="res://.godot/imported/intro.dialogue-3bb00dd6421fec4b467c9b900375fc91.tres"
[deps]
source_file="res://Dialogues/intro.dialogue"
dest_files=["res://.godot/imported/intro.dialogue-3bb00dd6421fec4b467c9b900375fc91.tres"]
[params]
defaults=true