diff --git a/Art/BlackScreen.png.import b/Art/BlackScreen.png.import index 212d2f6..49cc2dd 100644 --- a/Art/BlackScreen.png.import +++ b/Art/BlackScreen.png.import @@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/BlackScreen.png-451daa3be1d42cdae0c158b32196a compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -25,6 +27,10 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/Art/DialogTheme.tres b/Art/DialogTheme.tres index 02097f5..3c68e89 100644 --- a/Art/DialogTheme.tres +++ b/Art/DialogTheme.tres @@ -1,6 +1,4 @@ -[gd_resource type="Theme" load_steps=4 format=3 uid="uid://dn1n15xwt8ucv"] - -[ext_resource type="FontFile" uid="uid://ckg5x0oonnjtn" path="res://Art/XanhMono-Regular.ttf" id="1_vfunk"] +[gd_resource type="Theme" load_steps=3 format=3 uid="uid://dn1n15xwt8ucv"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nxxb1"] @@ -10,7 +8,6 @@ border_color = Color(0, 0, 0, 1) shadow_color = Color(0, 0, 0, 1) [resource] -default_font = ExtResource("1_vfunk") default_font_size = 20 AcceptDialog/styles/embedded_border = SubResource("StyleBoxFlat_nxxb1") AcceptDialog/styles/panel = SubResource("StyleBoxFlat_8rsmc") diff --git a/Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf b/Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf new file mode 100644 index 0000000..9deeb16 Binary files /dev/null and b/Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf differ diff --git a/Art/XanhMono-Regular.ttf.import b/Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf.import similarity index 55% rename from Art/XanhMono-Regular.ttf.import rename to Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf.import index 4152fcc..e966ea3 100644 --- a/Art/XanhMono-Regular.ttf.import +++ b/Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf.import @@ -2,13 +2,13 @@ importer="font_data_dynamic" type="FontFile" -uid="uid://ckg5x0oonnjtn" -path="res://.godot/imported/XanhMono-Regular.ttf-7df0fa6b1f216f60ea4273e3a7ce2a72.fontdata" +uid="uid://dv8ppra5tgn1t" +path="res://.godot/imported/PixelatedEleganceRegular-ovyAA.ttf-f18db437f4c411fd6093fce35959abd6.fontdata" [deps] -source_file="res://Art/XanhMono-Regular.ttf" -dest_files=["res://.godot/imported/XanhMono-Regular.ttf-7df0fa6b1f216f60ea4273e3a7ce2a72.fontdata"] +source_file="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" +dest_files=["res://.godot/imported/PixelatedEleganceRegular-ovyAA.ttf-f18db437f4c411fd6093fce35959abd6.fontdata"] [params] @@ -21,8 +21,10 @@ msdf_pixel_range=8 msdf_size=48 allow_system_fallback=true force_autohinter=false +modulate_color_glyphs=false hinting=1 subpixel_positioning=1 +keep_rounding_remainders=true oversampling=0.0 Fallbacks=null fallbacks=[] diff --git a/Art/Fonts/pix32.zip b/Art/Fonts/pix32.zip new file mode 100644 index 0000000..96a5f08 Binary files /dev/null and b/Art/Fonts/pix32.zip differ diff --git a/Art/Fonts/pixelated-elegance-font.zip b/Art/Fonts/pixelated-elegance-font.zip new file mode 100644 index 0000000..fc9a051 Binary files /dev/null and b/Art/Fonts/pixelated-elegance-font.zip differ diff --git a/Art/Patients/patient_2202/Nuna.aseprite b/Art/Patients/patient_2202/Nuna.aseprite new file mode 100644 index 0000000..271771a Binary files /dev/null and b/Art/Patients/patient_2202/Nuna.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_abnormal.aseprite b/Art/Patients/patient_2202/nuna_abnormal.aseprite new file mode 100644 index 0000000..1925f7b Binary files /dev/null and b/Art/Patients/patient_2202/nuna_abnormal.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_angry.aseprite b/Art/Patients/patient_2202/nuna_angry.aseprite new file mode 100644 index 0000000..7ba9818 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_angry.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_angry.png.import b/Art/Patients/patient_2202/nuna_angry.png.import new file mode 100644 index 0000000..c75d29a --- /dev/null +++ b/Art/Patients/patient_2202/nuna_angry.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b1j7n7pay63ej" +path="res://.godot/imported/nuna_angry.png-1425f5a8989cecbea6a5457ee8bdb7d0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/nuna_angry.png" +dest_files=["res://.godot/imported/nuna_angry.png-1425f5a8989cecbea6a5457ee8bdb7d0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/nuna_angry_resized.aseprite b/Art/Patients/patient_2202/nuna_angry_resized.aseprite new file mode 100644 index 0000000..7e0fbb0 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_angry_resized.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_evil_resized.aseprite b/Art/Patients/patient_2202/nuna_evil_resized.aseprite new file mode 100644 index 0000000..c155d6b Binary files /dev/null and b/Art/Patients/patient_2202/nuna_evil_resized.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_happy.aseprite b/Art/Patients/patient_2202/nuna_happy.aseprite new file mode 100644 index 0000000..b97cd34 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_happy.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_happy.png.import b/Art/Patients/patient_2202/nuna_happy.png.import new file mode 100644 index 0000000..8eaa419 --- /dev/null +++ b/Art/Patients/patient_2202/nuna_happy.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://di72jydtmreap" +path="res://.godot/imported/nuna_happy.png-f44ff4c104a1ff6f0c9abc449bd34590.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/nuna_happy.png" +dest_files=["res://.godot/imported/nuna_happy.png-f44ff4c104a1ff6f0c9abc449bd34590.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/nuna_happy_resized.aseprite b/Art/Patients/patient_2202/nuna_happy_resized.aseprite new file mode 100644 index 0000000..985c0c4 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_happy_resized.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_irritated.aseprite b/Art/Patients/patient_2202/nuna_irritated.aseprite new file mode 100644 index 0000000..ed50bff Binary files /dev/null and b/Art/Patients/patient_2202/nuna_irritated.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_irritated_resized.aseprite b/Art/Patients/patient_2202/nuna_irritated_resized.aseprite new file mode 100644 index 0000000..d45161f Binary files /dev/null and b/Art/Patients/patient_2202/nuna_irritated_resized.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_neutral.aseprite b/Art/Patients/patient_2202/nuna_neutral.aseprite new file mode 100644 index 0000000..cc21801 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_neutral.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_neutral.png.import b/Art/Patients/patient_2202/nuna_neutral.png.import new file mode 100644 index 0000000..90231cf --- /dev/null +++ b/Art/Patients/patient_2202/nuna_neutral.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dq4metn5vi6la" +path="res://.godot/imported/nuna_neutral.png-0ce9ba76a689895c3487b6c4d4ddd6c0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/nuna_neutral.png" +dest_files=["res://.godot/imported/nuna_neutral.png-0ce9ba76a689895c3487b6c4d4ddd6c0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/nuna_neutral_resized.aseprite b/Art/Patients/patient_2202/nuna_neutral_resized.aseprite new file mode 100644 index 0000000..e3503f0 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_neutral_resized.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_sad.aseprite b/Art/Patients/patient_2202/nuna_sad.aseprite new file mode 100644 index 0000000..993be62 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_sad.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_sad.png.import b/Art/Patients/patient_2202/nuna_sad.png.import new file mode 100644 index 0000000..d4d0bc3 --- /dev/null +++ b/Art/Patients/patient_2202/nuna_sad.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ci0js4ywe63h6" +path="res://.godot/imported/nuna_sad.png-e86f26ea60abadbc00aa0071aa24adf6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/nuna_sad.png" +dest_files=["res://.godot/imported/nuna_sad.png-e86f26ea60abadbc00aa0071aa24adf6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/nuna_sad_resized.aseprite b/Art/Patients/patient_2202/nuna_sad_resized.aseprite new file mode 100644 index 0000000..7252d24 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_sad_resized.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_scared.aseprite b/Art/Patients/patient_2202/nuna_scared.aseprite new file mode 100644 index 0000000..3dadeb9 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_scared.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_scared.png.import b/Art/Patients/patient_2202/nuna_scared.png.import new file mode 100644 index 0000000..5efc444 --- /dev/null +++ b/Art/Patients/patient_2202/nuna_scared.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ccbdnb1ah1is" +path="res://.godot/imported/nuna_scared.png-5bbe832aeed5c34ba3cd08212cd19046.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/nuna_scared.png" +dest_files=["res://.godot/imported/nuna_scared.png-5bbe832aeed5c34ba3cd08212cd19046.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/nuna_scared_resized.aseprite b/Art/Patients/patient_2202/nuna_scared_resized.aseprite new file mode 100644 index 0000000..951a51c Binary files /dev/null and b/Art/Patients/patient_2202/nuna_scared_resized.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_smiling.aseprite b/Art/Patients/patient_2202/nuna_smiling.aseprite new file mode 100644 index 0000000..1a246cf Binary files /dev/null and b/Art/Patients/patient_2202/nuna_smiling.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_smiling_resized.aseprite b/Art/Patients/patient_2202/nuna_smiling_resized.aseprite new file mode 100644 index 0000000..029b9d0 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_smiling_resized.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_worried.aseprite b/Art/Patients/patient_2202/nuna_worried.aseprite new file mode 100644 index 0000000..00dea59 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_worried.aseprite differ diff --git a/Art/Patients/patient_2202/nuna_worried.png.import b/Art/Patients/patient_2202/nuna_worried.png.import new file mode 100644 index 0000000..ffe41c0 --- /dev/null +++ b/Art/Patients/patient_2202/nuna_worried.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://prjl104rs1a0" +path="res://.godot/imported/nuna_worried.png-8dcbccd03f94c296733062f9ed2c1f84.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/nuna_worried.png" +dest_files=["res://.godot/imported/nuna_worried.png-8dcbccd03f94c296733062f9ed2c1f84.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/nuna_worried_resized.aseprite b/Art/Patients/patient_2202/nuna_worried_resized.aseprite new file mode 100644 index 0000000..52bf2e3 Binary files /dev/null and b/Art/Patients/patient_2202/nuna_worried_resized.aseprite differ diff --git a/Art/Patients/patient_2202/patient_2202_abnormal.png b/Art/Patients/patient_2202/patient_2202_abnormal.png new file mode 100644 index 0000000..1714b47 Binary files /dev/null and b/Art/Patients/patient_2202/patient_2202_abnormal.png differ diff --git a/Art/Patients/patient_2202/patient_2202_abnormal.png.import b/Art/Patients/patient_2202/patient_2202_abnormal.png.import new file mode 100644 index 0000000..be8c19e --- /dev/null +++ b/Art/Patients/patient_2202/patient_2202_abnormal.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bs7nfx3d20u6g" +path="res://.godot/imported/patient_2202_abnormal.png-72aacafaa6fe55a20421ea2f0e04c8a8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/patient_2202_abnormal.png" +dest_files=["res://.godot/imported/patient_2202_abnormal.png-72aacafaa6fe55a20421ea2f0e04c8a8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/patient_2202_angry.png b/Art/Patients/patient_2202/patient_2202_angry.png new file mode 100644 index 0000000..3c3cbc4 Binary files /dev/null and b/Art/Patients/patient_2202/patient_2202_angry.png differ diff --git a/Art/Patients/patient_2202/patient_2202_angry.png.import b/Art/Patients/patient_2202/patient_2202_angry.png.import new file mode 100644 index 0000000..74b1ae4 --- /dev/null +++ b/Art/Patients/patient_2202/patient_2202_angry.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d12qosp0hu3vy" +path="res://.godot/imported/patient_2202_angry.png-cae6b0d9d6b1fef6c98953be722ddaca.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/patient_2202_angry.png" +dest_files=["res://.godot/imported/patient_2202_angry.png-cae6b0d9d6b1fef6c98953be722ddaca.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/patient_2202_happy.png b/Art/Patients/patient_2202/patient_2202_happy.png new file mode 100644 index 0000000..5d554c9 Binary files /dev/null and b/Art/Patients/patient_2202/patient_2202_happy.png differ diff --git a/Art/Patients/patient_2202/patient_2202_happy.png.import b/Art/Patients/patient_2202/patient_2202_happy.png.import new file mode 100644 index 0000000..82fd839 --- /dev/null +++ b/Art/Patients/patient_2202/patient_2202_happy.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cemrf0w8pi6ih" +path="res://.godot/imported/patient_2202_happy.png-08c349984514a613710ffe157bc3213e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/patient_2202_happy.png" +dest_files=["res://.godot/imported/patient_2202_happy.png-08c349984514a613710ffe157bc3213e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/patient_2202_irritated.png b/Art/Patients/patient_2202/patient_2202_irritated.png new file mode 100644 index 0000000..61a6670 Binary files /dev/null and b/Art/Patients/patient_2202/patient_2202_irritated.png differ diff --git a/Art/Patients/patient_2202/patient_2202_irritated.png.import b/Art/Patients/patient_2202/patient_2202_irritated.png.import new file mode 100644 index 0000000..f25cd1e --- /dev/null +++ b/Art/Patients/patient_2202/patient_2202_irritated.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfonnxqhwn3eo" +path="res://.godot/imported/patient_2202_irritated.png-dd8ffea344f40c41d9fe683454290a91.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/patient_2202_irritated.png" +dest_files=["res://.godot/imported/patient_2202_irritated.png-dd8ffea344f40c41d9fe683454290a91.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/patient_2202_neutral.png b/Art/Patients/patient_2202/patient_2202_neutral.png new file mode 100644 index 0000000..67f1afd Binary files /dev/null and b/Art/Patients/patient_2202/patient_2202_neutral.png differ diff --git a/Art/Patients/patient_2202/patient_2202_neutral.png.import b/Art/Patients/patient_2202/patient_2202_neutral.png.import new file mode 100644 index 0000000..3dfb796 --- /dev/null +++ b/Art/Patients/patient_2202/patient_2202_neutral.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://wm5ht6ktonff" +path="res://.godot/imported/patient_2202_neutral.png-2b2b51f031da948bb3607b9488cce26e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/patient_2202_neutral.png" +dest_files=["res://.godot/imported/patient_2202_neutral.png-2b2b51f031da948bb3607b9488cce26e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/patient_2202_sad.png b/Art/Patients/patient_2202/patient_2202_sad.png new file mode 100644 index 0000000..fa0b2b1 Binary files /dev/null and b/Art/Patients/patient_2202/patient_2202_sad.png differ diff --git a/Art/Patients/patient_2202/patient_2202_sad.png.import b/Art/Patients/patient_2202/patient_2202_sad.png.import new file mode 100644 index 0000000..fa1f82b --- /dev/null +++ b/Art/Patients/patient_2202/patient_2202_sad.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c075dcbrhur56" +path="res://.godot/imported/patient_2202_sad.png-24cb1a35cc330af2e9e656e591022cd2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/patient_2202_sad.png" +dest_files=["res://.godot/imported/patient_2202_sad.png-24cb1a35cc330af2e9e656e591022cd2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/patient_2202_scared.png b/Art/Patients/patient_2202/patient_2202_scared.png new file mode 100644 index 0000000..7d6ee9a Binary files /dev/null and b/Art/Patients/patient_2202/patient_2202_scared.png differ diff --git a/Art/Patients/patient_2202/patient_2202_scared.png.import b/Art/Patients/patient_2202/patient_2202_scared.png.import new file mode 100644 index 0000000..2f120a3 --- /dev/null +++ b/Art/Patients/patient_2202/patient_2202_scared.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://buxk0uu546v3v" +path="res://.godot/imported/patient_2202_scared.png-47d7ec47f1791d4872470a262317c972.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/patient_2202_scared.png" +dest_files=["res://.godot/imported/patient_2202_scared.png-47d7ec47f1791d4872470a262317c972.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/patient_2202_smiling.png b/Art/Patients/patient_2202/patient_2202_smiling.png new file mode 100644 index 0000000..c04f1cd Binary files /dev/null and b/Art/Patients/patient_2202/patient_2202_smiling.png differ diff --git a/Art/Patients/patient_2202/patient_2202_smiling.png.import b/Art/Patients/patient_2202/patient_2202_smiling.png.import new file mode 100644 index 0000000..4acd7a5 --- /dev/null +++ b/Art/Patients/patient_2202/patient_2202_smiling.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://citxfpd2fpphh" +path="res://.godot/imported/patient_2202_smiling.png-a0f47cebaa35544ea7e37c04e41d6964.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/patient_2202_smiling.png" +dest_files=["res://.godot/imported/patient_2202_smiling.png-a0f47cebaa35544ea7e37c04e41d6964.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_2202/patient_2202_worried.png b/Art/Patients/patient_2202/patient_2202_worried.png new file mode 100644 index 0000000..b5182f4 Binary files /dev/null and b/Art/Patients/patient_2202/patient_2202_worried.png differ diff --git a/Art/Patients/patient_2202/patient_2202_worried.png.import b/Art/Patients/patient_2202/patient_2202_worried.png.import new file mode 100644 index 0000000..363724f --- /dev/null +++ b/Art/Patients/patient_2202/patient_2202_worried.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://fp87sn2gygqm" +path="res://.godot/imported/patient_2202_worried.png-aa600a90cef858bc06f415b3a74de9a7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_2202/patient_2202_worried.png" +dest_files=["res://.godot/imported/patient_2202_worried.png-aa600a90cef858bc06f415b3a74de9a7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_47/patient_47.aseprite b/Art/Patients/patient_47/patient_47.aseprite new file mode 100644 index 0000000..1cbbdd7 Binary files /dev/null and b/Art/Patients/patient_47/patient_47.aseprite differ diff --git a/Art/Patients/patient_48/patient_48.aseprite b/Art/Patients/patient_48/patient_48.aseprite new file mode 100644 index 0000000..55d4285 Binary files /dev/null and b/Art/Patients/patient_48/patient_48.aseprite differ diff --git a/Art/Patients/patient_48/patient_48_abnormal.png b/Art/Patients/patient_48/patient_48_abnormal.png new file mode 100644 index 0000000..9e33ae4 Binary files /dev/null and b/Art/Patients/patient_48/patient_48_abnormal.png differ diff --git a/Art/Patients/patient_48/patient_48_abnormal.png.import b/Art/Patients/patient_48/patient_48_abnormal.png.import new file mode 100644 index 0000000..1ca38c8 --- /dev/null +++ b/Art/Patients/patient_48/patient_48_abnormal.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bgir6nhs83g2m" +path="res://.godot/imported/patient_48_abnormal.png-70bc1e40d26e8e891b711f5f226cf2e5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_48/patient_48_abnormal.png" +dest_files=["res://.godot/imported/patient_48_abnormal.png-70bc1e40d26e8e891b711f5f226cf2e5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_48/patient_48_angry.png b/Art/Patients/patient_48/patient_48_angry.png new file mode 100644 index 0000000..6af89e2 Binary files /dev/null and b/Art/Patients/patient_48/patient_48_angry.png differ diff --git a/Art/Patients/patient_48/patient_48_angry.png.import b/Art/Patients/patient_48/patient_48_angry.png.import new file mode 100644 index 0000000..759c1e1 --- /dev/null +++ b/Art/Patients/patient_48/patient_48_angry.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bgcvyh5m38r5m" +path="res://.godot/imported/patient_48_angry.png-42efe02eefc0b2a6b093720ee92a34c9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_48/patient_48_angry.png" +dest_files=["res://.godot/imported/patient_48_angry.png-42efe02eefc0b2a6b093720ee92a34c9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_48/patient_48_irritated.png b/Art/Patients/patient_48/patient_48_irritated.png new file mode 100644 index 0000000..c9cdcac Binary files /dev/null and b/Art/Patients/patient_48/patient_48_irritated.png differ diff --git a/Art/Patients/patient_48/patient_48_irritated.png.import b/Art/Patients/patient_48/patient_48_irritated.png.import new file mode 100644 index 0000000..11c187a --- /dev/null +++ b/Art/Patients/patient_48/patient_48_irritated.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3g42owikyxad" +path="res://.godot/imported/patient_48_irritated.png-0ad49875c95d8bbaee66b876c1276821.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_48/patient_48_irritated.png" +dest_files=["res://.godot/imported/patient_48_irritated.png-0ad49875c95d8bbaee66b876c1276821.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_48/patient_48_neutral.png b/Art/Patients/patient_48/patient_48_neutral.png new file mode 100644 index 0000000..5ca9b22 Binary files /dev/null and b/Art/Patients/patient_48/patient_48_neutral.png differ diff --git a/Art/Patients/patient_48/patient_48_neutral.png.import b/Art/Patients/patient_48/patient_48_neutral.png.import new file mode 100644 index 0000000..32338de --- /dev/null +++ b/Art/Patients/patient_48/patient_48_neutral.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1lm8p2lk2u5c" +path="res://.godot/imported/patient_48_neutral.png-8d0897913c026beb4c55058e582ed721.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_48/patient_48_neutral.png" +dest_files=["res://.godot/imported/patient_48_neutral.png-8d0897913c026beb4c55058e582ed721.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_48/patient_48_panicked.png b/Art/Patients/patient_48/patient_48_panicked.png new file mode 100644 index 0000000..f65d528 Binary files /dev/null and b/Art/Patients/patient_48/patient_48_panicked.png differ diff --git a/Art/Patients/patient_48/patient_48_panicked.png.import b/Art/Patients/patient_48/patient_48_panicked.png.import new file mode 100644 index 0000000..3f3b428 --- /dev/null +++ b/Art/Patients/patient_48/patient_48_panicked.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dev46xxc3m11j" +path="res://.godot/imported/patient_48_panicked.png-d7d148bbde52465306b4147a6cb6a890.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_48/patient_48_panicked.png" +dest_files=["res://.godot/imported/patient_48_panicked.png-d7d148bbde52465306b4147a6cb6a890.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_48/patient_48_puzzled.png b/Art/Patients/patient_48/patient_48_puzzled.png new file mode 100644 index 0000000..c02683e Binary files /dev/null and b/Art/Patients/patient_48/patient_48_puzzled.png differ diff --git a/Art/Patients/patient_48/patient_48_puzzled.png.import b/Art/Patients/patient_48/patient_48_puzzled.png.import new file mode 100644 index 0000000..cde1aa7 --- /dev/null +++ b/Art/Patients/patient_48/patient_48_puzzled.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://prcu5i0tp2oe" +path="res://.godot/imported/patient_48_puzzled.png-bb6f7ef90aa5c31f46a0e69221ee5f68.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_48/patient_48_puzzled.png" +dest_files=["res://.godot/imported/patient_48_puzzled.png-bb6f7ef90aa5c31f46a0e69221ee5f68.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_48/patient_48_resized.aseprite b/Art/Patients/patient_48/patient_48_resized.aseprite new file mode 100644 index 0000000..5d7b817 Binary files /dev/null and b/Art/Patients/patient_48/patient_48_resized.aseprite differ diff --git a/Art/Patients/patient_48/patient_48_scared.png b/Art/Patients/patient_48/patient_48_scared.png new file mode 100644 index 0000000..588863f Binary files /dev/null and b/Art/Patients/patient_48/patient_48_scared.png differ diff --git a/Art/Patients/patient_48/patient_48_scared.png.import b/Art/Patients/patient_48/patient_48_scared.png.import new file mode 100644 index 0000000..0e0fc8a --- /dev/null +++ b/Art/Patients/patient_48/patient_48_scared.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmty8wkoxuygw" +path="res://.godot/imported/patient_48_scared.png-625ec39f7961bec8648056eb18bc21f8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_48/patient_48_scared.png" +dest_files=["res://.godot/imported/patient_48_scared.png-625ec39f7961bec8648056eb18bc21f8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_48/patient_48_smiling.png b/Art/Patients/patient_48/patient_48_smiling.png new file mode 100644 index 0000000..f84b43a Binary files /dev/null and b/Art/Patients/patient_48/patient_48_smiling.png differ diff --git a/Art/Patients/patient_48/patient_48_smiling.png.import b/Art/Patients/patient_48/patient_48_smiling.png.import new file mode 100644 index 0000000..e9ab26e --- /dev/null +++ b/Art/Patients/patient_48/patient_48_smiling.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5fswfatb5143" +path="res://.godot/imported/patient_48_smiling.png-e77ffc1772a50dbb2ee889e28100c6fa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_48/patient_48_smiling.png" +dest_files=["res://.godot/imported/patient_48_smiling.png-e77ffc1772a50dbb2ee889e28100c6fa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_48/patient_48_suspicious.png b/Art/Patients/patient_48/patient_48_suspicious.png new file mode 100644 index 0000000..8a3b608 Binary files /dev/null and b/Art/Patients/patient_48/patient_48_suspicious.png differ diff --git a/Art/Patients/patient_48/patient_48_suspicious.png.import b/Art/Patients/patient_48/patient_48_suspicious.png.import new file mode 100644 index 0000000..0b62c29 --- /dev/null +++ b/Art/Patients/patient_48/patient_48_suspicious.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8ny8x1xis77j" +path="res://.godot/imported/patient_48_suspicious.png-ee851a5d43a8b3b7b035038be9cf015e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_48/patient_48_suspicious.png" +dest_files=["res://.godot/imported/patient_48_suspicious.png-ee851a5d43a8b3b7b035038be9cf015e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_48/patient_48_worried.png b/Art/Patients/patient_48/patient_48_worried.png new file mode 100644 index 0000000..f134677 Binary files /dev/null and b/Art/Patients/patient_48/patient_48_worried.png differ diff --git a/Art/Patients/patient_48/patient_48_worried.png.import b/Art/Patients/patient_48/patient_48_worried.png.import new file mode 100644 index 0000000..acf0f6b --- /dev/null +++ b/Art/Patients/patient_48/patient_48_worried.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddjttu8a18so0" +path="res://.godot/imported/patient_48_worried.png-c9928005656ca7e4f38e1cf54c0052d4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_48/patient_48_worried.png" +dest_files=["res://.godot/imported/patient_48_worried.png-c9928005656ca7e4f38e1cf54c0052d4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49.aseprite b/Art/Patients/patient_49/patient_49.aseprite new file mode 100644 index 0000000..92a92f0 Binary files /dev/null and b/Art/Patients/patient_49/patient_49.aseprite differ diff --git a/Art/Patients/patient_49/patient_49_abnormal.aseprite b/Art/Patients/patient_49/patient_49_abnormal.aseprite new file mode 100644 index 0000000..0aad96c Binary files /dev/null and b/Art/Patients/patient_49/patient_49_abnormal.aseprite differ diff --git a/Art/Patients/patient_49/patient_49_abnormal.png b/Art/Patients/patient_49/patient_49_abnormal.png new file mode 100644 index 0000000..a667feb Binary files /dev/null and b/Art/Patients/patient_49/patient_49_abnormal.png differ diff --git a/Art/Patients/patient_49/patient_49_abnormal.png.import b/Art/Patients/patient_49/patient_49_abnormal.png.import new file mode 100644 index 0000000..51fcef9 --- /dev/null +++ b/Art/Patients/patient_49/patient_49_abnormal.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://j80my7ph8a8t" +path="res://.godot/imported/patient_49_abnormal.png-d4b66bc249b1ca87bfd47807b99c3e53.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_abnormal.png" +dest_files=["res://.godot/imported/patient_49_abnormal.png-d4b66bc249b1ca87bfd47807b99c3e53.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49_elated.png b/Art/Patients/patient_49/patient_49_elated.png new file mode 100644 index 0000000..d38b648 Binary files /dev/null and b/Art/Patients/patient_49/patient_49_elated.png differ diff --git a/Art/Patients/patient_49/patient_49_elated.png.import b/Art/Patients/patient_49/patient_49_elated.png.import new file mode 100644 index 0000000..ea95ea6 --- /dev/null +++ b/Art/Patients/patient_49/patient_49_elated.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://q7b02jkfiuao" +path="res://.godot/imported/patient_49_elated.png-b44e0741f45183927c785cedb8da0e81.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_elated.png" +dest_files=["res://.godot/imported/patient_49_elated.png-b44e0741f45183927c785cedb8da0e81.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49_happy.png b/Art/Patients/patient_49/patient_49_happy.png new file mode 100644 index 0000000..94c2670 Binary files /dev/null and b/Art/Patients/patient_49/patient_49_happy.png differ diff --git a/Art/Patients/patient_49/patient_49_happy.png.import b/Art/Patients/patient_49/patient_49_happy.png.import new file mode 100644 index 0000000..3fd6f85 --- /dev/null +++ b/Art/Patients/patient_49/patient_49_happy.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b77rr5e7p6gnh" +path="res://.godot/imported/patient_49_happy.png-6ea61c1d0929197ec8d77baa00ff334c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_happy.png" +dest_files=["res://.godot/imported/patient_49_happy.png-6ea61c1d0929197ec8d77baa00ff334c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49_irritated.png b/Art/Patients/patient_49/patient_49_irritated.png new file mode 100644 index 0000000..86cd9cd Binary files /dev/null and b/Art/Patients/patient_49/patient_49_irritated.png differ diff --git a/Art/Patients/patient_49/patient_49_irritated.png.import b/Art/Patients/patient_49/patient_49_irritated.png.import new file mode 100644 index 0000000..c728e04 --- /dev/null +++ b/Art/Patients/patient_49/patient_49_irritated.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cweta46xt5l24" +path="res://.godot/imported/patient_49_irritated.png-3fabb6663bede52c4fd3572fd709e791.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_irritated.png" +dest_files=["res://.godot/imported/patient_49_irritated.png-3fabb6663bede52c4fd3572fd709e791.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49_laughing.png b/Art/Patients/patient_49/patient_49_laughing.png new file mode 100644 index 0000000..55d8ec2 Binary files /dev/null and b/Art/Patients/patient_49/patient_49_laughing.png differ diff --git a/Art/Patients/patient_49/patient_49_laughing.png.import b/Art/Patients/patient_49/patient_49_laughing.png.import new file mode 100644 index 0000000..67f94e2 --- /dev/null +++ b/Art/Patients/patient_49/patient_49_laughing.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cwmveq47qsmly" +path="res://.godot/imported/patient_49_laughing.png-ce2a7e98740eb2082d8e49d1f30e005b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_laughing.png" +dest_files=["res://.godot/imported/patient_49_laughing.png-ce2a7e98740eb2082d8e49d1f30e005b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49_maniac.png b/Art/Patients/patient_49/patient_49_maniac.png new file mode 100644 index 0000000..e059781 Binary files /dev/null and b/Art/Patients/patient_49/patient_49_maniac.png differ diff --git a/Art/Patients/patient_49/patient_49_maniac.png.import b/Art/Patients/patient_49/patient_49_maniac.png.import new file mode 100644 index 0000000..8bb1e14 --- /dev/null +++ b/Art/Patients/patient_49/patient_49_maniac.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dl7fn3xpdgx6r" +path="res://.godot/imported/patient_49_maniac.png-e35225d644e468c0a3a4f7ff4b724b7d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_maniac.png" +dest_files=["res://.godot/imported/patient_49_maniac.png-e35225d644e468c0a3a4f7ff4b724b7d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49_neutral.png b/Art/Patients/patient_49/patient_49_neutral.png new file mode 100644 index 0000000..aed0c71 Binary files /dev/null and b/Art/Patients/patient_49/patient_49_neutral.png differ diff --git a/Art/Patients/patient_49/patient_49_neutral.png.import b/Art/Patients/patient_49/patient_49_neutral.png.import new file mode 100644 index 0000000..60e4be5 --- /dev/null +++ b/Art/Patients/patient_49/patient_49_neutral.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ldvfnt0jwjxh" +path="res://.godot/imported/patient_49_neutral.png-155ed817fc99fab1ed366733c0a37dee.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_neutral.png" +dest_files=["res://.godot/imported/patient_49_neutral.png-155ed817fc99fab1ed366733c0a37dee.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49_puzzled.png b/Art/Patients/patient_49/patient_49_puzzled.png new file mode 100644 index 0000000..8d9e767 Binary files /dev/null and b/Art/Patients/patient_49/patient_49_puzzled.png differ diff --git a/Art/Patients/patient_49/patient_49_puzzled.png.import b/Art/Patients/patient_49/patient_49_puzzled.png.import new file mode 100644 index 0000000..36e42f4 --- /dev/null +++ b/Art/Patients/patient_49/patient_49_puzzled.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cf5uxsptbjklj" +path="res://.godot/imported/patient_49_puzzled.png-ce261e87cb5c9effc67ab1c4dc7de81f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_puzzled.png" +dest_files=["res://.godot/imported/patient_49_puzzled.png-ce261e87cb5c9effc67ab1c4dc7de81f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49_resized.aseprite b/Art/Patients/patient_49/patient_49_resized.aseprite new file mode 100644 index 0000000..028db40 Binary files /dev/null and b/Art/Patients/patient_49/patient_49_resized.aseprite differ diff --git a/Art/Patients/patient_49/patient_49_shocked.png b/Art/Patients/patient_49/patient_49_shocked.png new file mode 100644 index 0000000..1659b5d Binary files /dev/null and b/Art/Patients/patient_49/patient_49_shocked.png differ diff --git a/Art/Patients/patient_49/patient_49_shocked.png.import b/Art/Patients/patient_49/patient_49_shocked.png.import new file mode 100644 index 0000000..a5943fc --- /dev/null +++ b/Art/Patients/patient_49/patient_49_shocked.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bds3eyrmckqp1" +path="res://.godot/imported/patient_49_shocked.png-f4de7c3fcab3d101bb96a912f089780b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_shocked.png" +dest_files=["res://.godot/imported/patient_49_shocked.png-f4de7c3fcab3d101bb96a912f089780b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49_smiling.png b/Art/Patients/patient_49/patient_49_smiling.png new file mode 100644 index 0000000..8646814 Binary files /dev/null and b/Art/Patients/patient_49/patient_49_smiling.png differ diff --git a/Art/Patients/patient_49/patient_49_smiling.png.import b/Art/Patients/patient_49/patient_49_smiling.png.import new file mode 100644 index 0000000..6d4b08f --- /dev/null +++ b/Art/Patients/patient_49/patient_49_smiling.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cywtlgsil106x" +path="res://.godot/imported/patient_49_smiling.png-f4fdbec52d6e35b0967352e45ea17392.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_smiling.png" +dest_files=["res://.godot/imported/patient_49_smiling.png-f4fdbec52d6e35b0967352e45ea17392.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_49/patient_49_worried.png b/Art/Patients/patient_49/patient_49_worried.png new file mode 100644 index 0000000..26bd3c0 Binary files /dev/null and b/Art/Patients/patient_49/patient_49_worried.png differ diff --git a/Art/Patients/patient_49/patient_49_worried.png.import b/Art/Patients/patient_49/patient_49_worried.png.import new file mode 100644 index 0000000..f5f8f9a --- /dev/null +++ b/Art/Patients/patient_49/patient_49_worried.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://j1lktsipjpbk" +path="res://.godot/imported/patient_49_worried.png-da18ae4c6f7d31955b1bcf5d65f8c1e7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_49/patient_49_worried.png" +dest_files=["res://.godot/imported/patient_49_worried.png-da18ae4c6f7d31955b1bcf5d65f8c1e7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_50/patient_50.aseprite b/Art/Patients/patient_50/patient_50.aseprite new file mode 100644 index 0000000..e09a3b0 Binary files /dev/null and b/Art/Patients/patient_50/patient_50.aseprite differ diff --git a/Art/Patients/patient_50/patient_50_abnormal.aseprite b/Art/Patients/patient_50/patient_50_abnormal.aseprite new file mode 100644 index 0000000..37bb871 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_abnormal.aseprite differ diff --git a/Art/Patients/patient_50/patient_50_abnormal.png b/Art/Patients/patient_50/patient_50_abnormal.png new file mode 100644 index 0000000..5c296bd Binary files /dev/null and b/Art/Patients/patient_50/patient_50_abnormal.png differ diff --git a/Art/Patients/patient_50/patient_50_abnormal.png.import b/Art/Patients/patient_50/patient_50_abnormal.png.import new file mode 100644 index 0000000..c2b3ca9 --- /dev/null +++ b/Art/Patients/patient_50/patient_50_abnormal.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cedwurvr4at5u" +path="res://.godot/imported/patient_50_abnormal.png-2f6ac6b251a442a15a7ce76b8a4cdbb5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_50/patient_50_abnormal.png" +dest_files=["res://.godot/imported/patient_50_abnormal.png-2f6ac6b251a442a15a7ce76b8a4cdbb5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_50/patient_50_neutral.aseprite b/Art/Patients/patient_50/patient_50_neutral.aseprite new file mode 100644 index 0000000..0b3af32 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_neutral.aseprite differ diff --git a/Art/Patients/patient_50/patient_50_neutral.png b/Art/Patients/patient_50/patient_50_neutral.png new file mode 100644 index 0000000..bb09687 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_neutral.png differ diff --git a/Art/Patients/patient_50/patient_50_neutral.png.import b/Art/Patients/patient_50/patient_50_neutral.png.import new file mode 100644 index 0000000..e5cf223 --- /dev/null +++ b/Art/Patients/patient_50/patient_50_neutral.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://x52vv4rovnff" +path="res://.godot/imported/patient_50_neutral.png-b7037fbc84bb7afd00b8f5e3f4d6efba.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_50/patient_50_neutral.png" +dest_files=["res://.godot/imported/patient_50_neutral.png-b7037fbc84bb7afd00b8f5e3f4d6efba.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_50/patient_50_neutral_resized.png.import b/Art/Patients/patient_50/patient_50_neutral_resized.png.import new file mode 100644 index 0000000..e71b919 --- /dev/null +++ b/Art/Patients/patient_50/patient_50_neutral_resized.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0kw6gujxerwx" +path="res://.godot/imported/patient_50_neutral_resized.png-20b85ea92eb786387b99128ea1a57182.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_50/patient_50_neutral_resized.png" +dest_files=["res://.godot/imported/patient_50_neutral_resized.png-20b85ea92eb786387b99128ea1a57182.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_50/patient_50_panicked.aseprite b/Art/Patients/patient_50/patient_50_panicked.aseprite new file mode 100644 index 0000000..017fee6 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_panicked.aseprite differ diff --git a/Art/Patients/patient_50/patient_50_panicked.png b/Art/Patients/patient_50/patient_50_panicked.png new file mode 100644 index 0000000..b54329d Binary files /dev/null and b/Art/Patients/patient_50/patient_50_panicked.png differ diff --git a/Art/Patients/patient_50/patient_50_panicked.png.import b/Art/Patients/patient_50/patient_50_panicked.png.import new file mode 100644 index 0000000..32d38a9 --- /dev/null +++ b/Art/Patients/patient_50/patient_50_panicked.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://23o81p8814b4" +path="res://.godot/imported/patient_50_panicked.png-3dd29f0e87c7dffd4b2b1808fb022867.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_50/patient_50_panicked.png" +dest_files=["res://.godot/imported/patient_50_panicked.png-3dd29f0e87c7dffd4b2b1808fb022867.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_50/patient_50_puzzled.aseprite b/Art/Patients/patient_50/patient_50_puzzled.aseprite new file mode 100644 index 0000000..d04e043 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_puzzled.aseprite differ diff --git a/Art/Patients/patient_50/patient_50_puzzled.png b/Art/Patients/patient_50/patient_50_puzzled.png new file mode 100644 index 0000000..dfe4364 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_puzzled.png differ diff --git a/Art/Patients/patient_50/patient_50_puzzled.png.import b/Art/Patients/patient_50/patient_50_puzzled.png.import new file mode 100644 index 0000000..f474a45 --- /dev/null +++ b/Art/Patients/patient_50/patient_50_puzzled.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmbbqow7pynj2" +path="res://.godot/imported/patient_50_puzzled.png-f16e837996aff91ba22c75e7a9d703ce.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_50/patient_50_puzzled.png" +dest_files=["res://.godot/imported/patient_50_puzzled.png-f16e837996aff91ba22c75e7a9d703ce.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_50/patient_50_resized.aseprite b/Art/Patients/patient_50/patient_50_resized.aseprite new file mode 100644 index 0000000..bbf81e1 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_resized.aseprite differ diff --git a/Art/Patients/patient_50/patient_50_scared.aseprite b/Art/Patients/patient_50/patient_50_scared.aseprite new file mode 100644 index 0000000..5cba200 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_scared.aseprite differ diff --git a/Art/Patients/patient_50/patient_50_scared.png b/Art/Patients/patient_50/patient_50_scared.png new file mode 100644 index 0000000..71e1351 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_scared.png differ diff --git a/Art/Patients/patient_50/patient_50_scared.png.import b/Art/Patients/patient_50/patient_50_scared.png.import new file mode 100644 index 0000000..4f27a6f --- /dev/null +++ b/Art/Patients/patient_50/patient_50_scared.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cvuke1hj775r1" +path="res://.godot/imported/patient_50_scared.png-2e0bc6ade70417b8fcdd78a65dd1b164.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_50/patient_50_scared.png" +dest_files=["res://.godot/imported/patient_50_scared.png-2e0bc6ade70417b8fcdd78a65dd1b164.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_50/patient_50_shocked.aseprite b/Art/Patients/patient_50/patient_50_shocked.aseprite new file mode 100644 index 0000000..1908e67 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_shocked.aseprite differ diff --git a/Art/Patients/patient_50/patient_50_shocked.png b/Art/Patients/patient_50/patient_50_shocked.png new file mode 100644 index 0000000..8b6b17f Binary files /dev/null and b/Art/Patients/patient_50/patient_50_shocked.png differ diff --git a/Art/Patients/patient_50/patient_50_shocked.png.import b/Art/Patients/patient_50/patient_50_shocked.png.import new file mode 100644 index 0000000..f930175 --- /dev/null +++ b/Art/Patients/patient_50/patient_50_shocked.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ciqeu6tfglx5r" +path="res://.godot/imported/patient_50_shocked.png-003b583a1bb31da9456760135b5c53ef.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_50/patient_50_shocked.png" +dest_files=["res://.godot/imported/patient_50_shocked.png-003b583a1bb31da9456760135b5c53ef.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_50/patient_50_smiling.aseprite b/Art/Patients/patient_50/patient_50_smiling.aseprite new file mode 100644 index 0000000..4dac9a2 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_smiling.aseprite differ diff --git a/Art/Patients/patient_50/patient_50_smiling.png b/Art/Patients/patient_50/patient_50_smiling.png new file mode 100644 index 0000000..1427714 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_smiling.png differ diff --git a/Art/Patients/patient_50/patient_50_smiling.png.import b/Art/Patients/patient_50/patient_50_smiling.png.import new file mode 100644 index 0000000..33dd1ab --- /dev/null +++ b/Art/Patients/patient_50/patient_50_smiling.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c32ybybewmk81" +path="res://.godot/imported/patient_50_smiling.png-828ac78bc45025aa2e82ea7923418d2d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_50/patient_50_smiling.png" +dest_files=["res://.godot/imported/patient_50_smiling.png-828ac78bc45025aa2e82ea7923418d2d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_50/patient_50_worried.aseprite b/Art/Patients/patient_50/patient_50_worried.aseprite new file mode 100644 index 0000000..c71936e Binary files /dev/null and b/Art/Patients/patient_50/patient_50_worried.aseprite differ diff --git a/Art/Patients/patient_50/patient_50_worried.png b/Art/Patients/patient_50/patient_50_worried.png new file mode 100644 index 0000000..0a50561 Binary files /dev/null and b/Art/Patients/patient_50/patient_50_worried.png differ diff --git a/Art/Patients/patient_50/patient_50_worried.png.import b/Art/Patients/patient_50/patient_50_worried.png.import new file mode 100644 index 0000000..cc7e69a --- /dev/null +++ b/Art/Patients/patient_50/patient_50_worried.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c667pc6rtsvkf" +path="res://.godot/imported/patient_50_worried.png-e9ee4efd1fcd36285729002496a818cc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Patients/patient_50/patient_50_worried.png" +dest_files=["res://.godot/imported/patient_50_worried.png-e9ee4efd1fcd36285729002496a818cc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Patients/patient_564/patient_564_angry.png.import b/Art/Patients/patient_564/patient_564_angry.png.import index 8e9933b..c40c7a2 100644 --- a/Art/Patients/patient_564/patient_564_angry.png.import +++ b/Art/Patients/patient_564/patient_564_angry.png.import @@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/patient_564_angry.png-233db4ed2aa1c2073b958dd compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -25,6 +27,10 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/Art/Patients/patient_564/patient_564_full.png.import b/Art/Patients/patient_564/patient_564_full.png.import index bbbe064..f722089 100644 --- a/Art/Patients/patient_564/patient_564_full.png.import +++ b/Art/Patients/patient_564/patient_564_full.png.import @@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/patient_564_full.png-427197587e8d779d455b567f compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -25,6 +27,10 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/Art/Patients/patient_564/patient_564_neutral.png.import b/Art/Patients/patient_564/patient_564_neutral.png.import index db26637..6ed01af 100644 --- a/Art/Patients/patient_564/patient_564_neutral.png.import +++ b/Art/Patients/patient_564/patient_564_neutral.png.import @@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/patient_564_neutral.png-b41269b93c0ebc8d180e6 compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -25,6 +27,10 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/Art/Patients/patient_564/patient_564_sad.png.import b/Art/Patients/patient_564/patient_564_sad.png.import index 2a2cee2..5b17a3b 100644 --- a/Art/Patients/patient_564/patient_564_sad.png.import +++ b/Art/Patients/patient_564/patient_564_sad.png.import @@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/patient_564_sad.png-e66ee7c3da902a9f0c5751be3 compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -25,6 +27,10 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/Art/Patients/patient_564/patient_564_scared.png.import b/Art/Patients/patient_564/patient_564_scared.png.import index da2ecc0..edde8ae 100644 --- a/Art/Patients/patient_564/patient_564_scared.png.import +++ b/Art/Patients/patient_564/patient_564_scared.png.import @@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/patient_564_scared.png-ae3b888b68aaaf578da795 compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -25,6 +27,10 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/Art/Patients/patient_564/patient_564_smiling.png.import b/Art/Patients/patient_564/patient_564_smiling.png.import index b566c90..204b44e 100644 --- a/Art/Patients/patient_564/patient_564_smiling.png.import +++ b/Art/Patients/patient_564/patient_564_smiling.png.import @@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/patient_564_smiling.png-89f34e1d6e52c0aa632df compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -25,6 +27,10 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/Art/ScreeningDesk.png.import b/Art/Rooms/Backwall.png.import similarity index 68% rename from Art/ScreeningDesk.png.import rename to Art/Rooms/Backwall.png.import index ce48f1c..4b6aa09 100644 --- a/Art/ScreeningDesk.png.import +++ b/Art/Rooms/Backwall.png.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://dc2nmwcscjig4" -path="res://.godot/imported/ScreeningDesk.png-b7e3d5e6ea1b84ff18d02e5badc4df09.ctex" +uid="uid://c7c14olrcdoa6" +path="res://.godot/imported/Backwall.png-1bf10c36ef871becca36cc15a8eae226.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://Art/ScreeningDesk.png" -dest_files=["res://.godot/imported/ScreeningDesk.png-b7e3d5e6ea1b84ff18d02e5badc4df09.ctex"] +source_file="res://Art/Rooms/Backwall.png" +dest_files=["res://.godot/imported/Backwall.png-1bf10c36ef871becca36cc15a8eae226.ctex"] [params] diff --git a/Art/Rooms/ScreeningRoom.png.import b/Art/Rooms/ScreeningRoom.png.import new file mode 100644 index 0000000..736ceb7 --- /dev/null +++ b/Art/Rooms/ScreeningRoom.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b1x6k47a31mhv" +path="res://.godot/imported/ScreeningRoom.png-cddc7e427712e704065d69294155b1ba.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/ScreeningRoom.png" +dest_files=["res://.godot/imported/ScreeningRoom.png-cddc7e427712e704065d69294155b1ba.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/ScreeningRoomGlass.png.import b/Art/Rooms/ScreeningRoomGlass.png.import new file mode 100644 index 0000000..82e6abd --- /dev/null +++ b/Art/Rooms/ScreeningRoomGlass.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bghd0754557fj" +path="res://.godot/imported/ScreeningRoomGlass.png-33ddbe65a5561d8ba2c2c84bbe44b91d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/ScreeningRoomGlass.png" +dest_files=["res://.godot/imported/ScreeningRoomGlass.png-33ddbe65a5561d8ba2c2c84bbe44b91d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/backroom.png b/Art/Rooms/screening_room/backroom.png new file mode 100644 index 0000000..9fb3440 Binary files /dev/null and b/Art/Rooms/screening_room/backroom.png differ diff --git a/Art/Rooms/screening_room/backroom.png.import b/Art/Rooms/screening_room/backroom.png.import new file mode 100644 index 0000000..35d95b4 --- /dev/null +++ b/Art/Rooms/screening_room/backroom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://by34irl73n8pb" +path="res://.godot/imported/backroom.png-ebf3f437a8e4f79ee56faafa1f171c63.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/backroom.png" +dest_files=["res://.godot/imported/backroom.png-ebf3f437a8e4f79ee56faafa1f171c63.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/backwall.png b/Art/Rooms/screening_room/backwall.png new file mode 100644 index 0000000..e85293d Binary files /dev/null and b/Art/Rooms/screening_room/backwall.png differ diff --git a/Art/Rooms/screening_room/backwall.png.import b/Art/Rooms/screening_room/backwall.png.import new file mode 100644 index 0000000..b2532aa --- /dev/null +++ b/Art/Rooms/screening_room/backwall.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dj1x60fld5400" +path="res://.godot/imported/backwall.png-42324951877d6b82bd80ccbbf5943468.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/backwall.png" +dest_files=["res://.godot/imported/backwall.png-42324951877d6b82bd80ccbbf5943468.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/button_bitmap.bmp.import b/Art/Rooms/screening_room/button_bitmap.bmp.import new file mode 100644 index 0000000..4684c36 --- /dev/null +++ b/Art/Rooms/screening_room/button_bitmap.bmp.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ch426lkg7d78e" +path="res://.godot/imported/button_bitmap.bmp-d763405576bb398a4247757922d14488.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/button_bitmap.bmp" +dest_files=["res://.godot/imported/button_bitmap.bmp-d763405576bb398a4247757922d14488.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/button_bitmap.png b/Art/Rooms/screening_room/button_bitmap.png new file mode 100644 index 0000000..8ce294d Binary files /dev/null and b/Art/Rooms/screening_room/button_bitmap.png differ diff --git a/Art/Rooms/screening_room/button_bitmap.png.import b/Art/Rooms/screening_room/button_bitmap.png.import new file mode 100644 index 0000000..ba47620 --- /dev/null +++ b/Art/Rooms/screening_room/button_bitmap.png.import @@ -0,0 +1,16 @@ +[remap] + +importer="bitmap" +type="BitMap" +uid="uid://b1y07k0opbyju" +path="res://.godot/imported/button_bitmap.png-6f5ac0ed87f203205525315b2bf5eccb.res" + +[deps] + +source_file="res://Art/Rooms/screening_room/button_bitmap.png" +dest_files=["res://.godot/imported/button_bitmap.png-6f5ac0ed87f203205525315b2bf5eccb.res"] + +[params] + +create_from=0 +threshold=0.5 diff --git a/Art/Rooms/screening_room/buttons.aseprite b/Art/Rooms/screening_room/buttons.aseprite new file mode 100644 index 0000000..f929a60 Binary files /dev/null and b/Art/Rooms/screening_room/buttons.aseprite differ diff --git a/Art/Rooms/screening_room/buttons_resized.aseprite b/Art/Rooms/screening_room/buttons_resized.aseprite new file mode 100644 index 0000000..809bf90 Binary files /dev/null and b/Art/Rooms/screening_room/buttons_resized.aseprite differ diff --git a/Art/Rooms/screening_room/cell.aseprite b/Art/Rooms/screening_room/cell.aseprite new file mode 100644 index 0000000..180059c Binary files /dev/null and b/Art/Rooms/screening_room/cell.aseprite differ diff --git a/Art/Rooms/screening_room/cell_resized.aseprite b/Art/Rooms/screening_room/cell_resized.aseprite new file mode 100644 index 0000000..f2da8df Binary files /dev/null and b/Art/Rooms/screening_room/cell_resized.aseprite differ diff --git a/Art/Rooms/screening_room/computer.aseprite b/Art/Rooms/screening_room/computer.aseprite new file mode 100644 index 0000000..e023a71 Binary files /dev/null and b/Art/Rooms/screening_room/computer.aseprite differ diff --git a/Art/Rooms/screening_room/computer.png b/Art/Rooms/screening_room/computer.png new file mode 100644 index 0000000..7c488e7 Binary files /dev/null and b/Art/Rooms/screening_room/computer.png differ diff --git a/Art/Rooms/screening_room/computer.png.import b/Art/Rooms/screening_room/computer.png.import new file mode 100644 index 0000000..b5b67c0 --- /dev/null +++ b/Art/Rooms/screening_room/computer.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cci0pb12chlps" +path="res://.godot/imported/computer.png-ba57b246f55c37c25c0166a95ff245c8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/computer.png" +dest_files=["res://.godot/imported/computer.png-ba57b246f55c37c25c0166a95ff245c8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/console_screen.aseprite b/Art/Rooms/screening_room/console_screen.aseprite new file mode 100644 index 0000000..ea0868f Binary files /dev/null and b/Art/Rooms/screening_room/console_screen.aseprite differ diff --git a/Art/Rooms/screening_room/console_screen.png b/Art/Rooms/screening_room/console_screen.png new file mode 100644 index 0000000..173ebca Binary files /dev/null and b/Art/Rooms/screening_room/console_screen.png differ diff --git a/Art/Rooms/screening_room/console_screen.png.import b/Art/Rooms/screening_room/console_screen.png.import new file mode 100644 index 0000000..d65d9fc --- /dev/null +++ b/Art/Rooms/screening_room/console_screen.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3frxq4vyu0pr" +path="res://.godot/imported/console_screen.png-7a8fb44238bb5e0c2db3ec797d0d8e88.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/console_screen.png" +dest_files=["res://.godot/imported/console_screen.png-7a8fb44238bb5e0c2db3ec797d0d8e88.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/decision_screen.aseprite b/Art/Rooms/screening_room/decision_screen.aseprite new file mode 100644 index 0000000..19faee6 Binary files /dev/null and b/Art/Rooms/screening_room/decision_screen.aseprite differ diff --git a/Art/Rooms/screening_room/decision_screen.png b/Art/Rooms/screening_room/decision_screen.png new file mode 100644 index 0000000..3f38679 Binary files /dev/null and b/Art/Rooms/screening_room/decision_screen.png differ diff --git a/Art/Rooms/screening_room/decision_screen.png.import b/Art/Rooms/screening_room/decision_screen.png.import new file mode 100644 index 0000000..81413dd --- /dev/null +++ b/Art/Rooms/screening_room/decision_screen.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6ary7qg7jhc6" +path="res://.godot/imported/decision_screen.png-68c88269c3e194efbf0416570df0f2cf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/decision_screen.png" +dest_files=["res://.godot/imported/decision_screen.png-68c88269c3e194efbf0416570df0f2cf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/desk.aseprite b/Art/Rooms/screening_room/desk.aseprite new file mode 100644 index 0000000..c602c1c Binary files /dev/null and b/Art/Rooms/screening_room/desk.aseprite differ diff --git a/Art/Rooms/screening_room/desk.png b/Art/Rooms/screening_room/desk.png new file mode 100644 index 0000000..a302892 Binary files /dev/null and b/Art/Rooms/screening_room/desk.png differ diff --git a/Art/Rooms/screening_room/desk.png.import b/Art/Rooms/screening_room/desk.png.import new file mode 100644 index 0000000..3a1b1bf --- /dev/null +++ b/Art/Rooms/screening_room/desk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jychpkygmjib" +path="res://.godot/imported/desk.png-9a99347d365a703a624d263049875d2e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/desk.png" +dest_files=["res://.godot/imported/desk.png-9a99347d365a703a624d263049875d2e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/desk_resized.png.import b/Art/Rooms/screening_room/desk_resized.png.import new file mode 100644 index 0000000..a9ef374 --- /dev/null +++ b/Art/Rooms/screening_room/desk_resized.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dx81l55603ooy" +path="res://.godot/imported/desk_resized.png-95bb621ab96fed81392a1f2dae369ff9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/desk_resized.png" +dest_files=["res://.godot/imported/desk_resized.png-95bb621ab96fed81392a1f2dae369ff9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/green_backroom.png b/Art/Rooms/screening_room/green_backroom.png new file mode 100644 index 0000000..342fc46 Binary files /dev/null and b/Art/Rooms/screening_room/green_backroom.png differ diff --git a/Art/Rooms/screening_room/green_backroom.png.import b/Art/Rooms/screening_room/green_backroom.png.import new file mode 100644 index 0000000..3f75e12 --- /dev/null +++ b/Art/Rooms/screening_room/green_backroom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cg2mywrvfpvad" +path="res://.godot/imported/green_backroom.png-b78b9ebc0fe838f6beaacd8a0674a413.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/green_backroom.png" +dest_files=["res://.godot/imported/green_backroom.png-b78b9ebc0fe838f6beaacd8a0674a413.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/green_button.png b/Art/Rooms/screening_room/green_button.png new file mode 100644 index 0000000..ffc19a5 Binary files /dev/null and b/Art/Rooms/screening_room/green_button.png differ diff --git a/Art/Rooms/screening_room/green_button.png.import b/Art/Rooms/screening_room/green_button.png.import new file mode 100644 index 0000000..b6421b7 --- /dev/null +++ b/Art/Rooms/screening_room/green_button.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhqx2kjgn1irf" +path="res://.godot/imported/green_button.png-ca919e7a21e70d61652bc560c1a87870.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/green_button.png" +dest_files=["res://.godot/imported/green_button.png-ca919e7a21e70d61652bc560c1a87870.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/green_button_hover.png b/Art/Rooms/screening_room/green_button_hover.png new file mode 100644 index 0000000..d403f4e Binary files /dev/null and b/Art/Rooms/screening_room/green_button_hover.png differ diff --git a/Art/Rooms/screening_room/green_button_hover.png.import b/Art/Rooms/screening_room/green_button_hover.png.import new file mode 100644 index 0000000..8b96b94 --- /dev/null +++ b/Art/Rooms/screening_room/green_button_hover.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dja217ihoi5rb" +path="res://.godot/imported/green_button_hover.png-d6c809b43e90359bee56e77c5ec308eb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/green_button_hover.png" +dest_files=["res://.godot/imported/green_button_hover.png-d6c809b43e90359bee56e77c5ec308eb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/green_button_pressed.png b/Art/Rooms/screening_room/green_button_pressed.png new file mode 100644 index 0000000..715aee4 Binary files /dev/null and b/Art/Rooms/screening_room/green_button_pressed.png differ diff --git a/Art/Rooms/screening_room/green_button_pressed.png.import b/Art/Rooms/screening_room/green_button_pressed.png.import new file mode 100644 index 0000000..7bca796 --- /dev/null +++ b/Art/Rooms/screening_room/green_button_pressed.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bagw75cp302xk" +path="res://.godot/imported/green_button_pressed.png-d396dd2f1da5d12b54d85946c413b1d0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/green_button_pressed.png" +dest_files=["res://.godot/imported/green_button_pressed.png-d396dd2f1da5d12b54d85946c413b1d0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/journal.aseprite b/Art/Rooms/screening_room/journal.aseprite new file mode 100644 index 0000000..a098d2d Binary files /dev/null and b/Art/Rooms/screening_room/journal.aseprite differ diff --git a/Art/Rooms/screening_room/journal.png b/Art/Rooms/screening_room/journal.png new file mode 100644 index 0000000..86c206c Binary files /dev/null and b/Art/Rooms/screening_room/journal.png differ diff --git a/Art/Rooms/screening_room/journal.png.import b/Art/Rooms/screening_room/journal.png.import new file mode 100644 index 0000000..dfacde1 --- /dev/null +++ b/Art/Rooms/screening_room/journal.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://h5s4s6h6fdq7" +path="res://.godot/imported/journal.png-0f8af58e71ece90ab033ed16a1bf46e9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/journal.png" +dest_files=["res://.godot/imported/journal.png-0f8af58e71ece90ab033ed16a1bf46e9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/journal_bitmap.png b/Art/Rooms/screening_room/journal_bitmap.png new file mode 100644 index 0000000..d902744 Binary files /dev/null and b/Art/Rooms/screening_room/journal_bitmap.png differ diff --git a/Art/Rooms/screening_room/journal_bitmap.png.import b/Art/Rooms/screening_room/journal_bitmap.png.import new file mode 100644 index 0000000..42efb38 --- /dev/null +++ b/Art/Rooms/screening_room/journal_bitmap.png.import @@ -0,0 +1,16 @@ +[remap] + +importer="bitmap" +type="BitMap" +uid="uid://byqmvpivmvsnl" +path="res://.godot/imported/journal_bitmap.png-864482ad29e4d66ee3d688f1e4ea3213.res" + +[deps] + +source_file="res://Art/Rooms/screening_room/journal_bitmap.png" +dest_files=["res://.godot/imported/journal_bitmap.png-864482ad29e4d66ee3d688f1e4ea3213.res"] + +[params] + +create_from=0 +threshold=0.5 diff --git a/Art/Rooms/screening_room/journal_hover.png b/Art/Rooms/screening_room/journal_hover.png new file mode 100644 index 0000000..661d435 Binary files /dev/null and b/Art/Rooms/screening_room/journal_hover.png differ diff --git a/Art/Rooms/screening_room/journal_hover.png.import b/Art/Rooms/screening_room/journal_hover.png.import new file mode 100644 index 0000000..70941d0 --- /dev/null +++ b/Art/Rooms/screening_room/journal_hover.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7tbgcyxjj516" +path="res://.godot/imported/journal_hover.png-608ebd44a5b30c847a6a84cc13fec239.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/journal_hover.png" +dest_files=["res://.godot/imported/journal_hover.png-608ebd44a5b30c847a6a84cc13fec239.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/journal_open.aseprite b/Art/Rooms/screening_room/journal_open.aseprite new file mode 100644 index 0000000..569468a Binary files /dev/null and b/Art/Rooms/screening_room/journal_open.aseprite differ diff --git a/Art/Rooms/screening_room/journal_open.png b/Art/Rooms/screening_room/journal_open.png new file mode 100644 index 0000000..1e0534a Binary files /dev/null and b/Art/Rooms/screening_room/journal_open.png differ diff --git a/Art/Rooms/screening_room/journal_open.png.import b/Art/Rooms/screening_room/journal_open.png.import new file mode 100644 index 0000000..3274dd2 --- /dev/null +++ b/Art/Rooms/screening_room/journal_open.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://diln53wxa3oep" +path="res://.godot/imported/journal_open.png-fc38da49fbd4290009c9c0a4104e75ac.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/journal_open.png" +dest_files=["res://.godot/imported/journal_open.png-fc38da49fbd4290009c9c0a4104e75ac.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/journal_open_resized.aseprite b/Art/Rooms/screening_room/journal_open_resized.aseprite new file mode 100644 index 0000000..e0f9986 Binary files /dev/null and b/Art/Rooms/screening_room/journal_open_resized.aseprite differ diff --git a/Art/Rooms/screening_room/journal_palette.aseprite b/Art/Rooms/screening_room/journal_palette.aseprite new file mode 100644 index 0000000..eb9416d Binary files /dev/null and b/Art/Rooms/screening_room/journal_palette.aseprite differ diff --git a/Art/Rooms/screening_room/journal_pressed.png b/Art/Rooms/screening_room/journal_pressed.png new file mode 100644 index 0000000..c3c759a Binary files /dev/null and b/Art/Rooms/screening_room/journal_pressed.png differ diff --git a/Art/Rooms/screening_room/journal_pressed.png.import b/Art/Rooms/screening_room/journal_pressed.png.import new file mode 100644 index 0000000..b1124c9 --- /dev/null +++ b/Art/Rooms/screening_room/journal_pressed.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1djtmkdqtplt" +path="res://.godot/imported/journal_pressed.png-5afac07ed149c485db76f20cf0200bfc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/journal_pressed.png" +dest_files=["res://.godot/imported/journal_pressed.png-5afac07ed149c485db76f20cf0200bfc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/journal_resized.aseprite b/Art/Rooms/screening_room/journal_resized.aseprite new file mode 100644 index 0000000..4257249 Binary files /dev/null and b/Art/Rooms/screening_room/journal_resized.aseprite differ diff --git a/Art/Rooms/screening_room/journal_unpressed.png b/Art/Rooms/screening_room/journal_unpressed.png new file mode 100644 index 0000000..c3c759a Binary files /dev/null and b/Art/Rooms/screening_room/journal_unpressed.png differ diff --git a/Art/Rooms/screening_room/journal_unpressed.png.import b/Art/Rooms/screening_room/journal_unpressed.png.import new file mode 100644 index 0000000..317bf8a --- /dev/null +++ b/Art/Rooms/screening_room/journal_unpressed.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4tgfgi4earv1" +path="res://.godot/imported/journal_unpressed.png-473aa7d390269ab7399fd5cdc15bbeb3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/journal_unpressed.png" +dest_files=["res://.godot/imported/journal_unpressed.png-473aa7d390269ab7399fd5cdc15bbeb3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/middle_screen.aseprite b/Art/Rooms/screening_room/middle_screen.aseprite new file mode 100644 index 0000000..66ba1e9 Binary files /dev/null and b/Art/Rooms/screening_room/middle_screen.aseprite differ diff --git a/Art/Rooms/screening_room/middle_screen.png b/Art/Rooms/screening_room/middle_screen.png new file mode 100644 index 0000000..889605b Binary files /dev/null and b/Art/Rooms/screening_room/middle_screen.png differ diff --git a/Art/Rooms/screening_room/middle_screen.png.import b/Art/Rooms/screening_room/middle_screen.png.import new file mode 100644 index 0000000..97d5fec --- /dev/null +++ b/Art/Rooms/screening_room/middle_screen.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b21h7s3820sc8" +path="res://.godot/imported/middle_screen.png-5df50600ef80bdd44b9d49ec3acab76b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/middle_screen.png" +dest_files=["res://.godot/imported/middle_screen.png-5df50600ef80bdd44b9d49ec3acab76b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/red_backroom.png b/Art/Rooms/screening_room/red_backroom.png new file mode 100644 index 0000000..0791085 Binary files /dev/null and b/Art/Rooms/screening_room/red_backroom.png differ diff --git a/Art/Rooms/screening_room/red_backroom.png.import b/Art/Rooms/screening_room/red_backroom.png.import new file mode 100644 index 0000000..8f39623 --- /dev/null +++ b/Art/Rooms/screening_room/red_backroom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cs4ofocaj6xdg" +path="res://.godot/imported/red_backroom.png-997cc1c9e16569e061d9a3119567666f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/red_backroom.png" +dest_files=["res://.godot/imported/red_backroom.png-997cc1c9e16569e061d9a3119567666f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/red_button.png b/Art/Rooms/screening_room/red_button.png new file mode 100644 index 0000000..0098a5b Binary files /dev/null and b/Art/Rooms/screening_room/red_button.png differ diff --git a/Art/Rooms/screening_room/red_button.png.import b/Art/Rooms/screening_room/red_button.png.import new file mode 100644 index 0000000..4038591 --- /dev/null +++ b/Art/Rooms/screening_room/red_button.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqn1o6cff1g4k" +path="res://.godot/imported/red_button.png-38f1381d15af377eb9dc1e8a89a0cfa5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/red_button.png" +dest_files=["res://.godot/imported/red_button.png-38f1381d15af377eb9dc1e8a89a0cfa5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/red_button_hover.png b/Art/Rooms/screening_room/red_button_hover.png new file mode 100644 index 0000000..b8ede54 Binary files /dev/null and b/Art/Rooms/screening_room/red_button_hover.png differ diff --git a/Art/Rooms/screening_room/red_button_hover.png.import b/Art/Rooms/screening_room/red_button_hover.png.import new file mode 100644 index 0000000..3f293f6 --- /dev/null +++ b/Art/Rooms/screening_room/red_button_hover.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c35f0aquvroje" +path="res://.godot/imported/red_button_hover.png-4f9a9f69a7a02da63cb0d5809b7d3031.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/red_button_hover.png" +dest_files=["res://.godot/imported/red_button_hover.png-4f9a9f69a7a02da63cb0d5809b7d3031.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/red_button_pressed.png b/Art/Rooms/screening_room/red_button_pressed.png new file mode 100644 index 0000000..540bda7 Binary files /dev/null and b/Art/Rooms/screening_room/red_button_pressed.png differ diff --git a/Art/Rooms/screening_room/red_button_pressed.png.import b/Art/Rooms/screening_room/red_button_pressed.png.import new file mode 100644 index 0000000..985a1a7 --- /dev/null +++ b/Art/Rooms/screening_room/red_button_pressed.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://kfq5b58sicqf" +path="res://.godot/imported/red_button_pressed.png-2b6b8a16406776742b93fa7c9dd2f9c1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/red_button_pressed.png" +dest_files=["res://.godot/imported/red_button_pressed.png-2b6b8a16406776742b93fa7c9dd2f9c1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/release.aseprite b/Art/Rooms/screening_room/release.aseprite new file mode 100644 index 0000000..9c872c0 Binary files /dev/null and b/Art/Rooms/screening_room/release.aseprite differ diff --git a/Art/Rooms/screening_room/release_off.png b/Art/Rooms/screening_room/release_off.png new file mode 100644 index 0000000..3776a66 Binary files /dev/null and b/Art/Rooms/screening_room/release_off.png differ diff --git a/Art/Rooms/screening_room/release_off.png.import b/Art/Rooms/screening_room/release_off.png.import new file mode 100644 index 0000000..c1ef3e7 --- /dev/null +++ b/Art/Rooms/screening_room/release_off.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://n2k0abd5iwrn" +path="res://.godot/imported/release_off.png-0702aba7be449505953cd0383ea1aef1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/release_off.png" +dest_files=["res://.godot/imported/release_off.png-0702aba7be449505953cd0383ea1aef1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/release_on.png b/Art/Rooms/screening_room/release_on.png new file mode 100644 index 0000000..73a1fa5 Binary files /dev/null and b/Art/Rooms/screening_room/release_on.png differ diff --git a/Art/Rooms/screening_room/release_on.png.import b/Art/Rooms/screening_room/release_on.png.import new file mode 100644 index 0000000..689c88b --- /dev/null +++ b/Art/Rooms/screening_room/release_on.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfi85pwknn8dw" +path="res://.godot/imported/release_on.png-cdf8a5dacfe20cb33d44b814e4ed12c3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/release_on.png" +dest_files=["res://.godot/imported/release_on.png-cdf8a5dacfe20cb33d44b814e4ed12c3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/release_resized.aseprite b/Art/Rooms/screening_room/release_resized.aseprite new file mode 100644 index 0000000..e028613 Binary files /dev/null and b/Art/Rooms/screening_room/release_resized.aseprite differ diff --git a/Art/Rooms/screening_room/screening_room.aseprite b/Art/Rooms/screening_room/screening_room.aseprite new file mode 100644 index 0000000..15cb238 Binary files /dev/null and b/Art/Rooms/screening_room/screening_room.aseprite differ diff --git a/Art/Rooms/screening_room/screening_room.png b/Art/Rooms/screening_room/screening_room.png new file mode 100644 index 0000000..9b4029a Binary files /dev/null and b/Art/Rooms/screening_room/screening_room.png differ diff --git a/Art/Rooms/screening_room/screening_room.png.import b/Art/Rooms/screening_room/screening_room.png.import new file mode 100644 index 0000000..858de48 --- /dev/null +++ b/Art/Rooms/screening_room/screening_room.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ds1whnmwt7xjt" +path="res://.godot/imported/screening_room.png-0463338d87078a7a4198dd0e7c8bd531.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/screening_room.png" +dest_files=["res://.godot/imported/screening_room.png-0463338d87078a7a4198dd0e7c8bd531.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/screening_room_bis.aseprite b/Art/Rooms/screening_room/screening_room_bis.aseprite new file mode 100644 index 0000000..735fa7b Binary files /dev/null and b/Art/Rooms/screening_room/screening_room_bis.aseprite differ diff --git a/Art/Rooms/screening_room/screening_room_glass.png b/Art/Rooms/screening_room/screening_room_glass.png new file mode 100644 index 0000000..52897b3 Binary files /dev/null and b/Art/Rooms/screening_room/screening_room_glass.png differ diff --git a/Art/Rooms/screening_room/screening_room_glass.png.import b/Art/Rooms/screening_room/screening_room_glass.png.import new file mode 100644 index 0000000..190cde0 --- /dev/null +++ b/Art/Rooms/screening_room/screening_room_glass.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2epvjeqvmgrs" +path="res://.godot/imported/screening_room_glass.png-ec4ab43e606292a880fcc2fbdab14a28.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/screening_room_glass.png" +dest_files=["res://.godot/imported/screening_room_glass.png-ec4ab43e606292a880fcc2fbdab14a28.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/screening_room_glass_red.png.import b/Art/Rooms/screening_room/screening_room_glass_red.png.import new file mode 100644 index 0000000..fa72ada --- /dev/null +++ b/Art/Rooms/screening_room/screening_room_glass_red.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6exxdnqphxbw" +path="res://.godot/imported/screening_room_glass_red.png-4e2af6e69e2282b895a561723a70b853.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/screening_room_glass_red.png" +dest_files=["res://.godot/imported/screening_room_glass_red.png-4e2af6e69e2282b895a561723a70b853.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/screening_room_resized.aseprite b/Art/Rooms/screening_room/screening_room_resized.aseprite new file mode 100644 index 0000000..52e5dbb Binary files /dev/null and b/Art/Rooms/screening_room/screening_room_resized.aseprite differ diff --git a/Art/Rooms/screening_room/screening_room_upper.aseprite b/Art/Rooms/screening_room/screening_room_upper.aseprite new file mode 100644 index 0000000..12c7a20 Binary files /dev/null and b/Art/Rooms/screening_room/screening_room_upper.aseprite differ diff --git a/Art/Rooms/screening_room/shutter_bottom.aseprite b/Art/Rooms/screening_room/shutter_bottom.aseprite new file mode 100644 index 0000000..0dd1aa9 Binary files /dev/null and b/Art/Rooms/screening_room/shutter_bottom.aseprite differ diff --git a/Art/Rooms/screening_room/shutter_bottom.png b/Art/Rooms/screening_room/shutter_bottom.png new file mode 100644 index 0000000..25981f4 Binary files /dev/null and b/Art/Rooms/screening_room/shutter_bottom.png differ diff --git a/Art/Rooms/screening_room/shutter_bottom.png.import b/Art/Rooms/screening_room/shutter_bottom.png.import new file mode 100644 index 0000000..4880250 --- /dev/null +++ b/Art/Rooms/screening_room/shutter_bottom.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdbiwtt2w37kr" +path="res://.godot/imported/shutter_bottom.png-3770ab6aad5aaee5b614c1044ba685cb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/shutter_bottom.png" +dest_files=["res://.godot/imported/shutter_bottom.png-3770ab6aad5aaee5b614c1044ba685cb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/shutter_bottom_resized.aseprite b/Art/Rooms/screening_room/shutter_bottom_resized.aseprite new file mode 100644 index 0000000..c76551e Binary files /dev/null and b/Art/Rooms/screening_room/shutter_bottom_resized.aseprite differ diff --git a/Art/Rooms/screening_room/shutter_top.aseprite b/Art/Rooms/screening_room/shutter_top.aseprite new file mode 100644 index 0000000..ac766e9 Binary files /dev/null and b/Art/Rooms/screening_room/shutter_top.aseprite differ diff --git a/Art/Rooms/screening_room/shutter_top.png b/Art/Rooms/screening_room/shutter_top.png new file mode 100644 index 0000000..8027c90 Binary files /dev/null and b/Art/Rooms/screening_room/shutter_top.png differ diff --git a/Art/Rooms/screening_room/shutter_top.png.import b/Art/Rooms/screening_room/shutter_top.png.import new file mode 100644 index 0000000..773c5bf --- /dev/null +++ b/Art/Rooms/screening_room/shutter_top.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4qtc0tkq2wba" +path="res://.godot/imported/shutter_top.png-47dbf9fb0676152bd2c08e9275254d00.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/shutter_top.png" +dest_files=["res://.godot/imported/shutter_top.png-47dbf9fb0676152bd2c08e9275254d00.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/shutter_top_resized.aseprite b/Art/Rooms/screening_room/shutter_top_resized.aseprite new file mode 100644 index 0000000..388e362 Binary files /dev/null and b/Art/Rooms/screening_room/shutter_top_resized.aseprite differ diff --git a/Art/Rooms/screening_room/shutters.aseprite b/Art/Rooms/screening_room/shutters.aseprite new file mode 100644 index 0000000..6a625eb Binary files /dev/null and b/Art/Rooms/screening_room/shutters.aseprite differ diff --git a/Art/Rooms/screening_room/tea_cup.aseprite b/Art/Rooms/screening_room/tea_cup.aseprite new file mode 100644 index 0000000..dfb2f82 Binary files /dev/null and b/Art/Rooms/screening_room/tea_cup.aseprite differ diff --git a/Art/Rooms/screening_room/tea_cup.png b/Art/Rooms/screening_room/tea_cup.png new file mode 100644 index 0000000..ba35b2e Binary files /dev/null and b/Art/Rooms/screening_room/tea_cup.png differ diff --git a/Art/Rooms/screening_room/tea_cup.png.import b/Art/Rooms/screening_room/tea_cup.png.import new file mode 100644 index 0000000..534f656 --- /dev/null +++ b/Art/Rooms/screening_room/tea_cup.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djhhkejop4636" +path="res://.godot/imported/tea_cup.png-b1ae6d4560e5ac222edbcf15f37afaf6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/tea_cup.png" +dest_files=["res://.godot/imported/tea_cup.png-b1ae6d4560e5ac222edbcf15f37afaf6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/tea_cup_resized.aseprite b/Art/Rooms/screening_room/tea_cup_resized.aseprite new file mode 100644 index 0000000..78f7684 Binary files /dev/null and b/Art/Rooms/screening_room/tea_cup_resized.aseprite differ diff --git a/Art/Rooms/screening_room/terminate.aseprite b/Art/Rooms/screening_room/terminate.aseprite new file mode 100644 index 0000000..bc618a1 Binary files /dev/null and b/Art/Rooms/screening_room/terminate.aseprite differ diff --git a/Art/Rooms/screening_room/terminate_off.png b/Art/Rooms/screening_room/terminate_off.png new file mode 100644 index 0000000..9f9aa22 Binary files /dev/null and b/Art/Rooms/screening_room/terminate_off.png differ diff --git a/Art/Rooms/screening_room/terminate_off.png.import b/Art/Rooms/screening_room/terminate_off.png.import new file mode 100644 index 0000000..fc290d5 --- /dev/null +++ b/Art/Rooms/screening_room/terminate_off.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvns3403af0no" +path="res://.godot/imported/terminate_off.png-ff94952be4de92a8e958e7d9a348ac99.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/terminate_off.png" +dest_files=["res://.godot/imported/terminate_off.png-ff94952be4de92a8e958e7d9a348ac99.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/terminate_on.png b/Art/Rooms/screening_room/terminate_on.png new file mode 100644 index 0000000..4aaf691 Binary files /dev/null and b/Art/Rooms/screening_room/terminate_on.png differ diff --git a/Art/Rooms/screening_room/terminate_on.png.import b/Art/Rooms/screening_room/terminate_on.png.import new file mode 100644 index 0000000..171d13e --- /dev/null +++ b/Art/Rooms/screening_room/terminate_on.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b4fe8gvg86l4f" +path="res://.godot/imported/terminate_on.png-cc42d1d049d48f4f0d7dfb1da605320d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/Rooms/screening_room/terminate_on.png" +dest_files=["res://.godot/imported/terminate_on.png-cc42d1d049d48f4f0d7dfb1da605320d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/Rooms/screening_room/terminate_resized.aseprite b/Art/Rooms/screening_room/terminate_resized.aseprite new file mode 100644 index 0000000..7e7b3e8 Binary files /dev/null and b/Art/Rooms/screening_room/terminate_resized.aseprite differ diff --git a/Art/ScreeningDesk.aseprite b/Art/ScreeningDesk.aseprite new file mode 100644 index 0000000..fd3b691 Binary files /dev/null and b/Art/ScreeningDesk.aseprite differ diff --git a/Art/ScreeningDesk.png b/Art/ScreeningDesk.png deleted file mode 100644 index 193c805..0000000 Binary files a/Art/ScreeningDesk.png and /dev/null differ diff --git a/Art/ScreeningRoom 2.aseprite b/Art/ScreeningRoom 2.aseprite new file mode 100644 index 0000000..539a589 Binary files /dev/null and b/Art/ScreeningRoom 2.aseprite differ diff --git a/Art/ScreeningRoom.aseprite b/Art/ScreeningRoom.aseprite new file mode 100644 index 0000000..0bd7a97 Binary files /dev/null and b/Art/ScreeningRoom.aseprite differ diff --git a/Art/Sprite-0009.aseprite b/Art/Sprite-0009.aseprite new file mode 100644 index 0000000..cb32950 Binary files /dev/null and b/Art/Sprite-0009.aseprite differ diff --git a/Art/UI/button_theme.tres b/Art/UI/button_theme.tres new file mode 100644 index 0000000..754a1bf --- /dev/null +++ b/Art/UI/button_theme.tres @@ -0,0 +1,24 @@ +[gd_resource type="Theme" load_steps=6 format=3 uid="uid://3k7sqw211ncl"] + +[ext_resource type="FontFile" uid="uid://dv8ppra5tgn1t" path="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" id="1_bcl6o"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ksx6h"] +bg_color = Color(0.0784314, 0.0784314, 0.0784314, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mpwjd"] +bg_color = Color(0.352941, 0.352941, 0.352941, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_erjap"] +bg_color = Color(0.196078, 0.196078, 0.196078, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3ta32"] +bg_color = Color(0.509804, 0.509804, 0.509804, 1) + +[resource] +Button/colors/font_color = Color(0.87451, 0.87451, 0.87451, 1) +Button/colors/font_disabled_color = Color(0.875, 0.875, 0.875, 0.5) +Button/fonts/font = ExtResource("1_bcl6o") +Button/styles/disabled = SubResource("StyleBoxFlat_ksx6h") +Button/styles/hover = SubResource("StyleBoxFlat_mpwjd") +Button/styles/normal = SubResource("StyleBoxFlat_erjap") +Button/styles/pressed = SubResource("StyleBoxFlat_3ta32") diff --git a/Art/UI/menu_gear.aseprite b/Art/UI/menu_gear.aseprite new file mode 100644 index 0000000..0b9fcb6 Binary files /dev/null and b/Art/UI/menu_gear.aseprite differ diff --git a/Art/UI/menu_gear.png b/Art/UI/menu_gear.png new file mode 100644 index 0000000..387e750 Binary files /dev/null and b/Art/UI/menu_gear.png differ diff --git a/Art/UI/menu_gear.png.import b/Art/UI/menu_gear.png.import new file mode 100644 index 0000000..55f96e4 --- /dev/null +++ b/Art/UI/menu_gear.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://l7786mlmvet8" +path="res://.godot/imported/menu_gear.png-b63d423f99c86dea23eac1ace1b5a4d1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/UI/menu_gear.png" +dest_files=["res://.godot/imported/menu_gear.png-b63d423f99c86dea23eac1ace1b5a4d1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/UI/tablet.aseprite b/Art/UI/tablet.aseprite new file mode 100644 index 0000000..5564bf6 Binary files /dev/null and b/Art/UI/tablet.aseprite differ diff --git a/Art/UI/tablet.png b/Art/UI/tablet.png new file mode 100644 index 0000000..c1881ce Binary files /dev/null and b/Art/UI/tablet.png differ diff --git a/Art/UI/tablet.png.import b/Art/UI/tablet.png.import new file mode 100644 index 0000000..e8cf7ee --- /dev/null +++ b/Art/UI/tablet.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ctmr6cncu5ir5" +path="res://.godot/imported/tablet.png-f48fbfc375db0b9050b449fee28e1aa9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Art/UI/tablet.png" +dest_files=["res://.godot/imported/tablet.png-f48fbfc375db0b9050b449fee28e1aa9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Art/UI/tablet_resized.aseprite b/Art/UI/tablet_resized.aseprite new file mode 100644 index 0000000..780184c Binary files /dev/null and b/Art/UI/tablet_resized.aseprite differ diff --git a/Art/XanhMono-Italic.ttf b/Art/XanhMono-Italic.ttf deleted file mode 100644 index a37272a..0000000 Binary files a/Art/XanhMono-Italic.ttf and /dev/null differ diff --git a/Art/XanhMono-Italic.ttf.import b/Art/XanhMono-Italic.ttf.import deleted file mode 100644 index 40c57cb..0000000 --- a/Art/XanhMono-Italic.ttf.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="font_data_dynamic" -type="FontFile" -uid="uid://mi5v1uq78g7o" -path="res://.godot/imported/XanhMono-Italic.ttf-0e793ae2396790fa8769f584957eb932.fontdata" - -[deps] - -source_file="res://Art/XanhMono-Italic.ttf" -dest_files=["res://.godot/imported/XanhMono-Italic.ttf-0e793ae2396790fa8769f584957eb932.fontdata"] - -[params] - -Rendering=null -antialiasing=1 -generate_mipmaps=false -disable_embedded_bitmaps=true -multichannel_signed_distance_field=false -msdf_pixel_range=8 -msdf_size=48 -allow_system_fallback=true -force_autohinter=false -hinting=1 -subpixel_positioning=1 -oversampling=0.0 -Fallbacks=null -fallbacks=[] -Compress=null -compress=true -preload=[] -language_support={} -script_support={} -opentype_features={} diff --git a/Art/XanhMono-Regular.ttf b/Art/XanhMono-Regular.ttf deleted file mode 100644 index 5155e2d..0000000 Binary files a/Art/XanhMono-Regular.ttf and /dev/null differ diff --git a/Art/angry_monk.aseprite b/Art/angry_monk.aseprite new file mode 100644 index 0000000..1e54873 Binary files /dev/null and b/Art/angry_monk.aseprite differ diff --git a/Art/laughing_monk.aseprite b/Art/laughing_monk.aseprite new file mode 100644 index 0000000..6e439c7 Binary files /dev/null and b/Art/laughing_monk.aseprite differ diff --git a/Art/meeting_room.png.import b/Art/meeting_room.png.import index e9c3d93..cc9e2dd 100644 --- a/Art/meeting_room.png.import +++ b/Art/meeting_room.png.import @@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/meeting_room.png-a1beaf0a6ed858164e9efb73c744 compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -25,6 +27,10 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/Art/old_man.aseprite b/Art/old_man.aseprite new file mode 100644 index 0000000..445955c Binary files /dev/null and b/Art/old_man.aseprite differ diff --git a/Art/old_man_2.aseprite b/Art/old_man_2.aseprite new file mode 100644 index 0000000..8059cc2 Binary files /dev/null and b/Art/old_man_2.aseprite differ diff --git a/Art/recovery_room.png.import b/Art/recovery_room.png.import index a056185..182b183 100644 --- a/Art/recovery_room.png.import +++ b/Art/recovery_room.png.import @@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/recovery_room.png-d4d54c93d559cd3cd2958e41cff compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -25,6 +27,10 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/Bedroom.tscn b/Bedroom.tscn deleted file mode 100644 index f265b6e..0000000 --- a/Bedroom.tscn +++ /dev/null @@ -1,53 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://dcjhx2g4d056v"] - -[ext_resource type="Script" path="res://bedroom.gd" id="1_di4oy"] -[ext_resource type="Texture2D" uid="uid://c3je4gir1qlot" path="res://Art/BlackScreen.png" id="2_trxfw"] -[ext_resource type="Texture2D" uid="uid://bs1t18iatp3rk" path="res://Art/recovery_room.png" id="3_le6i2"] - -[node name="Corridor" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_di4oy") - -[node name="ColorRect" type="ColorRect" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -color = Color(0, 0, 0, 1) - -[node name="Background" type="VBoxContainer" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -theme_override_constants/separation = 0 - -[node name="UpperBorder" type="TextureRect" parent="Background"] -layout_mode = 2 -size_flags_vertical = 3 -size_flags_stretch_ratio = 0.1 -texture = ExtResource("2_trxfw") -expand_mode = 1 - -[node name="Background" type="TextureRect" parent="Background"] -layout_mode = 2 -size_flags_vertical = 3 -size_flags_stretch_ratio = 1.5 -texture = ExtResource("3_le6i2") -expand_mode = 1 - -[node name="LowerBorder" type="TextureRect" parent="Background"] -layout_mode = 2 -size_flags_vertical = 3 -size_flags_stretch_ratio = 0.1 -texture = ExtResource("2_trxfw") -expand_mode = 1 diff --git a/Dialogues/Patients/patient_2202.dialogue b/Dialogues/Patients/patient_2202.dialogue new file mode 100644 index 0000000..428d75e --- /dev/null +++ b/Dialogues/Patients/patient_2202.dialogue @@ -0,0 +1,100 @@ +~ start + +do Patient.marker.fill(0) +set Patient.dialog_tree = Patient.build_dialog_tree([31, 32, 33, 34]) + +The patient looks visibly distraught, fidgeting with anxiety. + +Gwendoline Gorbado: [#worried] Who are you ?! Where am I ?? + +- Decline your identity. + Gwendoline Gorbado: [#worried] I don't understand, please, tell me where I am... + => question_2 + +- I am trying to help you here. Calm down and tell me who you are. + do Patient.marker[0] += 1 + Gwendoline Gorbado: [#worried] A-alright, my name is Gwendoline Gorbado. Please, i need to know where I am ! + => question_2 + + +~ question_2 + +- Focus on answering my questions. + Gwendoline Gorbado: [#sad] Why is this happening to me... + => question_3 + +- I am sorry, i cannot disclose our location. I might be able to help if you answer my questions. + do Patient.marker[0] += 1 + Gwendoline Gorbado: [#neutral] O-okay, okay, I'll trust you. What do you need to know ? + => question_3 + + +~ question_3 + +if Patient.marker[1] == 4 + => question_4 + +- What is your job ? [if Patient.dialog_tree[31] == "not_asked"] + set Patient.dialog_tree[31] = "asked" + do Patient.marker[1] += 1 + Gwendoline Gorbado: [#sad] I work in IT... It's a really hard job, you know. An awful lot of pressure for a sliver of recognition. + => question_3 + +- How was your mood lately ? [if Patient.dialog_tree[32] == "not_asked"] + set Patient.dialog_tree[32] = "asked" + do Patient.marker[1] += 1 + Gwendoline Gorbado: [#sad] I'm feeling not fine at all to be honest, I've been having some really dark ideas for a while... + => question_3 + +- Have you been having panic attacks recently ? [if Patient.dialog_tree[33] == "not_asked"] + set Patient.dialog_tree[33] = "asked" + do Patient.marker[1] += 1 + Gwendoline Gorbado: [#worried] Yes, a lot. Just thinking about my work makes me so stressed I want to puke. + => question_3 + +- What is your favorite meal ? [if Patient.dialog_tree[34] == "not_asked"] + set Patient.dialog_tree[34] = "asked" + do Patient.marker[1] += 1 + Gwendoline Gorbado: [#smiling] M-my favourite meal ? I... It's probably Jajangmyeon. Korean noodles, very tasty. Such a weird question... + => question_3 + + +~ question_4 + +- Are you an Abnormal ? + Gwendoline Gorbado: [#irritated] What ? No, never ! How would i even be feeling sad if I were one of those creatures ?! + => question_5 + +- Have you been lying to me ? + Gwendoline Gorbado: [#worried] No, I told you the truth, i swear ! + => question_5 + + +~ question_5 + +- Prove to me you are human. + if Patient.marker[0] == 2 + Gwendoline Gorbado: [#sad] I don't know what to tell you... Just do what you have to. You know, I've been thinking about ending it all myself, I'd even bought a rope. I feel like there's no hope anymore, nothing. + else + Gwendoline Gorbado: [#irritated] I've told you everything. Just make your decision now, I am powerless anyways. + +do Globals.console_message.emit("[color=dark_orange]ALLOTED ASSESSMENT TIME ELAPSED[/color]\n\n[color=orange_red]PLEASE MAKE A DECISION[/color]") + +=> END + + + +~ terminate + +Gwendoline Gorbado: [#sad] Maybe this is what i deserve... +Gwendoline Gorbado: I hope you'll make it quick... + +=> END + + + +~ release + +Gwendoline Gorbado: [#smiling] You are kind. I wish i had met people like you in my life. Thank you. + +=> END \ No newline at end of file diff --git a/Dialogues/Patients/patient_2202.dialogue.import b/Dialogues/Patients/patient_2202.dialogue.import new file mode 100644 index 0000000..c5e697e --- /dev/null +++ b/Dialogues/Patients/patient_2202.dialogue.import @@ -0,0 +1,16 @@ +[remap] + +importer="dialogue_manager" +importer_version=15 +type="Resource" +uid="uid://cocg8tx4s4rj3" +path="res://.godot/imported/patient_2202.dialogue-2dd94b65ae52332b55f15ada961486ad.tres" + +[deps] + +source_file="res://Dialogues/Patients/patient_2202.dialogue" +dest_files=["res://.godot/imported/patient_2202.dialogue-2dd94b65ae52332b55f15ada961486ad.tres"] + +[params] + +defaults=true diff --git a/Dialogues/Patients/patient_2202_file.cfg b/Dialogues/Patients/patient_2202_file.cfg new file mode 100644 index 0000000..be788ec --- /dev/null +++ b/Dialogues/Patients/patient_2202_file.cfg @@ -0,0 +1,6 @@ +initial_emotion="worried" +name="Gwendoline Gorbado" +age="28" +profession="IT Engineer" +psychiatric_assessment="DEPRESSION\n\nANXIETY" +status="normal" diff --git a/Dialogues/Patients/patient_48.dialogue b/Dialogues/Patients/patient_48.dialogue new file mode 100644 index 0000000..d007ed5 --- /dev/null +++ b/Dialogues/Patients/patient_48.dialogue @@ -0,0 +1,121 @@ +~ start + +do Patient.marker.fill(0) +set Patient.dialog_tree = Patient.build_dialog_tree([11,12,21,22]) + +Phil Fossa: [#smiling] About time ! You there ! Release me at once ! + +- Hi. Let us talk first, and then we'll see what I can do about that. + Phil Fossa: [#puzzled] Talk ? What's the point of talking when you could just ACT ? + Phil Fossa: [#neutral] ... Fine, let's get this over with. Ask your petty questions. + => part_1 + +- Greetings, sir. You may do well to lower your tone if you expect my help. + Phil Fossa: [#irritated] Are you threatening me ?? How unpolite is that ?! I'll sue you ! + => part_0 + +- Is that how you speak to your students ? + Phil Fossa: [#puzzled] Well, obviously yes ? That's how you teach stuff to unruly youngsters. Through sheer authority and charisma. + => part_0 + +- Big words for such a small dude... + Phil Fossa: [#angry] The size of the body does not beget the size of the mind, you uneducated wretch ! Beware the anger of the cultivated intellect ! + => part_0 + + +~ part_0 +- Alright alright, let's chill. I just need to ask you some questions before I release you. Please bear with me. + Phil Fossa: [#irritated] Questions ? I guess I'll spare you some of my precious time. Make it quick, peasant. + => part_1 + +~ part_1 + +if Patient.marker[0] == 2 + => part_2 + +- Are there people conspiring against you or threatening you ? [if Patient.dialog_tree[11] == "not_asked"] + set Patient.dialog_tree[11] = "asked" + do Patient.marker[0] += 1 + Phil Fossa: [#suspicious] How did you know ? Are you working for them ? I didn't think they'd find me again so quickly... + Phil Fossa: [#smiling] Nah, you're too dumb to be one of them. You're safe. + + - Please focus, sir. Are there people conspiring against you, or threatening you ? + Phil Fossa: [#neutral] Yeah, yeah. It's been more than 40 years, I'm used to it. + Phil Fossa: [#irritated] They try to mic up my flat every other Sunday, to steal my knowledge. They follow me when I'm outside. + Phil Fossa: [#smiling] But they're no match for me. I outsmart them at every corner. + + - Who are they ? + Phil Fossa: [#puzzled] Well, people from the Learning Rogues, of course. Thought it was common knowledge. How uncultured are you ? + Phil Fossa: [#irritated] They're a secret organization from times immemorial. They actively work to siphon knowledge from high-intellect individuals like me. + Phil Fossa: [#neutral] Satisfied ? Care to release me now ? + => part_1 + +- You seem to value yourself highly. Why is that ? [if Patient.dialog_tree[12] == "not_asked"] + set Patient.dialog_tree[12] = "asked" + do Patient.marker[0] += 1 + Phil Fossa: [#puzzled] Why ? You have one of the nine wise Sages standing before you, that's why ! Your lack of knowledge baffles me. + Phil Fossa: [#smiling] I was chosen at birth to learn the secrets of infinite Wisdom. I could utter a single word and hurl myself out of here in an instant if i wished to. + + - Then why aren't you teleporting yourself out of here ? + Phil Fossa: [#irritated] I tried to, but it ain't working here. Probably a trick of the Learning Rogues. I need some time to decipher their scheming. + => part_1 + + +~ part_2 + +if Patient.marker[1] == 2 + => part_3 + +- Did the Learning Rogues follow you here ? Are they watching you right now ? [if Patient.dialog_tree[21] == "not_asked"] + set Patient.dialog_tree[21] = "asked" + do Patient.marker[1] += 1 + Phil Fossa: [#suspicious] Yes, they followed me. They hid mics in my room. Trying to capture my secrets. I won't let them. + + - How do you know they hid mics here ? + if Patient.abnormal == true + Phil Fossa: [#worried] Because I can hear them talking on the mics. Laughing about me. Telling jokes about how they'll steal my knowledge until I'm nothing. I'm nothing. + else + Phil Fossa: [#smiling] I just know it. Never doubt the instincts of a wise Sage. The world shows itself to me in paths beyond the ken of your limited abilities. + => part_2 + +- Why a knowledge stealing organization ? Why this wise Sage bullshit ? [if Patient.dialog_tree[22] == "not_asked"] + set Patient.dialog_tree[22] = "asked" + do Patient.marker[1] += 1 + Phil Fossa: [#angry] What ?! How dare you speak to me like this ?! To one of the Nine ?? + Phil Fossa: [#worried] I... The kids... They never listen ! I teach them with all the passion in the world, but they don't do their homework, they stay on their phones during class ! + Phil Fossa: [#scared] I have so much to give them, but they don't care ! Why ? I give them my life, and what do I get in return ? What's the point ?? + Phil Fossa: [#smiling] The Learning Rogues do care ! They understand the importance of knowledge, they understand how wise I am ! + => part_2 + + +~ part_3 +Phil Fossa: [#irritated] That's enough ! I've anwered enough of your silly questions. +Phil Fossa: [#angry] Now release me at once ! I'm not talking to you until you've done so. +Phil Fossa: [#smiling] So the wise Sage hath spoken. + + +do Globals.console_message.emit("[color=dark_orange]ALLOTED ASSESSMENT TIME ELAPSED[/color]\n\n[color=orange_red]PLEASE MAKE A DECISION[/color]") + +=> END + + +~ terminate + +if Patient.abnormal == true + Phil Fossa: [#abnormal] How did you know, you fucking dog ? I'll tear the flesh from your bones. I'll gorge myself on your entrails. I'll- +else + Phil Fossa: [#scared] What ?? How dare you ! You can't do that ! I'm one of the Sages !! + Phil Fossa: [#panicked] Please no ! I'm sorry, please don't do that ! I swear I'm not sick ! I beg you !! Please I'm so sorry !! Nononono- + +=> END + + +~ release + +if Patient.abnormal == true + Phil Fossa: [#abnormal] Thank you. At last, my freedom awaits on the surface. It feels good, so good... +else + Phil Fossa: [#smiling] Thank you. I knew you would have some common sense. + Phil Fossa: [#smiling] So long, and may our paths never cross again. Conversing with your inferior mind is at risk of damaging my hallowed intellect. + +=> END \ No newline at end of file diff --git a/Dialogues/Patients/patient_48.dialogue.import b/Dialogues/Patients/patient_48.dialogue.import new file mode 100644 index 0000000..fb1ddf2 --- /dev/null +++ b/Dialogues/Patients/patient_48.dialogue.import @@ -0,0 +1,16 @@ +[remap] + +importer="dialogue_manager" +importer_version=15 +type="Resource" +uid="uid://dx2bpp3qwpyy8" +path="res://.godot/imported/patient_48.dialogue-f01d9c4b5474d92c5db61e8467f277e9.tres" + +[deps] + +source_file="res://Dialogues/Patients/patient_48.dialogue" +dest_files=["res://.godot/imported/patient_48.dialogue-f01d9c4b5474d92c5db61e8467f277e9.tres"] + +[params] + +defaults=true diff --git a/Dialogues/Patients/patient_48_file.cfg b/Dialogues/Patients/patient_48_file.cfg new file mode 100644 index 0000000..0cf9841 --- /dev/null +++ b/Dialogues/Patients/patient_48_file.cfg @@ -0,0 +1,6 @@ +initial_emotion="irritated" +name="Phil Fossa" +age="65" +profession="College Teacher" +psychiatric_assessment="PERSECUTORY DELUSIONS\n\nMEGALOMANIAC DELUSIONS" +status="random" diff --git a/Dialogues/Patients/patient_49.dialogue b/Dialogues/Patients/patient_49.dialogue new file mode 100644 index 0000000..c5bce7b --- /dev/null +++ b/Dialogues/Patients/patient_49.dialogue @@ -0,0 +1,245 @@ +~ start + +do Patient.marker.fill(0) +set Patient.dialog_tree = Patient.build_dialog_tree([31,32,33,34,41,42,43,44,411,412,413,421,422,423]) + +Jeremiah Brunn: [#smiling] Hi. What's up ? You here to release me ? + +- Hello. I am here to assess that indeed. + Jeremiah Brunn: [#smiling] Good, good. Ask your questions, I am ready. + => part_2 + +- That'll depend on what you tell me. + Jeremiah Brunn: [#smiling] Sounds fair. Let's do it then. + => part_2 +- I ask the questions, you answer. Got it ? + if Patient.abnormal == true + Jeremiah Brunn: [#neutral] Fine. I hope that glass between us is sturdy enough, though. For your sake. + else + Jeremiah Brunn: [#smiling] Alright, alright, chill out, friend. Ask away. + => part_2 + +~ part_2 + +- Why were you brought here ? What happened ? + Jeremiah Brunn: [#neutral] I was busy minding my own quiet business, when these so-called "law enforcers" jumped me. + Jeremiah Brunn: [#irritated] I was detained and transferred here against my will. Once I'm out of here, I'm going to sue them to oblivion. + + - What kind of quiet business warrants detainment ? What exactly were you doing out there ? + Jeremiah Brunn: [#neutral] I was simply playing some music. That's my job after all. Can you blame someone for singing aloud in public ? Is that a way to treat a star like me ? + + - A star ? What do you mean ? + Jeremiah Brunn: [#puzzled] What do you mean, what do I mean ? Don't you recognize me ?! + + - I am afraid I don't. + if Patient.abnormal == true + Jeremiah Brunn: [#irritated] People these days... Uncultured swine, good for the slaughter. + else + Jeremiah Brunn: [#puzzled] Sigh... I can't blame you. I haven't been able to take properly care of myself in this cell. + => part_2_1 + - Your face and name do ring a bell, but I can't quite remember who you are. + Jeremiah Brunn: [#puzzled] Sigh... I can't blame you. I haven't been able to take properly care of myself in this cell. + => part_2_1 + - Stop wasting time and decline your identity. + if Patient.abnormal == true + Jeremiah Brunn: [#irritated] Watch your tone. Disrespect always backfires on you at some point, sometimes earlier than you might expect. + else + Jeremiah Brunn: [#puzzled] Is that how you treat all your patients ? No wonder you're staying hidden behind a stained glass like a rat. I guess nobody likes you. + => part_2_1 + ~ part_2_1 + Jeremiah Brunn: [#smiling] Anyways, name's Jean Deep. Sorry, I'm not giving you an autograph. Not while I'm imprisoned here. + => part_3 + + +~ part_3 + +if Patient.marker[0] == 4 + => part_4_1 + +- Aren't you named Jeremiah Brunn though ? [if Patient.dialog_tree[31] == "not_asked"] + set Patient.dialog_tree[31] = "asked" + do Patient.marker[0] += 1 + Jeremiah Brunn: [#puzzled] Bah ! That's just my cover name, to dodge my stalkers. Simple, but effective. + => part_3 + +- You don't exactly look like Jean Deep though... [if Patient.dialog_tree[32] == "not_asked"] + set Patient.dialog_tree[32] = "asked" + do Patient.marker[0] += 1 + Jeremiah Brunn: [#neutral] I know ! I was bestowed a hallowed blessing from God, but that honour came with a curse... + Jeremiah Brunn: [#worried] My divine physique was altered. A small price to pay such a heavenly reward, but still a painful one. + => part_3 + +- What do you do in life ? [if Patient.dialog_tree[33] == "not_asked"] + set Patient.dialog_tree[33] = "asked" + do Patient.marker[0] += 1 + Jeremiah Brunn: [#smiling] I'm a rocker, of course. Rock'n Roll is my life, it's the blood in my veins. + + - As far as i know, Jean Deep is an actor, not a musician. + Jeremiah Brunn: [#laughing] Then you don't know very far, friend ! I am well reputed in both acting and rocking circles ! + Jeremiah Brunn: [#irritated] Although that doesn't surprise me... They have been conspiring against me, smearing my name. No wonder they erased public knowledge of my music career. + => part_3 + +- Isn't Jean Deep supposed to be in another country right now ? What brought you here so far from home ? [if Patient.dialog_tree[34] == "not_asked"] + set Patient.dialog_tree[34] = "asked" + do Patient.marker[0] += 1 + Jeremiah Brunn: [#laughing] Divine business, my friend ! Divine and secretive. They must not know what I am about to achieve here. + => part_3 + + +~ part_4 + +if Patient.marker[1] == 3 + Jeremiah Brunn: [#elated] Oh, the time of Ascension is nigh ! The Great Worm, at last ! + => part_5 + +- What divine blessing are you talking about ? [if Patient.dialog_tree[41] == "not_asked"] + set Patient.dialog_tree[41] = "asked" + do Patient.marker[1] += 1 + Jeremiah Brunn: [#laughing] I am talking about the most sanctified amongst God's gifts. It is the blessing of the Great Worm. + + - Care to elaborate, Jean Worm ? + if Patient.abnormal == true + Jeremiah Brunn: [#irritated] Careful. The early bird gets the worm, and the early worm gets the discourteous. + else + Jeremiah Brunn: [#puzzled] Jean Worm ? I'm sorry but your utter lack of taste for names is... unsettling. + => part_4_1_0 + + - What is this Great Worm blessing exactly ? + => part_4_1_0 + + ~ part_4_1_0 + + Jeremiah Brunn: [#smiling] God has bestowed upon me the most blessed of presents. I am to be transfigured in His name. + if Patient.abnormal == true + Jeremiah Brunn: [#laughing] I am to become the Worm. To consume God's enemies. To repast on flesh and bones. + else + Jeremiah Brunn: [#laughing] I am to become the Worm. To wield holiness on this Earth. To bring about his law and dominion to humankind. + + ~ part_4_1 + + if Patient.marker[2] == 3 + => part_4 + + - When did you receive this blessing ? [if Patient.dialog_tree[411] == "not_asked"] + set Patient.dialog_tree[411] = "asked" + do Patient.marker[2] += 1 + Jeremiah Brunn: [#smiling] A long, long time ago. I think i was around 19 when I saw It for the first time. The Great Worm, shining under God's light, promising me protection and eternal love. + Jeremiah Brunn: [#neutral] That is when I knew who I was, and who I was fated to become. Since then, life has been nothing but a long wait until my promised Ascension. + => part_4_1 + + - How are you so certain you are the chosen one ? [if Patient.dialog_tree[412] == "not_asked"] + set Patient.dialog_tree[412] = "asked" + do Patient.marker[2] += 1 + Jeremiah Brunn: [#happy] Because that's what God tells me, of course. I can hear Him whispering to me, quietly. Comforting me. + + - What is God telling you right now ? + if Patient.abnormal == true + Jeremiah Brunn: [#laughing] To kill His enemies. That my Deliverance is about to begin. That my first commandment is to consume you. + else + Jeremiah Brunn: [#happy] That everything's going to be fine. That I am safe. That I can no longer be harmed. + => part_4_1 + + - Of all possible divine creatures, why a Worm ? [if Patient.dialog_tree[413] == "not_asked"] + set Patient.dialog_tree[413] = "asked" + do Patient.marker[2] += 1 + Jeremiah Brunn: [#smiling] Because the Worm is a symbol of strength, of manhood ! God wants me to know I am a man. + Jeremiah Brunn: [#worried] Uncle would always tell me I was not a man, that I liked other men too much. + Jeremiah Brunn: [#happy] But God knows better. Thanks to Him, I know I like women. I'm sure of it. After all, Jean Deep is a womanizer, isn't he ? + => part_4_1 + + +- I want to talk about the people conspiring against you. Who are they ? [if Patient.dialog_tree[42] == "not_asked"] + set Patient.dialog_tree[42] = "asked" + do Patient.marker[1] += 1 + Jeremiah Brunn: [#worried] They are the secret Cabal of the Worm ! Working in my uncle's name, to prevent Jean Deep from ascending. You have to believe me. + => part_4_2 + + ~ part_4_2 + + if Patient.marker[3] == 3 + => part_4 + + - Your uncle leads a secret cabal ? [if Patient.dialog_tree[421] == "not_asked"] + set Patient.dialog_tree[421] = "asked" + do Patient.marker[3] += 1 + Jeremiah Brunn: [#shocked] Yes, he does ! Ever since I was a boy, he's been working to undermine me, to deprive me of my manhood ! + Jeremiah Brunn: [#irritated] No more. God is on my side. I'm fighting back now. + + - What did your uncle do to you ? + Jeremiah Brunn: [#irritated] He is the great Devil, never to be trusted again. He wounded me, stole my manhood when I was not prepared. + Jeremiah Brunn: [#happy] Now he cannot anymore. I am under God's watchful gaze. + => part_4_2 + + - Why's the Cabal of the Worm trying to prevent its return ? Shouldn't they be delighted to witness your ascension ? [if Patient.dialog_tree[422] == "not_asked"] + set Patient.dialog_tree[422] = "asked" + do Patient.marker[3] += 1 + Jeremiah Brunn: [#smiling] The Cabal of the Worm, cursed be their name, were founded precisely because they fear the Worm and its manhood. They want It to never exist. + Jeremiah Brunn: [#laughing] But the Great Worm cannot be stopped. I will reclaim my manhood. It is written. + => part_4_2 + + - Do you feel safe in this cell ? Do you think the cabal can reach you here ? [if Patient.dialog_tree[423] == "not_asked"] + set Patient.dialog_tree[423] = "asked" + do Patient.marker[3] += 1 + Jeremiah Brunn: [#worried] M-maybe... There is no place on Earth they cannot reach ! Their influence is boundless ! + if Patient.abnormal == true + Jeremiah Brunn: [#irritated] Let them try. The Worm hungers for a feast. + else + Jeremiah Brunn: [#happy] But God is here for me now. He whispers words of protection and safety. I am secure. I am safe. + => part_4_2 + +- And why has God chosen to bless you thus ? [if Patient.dialog_tree[43] == "not_asked"] + set Patient.dialog_tree[43] = "asked" + do Patient.marker[1] += 1 + Jeremiah Brunn: [#laughing] Because I am Jean Deep, obviously. It's God's way of saying thanks. + => part_4 + + +~ part_5 +Jeremiah Brunn: [#elated] God's will is clear to me now. I am to be His vessel. The Worm is coming. + +- What makes you think the Worm is coming now ? + if Patient.abnormal == true + Jeremiah Brunn: [#elated] Because I can feel it ! The Worm is coming ! I can feel It in me ! It is rearranging my insides ! + Jeremiah Brunn: [#maniac] My organs are transforming ! The Worm is coming ! I am becoming ! + else + Jeremiah Brunn: [#elated] Because God's telling me right now ! His whispers are deafening ! + Jeremiah Brunn: [#elated] The worm is coming ! I am becoming ! + => part_6 + + +~ part_6 + +- How are you going to ascend ? + Jeremiah Brunn: [#elated] Sliding into a hole, makes a grown man whole ! Sliding into my hole, makes my uncle the Devil ! Sliding once and for all, makes the Great Worm God's goal ! + + - Focus and talk to me, Mr Brunn, or I won't be able to help you. + Jeremiah Brunn: [#elated] A rocking worm worming between the rocks, the rock'n roll of the Jean Deep the rocker Worm ! + Jeremiah Brunn: [#maniac] Two rocks and a worm, manhood in its purest form ! + Jeremiah Brunn: [#maniac] Uncle can't touch me now, he can't touch my worm ! For a Worm has no worm ! + Jeremiah Brunn: [#elated] A Worm IS a worm ! A Worm is the Great Worm ! Unto me came Wormhood, and with it women in a flood ! + Jeremiah Brunn: [#maniac] Jean Deep the Wormanizer, Jean Deep God's Harbinger ! Ahahahahahahahaha !!! + +do Globals.console_message.emit("[color=dark_orange]ALLOTED ASSESSMENT TIME ELAPSED[/color]\n\n[color=orange_red]PLEASE MAKE A DECISION[/color]") + +=> END + + + +~ terminate +if Patient.abnormal == true + Jeremiah Brunn: [#abnormal] I AM FREE I AM SAFE I AM POWERFUL I AM STRONG I AM SECURE I AM THE WORM I MUST CONSUME [color=orange]CONSUME[/color] [color=orange_red]CONSUME CONSUME[/color] [color=red]CONSUME CONSUME[/color] +else + Jeremiah Brunn: [#elated] I am the Worm ! See ?? I am safe from Uncle now ! I am safe !! Safe !! I am God's Herald on this Earth ! Nobody can hurt me anymore ! Nobody !!! + +=> END + + + +~ release + +if Patient.abnormal == true + Jeremiah Brunn: [#abnormal] I AM FREE I AM SAFE I AM POWERFUL I AM STRONG I AM SECURE I AM THE WORM I MUST CONSUME [color=orange]CONSUME[/color] [color=orange_red]CONSUME CONSUME[/color] [color=red]CONSUME CONSUME[/color] +else + Jeremiah Brunn: [#elated] I am the Worm ! See ?? I am safe from Uncle now ! I am safe !! Safe !! I am God's Herald on this Earth ! Nobody can hurt me anymore ! Nobody !!! + +=> END \ No newline at end of file diff --git a/Dialogues/Patients/patient_49.dialogue.import b/Dialogues/Patients/patient_49.dialogue.import new file mode 100644 index 0000000..6e3f884 --- /dev/null +++ b/Dialogues/Patients/patient_49.dialogue.import @@ -0,0 +1,16 @@ +[remap] + +importer="dialogue_manager" +importer_version=15 +type="Resource" +uid="uid://bhwdwrk1lkbxv" +path="res://.godot/imported/patient_49.dialogue-2a9b5320dc09d9edc8fd43e1d8fe59de.tres" + +[deps] + +source_file="res://Dialogues/Patients/patient_49.dialogue" +dest_files=["res://.godot/imported/patient_49.dialogue-2a9b5320dc09d9edc8fd43e1d8fe59de.tres"] + +[params] + +defaults=true diff --git a/Dialogues/Patients/patient_49_file.cfg b/Dialogues/Patients/patient_49_file.cfg new file mode 100644 index 0000000..e276ecc --- /dev/null +++ b/Dialogues/Patients/patient_49_file.cfg @@ -0,0 +1,6 @@ +initial_emotion="neutral" +name="Jeremiah Brunn" +age="53" +profession="Musician" +psychiatric_assessment="MEGALOMANIAC DELUSIONS\n\nMYSTICAL DELUSIONS\n\nAUDITORY HALLUCINATIONS" +status="random" diff --git a/Dialogues/Patients/patient_50.dialogue b/Dialogues/Patients/patient_50.dialogue new file mode 100644 index 0000000..eff4a8c --- /dev/null +++ b/Dialogues/Patients/patient_50.dialogue @@ -0,0 +1,196 @@ +~ start + +do Patient.marker.fill(0) +set Patient.dialog_tree = Patient.build_dialog_tree([21,211,212,2111,2112,2113,22,23,231,232,233,31,32,41,42,43,44]) + +The patient looks at you with a placid smile, not uttering a single word. + +- Greetings. What's your name ? + Helen Moore: [#smiling] I'm Helen Moore. Pleased to meet you. + => part_2 + + +~ part_2 + +if Patient.marker[0] == 3 + - I apologize, but if I am to help you, I must keep inquiring about the individual who's been touching you. + Helen Moore: [#worried] ... Alright, i'll try to answer as much as I can. But as soon as i hear him coming, i'm shutting my lips ! + => part_3 + +- Why are you smiling ? [if Patient.dialog_tree[21] == "not_asked"] + set Patient.dialog_tree[21] = "asked" + do Patient.marker[0] += 1 + Helen Moore: [#smiling] Because I am fine. I am safe. + + ~ part_2_1 + - This place is far from safe. Actually, it's the exact opposite of it. [if Patient.dialog_tree[211] == "not_asked"] + set Patient.dialog_tree[211] = "asked" + do Patient.marker[1] += 1 + Helen Moore: [#puzzled] Well tried, but i know i am safe. + => part_2_1 + + - What makes you think you are safe ? [if Patient.dialog_tree[212] == "not_asked"] + set Patient.dialog_tree[212] = "asked" + do Patient.marker[1] += 1 + Helen Moore: [#smiling] I have tucked my shirt in my pants. + => part_2_1 + + - How exactly does tucking your shirt make you safe ? [if Patient.marker[1] == 2] + Helen Moore: [#smiling] Because he can't touch me then. + + ~ part_2_1_1 + + if Patient.marker[2] == 3 + - Has he touched you since you arrived here ? + Helen Moore: [#worried] No he hasn't ! Please, please, stop talking about him, i can feel he's hearing us ! + => part_2 + + - Who's "he" ? [if Patient.dialog_tree[2111] == "not_asked"] + set Patient.dialog_tree[2111] = "asked" + do Patient.marker[2] += 1 + Helen Moore: [#worried] I can't answer that ! He might hear me if i do ! + => part_2_1_1 + - How is he touching you ? [if Patient.dialog_tree[2112] == "not_asked"] + set Patient.dialog_tree[2112] = "asked" + do Patient.marker[2] += 1 + Helen Moore: [#worried] If i'm not being careful, he can ! But i've tucked my shirt, so now he can't ! Right ? + => part_2_1_1 + - Why is he touching you ? [if Patient.dialog_tree[2113] == "not_asked"] + set Patient.dialog_tree[2113] = "asked" + do Patient.marker[2] += 1 + Helen Moore: [#worried] Because he can ! He always liked to do it, he always did ! + => part_2_1_1 + + +- What is your job ? [if Patient.dialog_tree[22] == "not_asked"] + set Patient.dialog_tree[22] = "asked" + do Patient.marker[0] += 1 + Helen Moore: [#smiling] I work with children. I help them. It makes me feel better about... stuff. + + - May i ask what "stuff" you are referring to ? + Helen Moore: [#worried] I... I mean... I think children deserve to be loved. I think they deserve everything. Everything but pain. That's what i think. + => part_2 + +- Are you hearing voices right now ? [if Patient.dialog_tree[23] == "not_asked"] + set Patient.dialog_tree[23] = "asked" + do Patient.marker[0] += 1 + Helen Moore: [#neutral] Yes... I think. + + ~ part_2_3 + + if Patient.marker[3] == 3 + - Who do these voices belong to ? Do you know them ? + Helen Moore: [#smiling] Of course i do ! It's Mom and Sis ! And the third one's Dad ! + => part_2 + + - What are they telling you ? [if Patient.dialog_tree[231] == "not_asked"] + set Patient.dialog_tree[231] = "asked" + do Patient.marker[3] += 1 + Helen Moore: [#neutral] They tell me i'm fine, as long as i stay hidden. + + - Why do you need to stay hidden ? + Helen Moore: [#puzzled] To be fine. I just told you. + => part_2_3 + + - How many voices are you hearing ? [if Patient.dialog_tree[232] == "not_asked"] + set Patient.dialog_tree[232] = "asked" + do Patient.marker[3] += 1 + Helen Moore: [#neutral] I hear two right now. Usually there are three, but the third is busy. + => part_2_3 + + - Are the voices telling you to hurt yourself ? [if Patient.dialog_tree[233] == "not_asked"] + set Patient.dialog_tree[233] = "asked" + do Patient.marker[3] += 1 + Helen Moore: [#worried] Sometimes they do, especially the third one. I try not to listen, but they never stop whispering ! + => part_2_3 + + + +~ part_3 + +if Patient.marker[4] == 2 + Helen Moore: [#scared] Wait, I can hear him coming ! I'm not answering you anymore ! + + - Please Mrs Moore, I am trying to help you. I only have a few more questions, I promise you. + Helen Moore: [#scared] No, he's coming, can't you hear ?! He's coming ! + + - You've tucked your shirt in your pants, remember ? You are safe. + Helen Moore: [#scared] I... Fine, but please make it quick ! He's getting closer ! + => part_4 + +- Please forgive me, but i have to insist. Who's the "he" you're talking about ? Do you know him ? [if Patient.dialog_tree[31] == "not_asked"] + set Patient.dialog_tree[31] = "asked" + do Patient.marker[4] += 1 + Helen Moore: [#worried] Of course I know him... He's my Dad ! + => part_3 + +- When did he start touching you ? [if Patient.dialog_tree[32] == "not_asked"] + set Patient.dialog_tree[32] = "asked" + do Patient.marker[4] += 1 + Helen Moore: [#worried] I don't know... As far as I can remember, he always did. + Helen Moore: [#shocked] When I was 6, he would wait for Mom to go to work so he could take me to the bathroom with him. + Helen Moore: [#shocked] I learned to tuck my pants in my socks, and my pajamas in my pants, so he wouldn't caress me when he kissed me goodnight. + => part_3 + + +~ part_4 + +if Patient.marker[5] == 4 + Helen Moore: [#panicked] Oh nonono he's coming he heard us he's coming he heard us he knows he knows oh god please help me !! + => part_6 + +- How old is your father now ? Do you still see him ? [if Patient.dialog_tree[41] == "not_asked"] + set Patient.dialog_tree[41] = "asked" + do Patient.marker[5] += 1 + Helen Moore: [#scared] Dad died ten years ago from a stroke ! Why are you asking me this ?? He's going to hear us ! + => part_4 + +- Why are you still hearing your mother and sister ? [if Patient.dialog_tree[42] == "not_asked"] + set Patient.dialog_tree[42] = "asked" + do Patient.marker[5] += 1 + Helen Moore: [#scared] Mom ? Mom knew ! I know she knew, but she didn't do anything ! She kept going to work, she kept leaving me alone with Dad ! Why ? Why ?? + Helen Moore: [#scared] And Sis... Sis left me too ! She told me she would always be here for me, and yet she jumped out of the window ! Why did she leave me ?? + => part_4 + +- When did your father last touch you ? [if Patient.dialog_tree[43] == "not_asked"] + set Patient.dialog_tree[43] = "asked" + do Patient.marker[5] += 1 + Helen Moore: [#scared] L-Last month, just before I was sent here ! Please stop talking about him, he's getting closer, i can hear him ! + => part_4 + +- Did your father also touch your sister ? [if Patient.dialog_tree[44] == "not_asked"] + set Patient.dialog_tree[44] = "asked" + do Patient.marker[5] += 1 + Helen Moore: [#scared] One day, I was playing with Sis, but Dad took her to his bedroom. + Helen Moore: [#scared] He locked the door, so I couldn't see, but I could hear ! + Helen Moore: [#shocked] The noises... I can still hear them. Sis never played with me again after that. + => part_4 + + +~ part_6 + +- I have one last question Mrs Moore, and then it's over, i promise. + Helen Moore: [#panicked] Dad I'm sorry I know I didn't want to you know that please don't please I'm sorry please Dad please + + - Do you feel your father touching you ? Do you feel his hands on your skin right now ? + Helen Moore: [#panicked] I feel him i feel his hands they hurt it hurts it HuRtS sO MucH pLEAse HElp mE PLEase I'm sorry plEASe mOm HELP PleaSE + +do Globals.console_message.emit("[color=dark_orange]ALLOTED ASSESSMENT TIME ELAPSED[/color]\n\n[color=orange_red]PLEASE MAKE A DECISION[/color]") + +=> END + + + +~ terminate + +Helen Moore: [#abnormal] WHY DAD WHY ARE YOU TOUCHING ME AGAIN I'M SORRY PLEASE STOP MOM WHY DIDN'T YOU HELP ME WHY IT HURTS [color=orange]IT HURTS[/color] [color=orange_red]IT HURTS IT HURTS[/color] [color=red]IT HURTS IT HURTS[/color] + +=> END + + + +~ release + +Helen Moore: [#abnormal] WHY DAD WHY ARE YOU TOUCHING ME AGAIN I'M SORRY PLEASE STOP MOM WHY DIDN'T YOU HELP ME WHY IT HURTS [color=orange]IT HURTS[/color] [color=orange_red]IT HURTS IT HURTS[/color] [color=red]IT HURTS IT HURTS[/color] + +=> END \ No newline at end of file diff --git a/Dialogues/Patients/patient_50.dialogue.import b/Dialogues/Patients/patient_50.dialogue.import new file mode 100644 index 0000000..4b4186e --- /dev/null +++ b/Dialogues/Patients/patient_50.dialogue.import @@ -0,0 +1,16 @@ +[remap] + +importer="dialogue_manager" +importer_version=15 +type="Resource" +uid="uid://bsbmk75nv76ln" +path="res://.godot/imported/patient_50.dialogue-515cfc3520f8541d20e9998e85802f8c.tres" + +[deps] + +source_file="res://Dialogues/Patients/patient_50.dialogue" +dest_files=["res://.godot/imported/patient_50.dialogue-515cfc3520f8541d20e9998e85802f8c.tres"] + +[params] + +defaults=true diff --git a/Dialogues/Patients/patient_50_file.cfg b/Dialogues/Patients/patient_50_file.cfg new file mode 100644 index 0000000..26233b0 --- /dev/null +++ b/Dialogues/Patients/patient_50_file.cfg @@ -0,0 +1,6 @@ +initial_emotion="smiling" +name="Helen Moore" +age="34" +profession="Social care worker" +psychiatric_assessment="AUDITORY HALLUCINATIONS\n\nPERSECUTORY DELUSIONS" +status="abnormal" diff --git a/Dialogues/Patients/patient_564.dialogue b/Dialogues/Patients/patient_564.dialogue deleted file mode 100644 index 96f8a66..0000000 --- a/Dialogues/Patients/patient_564.dialogue +++ /dev/null @@ -1,33 +0,0 @@ -~ start - -The patient remains still, watching you expectantly. - -Vladislav Orleg: [#smiling] Hello, i'm smiling ! -Vladislav Orleg: [#angry] Now i'm very angry ! -Vladislav Orleg: [#scared] Suddenly i'm scared ! -Vladislav Orleg: There is no change of emotion, i'm still scared ! -Vladislav Orleg: [#neutral] I'm neutral again. -Vladislav Orleg: [#sad] I'm depressed... -You: Alright, you're going to Containment you bastard. -Vladislav Orleg: [#scared] Nooooooo !!! - -=> END - - - -~ containment - -You activate the switch concealed beneath your desk. Guards briskly walk in to haul the patient off. -Vladislav Orleg: [#scared] Wait, you're making a terrible mistake ! -Vladislav Orleg: I am not sick, i swear ! - -=> END - - - -~ psych_ward - -You: My evaluation revealed no cause for concern. You will be transferred to the Psych Ward to receive proper treatment. -Vladislav Orleg: [#smiling] Thank you Doctor. Thank you so much. - -=> END \ No newline at end of file diff --git a/Dialogues/Patients/patient_564.dialogue.import b/Dialogues/Patients/patient_564.dialogue.import deleted file mode 100644 index 143f720..0000000 --- a/Dialogues/Patients/patient_564.dialogue.import +++ /dev/null @@ -1,15 +0,0 @@ -[remap] - -importer="dialogue_manager_compiler_13" -type="Resource" -uid="uid://c0d76rpvim0cu" -path="res://.godot/imported/patient_564.dialogue-c43dc74675a586a799f5a9ead356d3ea.tres" - -[deps] - -source_file="res://Dialogues/Patients/patient_564.dialogue" -dest_files=["res://.godot/imported/patient_564.dialogue-c43dc74675a586a799f5a9ead356d3ea.tres"] - -[params] - -defaults=true diff --git a/Dialogues/Patients/patient_564_file.txt b/Dialogues/Patients/patient_564_file.txt deleted file mode 100644 index 57396a9..0000000 --- a/Dialogues/Patients/patient_564_file.txt +++ /dev/null @@ -1,12 +0,0 @@ - - -NAME: Orleg, Vladislav - -AGE: 59 - -PROFESSION: Gardener - -ADMISSION REASON: -Delirium -Verbal agression -Homicidal threats diff --git a/Dialogues/balloon.gd b/Dialogues/balloon.gd index 229c650..45907c2 100644 --- a/Dialogues/balloon.gd +++ b/Dialogues/balloon.gd @@ -49,7 +49,8 @@ var dialogue_line: DialogueLine: dialogue_label.dialogue_line = dialogue_line responses_menu.hide() - responses_menu.set_responses(dialogue_line.responses) + dialogue_line.responses.reverse() + responses_menu.responses = dialogue_line.responses # Show our balloon balloon.show() @@ -163,6 +164,7 @@ func _on_balloon_gui_input(event: InputEvent) -> void: func _on_responses_menu_response_selected(response: DialogueResponse) -> void: + Globals.response_chosen.emit(response) next(response.next_id) diff --git a/Dialogues/balloon.gd.uid b/Dialogues/balloon.gd.uid new file mode 100644 index 0000000..aa5346a --- /dev/null +++ b/Dialogues/balloon.gd.uid @@ -0,0 +1 @@ +uid://bopx5vxrplpny diff --git a/Dialogues/balloon_big.tscn b/Dialogues/balloon_big.tscn index 51beefa..4bd149e 100644 --- a/Dialogues/balloon_big.tscn +++ b/Dialogues/balloon_big.tscn @@ -1,10 +1,8 @@ -[gd_scene load_steps=11 format=3 uid="uid://bh0e0uhmtk0ck"] +[gd_scene load_steps=9 format=3 uid="uid://bh0e0uhmtk0ck"] -[ext_resource type="Script" path="res://Dialogues/balloon.gd" id="1_trhxk"] -[ext_resource type="FontFile" uid="uid://mi5v1uq78g7o" path="res://Art/XanhMono-Italic.ttf" id="2_8uhga"] +[ext_resource type="Script" uid="uid://bopx5vxrplpny" path="res://Dialogues/balloon.gd" id="1_trhxk"] [ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="3_ek02p"] -[ext_resource type="FontFile" uid="uid://ckg5x0oonnjtn" path="res://Art/XanhMono-Regular.ttf" id="4_ovm88"] -[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="5_s4c7i"] +[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="5_s4c7i"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"] bg_color = Color(0, 0, 0, 1) @@ -107,7 +105,6 @@ 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_8uhga") theme_override_font_sizes/normal_font_size = 25 bbcode_enabled = true text = "Character" @@ -118,16 +115,11 @@ scroll_active = false unique_name_in_owner = true layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/normal_font = ExtResource("4_ovm88") -theme_override_fonts/mono_font = ExtResource("4_ovm88") -theme_override_fonts/italics_font = ExtResource("2_8uhga") -theme_override_fonts/bold_italics_font = ExtResource("2_8uhga") -theme_override_fonts/bold_font = ExtResource("4_ovm88") +theme_override_font_sizes/normal_font_size = 30 +theme_override_font_sizes/bold_font_size = 30 theme_override_font_sizes/bold_italics_font_size = 30 theme_override_font_sizes/italics_font_size = 30 theme_override_font_sizes/mono_font_size = 30 -theme_override_font_sizes/normal_font_size = 30 -theme_override_font_sizes/bold_font_size = 30 text = "Dialogue..." [node name="Responses" type="MarginContainer" parent="Balloon"] diff --git a/Dialogues/balloon_small.tscn b/Dialogues/balloon_small.tscn index 58d8d42..690e196 100644 --- a/Dialogues/balloon_small.tscn +++ b/Dialogues/balloon_small.tscn @@ -1,10 +1,8 @@ -[gd_scene load_steps=11 format=3 uid="uid://22ca8midmqwo"] +[gd_scene load_steps=9 format=3 uid="uid://22ca8midmqwo"] -[ext_resource type="Script" path="res://Dialogues/balloon.gd" id="1_hghjc"] -[ext_resource type="FontFile" uid="uid://mi5v1uq78g7o" path="res://Art/XanhMono-Italic.ttf" id="2_camm1"] +[ext_resource type="Script" uid="uid://bopx5vxrplpny" path="res://Dialogues/balloon.gd" id="1_hghjc"] [ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="3_k7jp1"] -[ext_resource type="FontFile" uid="uid://ckg5x0oonnjtn" path="res://Art/XanhMono-Regular.ttf" id="4_w3tww"] -[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="5_f5bwa"] +[ext_resource type="Script" uid="uid://yuni2welux1i" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="5_f5bwa"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"] bg_color = Color(0, 0, 0, 1) @@ -107,7 +105,6 @@ 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_camm1") theme_override_font_sizes/normal_font_size = 25 bbcode_enabled = true text = "Character" @@ -118,16 +115,11 @@ scroll_active = false unique_name_in_owner = true layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/normal_font = ExtResource("4_w3tww") -theme_override_fonts/mono_font = ExtResource("4_w3tww") -theme_override_fonts/italics_font = ExtResource("2_camm1") -theme_override_fonts/bold_italics_font = ExtResource("2_camm1") -theme_override_fonts/bold_font = ExtResource("4_w3tww") +theme_override_font_sizes/normal_font_size = 30 +theme_override_font_sizes/bold_font_size = 30 theme_override_font_sizes/bold_italics_font_size = 30 theme_override_font_sizes/italics_font_size = 30 theme_override_font_sizes/mono_font_size = 30 -theme_override_font_sizes/normal_font_size = 30 -theme_override_font_sizes/bold_font_size = 30 text = "Dialogue..." [node name="Responses" type="MarginContainer" parent="Balloon"] diff --git a/Dialogues/balloon_test.tscn b/Dialogues/balloon_test.tscn new file mode 100644 index 0000000..c29d3b0 --- /dev/null +++ b/Dialogues/balloon_test.tscn @@ -0,0 +1,156 @@ +[gd_scene load_steps=11 format=3 uid="uid://7uw7j7ymucjx"] + +[ext_resource type="Script" uid="uid://bopx5vxrplpny" path="res://Dialogues/balloon.gd" id="1_okgs6"] +[ext_resource type="FontFile" uid="uid://dv8ppra5tgn1t" path="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" id="2_d4irk"] +[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="3_sg5qg"] +[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="5_cd5om"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sfbl0"] +content_margin_top = 20.0 +content_margin_bottom = 20.0 +bg_color = Color(0, 0, 0, 1) +border_color = Color(0.329412, 0.329412, 0.329412, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7gkrf"] +content_margin_top = 20.0 +content_margin_bottom = 20.0 +bg_color = Color(0.352941, 0.352941, 0.352941, 1) +border_color = Color(1, 1, 1, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_eipem"] +content_margin_top = 20.0 +content_margin_bottom = 20.0 +bg_color = Color(0.352941, 0.352941, 0.352941, 1) +border_color = Color(0.6, 0.6, 0.6, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_b3r5k"] +content_margin_top = 20.0 +content_margin_bottom = 20.0 +bg_color = Color(0.196078, 0.196078, 0.196078, 1) + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_cypdp"] + +[sub_resource type="Theme" id="Theme_qq3yp"] +default_font_size = 20 +Button/constants/outline_size = 3 +Button/styles/disabled = SubResource("StyleBoxFlat_sfbl0") +Button/styles/focus = SubResource("StyleBoxFlat_7gkrf") +Button/styles/hover = SubResource("StyleBoxFlat_eipem") +Button/styles/normal = SubResource("StyleBoxFlat_b3r5k") +MarginContainer/constants/margin_bottom = 15 +MarginContainer/constants/margin_left = 30 +MarginContainer/constants/margin_right = 30 +MarginContainer/constants/margin_top = 15 +Panel/styles/panel = SubResource("StyleBoxEmpty_cypdp") + +[node name="ExampleBalloon" type="CanvasLayer"] +layer = 100 +script = ExtResource("1_okgs6") + +[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 +size_flags_horizontal = 0 +size_flags_vertical = 0 +mouse_filter = 2 +mouse_force_pass_scroll_events = false +theme = SubResource("Theme_qq3yp") + +[node name="Panel" type="Panel" parent="Balloon"] +clip_children = 2 +layout_mode = 1 +offset_left = 1167.0 +offset_top = 331.0 +offset_right = 1807.0 +offset_bottom = 566.0 +mouse_force_pass_scroll_events = false + +[node name="Dialogue" type="MarginContainer" parent="Balloon/Panel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 2.0 +offset_top = -23.0 +offset_right = 2.0 +offset_bottom = 34.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/Panel/Dialogue"] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/Panel/Dialogue/VBoxContainer"] +unique_name_in_owner = true +clip_contents = false +custom_minimum_size = Vector2(0, 30) +layout_mode = 2 +mouse_filter = 1 +theme_override_colors/default_color = Color(0.784314, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_outline_size = 0 +theme_override_constants/outline_size = 0 +theme_override_fonts/normal_font = ExtResource("2_d4irk") +theme_override_font_sizes/normal_font_size = 24 +bbcode_enabled = true +text = "Character" +fit_content = true +scroll_active = false + +[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/VBoxContainer" instance=ExtResource("3_sg5qg")] +unique_name_in_owner = true +clip_contents = false +layout_mode = 2 +size_flags_vertical = 3 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_constants/line_separation = 10 +theme_override_constants/shadow_outline_size = 6 +theme_override_constants/outline_size = 12 +theme_override_fonts/normal_font = ExtResource("2_d4irk") +theme_override_font_sizes/normal_font_size = 24 +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 = 232.0 +offset_top = -384.0 +offset_right = 952.0 +offset_bottom = -4.0 +grow_horizontal = 2 +grow_vertical = 0 +theme_override_constants/margin_left = 28 +theme_override_constants/margin_top = 28 +theme_override_constants/margin_right = 36 +theme_override_constants/margin_bottom = 28 + +[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/Responses" node_paths=PackedStringArray("response_template")] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 2 +script = ExtResource("5_cd5om") +response_template = NodePath("ResponseExample") + +[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("2_d4irk") +theme_override_font_sizes/font_size = 16 +text = "What brought you here ?" +autowrap_mode = 2 + +[connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"] +[connection signal="gui_input" from="Balloon/Panel" to="." method="_on_balloon_gui_input"] +[connection signal="response_selected" from="Balloon/Responses/ResponsesMenu" to="." method="_on_responses_menu_response_selected"] diff --git a/MainHub.tscn b/MainHub.tscn index 74cf9c1..fe02f57 100644 --- a/MainHub.tscn +++ b/MainHub.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=7 format=3 uid="uid://buoev4bra11fe"] -[ext_resource type="Script" path="res://main_hub.gd" id="1_rvfpd"] +[ext_resource type="Script" uid="uid://c6kqjqj6cs4wa" path="res://main_hub.gd" id="1_rvfpd"] [ext_resource type="PackedScene" uid="uid://cahspuhi43bx7" path="res://title_screen.tscn" id="2_3wmv1"] [ext_resource type="PackedScene" uid="uid://chytk2cctjk3x" path="res://screening_desk.tscn" id="3_whctb"] [ext_resource type="PackedScene" uid="uid://dcjhx2g4d056v" path="res://bedroom.tscn" id="4_4fm57"] diff --git a/addons/dialogue_manager/DialogueManager.cs b/addons/dialogue_manager/DialogueManager.cs index c7b7a5c..66f4dba 100644 --- a/addons/dialogue_manager/DialogueManager.cs +++ b/addons/dialogue_manager/DialogueManager.cs @@ -1,6 +1,7 @@ using Godot; using Godot.Collections; using System; +using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -8,6 +9,14 @@ using System.Threading.Tasks; namespace DialogueManagerRuntime { + + public enum MutationBehaviour + { + Wait, + DoNotWait, + Skip + } + public enum TranslationSource { None, @@ -16,13 +25,15 @@ namespace DialogueManagerRuntime PO } - public partial class DialogueManager : Node + public partial class DialogueManager : RefCounted { + public delegate void DialogueStartedEventHandler(Resource dialogueResource); public delegate void PassedTitleEventHandler(string title); public delegate void GotDialogueEventHandler(DialogueLine dialogueLine); public delegate void MutatedEventHandler(Dictionary mutation); public delegate void DialogueEndedEventHandler(Resource dialogueResource); + public static DialogueStartedEventHandler? DialogueStarted; public static PassedTitleEventHandler? PassedTitle; public static GotDialogueEventHandler? GotDialogue; public static MutatedEventHandler? Mutated; @@ -38,6 +49,7 @@ namespace DialogueManagerRuntime if (instance == null) { instance = Engine.GetSingleton("DialogueManager"); + instance.Connect("bridge_dialogue_started", Callable.From((Resource dialogueResource) => DialogueStarted?.Invoke(dialogueResource))); } return instance; } @@ -86,11 +98,6 @@ namespace DialogueManagerRuntime instance.Connect("dialogue_ended", Callable.From((Resource dialogueResource) => DialogueEnded?.Invoke(dialogueResource))); } - public void Prepare() - { - Prepare(Instance); - } - public static async Task GetSingleton() { @@ -121,11 +128,11 @@ namespace DialogueManagerRuntime return (Resource)Instance.Call("create_resource_from_text", text); } - public static async Task GetNextDialogueLine(Resource dialogueResource, string key = "", Array? extraGameStates = null) + public static async Task GetNextDialogueLine(Resource dialogueResource, string key = "", Array? extraGameStates = null, MutationBehaviour mutation_behaviour = MutationBehaviour.Wait) { var instance = (Node)Instance.Call("_bridge_get_new_instance"); Prepare(instance); - instance.Call("_bridge_get_next_dialogue_line", dialogueResource, key, extraGameStates ?? new Array()); + instance.Call("_bridge_get_next_dialogue_line", dialogueResource, key, extraGameStates ?? new Array(), (int)mutation_behaviour); var result = await instance.ToSignal(instance, "bridge_get_next_dialogue_line_completed"); instance.QueueFree(); @@ -134,6 +141,19 @@ namespace DialogueManagerRuntime return new DialogueLine((RefCounted)result[0]); } + public static async Task GetLine(Resource dialogueResource, string key = "", Array? extraGameStates = null) + { + var instance = (Node)Instance.Call("_bridge_get_new_instance"); + Prepare(instance); + instance.Call("_bridge_get_line", dialogueResource, key, extraGameStates ?? new Array()); + var result = await instance.ToSignal(instance, "bridge_get_line_completed"); + instance.QueueFree(); + + if ((RefCounted)result[0] == null) return null; + + return new DialogueLine((RefCounted)result[0]); + } + public static CanvasLayer ShowExampleDialogueBalloon(Resource dialogueResource, string key = "", Array? extraGameStates = null) { @@ -163,6 +183,18 @@ namespace DialogueManagerRuntime } + public static Array StaticIdToLineIds(Resource dialogueResource, string staticId) + { + return (Array)Instance.Call("static_id_to_line_ids", dialogueResource, staticId); + } + + + public static string StaticIdToLineId(Resource dialogueResource, string staticId) + { + return (string)Instance.Call("static_id_to_line_id", dialogueResource, staticId); + } + + public static async void Mutate(Dictionary mutation, Array? extraGameStates = null, bool isInlineMutation = false) { Instance.Call("_bridge_mutate", mutation, extraGameStates ?? new Array(), isInlineMutation); @@ -170,16 +202,167 @@ namespace DialogueManagerRuntime } - public bool ThingHasMethod(GodotObject thing, string method) + public static Array GetMembersForAutoload(Script script) { - MethodInfo? info = thing.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public); - return info != null; + Array members = new Array(); + + string typeName = script.ResourcePath.GetFile().GetBaseName(); + var matchingTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.Name == typeName); + foreach (var matchingType in matchingTypes) + { + var memberInfos = matchingType.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly); + foreach (var memberInfo in memberInfos) + { + string type; + switch (memberInfo.MemberType) + { + case MemberTypes.Field: + FieldInfo fieldInfo = memberInfo as FieldInfo; + + if (fieldInfo.FieldType.ToString().Contains("EventHandler")) + { + type = "signal"; + } + else if (fieldInfo.IsLiteral) + { + type = "constant"; + } + else + { + type = "property"; + } + break; + case MemberTypes.Method: + type = "method"; + break; + + case MemberTypes.NestedType: + type = "constant"; + break; + + default: + continue; + } + + members.Add(new Dictionary() { + { "name", memberInfo.Name }, + { "type", type } + }); + } + } + + return members; + } + + + public bool ThingHasConstant(GodotObject thing, string property) + { + var memberInfos = thing.GetType().GetMember(property, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly); + return memberInfos.Length > 0; + } + + + public Variant ResolveThingConstant(GodotObject thing, string property) + { + var memberInfos = thing.GetType().GetMember(property, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly); + foreach (var memberInfo in memberInfos) + { + if (memberInfo != null) + { + try + { + switch (memberInfo.MemberType) + { + case MemberTypes.Field: + return convertValueToVariant((memberInfo as FieldInfo).GetValue(thing)); + + case MemberTypes.Property: + return convertValueToVariant((memberInfo as PropertyInfo).GetValue(thing)); + + case MemberTypes.NestedType: + var type = thing.GetType().GetNestedType(property); + if (type.IsEnum) + { + return GetEnumAsDictionary(type); + } + break; + + default: + continue; + } + } + catch (Exception e) + { + throw new Exception($"{property} is not supported by Variant."); + } + } + } + + throw new Exception($"{property} is not a public constant on {thing}"); + } + + + Dictionary GetEnumAsDictionary(Type enumType) + { + Dictionary dictionary = new Dictionary(); + foreach (var value in enumType.GetEnumValuesAsUnderlyingType()) + { + var key = enumType.GetEnumName(value); + if (key != null) + { + dictionary.Add(key, convertValueToVariant(value)); + } + } + return dictionary; + } + + + Variant convertValueToVariant(object value) + { + Type rawType = value.GetType(); + if (rawType.IsEnum) + { + var values = GetEnumAsDictionary(rawType); + value = values[value.ToString()]; + } + + return value switch + { + Variant v => v, + int v => Variant.From((long)v), + float v => Variant.From((double)v), + System.String v => Variant.From((string)v), + _ => Variant.From(value) + }; + } + + + public bool ThingHasMethod(GodotObject thing, string method, Array args) + { + var methodInfos = thing.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly); + foreach (var methodInfo in methodInfos) + { + if (methodInfo.Name == method && args.Count >= methodInfo.GetParameters().Where(p => !p.HasDefaultValue).Count()) + { + return true; + } + } + + return false; } public async void ResolveThingMethod(GodotObject thing, string method, Array args) { - MethodInfo? info = thing.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public); + MethodInfo? info = null; + var methodInfos = thing.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly); + foreach (var methodInfo in methodInfos) + { + if (methodInfo.Name == method && args.Count >= methodInfo.GetParameters().Where(p => !p.HasDefaultValue).Count()) + { + info = methodInfo; + } + } if (info == null) return; @@ -216,18 +399,15 @@ namespace DialogueManagerRuntime if (result is Task taskResult) { - // await Tasks and handle result if it is a Task await taskResult; - var taskType = taskResult.GetType(); - if (taskType.IsGenericType && taskType.GetGenericTypeDefinition() == typeof(Task<>)) + try { - var resultProperty = taskType.GetProperty("Result"); - var taskResultValue = resultProperty.GetValue(taskResult); - EmitSignal(SignalName.Resolved, (Variant)taskResultValue); + Variant value = (Variant)taskResult.GetType().GetProperty("Result").GetValue(taskResult); + EmitSignal(SignalName.Resolved, value); } - else + catch (Exception) { - EmitSignal(SignalName.Resolved, null); + EmitSignal(SignalName.Resolved); } } else @@ -236,6 +416,12 @@ namespace DialogueManagerRuntime } } #nullable enable + + + public static string GetErrorMessage(int error) + { + return (string)Instance.Call("_bridge_get_error_message", error); + } } @@ -295,12 +481,6 @@ namespace DialogueManagerRuntime get => time; } - private Dictionary pauses = new Dictionary(); - public Dictionary Pauses - { - get => pauses; - } - private Dictionary speeds = new Dictionary(); public Dictionary Speeds { @@ -313,6 +493,12 @@ namespace DialogueManagerRuntime get => inline_mutations; } + private Array concurrent_lines = new Array(); + public Array ConcurrentLines + { + get => concurrent_lines; + } + private Array extra_game_states = new Array(); public Array ExtraGameStates { @@ -327,17 +513,22 @@ namespace DialogueManagerRuntime public DialogueLine(RefCounted data) { + id = (string)data.Get("id"); type = (string)data.Get("type"); next_id = (string)data.Get("next_id"); character = (string)data.Get("character"); text = (string)data.Get("text"); translation_key = (string)data.Get("translation_key"); - pauses = (Dictionary)data.Get("pauses"); speeds = (Dictionary)data.Get("speeds"); inline_mutations = (Array)data.Get("inline_mutations"); time = (string)data.Get("time"); tags = (Array)data.Get("tags"); + foreach (var concurrent_line_data in (Array)data.Get("concurrent_lines")) + { + concurrent_lines.Add(new DialogueLine(concurrent_line_data)); + } + foreach (var response in (Array)data.Get("responses")) { responses.Add(new DialogueResponse(response)); @@ -345,6 +536,20 @@ namespace DialogueManagerRuntime } + public bool HasTag(string tagName) + { + string wrapped = $"{tagName}="; + foreach (var tag in tags) + { + if (tag.StartsWith(wrapped)) + { + return true; + } + } + return false; + } + + public string GetTagValue(string tagName) { string wrapped = $"{tagName}="; @@ -389,6 +594,13 @@ namespace DialogueManagerRuntime set => is_allowed = value; } + private string condition_as_text = ""; + public string ConditionAsText + { + get => condition_as_text; + set => condition_as_text = value; + } + private string text = ""; public string Text { diff --git a/addons/dialogue_manager/DialogueManager.cs.uid b/addons/dialogue_manager/DialogueManager.cs.uid new file mode 100644 index 0000000..8d5ba92 --- /dev/null +++ b/addons/dialogue_manager/DialogueManager.cs.uid @@ -0,0 +1 @@ +uid://c4c5lsrwy3opj diff --git a/addons/dialogue_manager/assets/banner.png b/addons/dialogue_manager/assets/banner.png new file mode 100644 index 0000000..48168b2 Binary files /dev/null and b/addons/dialogue_manager/assets/banner.png differ diff --git a/addons/dialogue_manager/assets/banner.png.import b/addons/dialogue_manager/assets/banner.png.import new file mode 100644 index 0000000..2f56758 --- /dev/null +++ b/addons/dialogue_manager/assets/banner.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cnm67htuohhlo" +path="res://.godot/imported/banner.png-7e9e6a304eef850602c8d5afb80df9c3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogue_manager/assets/banner.png" +dest_files=["res://.godot/imported/banner.png-7e9e6a304eef850602c8d5afb80df9c3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/dialogue_manager/compiler/compilation.gd b/addons/dialogue_manager/compiler/compilation.gd new file mode 100644 index 0000000..34f3d1e --- /dev/null +++ b/addons/dialogue_manager/compiler/compilation.gd @@ -0,0 +1,1130 @@ +## A single compilation instance of some dialogue. +class_name DMCompilation extends RefCounted + + +#region Compilation locals + + +## A list of file paths that were imported by this file. +var imported_paths: PackedStringArray = [] +## A list of state names from "using" clauses. +var using_states: PackedStringArray = [] +## A map of titles in this file. +var titles: Dictionary = {} +## The first encountered title in this file. +var first_title: String = "" +## A list of character names in this file. +var character_names: PackedStringArray = [] +## A list of any compilation errors. +var errors: Array[Dictionary] = [] +## A map of all compiled lines. +var lines: Dictionary = {} +## A flattened and simplified map of compiled lines for storage in a resource. +var data: Dictionary = {} + + +#endregion + +#region External processing + + +var processor: DMDialogueProcessor = null + + +#endregion + +#region Internal variables + + +# A list of all [RegEx] references +var regex: DMCompilerRegEx = DMCompilerRegEx.new() +# For parsing condition/mutation expressions +var expression_parser: DMExpressionParser = DMExpressionParser.new() + +# A map of titles that came from imported files. +var _imported_titles: Dictionary = {} +# Used to keep track of circular imports. +var _imported_line_map: Dictionary = {} +# The number of imported lines. +var _imported_line_count: int = 0 +# A list of already encountered static line IDs. +var _known_translation_keys: Dictionary = {} +# A noop for retrieving the next line without conditions. +var _first: Callable = func(_s): return true + +# Title jumps are adjusted as they are parsed so any goto lines might need to be adjusted after they are first seen. +var _goto_lines: Dictionary = {} + + +#endregion + +#region Main + + +## Compile some text. +func compile(text: String, path: String = ".") -> Error: + titles = {} + character_names = [] + + parse_line_tree(build_line_tree(inject_imported_files(text + "\n=> END", path))) + + # Convert the compiles lines to a Dictionary so they can be stored. + for id in lines: + var line: DMCompiledLine = lines[id] + data[id] = line.to_data() + + if errors.size() > 0: + return ERR_PARSE_ERROR + + return OK + + +## Inject any imported files +func inject_imported_files(text: String, path: String) -> PackedStringArray: + # Work out imports + var known_imports: Dictionary = {} + + # Include the base file path so that we can get around circular dependencies + known_imports[path.hash()] = "." + + var raw_lines: PackedStringArray = text.split("\n") + + for id in range(0, raw_lines.size()): + var line = raw_lines[id] + if is_import_line(line): + var import_data: Dictionary = extract_import_path_and_name(line) + + if not import_data.has("path"): continue + + var import_hash: int = import_data.path.hash() + if import_data.size() > 0: + # Keep track of titles so we can add imported ones later + if str(import_hash) in _imported_titles.keys(): + add_error(id, 0, DMConstants.ERR_FILE_ALREADY_IMPORTED) + if import_data.prefix in _imported_titles.values(): + add_error(id, 0, DMConstants.ERR_DUPLICATE_IMPORT_NAME) + _imported_titles[str(import_hash)] = import_data.prefix + + # Import the file content + if not known_imports.has(import_hash): + var error: Error = import_content(import_data.path, import_data.prefix, _imported_line_map, known_imports) + if error != OK: + add_error(id, 0, error) + + # Make a map so we can refer compiled lines to where they were imported from + if not _imported_line_map.has(import_hash): + _imported_line_map[import_hash] = { + hash = import_hash, + imported_on_line_number = id, + from_line = 0, + to_line = 0 + } + + var imported_content: String = "" + var cummulative_line_number: int = 0 + for item in _imported_line_map.values(): + item["from_line"] = cummulative_line_number + if known_imports.has(item.hash): + cummulative_line_number += known_imports[item.hash].split("\n").size() + item["to_line"] = cummulative_line_number + if known_imports.has(item.hash): + imported_content += known_imports[item.hash] + "\n" + + if imported_content == "": + _imported_line_count = 0 + return text.split("\n") + else: + _imported_line_count = cummulative_line_number + 1 + # Combine imported lines with the original lines + return (imported_content + "\n" + text).split("\n") + + +## Import content from another dialogue file or return an ERR +func import_content(path: String, prefix: String, imported_line_map: Dictionary, known_imports: Dictionary) -> Error: + if FileAccess.file_exists(path): + var file = FileAccess.open(path, FileAccess.READ) + var content: PackedStringArray = file.get_as_text().strip_edges().split("\n") + + for index in range(0, content.size()): + var line = content[index] + if is_import_line(line): + var import = extract_import_path_and_name(line) + if import.size() > 0: + if not known_imports.has(import.path.hash()): + # Add an empty record into the keys just so we don't end up with cyclic dependencies + known_imports[import.path.hash()] = "" + if import_content(import.path, import.prefix, imported_line_map, known_imports) != OK: + return ERR_LINK_FAILED + + if not imported_line_map.has(import.path.hash()): + # Make a map so we can refer compiled lines to where they were imported from + imported_line_map[import.path.hash()] = { + hash = import.path.hash(), + imported_on_line_number = index, + from_line = 0, + to_line = 0 + } + + _imported_titles[import.prefix] = import.path.hash() + + var origin_hash: int = -1 + for hash_value in known_imports.keys(): + if known_imports[hash_value] == ".": + origin_hash = hash_value + + # Replace any titles or jump points with references to the files they point to (event if they point to their own file) + for i in range(0, content.size()): + var line = content[i] + if line.strip_edges().begins_with("~ "): + var indent: String = "\t".repeat(get_indent(line)) + var title = line.strip_edges().substr(2) + if "/" in line: + var bits = title.split("/") + content[i] = "%s~ %s/%s" % [indent, _imported_titles[bits[0]], bits[1]] + else: + content[i] = "%s~ %s/%s" % [indent, str(path.hash()), title] + + elif "=>< " in line: + var jump: String = line.substr(line.find("=>< ") + "=>< ".length()).strip_edges() + if "/" in jump: + var bits: PackedStringArray = jump.split("/") + var title_hash: int = _imported_titles[bits[0]] + if title_hash == origin_hash: + content[i] = "%s=>< %s" % [line.split("=>< ")[0], bits[1]] + else: + content[i] = "%s=>< %s/%s" % [line.split("=>< ")[0], title_hash, bits[1]] + + elif not jump in ["END", "END!"] and not jump.begins_with("{{"): + content[i] = "%s=>< %s/%s" % [line.split("=>< ")[0], str(path.hash()), jump] + + elif "=> " in line: + var jump: String = line.substr(line.find("=> ") + "=> ".length()).strip_edges() + if "/" in jump: + var bits: PackedStringArray = jump.split("/") + var title_hash: int = _imported_titles[bits[0]] + if title_hash == origin_hash: + content[i] = "%s=> %s" % [line.split("=> ")[0], bits[1]] + else: + content[i] = "%s=> %s/%s" % [line.split("=> ")[0], title_hash, bits[1]] + + elif not jump in ["END", "END!"] and not jump.begins_with("{{"): + content[i] = "%s=> %s/%s" % [line.split("=> ")[0], str(path.hash()), jump] + + imported_paths.append(path) + known_imports[path.hash()] = "\n".join(content) + "\n=> END\n" + return OK + else: + return ERR_FILE_NOT_FOUND + + +## Build a tree of parent/child relationships +func build_line_tree(raw_lines: PackedStringArray) -> DMTreeLine: + var root: DMTreeLine = DMTreeLine.new("") + var parent_chain: Array[DMTreeLine] = [root] + var previous_line: DMTreeLine + var doc_comments: PackedStringArray = [] + + # Get list of known autoloads + var autoload_names: PackedStringArray = get_autoload_names() + + for i in range(0, raw_lines.size()): + var raw_line: String = get_processor()._preprocess_line(raw_lines[i]) + var tree_line: DMTreeLine = DMTreeLine.new(str(i - _imported_line_count)) + + tree_line.line_number = i + 1 + tree_line.type = get_line_type(raw_line) + tree_line.text = raw_line.strip_edges() + + # Handle any "using" directives. + if tree_line.type == DMConstants.TYPE_USING: + var using_match: RegExMatch = regex.USING_REGEX.search(raw_line) + if "state" in using_match.names: + var using_state: String = using_match.strings[using_match.names.state].strip_edges() + if not using_state in autoload_names: + add_error(tree_line.line_number, 0, DMConstants.ERR_UNKNOWN_USING) + elif not using_state in using_states: + using_states.append(using_state) + continue + # Ignore import lines because they've already been processed. + elif is_import_line(raw_line): + continue + + tree_line.indent = get_indent(raw_line) + + # Attach doc comments + if raw_line.strip_edges().begins_with("##"): + doc_comments.append(raw_line.replace("##", "").strip_edges()) + elif tree_line.type == DMConstants.TYPE_DIALOGUE: + tree_line.notes = "\n".join(doc_comments) + doc_comments.clear() + + # Empty lines are only kept so that we can work out groupings of things (eg. randomised + # lines). Therefore we only need to keep one empty line in a row even if there + # are multiple. The indent of an empty line is assumed to be the same as the non-empty line + # following it. That way, grouping calculations should work. + if tree_line.type in [DMConstants.TYPE_UNKNOWN, DMConstants.TYPE_COMMENT] and raw_lines.size() > i + 1: + var next_line = raw_lines[i + 1] + if get_line_type(next_line) in [DMConstants.TYPE_UNKNOWN, DMConstants.TYPE_COMMENT]: + continue + else: + tree_line.type = DMConstants.TYPE_UNKNOWN + tree_line.indent = get_indent(next_line) + + # Nothing should be more than a single indent past its parent. + if tree_line.indent > parent_chain.size(): + add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_INVALID_INDENTATION) + + # Check for indentation changes + if tree_line.indent > parent_chain.size() - 1: + parent_chain.append(previous_line) + elif tree_line.indent < parent_chain.size() - 1: + parent_chain.resize(tree_line.indent + 1) + + # Add any titles to the list of known titles + if tree_line.type == DMConstants.TYPE_TITLE: + var title: String = tree_line.text.substr(2) + if title == "": + add_error(i, 2, DMConstants.ERR_EMPTY_TITLE) + elif titles.has(title): + add_error(i, 2, DMConstants.ERR_DUPLICATE_TITLE) + else: + titles[title] = tree_line.id + if "/" in title: + # Replace the hash title with something human readable. + var bits: PackedStringArray = title.split("/") + if _imported_titles.has(bits[0]): + title = _imported_titles[bits[0]] + "/" + bits[1] + titles[title] = tree_line.id + elif first_title == "" and i >= _imported_line_count: + first_title = tree_line.id + + # Append the current line to the current parent (note: the root is the most basic parent). + var parent: DMTreeLine = parent_chain[parent_chain.size() - 1] + tree_line.parent = weakref(parent) + parent.children.append(tree_line) + + previous_line = tree_line + + return root + + +#endregion + +#region Parsing + + +func parse_line_tree(root: DMTreeLine, parent: DMCompiledLine = null) -> Array[DMCompiledLine]: + var compiled_lines: Array[DMCompiledLine] = [] + + for i in range(0, root.children.size()): + var tree_line: DMTreeLine = root.children[i] + var line: DMCompiledLine = DMCompiledLine.new(tree_line.id, tree_line.type) + + match line.type: + DMConstants.TYPE_UNKNOWN: + line.next_id = get_next_matching_sibling_id(root.children, i, parent, _first) + + DMConstants.TYPE_TITLE: + parse_title_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_CONDITION: + parse_condition_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_WHILE: + parse_while_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_MATCH: + parse_match_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_WHEN: + parse_when_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_MUTATION: + parse_mutation_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_GOTO: + # Extract any weighted random calls before parsing dialogue + if tree_line.text.begins_with("%"): + parse_random_line(tree_line, line, root.children, i, parent) + parse_goto_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_RESPONSE: + parse_response_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_RANDOM: + parse_random_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_DIALOGUE: + # Extract any weighted random calls before parsing dialogue + if tree_line.text.begins_with("%"): + parse_random_line(tree_line, line, root.children, i, parent) + parse_dialogue_line(tree_line, line, root.children, i, parent) + + # Main line map is keyed by ID + lines[line.id] = line + + # Apply any post-processing. + get_processor()._process_line(line) + + # Returned lines order is preserved so that it can be used for compiling children + compiled_lines.append(line) + + return compiled_lines + + +## Parse a title and apply it to the given line +func parse_title_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + line.text = tree_line.text.substr(tree_line.text.find("~ ") + 2).strip_edges() + + # Titles can't have numbers as the first letter (unless they are external titles which get replaced with hashes) + if tree_line.line_number >= _imported_line_count and regex.BEGINS_WITH_NUMBER_REGEX.search(line.text): + result = add_error(tree_line.line_number, 2, DMConstants.ERR_TITLE_BEGINS_WITH_NUMBER) + + # Only import titles are allowed to have "/" in them + var valid_title = regex.VALID_TITLE_REGEX.search(line.text.replace("/", "")) + if not valid_title: + result = add_error(tree_line.line_number, 2, DMConstants.ERR_TITLE_INVALID_CHARACTERS) + + line.next_id = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + + ## Update the titles reference to point to the actual first line + titles[line.text] = line.next_id + + ## Update any lines that point to this title + if _goto_lines.has(line.text): + for goto_line in _goto_lines[line.text]: + goto_line.next_id = line.next_id + + return result + + +## Parse a goto and apply it to the given line. +func parse_goto_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + # Work out where this line is jumping to. + var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(tree_line.text, titles) + if goto_data.error: + return add_error(tree_line.line_number, tree_line.indent + 2, goto_data.error) + if goto_data.next_id or goto_data.expression: + line.next_id = goto_data.next_id + line.next_id_expression = goto_data.expression + add_reference_to_title(goto_data.title, line) + + if goto_data.is_snippet: + line.is_snippet = true + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + + return OK + + +## Parse a condition line and apply to the given line +func parse_condition_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + # Work out the next IDs before parsing the condition line itself so that the last + # child can inherit from the chain. + + # Find the next conditional sibling that is part of this grouping (if there is one). + for next_sibling: DMTreeLine in siblings.slice(sibling_index + 1): + if not next_sibling.type in [DMConstants.TYPE_UNKNOWN, DMConstants.TYPE_CONDITION]: + break + elif next_sibling.type == DMConstants.TYPE_CONDITION: + if next_sibling.text.begins_with("el"): + line.next_sibling_id = next_sibling.id + break + else: + break + + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, func(s: DMTreeLine): + # The next line that isn't a conditional or is a new "if" + return s.type != DMConstants.TYPE_CONDITION or s.text.begins_with("if ") + ) + # Any empty IDs should end the conversation. + if line.next_id_after == DMConstants.ID_NULL: + line.next_id_after = parent.next_id_after if parent != null and parent.next_id_after else DMConstants.ID_END + + # Having no nested body is an immediate failure. + if tree_line.children.size() == 0: + return add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_INVALID_CONDITION_INDENTATION) + + # Try to parse the conditional expression ("else" has no expression). + if "if " in tree_line.text: + var condition: Dictionary = extract_condition(tree_line.text, false, tree_line.indent) + if condition.has("error"): + return add_error(tree_line.line_number, condition.index, condition.error) + else: + line.expression = condition + + # Parse any nested body lines + parse_children(tree_line, line) + + return OK + + +## Parse a while loop and apply it to the given line. +func parse_while_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + + # Parse the while condition + var condition: Dictionary = extract_condition(tree_line.text, false, tree_line.indent) + if condition.has("error"): + return add_error(tree_line.line_number, condition.index, condition.error) + else: + line.expression = condition + + # Parse the nested body (it should take care of looping back to this line when it finishes) + parse_children(tree_line, line) + + return OK + + +func parse_match_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + # The next line after is the next sibling + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + + # Extract the condition to match to + var condition: Dictionary = extract_condition(tree_line.text, false, tree_line.indent) + if condition.has("error"): + result = add_error(tree_line.line_number, condition.index, condition.error) + else: + line.expression = condition + + # Match statements should have children + if tree_line.children.size() == 0: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_INVALID_CONDITION_INDENTATION) + + # Check that all children are when or else. + for child in tree_line.children: + if child.type == DMConstants.TYPE_WHEN: continue + if child.type == DMConstants.TYPE_UNKNOWN: continue + if child.type == DMConstants.TYPE_CONDITION and child.text.begins_with("else"): continue + + result = add_error(child.line_number, child.indent, DMConstants.ERR_EXPECTED_WHEN_OR_ELSE) + + # Each child should be a "when" or "else". We don't need those lines themselves, just their + # condition and the line they point to if the conditions passes. + var children: Array[DMCompiledLine] = parse_children(tree_line, line) + for child: DMCompiledLine in children: + # "when" cases + if child.type == DMConstants.TYPE_WHEN: + line.siblings.append({ + condition = child.expression, + next_id = child.next_id + }) + # "else" case + elif child.type == DMConstants.TYPE_CONDITION: + if line.siblings.any(func(s): return s.has("is_else")): + result = add_error(child.line_number, child.indent, DMConstants.ERR_ONLY_ONE_ELSE_ALLOWED) + else: + line.siblings.append({ + next_id = child.next_id, + is_else = true + }) + # Remove the line from the list of all lines because we don't need it any more. + lines.erase(child.id) + + return result + + +func parse_when_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + # This when line should be found inside a match line + if parent.type != DMConstants.TYPE_MATCH: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_WHEN_MUST_BELONG_TO_MATCH) + + # When lines should have children + if tree_line.children.size() == 0: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_INVALID_CONDITION_INDENTATION) + + # The next line after a when is the same as its parent match line + line.next_id_after = parent.next_id_after + + # Extract the condition to match to + var condition: Dictionary = extract_condition(tree_line.text, false, tree_line.indent) + if condition.has("error"): + result = add_error(tree_line.line_number, condition.index, condition.error) + else: + line.expression = condition + + parse_children(tree_line, line) + + return result + + +## Parse a mutation line and apply it to the given line +func parse_mutation_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var mutation: Dictionary = extract_mutation(tree_line.text) + if mutation.has("error"): + return add_error(tree_line.line_number, mutation.index, mutation.error) + else: + line.expression = mutation + + line.next_id = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + + return OK + + +## Parse a response and apply it to the given line. +func parse_response_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + # Remove the "- " + tree_line.text = tree_line.text.substr(2) + + # Extract the static line ID + var static_line_id: String = extract_static_line_id(tree_line.text) + if static_line_id: + tree_line.text = tree_line.text.replace("[ID:%s]" % [static_line_id], "") + line.translation_key = static_line_id + + # Handle conditional responses and remove them from the prompt text. + if " [if " in tree_line.text: + var condition = extract_condition(tree_line.text, true, tree_line.indent) + if condition.has("error"): + result = add_error(tree_line.line_number, condition.index, condition.error) + else: + line.expression = condition + # Extract just the raw condition text + var found: RegExMatch = regex.WRAPPED_CONDITION_REGEX.search(tree_line.text) + line.expression_text = found.strings[found.names.expression] + + tree_line.text = regex.WRAPPED_CONDITION_REGEX.sub(tree_line.text, "").strip_edges() + + # Find the original response in this group of responses. + var original_response: DMTreeLine = tree_line + for i in range(sibling_index - 1, -1, -1): + if siblings[i].type == DMConstants.TYPE_RESPONSE: + original_response = siblings[i] + elif siblings[i].type != DMConstants.TYPE_UNKNOWN: + break + + # If it's the original response then set up an original line. + if original_response == tree_line: + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, (func(s: DMTreeLine): + # The next line that isn't a response. + return not s.type in [DMConstants.TYPE_RESPONSE, DMConstants.TYPE_UNKNOWN] + ), true) + line.responses = [line.id] + # If this line has children then the next ID is the first child. + if tree_line.children.size() > 0: + parse_children(tree_line, line) + # Otherwise use the same ID for after the random group. + else: + line.next_id = line.next_id_after + # Otherwise let the original line know about it. + else: + var original_line: DMCompiledLine = lines[original_response.id] + line.next_id_after = original_line.next_id_after + line.siblings = original_line.siblings + original_line.responses.append(line.id) + # If this line has children then the next ID is the first child. + if tree_line.children.size() > 0: + parse_children(tree_line, line) + # Otherwise use the original line's next ID after. + else: + line.next_id = original_line.next_id_after + + parse_character_and_dialogue(tree_line, line, siblings, sibling_index, parent) + + return OK + + +## Parse a randomised line +func parse_random_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + # Find the weight + var weight: float = 1 + var found = regex.WEIGHTED_RANDOM_SIBLINGS_REGEX.search(tree_line.text + " ") + var condition: Dictionary = {} + if found: + if found.names.has("weight"): + weight = found.strings[found.names.weight].to_float() + if found.names.has("condition"): + condition = extract_condition(tree_line.text, true, tree_line.indent) + + # Find the original random sibling. It will be the jump off point. + var original_sibling: DMTreeLine = tree_line + for i in range(sibling_index - 1, -1, -1): + if siblings[i] and siblings[i].is_random: + original_sibling = siblings[i] + else: + break + + var weighted_sibling: Dictionary = { weight = weight, id = line.id, condition = condition } + + # If it's the original sibling then set up an original line. + if original_sibling == tree_line: + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, (func(s: DMTreeLine): + # The next line that isn't a randomised line. + # NOTE: DMTreeLine.is_random won't be set at this point so we need to check for the "%" prefix. + return not s.text.begins_with("%") + ), true) + line.siblings = [weighted_sibling] + # If this line has children then the next ID is the first child. + if tree_line.children.size() > 0: + parse_children(tree_line, line) + # Otherwise use the same ID for after the random group. + else: + line.next_id = line.next_id_after + + # Otherwise let the original line know about it. + else: + var original_line: DMCompiledLine = lines[original_sibling.id] + line.next_id_after = original_line.next_id_after + line.siblings = original_line.siblings + original_line.siblings.append(weighted_sibling) + # If this line has children then the next ID is the first child. + if tree_line.children.size() > 0: + parse_children(tree_line, line) + # Otherwise use the original line's next ID after. + else: + line.next_id = original_line.next_id_after + + # Remove the randomise syntax from the line. + tree_line.text = regex.WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(tree_line.text, "") + tree_line.is_random = true + + return OK + + +## Parse some dialogue and apply it to the given line. +func parse_dialogue_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + # Remove escape character + if tree_line.text.begins_with("\\using"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\if"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\elif"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\else"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\while"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\match"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\when"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\do"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\set"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\-"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\~"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\=>"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\%"): tree_line.text = tree_line.text.substr(1) + + # Append any further dialogue + for i in range(0, tree_line.children.size()): + var child: DMTreeLine = tree_line.children[i] + if child.type == DMConstants.TYPE_DIALOGUE: + # Nested dialogue lines cannot have further nested dialogue. + if child.children.size() > 0: + add_error(child.children[0].line_number, child.children[0].indent, DMConstants.ERR_INVALID_INDENTATION) + # Mark this as a dialogue child of another dialogue line. + child.is_nested_dialogue = true + var child_line = DMCompiledLine.new("", DMConstants.TYPE_DIALOGUE) + parse_character_and_dialogue(child, child_line, [], 0, parent) + var child_static_line_id: String = extract_static_line_id(child.text) + if child_line.character != "" or child_static_line_id != "": + add_error(child.line_number, child.indent, DMConstants.ERR_UNEXPECTED_SYNTAX_ON_NESTED_DIALOGUE_LINE) + # Check that only the last child (or none) has a jump reference + if i < tree_line.children.size() - 1 and " =>" in child.text: + add_error(child.line_number, child.indent, DMConstants.ERR_NESTED_DIALOGUE_INVALID_JUMP) + if i == 0 and " =>" in tree_line.text: + add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_NESTED_DIALOGUE_INVALID_JUMP) + + tree_line.text += "\n" + child.text + elif child.type == DMConstants.TYPE_UNKNOWN: + tree_line.text += "\n" + else: + result = add_error(child.line_number, child.indent, DMConstants.ERR_INVALID_INDENTATION) + + # Extract the static line ID + var static_line_id: String = extract_static_line_id(tree_line.text) + if static_line_id: + tree_line.text = tree_line.text.replace(" [ID:", "[ID:").replace("[ID:%s]" % [static_line_id], "") + line.translation_key = static_line_id + + # Check for simultaneous lines + if tree_line.text.begins_with("| "): + # Jumps are only allowed on the origin line. + if " =>" in tree_line.text: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES) + # Check for a valid previous line. + tree_line.text = tree_line.text.substr(2) + var previous_sibling: DMTreeLine = siblings[sibling_index - 1] + if previous_sibling.type != DMConstants.TYPE_DIALOGUE: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_CONCURRENT_LINE_WITHOUT_ORIGIN) + else: + # Because the previous line's concurrent_lines array is the same as + # any line before that this doesn't need to check any higher up. + var previous_line: DMCompiledLine = lines[previous_sibling.id] + previous_line.concurrent_lines.append(line.id) + line.concurrent_lines = previous_line.concurrent_lines + + parse_character_and_dialogue(tree_line, line, siblings, sibling_index, parent) + + # Check for any inline expression errors + var resolved_line_data: DMResolvedLineData = DMResolvedLineData.new("") + var bbcodes: Array[Dictionary] = resolved_line_data.find_bbcode_positions_in_string(tree_line.text, true, true) + for bbcode: Dictionary in bbcodes: + var tag: String = bbcode.code + var code: String = bbcode.raw_args + if tag.begins_with("$>") or tag.begins_with("do") or tag.begins_with("set") or tag.begins_with("if"): + var expression: Array = expression_parser.tokenise(code, DMConstants.TYPE_MUTATION, bbcode.start + bbcode.code.length()) + if expression.size() == 0: + add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_INVALID_EXPRESSION) + elif expression[0].type == DMConstants.TYPE_ERROR: + add_error(tree_line.line_number, tree_line.indent + expression[0].i, expression[0].value) + + # If the line isn't part of a weighted random group then make it point to the next + # available sibling. + if line.next_id == DMConstants.ID_NULL and line.siblings.size() == 0: + line.next_id = get_next_matching_sibling_id(siblings, sibling_index, parent, func(s: DMTreeLine): + # Ignore concurrent lines. + return not s.text.begins_with("| ") + ) + + return result + + +## Parse the character name and dialogue and apply it to a given line. +func parse_character_and_dialogue(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + var text: String = tree_line.text + + # Attach any doc comments. + line.notes = tree_line.notes + + # Extract tags. + var tag_data: DMResolvedTagData = DMResolvedTagData.new(text) + line.tags = tag_data.tags + text = tag_data.text_without_tags + + # Handle inline gotos and remove them from the prompt text. + if " =><" in text: + # Because of when the return point needs to be known at runtime we need to split + # this line into two (otherwise the return point would be dependent on the balloon). + var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, titles) + if goto_data.error: + result = add_error(tree_line.line_number, tree_line.indent + 3, goto_data.error) + if goto_data.next_id or goto_data.expression: + text = goto_data.text_without_goto + var goto_line: DMCompiledLine = DMCompiledLine.new(line.id + ".1", DMConstants.TYPE_GOTO) + goto_line.next_id = goto_data.next_id + line.next_id_expression = goto_data.expression + if line.type == DMConstants.TYPE_RESPONSE: + goto_line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, func(s: DMTreeLine): + # If this is coming from a response then we want the next non-response line. + return s.type != DMConstants.TYPE_RESPONSE + ) + else: + goto_line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + goto_line.is_snippet = true + lines[goto_line.id] = goto_line + line.next_id = goto_line.id + add_reference_to_title(goto_data.title, goto_line) + elif " =>" in text: + var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, titles) + if goto_data.error: + result = add_error(tree_line.line_number, tree_line.indent + 2, goto_data.error) + if goto_data.next_id or goto_data.expression: + text = goto_data.text_without_goto + line.next_id = goto_data.next_id + line.next_id_expression = goto_data.expression + add_reference_to_title(goto_data.title, line) + + # Handle the dialogue. + text = text.replace("\\:", "!ESCAPED_COLON!") + if ": " in text: + # If a character was given then split it out. + var bits = Array(text.strip_edges().split(": ")) + line.character = bits.pop_front().strip_edges() + if not line.character in character_names: + character_names.append(line["character"]) + # Character names can have expressions in them. + line.character_replacements = expression_parser.extract_replacements(line.character, tree_line.indent) + for replacement in line.character_replacements: + if replacement.has("error"): + result = add_error(tree_line.line_number, replacement.index, replacement.error) + text = ": ".join(bits).replace("!ESCAPED_COLON!", ":") + else: + line.character = "" + text = text.replace("!ESCAPED_COLON!", ":") + + # Extract any expressions in the dialogue. + line.text_replacements = expression_parser.extract_replacements(text, line.character.length() + 2 + tree_line.indent) + for replacement in line.text_replacements: + if replacement.has("error"): + result = add_error(tree_line.line_number, replacement.index, replacement.error) + + # Replace any newlines. + text = text.replace("\\n", "\n").strip_edges() + + # If there was no manual translation key then just use the text itself (unless this is a + # child dialogue below another dialogue line). + if not tree_line.is_nested_dialogue and line.translation_key == "": + # Show an error if missing translations is enabled + if DMSettings.get_setting(DMSettings.MISSING_TRANSLATIONS_ARE_ERRORS, false): + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_MISSING_ID) + else: + line.translation_key = text + + line.text = text + + # IDs can't be duplicated for text that doesn't match. + if line.translation_key != "": + if _known_translation_keys.has(line.translation_key) and _known_translation_keys.get(line.translation_key) != line.text: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_DUPLICATE_ID) + else: + _known_translation_keys[line.translation_key] = line.text + + return result + + +#endregion + +#region Errors + + +## Add a compilation error to the list. Returns the given error code. +func add_error(line_number: int, column_number: int, error: int) -> Error: + # See if the error was in an imported file + for item in _imported_line_map.values(): + if line_number < item.to_line: + errors.append({ + line_number = item.imported_on_line_number, + column_number = 0, + error = DMConstants.ERR_ERRORS_IN_IMPORTED_FILE, + external_error = error, + external_line_number = line_number + }) + return error + + # Otherwise, it's in this file + errors.append({ + line_number = line_number - _imported_line_count, + column_number = column_number, + error = error + }) + + return error + + +#endregion + +#region Helpers + + +## Get the names of any autoloads in the project. +func get_autoload_names() -> PackedStringArray: + var autoloads: PackedStringArray = [] + + var project = ConfigFile.new() + project.load("res://project.godot") + if project.has_section("autoload"): + return Array(project.get_section_keys("autoload")).filter(func(key): return key != "DialogueManager") + + return autoloads + + +## Check if a line is importing another file. +func is_import_line(text: String) -> bool: + return text.begins_with("import ") and " as " in text + + +## Extract the import information from an import line +func extract_import_path_and_name(line: String) -> Dictionary: + var found: RegExMatch = regex.IMPORT_REGEX.search(line) + if found: + return { + path = found.strings[found.names.path], + prefix = found.strings[found.names.prefix] + } + else: + return {} + + +## Load the configured processor (or the default one is none configured). +func get_processor() -> DMDialogueProcessor: + if processor == null: + var processor_path: String = DMSettings.get_setting(DMSettings.DIALOGUE_PROCESSOR_PATH, "") + processor = DMDialogueProcessor.new() if processor_path.is_empty() else load(processor_path).new() + return processor + + +## Get the indent of a raw line +func get_indent(raw_line: String) -> int: + var tabs: RegExMatch = regex.INDENT_REGEX.search(raw_line) + if tabs: + return tabs.get_string().length() + else: + return 0 + + +## Get the type of a raw line +func get_line_type(raw_line: String) -> String: + raw_line = raw_line.strip_edges() + var text: String = regex.WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(raw_line + " ", "").strip_edges() + + if text.begins_with("import "): + return DMConstants.TYPE_IMPORT + + if text.begins_with("using "): + return DMConstants.TYPE_USING + + if text.begins_with("#"): + return DMConstants.TYPE_COMMENT + + if text.begins_with("~ "): + return DMConstants.TYPE_TITLE + + if text.begins_with("if ") or text.begins_with("elif") or text.begins_with("else"): + return DMConstants.TYPE_CONDITION + + if text.begins_with("while "): + return DMConstants.TYPE_WHILE + + if text.begins_with("match "): + return DMConstants.TYPE_MATCH + + if text.begins_with("when "): + return DMConstants.TYPE_WHEN + + if text.begins_with("do ") or text.begins_with("do! ") or text.begins_with("set ") or text.begins_with("$> ") or text.begins_with("$>> "): + return DMConstants.TYPE_MUTATION + + if text.begins_with("=> ") or text.begins_with("=>< "): + return DMConstants.TYPE_GOTO + + if text.begins_with("- "): + return DMConstants.TYPE_RESPONSE + + if raw_line.begins_with("%") and text.is_empty(): + return DMConstants.TYPE_RANDOM + + if not text.is_empty(): + return DMConstants.TYPE_DIALOGUE + + return DMConstants.TYPE_UNKNOWN + + +## Get the next sibling that passes a [Callable] matcher. +func get_next_matching_sibling_id(siblings: Array[DMTreeLine], from_index: int, parent: DMCompiledLine, matcher: Callable, with_empty_lines: bool = false) -> String: + for i in range(from_index + 1, siblings.size()): + var next_sibling: DMTreeLine = siblings[i] + + if not with_empty_lines: + # Ignore empty lines + if not next_sibling or next_sibling.type == DMConstants.TYPE_UNKNOWN: + continue + + if matcher.call(next_sibling): + return next_sibling.id + + # If no next ID can be found then check the parent for where to go next. + if parent != null: + return parent.id if parent.type == DMConstants.TYPE_WHILE else parent.next_id_after + + return DMConstants.ID_NULL + + +## Extract a static line ID from some text. +func extract_static_line_id(text: String) -> String: + # Find a static translation key, eg. [ID:something] + var found: RegExMatch = regex.STATIC_LINE_ID_REGEX.search(text) + if found: + return found.strings[found.names.id] + else: + return "" + + +## Extract a condition (or inline condition) from some text. +func extract_condition(text: String, is_wrapped: bool, index: int) -> Dictionary: + var regex: RegEx = regex.WRAPPED_CONDITION_REGEX if is_wrapped else regex.CONDITION_REGEX + var found: RegExMatch = regex.search(text) + + if found == null: + return { + index = 0, + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + + var raw_condition: String = found.strings[found.names.expression] + if raw_condition.ends_with(":"): + raw_condition = raw_condition.substr(0, raw_condition.length() - 1) + + var expression: Array = expression_parser.tokenise(raw_condition, DMConstants.TYPE_CONDITION, index + found.get_start("expression")) + + if expression.size() == 0: + return { + index = index + found.get_start("expression"), + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + elif expression[0].type == DMConstants.TYPE_ERROR: + return { + index = expression[0].i, + error = expression[0].value + } + else: + return { + expression = expression + } + + +## Extract a mutation from some text. +func extract_mutation(text: String) -> Dictionary: + var found: RegExMatch = regex.MUTATION_REGEX.search(text) + + if not found: + return { + index = 0, + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + + if found.names.has("expression"): + var expression: Array = expression_parser.tokenise(found.strings[found.names.expression], DMConstants.TYPE_MUTATION, found.get_start("expression")) + if expression.size() == 0: + return { + index = found.get_start("expression"), + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + elif expression[0].type == DMConstants.TYPE_ERROR: + return { + index = expression[0].i, + error = expression[0].value + } + else: + return { + expression = expression, + is_blocking = not "!" in found.strings[found.names.keyword] and found.strings[found.names.keyword] != "$>>" + } + + else: + return { + index = found.get_start(), + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + + +## Keep track of lines referencing titles because their own next_id might not have been resolved yet. +func add_reference_to_title(title: String, line: DMCompiledLine) -> void: + if title in [DMConstants.ID_END, DMConstants.ID_END_CONVERSATION, DMConstants.ID_NULL]: return + + if not _goto_lines.has(title): + _goto_lines[title] = [] + _goto_lines[title].append(line) + + +## Parse a nested block of child lines +func parse_children(tree_line: DMTreeLine, line: DMCompiledLine) -> Array[DMCompiledLine]: + var children = parse_line_tree(tree_line, line) + if children.size() > 0: + line.next_id = children.front().id + # The last child should jump to the next line after its parent condition group + var last_child: DMCompiledLine = children.back() + if last_child.next_id == DMConstants.ID_NULL: + last_child.next_id = line.next_id_after + if last_child.siblings.size() > 0: + for sibling in last_child.siblings: + lines.get(sibling.id).next_id = last_child.next_id + + return children + + +#endregion diff --git a/addons/dialogue_manager/compiler/compilation.gd.uid b/addons/dialogue_manager/compiler/compilation.gd.uid new file mode 100644 index 0000000..24a13ee --- /dev/null +++ b/addons/dialogue_manager/compiler/compilation.gd.uid @@ -0,0 +1 @@ +uid://dsgpnyqg6cprg diff --git a/addons/dialogue_manager/compiler/compiled_line.gd b/addons/dialogue_manager/compiler/compiled_line.gd new file mode 100644 index 0000000..9e34f09 --- /dev/null +++ b/addons/dialogue_manager/compiler/compiled_line.gd @@ -0,0 +1,161 @@ +## A compiled line of dialogue. +class_name DMCompiledLine extends RefCounted + + +## The ID of the line +var id: String +## The translation key (or static line ID). +var translation_key: String = "" +## The type of line. +var type: String = "" +## The character name. +var character: String = "" +## Any interpolation expressions for the character name. +var character_replacements: Array[Dictionary] = [] +## The text of the line. +var text: String = "" +## Any interpolation expressions for the text. +var text_replacements: Array[Dictionary] = [] +## Any response siblings associated with this line. +var responses: PackedStringArray = [] +## Any randomise or case siblings for this line. +var siblings: Array[Dictionary] = [] +## Any lines said simultaneously. +var concurrent_lines: PackedStringArray = [] +## Any tags on this line. +var tags: PackedStringArray = [] +## The condition or mutation expression for this line. +var expression: Dictionary = {} +## The express as the raw text that was given. +var expression_text: String = "" +## The next sequential line to go to after this line. +var next_id: String = "" +## The next line to go to after this line if it is unknown and compile time. +var next_id_expression: Array[Dictionary] = [] +## Whether this jump line should return after the jump target sequence has ended. +var is_snippet: bool = false +## The ID of the next sibling line. +var next_sibling_id: String = "" +## The ID after this line if it belongs to a block (eg. conditions). +var next_id_after: String = "" +## Any doc comments attached to this line. +var notes: String = "" + + +#region Hooks + + +func _init(initial_id: String, initial_type: String) -> void: + id = initial_id + type = initial_type + + +func _to_string() -> String: + var s: Array = [ + "[%s]" % [type], + "%s:" % [character] if character != "" else null, + text if text != "" else null, + expression if expression.size() > 0 else null, + "[%s]" % [",".join(tags)] if tags.size() > 0 else null, + str(siblings) if siblings.size() > 0 else null, + str(responses) if responses.size() > 0 else null, + "=> END" if "end" in next_id else "=> %s" % [next_id], + "(~> %s)" % [next_sibling_id] if next_sibling_id != "" else null, + "(==> %s)" % [next_id_after] if next_id_after != "" else null, + ].filter(func(item): return item != null) + + return " ".join(s) + + +#endregion + +#region Helpers + + +## Express this line as a [Dictionary] that can be stored in a resource. +func to_data() -> Dictionary: + var d: Dictionary = { + id = id, + type = type, + next_id = next_id + } + + if next_id_expression.size() > 0: + d.next_id_expression = next_id_expression + + match type: + DMConstants.TYPE_CONDITION: + d.condition = expression + if not next_sibling_id.is_empty(): + d.next_sibling_id = next_sibling_id + d.next_id_after = next_id_after + + DMConstants.TYPE_WHILE: + d.condition = expression + d.next_id_after = next_id_after + + DMConstants.TYPE_MATCH: + d.condition = expression + d.next_id_after = next_id_after + d.cases = siblings + + DMConstants.TYPE_MUTATION: + d.mutation = expression + + DMConstants.TYPE_GOTO: + d.is_snippet = is_snippet + d.next_id_after = next_id_after + if not siblings.is_empty(): + d.siblings = siblings + + DMConstants.TYPE_RANDOM: + d.siblings = siblings + + DMConstants.TYPE_RESPONSE: + d.text = text + + if not responses.is_empty(): + d.responses = responses + + if translation_key != text: + d.translation_key = translation_key + if not expression.is_empty(): + d.condition = expression + if not character.is_empty(): + d.character = character + if not character_replacements.is_empty(): + d.character_replacements = character_replacements + if not text_replacements.is_empty(): + d.text_replacements = text_replacements + if not tags.is_empty(): + d.tags = tags + if not notes.is_empty(): + d.notes = notes + if not expression_text.is_empty(): + d.condition_as_text = expression_text + + DMConstants.TYPE_DIALOGUE: + d.text = text + + if translation_key != text: + d.translation_key = translation_key + + if not character.is_empty(): + d.character = character + if not character_replacements.is_empty(): + d.character_replacements = character_replacements + if not text_replacements.is_empty(): + d.text_replacements = text_replacements + if not tags.is_empty(): + d.tags = tags + if not notes.is_empty(): + d.notes = notes + if not siblings.is_empty(): + d.siblings = siblings + if not concurrent_lines.is_empty(): + d.concurrent_lines = concurrent_lines + + return d + + +#endregion diff --git a/addons/dialogue_manager/compiler/compiled_line.gd.uid b/addons/dialogue_manager/compiler/compiled_line.gd.uid new file mode 100644 index 0000000..17ec55e --- /dev/null +++ b/addons/dialogue_manager/compiler/compiled_line.gd.uid @@ -0,0 +1 @@ +uid://dg8j5hudp4210 diff --git a/addons/dialogue_manager/compiler/compiler.gd b/addons/dialogue_manager/compiler/compiler.gd new file mode 100644 index 0000000..99f6744 --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler.gd @@ -0,0 +1,57 @@ +## A compiler of Dialogue Manager dialogue. +class_name DMCompiler extends RefCounted + + +## Compile a dialogue script. +static func compile_string(text: String, path: String) -> DMCompilerResult: + var compilation: DMCompilation = DMCompilation.new() + compilation.compile(text, path) + + var result: DMCompilerResult = DMCompilerResult.new() + result.imported_paths = compilation.imported_paths + result.using_states = compilation.using_states + result.character_names = compilation.character_names + result.titles = compilation.titles + result.first_title = compilation.first_title + result.errors = compilation.errors + result.lines = compilation.data + result.raw_text = text + + return result + + +## Get the line type of a string. The returned string will match one of the [code]TYPE_[/code] constants of [DMConstants]. +static func get_line_type(text: String) -> String: + var compilation: DMCompilation = DMCompilation.new() + return compilation.get_line_type(text) + + +## Get the static line ID (eg. [code][ID:SOMETHING][/code]) of some text. +static func get_static_line_id(text: String) -> String: + var compilation: DMCompilation = DMCompilation.new() + return compilation.extract_static_line_id(text) + + +## Get the translatable part of a line. +static func extract_translatable_string(text: String) -> String: + var compilation: DMCompilation = DMCompilation.new() + + var tree_line = DMTreeLine.new("") + tree_line.text = text + var line: DMCompiledLine = DMCompiledLine.new("", compilation.get_line_type(text)) + compilation.parse_character_and_dialogue(tree_line, line, [tree_line], 0, null) + + return line.text + + +## Extract a mutation from a string. +static func extract_mutation(text: String) -> Dictionary: + var compilation: DMCompilation = DMCompilation.new() + return compilation.extract_mutation(text) + + +## Get the known titles in a dialogue script. +static func get_titles_in_text(text: String, path: String) -> Dictionary: + var compilation: DMCompilation = DMCompilation.new() + compilation.build_line_tree(compilation.inject_imported_files(text, path)) + return compilation.titles diff --git a/addons/dialogue_manager/compiler/compiler.gd.uid b/addons/dialogue_manager/compiler/compiler.gd.uid new file mode 100644 index 0000000..e041f10 --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler.gd.uid @@ -0,0 +1 @@ +uid://chtfdmr0cqtp4 diff --git a/addons/dialogue_manager/compiler/compiler_regex.gd b/addons/dialogue_manager/compiler/compiler_regex.gd new file mode 100644 index 0000000..f2b04b0 --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler_regex.gd @@ -0,0 +1,50 @@ +## A collection of [RegEx] for use by the [DMCompiler]. +class_name DMCompilerRegEx extends RefCounted + + +var IMPORT_REGEX: RegEx = RegEx.create_from_string("import \"(?[^\"]+)\" as (?[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+)") +var USING_REGEX: RegEx = RegEx.create_from_string("^using (?.*)$") +var INDENT_REGEX: RegEx = RegEx.create_from_string("^\\t+") +var VALID_TITLE_REGEX: RegEx = RegEx.create_from_string("^[a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*$") +var BEGINS_WITH_NUMBER_REGEX: RegEx = RegEx.create_from_string("^\\d") +var CONDITION_REGEX: RegEx = RegEx.create_from_string("(if|elif|while|else if|match|when) (?.*)\\:?") +var WRAPPED_CONDITION_REGEX: RegEx = RegEx.create_from_string("\\[if (?.*)\\]") +var MUTATION_REGEX: RegEx = RegEx.create_from_string("(?\\$\\>|\\$\\>\\>|do|do!|set) (?.*)") +var STATIC_LINE_ID_REGEX: RegEx = RegEx.create_from_string("\\[ID:(?.*?)\\]") +var WEIGHTED_RANDOM_SIBLINGS_REGEX: RegEx = RegEx.create_from_string("^\\%(?[\\d.]+)?( \\[if (?.+?)\\])? ") +var GOTO_REGEX: RegEx = RegEx.create_from_string("=>.*)") + +var INLINE_RANDOM_REGEX: RegEx = RegEx.create_from_string("\\[\\[(?.*?)\\]\\]") +var INLINE_CONDITIONALS_REGEX: RegEx = RegEx.create_from_string("\\[if (?.+?)\\](?.*?)\\[\\/if\\]") + +var TAGS_REGEX: RegEx = RegEx.create_from_string("\\[#(?.*?)\\]") + +var REPLACEMENTS_REGEX: RegEx = RegEx.create_from_string("{{(.*?)}}") + +var ALPHA_NUMERIC: RegEx = RegEx.create_from_string("[^a-zA-Z0-9\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+") + +var TOKEN_DEFINITIONS: Dictionary = { + DMConstants.TOKEN_FUNCTION: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\("), + DMConstants.TOKEN_DICTIONARY_REFERENCE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\["), + DMConstants.TOKEN_PARENS_OPEN: RegEx.create_from_string("^\\("), + DMConstants.TOKEN_PARENS_CLOSE: RegEx.create_from_string("^\\)"), + DMConstants.TOKEN_BRACKET_OPEN: RegEx.create_from_string("^\\["), + DMConstants.TOKEN_BRACKET_CLOSE: RegEx.create_from_string("^\\]"), + DMConstants.TOKEN_BRACE_OPEN: RegEx.create_from_string("^\\{"), + DMConstants.TOKEN_BRACE_CLOSE: RegEx.create_from_string("^\\}"), + DMConstants.TOKEN_COLON: RegEx.create_from_string("^:"), + DMConstants.TOKEN_COMPARISON: RegEx.create_from_string("^(==|<=|>=|<|>|!=|in )"), + DMConstants.TOKEN_ASSIGNMENT: RegEx.create_from_string("^(\\+=|\\-=|\\*=|/=|=)"), + DMConstants.TOKEN_NUMBER: RegEx.create_from_string("^\\-?\\d+(\\.\\d+)?"), + DMConstants.TOKEN_OPERATOR: RegEx.create_from_string("^(\\+|\\-|\\*|/|%)"), + DMConstants.TOKEN_COMMA: RegEx.create_from_string("^,"), + DMConstants.TOKEN_NULL_COALESCE: RegEx.create_from_string("^\\?\\."), + DMConstants.TOKEN_DOT: RegEx.create_from_string("^\\."), + DMConstants.TOKEN_STRING: RegEx.create_from_string("^&?(\".*?\"|\'.*?\')"), + DMConstants.TOKEN_NOT: RegEx.create_from_string("^(not( |$)|!)"), + DMConstants.TOKEN_AND_OR: RegEx.create_from_string("^(and|or|&&|\\|\\|)( |$)"), + DMConstants.TOKEN_VARIABLE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*"), + DMConstants.TOKEN_COMMENT: RegEx.create_from_string("^#.*"), + DMConstants.TOKEN_CONDITION: RegEx.create_from_string("^(if|elif|else)"), + DMConstants.TOKEN_BOOL: RegEx.create_from_string("^(true|false)") +} diff --git a/addons/dialogue_manager/compiler/compiler_regex.gd.uid b/addons/dialogue_manager/compiler/compiler_regex.gd.uid new file mode 100644 index 0000000..bd969df --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler_regex.gd.uid @@ -0,0 +1 @@ +uid://d3tvcrnicjibp diff --git a/addons/dialogue_manager/compiler/compiler_result.gd b/addons/dialogue_manager/compiler/compiler_result.gd new file mode 100644 index 0000000..acbf60f --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler_result.gd @@ -0,0 +1,27 @@ +## The result of using the [DMCompiler] to compile some dialogue. +class_name DMCompilerResult extends RefCounted + + +## Any paths that were imported into the compiled dialogue file. +var imported_paths: PackedStringArray = [] + +## Any "using" directives. +var using_states: PackedStringArray = [] + +## All titles in the file and the line they point to. +var titles: Dictionary = {} + +## The first title in the file. +var first_title: String = "" + +## All character names. +var character_names: PackedStringArray = [] + +## Any compilation errors. +var errors: Array[Dictionary] = [] + +## A map of all compiled lines. +var lines: Dictionary = {} + +## The raw dialogue text. +var raw_text: String = "" diff --git a/addons/dialogue_manager/compiler/compiler_result.gd.uid b/addons/dialogue_manager/compiler/compiler_result.gd.uid new file mode 100644 index 0000000..f1f76fd --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler_result.gd.uid @@ -0,0 +1 @@ +uid://dmk74tknimqvg diff --git a/addons/dialogue_manager/compiler/expression_parser.gd b/addons/dialogue_manager/compiler/expression_parser.gd new file mode 100644 index 0000000..7dfb2af --- /dev/null +++ b/addons/dialogue_manager/compiler/expression_parser.gd @@ -0,0 +1,529 @@ +## A class for parsing a condition/mutation expression for use with the [DMCompiler]. +class_name DMExpressionParser extends RefCounted + + +var include_comments: bool = false + + +# Reference to the common [RegEx] that the parser needs. +var regex: DMCompilerRegEx = DMCompilerRegEx.new() + + +## Break a string down into an expression. +func tokenise(text: String, line_type: String, index: int) -> Array: + var tokens: Array[Dictionary] = [] + var limit: int = 0 + while text.strip_edges() != "" and limit < 1000: + limit += 1 + var found = _find_match(text) + if found.size() > 0: + tokens.append({ + index = index, + type = found.type, + value = found.value + }) + index += found.value.length() + text = found.remaining_text + elif text.begins_with(" "): + index += 1 + text = text.substr(1) + else: + return _build_token_tree_error([], DMConstants.ERR_INVALID_EXPRESSION, index) + + return _build_token_tree(tokens, line_type, "")[0] + + +## Extract any expressions from some text +func extract_replacements(text: String, index: int) -> Array[Dictionary]: + var founds: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(text) + + if founds == null or founds.size() == 0: + return [] + + var replacements: Array[Dictionary] = [] + for found in founds: + var replacement: Dictionary = {} + var value_in_text: String = found.strings[0].substr(0, found.strings[0].length() - 2).substr(2) + + # If there are closing curlie hard-up against the end of a {{...}} block then check for further + # curlies just outside of the block. + var text_suffix: String = text.substr(found.get_end(0)) + var expression_suffix: String = "" + while text_suffix.begins_with("}"): + expression_suffix += "}" + text_suffix = text_suffix.substr(1) + value_in_text += expression_suffix + + var expression: Array = tokenise(value_in_text, DMConstants.TYPE_DIALOGUE, index + found.get_start(1)) + if expression.size() == 0: + replacement = { + index = index + found.get_start(1), + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + elif expression[0].type == DMConstants.TYPE_ERROR: + replacement = { + index = expression[0].i, + error = expression[0].value + } + else: + replacement = { + value_in_text = "{{%s}}" % value_in_text, + expression = expression + } + replacements.append(replacement) + + return replacements + + +#region Helpers + + +# Create a token that represents an error. +func _build_token_tree_error(tree: Array, error: int, index: int) -> Array: + tree.insert(0, { + type = DMConstants.TOKEN_ERROR, + value = error, + i = index + }) + return tree + + +# Convert a list of tokens into an abstract syntax tree. +func _build_token_tree(tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Array: + var tree: Array[Dictionary] = [] + var limit = 0 + while tokens.size() > 0 and limit < 1000: + limit += 1 + var token = tokens.pop_front() + + var error = _check_next_token(token, tokens, line_type, expected_close_token) + if error != OK: + var error_token: Dictionary = tokens[1] if tokens.size() > 1 else token + return [_build_token_tree_error(tree, error, error_token.index), tokens] + + match token.type: + DMConstants.TOKEN_COMMENT: + if include_comments: + tree.append({ + type = DMConstants.TOKEN_COMMENT, + value = token.value, + i = token.index + }) + + DMConstants.TOKEN_FUNCTION: + var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_PARENS_CLOSE) + + if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR: + return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens] + + tree.append({ + type = DMConstants.TOKEN_FUNCTION, + # Consume the trailing "(" + function = token.value.substr(0, token.value.length() - 1), + value = _tokens_to_list(sub_tree[0]), + i = token.index + }) + tokens = sub_tree[1] + + DMConstants.TOKEN_DICTIONARY_REFERENCE: + var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACKET_CLOSE) + + if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR: + return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens] + + var args = _tokens_to_list(sub_tree[0]) + if args.size() != 1: + return [_build_token_tree_error(tree, DMConstants.ERR_INVALID_INDEX, token.index), tokens] + + tree.append({ + type = DMConstants.TOKEN_DICTIONARY_REFERENCE, + # Consume the trailing "[" + variable = token.value.substr(0, token.value.length() - 1), + value = args[0], + i = token.index + }) + tokens = sub_tree[1] + + DMConstants.TOKEN_BRACE_OPEN: + var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACE_CLOSE) + + if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR: + return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens] + + var t = sub_tree[0] + for i in range(0, t.size() - 2): + # Convert Lua style dictionaries to string keys + if t[i].type == DMConstants.TOKEN_VARIABLE and t[i+1].type == DMConstants.TOKEN_ASSIGNMENT: + t[i].type = DMConstants.TOKEN_STRING + t[i+1].type = DMConstants.TOKEN_COLON + t[i+1].erase("value") + + tree.append({ + type = DMConstants.TOKEN_DICTIONARY, + value = _tokens_to_dictionary(sub_tree[0]), + i = token.index + }) + + tokens = sub_tree[1] + + DMConstants.TOKEN_BRACKET_OPEN: + var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACKET_CLOSE) + + if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR: + return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens] + + var type = DMConstants.TOKEN_ARRAY + var value = _tokens_to_list(sub_tree[0]) + + # See if this is referencing a nested dictionary value + if tree.size() > 0: + var previous_token = tree[tree.size() - 1] + if previous_token.type in [DMConstants.TOKEN_DICTIONARY_REFERENCE, DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE]: + type = DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE + value = value[0] + + tree.append({ + type = type, + value = value, + i = token.index + }) + tokens = sub_tree[1] + + DMConstants.TOKEN_PARENS_OPEN: + var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_PARENS_CLOSE) + + if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR: + return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens] + + tree.append({ + type = DMConstants.TOKEN_GROUP, + value = sub_tree[0], + i = token.index + }) + tokens = sub_tree[1] + + DMConstants.TOKEN_PARENS_CLOSE, \ + DMConstants.TOKEN_BRACE_CLOSE, \ + DMConstants.TOKEN_BRACKET_CLOSE: + if token.type != expected_close_token: + return [_build_token_tree_error(tree, DMConstants.ERR_UNEXPECTED_CLOSING_BRACKET, token.index), tokens] + + tree.append({ + type = token.type, + i = token.index + }) + + return [tree, tokens] + + DMConstants.TOKEN_NOT: + # Double nots negate each other + if tokens.size() > 0 and tokens.front().type == DMConstants.TOKEN_NOT: + tokens.pop_front() + else: + tree.append({ + type = token.type, + i = token.index + }) + + DMConstants.TOKEN_COMMA, \ + DMConstants.TOKEN_COLON, \ + DMConstants.TOKEN_DOT, \ + DMConstants.TOKEN_NULL_COALESCE: + tree.append({ + type = token.type, + i = token.index + }) + + DMConstants.TOKEN_COMPARISON, \ + DMConstants.TOKEN_ASSIGNMENT, \ + DMConstants.TOKEN_OPERATOR, \ + DMConstants.TOKEN_AND_OR, \ + DMConstants.TOKEN_VARIABLE: + var value = token.value.strip_edges() + if value == "&&": + value = "and" + elif value == "||": + value = "or" + tree.append({ + type = token.type, + value = value, + i = token.index + }) + + DMConstants.TOKEN_STRING: + if token.value.begins_with("&"): + tree.append({ + type = token.type, + value = StringName(token.value.substr(2, token.value.length() - 3)), + i = token.index + }) + else: + tree.append({ + type = token.type, + value = token.value.substr(1, token.value.length() - 2), + i = token.index + }) + + DMConstants.TOKEN_CONDITION: + return [_build_token_tree_error(tree, DMConstants.ERR_UNEXPECTED_CONDITION, token.index), token] + + DMConstants.TOKEN_BOOL: + tree.append({ + type = token.type, + value = token.value.to_lower() == "true", + i = token.index + }) + + DMConstants.TOKEN_NUMBER: + var value = token.value.to_float() if "." in token.value else token.value.to_int() + # If previous token is a number and this one is a negative number then + # inject a minus operator token in between them. + if tree.size() > 0 and token.value.begins_with("-") and tree[tree.size() - 1].type == DMConstants.TOKEN_NUMBER: + tree.append(({ + type = DMConstants.TOKEN_OPERATOR, + value = "-", + i = token.index + })) + tree.append({ + type = token.type, + value = -1 * value, + i = token.index + }) + else: + tree.append({ + type = token.type, + value = value, + i = token.index + }) + + if expected_close_token != "": + var index: int = tokens[0].i if tokens.size() > 0 else 0 + return [_build_token_tree_error(tree, DMConstants.ERR_MISSING_CLOSING_BRACKET, index), tokens] + + return [tree, tokens] + + +# Check the next token to see if it is valid to follow this one. +func _check_next_token(token: Dictionary, next_tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Error: + var next_token: Dictionary = { type = null } + if next_tokens.size() > 0: + next_token = next_tokens.front() + + # Guard for assigning in a condition. If the assignment token isn't inside a Lua dictionary + # then it's an unexpected assignment in a condition line. + if token.type == DMConstants.TOKEN_ASSIGNMENT and line_type == DMConstants.TYPE_CONDITION and not next_tokens.any(func(t): return t.type == expected_close_token): + return DMConstants.ERR_UNEXPECTED_ASSIGNMENT + + # Special case for a negative number after this one + if token.type == DMConstants.TOKEN_NUMBER and next_token.type == DMConstants.TOKEN_NUMBER and next_token.value.begins_with("-"): + return OK + + var expected_token_types = [] + var unexpected_token_types = [] + match token.type: + DMConstants.TOKEN_FUNCTION, \ + DMConstants.TOKEN_PARENS_OPEN: + unexpected_token_types = [ + null, + DMConstants.TOKEN_COMMA, + DMConstants.TOKEN_COLON, + DMConstants.TOKEN_COMPARISON, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_OPERATOR, + DMConstants.TOKEN_AND_OR, + DMConstants.TOKEN_DOT + ] + + DMConstants.TOKEN_BRACKET_CLOSE: + unexpected_token_types = [ + DMConstants.TOKEN_NOT, + DMConstants.TOKEN_BOOL, + DMConstants.TOKEN_STRING, + DMConstants.TOKEN_NUMBER, + DMConstants.TOKEN_VARIABLE + ] + + DMConstants.TOKEN_BRACE_OPEN: + expected_token_types = [ + DMConstants.TOKEN_STRING, + DMConstants.TOKEN_VARIABLE, + DMConstants.TOKEN_NUMBER, + DMConstants.TOKEN_BRACE_CLOSE + ] + + DMConstants.TOKEN_PARENS_CLOSE, \ + DMConstants.TOKEN_BRACE_CLOSE: + unexpected_token_types = [ + DMConstants.TOKEN_NOT, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_BOOL, + DMConstants.TOKEN_STRING, + DMConstants.TOKEN_NUMBER, + DMConstants.TOKEN_VARIABLE + ] + + DMConstants.TOKEN_COMPARISON, \ + DMConstants.TOKEN_OPERATOR, \ + DMConstants.TOKEN_DOT, \ + DMConstants.TOKEN_NULL_COALESCE, \ + DMConstants.TOKEN_NOT, \ + DMConstants.TOKEN_AND_OR, \ + DMConstants.TOKEN_DICTIONARY_REFERENCE: + unexpected_token_types = [ + null, + DMConstants.TOKEN_COMMA, + DMConstants.TOKEN_COLON, + DMConstants.TOKEN_COMPARISON, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_OPERATOR, + DMConstants.TOKEN_AND_OR, + DMConstants.TOKEN_PARENS_CLOSE, + DMConstants.TOKEN_BRACE_CLOSE, + DMConstants.TOKEN_BRACKET_CLOSE, + DMConstants.TOKEN_DOT + ] + + DMConstants.TOKEN_COMMA: + unexpected_token_types = [ + null, + DMConstants.TOKEN_COMMA, + DMConstants.TOKEN_COLON, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_OPERATOR, + DMConstants.TOKEN_AND_OR, + DMConstants.TOKEN_PARENS_CLOSE, + DMConstants.TOKEN_BRACE_CLOSE, + DMConstants.TOKEN_BRACKET_CLOSE, + DMConstants.TOKEN_DOT + ] + + DMConstants.TOKEN_COLON: + unexpected_token_types = [ + DMConstants.TOKEN_COMMA, + DMConstants.TOKEN_COLON, + DMConstants.TOKEN_COMPARISON, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_OPERATOR, + DMConstants.TOKEN_AND_OR, + DMConstants.TOKEN_PARENS_CLOSE, + DMConstants.TOKEN_BRACE_CLOSE, + DMConstants.TOKEN_BRACKET_CLOSE, + DMConstants.TOKEN_DOT + ] + + DMConstants.TOKEN_BOOL, \ + DMConstants.TOKEN_STRING, \ + DMConstants.TOKEN_NUMBER: + unexpected_token_types = [ + DMConstants.TOKEN_NOT, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_BOOL, + DMConstants.TOKEN_STRING, + DMConstants.TOKEN_NUMBER, + DMConstants.TOKEN_VARIABLE, + DMConstants.TOKEN_FUNCTION, + DMConstants.TOKEN_PARENS_OPEN, + DMConstants.TOKEN_BRACE_OPEN, + DMConstants.TOKEN_BRACKET_OPEN + ] + + DMConstants.TOKEN_VARIABLE: + unexpected_token_types = [ + DMConstants.TOKEN_NOT, + DMConstants.TOKEN_BOOL, + DMConstants.TOKEN_STRING, + DMConstants.TOKEN_NUMBER, + DMConstants.TOKEN_VARIABLE, + DMConstants.TOKEN_FUNCTION, + DMConstants.TOKEN_PARENS_OPEN, + DMConstants.TOKEN_BRACE_OPEN, + DMConstants.TOKEN_BRACKET_OPEN + ] + + if (expected_token_types.size() > 0 and not next_token.type in expected_token_types) \ + or (unexpected_token_types.size() > 0 and next_token.type in unexpected_token_types): + match next_token.type: + null: + return DMConstants.ERR_UNEXPECTED_END_OF_EXPRESSION + + DMConstants.TOKEN_FUNCTION: + return DMConstants.ERR_UNEXPECTED_FUNCTION + + DMConstants.TOKEN_PARENS_OPEN, \ + DMConstants.TOKEN_PARENS_CLOSE: + return DMConstants.ERR_UNEXPECTED_BRACKET + + DMConstants.TOKEN_COMPARISON, \ + DMConstants.TOKEN_ASSIGNMENT, \ + DMConstants.TOKEN_OPERATOR, \ + DMConstants.TOKEN_NOT, \ + DMConstants.TOKEN_AND_OR: + return DMConstants.ERR_UNEXPECTED_OPERATOR + + DMConstants.TOKEN_COMMA: + return DMConstants.ERR_UNEXPECTED_COMMA + DMConstants.TOKEN_COLON: + return DMConstants.ERR_UNEXPECTED_COLON + DMConstants.TOKEN_DOT: + return DMConstants.ERR_UNEXPECTED_DOT + + DMConstants.TOKEN_BOOL: + return DMConstants.ERR_UNEXPECTED_BOOLEAN + DMConstants.TOKEN_STRING: + return DMConstants.ERR_UNEXPECTED_STRING + DMConstants.TOKEN_NUMBER: + return DMConstants.ERR_UNEXPECTED_NUMBER + DMConstants.TOKEN_VARIABLE: + return DMConstants.ERR_UNEXPECTED_VARIABLE + + return DMConstants.ERR_INVALID_EXPRESSION + + return OK + + +# Convert a series of comma separated tokens to an [Array]. +func _tokens_to_list(tokens: Array[Dictionary]) -> Array[Array]: + var list: Array[Array] = [] + var current_item: Array[Dictionary] = [] + for token in tokens: + if token.type == DMConstants.TOKEN_COMMA: + list.append(current_item) + current_item = [] + else: + current_item.append(token) + + if current_item.size() > 0: + list.append(current_item) + + return list + + +# Convert a series of key/value tokens into a [Dictionary] +func _tokens_to_dictionary(tokens: Array[Dictionary]) -> Dictionary: + var dictionary = {} + for i in range(0, tokens.size()): + if tokens[i].type == DMConstants.TOKEN_COLON: + if tokens.size() == i + 2: + dictionary[tokens[i - 1]] = tokens[i + 1] + else: + dictionary[tokens[i - 1]] = { type = DMConstants.TOKEN_GROUP, value = tokens.slice(i + 1), i = tokens[0].i } + + return dictionary + + +# Work out what the next token is from a string. +func _find_match(input: String) -> Dictionary: + for key in regex.TOKEN_DEFINITIONS.keys(): + var regex = regex.TOKEN_DEFINITIONS.get(key) + var found = regex.search(input) + if found: + return { + type = key, + remaining_text = input.substr(found.strings[0].length()), + value = found.strings[0] + } + + return {} + + +#endregion diff --git a/addons/dialogue_manager/compiler/expression_parser.gd.uid b/addons/dialogue_manager/compiler/expression_parser.gd.uid new file mode 100644 index 0000000..0793701 --- /dev/null +++ b/addons/dialogue_manager/compiler/expression_parser.gd.uid @@ -0,0 +1 @@ +uid://dbi4hbar8ubwu diff --git a/addons/dialogue_manager/compiler/resolved_goto_data.gd b/addons/dialogue_manager/compiler/resolved_goto_data.gd new file mode 100644 index 0000000..16bca6f --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_goto_data.gd @@ -0,0 +1,68 @@ +## Data associated with a dialogue jump/goto line. +class_name DMResolvedGotoData extends RefCounted + + +## The title that was specified +var title: String = "" +## The target line's ID +var next_id: String = "" +## An expression to determine the target line at runtime. +var expression: Array[Dictionary] = [] +## The given line text with the jump syntax removed. +var text_without_goto: String = "" +## Whether this is a jump-and-return style jump. +var is_snippet: bool = false +## A parse error if there was one. +var error: int +## The index in the string where +var index: int = 0 + +# An instance of the compiler [RegEx] list. +var regex: DMCompilerRegEx = DMCompilerRegEx.new() + + +func _init(text: String, titles: Dictionary) -> void: + if not "=> " in text and not "=>< " in text: return + + if "=> " in text: + text_without_goto = text.substr(0, text.find("=> ")).strip_edges() + elif "=>< " in text: + is_snippet = true + text_without_goto = text.substr(0, text.find("=>< ")).strip_edges() + + var found: RegExMatch = regex.GOTO_REGEX.search(text) + if found == null: + return + + title = found.strings[found.names.goto].strip_edges() + index = found.get_start(0) + + if title == "": + error = DMConstants.ERR_UNKNOWN_TITLE + return + + # "=> END!" means end the conversation, ignoring any "=><" chains. + if title == "END!": + next_id = DMConstants.ID_END_CONVERSATION + + # "=> END" means end the current title (and go back to the previous one if there is one + # in the stack) + elif title == "END": + next_id = DMConstants.ID_END + + elif titles.has(title): + next_id = titles.get(title) + elif title.begins_with("{{"): + var expression_parser: DMExpressionParser = DMExpressionParser.new() + var title_expression: Array[Dictionary] = expression_parser.extract_replacements(title, 0) + if title_expression[0].has("error"): + error = title_expression[0].error + else: + expression = title_expression[0].expression + else: + next_id = title + error = DMConstants.ERR_UNKNOWN_TITLE + + +func _to_string() -> String: + return "%s =>%s %s (%s)" % [text_without_goto, "<" if is_snippet else "", title, next_id] diff --git a/addons/dialogue_manager/compiler/resolved_goto_data.gd.uid b/addons/dialogue_manager/compiler/resolved_goto_data.gd.uid new file mode 100644 index 0000000..cb05e08 --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_goto_data.gd.uid @@ -0,0 +1 @@ +uid://llhl5pt47eoq diff --git a/addons/dialogue_manager/compiler/resolved_line_data.gd b/addons/dialogue_manager/compiler/resolved_line_data.gd new file mode 100644 index 0000000..31e9b21 --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_line_data.gd @@ -0,0 +1,160 @@ +## Any data associated with inline dialogue BBCodes. +class_name DMResolvedLineData extends RefCounted + +## The line's text +var text: String = "" +## A map of speed changes against where they are found in the text. +var speeds: Dictionary = {} +## A list of any mutations to run and where they are found in the text. +var mutations: Array[Array] = [] +## A duration reference for the line. Represented as "auto" or a stringified number. +var time: String = "" + + +func _init(line: String) -> void: + text = line + speeds = {} + mutations = [] + time = "" + + var bbcodes: Array = [] + + # Remove any escaped brackets (ie. "\[") + var escaped_open_brackets: PackedInt32Array = [] + var escaped_close_brackets: PackedInt32Array = [] + for i in range(0, text.length() - 1): + if text.substr(i, 2) == "\\[": + text = text.substr(0, i) + "!" + text.substr(i + 2) + escaped_open_brackets.append(i) + elif text.substr(i, 2) == "\\]": + text = text.substr(0, i) + "!" + text.substr(i + 2) + escaped_close_brackets.append(i) + + # Extract all of the BB codes so that we know the actual text (we could do this easier with + # a RichTextLabel but then we'd need to await idle_frame which is annoying) + var bbcode_positions = find_bbcode_positions_in_string(text) + var accumulaive_length_offset = 0 + for position in bbcode_positions: + # Ignore our own markers + if position.code in ["wait", "speed", "/speed", "$>", "$>>", "do", "do!", "set", "next", "if", "else", "/if"]: + continue + + bbcodes.append({ + bbcode = position.bbcode, + start = position.start, + offset_start = position.start - accumulaive_length_offset + }) + accumulaive_length_offset += position.bbcode.length() + + for bb in bbcodes: + text = text.substr(0, bb.offset_start) + text.substr(bb.offset_start + bb.bbcode.length()) + + # Now find any dialogue markers + var next_bbcode_position = find_bbcode_positions_in_string(text, false) + var limit = 0 + while next_bbcode_position.size() > 0 and limit < 1000: + limit += 1 + + var bbcode = next_bbcode_position[0] + var index = bbcode.start + var code = bbcode.code + var raw_args = bbcode.raw_args + var args = {} + if code in ["$>", "$>>", "do", "do!", "set"]: + args["value"] = DMCompiler.extract_mutation("%s %s" % [code, raw_args]) + else: + # Could be something like: + # "=1.0" + # " rate=20 level=10" + if raw_args and raw_args[0] == "=": + raw_args = "value" + raw_args + for pair in raw_args.strip_edges().split(" "): + if "=" in pair: + var bits = pair.split("=") + args[bits[0]] = bits[1] + + match code: + "wait": + var wait: Dictionary = DMCompiler.extract_mutation("do wait(%s)" % [args.get("value", "null")]) + mutations.append([index, wait]) + "speed": + speeds[index] = args.get("value").to_float() + "/speed": + speeds[index] = 1.0 + "$>", "$>>", "do", "do!", "set": + mutations.append([index, args.get("value")]) + "next": + time = args.get("value") if args.has("value") else "0" + + # Find any BB codes that are after this index and remove the length from their start + var length = bbcode.bbcode.length() + for bb in bbcodes: + if bb.offset_start > bbcode.start: + bb.offset_start -= length + bb.start -= length + + # Find any escaped brackets after this that need moving + for i in range(0, escaped_open_brackets.size()): + if escaped_open_brackets[i] > bbcode.start: + escaped_open_brackets[i] -= length + for i in range(0, escaped_close_brackets.size()): + if escaped_close_brackets[i] > bbcode.start: + escaped_close_brackets[i] -= length + + text = text.substr(0, index) + text.substr(index + length) + next_bbcode_position = find_bbcode_positions_in_string(text, false) + + # Put the BB Codes back in + for bb in bbcodes: + text = text.insert(bb.start, bb.bbcode) + + # Put the escaped brackets back in + for index in escaped_open_brackets: + text = text.left(index) + "[" + text.right(text.length() - index - 1) + for index in escaped_close_brackets: + text = text.left(index) + "]" + text.right(text.length() - index - 1) + + +func find_bbcode_positions_in_string(string: String, find_all: bool = true, include_conditions: bool = false) -> Array[Dictionary]: + if not "[" in string: return [] + + var positions: Array[Dictionary] = [] + + var open_brace_count: int = 0 + var start: int = 0 + var bbcode: String = "" + var code: String = "" + var is_finished_code: bool = false + for i in range(0, string.length()): + if string[i] == "[": + if open_brace_count == 0: + start = i + bbcode = "" + code = "" + is_finished_code = false + open_brace_count += 1 + + else: + if not is_finished_code and (string[i].to_upper() != string[i] or ["/", "!", "$", ">"].has(string[i])): + code += string[i] + else: + is_finished_code = true + + if open_brace_count > 0: + bbcode += string[i] + + if string[i] == "]": + open_brace_count -= 1 + if open_brace_count == 0 and (include_conditions or not code in ["if", "else", "/if"]): + positions.append({ + bbcode = bbcode, + code = code, + start = start, + end = i, + raw_args = bbcode.substr(code.length() + 1, bbcode.length() - code.length() - 2).strip_edges() + }) + + if not find_all: + return positions + + return positions diff --git a/addons/dialogue_manager/compiler/resolved_line_data.gd.uid b/addons/dialogue_manager/compiler/resolved_line_data.gd.uid new file mode 100644 index 0000000..bbea7d2 --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_line_data.gd.uid @@ -0,0 +1 @@ +uid://0k6q8kukq0qa diff --git a/addons/dialogue_manager/compiler/resolved_tag_data.gd b/addons/dialogue_manager/compiler/resolved_tag_data.gd new file mode 100644 index 0000000..e926ada --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_tag_data.gd @@ -0,0 +1,26 @@ +## Tag data associated with a line of dialogue. +class_name DMResolvedTagData extends RefCounted + + +## The list of tags. +var tags: PackedStringArray = [] +## The line with any tag syntax removed. +var text_without_tags: String = "" + +# An instance of the compiler [RegEx]. +var regex: DMCompilerRegEx = DMCompilerRegEx.new() + + +func _init(text: String) -> void: + var resolved_tags: PackedStringArray = [] + var tag_matches: Array[RegExMatch] = regex.TAGS_REGEX.search_all(text) + for tag_match in tag_matches: + text = text.replace(tag_match.get_string(), "") + var tags = tag_match.get_string().replace("[#", "").replace("]", "").replace(", ", ",").split(",") + for tag in tags: + tag = tag.replace("#", "") + if not tag in resolved_tags: + resolved_tags.append(tag) + + tags = resolved_tags + text_without_tags = text diff --git a/addons/dialogue_manager/compiler/resolved_tag_data.gd.uid b/addons/dialogue_manager/compiler/resolved_tag_data.gd.uid new file mode 100644 index 0000000..98c6f51 --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_tag_data.gd.uid @@ -0,0 +1 @@ +uid://cqai3ikuilqfq diff --git a/addons/dialogue_manager/compiler/tree_line.gd b/addons/dialogue_manager/compiler/tree_line.gd new file mode 100644 index 0000000..667daad --- /dev/null +++ b/addons/dialogue_manager/compiler/tree_line.gd @@ -0,0 +1,46 @@ +## An intermediate representation of a dialogue line before it gets compiled. +class_name DMTreeLine extends RefCounted + + +## The line number where this dialogue was found (after imported files have had their content imported). +var line_number: int = 0 +## The parent [DMTreeLine] of this line. +## This is stored as a Weak Reference so that this RefCounted can elegantly free itself. +## Without it being a Weak Reference, this can easily cause a cyclical reference that keeps this resource alive. +var parent: WeakRef +## The ID of this line. +var id: String +## The type of this line (as a [String] defined in [DMConstants]. +var type: String = "" +## Is this line part of a randomised group? +var is_random: bool = false +## The indent count for this line. +var indent: int = 0 +## The text of this line. +var text: String = "" +## The child [DMTreeLine]s of this line. +var children: Array[DMTreeLine] = [] +## Any doc comments attached to this line. +var notes: String = "" +## Is this a dialogue line that is the child of another dialogue line? +var is_nested_dialogue: bool = false + + +func _init(initial_id: String) -> void: + id = initial_id + + +func _to_string() -> String: + var tabs = [] + tabs.resize(indent) + tabs.fill("\t") + tabs = "".join(tabs) + + return tabs.join([tabs + "{\n", + "\tid: %s\n" % [id], + "\ttype: %s\n" % [type], + "\tis_random: %s\n" % ["true" if is_random else "false"], + "\ttext: %s\n" % [text], + "\tnotes: %s\n" % [notes], + "\tchildren: []\n" if children.size() == 0 else "\tchildren: [\n" + ",\n".join(children.map(func(child): return str(child))) + "]\n", + "}"]) diff --git a/addons/dialogue_manager/compiler/tree_line.gd.uid b/addons/dialogue_manager/compiler/tree_line.gd.uid new file mode 100644 index 0000000..fe1db3a --- /dev/null +++ b/addons/dialogue_manager/compiler/tree_line.gd.uid @@ -0,0 +1 @@ +uid://dsu4i84dpif14 diff --git a/addons/dialogue_manager/components/code_edit.gd b/addons/dialogue_manager/components/code_edit.gd index e94bd46..8d563ea 100644 --- a/addons/dialogue_manager/components/code_edit.gd +++ b/addons/dialogue_manager/components/code_edit.gd @@ -1,5 +1,5 @@ @tool -extends CodeEdit +class_name DMCodeEdit extends CodeEdit signal active_title_change(title: String) @@ -7,10 +7,6 @@ signal error_clicked(line_number: int) signal external_file_requested(path: String, title: String) -const DialogueManagerParser = preload("./parser.gd") -const DialogueSyntaxHighlighter = preload("./code_edit_syntax_highlighter.gd") - - # A link back to the owner `MainView` var main_view @@ -19,7 +15,7 @@ var theme_overrides: Dictionary: set(value): theme_overrides = value - syntax_highlighter = DialogueSyntaxHighlighter.new() + syntax_highlighter = DMSyntaxHighlighter.new() # General UI add_theme_color_override("font_color", theme_overrides.text_color) @@ -57,6 +53,10 @@ var font_size: int: var WEIGHTED_RANDOM_PREFIX: RegEx = RegEx.create_from_string("^\\%[\\d.]+\\s") +var compiler_regex: DMCompilerRegEx = DMCompilerRegEx.new() +var _autoloads: Dictionary[String, String] = {} +var _autoload_member_cache: Dictionary[String, Dictionary] = {} + func _ready() -> void: # Add error gutter @@ -67,7 +67,11 @@ func _ready() -> void: if not has_comment_delimiter("#"): add_comment_delimiter("#", "", true) - syntax_highlighter = DialogueSyntaxHighlighter.new() + syntax_highlighter = DMSyntaxHighlighter.new() + + # Keep track of any autoloads + ProjectSettings.settings_changed.connect(_on_project_settings_changed) + _on_project_settings_changed() func _gui_input(event: InputEvent) -> void: @@ -96,6 +100,12 @@ func _gui_input(event: InputEvent) -> void: "text_size_reset": self.font_size = theme_overrides.font_size get_viewport().set_input_as_handled() + "make_bold": + insert_bbcode("[b]", "[/b]") + get_viewport().set_input_as_handled() + "make_italic": + insert_bbcode("[i]", "[/i]") + get_viewport().set_input_as_handled() elif event is InputEventMouse: match event.as_text(): @@ -137,10 +147,20 @@ func _drop_data(at_position: Vector2, data) -> void: if cursor.x > -1 and cursor.y > -1: set_cursor(cursor) remove_secondary_carets() - if has_method("insert_text"): - call("insert_text", "\"%s\"" % file, cursor.y, cursor.x) + var resource = load(file) + # If the dropped file is an audio stream then assume it's a voice reference + if is_instance_of(resource, AudioStream): + var current_voice_regex: RegEx = RegEx.create_from_string("\\[#voice=.+\\]") + var path: String = ResourceUID.call("path_to_uid", file) if ResourceUID.has_method("path_to_uid") else file + var line_text: String = get_line(cursor.y) + var voice_text: String = "[#voice=%s]" % [path] + if current_voice_regex.search(line_text): + set_line(cursor.y, current_voice_regex.replace(get_line(cursor.y), voice_text)) + else: + insert_text(" " + voice_text, cursor.y, line_text.length()) + # Other wise it's just a file reference else: - call("insert_text_at_cursor", "\"%s\"" % file) + insert_text("\"%s\"" % file, cursor.y, cursor.x) grab_focus() @@ -148,6 +168,7 @@ func _request_code_completion(force: bool) -> void: var cursor: Vector2 = get_cursor() var current_line: String = get_line(cursor.y) + # Match jumps if ("=> " in current_line or "=>< " in current_line) and (cursor.x > current_line.find("=>")): var prompt: String = current_line.split("=>")[1] if prompt.begins_with("< "): @@ -162,19 +183,19 @@ func _request_code_completion(force: bool) -> void: add_code_completion_option(CodeEdit.KIND_CLASS, "END!", "END!".substr(prompt.length()), theme_overrides.text_color, get_theme_icon("Stop", "EditorIcons")) # Get all titles, including those in imports - var parser: DialogueManagerParser = DialogueManagerParser.new() - parser.prepare(text, main_view.current_file_path, false) - for title in parser.titles: - if "/" in title: + for title: String in DMCompiler.get_titles_in_text(text, main_view.current_file_path): + # Ignore any imported titles that aren't resolved to human readable. + if title.to_int() > 0: + continue + + elif "/" in title: var bits = title.split("/") if matches_prompt(prompt, bits[0]) or matches_prompt(prompt, bits[1]): add_code_completion_option(CodeEdit.KIND_CLASS, title, title.substr(prompt.length()), theme_overrides.text_color, get_theme_icon("CombineLines", "EditorIcons")) elif matches_prompt(prompt, title): add_code_completion_option(CodeEdit.KIND_CLASS, title, title.substr(prompt.length()), theme_overrides.text_color, get_theme_icon("ArrowRight", "EditorIcons")) - update_code_completion_options(true) - parser.free() - return + # Match character names var name_so_far: String = WEIGHTED_RANDOM_PREFIX.sub(current_line.strip_edges(), "") if name_so_far != "" and name_so_far[0].to_upper() == name_so_far[0]: # Only show names starting with that character @@ -182,9 +203,72 @@ func _request_code_completion(force: bool) -> void: if names.size() > 0: for name in names: add_code_completion_option(CodeEdit.KIND_CLASS, name + ": ", name.substr(name_so_far.length()) + ": ", theme_overrides.text_color, get_theme_icon("Sprite2D", "EditorIcons")) - update_code_completion_options(true) - else: - cancel_code_completion() + + # Match autoloads on mutation lines + for prefix in ["$>", "$>>", "do ", "do! ", "set ", "if ", "elif ", "else if ", "match ", "when ", "using "]: + if (current_line.strip_edges().begins_with(prefix) and (cursor.x > current_line.find(prefix))): + var expression: String = current_line.substr(0, cursor.x).strip_edges().substr(3) + # Find the last couple of tokens + var possible_prompt: String = expression.reverse() + possible_prompt = possible_prompt.substr(0, possible_prompt.find(" ")) + possible_prompt = possible_prompt.substr(0, possible_prompt.find("(")) + possible_prompt = possible_prompt.reverse() + var segments: PackedStringArray = possible_prompt.split(".").slice(-2) + var auto_completes: Array[Dictionary] = [] + + # Autoloads and state shortcuts + if segments.size() == 1: + var prompt: String = segments[0] + for autoload in _autoloads.keys(): + if matches_prompt(prompt, autoload): + auto_completes.append({ + prompt = prompt, + text = autoload, + type = "script" + }) + for autoload in get_state_shortcuts(): + for member: Dictionary in get_members_for_autoload(autoload): + if matches_prompt(prompt, member.name): + auto_completes.append({ + prompt = prompt, + text = member.name, + type = member.type + }) + + # Members of an autoload + elif segments[0] in _autoloads.keys() and not current_line.strip_edges().begins_with("using "): + var prompt: String = segments[1] + for member: Dictionary in get_members_for_autoload(segments[0]): + if matches_prompt(prompt, member.name): + auto_completes.append({ + prompt = prompt, + text = member.name, + type = member.type + }) + + auto_completes.sort_custom(func(a, b): return a.text < b.text) + + for auto_complete in auto_completes: + var icon: Texture2D + var text: String = auto_complete.text + match auto_complete.type: + "script": + icon = get_theme_icon("Script", "EditorIcons") + "property": + icon = get_theme_icon("MemberProperty", "EditorIcons") + "method": + icon = get_theme_icon("MemberMethod", "EditorIcons") + text += "()" + "signal": + icon = get_theme_icon("MemberSignal", "EditorIcons") + "constant": + icon = get_theme_icon("MemberConstant", "EditorIcons") + var insert: String = text.substr(auto_complete.prompt.length()) + add_code_completion_option(CodeEdit.KIND_CLASS, text, insert, theme_overrides.text_color, icon) + + update_code_completion_options(true) + if get_code_completion_options().size() == 0: + cancel_code_completion() func _filter_code_completion_candidates(candidates: Array) -> Array: @@ -196,17 +280,21 @@ func _confirm_code_completion(replace: bool) -> void: var completion = get_code_completion_option(get_code_completion_selected_index()) begin_complex_operation() # Delete any part of the text that we've already typed - for i in range(0, completion.display_text.length() - completion.insert_text.length()): - backspace() + if completion.insert_text.length() > 0: + for i in range(0, completion.display_text.length() - completion.insert_text.length()): + backspace() # Insert the whole match insert_text_at_caret(completion.display_text) end_complex_operation() + if completion.display_text.ends_with("()"): + set_cursor(get_cursor() - Vector2.RIGHT) + # Close the autocomplete menu on the next tick call_deferred("cancel_code_completion") -### Helpers +#region Helpers # Get the current caret as a Vector2 @@ -225,6 +313,69 @@ func matches_prompt(prompt: String, matcher: String) -> bool: return prompt.length() < matcher.length() and matcher.to_lower().begins_with(prompt.to_lower()) +func get_state_shortcuts() -> PackedStringArray: + # Get any shortcuts defined in settings + var shortcuts: PackedStringArray = DMSettings.get_setting(DMSettings.STATE_AUTOLOAD_SHORTCUTS, []) + # Check for "using" clauses + for line: String in text.split("\n"): + var found: RegExMatch = compiler_regex.USING_REGEX.search(line) + if found: + shortcuts.append(found.strings[found.names.state]) + # Check for any other script sources + for extra_script_source in DMSettings.get_setting(DMSettings.EXTRA_AUTO_COMPLETE_SCRIPT_SOURCES, []): + shortcuts.append(extra_script_source) + + return shortcuts + + +func get_members_for_autoload(autoload_name: String) -> Array[Dictionary]: + # Debounce method list lookups + if _autoload_member_cache.has(autoload_name) and _autoload_member_cache.get(autoload_name).get("at") > Time.get_ticks_msec() - 10000: + return _autoload_member_cache.get(autoload_name).get("members") + + if not _autoloads.has(autoload_name) and not autoload_name.begins_with("res://") and not autoload_name.begins_with("uid://"): return [] + + var autoload = load(_autoloads.get(autoload_name, autoload_name)) + var script: Script = autoload if autoload is Script else autoload.get_script() + + if not is_instance_valid(script): return [] + + var members: Array[Dictionary] = [] + if script.resource_path.ends_with(".gd"): + for m: Dictionary in script.get_script_method_list(): + if not m.name.begins_with("@"): + members.append({ + name = m.name, + type = "method" + }) + for m: Dictionary in script.get_script_property_list(): + members.append({ + name = m.name, + type = "property" + }) + for m: Dictionary in script.get_script_signal_list(): + members.append({ + name = m.name, + type = "signal" + }) + for c: String in script.get_script_constant_map(): + members.append({ + name = c, + type = "constant" + }) + elif script.resource_path.ends_with(".cs"): + var dotnet = load(Engine.get_meta("DialogueManagerPlugin").get_plugin_path() + "/DialogueManager.cs").new() + for m: Dictionary in dotnet.GetMembersForAutoload(script): + members.append(m) + + _autoload_member_cache[autoload_name] = { + at = Time.get_ticks_msec(), + members = members + } + + return members + + ## Get a list of titles from the current text func get_titles() -> PackedStringArray: var titles = PackedStringArray([]) @@ -232,6 +383,7 @@ func get_titles() -> PackedStringArray: for line in lines: if line.strip_edges().begins_with("~ "): titles.append(line.strip_edges().substr(2)) + return titles @@ -270,6 +422,11 @@ func get_character_names(beginning_with: String) -> PackedStringArray: # Mark a line as an error or not func mark_line_as_error(line_number: int, is_error: bool) -> void: + # Lines display counting from 1 but are actually indexed from 0 + line_number -= 1 + + if line_number < 0: return + if is_error: set_line_background_color(line_number, theme_overrides.error_line_color) set_line_gutter_icon(line_number, 0, get_theme_icon("StatusError", "EditorIcons")) @@ -417,7 +574,18 @@ func move_line(offset: int) -> void: scroll_vertical = starting_scroll + offset -### Signals +#endregion + +#region Signals + + +func _on_project_settings_changed() -> void: + _autoloads = {} + var project = ConfigFile.new() + project.load("res://project.godot") + for autoload in project.get_section_keys("autoload"): + if autoload != "DialogueManager": + _autoloads[autoload] = project.get_value("autoload", autoload).substr(1) func _on_code_edit_symbol_validate(symbol: String) -> void: @@ -456,3 +624,6 @@ func _on_code_edit_gutter_clicked(line: int, gutter: int) -> void: var line_errors = errors.filter(func(error): return error.line_number == line) if line_errors.size() > 0: error_clicked.emit(line) + + +#endregion diff --git a/addons/dialogue_manager/components/code_edit.gd.uid b/addons/dialogue_manager/components/code_edit.gd.uid new file mode 100644 index 0000000..ab2b9e5 --- /dev/null +++ b/addons/dialogue_manager/components/code_edit.gd.uid @@ -0,0 +1 @@ +uid://djeybvlb332mp diff --git a/addons/dialogue_manager/components/code_edit.tscn b/addons/dialogue_manager/components/code_edit.tscn index a974ea3..0c25707 100644 --- a/addons/dialogue_manager/components/code_edit.tscn +++ b/addons/dialogue_manager/components/code_edit.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=4 format=3 uid="uid://civ6shmka5e8u"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="1_58cfo"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/code_edit.gd" id="1_g324i"] +[ext_resource type="Script" uid="uid://klpiq4tk3t7a" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="1_58cfo"] +[ext_resource type="Script" uid="uid://djeybvlb332mp" path="res://addons/dialogue_manager/components/code_edit.gd" id="1_g324i"] [sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_cobxx"] script = ExtResource("1_58cfo") diff --git a/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd b/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd index 7e4a98f..5c4e097 100644 --- a/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd +++ b/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd @@ -1,56 +1,21 @@ @tool -extends SyntaxHighlighter +class_name DMSyntaxHighlighter extends SyntaxHighlighter -const DialogueManagerParser = preload("./parser.gd") - - -enum ExpressionType {DO, SET, IF} - - -var dialogue_manager_parser: DialogueManagerParser = DialogueManagerParser.new() - -var regex_titles: RegEx = RegEx.create_from_string("^\\s*(?~\\s+[^\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)\\-\\=\\+\\{\\}\\[\\]\\;\\:\\\"\\'\\,\\.\\<\\>\\?\\/\\s]+)") -var regex_comments: RegEx = RegEx.create_from_string("(?:(?>\"(?:\\\\\"|[^\"\\n])*\")[^\"\\n]*?\\s*(?<comment>#[^\\n]*)$|^[^\"#\\n]*?\\s*(?<comment2>#[^\\n]*))") -var regex_mutation: RegEx = RegEx.create_from_string("^\\s*(do|do!|set) (?<mutation>.*)") -var regex_condition: RegEx = RegEx.create_from_string("^\\s*(if|elif|while|else if) (?<condition>.*)") -var regex_wcondition: RegEx = RegEx.create_from_string("\\[if (?<condition>((?:[^\\[\\]]*)|(?:\\[(?1)\\]))*?)\\]") -var regex_wendif: RegEx = RegEx.create_from_string("\\[(\\/if|else)\\]") -var regex_rgroup: RegEx = RegEx.create_from_string("\\[\\[(?<options>.*?)\\]\\]") -var regex_endconditions: RegEx = RegEx.create_from_string("^\\s*(endif|else):?\\s*$") -var regex_tags: RegEx = RegEx.create_from_string("\\[(?<tag>(?!(?:ID:.*)|if)[a-zA-Z_][a-zA-Z0-9_]*!?)(?:[= ](?<val>[^\\[\\]]+))?\\](?:(?<text>(?!\\[\\/\\k<tag>\\]).*?)?(?<end>\\[\\/\\k<tag>\\]))?") -var regex_dialogue: RegEx = RegEx.create_from_string("^\\s*(?:(?<random>\\%[\\d.]* )|(?<response>- ))?(?:(?<character>[^#:]*): )?(?<dialogue>.*)$") -var regex_goto: RegEx = RegEx.create_from_string("=><? (?:(?<file>[^\\/]+)\\/)?(?<title>[^\\/]*)") -var regex_string: RegEx = RegEx.create_from_string("^&?(?<delimiter>[\"'])(?<content>(?:\\\\{2})*|(?:.*?[^\\\\](?:\\\\{2})*))\\1$") -var regex_escape: RegEx = RegEx.create_from_string("\\\\.") -var regex_number: RegEx = RegEx.create_from_string("^-?(?:(?:0x(?:[0-9A-Fa-f]{2})+)|(?:0b[01]+)|(?:\\d+(?:(?:[\\.]\\d*)?(?:e\\d+)?)|(?:_\\d+)+)?)$") -var regex_array: RegEx = RegEx.create_from_string("\\[((?>[^\\[\\]]+|(?R))*)\\]") -var regex_dict: RegEx = RegEx.create_from_string("^\\{((?>[^\\{\\}]+|(?R))*)\\}$") -var regex_kvdict: RegEx = RegEx.create_from_string("^\\s*(?<left>.*?)\\s*(?<colon>:|=)\\s*(?<right>[^\\/]+)$") -var regex_commas: RegEx = RegEx.create_from_string("([^,]+)(?:\\s*,\\s*)?") -var regex_assignment: RegEx = RegEx.create_from_string("^\\s*(?<var>[a-zA-Z_][a-zA-Z_0-9]*)(?:(?<attr>(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)+)|(?:\\[(?<key>[^\\]]+)\\]))?\\s*(?<op>(?:\\/|\\*|-|\\+)?=)\\s*(?<val>.*)$") -var regex_varname: RegEx = RegEx.create_from_string("^\\s*(?!true|false|and|or|&&|\\|\\|not|in|null)(?<var>[a-zA-Z_][a-zA-Z_0-9]*)(?:(?<attr>(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)+)|(?:\\[(?<key>[^\\]]+)\\]))?\\s*$") -var regex_keyword: RegEx = RegEx.create_from_string("^\\s*(true|false|null)\\s*$") -var regex_function: RegEx = RegEx.create_from_string("^\\s*([a-zA-Z_][a-zA-Z_0-9]*\\s*)\\(") -var regex_comparison: RegEx = RegEx.create_from_string("^(?<left>.*?)\\s*(?<op>==|>=|<=|<|>|!=)\\s*(?<right>.*)$") -var regex_blogical: RegEx = RegEx.create_from_string("^(?<left>.*?)\\s+(?<op>and|or|in|&&|\\|\\|)\\s+(?<right>.*)$") -var regex_ulogical: RegEx = RegEx.create_from_string("^\\s*(?<op>not)\\s+(?<right>.*)$") -var regex_paren: RegEx = RegEx.create_from_string("\\((?<paren>((?:[^\\(\\)]*)|(?:\\((?1)\\)))*?)\\)") +var regex: DMCompilerRegEx = DMCompilerRegEx.new() +var compilation: DMCompilation = DMCompilation.new() +var expression_parser = DMExpressionParser.new() var cache: Dictionary = {} -func _notification(what: int) -> void: - if what == NOTIFICATION_PREDELETE: - dialogue_manager_parser.free() - - func _clear_highlighting_cache() -> void: - cache = {} + cache.clear() -## Returns the syntax coloring for a dialogue file line func _get_line_syntax_highlighting(line: int) -> Dictionary: + expression_parser.include_comments = true + var colors: Dictionary = {} var text_edit: TextEdit = get_text_edit() var text: String = text_edit.get_line(line) @@ -63,323 +28,210 @@ func _get_line_syntax_highlighting(line: int) -> Dictionary: if text in cache: return cache[text] - # Comments have to be removed to make the remaining processing easier. - # Count both end-of-line and single-line comments - # Comments are not allowed within dialogue lines or response lines, so we ask the parser what it thinks the current line is - if not (dialogue_manager_parser.is_dialogue_line(text) or dialogue_manager_parser.is_response_line(text)) or dialogue_manager_parser.is_line_empty(text) or dialogue_manager_parser.is_import_line(text): - var comment_matches: Array[RegExMatch] = regex_comments.search_all(text) - for comment_match in comment_matches: - for i in ["comment", "comment2"]: - if i in comment_match.names: - colors[comment_match.get_start(i)] = {"color": text_edit.theme_overrides.comments_color} - text = text.substr(0, comment_match.get_start(i)) + var theme: Dictionary = text_edit.theme_overrides - # Dialogues - var dialogue_matches: Array[RegExMatch] = regex_dialogue.search_all(text) - for dialogue_match in dialogue_matches: - if "random" in dialogue_match.names: - colors[dialogue_match.get_start("random")] = {"color": text_edit.theme_overrides.symbols_color} - colors[dialogue_match.get_end("random")] = {"color": text_edit.theme_overrides.text_color} - if "response" in dialogue_match.names: - colors[dialogue_match.get_start("response")] = {"color": text_edit.theme_overrides.symbols_color} - colors[dialogue_match.get_end("response")] = {"color": text_edit.theme_overrides.text_color} - if "character" in dialogue_match.names: - colors[dialogue_match.get_start("character")] = {"color": text_edit.theme_overrides.members_color} - colors[dialogue_match.get_end("character")] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_dialogue_syntax_highlighting(dialogue_match.get_start("dialogue"), dialogue_match.get_string("dialogue")), true) + var index: int = 0 - # Title lines - if dialogue_manager_parser.is_title_line(text): - var title_matches: Array[RegExMatch] = regex_titles.search_all(text) - for title_match in title_matches: - colors[title_match.get_start("title")] = {"color": text_edit.theme_overrides.titles_color} + match DMCompiler.get_line_type(text): + DMConstants.TYPE_USING: + colors[index] = { color = theme.conditions_color } + colors[index + "using ".length()] = { color = theme.text_color } - # Import lines - var import_matches: Array[RegExMatch] = dialogue_manager_parser.IMPORT_REGEX.search_all(text) - for import_match in import_matches: - colors[import_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color} - colors[import_match.get_start("path") - 1] = {"color": text_edit.theme_overrides.strings_color} - colors[import_match.get_end("path") + 1] = {"color": text_edit.theme_overrides.conditions_color} - colors[import_match.get_start("prefix")] = {"color": text_edit.theme_overrides.members_color} - colors[import_match.get_end("prefix")] = {"color": text_edit.theme_overrides.conditions_color} + DMConstants.TYPE_IMPORT: + colors[index] = { color = theme.conditions_color } + var import: RegExMatch = regex.IMPORT_REGEX.search(text) + if import: + colors[index + import.get_start("path") - 1] = { color = theme.strings_color } + colors[index + import.get_end("path") + 1] = { color = theme.conditions_color } + colors[index + import.get_start("prefix")] = { color = theme.text_color } - # Using clauses - var using_matches: Array[RegExMatch] = dialogue_manager_parser.USING_REGEX.search_all(text) - for using_match in using_matches: - colors[using_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color} - colors[using_match.get_start("state") - 1] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TYPE_COMMENT: + colors[index] = { color = theme.comments_color } - # Condition keywords and expressions - var condition_matches: Array[RegExMatch] = regex_condition.search_all(text) - for condition_match in condition_matches: - colors[condition_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color} - colors[condition_match.get_end(1)] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_expression_syntax_highlighting(condition_match.get_start("condition"), ExpressionType.IF, condition_match.get_string("condition")), true) - # endif/else - var endcondition_matches: Array[RegExMatch] = regex_endconditions.search_all(text) - for endcondition_match in endcondition_matches: - colors[endcondition_match.get_start(1)] = {"color": text_edit.theme_overrides.conditions_color} - colors[endcondition_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} + DMConstants.TYPE_TITLE: + colors[index] = { color = theme.titles_color } - # Mutations - var mutation_matches: Array[RegExMatch] = regex_mutation.search_all(text) - for mutation_match in mutation_matches: - colors[mutation_match.get_start(0)] = {"color": text_edit.theme_overrides.mutations_color} - colors.merge(_get_expression_syntax_highlighting(mutation_match.get_start("mutation"), ExpressionType.DO if mutation_match.strings[1].begins_with("do") else ExpressionType.SET, mutation_match.get_string("mutation")), true) + DMConstants.TYPE_CONDITION, DMConstants.TYPE_WHILE, DMConstants.TYPE_MATCH, DMConstants.TYPE_WHEN: + colors[0] = { color = theme.conditions_color } + index = text.find(" ") + if index > -1: + var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_CONDITION, 0) + if expression.size() == 0: + colors[index] = { color = theme.critical_color } + else: + _highlight_expression(expression, colors, index) + + DMConstants.TYPE_MUTATION: + colors[0] = { color = theme.mutations_line_color } + index = text.find(" ") + var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_MUTATION, 0) + if expression.size() == 0: + colors[index] = { color = theme.critical_color } + else: + _highlight_expression(expression, colors, index) + + DMConstants.TYPE_GOTO: + if text.strip_edges().begins_with("%"): + colors[index] = { color = theme.symbols_color } + index = text.find(" ") + _highlight_goto(text, colors, index) + + DMConstants.TYPE_RANDOM: + colors[index] = { color = theme.symbols_color } + + DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE: + if text.strip_edges().begins_with("%"): + colors[index] = { color = theme.symbols_color } + index = text.find(" ", text.find("%")) + colors[index] = { color = theme.text_color.lerp(theme.symbols_color, 0.5) } + + var dialogue_text: String = text.substr(index, text.find("=>")) + + # Highlight character name (but ignore ":" within line ID reference) + var split_index: int = dialogue_text.replace("\\:", "??").find(":") + if text.substr(split_index - 3, 3) != "[ID": + colors[index + split_index + 1] = { color = theme.text_color } + else: + # If there's no character name then just highlight the text as dialogue. + colors[index] = { color = theme.text_color } + + # Interpolation + var replacements: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(dialogue_text) + for replacement: RegExMatch in replacements: + var expression_text: String = replacement.get_string().substr(0, replacement.get_string().length() - 2).substr(2) + var expression: Array = expression_parser.tokenise(expression_text, DMConstants.TYPE_MUTATION, replacement.get_start()) + var expression_index: int = index + replacement.get_start() + colors[expression_index] = { color = theme.symbols_color } + if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR: + colors[expression_index] = { color = theme.critical_color } + else: + _highlight_expression(expression, colors, index + 2) + colors[expression_index + expression_text.length() + 2] = { color = theme.symbols_color } + colors[expression_index + expression_text.length() + 4] = { color = theme.text_color } + # Tags (and inline mutations) + var resolved_line_data: DMResolvedLineData = DMResolvedLineData.new("") + var bbcodes: Array[Dictionary] = resolved_line_data.find_bbcode_positions_in_string(dialogue_text, true, true) + for bbcode: Dictionary in bbcodes: + var tag: String = bbcode.code + var code: String = bbcode.raw_args + if code.begins_with("["): + colors[index + bbcode.start] = { color = theme.symbols_color } + colors[index + bbcode.start + 2] = { color = theme.text_color } + var pipe_cursor: int = code.find("|") + while pipe_cursor > -1: + colors[index + bbcode.start + pipe_cursor + 1] = { color = theme.symbols_color } + colors[index + bbcode.start + pipe_cursor + 2] = { color = theme.text_color } + pipe_cursor = code.find("|", pipe_cursor + 1) + colors[index + bbcode.end - 1] = { color = theme.symbols_color } + colors[index + bbcode.end + 1] = { color = theme.text_color } + else: + colors[index + bbcode.start] = { color = theme.symbols_color } + if tag.begins_with("$>") or tag.begins_with("do") or tag.begins_with("set") or tag.begins_with("if"): + if tag.begins_with("if"): + colors[index + bbcode.start + 1] = { color = theme.conditions_color } + else: + colors[index + bbcode.start + 1] = { color = theme.mutations_line_color } + var expression: Array = expression_parser.tokenise(code, DMConstants.TYPE_MUTATION, bbcode.start + bbcode.code.length()) + if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR: + colors[index + bbcode.start + tag.length() + 1] = { color = theme.critical_color } + else: + _highlight_expression(expression, colors, index + 2) + # else and closing if have no expression + elif tag.begins_with("else") or tag.begins_with("/if"): + colors[index + bbcode.start + 1] = { color = theme.conditions_color } + colors[index + bbcode.end] = { color = theme.symbols_color } + colors[index + bbcode.end + 1] = { color = theme.text_color } + # Jumps + if "=> " in text or "=>< " in text: + _highlight_goto(text, colors, index) # Order the dictionary keys to prevent CodeEdit from having issues - var new_colors: Dictionary = {} + var ordered_colors: Dictionary = {} var ordered_keys: Array = colors.keys() ordered_keys.sort() - for index in ordered_keys: - new_colors[index] = colors[index] + for key_index: int in ordered_keys: + ordered_colors[key_index] = colors[key_index] - cache[text] = new_colors - return new_colors + cache[text] = ordered_colors + return ordered_colors -## Return the syntax highlighting for a dialogue line -func _get_dialogue_syntax_highlighting(start_index: int, text: String) -> Dictionary: - var text_edit: TextEdit = get_text_edit() - var colors: Dictionary = {} +func _highlight_expression(tokens: Array, colors: Dictionary, index: int) -> int: + var theme: Dictionary = get_text_edit().theme_overrides + var last_index: int = index + for token: Dictionary in tokens: + last_index = token.i + match token.type: + DMConstants.TOKEN_COMMENT: + colors[index + token.i] = { color = theme.comments_color } - # #tag style tags - var hashtag_matches: Array[RegExMatch] = dialogue_manager_parser.TAGS_REGEX.search_all(text) - for hashtag_match in hashtag_matches: - colors[start_index + hashtag_match.get_start(0)] = { "color": text_edit.theme_overrides.comments_color } - colors[start_index + hashtag_match.get_end(0)] = { "color": text_edit.theme_overrides.text_color } + DMConstants.TOKEN_CONDITION, DMConstants.TOKEN_AND_OR: + colors[index + token.i] = { color = theme.conditions_color } - # bbcode-like global tags - var tag_matches: Array[RegExMatch] = regex_tags.search_all(text) - for tag_match in tag_matches: - colors[start_index + tag_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - if "val" in tag_match.names: - colors.merge(_get_literal_syntax_highlighting(start_index + tag_match.get_start("val"), tag_match.get_string("val")), true) - colors[start_index + tag_match.get_end("val")] = {"color": text_edit.theme_overrides.symbols_color} - # Show the text color straight in the editor for better ease-of-use - if tag_match.get_string("tag") == "color": - colors[start_index + tag_match.get_start("val")] = {"color": Color.from_string(tag_match.get_string("val"), text_edit.theme_overrides.text_color)} - if "text" in tag_match.names: - colors[start_index + tag_match.get_start("text")] = {"color": text_edit.theme_overrides.text_color} - # Text can still contain tags if several effects are applied ([center][b]Something[/b][/center], so recursing - colors.merge(_get_dialogue_syntax_highlighting(start_index + tag_match.get_start("text"), tag_match.get_string("text")), true) - colors[start_index + tag_match.get_end("text")] = {"color": text_edit.theme_overrides.symbols_color} - if "end" in tag_match.names: - colors[start_index + tag_match.get_start("end")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + tag_match.get_end("end")] = {"color": text_edit.theme_overrides.text_color} - colors[start_index + tag_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_VARIABLE: + if token.value in ["true", "false"]: + colors[index + token.i] = { color = theme.conditions_color } + else: + colors[index + token.i] = { color = theme.members_color } - # ID tag - var translation_matches: Array[RegExMatch] = dialogue_manager_parser.TRANSLATION_REGEX.search_all(text) - for translation_match in translation_matches: - colors[start_index + translation_match.get_start(0)] = {"color": text_edit.theme_overrides.comments_color} - colors[start_index + translation_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_OPERATOR, DMConstants.TOKEN_COLON, \ + DMConstants.TOKEN_COMMA, DMConstants.TOKEN_DOT, DMConstants.TOKEN_NULL_COALESCE, \ + DMConstants.TOKEN_ASSIGNMENT, DMConstants.TOKEN_COMPARISON: + if token.get("value", null) == "in": + colors[index + token.i] = { color = theme.conditions_color } + else: + colors[index + token.i] = { color = theme.symbols_color } - # Replacements - var replacement_matches: Array[RegExMatch] = dialogue_manager_parser.REPLACEMENTS_REGEX.search_all(text) - for replacement_match in replacement_matches: - colors[start_index + replacement_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + replacement_match.get_start(1)] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + replacement_match.get_start(1), replacement_match.strings[1]), true) - colors[start_index + replacement_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + replacement_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_NUMBER: + colors[index + token.i] = { color = theme.numbers_color } - # Jump at the end of a response - var goto_matches: Array[RegExMatch] = regex_goto.search_all(text) - for goto_match in goto_matches: - colors[start_index + goto_match.get_start(0)] = {"color": text_edit.theme_overrides.jumps_color} - if "file" in goto_match.names: - colors[start_index + goto_match.get_start("file")] = {"color": text_edit.theme_overrides.jumps_color} - colors[start_index + goto_match.get_end("file")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + goto_match.get_start("title")] = {"color": text_edit.theme_overrides.jumps_color} - colors[start_index + goto_match.get_end("title")] = {"color": text_edit.theme_overrides.jumps_color} - colors[start_index + goto_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_STRING: + colors[index + token.i] = { color = theme.strings_color } - # Wrapped condition - var wcondition_matches: Array[RegExMatch] = regex_wcondition.search_all(text) - for wcondition_match in wcondition_matches: - colors[start_index + wcondition_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + wcondition_match.get_start(0) + 1] = {"color": text_edit.theme_overrides.conditions_color} - colors[start_index + wcondition_match.get_start(0) + 3] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + wcondition_match.get_start("condition"), wcondition_match.get_string("condition")), true) - colors[start_index + wcondition_match.get_end("condition")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + wcondition_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} - # [/if] tag for color matching with the opening tag - var wendif_matches: Array[RegExMatch] = regex_wendif.search_all(text) - for wendif_match in wendif_matches: - colors[start_index + wendif_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + wendif_match.get_start(1)] = {"color": text_edit.theme_overrides.conditions_color} - colors[start_index + wendif_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + wendif_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_FUNCTION: + colors[index + token.i] = { color = theme.mutations_color } + colors[index + token.i + token.function.length()] = { color = theme.symbols_color } + for parameter: Array in token.value: + last_index = _highlight_expression(parameter, colors, index) + DMConstants.TOKEN_PARENS_CLOSE: + colors[index + token.i] = { color = theme.symbols_color } - # Random groups - var rgroup_matches: Array[RegExMatch] = regex_rgroup.search_all(text) - for rgroup_match in rgroup_matches: - colors[start_index + rgroup_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + rgroup_match.get_start("options")] = {"color": text_edit.theme_overrides.text_color} - var separator_matches: Array[RegExMatch] = RegEx.create_from_string("\\|").search_all(rgroup_match.get_string("options")) - for separator_match in separator_matches: - colors[start_index + rgroup_match.get_start("options") + separator_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + rgroup_match.get_start("options") + separator_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} - colors[start_index + rgroup_match.get_end("options")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + rgroup_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_DICTIONARY_REFERENCE: + colors[index + token.i] = { color = theme.members_color } + colors[index + token.i + token.variable.length()] = { color = theme.symbols_color } + last_index = _highlight_expression(token.value, colors, index) + DMConstants.TOKEN_ARRAY: + colors[index + token.i] = { color = theme.symbols_color } + for item: Array in token.value: + last_index = _highlight_expression(item, colors, index) + DMConstants.TOKEN_BRACKET_CLOSE: + colors[index + token.i] = { color = theme.symbols_color } - return colors + DMConstants.TOKEN_DICTIONARY: + colors[index + token.i] = { color = theme.symbols_color } + last_index = _highlight_expression(token.value.keys() + token.value.values(), colors, index) + DMConstants.TOKEN_BRACE_CLOSE: + colors[index + token.i] = { color = theme.symbols_color } + last_index += 1 + + DMConstants.TOKEN_GROUP: + last_index = _highlight_expression(token.value, colors, index) + + return last_index -## Returns the syntax highlighting for an expression (mutation set/do, or condition) -func _get_expression_syntax_highlighting(start_index: int, type: ExpressionType, text: String) -> Dictionary: - var text_edit: TextEdit = get_text_edit() - var colors: Dictionary = {} +func _highlight_goto(text: String, colors: Dictionary, index: int) -> int: + var theme: Dictionary = get_text_edit().theme_overrides + var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, {}) + colors[goto_data.index] = { color = theme.jumps_color } + if "{{" in text: + index = text.find("{{", goto_data.index) + var last_index: int = 0 + if goto_data.error: + colors[index + 2] = { color = theme.critical_color } + else: + last_index = _highlight_expression(goto_data.expression, colors, index) + index = text.find("}}", index + last_index) + colors[index] = { color = theme.jumps_color } - if type == ExpressionType.SET: - var assignment_matches: Array[RegExMatch] = regex_assignment.search_all(text) - for assignment_match in assignment_matches: - colors[start_index + assignment_match.get_start("var")] = {"color": text_edit.theme_overrides.text_color} - if "attr" in assignment_match.names: - colors[start_index + assignment_match.get_start("attr")] = {"color": text_edit.theme_overrides.members_color} - colors[start_index + assignment_match.get_end("attr")] = {"color": text_edit.theme_overrides.text_color} - if "key" in assignment_match.names: - # Braces are outside of the key, so coloring them symbols_color - colors[start_index + assignment_match.get_start("key") - 1] = {"color": text_edit.theme_overrides.symbols_color} - colors.merge(_get_literal_syntax_highlighting(start_index + assignment_match.get_start("key"), assignment_match.get_string("key")), true) - colors[start_index + assignment_match.get_end("key")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + assignment_match.get_end("key") + 1] = {"color": text_edit.theme_overrides.text_color} - - colors[start_index + assignment_match.get_start("op")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + assignment_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + assignment_match.get_start("val"), assignment_match.get_string("val")), true) - else: - colors.merge(_get_literal_syntax_highlighting(start_index, text), true) - - return colors - - -## Return the syntax highlighting for a literal -## For this purpose, "literal" refers to a regular code line that could be used to get a value out of: -## - function calls -## - real literals (bool, string, int, float, etc.) -## - logical operators (>, <, >=, or, and, not, etc.) -func _get_literal_syntax_highlighting(start_index: int, text: String) -> Dictionary: - var text_edit: TextEdit = get_text_edit() - var colors: Dictionary = {} - - # Remove spaces at start/end of the literal - var text_length: int = text.length() - text = text.lstrip(" ") - start_index += text_length - text.length() - text = text.rstrip(" ") - - # Parenthesis expression. - var paren_matches: Array[RegExMatch] = regex_paren.search_all(text) - for paren_match in paren_matches: - colors[start_index + paren_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + paren_match.get_start(0) + 1] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + paren_match.get_start("paren"), paren_match.get_string("paren")), true) - colors[start_index + paren_match.get_end(0) - 1] = {"color": text_edit.theme_overrides.symbols_color} - - # Strings - var string_matches: Array[RegExMatch] = regex_string.search_all(text) - for string_match in string_matches: - colors[start_index + string_match.get_start(0)] = {"color": text_edit.theme_overrides.strings_color} - if "content" in string_match.names: - var escape_matches: Array[RegExMatch] = regex_escape.search_all(string_match.get_string("content")) - for escape_match in escape_matches: - colors[start_index + string_match.get_start("content") + escape_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + string_match.get_start("content") + escape_match.get_end(0)] = {"color": text_edit.theme_overrides.strings_color} - - # Numbers - var number_matches: Array[RegExMatch] = regex_number.search_all(text) - for number_match in number_matches: - colors[start_index + number_match.get_start(0)] = {"color": text_edit.theme_overrides.numbers_color} - - # Arrays - var array_matches: Array[RegExMatch] = regex_array.search_all(text) - for array_match in array_matches: - colors[start_index + array_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors.merge(_get_list_syntax_highlighting(start_index + array_match.get_start(1), array_match.strings[1]), true) - colors[start_index + array_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} - - # Dictionaries - var dict_matches: Array[RegExMatch] = regex_dict.search_all(text) - for dict_match in dict_matches: - colors[start_index + dict_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors.merge(_get_list_syntax_highlighting(start_index + dict_match.get_start(1), dict_match.strings[1]), true) - colors[start_index + dict_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} - - # Dictionary key: value pairs - var kvdict_matches: Array[RegExMatch] = regex_kvdict.search_all(text) - for kvdict_match in kvdict_matches: - colors.merge(_get_literal_syntax_highlighting(start_index + kvdict_match.get_start("left"), kvdict_match.get_string("left")), true) - colors[start_index + kvdict_match.get_start("colon")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + kvdict_match.get_end("colon")] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + kvdict_match.get_start("right"), kvdict_match.get_string("right")), true) - - # Booleans - var bool_matches: Array[RegExMatch] = regex_keyword.search_all(text) - for bool_match in bool_matches: - colors[start_index + bool_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color} - - # Functions - var function_matches: Array[RegExMatch] = regex_function.search_all(text) - for function_match in function_matches: - var last_brace_index: int = text.rfind(")") - colors[start_index + function_match.get_start(1)] = {"color": text_edit.theme_overrides.mutations_color} - colors[start_index + function_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} - colors.merge(_get_list_syntax_highlighting(start_index + function_match.get_end(0), text.substr(function_match.get_end(0), last_brace_index - function_match.get_end(0))), true) - colors[start_index + last_brace_index] = {"color": text_edit.theme_overrides.symbols_color} - - # Variables - var varname_matches: Array[RegExMatch] = regex_varname.search_all(text) - for varname_match in varname_matches: - colors[start_index + varname_match.get_start("var")] = {"color": text_edit.theme_overrides.text_color} - if "attr" in varname_match.names: - colors[start_index + varname_match.get_start("attr")] = {"color": text_edit.theme_overrides.members_color} - colors[start_index + varname_match.get_end("attr")] = {"color": text_edit.theme_overrides.text_color} - if "key" in varname_match.names: - # Braces are outside of the key, so coloring them symbols_color - colors[start_index + varname_match.get_start("key") - 1] = {"color": text_edit.theme_overrides.symbols_color} - colors.merge(_get_literal_syntax_highlighting(start_index + varname_match.get_start("key"), varname_match.get_string("key")), true) - colors[start_index + varname_match.get_end("key")] = {"color": text_edit.theme_overrides.symbols_color} - - # Comparison operators - var comparison_matches: Array[RegExMatch] = regex_comparison.search_all(text) - for comparison_match in comparison_matches: - colors.merge(_get_literal_syntax_highlighting(start_index + comparison_match.get_start("left"), comparison_match.get_string("left")), true) - colors[start_index + comparison_match.get_start("op")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + comparison_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color} - var right = comparison_match.get_string("right") - if right.ends_with(":"): - right = right.substr(0, right.length() - 1) - colors.merge(_get_literal_syntax_highlighting(start_index + comparison_match.get_start("right"), right), true) - colors[start_index + comparison_match.get_start("right") + right.length()] = { "color": text_edit.theme_overrides.symbols_color } - - # Logical binary operators - var blogical_matches: Array[RegExMatch] = regex_blogical.search_all(text) - for blogical_match in blogical_matches: - colors.merge(_get_literal_syntax_highlighting(start_index + blogical_match.get_start("left"), blogical_match.get_string("left")), true) - colors[start_index + blogical_match.get_start("op")] = {"color": text_edit.theme_overrides.conditions_color} - colors[start_index + blogical_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + blogical_match.get_start("right"), blogical_match.get_string("right")), true) - - # Logical unary operators - var ulogical_matches: Array[RegExMatch] = regex_ulogical.search_all(text) - for ulogical_match in ulogical_matches: - colors[start_index + ulogical_match.get_start("op")] = {"color": text_edit.theme_overrides.conditions_color} - colors[start_index + ulogical_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + ulogical_match.get_start("right"), ulogical_match.get_string("right")), true) - - return colors - - -## Returns the syntax coloring for a list of literals separated by commas -func _get_list_syntax_highlighting(start_index: int, text: String) -> Dictionary: - var text_edit: TextEdit = get_text_edit() - var colors: Dictionary = {} - - # Comma-separated list of literals (for arrays and function arguments) - var element_matches: Array[RegExMatch] = regex_commas.search_all(text) - for element_match in element_matches: - colors.merge(_get_literal_syntax_highlighting(start_index + element_match.get_start(1), element_match.strings[1]), true) - - return colors + return index diff --git a/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd.uid b/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd.uid new file mode 100644 index 0000000..9bad8cc --- /dev/null +++ b/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd.uid @@ -0,0 +1 @@ +uid://klpiq4tk3t7a diff --git a/addons/dialogue_manager/components/download_update_panel.gd b/addons/dialogue_manager/components/download_update_panel.gd index 617d308..e67a93f 100644 --- a/addons/dialogue_manager/components/download_update_panel.gd +++ b/addons/dialogue_manager/components/download_update_panel.gd @@ -34,7 +34,7 @@ func _ready() -> void: func _on_download_button_pressed() -> void: # Safeguard the actual dialogue manager repo from accidentally updating itself - if FileAccess.file_exists("res://examples/test_scenes/test_scene.gd"): + if FileAccess.file_exists("res://tests/test_basic_dialogue.gd"): prints("You can't update the addon from within itself.") failed.emit() return diff --git a/addons/dialogue_manager/components/download_update_panel.gd.uid b/addons/dialogue_manager/components/download_update_panel.gd.uid new file mode 100644 index 0000000..7910ab4 --- /dev/null +++ b/addons/dialogue_manager/components/download_update_panel.gd.uid @@ -0,0 +1 @@ +uid://kpwo418lb2t2 diff --git a/addons/dialogue_manager/components/download_update_panel.tscn b/addons/dialogue_manager/components/download_update_panel.tscn index f3f4523..540abd3 100644 --- a/addons/dialogue_manager/components/download_update_panel.tscn +++ b/addons/dialogue_manager/components/download_update_panel.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://qdxrxv3c3hxk"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/download_update_panel.gd" id="1_4tm1k"] +[ext_resource type="Script" uid="uid://kpwo418lb2t2" path="res://addons/dialogue_manager/components/download_update_panel.gd" id="1_4tm1k"] [ext_resource type="Texture2D" uid="uid://d3baj6rygkb3f" path="res://addons/dialogue_manager/assets/update.svg" id="2_4o2m6"] [node name="DownloadUpdatePanel" type="Control"] diff --git a/addons/dialogue_manager/components/editor_property/editor_property.gd.uid b/addons/dialogue_manager/components/editor_property/editor_property.gd.uid new file mode 100644 index 0000000..283cc43 --- /dev/null +++ b/addons/dialogue_manager/components/editor_property/editor_property.gd.uid @@ -0,0 +1 @@ +uid://nyypeje1a036 diff --git a/addons/dialogue_manager/components/editor_property/editor_property_control.gd b/addons/dialogue_manager/components/editor_property/editor_property_control.gd index ca52c79..d063d0e 100644 --- a/addons/dialogue_manager/components/editor_property/editor_property_control.gd +++ b/addons/dialogue_manager/components/editor_property/editor_property_control.gd @@ -63,7 +63,7 @@ func build_menu() -> void: func _on_new_dialog_file_selected(path: String) -> void: editor_plugin.main_view.new_file(path) is_waiting_for_file = false - if Engine.get_meta("DialogueCache").has_file(path): + if Engine.get_meta("DMCache").has_file(path): resource_changed.emit(load(path)) else: var next_resource: Resource = await editor_plugin.import_plugin.compiled_resource @@ -81,7 +81,7 @@ func _on_file_dialog_canceled() -> void: func _on_resource_button_pressed() -> void: if is_instance_valid(resource): - editor_plugin.get_editor_interface().call_deferred("edit_resource", resource) + EditorInterface.call_deferred("edit_resource", resource) else: build_menu() menu.position = get_viewport().position + Vector2i( @@ -112,7 +112,7 @@ func _on_menu_id_pressed(id: int) -> void: ITEM_QUICK_LOAD: quick_selected_file = "" - files_list.files = Engine.get_meta("DialogueCache").get_files() + files_list.files = Engine.get_meta("DMCache").get_files() if resource: files_list.select_file(resource.resource_path) quick_open_dialog.popup_centered() @@ -123,13 +123,13 @@ func _on_menu_id_pressed(id: int) -> void: open_dialog.popup_centered() ITEM_EDIT: - editor_plugin.get_editor_interface().call_deferred("edit_resource", resource) + EditorInterface.call_deferred("edit_resource", resource) ITEM_CLEAR: resource_changed.emit(null) ITEM_FILESYSTEM: - var file_system = editor_plugin.get_editor_interface().get_file_system_dock() + var file_system = EditorInterface.get_file_system_dock() file_system.navigate_to_path(resource.resource_path) diff --git a/addons/dialogue_manager/components/editor_property/editor_property_control.gd.uid b/addons/dialogue_manager/components/editor_property/editor_property_control.gd.uid new file mode 100644 index 0000000..aab7d8d --- /dev/null +++ b/addons/dialogue_manager/components/editor_property/editor_property_control.gd.uid @@ -0,0 +1 @@ +uid://dooe2pflnqtve diff --git a/addons/dialogue_manager/components/editor_property/editor_property_control.tscn b/addons/dialogue_manager/components/editor_property/editor_property_control.tscn index 8adb6a1..7cb02e8 100644 --- a/addons/dialogue_manager/components/editor_property/editor_property_control.tscn +++ b/addons/dialogue_manager/components/editor_property/editor_property_control.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=4 format=3 uid="uid://ycn6uaj7dsrh"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/editor_property/editor_property_control.gd" id="1_het12"] +[ext_resource type="Script" uid="uid://dooe2pflnqtve" path="res://addons/dialogue_manager/components/editor_property/editor_property_control.gd" id="1_het12"] [ext_resource type="PackedScene" uid="uid://b16uuqjuof3n5" path="res://addons/dialogue_manager/components/editor_property/resource_button.tscn" id="2_hh3d4"] [ext_resource type="PackedScene" uid="uid://dnufpcdrreva3" path="res://addons/dialogue_manager/components/files_list.tscn" id="3_l8fp6"] diff --git a/addons/dialogue_manager/components/editor_property/resource_button.gd.uid b/addons/dialogue_manager/components/editor_property/resource_button.gd.uid new file mode 100644 index 0000000..b1b9d26 --- /dev/null +++ b/addons/dialogue_manager/components/editor_property/resource_button.gd.uid @@ -0,0 +1 @@ +uid://damhqta55t67c diff --git a/addons/dialogue_manager/components/editor_property/resource_button.tscn b/addons/dialogue_manager/components/editor_property/resource_button.tscn index d68d851..691e527 100644 --- a/addons/dialogue_manager/components/editor_property/resource_button.tscn +++ b/addons/dialogue_manager/components/editor_property/resource_button.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://b16uuqjuof3n5"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/editor_property/resource_button.gd" id="1_7u2i7"] +[ext_resource type="Script" uid="uid://damhqta55t67c" path="res://addons/dialogue_manager/components/editor_property/resource_button.gd" id="1_7u2i7"] [node name="ResourceButton" type="Button"] offset_right = 8.0 diff --git a/addons/dialogue_manager/components/errors_panel.gd b/addons/dialogue_manager/components/errors_panel.gd index f3e294d..0b72d37 100644 --- a/addons/dialogue_manager/components/errors_panel.gd +++ b/addons/dialogue_manager/components/errors_panel.gd @@ -59,7 +59,7 @@ func show_error() -> void: show() count_label.text = DialogueConstants.translate(&"n_of_n").format({ index = error_index + 1, total = errors.size() }) var error = errors[error_index] - error_button.text = DialogueConstants.translate(&"errors.line_and_message").format({ line = error.line_number + 1, column = error.column_number, message = DialogueConstants.get_error_message(error.error) }) + error_button.text = DialogueConstants.translate(&"errors.line_and_message").format({ line = error.line_number, column = error.column_number, message = DialogueConstants.get_error_message(error.error) }) if error.has("external_error"): error_button.text += " " + DialogueConstants.get_error_message(error.external_error) @@ -72,7 +72,7 @@ func _on_errors_panel_theme_changed() -> void: func _on_error_button_pressed() -> void: - emit_signal("error_pressed", errors[error_index].line_number, errors[error_index].column_number) + error_pressed.emit(errors[error_index].line_number, errors[error_index].column_number) func _on_previous_button_pressed() -> void: diff --git a/addons/dialogue_manager/components/errors_panel.gd.uid b/addons/dialogue_manager/components/errors_panel.gd.uid new file mode 100644 index 0000000..c305a80 --- /dev/null +++ b/addons/dialogue_manager/components/errors_panel.gd.uid @@ -0,0 +1 @@ +uid://d2l8nlb6hhrfp diff --git a/addons/dialogue_manager/components/errors_panel.tscn b/addons/dialogue_manager/components/errors_panel.tscn index 956552b..0b653cc 100644 --- a/addons/dialogue_manager/components/errors_panel.tscn +++ b/addons/dialogue_manager/components/errors_panel.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=4 format=3 uid="uid://cs8pwrxr5vxix"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/errors_panel.gd" id="1_nfm3c"] +[ext_resource type="Script" uid="uid://d2l8nlb6hhrfp" path="res://addons/dialogue_manager/components/errors_panel.gd" id="1_nfm3c"] -[sub_resource type="Image" id="Image_wy5pj"] +[sub_resource type="Image" id="Image_w0gko"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), "format": "RGBA8", @@ -12,7 +12,7 @@ data = { } [sub_resource type="ImageTexture" id="ImageTexture_s6fxl"] -image = SubResource("Image_wy5pj") +image = SubResource("Image_w0gko") [node name="ErrorsPanel" type="HBoxContainer"] visible = false diff --git a/addons/dialogue_manager/components/files_list.gd b/addons/dialogue_manager/components/files_list.gd index 21a4415..31f6158 100644 --- a/addons/dialogue_manager/components/files_list.gd +++ b/addons/dialogue_manager/components/files_list.gd @@ -114,6 +114,8 @@ func apply_filter() -> void: func apply_theme() -> void: if is_instance_valid(filter_edit): filter_edit.right_icon = get_theme_icon("Search", "EditorIcons") + if is_instance_valid(list): + list.add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Panel")) ### Signals diff --git a/addons/dialogue_manager/components/files_list.gd.uid b/addons/dialogue_manager/components/files_list.gd.uid new file mode 100644 index 0000000..2a1089a --- /dev/null +++ b/addons/dialogue_manager/components/files_list.gd.uid @@ -0,0 +1 @@ +uid://dqa4a4wwoo0aa diff --git a/addons/dialogue_manager/components/files_list.tscn b/addons/dialogue_manager/components/files_list.tscn index 9d6f854..c9e862b 100644 --- a/addons/dialogue_manager/components/files_list.tscn +++ b/addons/dialogue_manager/components/files_list.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://dnufpcdrreva3"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/files_list.gd" id="1_cytii"] +[ext_resource type="Script" uid="uid://dqa4a4wwoo0aa" path="res://addons/dialogue_manager/components/files_list.gd" id="1_cytii"] [ext_resource type="Texture2D" uid="uid://d3lr2uas6ax8v" path="res://addons/dialogue_manager/assets/icon.svg" id="2_3ijx1"] [node name="FilesList" type="VBoxContainer"] @@ -9,6 +9,7 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +size_flags_vertical = 3 script = ExtResource("1_cytii") icon = ExtResource("2_3ijx1") diff --git a/addons/dialogue_manager/components/find_in_files.gd b/addons/dialogue_manager/components/find_in_files.gd index 916b744..2614eca 100644 --- a/addons/dialogue_manager/components/find_in_files.gd +++ b/addons/dialogue_manager/components/find_in_files.gd @@ -114,7 +114,7 @@ func find_in_files() -> Dictionary: var results: Dictionary = {} var q: String = input.text - var cache = Engine.get_meta("DialogueCache") + var cache = Engine.get_meta("DMCache") var file: FileAccess for path in cache.get_files(): var path_results: Array = [] diff --git a/addons/dialogue_manager/components/find_in_files.gd.uid b/addons/dialogue_manager/components/find_in_files.gd.uid new file mode 100644 index 0000000..380a491 --- /dev/null +++ b/addons/dialogue_manager/components/find_in_files.gd.uid @@ -0,0 +1 @@ +uid://q368fmxxa8sd diff --git a/addons/dialogue_manager/components/find_in_files.tscn b/addons/dialogue_manager/components/find_in_files.tscn index 8aaef4b..97fca24 100644 --- a/addons/dialogue_manager/components/find_in_files.tscn +++ b/addons/dialogue_manager/components/find_in_files.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://0n7hwviyyly4"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/find_in_files.gd" id="1_3xicy"] +[ext_resource type="Script" uid="uid://q368fmxxa8sd" path="res://addons/dialogue_manager/components/find_in_files.gd" id="1_3xicy"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_owohg"] bg_color = Color(0.266667, 0.278431, 0.352941, 0.243137) diff --git a/addons/dialogue_manager/components/parse_result.gd b/addons/dialogue_manager/components/parse_result.gd deleted file mode 100644 index d467cb9..0000000 --- a/addons/dialogue_manager/components/parse_result.gd +++ /dev/null @@ -1,10 +0,0 @@ -class_name DialogueManagerParseResult extends RefCounted - -var imported_paths: PackedStringArray = [] -var using_states: PackedStringArray = [] -var titles: Dictionary = {} -var character_names: PackedStringArray = [] -var first_title: String = "" -var lines: Dictionary = {} -var errors: Array[Dictionary] = [] -var raw_text: String = "" diff --git a/addons/dialogue_manager/components/parser.gd b/addons/dialogue_manager/components/parser.gd deleted file mode 100644 index bb5bb96..0000000 --- a/addons/dialogue_manager/components/parser.gd +++ /dev/null @@ -1,1798 +0,0 @@ -@tool - -class_name DialogueManagerParser extends Object - - -const DialogueConstants = preload("../constants.gd") -const DialogueSettings = preload("../settings.gd") -const ResolvedLineData = preload("./resolved_line_data.gd") -const ResolvedTagData = preload("./resolved_tag_data.gd") -const DialogueManagerParseResult = preload("./parse_result.gd") - - -var IMPORT_REGEX: RegEx = RegEx.create_from_string("import \"(?<path>[^\"]+)\" as (?<prefix>[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+)") -var USING_REGEX: RegEx = RegEx.create_from_string("^using (?<state>.*)$") -var VALID_TITLE_REGEX: RegEx = RegEx.create_from_string("^[a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+$") -var BEGINS_WITH_NUMBER_REGEX: RegEx = RegEx.create_from_string("^\\d") -var TRANSLATION_REGEX: RegEx = RegEx.create_from_string("\\[ID:(?<tr>.*?)\\]") -var TAGS_REGEX: RegEx = RegEx.create_from_string("\\[#(?<tags>.*?)\\]") -var MUTATION_REGEX: RegEx = RegEx.create_from_string("(?<keyword>do|do!|set) (?<mutation>.*)") -var CONDITION_REGEX: RegEx = RegEx.create_from_string("(if|elif|while|else if) (?<condition>.*)") -var WRAPPED_CONDITION_REGEX: RegEx = RegEx.create_from_string("\\[if (?<condition>.*)\\]") -var REPLACEMENTS_REGEX: RegEx = RegEx.create_from_string("{{(.*?)}}") -var GOTO_REGEX: RegEx = RegEx.create_from_string("=><? (?<jump_to_title>.*)") -var INDENT_REGEX: RegEx = RegEx.create_from_string("^\\t+") -var INLINE_RANDOM_REGEX: RegEx = RegEx.create_from_string("\\[\\[(?<options>.*?)\\]\\]") -var INLINE_CONDITIONALS_REGEX: RegEx = RegEx.create_from_string("\\[if (?<condition>.+?)\\](?<body>.*?)\\[\\/if\\]") - -var TOKEN_DEFINITIONS: Dictionary = { - DialogueConstants.TOKEN_FUNCTION: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\("), - DialogueConstants.TOKEN_DICTIONARY_REFERENCE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\["), - DialogueConstants.TOKEN_PARENS_OPEN: RegEx.create_from_string("^\\("), - DialogueConstants.TOKEN_PARENS_CLOSE: RegEx.create_from_string("^\\)"), - DialogueConstants.TOKEN_BRACKET_OPEN: RegEx.create_from_string("^\\["), - DialogueConstants.TOKEN_BRACKET_CLOSE: RegEx.create_from_string("^\\]"), - DialogueConstants.TOKEN_BRACE_OPEN: RegEx.create_from_string("^\\{"), - DialogueConstants.TOKEN_BRACE_CLOSE: RegEx.create_from_string("^\\}"), - DialogueConstants.TOKEN_COLON: RegEx.create_from_string("^:"), - DialogueConstants.TOKEN_COMPARISON: RegEx.create_from_string("^(==|<=|>=|<|>|!=|in )"), - DialogueConstants.TOKEN_ASSIGNMENT: RegEx.create_from_string("^(\\+=|\\-=|\\*=|/=|=)"), - DialogueConstants.TOKEN_NUMBER: RegEx.create_from_string("^\\-?\\d+(\\.\\d+)?"), - DialogueConstants.TOKEN_OPERATOR: RegEx.create_from_string("^(\\+|\\-|\\*|/|%)"), - DialogueConstants.TOKEN_COMMA: RegEx.create_from_string("^,"), - DialogueConstants.TOKEN_DOT: RegEx.create_from_string("^\\."), - DialogueConstants.TOKEN_STRING: RegEx.create_from_string("^&?(\".*?\"|\'.*?\')"), - DialogueConstants.TOKEN_NOT: RegEx.create_from_string("^(not( |$)|!)"), - DialogueConstants.TOKEN_AND_OR: RegEx.create_from_string("^(and|or|&&|\\|\\|)( |$)"), - DialogueConstants.TOKEN_VARIABLE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*"), - DialogueConstants.TOKEN_COMMENT: RegEx.create_from_string("^#.*"), - DialogueConstants.TOKEN_CONDITION: RegEx.create_from_string("^(if|elif|else)"), - DialogueConstants.TOKEN_BOOL: RegEx.create_from_string("^(true|false)") -} - -var WEIGHTED_RANDOM_SIBLINGS_REGEX: RegEx = RegEx.create_from_string("^\\%(?<weight>[\\d.]+)?( \\[if (?<condition>.+?)\\])? ") - -var raw_lines: PackedStringArray = [] -var parent_stack: Array[String] = [] - -var parsed_lines: Dictionary = {} -var imported_paths: PackedStringArray = [] -var using_states: PackedStringArray = [] -var titles: Dictionary = {} -var character_names: PackedStringArray = [] -var first_title: String = "" -var errors: Array[Dictionary] = [] -var raw_text: String = "" - -var _imported_line_map: Dictionary = {} -var _imported_line_count: int = 0 - -var while_loopbacks: Array[String] = [] - - -## Parse some raw dialogue text and return a dictionary containing parse results -static func parse_string(string: String, path: String) -> DialogueManagerParseResult: - var parser = new() - var error: Error = parser.parse(string, path) - var data: DialogueManagerParseResult = parser.get_data() - parser.free() - - if error == OK: - return data - else: - return null - - -## Extract bbcode and other markers from a string -static func extract_markers_from_string(string: String) -> ResolvedLineData: - var parser = new() - var markers: ResolvedLineData = parser.extract_markers(string) - parser.free() - - return markers - - -## Parse some raw dialogue text and return a dictionary containing parse results -func parse(text: String, path: String) -> Error: - prepare(text, path) - raw_text = text - - # Parse all of the content - var known_translations = {} - - # Get list of known autoloads - var autoload_names: PackedStringArray = get_autoload_names() - - # Keep track of the last doc comment - var doc_comments: Array[String] = [] - - # Then parse all lines - for id in range(0, raw_lines.size()): - var raw_line: String = raw_lines[id] - - var line: Dictionary = { - id = str(id), - next_id = DialogueConstants.ID_NULL - } - - # Work out if we are inside a conditional or option or if we just - # indented back out of one - var indent_size: int = get_indent(raw_line) - if indent_size < parent_stack.size() and not is_line_empty(raw_line): - for _tab in range(0, parent_stack.size() - indent_size): - parent_stack.pop_back() - - # If we are indented then this line should know about its parent - if parent_stack.size() > 0: - line["parent_id"] = parent_stack.back() - - # Trim any indentation (now that we've calculated it) so we can check - # the begining of each line for its type - raw_line = raw_line.strip_edges(true, false) - - # Grab translations - var translation_key: String = extract_translation(raw_line) - if translation_key != "": - line["translation_key"] = translation_key - raw_line = raw_line.replace("[ID:%s]" % translation_key, "") - - # Check for each kind of line - - # Start shortcuts - if raw_line.begins_with("using "): - var using_match: RegExMatch = USING_REGEX.search(raw_line) - if "state" in using_match.names: - var using_state: String = using_match.strings[using_match.names.state].strip_edges() - if not using_state in autoload_names: - add_error(id, 0, DialogueConstants.ERR_UNKNOWN_USING) - elif not using_state in using_states: - using_states.append(using_state) - continue - - # Response - elif is_response_line(raw_line): - # Add any doc notes - line["notes"] = "\n".join(doc_comments) - doc_comments = [] - - parent_stack.append(str(id)) - line["type"] = DialogueConstants.TYPE_RESPONSE - - # Extract any #tags - var tag_data: ResolvedTagData = extract_tags(raw_line) - line["tags"] = tag_data.tags - raw_line = tag_data.line_without_tags - - if " [if " in raw_line: - line["condition"] = extract_condition(raw_line, true, indent_size) - if " =>" in raw_line: - line["next_id"] = extract_goto(raw_line) - if " =><" in raw_line: - # Because of when the return point needs to be known at runtime we need to split - # this line into two (otherwise the return point would be dependent on the balloon) - var goto_line: Dictionary = { - type = DialogueConstants.TYPE_GOTO, - next_id = extract_goto(raw_line), - next_id_after = find_next_line_after_responses(id), - is_snippet = true - } - parsed_lines[str(id) + ".1"] = goto_line - line["next_id"] = str(id) + ".1" - - # Make sure the added goto line can actually go to somewhere - if goto_line.next_id in [DialogueConstants.ID_ERROR, DialogueConstants.ID_ERROR_INVALID_TITLE, DialogueConstants.ID_ERROR_TITLE_HAS_NO_BODY]: - line["next_id"] = goto_line.next_id - - line["character"] = "" - line["character_replacements"] = [] as Array[Dictionary] - line["text"] = extract_response_prompt(raw_line) - - var previous_response_id = find_previous_response_id(id) - if parsed_lines.has(previous_response_id): - var previous_response = parsed_lines[previous_response_id] - # Add this response to the list on the first response so that it is the - # authority on what is in the list of responses - previous_response["responses"] = previous_response["responses"] + PackedStringArray([str(id)]) - else: - # No previous response so this is the first in the list - line["responses"] = PackedStringArray([str(id)]) - - line["next_id_after"] = find_next_line_after_responses(id) - - # If this response has no body then the next id is the next id after - if not line.has("next_id") or line.next_id == DialogueConstants.ID_NULL: - var next_nonempty_line_id = get_next_nonempty_line_id(id) - if next_nonempty_line_id != DialogueConstants.ID_NULL: - if get_indent(raw_lines[next_nonempty_line_id.to_int()]) <= indent_size: - line["next_id"] = line.next_id_after - else: - line["next_id"] = next_nonempty_line_id - - line["text_replacements"] = extract_dialogue_replacements(line.get("text"), indent_size + 2) - for replacement in line.text_replacements: - if replacement.has("error"): - add_error(id, replacement.index, replacement.error) - - # If this response has a character name in it then it will automatically be - # injected as a line of dialogue if the player selects it - var response_text: String = line.text.replace("\\:", "!ESCAPED_COLON!") - if ":" in response_text: - if DialogueSettings.get_setting("create_lines_for_responses_with_characters", true): - var first_child: Dictionary = { - type = DialogueConstants.TYPE_DIALOGUE, - next_id = line.next_id, - next_id_after = line.next_id_after, - text_replacements = line.text_replacements, - tags = line.tags, - translation_key = line.get("translation_key") - } - parse_response_character_and_text(id, response_text, first_child, indent_size, parsed_lines) - line["character"] = first_child.character - line["character_replacements"] = first_child.character_replacements - line["text"] = first_child.text - line["text_replacements"] = extract_dialogue_replacements(line.text, indent_size + 2) - line["translation_key"] = first_child.translation_key - parsed_lines[str(id) + ".2"] = first_child - line["next_id"] = str(id) + ".2" - else: - parse_response_character_and_text(id, response_text, line, indent_size, parsed_lines) - else: - line["text"] = response_text.replace("!ESCAPED_COLON!", ":") - - # Title - elif is_title_line(raw_line): - line["type"] = DialogueConstants.TYPE_TITLE - line["text"] = extract_title(raw_line) - # Titles can't have numbers as the first letter (unless they are external titles which get replaced with hashes) - if id >= _imported_line_count and BEGINS_WITH_NUMBER_REGEX.search(line.text): - add_error(id, 2, DialogueConstants.ERR_TITLE_BEGINS_WITH_NUMBER) - # Only import titles are allowed to have "/" in them - var valid_title = VALID_TITLE_REGEX.search(raw_line.replace("/", "").substr(raw_line.find("~ ") + 2).strip_edges()) - if not valid_title: - add_error(id, 2, DialogueConstants.ERR_TITLE_INVALID_CHARACTERS) - - # Condition - elif is_condition_line(raw_line, false): - parent_stack.append(str(id)) - line["type"] = DialogueConstants.TYPE_CONDITION - line["condition"] = extract_condition(raw_line, false, indent_size) - line["next_id_after"] = find_next_line_after_conditions(id) - var next_sibling_id = find_next_condition_sibling(id) - line["next_conditional_id"] = next_sibling_id if is_valid_id(next_sibling_id) else line.next_id_after - - elif is_condition_line(raw_line, true): - parent_stack.append(str(id)) - line["type"] = DialogueConstants.TYPE_CONDITION - line["next_id_after"] = find_next_line_after_conditions(id) - line["next_conditional_id"] = line["next_id_after"] - - elif is_while_condition_line(raw_line): - parent_stack.append(str(id)) - line["type"] = DialogueConstants.TYPE_CONDITION - line["condition"] = extract_condition(raw_line, false, indent_size) - line["next_id_after"] = find_next_line_after_conditions(id) - while_loopbacks.append(find_last_line_within_conditions(id)) - line["next_conditional_id"] = line["next_id_after"] - - # Mutation - elif is_mutation_line(raw_line): - line["type"] = DialogueConstants.TYPE_MUTATION - line["mutation"] = extract_mutation(raw_line) - - # Goto - elif is_goto_line(raw_line): - line["type"] = DialogueConstants.TYPE_GOTO - - if raw_line.begins_with("%"): - apply_weighted_random(id, raw_line, indent_size, line) - - line["next_id"] = extract_goto(raw_line) - if is_goto_snippet_line(raw_line): - line["is_snippet"] = true - line["next_id_after"] = get_line_after_line(id, indent_size, line) - else: - line["is_snippet"] = false - - # Nested dialogue - elif is_nested_dialogue_line(raw_line, parsed_lines, raw_lines, indent_size): - var parent_line: Dictionary = parsed_lines.values().back() - var parent_indent_size: int = get_indent(raw_lines[parent_line.id.to_int()]) - var should_update_translation_key: bool = parent_line.translation_key == parent_line.text - var suffix: String = raw_line.strip_edges(true, false) - if suffix == "": - suffix = " " - parent_line["text"] += "\n" + suffix - parent_line["text_replacements"] = extract_dialogue_replacements(parent_line.text, parent_line.character.length() + 2 + parent_indent_size) - for replacement in parent_line.text_replacements: - if replacement.has("error"): - add_error(id, replacement.index, replacement.error) - - if should_update_translation_key: - parent_line["translation_key"] = parent_line.text - - parent_line["next_id"] = get_line_after_line(id, parent_indent_size, parent_line) - - # Ignore this line when checking for indent errors - remove_error(parent_line.id.to_int(), DialogueConstants.ERR_INVALID_INDENTATION) - - var next_line = raw_lines[parent_line.next_id.to_int()] - if not is_dialogue_line(next_line) and get_indent(next_line) >= indent_size: - add_error(parent_line.next_id.to_int(), indent_size, DialogueConstants.ERR_INVALID_INDENTATION) - - continue - - elif raw_line.strip_edges().begins_with("##"): - doc_comments.append(raw_line.replace("##", "").strip_edges()) - continue - - elif is_line_empty(raw_line) or is_import_line(raw_line): - continue - - # Regular dialogue - else: - # Remove escape character - if raw_line.begins_with("\\using"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\if"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\elif"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\else"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\while"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\-"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\~"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\=>"): raw_line = raw_line.substr(1) - - # Check for jumps - if " => " in raw_line: - line["next_id"] = extract_goto(raw_line) - raw_line = raw_line.split(" => ")[0] - - # Add any doc notes - line["notes"] = "\n".join(doc_comments) - doc_comments = [] - - # Work out any weighted random siblings - if raw_line.begins_with("%"): - apply_weighted_random(id, raw_line, indent_size, line) - raw_line = WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(raw_line, "") - - line["type"] = DialogueConstants.TYPE_DIALOGUE - - # Extract any tags before we process the line - var tag_data: ResolvedTagData = extract_tags(raw_line) - line["tags"] = tag_data.tags - raw_line = tag_data.line_without_tags - - var l = raw_line.replace("\\:", "!ESCAPED_COLON!") - - if ":" in l: - var bits = Array(l.strip_edges().split(":")) - line["character"] = bits.pop_front().strip_edges() - if not line["character"] in character_names: - character_names.append(line["character"]) - # You can use variables in the character's name - line["character_replacements"] = extract_dialogue_replacements(line.character, indent_size) - for replacement in line.character_replacements: - if replacement.has("error"): - add_error(id, replacement.index, replacement.error) - line["text"] = ":".join(bits).replace("!ESCAPED_COLON!", ":") - else: - line["character"] = "" - line["character_replacements"] = [] as Array[Dictionary] - line["text"] = l.replace("!ESCAPED_COLON!", ":") - - line["text_replacements"] = extract_dialogue_replacements(line.text, line.character.length() + 2 + indent_size) - for replacement in line.text_replacements: - if replacement.has("error"): - add_error(id, replacement.index, replacement.error) - - # Unescape any newlines - line["text"] = line.text.replace("\\n", "\n").strip_edges() - - # Work out where to go after this line - if line.next_id == DialogueConstants.ID_NULL: - line["next_id"] = get_line_after_line(id, indent_size, line) - - # Check for duplicate translation keys - if line.type in [DialogueConstants.TYPE_DIALOGUE, DialogueConstants.TYPE_RESPONSE]: - if line.has("translation_key"): - if known_translations.has(line.translation_key) and known_translations.get(line.translation_key) != line.text: - add_error(id, indent_size, DialogueConstants.ERR_DUPLICATE_ID) - else: - known_translations[line.translation_key] = line.text - else: - # Default translations key - if DialogueSettings.get_setting("missing_translations_are_errors", false): - add_error(id, indent_size, DialogueConstants.ERR_MISSING_ID) - else: - line["translation_key"] = line.text - - ## Error checking - - # Can't find goto - var jump_index: int = raw_line.find("=>") - match line.next_id: - DialogueConstants.ID_ERROR: - add_error(id, jump_index, DialogueConstants.ERR_UNKNOWN_TITLE) - DialogueConstants.ID_ERROR_INVALID_TITLE: - add_error(id, jump_index, DialogueConstants.ERR_INVALID_TITLE_REFERENCE) - DialogueConstants.ID_ERROR_TITLE_HAS_NO_BODY: - add_error(id, jump_index, DialogueConstants.ERR_TITLE_REFERENCE_HAS_NO_CONTENT) - - # Line after condition isn't indented once to the right - if line.type == DialogueConstants.TYPE_CONDITION: - if is_valid_id(line.next_id): - var next_line: String = raw_lines[line.next_id.to_int()] - var next_indent: int = get_indent(next_line) - if next_indent != indent_size + 1: - add_error(line.next_id.to_int(), next_indent, DialogueConstants.ERR_INVALID_INDENTATION) - else: - add_error(id, indent_size, DialogueConstants.ERR_INVALID_CONDITION_INDENTATION) - - # Line after normal line is indented to the right - elif line.type in [ - DialogueConstants.TYPE_TITLE, - DialogueConstants.TYPE_DIALOGUE, - DialogueConstants.TYPE_MUTATION, - ] and is_valid_id(line.next_id): - var next_line = raw_lines[line.next_id.to_int()] - if next_line != null and get_indent(next_line) > indent_size: - add_error(id, indent_size, DialogueConstants.ERR_INVALID_INDENTATION) - - # Parsing condition failed - if line.has("condition") and line.condition.has("error"): - add_error(id, line.condition.index, line.condition.error) - - # Parsing mutation failed - elif line.has("mutation") and line.mutation.has("error"): - add_error(id, line.mutation.index, line.mutation.error) - - # Line failed to parse at all - if line.get("type") == DialogueConstants.TYPE_UNKNOWN: - add_error(id, 0, DialogueConstants.ERR_UNKNOWN_LINE_SYNTAX) - - # If there are no titles then use the first actual line - if first_title == "" and not is_import_line(raw_line): - first_title = str(id) - - # If this line is the last line of a while loop, edit the id of its next line - if str(id) in while_loopbacks: - if is_goto_snippet_line(raw_line): - line["next_id_after"] = line["parent_id"] - elif is_condition_line(raw_line, true) or is_while_condition_line(raw_line): - line["next_conditional_id"] = line["parent_id"] - line["next_id_after"] = line["parent_id"] - elif is_goto_line(raw_line) or is_title_line(raw_line): - pass - else: - line["next_id"] = line["parent_id"] - - # Done! - parsed_lines[str(id)] = line - - # Assume the last line ends the dialogue - var last_line: Dictionary = parsed_lines.values()[parsed_lines.values().size() - 1] - if last_line.next_id == "": - last_line.next_id = DialogueConstants.ID_END - - if errors.size() > 0: - return ERR_PARSE_ERROR - - return OK - - -func get_data() -> DialogueManagerParseResult: - var data: DialogueManagerParseResult = DialogueManagerParseResult.new() - data.imported_paths = imported_paths - data.using_states = using_states - data.titles = titles - data.character_names = character_names - data.first_title = first_title - data.lines = parsed_lines - data.errors = errors - data.raw_text = raw_text - return data - - -## Get the last parse errors -func get_errors() -> Array[Dictionary]: - return errors - - -## Prepare the parser by collecting all lines and titles -func prepare(text: String, path: String, include_imported_titles_hashes: bool = true) -> void: - using_states = [] - errors = [] - imported_paths = [] - _imported_line_map = {} - while_loopbacks = [] - titles = {} - character_names = [] - first_title = "" - raw_lines = text.split("\n") - - # Work out imports - var known_imports: Dictionary = {} - - # Include the base file path so that we can get around circular dependencies - known_imports[path.hash()] = "." - - var imported_titles: Dictionary = {} - for id in range(0, raw_lines.size()): - var line = raw_lines[id] - if is_import_line(line): - var import_data = extract_import_path_and_name(line) - var import_hash: int = import_data.path.hash() - if import_data.size() > 0: - # Keep track of titles so we can add imported ones later - if str(import_hash) in imported_titles.keys(): - add_error(id, 0, DialogueConstants.ERR_FILE_ALREADY_IMPORTED) - if import_data.prefix in imported_titles.values(): - add_error(id, 0, DialogueConstants.ERR_DUPLICATE_IMPORT_NAME) - imported_titles[str(import_hash)] = import_data.prefix - - # Import the file content - if not known_imports.has(import_hash): - var error: Error = import_content(import_data.path, import_data.prefix, _imported_line_map, known_imports) - if error != OK: - add_error(id, 0, error) - - # Make a map so we can refer compiled lines to where they were imported from - if not _imported_line_map.has(import_hash): - _imported_line_map[import_hash] = { - hash = import_hash, - imported_on_line_number = id, - from_line = 0, - to_line = 0 - } - - var imported_content: String = "" - var cummulative_line_number: int = 0 - for item in _imported_line_map.values(): - item["from_line"] = cummulative_line_number - if known_imports.has(item.hash): - cummulative_line_number += known_imports[item.hash].split("\n").size() - item["to_line"] = cummulative_line_number - if known_imports.has(item.hash): - imported_content += known_imports[item.hash] + "\n" - - _imported_line_count = cummulative_line_number + 1 - - # Join it with the actual content - raw_lines = (imported_content + "\n" + text).split("\n") - - # Find all titles first - for id in range(0, raw_lines.size()): - if raw_lines[id].strip_edges().begins_with("~ "): - var title: String = extract_title(raw_lines[id]) - if title == "": - add_error(id, 2, DialogueConstants.ERR_EMPTY_TITLE) - elif titles.has(title): - add_error(id, 2, DialogueConstants.ERR_DUPLICATE_TITLE) - else: - var next_nonempty_line_id: String = get_next_nonempty_line_id(id) - if next_nonempty_line_id != DialogueConstants.ID_NULL: - titles[title] = next_nonempty_line_id - if "/" in title: - if include_imported_titles_hashes == false: - titles.erase(title) - var bits: PackedStringArray = title.split("/") - if imported_titles.has(bits[0]): - title = imported_titles[bits[0]] + "/" + bits[1] - titles[title] = next_nonempty_line_id - elif first_title == "": - first_title = next_nonempty_line_id - else: - titles[title] = DialogueConstants.ID_ERROR_TITLE_HAS_NO_BODY - - -func add_error(line_number: int, column_number: int, error: int) -> void: - # See if the error was in an imported file - for item in _imported_line_map.values(): - if line_number < item.to_line: - errors.append({ - line_number = item.imported_on_line_number, - column_number = 0, - error = DialogueConstants.ERR_ERRORS_IN_IMPORTED_FILE, - external_error = error, - external_line_number = line_number - }) - return - - # Otherwise, it's in this file - errors.append({ - line_number = line_number - _imported_line_count, - column_number = column_number, - error = error - }) - - -func remove_error(line_number: int, error: int) -> void: - for i in range(errors.size() - 1, -1, -1): - var err = errors[i] - var is_native_error = err.line_number == line_number - _imported_line_count and err.error == error - var is_external_error = err.get("external_line_number") == line_number and err.get("external_error") == error - if is_native_error or is_external_error: - errors.remove_at(i) - return - - -func is_import_line(line: String) -> bool: - return line.begins_with("import ") and " as " in line - - -func is_title_line(line: String) -> bool: - return line.strip_edges(true, false).begins_with("~ ") - - -func is_condition_line(line: String, include_else: bool = true) -> bool: - line = line.strip_edges(true, false) - if line.begins_with("if ") or line.begins_with("elif ") or line.begins_with("else if"): return true - if include_else and line.begins_with("else"): return true - return false - -func is_while_condition_line(line: String) -> bool: - line = line.strip_edges(true, false) - if line.begins_with("while "): return true - return false - - -func is_mutation_line(line: String) -> bool: - line = line.strip_edges(true, false) - return line.begins_with("do ") or line.begins_with("do! ") or line.begins_with("set ") - - -func is_goto_line(line: String) -> bool: - line = line.strip_edges(true, false) - line = WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(line, "") - return line.begins_with("=> ") or line.begins_with("=>< ") - - -func is_goto_snippet_line(line: String) -> bool: - line = WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(line.strip_edges(), "") - return line.begins_with("=>< ") - - -func is_nested_dialogue_line(raw_line: String, parsed_lines: Dictionary, raw_lines: PackedStringArray, indent_size: int) -> bool: - if parsed_lines.values().is_empty(): return false - if raw_line.strip_edges().begins_with("#"): return false - - var parent_line: Dictionary = parsed_lines.values().back() - if parent_line.type != DialogueConstants.TYPE_DIALOGUE: return false - if get_indent(raw_lines[parent_line.id.to_int()]) >= indent_size: return false - return true - - -func is_dialogue_line(line: String) -> bool: - if line == null: return false - if is_response_line(line): return false - if is_title_line(line): return false - if is_condition_line(line, true): return false - if is_mutation_line(line): return false - if is_goto_line(line): return false - return true - - -func is_response_line(line: String) -> bool: - return line.strip_edges(true, false).begins_with("- ") - - -func is_valid_id(id: String) -> bool: - return false if id in [DialogueConstants.ID_NULL, DialogueConstants.ID_ERROR, DialogueConstants.ID_END_CONVERSATION] else true - - -func is_line_empty(line: String) -> bool: - line = line.strip_edges() - - if line == "": return true - if line == "endif": return true - if line.begins_with("#"): return true - - return false - - -func get_line_after_line(id: int, indent_size: int, line: Dictionary) -> String: - # Unless the next line is an outdent we can assume it comes next - var next_nonempty_line_id = get_next_nonempty_line_id(id) - - if next_nonempty_line_id != DialogueConstants.ID_NULL and indent_size <= get_indent(raw_lines[next_nonempty_line_id.to_int()]): - return next_nonempty_line_id - # Otherwise, we grab the ID from the parents next ID after children - elif line.has("parent_id") and parsed_lines.has(line.parent_id): - return parsed_lines[line.parent_id].next_id_after - else: - return DialogueConstants.ID_NULL - - -func get_indent(line: String) -> int: - var tabs: RegExMatch = INDENT_REGEX.search(line) - if tabs: - return tabs.get_string().length() - else: - return 0 - - -func get_next_nonempty_line_id(line_number: int) -> String: - for i in range(line_number + 1, raw_lines.size()): - if not is_line_empty(raw_lines[i]): - return str(i) - return DialogueConstants.ID_NULL - - -func find_previous_response_id(line_number: int) -> String: - var line = raw_lines[line_number] - var indent_size = get_indent(line) - - # Look back up the list to find the previous response - var last_found_response_id: String = str(line_number) - - for i in range(line_number - 1, -1, -1): - line = raw_lines[i] - - if is_line_empty(line): continue - - # If its a response at the same indent level then its a match - elif get_indent(line) == indent_size: - if line.strip_edges().begins_with("- "): - last_found_response_id = str(i) - else: - break - elif get_indent(line) < indent_size: - break - - # Return the most relevant ID - return last_found_response_id - - -func apply_weighted_random(id: int, raw_line: String, indent_size: int, line: Dictionary) -> void: - var weight: float = 1 - var found = WEIGHTED_RANDOM_SIBLINGS_REGEX.search(raw_line) - var condition: Dictionary = {} - if found: - if found.names.has("weight"): - weight = found.strings[found.names.weight].to_float() - if found.names.has("condition"): - condition = extract_condition(raw_line, true, indent_size) - - # Look back up the list to find the first weighted random line in this group - var original_random_line: Dictionary = {} - for i in range(id, 0, -1): - # Ignore doc comment lines - if raw_lines[i].strip_edges().begins_with("##"): - continue - # Lines that aren't prefixed with the random token are a dead end - if not raw_lines[i].strip_edges().begins_with("%") or get_indent(raw_lines[i]) != indent_size: - break - # Make sure we group random dialogue and random lines separately - elif WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(raw_line.strip_edges(), "").begins_with("=") != WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(raw_lines[i].strip_edges(), "").begins_with("="): - break - # Otherwise we've found the origin - elif parsed_lines.has(str(i)) and parsed_lines[str(i)].has("siblings"): - original_random_line = parsed_lines[str(i)] - break - - # Attach it to the original random line and work out where to go after the line - if original_random_line.size() > 0: - original_random_line["siblings"] += [{ weight = weight, id = str(id), condition = condition }] - if original_random_line.type != DialogueConstants.TYPE_GOTO: - # Update the next line for all siblings (not goto lines, though, they manage their - # own next ID) - original_random_line["next_id"] = get_line_after_line(id, indent_size, line) - for sibling in original_random_line["siblings"]: - if sibling.id in parsed_lines: - parsed_lines[sibling.id]["next_id"] = original_random_line["next_id"] - elif original_random_line.has("next_id_after"): - original_random_line["next_id_after"] = get_line_after_line(id, indent_size, line) - for sibling in original_random_line["siblings"]: - if sibling.id in parsed_lines: - parsed_lines[sibling.id]["next_id_after"] = original_random_line["next_id_after"] - - line["next_id"] = original_random_line.next_id - # Or set up this line as the original - else: - line["siblings"] = [{ weight = weight, id = str(id), condition = condition }] - line["next_id"] = get_line_after_line(id, indent_size, line) - - if line.next_id == DialogueConstants.ID_NULL: - line["next_id"] = DialogueConstants.ID_END - - -func find_next_condition_sibling(line_number: int) -> String: - var line = raw_lines[line_number] - var expected_indent = get_indent(line) - - # Look down the list and find an elif or else at the same indent level - for i in range(line_number + 1, raw_lines.size()): - line = raw_lines[i] - if is_line_empty(line): continue - - var l = line.strip_edges() - if l.begins_with("~ "): - return DialogueConstants.ID_END_CONVERSATION - - elif get_indent(line) < expected_indent: - return DialogueConstants.ID_NULL - - elif get_indent(line) == expected_indent: - # Found an if, which begins a different block - if l.begins_with("if"): - return DialogueConstants.ID_NULL - - # Found what we're looking for - elif (l.begins_with("elif ") or l.begins_with("else")): - return str(i) - - return DialogueConstants.ID_NULL - - -func find_next_line_after_conditions(line_number: int) -> String: - var line = raw_lines[line_number] - var expected_indent = get_indent(line) - - # Look down the list for the first non condition line at the same or less indent level - for i in range(line_number + 1, raw_lines.size()): - line = raw_lines[i] - - if is_line_empty(line): continue - - var line_indent = get_indent(line) - line = line.strip_edges() - - if line_indent > expected_indent: - continue - - elif line_indent == expected_indent: - if line.begins_with("elif ") or line.begins_with("else"): - continue - else: - return str(i) - - elif line_indent < expected_indent: - # We have to check the parent of this block - for p in range(line_number - 1, -1, -1): - line = raw_lines[p] - - if is_line_empty(line): continue - - line_indent = get_indent(line) - if line_indent < expected_indent: - return parsed_lines[str(p)].get("next_id_after", DialogueConstants.ID_NULL) - - return DialogueConstants.ID_END_CONVERSATION - - -func find_last_line_within_conditions(line_number: int) -> String: - var line = raw_lines[line_number] - var expected_indent = get_indent(line) - - var candidate = DialogueConstants.ID_NULL - - # Look down the list for the last line that has an indent level 1 more than this line - # Ending the search when you find a line the same or less indent level - for i in range(line_number + 1, raw_lines.size()): - line = raw_lines[i] - - if is_line_empty(line): continue - - var line_indent = get_indent(line) - line = line.strip_edges() - - if line_indent > expected_indent + 1: - continue - elif line_indent == (expected_indent + 1): - candidate = i - else: - break - - return str(candidate) - -func find_next_line_after_responses(line_number: int) -> String: - var line = raw_lines[line_number] - var expected_indent = get_indent(line) - - # Find the first line after this one that has a smaller indent that isn't another option - # If we hit the eof then we give up - for i in range(line_number + 1, raw_lines.size()): - line = raw_lines[i] - - if is_line_empty(line): continue - - var indent = get_indent(line) - - line = line.strip_edges() - - # We hit a title so the next line is a new start - if is_title_line(line): - return get_next_nonempty_line_id(i) - - # Another option - elif line.begins_with("- "): - if indent == expected_indent: - # ...at the same level so we continue - continue - elif indent < expected_indent: - # ...outdented so check the previous parent - var previous_parent = parent_stack[parent_stack.size() - 2] - if parsed_lines.has(str(previous_parent)): - return parsed_lines[str(previous_parent)].next_id_after - else: - return DialogueConstants.ID_NULL - - # We're at the end of a conditional so jump back up to see what's after it - elif line.begins_with("elif ") or line.begins_with("else"): - for p in range(line_number - 1, -1, -1): - line = raw_lines[p] - - if is_line_empty(line): continue - - var line_indent = get_indent(line) - if line_indent < expected_indent: - return parsed_lines[str(p)].next_id_after - - # Otherwise check the indent for an outdent - else: - line_number = i - line = raw_lines[line_number] - if get_indent(line) <= expected_indent: - return str(line_number) - - # EOF so it's also the end of a block - return DialogueConstants.ID_END - - -## Get the names of any autoloads in the project -func get_autoload_names() -> PackedStringArray: - var autoloads: PackedStringArray = [] - - var project = ConfigFile.new() - project.load("res://project.godot") - if project.has_section("autoload"): - return Array(project.get_section_keys("autoload")).filter(func(key): return key != "DialogueManager") - - return autoloads - - -## Import content from another dialogue file or return an ERR -func import_content(path: String, prefix: String, imported_line_map: Dictionary, known_imports: Dictionary) -> Error: - if FileAccess.file_exists(path): - var file = FileAccess.open(path, FileAccess.READ) - var content: PackedStringArray = file.get_as_text().split("\n") - - var imported_titles: Dictionary = {} - - for index in range(0, content.size()): - var line = content[index] - if is_import_line(line): - var import = extract_import_path_and_name(line) - if import.size() > 0: - if not known_imports.has(import.path.hash()): - # Add an empty record into the keys just so we don't end up with cyclic dependencies - known_imports[import.path.hash()] = "" - if import_content(import.path, import.prefix, imported_line_map, known_imports) != OK: - return ERR_LINK_FAILED - - if not imported_line_map.has(import.path.hash()): - # Make a map so we can refer compiled lines to where they were imported from - imported_line_map[import.path.hash()] = { - hash = import.path.hash(), - imported_on_line_number = index, - from_line = 0, - to_line = 0 - } - - imported_titles[import.prefix] = import.path.hash() - - var origin_hash: int = -1 - for hash_value in known_imports.keys(): - if known_imports[hash_value] == ".": - origin_hash = hash_value - - # Replace any titles or jump points with references to the files they point to (event if they point to their own file) - for i in range(0, content.size()): - var line = content[i] - if is_title_line(line): - var title = extract_title(line) - if "/" in line: - var bits = title.split("/") - content[i] = "~ %s/%s" % [imported_titles[bits[0]], bits[1]] - else: - content[i] = "~ %s/%s" % [str(path.hash()), title] - - elif "=>< " in line: - var jump: String = line.substr(line.find("=>< ") + "=>< ".length()).strip_edges() - if "/" in jump: - var bits: PackedStringArray = jump.split("/") - var title_hash: int = imported_titles[bits[0]] - if title_hash == origin_hash: - content[i] = "%s=>< %s" % [line.split("=>< ")[0], bits[1]] - else: - content[i] = "%s=>< %s/%s" % [line.split("=>< ")[0], title_hash, bits[1]] - - elif not jump in ["END", "END!"]: - content[i] = "%s=>< %s/%s" % [line.split("=>< ")[0], str(path.hash()), jump] - - elif "=> " in line: - var jump: String = line.substr(line.find("=> ") + "=> ".length()).strip_edges() - if "/" in jump: - var bits: PackedStringArray = jump.split("/") - var title_hash: int = imported_titles[bits[0]] - if title_hash == origin_hash: - content[i] = "%s=> %s" % [line.split("=> ")[0], bits[1]] - else: - content[i] = "%s=> %s/%s" % [line.split("=> ")[0], title_hash, bits[1]] - - elif not jump in ["END", "END!"]: - content[i] = "%s=> %s/%s" % [line.split("=> ")[0], str(path.hash()), jump] - - imported_paths.append(path) - known_imports[path.hash()] = "\n".join(content) + "\n=> END\n" - return OK - else: - return ERR_FILE_NOT_FOUND - - -func extract_import_path_and_name(line: String) -> Dictionary: - var found: RegExMatch = IMPORT_REGEX.search(line) - if found: - return { - path = found.strings[found.names.path], - prefix = found.strings[found.names.prefix] - } - else: - return {} - - -func extract_title(line: String) -> String: - return line.substr(line.find("~ ") + 2).strip_edges() - - -func extract_translation(line: String) -> String: - # Find a static translation key, eg. [ID:something] - var found: RegExMatch = TRANSLATION_REGEX.search(line) - if found: - return found.strings[found.names.tr] - else: - return "" - - -func extract_response_prompt(line: String) -> String: - # Find just the text prompt from a response, ignoring any conditions or gotos - line = line.substr(2) - if " [if " in line: - line = line.substr(0, line.find(" [if ")) - if " =>" in line: - line = line.substr(0, line.find(" =>")) - - # Without the translation key if there is one - var translation_key: String = extract_translation(line) - if translation_key: - line = line.replace("[ID:%s]" % translation_key, "") - - return line.replace("\\n", "\n").strip_edges() - - -func parse_response_character_and_text(id: int, text: String, line: Dictionary, indent_size: int, parsed_lines: Dictionary) -> void: - var bits = Array(text.strip_edges().split(": ")) - line["character"] = bits.pop_front().strip_edges() - line["character_replacements"] = extract_dialogue_replacements(line.character, line.character.length() + 2 + indent_size) - for replacement in line.character_replacements: - if replacement.has("error"): - add_error(id, replacement.index, replacement.error) - - if not line["character"] in character_names: - character_names.append(line["character"]) - - line["text"] = ":".join(bits).replace("!ESCAPED_COLON!", ":").strip_edges() - - if line.get("translation_key", null) == null: - line["translation_key"] = line.text - - -func extract_mutation(line: String) -> Dictionary: - var found: RegExMatch = MUTATION_REGEX.search(line) - - if not found: - return { - index = 0, - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - - if found.names.has("mutation"): - var expression: Array = tokenise(found.strings[found.names.mutation], DialogueConstants.TYPE_MUTATION, found.get_start("mutation")) - if expression.size() == 0: - return { - index = found.get_start("mutation"), - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - elif expression[0].type == DialogueConstants.TYPE_ERROR: - return { - index = expression[0].index, - error = expression[0].value - } - else: - return { - expression = expression, - is_blocking = not "!" in found.strings[found.names.keyword] - } - - else: - return { - index = found.get_start(), - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - - -func extract_condition(raw_line: String, is_wrapped: bool, index: int) -> Dictionary: - var condition: Dictionary = {} - - var regex: RegEx = WRAPPED_CONDITION_REGEX if is_wrapped else CONDITION_REGEX - var found: RegExMatch = regex.search(raw_line) - - if found == null: - return { - index = 0, - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - - var raw_condition: String = found.strings[found.names.condition] - var expression: Array = tokenise(raw_condition, DialogueConstants.TYPE_CONDITION, index + found.get_start("condition")) - - if expression.size() == 0: - return { - index = index + found.get_start("condition"), - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - elif expression[0].type == DialogueConstants.TYPE_ERROR: - return { - index = expression[0].index, - error = expression[0].value - } - else: - return { - expression = expression - } - - -func extract_dialogue_replacements(text: String, index: int) -> Array[Dictionary]: - var founds: Array[RegExMatch] = REPLACEMENTS_REGEX.search_all(text) - - if founds == null or founds.size() == 0: - return [] - - var replacements: Array[Dictionary] = [] - for found in founds: - var replacement: Dictionary = {} - var value_in_text: String = found.strings[1] - var expression: Array = tokenise(value_in_text, DialogueConstants.TYPE_DIALOGUE, index + found.get_start(1)) - if expression.size() == 0: - replacement = { - index = index + found.get_start(1), - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - elif expression[0].type == DialogueConstants.TYPE_ERROR: - replacement = { - index = expression[0].index, - error = expression[0].value - } - else: - replacement = { - value_in_text = "{{%s}}" % value_in_text, - expression = expression - } - replacements.append(replacement) - - return replacements - - -func extract_goto(line: String) -> String: - var found: RegExMatch = GOTO_REGEX.search(line) - - if found == null: return DialogueConstants.ID_ERROR - - var title: String = found.strings[found.names.jump_to_title].strip_edges() - - if " " in title or title == "": - return DialogueConstants.ID_ERROR_INVALID_TITLE - - # "=> END!" means end the conversation - if title == "END!": - return DialogueConstants.ID_END_CONVERSATION - # "=> END" means end the current title (and go back to the previous one if there is one - # in the stack) - elif title == "END": - return DialogueConstants.ID_END - - elif titles.has(title): - return titles.get(title) - else: - return DialogueConstants.ID_ERROR - - -func extract_tags(line: String) -> ResolvedTagData: - var resolved_tags: PackedStringArray = [] - var tag_matches: Array[RegExMatch] = TAGS_REGEX.search_all(line) - for tag_match in tag_matches: - line = line.replace(tag_match.get_string(), "") - var tags = tag_match.get_string().replace("[#", "").replace("]", "").replace(", ", ",").split(",") - for tag in tags: - tag = tag.replace("#", "") - if not tag in resolved_tags: - resolved_tags.append(tag) - - return ResolvedTagData.new({ - tags = resolved_tags, - line_without_tags = line - }) - - -func extract_markers(line: String) -> ResolvedLineData: - var text: String = line - var pauses: Dictionary = {} - var speeds: Dictionary = {} - var mutations: Array[Array] = [] - var bbcodes: Array = [] - var time: String = "" - - # Remove any escaped brackets (ie. "\[") - var escaped_open_brackets: PackedInt32Array = [] - var escaped_close_brackets: PackedInt32Array = [] - for i in range(0, text.length() - 1): - if text.substr(i, 2) == "\\[": - text = text.substr(0, i) + "!" + text.substr(i + 2) - escaped_open_brackets.append(i) - elif text.substr(i, 2) == "\\]": - text = text.substr(0, i) + "!" + text.substr(i + 2) - escaped_close_brackets.append(i) - - # Extract all of the BB codes so that we know the actual text (we could do this easier with - # a RichTextLabel but then we'd need to await idle_frame which is annoying) - var bbcode_positions = find_bbcode_positions_in_string(text) - var accumulaive_length_offset = 0 - for position in bbcode_positions: - # Ignore our own markers - if position.code in ["wait", "speed", "/speed", "do", "do!", "set", "next", "if", "else", "/if"]: - continue - - bbcodes.append({ - bbcode = position.bbcode, - start = position.start, - offset_start = position.start - accumulaive_length_offset - }) - accumulaive_length_offset += position.bbcode.length() - - for bb in bbcodes: - text = text.substr(0, bb.offset_start) + text.substr(bb.offset_start + bb.bbcode.length()) - - # Now find any dialogue markers - var next_bbcode_position = find_bbcode_positions_in_string(text, false) - var limit = 0 - while next_bbcode_position.size() > 0 and limit < 1000: - limit += 1 - - var bbcode = next_bbcode_position[0] - - var index = bbcode.start - var code = bbcode.code - var raw_args = bbcode.raw_args - var args = {} - if code in ["do", "do!", "set"]: - args["value"] = extract_mutation("%s %s" % [code, raw_args]) - else: - # Could be something like: - # "=1.0" - # " rate=20 level=10" - if raw_args and raw_args[0] == "=": - raw_args = "value" + raw_args - for pair in raw_args.strip_edges().split(" "): - if "=" in pair: - var bits = pair.split("=") - args[bits[0]] = bits[1] - - match code: - "wait": - if pauses.has(index): - pauses[index] += args.get("value").to_float() - else: - pauses[index] = args.get("value").to_float() - "speed": - speeds[index] = args.get("value").to_float() - "/speed": - speeds[index] = 1.0 - "do", "do!", "set": - mutations.append([index, args.get("value")]) - "next": - time = args.get("value") if args.has("value") else "0" - - # Find any BB codes that are after this index and remove the length from their start - var length = bbcode.bbcode.length() - for bb in bbcodes: - if bb.offset_start > bbcode.start: - bb.offset_start -= length - bb.start -= length - - # Find any escaped brackets after this that need moving - for i in range(0, escaped_open_brackets.size()): - if escaped_open_brackets[i] > bbcode.start: - escaped_open_brackets[i] -= length - for i in range(0, escaped_close_brackets.size()): - if escaped_close_brackets[i] > bbcode.start: - escaped_close_brackets[i] -= length - - text = text.substr(0, index) + text.substr(index + length) - next_bbcode_position = find_bbcode_positions_in_string(text, false) - - # Put the BB Codes back in - for bb in bbcodes: - text = text.insert(bb.start, bb.bbcode) - - # Put the escaped brackets back in - for index in escaped_open_brackets: - text = text.left(index) + "[" + text.right(text.length() - index - 1) - for index in escaped_close_brackets: - text = text.left(index) + "]" + text.right(text.length() - index - 1) - - return ResolvedLineData.new({ - text = text, - pauses = pauses, - speeds = speeds, - mutations = mutations, - time = time - }) - - -func find_bbcode_positions_in_string(string: String, find_all: bool = true) -> Array[Dictionary]: - if not "[" in string: return [] - - var positions: Array[Dictionary] = [] - - var open_brace_count: int = 0 - var start: int = 0 - var bbcode: String = "" - var code: String = "" - var is_finished_code: bool = false - for i in range(0, string.length()): - if string[i] == "[": - if open_brace_count == 0: - start = i - bbcode = "" - code = "" - is_finished_code = false - open_brace_count += 1 - - else: - if not is_finished_code and (string[i].to_upper() != string[i] or string[i] == "/" or string[i] == "!"): - code += string[i] - else: - is_finished_code = true - - if open_brace_count > 0: - bbcode += string[i] - - if string[i] == "]": - open_brace_count -= 1 - if open_brace_count == 0 and not code in ["if", "else", "/if"]: - positions.append({ - bbcode = bbcode, - code = code, - start = start, - raw_args = bbcode.substr(code.length() + 1, bbcode.length() - code.length() - 2).strip_edges() - }) - - if not find_all: - return positions - - return positions - - -func tokenise(text: String, line_type: String, index: int) -> Array: - var tokens: Array[Dictionary] = [] - var limit: int = 0 - while text.strip_edges() != "" and limit < 1000: - limit += 1 - var found = find_match(text) - if found.size() > 0: - tokens.append({ - index = index, - type = found.type, - value = found.value - }) - index += found.value.length() - text = found.remaining_text - elif text.begins_with(" "): - index += 1 - text = text.substr(1) - else: - return build_token_tree_error(DialogueConstants.ERR_INVALID_EXPRESSION, index) - - return build_token_tree(tokens, line_type, "")[0] - - -func build_token_tree_error(error: int, index: int) -> Array: - return [{ type = DialogueConstants.TOKEN_ERROR, value = error, index = index }] - - -func build_token_tree(tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Array: - var tree: Array[Dictionary] = [] - var limit = 0 - while tokens.size() > 0 and limit < 1000: - limit += 1 - var token = tokens.pop_front() - - var error = check_next_token(token, tokens, line_type, expected_close_token) - if error != OK: - return [build_token_tree_error(error, token.index), tokens] - - match token.type: - DialogueConstants.TOKEN_FUNCTION: - var sub_tree = build_token_tree(tokens, line_type, DialogueConstants.TOKEN_PARENS_CLOSE) - - if sub_tree[0].size() > 0 and sub_tree[0][0].type == DialogueConstants.TOKEN_ERROR: - return [build_token_tree_error(sub_tree[0][0].value, token.index), tokens] - - tree.append({ - type = DialogueConstants.TOKEN_FUNCTION, - # Consume the trailing "(" - function = token.value.substr(0, token.value.length() - 1), - value = tokens_to_list(sub_tree[0]) - }) - tokens = sub_tree[1] - - DialogueConstants.TOKEN_DICTIONARY_REFERENCE: - var sub_tree = build_token_tree(tokens, line_type, DialogueConstants.TOKEN_BRACKET_CLOSE) - - if sub_tree[0].size() > 0 and sub_tree[0][0].type == DialogueConstants.TOKEN_ERROR: - return [build_token_tree_error(sub_tree[0][0].value, token.index), tokens] - - var args = tokens_to_list(sub_tree[0]) - if args.size() != 1: - return [build_token_tree_error(DialogueConstants.ERR_INVALID_INDEX, token.index), tokens] - - tree.append({ - type = DialogueConstants.TOKEN_DICTIONARY_REFERENCE, - # Consume the trailing "[" - variable = token.value.substr(0, token.value.length() - 1), - value = args[0] - }) - tokens = sub_tree[1] - - DialogueConstants.TOKEN_BRACE_OPEN: - var sub_tree = build_token_tree(tokens, line_type, DialogueConstants.TOKEN_BRACE_CLOSE) - - if sub_tree[0].size() > 0 and sub_tree[0][0].type == DialogueConstants.TOKEN_ERROR: - return [build_token_tree_error(sub_tree[0][0].value, token.index), tokens] - - var t = sub_tree[0] - for i in range(0, t.size() - 2): - # Convert Lua style dictionaries to string keys - if t[i].type == DialogueConstants.TOKEN_VARIABLE and t[i+1].type == DialogueConstants.TOKEN_ASSIGNMENT: - t[i].type = DialogueConstants.TOKEN_STRING - t[i+1].type = DialogueConstants.TOKEN_COLON - t[i+1].erase("value") - - tree.append({ - type = DialogueConstants.TOKEN_DICTIONARY, - value = tokens_to_dictionary(sub_tree[0]) - }) - - tokens = sub_tree[1] - - DialogueConstants.TOKEN_BRACKET_OPEN: - var sub_tree = build_token_tree(tokens, line_type, DialogueConstants.TOKEN_BRACKET_CLOSE) - - if sub_tree[0].size() > 0 and sub_tree[0][0].type == DialogueConstants.TOKEN_ERROR: - return [build_token_tree_error(sub_tree[0][0].value, token.index), tokens] - - var type = DialogueConstants.TOKEN_ARRAY - var value = tokens_to_list(sub_tree[0]) - - # See if this is referencing a nested dictionary value - if tree.size() > 0: - var previous_token = tree[tree.size() - 1] - if previous_token.type in [DialogueConstants.TOKEN_DICTIONARY_REFERENCE, DialogueConstants.TOKEN_DICTIONARY_NESTED_REFERENCE]: - type = DialogueConstants.TOKEN_DICTIONARY_NESTED_REFERENCE - value = value[0] - - tree.append({ - type = type, - value = value - }) - tokens = sub_tree[1] - - DialogueConstants.TOKEN_PARENS_OPEN: - var sub_tree = build_token_tree(tokens, line_type, DialogueConstants.TOKEN_PARENS_CLOSE) - - if sub_tree[0][0].type == DialogueConstants.TOKEN_ERROR: - return [build_token_tree_error(sub_tree[0][0].value, token.index), tokens] - - tree.append({ - type = DialogueConstants.TOKEN_GROUP, - value = sub_tree[0] - }) - tokens = sub_tree[1] - - DialogueConstants.TOKEN_PARENS_CLOSE, \ - DialogueConstants.TOKEN_BRACE_CLOSE, \ - DialogueConstants.TOKEN_BRACKET_CLOSE: - if token.type != expected_close_token: - return [build_token_tree_error(DialogueConstants.ERR_UNEXPECTED_CLOSING_BRACKET, token.index), tokens] - - return [tree, tokens] - - DialogueConstants.TOKEN_NOT: - # Double nots negate each other - if tokens.size() > 0 and tokens.front().type == DialogueConstants.TOKEN_NOT: - tokens.pop_front() - else: - tree.append({ - type = token.type - }) - - DialogueConstants.TOKEN_COMMA, \ - DialogueConstants.TOKEN_COLON, \ - DialogueConstants.TOKEN_DOT: - tree.append({ - type = token.type - }) - - DialogueConstants.TOKEN_COMPARISON, \ - DialogueConstants.TOKEN_ASSIGNMENT, \ - DialogueConstants.TOKEN_OPERATOR, \ - DialogueConstants.TOKEN_AND_OR, \ - DialogueConstants.TOKEN_VARIABLE: - var value = token.value.strip_edges() - if value == "&&": - value = "and" - elif value == "||": - value = "or" - tree.append({ - type = token.type, - value = value - }) - - DialogueConstants.TOKEN_STRING: - if token.value.begins_with("&"): - tree.append({ - type = token.type, - value = StringName(token.value.substr(2, token.value.length() - 3)) - }) - else: - tree.append({ - type = token.type, - value = token.value.substr(1, token.value.length() - 2) - }) - - DialogueConstants.TOKEN_CONDITION: - return [build_token_tree_error(DialogueConstants.ERR_UNEXPECTED_CONDITION, token.index), token] - - DialogueConstants.TOKEN_BOOL: - tree.append({ - type = token.type, - value = token.value.to_lower() == "true" - }) - - DialogueConstants.TOKEN_NUMBER: - var value = token.value.to_float() if "." in token.value else token.value.to_int() - # If previous token is a number and this one is a negative number then - # inject a minus operator token in between them. - if tree.size() > 0 and token.value.begins_with("-") and tree[tree.size() - 1].type == DialogueConstants.TOKEN_NUMBER: - tree.append(({ - type = DialogueConstants.TOKEN_OPERATOR, - value = "-" - })) - tree.append({ - type = token.type, - value = -1 * value - }) - else: - tree.append({ - type = token.type, - value = value - }) - - if expected_close_token != "": - var index: int = tokens[0].index if tokens.size() > 0 else 0 - return [build_token_tree_error(DialogueConstants.ERR_MISSING_CLOSING_BRACKET, index), tokens] - - return [tree, tokens] - - -func check_next_token(token: Dictionary, next_tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Error: - var next_token: Dictionary = { type = null } - if next_tokens.size() > 0: - next_token = next_tokens.front() - - # Guard for assigning in a condition. If the assignment token isn't inside a Lua dictionary - # then it's an unexpected assignment in a condition line. - if token.type == DialogueConstants.TOKEN_ASSIGNMENT and line_type == DialogueConstants.TYPE_CONDITION and not next_tokens.any(func(t): return t.type == expected_close_token): - return DialogueConstants.ERR_UNEXPECTED_ASSIGNMENT - - # Special case for a negative number after this one - if token.type == DialogueConstants.TOKEN_NUMBER and next_token.type == DialogueConstants.TOKEN_NUMBER and next_token.value.begins_with("-"): - return OK - - var expected_token_types = [] - var unexpected_token_types = [] - match token.type: - DialogueConstants.TOKEN_FUNCTION, \ - DialogueConstants.TOKEN_PARENS_OPEN: - unexpected_token_types = [ - null, - DialogueConstants.TOKEN_COMMA, - DialogueConstants.TOKEN_COLON, - DialogueConstants.TOKEN_COMPARISON, - DialogueConstants.TOKEN_ASSIGNMENT, - DialogueConstants.TOKEN_OPERATOR, - DialogueConstants.TOKEN_AND_OR, - DialogueConstants.TOKEN_DOT - ] - - DialogueConstants.TOKEN_BRACKET_CLOSE: - unexpected_token_types = [ - DialogueConstants.TOKEN_NOT, - DialogueConstants.TOKEN_BOOL, - DialogueConstants.TOKEN_STRING, - DialogueConstants.TOKEN_NUMBER, - DialogueConstants.TOKEN_VARIABLE - ] - - DialogueConstants.TOKEN_BRACE_OPEN: - expected_token_types = [ - DialogueConstants.TOKEN_STRING, - DialogueConstants.TOKEN_VARIABLE, - DialogueConstants.TOKEN_NUMBER, - DialogueConstants.TOKEN_BRACE_CLOSE - ] - - DialogueConstants.TOKEN_PARENS_CLOSE, \ - DialogueConstants.TOKEN_BRACE_CLOSE: - unexpected_token_types = [ - DialogueConstants.TOKEN_NOT, - DialogueConstants.TOKEN_ASSIGNMENT, - DialogueConstants.TOKEN_BOOL, - DialogueConstants.TOKEN_STRING, - DialogueConstants.TOKEN_NUMBER, - DialogueConstants.TOKEN_VARIABLE - ] - - DialogueConstants.TOKEN_COMPARISON, \ - DialogueConstants.TOKEN_OPERATOR, \ - DialogueConstants.TOKEN_COMMA, \ - DialogueConstants.TOKEN_DOT, \ - DialogueConstants.TOKEN_NOT, \ - DialogueConstants.TOKEN_AND_OR, \ - DialogueConstants.TOKEN_DICTIONARY_REFERENCE: - unexpected_token_types = [ - null, - DialogueConstants.TOKEN_COMMA, - DialogueConstants.TOKEN_COLON, - DialogueConstants.TOKEN_COMPARISON, - DialogueConstants.TOKEN_ASSIGNMENT, - DialogueConstants.TOKEN_OPERATOR, - DialogueConstants.TOKEN_AND_OR, - DialogueConstants.TOKEN_PARENS_CLOSE, - DialogueConstants.TOKEN_BRACE_CLOSE, - DialogueConstants.TOKEN_BRACKET_CLOSE, - DialogueConstants.TOKEN_DOT - ] - - DialogueConstants.TOKEN_COLON: - unexpected_token_types = [ - DialogueConstants.TOKEN_COMMA, - DialogueConstants.TOKEN_COLON, - DialogueConstants.TOKEN_COMPARISON, - DialogueConstants.TOKEN_ASSIGNMENT, - DialogueConstants.TOKEN_OPERATOR, - DialogueConstants.TOKEN_AND_OR, - DialogueConstants.TOKEN_PARENS_CLOSE, - DialogueConstants.TOKEN_BRACE_CLOSE, - DialogueConstants.TOKEN_BRACKET_CLOSE, - DialogueConstants.TOKEN_DOT - ] - - DialogueConstants.TOKEN_BOOL, \ - DialogueConstants.TOKEN_STRING, \ - DialogueConstants.TOKEN_NUMBER: - unexpected_token_types = [ - DialogueConstants.TOKEN_NOT, - DialogueConstants.TOKEN_ASSIGNMENT, - DialogueConstants.TOKEN_BOOL, - DialogueConstants.TOKEN_STRING, - DialogueConstants.TOKEN_NUMBER, - DialogueConstants.TOKEN_VARIABLE, - DialogueConstants.TOKEN_FUNCTION, - DialogueConstants.TOKEN_PARENS_OPEN, - DialogueConstants.TOKEN_BRACE_OPEN, - DialogueConstants.TOKEN_BRACKET_OPEN - ] - - DialogueConstants.TOKEN_VARIABLE: - unexpected_token_types = [ - DialogueConstants.TOKEN_NOT, - DialogueConstants.TOKEN_BOOL, - DialogueConstants.TOKEN_STRING, - DialogueConstants.TOKEN_NUMBER, - DialogueConstants.TOKEN_VARIABLE, - DialogueConstants.TOKEN_FUNCTION, - DialogueConstants.TOKEN_PARENS_OPEN, - DialogueConstants.TOKEN_BRACE_OPEN, - DialogueConstants.TOKEN_BRACKET_OPEN - ] - - if (expected_token_types.size() > 0 and not next_token.type in expected_token_types or unexpected_token_types.size() > 0 and next_token.type in unexpected_token_types): - match next_token.type: - null: - return DialogueConstants.ERR_UNEXPECTED_END_OF_EXPRESSION - - DialogueConstants.TOKEN_FUNCTION: - return DialogueConstants.ERR_UNEXPECTED_FUNCTION - - DialogueConstants.TOKEN_PARENS_OPEN, \ - DialogueConstants.TOKEN_PARENS_CLOSE: - return DialogueConstants.ERR_UNEXPECTED_BRACKET - - DialogueConstants.TOKEN_COMPARISON, \ - DialogueConstants.TOKEN_ASSIGNMENT, \ - DialogueConstants.TOKEN_OPERATOR, \ - DialogueConstants.TOKEN_NOT, \ - DialogueConstants.TOKEN_AND_OR: - return DialogueConstants.ERR_UNEXPECTED_OPERATOR - - DialogueConstants.TOKEN_COMMA: - return DialogueConstants.ERR_UNEXPECTED_COMMA - DialogueConstants.TOKEN_COLON: - return DialogueConstants.ERR_UNEXPECTED_COLON - DialogueConstants.TOKEN_DOT: - return DialogueConstants.ERR_UNEXPECTED_DOT - - DialogueConstants.TOKEN_BOOL: - return DialogueConstants.ERR_UNEXPECTED_BOOLEAN - DialogueConstants.TOKEN_STRING: - return DialogueConstants.ERR_UNEXPECTED_STRING - DialogueConstants.TOKEN_NUMBER: - return DialogueConstants.ERR_UNEXPECTED_NUMBER - DialogueConstants.TOKEN_VARIABLE: - return DialogueConstants.ERR_UNEXPECTED_VARIABLE - - return DialogueConstants.ERR_INVALID_EXPRESSION - - return OK - - -func tokens_to_list(tokens: Array[Dictionary]) -> Array[Array]: - var list: Array[Array] = [] - var current_item: Array[Dictionary] = [] - for token in tokens: - if token.type == DialogueConstants.TOKEN_COMMA: - list.append(current_item) - current_item = [] - else: - current_item.append(token) - - if current_item.size() > 0: - list.append(current_item) - - return list - - -func tokens_to_dictionary(tokens: Array[Dictionary]) -> Dictionary: - var dictionary = {} - for i in range(0, tokens.size()): - if tokens[i].type == DialogueConstants.TOKEN_COLON: - if tokens.size() == i + 2: - dictionary[tokens[i-1]] = tokens[i+1] - else: - dictionary[tokens[i-1]] = { type = DialogueConstants.TOKEN_GROUP, value = tokens.slice(i+1) } - - return dictionary - - -func find_match(input: String) -> Dictionary: - for key in TOKEN_DEFINITIONS.keys(): - var regex = TOKEN_DEFINITIONS.get(key) - var found = regex.search(input) - if found: - return { - type = key, - remaining_text = input.substr(found.strings[0].length()), - value = found.strings[0] - } - - return {} diff --git a/addons/dialogue_manager/components/resolved_line_data.gd b/addons/dialogue_manager/components/resolved_line_data.gd deleted file mode 100644 index 1073586..0000000 --- a/addons/dialogue_manager/components/resolved_line_data.gd +++ /dev/null @@ -1,15 +0,0 @@ -extends RefCounted - -var text: String = "" -var pauses: Dictionary = {} -var speeds: Dictionary = {} -var mutations: Array[Array] = [] -var time: String = "" - - -func _init(data: Dictionary) -> void: - text = data.text - pauses = data.pauses - speeds = data.speeds - mutations = data.mutations - time = data.time diff --git a/addons/dialogue_manager/components/resolved_tag_data.gd b/addons/dialogue_manager/components/resolved_tag_data.gd deleted file mode 100644 index 728cc42..0000000 --- a/addons/dialogue_manager/components/resolved_tag_data.gd +++ /dev/null @@ -1,10 +0,0 @@ -extends RefCounted - - -var tags: PackedStringArray = [] -var line_without_tags: String = "" - - -func _init(data: Dictionary) -> void: - tags = data.tags - line_without_tags = data.line_without_tags diff --git a/addons/dialogue_manager/components/search_and_replace.gd b/addons/dialogue_manager/components/search_and_replace.gd index e91574e..ceb4028 100644 --- a/addons/dialogue_manager/components/search_and_replace.gd +++ b/addons/dialogue_manager/components/search_and_replace.gd @@ -114,7 +114,7 @@ func find_in_line(line: String, text: String, from_index: int = 0) -> int: return line.findn(text, from_index) -### Signals +#region Signals func _on_text_edit_gui_input(event: InputEvent) -> void: @@ -177,14 +177,17 @@ func _on_replace_button_pressed() -> void: # Replace the selection at result index var r: Array = results[result_index] + code_edit.begin_complex_operation() var lines: PackedStringArray = code_edit.text.split("\n") var line: String = lines[r[0]] line = line.substr(0, r[1]) + replace_input.text + line.substr(r[1] + r[2]) lines[r[0]] = line code_edit.text = "\n".join(lines) - search(input.text, result_index) + code_edit.end_complex_operation() code_edit.text_changed.emit() + search(input.text, result_index) + func _on_replace_all_button_pressed() -> void: if match_case_button.button_pressed: @@ -210,3 +213,6 @@ func _on_input_focus_entered() -> void: func _on_match_case_check_box_toggled(button_pressed: bool) -> void: search() + + +#endregion diff --git a/addons/dialogue_manager/components/search_and_replace.gd.uid b/addons/dialogue_manager/components/search_and_replace.gd.uid new file mode 100644 index 0000000..66ec826 --- /dev/null +++ b/addons/dialogue_manager/components/search_and_replace.gd.uid @@ -0,0 +1 @@ +uid://cijsmjkq21cdq diff --git a/addons/dialogue_manager/components/search_and_replace.tscn b/addons/dialogue_manager/components/search_and_replace.tscn index 82dd60d..52721c4 100644 --- a/addons/dialogue_manager/components/search_and_replace.tscn +++ b/addons/dialogue_manager/components/search_and_replace.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://gr8nakpbrhby"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/search_and_replace.gd" id="1_8oj1f"] +[ext_resource type="Script" uid="uid://cijsmjkq21cdq" path="res://addons/dialogue_manager/components/search_and_replace.gd" id="1_8oj1f"] [node name="SearchAndReplace" type="VBoxContainer"] visible = false diff --git a/addons/dialogue_manager/components/title_list.gd b/addons/dialogue_manager/components/title_list.gd index ee7cd13..67cdcf0 100644 --- a/addons/dialogue_manager/components/title_list.gd +++ b/addons/dialogue_manager/components/title_list.gd @@ -48,6 +48,8 @@ func apply_filter() -> void: func apply_theme() -> void: if is_instance_valid(filter_edit): filter_edit.right_icon = get_theme_icon("Search", "EditorIcons") + if is_instance_valid(list): + list.add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Panel")) ### Signals diff --git a/addons/dialogue_manager/components/title_list.gd.uid b/addons/dialogue_manager/components/title_list.gd.uid new file mode 100644 index 0000000..325ca61 --- /dev/null +++ b/addons/dialogue_manager/components/title_list.gd.uid @@ -0,0 +1 @@ +uid://d0k2wndjj0ifm diff --git a/addons/dialogue_manager/components/title_list.tscn b/addons/dialogue_manager/components/title_list.tscn index 6273122..ac2b983 100644 --- a/addons/dialogue_manager/components/title_list.tscn +++ b/addons/dialogue_manager/components/title_list.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://ctns6ouwwd68i"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/title_list.gd" id="1_5qqmd"] +[ext_resource type="Script" uid="uid://d0k2wndjj0ifm" path="res://addons/dialogue_manager/components/title_list.gd" id="1_5qqmd"] [node name="TitleList" type="VBoxContainer"] anchors_preset = 15 diff --git a/addons/dialogue_manager/components/update_button.gd b/addons/dialogue_manager/components/update_button.gd index 2f77c63..cf3f1b9 100644 --- a/addons/dialogue_manager/components/update_button.gd +++ b/addons/dialogue_manager/components/update_button.gd @@ -86,9 +86,9 @@ func _on_update_button_pressed() -> void: if needs_reload: var will_refresh = on_before_refresh.call() if will_refresh: - Engine.get_meta("DialogueManagerPlugin").get_editor_interface().restart_editor(true) + EditorInterface.restart_editor(true) else: - var scale: float = Engine.get_meta("DialogueManagerPlugin").get_editor_interface().get_editor_scale() + var scale: float = EditorInterface.get_editor_scale() download_dialog.min_size = Vector2(300, 250) * scale download_dialog.popup_centered() @@ -117,7 +117,7 @@ func _on_download_update_panel_failed() -> void: func _on_needs_reload_dialog_confirmed() -> void: - Engine.get_meta("DialogueManagerPlugin").get_editor_interface().restart_editor(true) + EditorInterface.restart_editor(true) func _on_timer_timeout() -> void: diff --git a/addons/dialogue_manager/components/update_button.gd.uid b/addons/dialogue_manager/components/update_button.gd.uid new file mode 100644 index 0000000..9981132 --- /dev/null +++ b/addons/dialogue_manager/components/update_button.gd.uid @@ -0,0 +1 @@ +uid://cr1tt12dh5ecr diff --git a/addons/dialogue_manager/components/update_button.tscn b/addons/dialogue_manager/components/update_button.tscn index 533a94e..6cff347 100644 --- a/addons/dialogue_manager/components/update_button.tscn +++ b/addons/dialogue_manager/components/update_button.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://co8yl23idiwbi"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/update_button.gd" id="1_d2tpb"] +[ext_resource type="Script" uid="uid://cr1tt12dh5ecr" path="res://addons/dialogue_manager/components/update_button.gd" id="1_d2tpb"] [ext_resource type="PackedScene" uid="uid://qdxrxv3c3hxk" path="res://addons/dialogue_manager/components/download_update_panel.tscn" id="2_iwm7r"] [node name="UpdateButton" type="Button"] diff --git a/addons/dialogue_manager/constants.gd b/addons/dialogue_manager/constants.gd index 5913aa1..aec6f12 100644 --- a/addons/dialogue_manager/constants.gd +++ b/addons/dialogue_manager/constants.gd @@ -1,9 +1,23 @@ -extends Node +class_name DMConstants extends RefCounted const USER_CONFIG_PATH = "user://dialogue_manager_user_config.json" const CACHE_PATH = "user://dialogue_manager_cache.json" + +enum MutationBehaviour { + Wait, + DoNotWait, + Skip +} + +enum TranslationSource { + None, + Guess, + CSV, + PO +} + # Token types const TOKEN_FUNCTION = &"function" @@ -23,6 +37,7 @@ const TOKEN_COMPARISON = &"comparison" const TOKEN_ASSIGNMENT = &"assignment" const TOKEN_OPERATOR = &"operator" const TOKEN_COMMA = &"comma" +const TOKEN_NULL_COALESCE = &"null_coalesce" const TOKEN_DOT = &"dot" const TOKEN_CONDITION = &"condition" const TOKEN_BOOL = &"bool" @@ -33,21 +48,27 @@ const TOKEN_NUMBER = &"number" const TOKEN_VARIABLE = &"variable" const TOKEN_COMMENT = &"comment" +const TOKEN_VALUE = &"value" const TOKEN_ERROR = &"error" # Line types -const TYPE_UNKNOWN = &"unknown" +const TYPE_UNKNOWN = &"" +const TYPE_IMPORT = &"import" +const TYPE_USING = &"using" +const TYPE_COMMENT = &"comment" const TYPE_RESPONSE = &"response" const TYPE_TITLE = &"title" const TYPE_CONDITION = &"condition" +const TYPE_WHILE = &"while" +const TYPE_MATCH = &"match" +const TYPE_WHEN = &"when" const TYPE_MUTATION = &"mutation" const TYPE_GOTO = &"goto" const TYPE_DIALOGUE = &"dialogue" +const TYPE_RANDOM = &"random" const TYPE_ERROR = &"error" -const TYPE_ELSE = &"else" - # Line IDs const ID_NULL = &"" @@ -94,6 +115,18 @@ const ERR_UNEXPECTED_VARIABLE = 132 const ERR_INVALID_INDEX = 133 const ERR_UNEXPECTED_ASSIGNMENT = 134 const ERR_UNKNOWN_USING = 135 +const ERR_EXPECTED_WHEN_OR_ELSE = 136 +const ERR_ONLY_ONE_ELSE_ALLOWED = 137 +const ERR_WHEN_MUST_BELONG_TO_MATCH = 138 +const ERR_CONCURRENT_LINE_WITHOUT_ORIGIN = 139 +const ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES = 140 +const ERR_UNEXPECTED_SYNTAX_ON_NESTED_DIALOGUE_LINE = 141 +const ERR_NESTED_DIALOGUE_INVALID_JUMP = 142 +const ERR_MISSING_RESOURCE_FOR_AUTOSTART = 143 + + +static var _current_locale: String = "" +static var _current_translation: Translation ## Get the error message @@ -169,18 +202,33 @@ static func get_error_message(error: int) -> String: return translate(&"errors.unexpected_assignment") ERR_UNKNOWN_USING: return translate(&"errors.unknown_using") + ERR_EXPECTED_WHEN_OR_ELSE: + return translate(&"errors.expected_when_or_else") + ERR_ONLY_ONE_ELSE_ALLOWED: + return translate(&"errors.only_one_else_allowed") + ERR_WHEN_MUST_BELONG_TO_MATCH: + return translate(&"errors.when_must_belong_to_match") + ERR_CONCURRENT_LINE_WITHOUT_ORIGIN: + return translate(&"errors.concurrent_line_without_origin") + ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES: + return translate(&"errors.goto_not_allowed_on_concurrect_lines") + ERR_UNEXPECTED_SYNTAX_ON_NESTED_DIALOGUE_LINE: + return translate(&"errors.unexpected_syntax_on_nested_dialogue_line") + ERR_NESTED_DIALOGUE_INVALID_JUMP: + return translate(&"errors.err_nested_dialogue_invalid_jump") + ERR_MISSING_RESOURCE_FOR_AUTOSTART: + return translate(&"errors.missing_resource_for_autostart") return translate(&"errors.unknown") static func translate(string: String) -> String: - var temp_node = new() - var base_path = temp_node.get_script().resource_path.get_base_dir() - temp_node.free() - - var language: String = TranslationServer.get_tool_locale() - var translations_path: String = "%s/l10n/%s.po" % [base_path, language] - var fallback_translations_path: String = "%s/l10n/%s.po" % [base_path, TranslationServer.get_tool_locale().substr(0, 2)] - var en_translations_path: String = "%s/l10n/en.po" % base_path - var translations: Translation = load(translations_path if FileAccess.file_exists(translations_path) else (fallback_translations_path if FileAccess.file_exists(fallback_translations_path) else en_translations_path)) - return translations.get_message(string) + var locale: String = TranslationServer.get_tool_locale() + if _current_translation == null or _current_locale != locale: + var base_path: String = new().get_script().resource_path.get_base_dir() + var translation_path: String = "%s/l10n/%s.po" % [base_path, locale] + var fallback_translation_path: String = "%s/l10n/%s.po" % [base_path, locale.substr(0, 2)] + var en_translation_path: String = "%s/l10n/en.po" % base_path + _current_translation = load(translation_path if FileAccess.file_exists(translation_path) else (fallback_translation_path if FileAccess.file_exists(fallback_translation_path) else en_translation_path)) + _current_locale = locale + return _current_translation.get_message(string) diff --git a/addons/dialogue_manager/constants.gd.uid b/addons/dialogue_manager/constants.gd.uid new file mode 100644 index 0000000..f431917 --- /dev/null +++ b/addons/dialogue_manager/constants.gd.uid @@ -0,0 +1 @@ +uid://b1oarbmjtyesf diff --git a/addons/dialogue_manager/dialogue_label.gd b/addons/dialogue_manager/dialogue_label.gd index 3a6caeb..eb639b3 100644 --- a/addons/dialogue_manager/dialogue_label.gd +++ b/addons/dialogue_manager/dialogue_label.gd @@ -9,17 +9,20 @@ class_name DialogueLabel extends RichTextLabel ## Emitted for each letter typed out. signal spoke(letter: String, letter_index: int, speed: float) -## Emitted when typing paused for a `[wait]` -signal paused_typing(duration: float) - ## Emitted when the player skips the typing of dialogue. signal skipped_typing() +## Emitted when typing starts +signal started_typing() + ## Emitted when typing finishes. signal finished_typing() +## [Deprecated] No longer emitted. +signal paused_typing(duration: float) -# The action to press to skip typing. + +## The action to press to skip typing. @export var skip_action: StringName = &"ui_cancel" ## The speed with which the text types out. @@ -44,23 +47,23 @@ var _already_mutated_indices: PackedInt32Array = [] ## The current line of dialogue. var dialogue_line: - set(next_dialogue_line): - dialogue_line = next_dialogue_line - custom_minimum_size = Vector2.ZERO - text = "" - text = dialogue_line.text + set(value): + if value != dialogue_line: + dialogue_line = value + _update_text() get: return dialogue_line ## Whether the label is currently typing itself out. var is_typing: bool = false: set(value): - var is_finished: bool = is_typing != value and value == false - is_typing = value + var is_finished: bool = _is_typing != value and value == false and visible_characters == get_total_character_count() + _is_typing = value if is_finished: finished_typing.emit() get: - return is_typing + return _is_typing and not _is_awaiting_mutation +var _is_typing: bool = false var _last_wait_index: int = -1 var _last_mutation_index: int = -1 @@ -69,7 +72,7 @@ var _is_awaiting_mutation: bool = false func _process(delta: float) -> void: - if self.is_typing: + if _is_typing: # Type out text if visible_ratio < 1: # See if we are waiting @@ -81,21 +84,18 @@ func _process(delta: float) -> void: else: # Make sure any mutations at the end of the line get run _mutate_inline_mutations(get_total_character_count()) - self.is_typing = false + is_typing = false -func _unhandled_input(event: InputEvent) -> void: - # Note: this will no longer be reached if using Dialogue Manager > 2.32.2. To make skip handling - # simpler (so all of mouse/keyboard/joypad are together) it is now the responsibility of the - # dialogue balloon. - if self.is_typing and visible_ratio < 1 and InputMap.has_action(skip_action) and event.is_action_pressed(skip_action): - get_viewport().set_input_as_handled() - skip_typing() +## Sets the label's text from the current dialogue line. Override if you want +## to do something more interesting in your subclass. +func _update_text() -> void: + text = dialogue_line.text ## Start typing out the text func type_out() -> void: - text = dialogue_line.text + _update_text() visible_characters = 0 visible_ratio = 0 _waiting_seconds = 0 @@ -103,24 +103,25 @@ func type_out() -> void: _last_mutation_index = -1 _already_mutated_indices.clear() - self.is_typing = true + is_typing = true + started_typing.emit() # Allow typing listeners a chance to connect await get_tree().process_frame if get_total_character_count() == 0: - self.is_typing = false + is_typing = false elif seconds_per_step == 0: _mutate_remaining_mutations() visible_characters = get_total_character_count() - self.is_typing = false + is_typing = false ## Stop typing out the text and jump right to the end func skip_typing() -> void: _mutate_remaining_mutations() visible_characters = get_total_character_count() - self.is_typing = false + is_typing = false skipped_typing.emit() @@ -136,17 +137,11 @@ func _type_next(delta: float, seconds_needed: float) -> void: _mutate_inline_mutations(visible_characters) if _is_awaiting_mutation: return - var additional_waiting_seconds: float = _get_pause(visible_characters) - # Pause on characters like "." - if _should_auto_pause(): - additional_waiting_seconds += seconds_per_pause_step - - # Pause at literal [wait] directives - if _last_wait_index != visible_characters and additional_waiting_seconds > 0: + var waiting_seconds: float = seconds_per_pause_step if _should_auto_pause() else 0 + if _last_wait_index != visible_characters and waiting_seconds > 0: _last_wait_index = visible_characters - _waiting_seconds += additional_waiting_seconds - paused_typing.emit(_get_pause(visible_characters)) + _waiting_seconds += waiting_seconds else: visible_characters += 1 if visible_characters <= get_total_character_count(): @@ -159,11 +154,6 @@ func _type_next(delta: float, seconds_needed: float) -> void: _type_next(delta, seconds_needed) -# Get the pause for the current typing position if there is one -func _get_pause(at_index: int) -> float: - return dialogue_line.pauses.get(at_index, 0) - - # Get the speed for the current typing position func _get_speed(at_index: int) -> float: var speed: float = 1 @@ -187,12 +177,13 @@ func _mutate_inline_mutations(index: int) -> void: if inline_mutation[0] > index: return if inline_mutation[0] == index and not _already_mutated_indices.has(index): - _already_mutated_indices.append(index) _is_awaiting_mutation = true # The DialogueManager can't be referenced directly here so we need to get it by its path - await Engine.get_singleton("DialogueManager").mutate(inline_mutation[1], dialogue_line.extra_game_states, true) + await Engine.get_singleton("DialogueManager")._mutate(inline_mutation[1], dialogue_line.extra_game_states, true) _is_awaiting_mutation = false + _already_mutated_indices.append(index) + # Determine if the current autopause character at the cursor should qualify to pause typing. func _should_auto_pause() -> bool: diff --git a/addons/dialogue_manager/dialogue_label.gd.uid b/addons/dialogue_manager/dialogue_label.gd.uid new file mode 100644 index 0000000..6bf86b1 --- /dev/null +++ b/addons/dialogue_manager/dialogue_label.gd.uid @@ -0,0 +1 @@ +uid://g32um0mltv5d diff --git a/addons/dialogue_manager/dialogue_label.tscn b/addons/dialogue_manager/dialogue_label.tscn index df48b64..0095933 100644 --- a/addons/dialogue_manager/dialogue_label.tscn +++ b/addons/dialogue_manager/dialogue_label.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://ckvgyvclnwggo"] -[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_label.gd" id="1_cital"] +[ext_resource type="Script" uid="uid://g32um0mltv5d" path="res://addons/dialogue_manager/dialogue_label.gd" id="1_cital"] [node name="DialogueLabel" type="RichTextLabel"] anchors_preset = 10 diff --git a/addons/dialogue_manager/dialogue_line.gd b/addons/dialogue_manager/dialogue_line.gd index ced12f2..ac90fb1 100644 --- a/addons/dialogue_manager/dialogue_line.gd +++ b/addons/dialogue_manager/dialogue_line.gd @@ -2,14 +2,11 @@ class_name DialogueLine extends RefCounted -const _DialogueConstants = preload("./constants.gd") - - ## The ID of this line var id: String ## The internal type of this dialogue object. One of [code]TYPE_DIALOGUE[/code] or [code]TYPE_MUTATION[/code] -var type: String = _DialogueConstants.TYPE_DIALOGUE +var type: String = DMConstants.TYPE_DIALOGUE ## The next line ID after this line. var next_id: String = "" @@ -29,9 +26,6 @@ var text_replacements: Array[Dictionary] = [] ## The key to use for translating this line. var translation_key: String = "" -## A map for when and for how long to pause while typing out the dialogue text. -var pauses: Dictionary = {} - ## A map for speed changes when typing out the dialogue text. var speeds: Dictionary = {} @@ -41,6 +35,9 @@ var inline_mutations: Array[Array] = [] ## A list of responses attached to this line of dialogue. var responses: Array = [] +## A list of lines that are spoken simultaneously with this one. +var concurrent_lines: Array[DialogueLine] = [] + ## A list of any extra game states to check when resolving variables and mutations. var extra_game_states: Array = [] @@ -65,33 +62,43 @@ func _init(data: Dictionary = {}) -> void: extra_game_states = data.get("extra_game_states", []) match type: - _DialogueConstants.TYPE_DIALOGUE: + DMConstants.TYPE_DIALOGUE: character = data.character character_replacements = data.get("character_replacements", [] as Array[Dictionary]) text = data.text text_replacements = data.get("text_replacements", [] as Array[Dictionary]) translation_key = data.get("translation_key", data.text) - pauses = data.get("pauses", {}) speeds = data.get("speeds", {}) inline_mutations = data.get("inline_mutations", [] as Array[Array]) time = data.get("time", "") tags = data.get("tags", []) + concurrent_lines = data.get("concurrent_lines", [] as Array[DialogueLine]) - _DialogueConstants.TYPE_MUTATION: + DMConstants.TYPE_MUTATION: mutation = data.mutation func _to_string() -> String: match type: - _DialogueConstants.TYPE_DIALOGUE: + DMConstants.TYPE_DIALOGUE: return "<DialogueLine character=\"%s\" text=\"%s\">" % [character, text] - _DialogueConstants.TYPE_MUTATION: + DMConstants.TYPE_MUTATION: return "<DialogueLine mutation>" return "" +## Check if a dialogue line has a given tag. +func has_tag(tag_name: String) -> bool: + var wrapped: String = "%s=" % tag_name + for t in tags: + if t.begins_with(wrapped): + return true + return false + + +## Get the value of a tag if the tag is in the form of [code]tag=value[/code] func get_tag_value(tag_name: String) -> String: - var wrapped := "%s=" % tag_name + var wrapped: String = "%s=" % tag_name for t in tags: if t.begins_with(wrapped): return t.replace(wrapped, "").strip_edges() diff --git a/addons/dialogue_manager/dialogue_line.gd.uid b/addons/dialogue_manager/dialogue_line.gd.uid new file mode 100644 index 0000000..7ec7029 --- /dev/null +++ b/addons/dialogue_manager/dialogue_line.gd.uid @@ -0,0 +1 @@ +uid://rhuq0eyf8ar2 diff --git a/addons/dialogue_manager/dialogue_manager.gd b/addons/dialogue_manager/dialogue_manager.gd index 13cd3b6..34c21b8 100644 --- a/addons/dialogue_manager/dialogue_manager.gd +++ b/addons/dialogue_manager/dialogue_manager.gd @@ -1,15 +1,15 @@ extends Node - -const DialogueConstants = preload("./constants.gd") -const Builtins = preload("./utilities/builtins.gd") -const DialogueSettings = preload("./settings.gd") const DialogueResource = preload("./dialogue_resource.gd") const DialogueLine = preload("./dialogue_line.gd") const DialogueResponse = preload("./dialogue_response.gd") -const DialogueManagerParser = preload("./components/parser.gd") -const DialogueManagerParseResult = preload("./components/parse_result.gd") -const ResolvedLineData = preload("./components/resolved_line_data.gd") + +const DMConstants = preload("./constants.gd") +const Builtins = preload("./utilities/builtins.gd") +const DMSettings = preload("./settings.gd") +const DMCompiler = preload("./compiler/compiler.gd") +const DMCompilerResult = preload("./compiler/compiler_result.gd") +const DMResolvedLineData = preload("./compiler/resolved_line_data.gd") ## Emitted when a dialogue balloon is created and dialogue starts @@ -31,24 +31,16 @@ signal dialogue_ended(resource: DialogueResource) ## Used internally. signal bridge_get_next_dialogue_line_completed(line: DialogueLine) -## Used inernally +## Used internally. +signal bridge_get_line_completed(line: DialogueLine) + +## Used internally +signal bridge_dialogue_started(resource: DialogueResource) + +## Used internally signal bridge_mutated() -enum MutationBehaviour { - Wait, - DoNotWait, - Skip -} - -enum TranslationSource { - None, - Guess, - CSV, - PO -} - - ## The list of globals that dialogue can query var game_states: Array = [] @@ -59,7 +51,7 @@ var include_singletons: bool = true var include_classes: bool = true ## Manage translation behaviour -var translation_source: TranslationSource = TranslationSource.Guess +var translation_source: DMConstants.TranslationSource = DMConstants.TranslationSource.Guess ## Used to resolve the current scene. Override if your game manages the current scene itself. var get_current_scene: Callable = func(): @@ -74,6 +66,10 @@ var _autoloads: Dictionary = {} var _node_properties: Array = [] var _method_info_cache: Dictionary = {} +var _dotnet_dialogue_manager: RefCounted + +var _expression_parser: DMExpressionParser = DMExpressionParser.new() + func _ready() -> void: # Cache the known Node2D properties @@ -84,66 +80,242 @@ func _ready() -> void: temp_node.free() # Make the dialogue manager available as a singleton - if Engine.has_singleton("DialogueManager"): - Engine.unregister_singleton("DialogueManager") - Engine.register_singleton("DialogueManager", self) - - # Connect up the C# signals if need be - if DialogueSettings.check_for_dotnet_solution(): - _get_dotnet_dialogue_manager().Prepare() + if not Engine.has_singleton("DialogueManager"): + Engine.register_singleton("DialogueManager", self) ## Step through lines and run any mutations until we either hit some dialogue or the end of the conversation -func get_next_dialogue_line(resource: DialogueResource, key: String = "", extra_game_states: Array = [], mutation_behaviour: MutationBehaviour = MutationBehaviour.Wait) -> DialogueLine: +func get_next_dialogue_line(resource: DialogueResource, key: String = "", extra_game_states: Array = [], mutation_behaviour: DMConstants.MutationBehaviour = DMConstants.MutationBehaviour.Wait) -> DialogueLine: + var line = await _get_next_dialogue_line(resource, key, extra_game_states, mutation_behaviour) + if line == null: + # End the conversation + dialogue_ended.emit(resource) + return line + + +# Internal line getter. +func _get_next_dialogue_line(resource: DialogueResource, key: String = "", extra_game_states: Array = [], mutation_behaviour: DMConstants.MutationBehaviour = DMConstants.MutationBehaviour.Wait) -> DialogueLine: # You have to provide a valid dialogue resource if resource == null: - assert(false, DialogueConstants.translate(&"runtime.no_resource")) + assert(false, DMConstants.translate(&"runtime.no_resource")) if resource.lines.size() == 0: - assert(false, DialogueConstants.translate(&"runtime.no_content").format({ file_path = resource.resource_path })) + assert(false, DMConstants.translate(&"runtime.no_content").format({ file_path = resource.resource_path })) # Inject any "using" states into the game_states for state_name in resource.using_states: var autoload = Engine.get_main_loop().root.get_node_or_null(state_name) if autoload == null: - printerr(DialogueConstants.translate(&"runtime.unknown_autoload").format({ autoload = state_name })) + printerr(DMConstants.translate(&"runtime.unknown_autoload").format({ autoload = state_name })) else: extra_game_states = [autoload] + extra_game_states + # Inject "self" into the extra game states. + extra_game_states = [{ "self": resource }] + extra_game_states + # Get the line data var dialogue: DialogueLine = await get_line(resource, key, extra_game_states) # If our dialogue is nothing then we hit the end - if not is_valid(dialogue): - (func(): dialogue_ended.emit(resource)).call_deferred() + if not _is_valid(dialogue): return null # Run the mutation if it is one - if dialogue.type == DialogueConstants.TYPE_MUTATION: - var actual_next_id: String = dialogue.next_id.split(",")[0] + if dialogue.type == DMConstants.TYPE_MUTATION: + var actual_next_id: String = dialogue.next_id.split("|")[0] match mutation_behaviour: - MutationBehaviour.Wait: - await mutate(dialogue.mutation, extra_game_states) - MutationBehaviour.DoNotWait: - mutate(dialogue.mutation, extra_game_states) - MutationBehaviour.Skip: + DMConstants.MutationBehaviour.Wait: + await _mutate(dialogue.mutation, extra_game_states) + DMConstants.MutationBehaviour.DoNotWait: + _mutate(dialogue.mutation, extra_game_states) + DMConstants.MutationBehaviour.Skip: pass - if actual_next_id in [DialogueConstants.ID_END_CONVERSATION, DialogueConstants.ID_NULL, null]: - # End the conversation - (func(): dialogue_ended.emit(resource)).call_deferred() + if actual_next_id in [DMConstants.ID_END_CONVERSATION, DMConstants.ID_NULL, null]: return null else: - return await get_next_dialogue_line(resource, dialogue.next_id, extra_game_states, mutation_behaviour) + return await _get_next_dialogue_line(resource, dialogue.next_id, extra_game_states, mutation_behaviour) else: got_dialogue.emit(dialogue) return dialogue -func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> ResolvedLineData: +## Get a line by its ID +func get_line(resource: DialogueResource, key: String, extra_game_states: Array) -> DialogueLine: + key = key.strip_edges() + + # See if we were given a stack instead of just the one key + var stack: Array = key.split("|") + key = stack.pop_front() + var id_trail: String = "" if stack.size() == 0 else "|" + "|".join(stack) + + # Key is blank so just use the first title (or start of file) + if key == null or key == "": + if resource.first_title.is_empty(): + key = resource.lines.keys()[0] + else: + key = resource.first_title + + # See if we just ended the conversation + if key in [DMConstants.ID_END, DMConstants.ID_NULL, null]: + if stack.size() > 0: + return await get_line(resource, "|".join(stack), extra_game_states) + else: + return null + elif key == DMConstants.ID_END_CONVERSATION: + return null + + # See if it is a title + if key.begins_with("~ "): + key = key.substr(2) + if resource.titles.has(key): + key = resource.titles.get(key) + + if key in resource.titles.values(): + passed_title.emit(resource.titles.find_key(key)) + + if not resource.lines.has(key): + assert(false, DMConstants.translate(&"errors.key_not_found").format({ key = key })) + + var data: Dictionary = resource.lines.get(key) + + # If next_id is an expression we need to resolve it. + if data.has(&"next_id_expression"): + data.next_id = await _resolve(data.next_id_expression, extra_game_states) + + # This title key points to another title key so we should jump there instead + if data.type == DMConstants.TYPE_TITLE and data.next_id in resource.titles.values(): + return await get_line(resource, data.next_id + id_trail, extra_game_states) + + # Handle match statements + if data.type == DMConstants.TYPE_MATCH: + var value = await _resolve_condition_value(data, extra_game_states) + var else_cases: Array[Dictionary] = data.cases.filter(func(s): return s.has("is_else")) + var else_case: Dictionary = {} if else_cases.size() == 0 else else_cases.front() + var next_id: String = "" + for case in data.cases: + if case == else_case: + continue + elif await _check_case_value(value, case, extra_game_states): + next_id = case.next_id + break + # Nothing matched so check for else case + if next_id == "": + if not else_case.is_empty(): + next_id = else_case.next_id + else: + next_id = data.next_id_after + return await get_line(resource, next_id + id_trail, extra_game_states) + + # Check for weighted random lines. + if data.has(&"siblings"): + # Only count siblings that pass their condition (if they have one). + var successful_siblings: Array = data.siblings.filter(func(sibling): return not sibling.has("condition") or await _check_condition(sibling, extra_game_states)) + # If there are no siblings that pass their conditions then just skip over them all. + if successful_siblings.size() == 0: + return await get_line(resource, data.next_id + id_trail, extra_game_states) + # Otherwise, pick a random one. + var target_weight: float = randf_range(0, successful_siblings.reduce(func(total, sibling): return total + sibling.weight, 0)) + var cummulative_weight: float = 0 + for sibling in successful_siblings: + if target_weight < cummulative_weight + sibling.weight: + data = resource.lines.get(sibling.id) + break + else: + cummulative_weight += sibling.weight + + # If this line is blank and it's the last line then check for returning snippets. + if data.type in [DMConstants.TYPE_COMMENT, DMConstants.TYPE_UNKNOWN]: + if data.next_id in [DMConstants.ID_END, DMConstants.ID_NULL, null]: + if stack.size() > 0: + return await get_line(resource, "|".join(stack), extra_game_states) + else: + return null + else: + return await get_line(resource, data.next_id + id_trail, extra_game_states) + + # If the line is a random block then go to the start of the block. + elif data.type == DMConstants.TYPE_RANDOM: + return await get_line(resource, data.next_id + id_trail, extra_game_states) + + # Check conditions. + elif data.type in [DMConstants.TYPE_CONDITION, DMConstants.TYPE_WHILE]: + # "else" will have no actual condition. + if await _check_condition(data, extra_game_states): + return await get_line(resource, data.next_id + id_trail, extra_game_states) + elif data.has("next_sibling_id") and not data.next_sibling_id.is_empty(): + return await get_line(resource, data.next_sibling_id + id_trail, extra_game_states) + else: + return await get_line(resource, data.next_id_after + id_trail, extra_game_states) + + # Evaluate jumps. + elif data.type == DMConstants.TYPE_GOTO: + if data.is_snippet and not id_trail.begins_with("|" + data.next_id_after): + id_trail = "|" + data.next_id_after + id_trail + return await get_line(resource, data.next_id + id_trail, extra_game_states) + + elif data.type == DMConstants.TYPE_DIALOGUE: + if not data.has(&"id"): + data.id = key + + # Set up a line object. + var line: DialogueLine = await create_dialogue_line(data, extra_game_states) + + # If the jump point somehow has no content then just end. + if not line: return null + + # Find any simultaneously said lines. + if data.has(&"concurrent_lines"): + # If the list includes this line then it isn't the origin line so ignore it. + if not data.concurrent_lines.has(data.id): + # Resolve IDs to their actual lines. + for line_id: String in data.concurrent_lines: + line.concurrent_lines.append(await get_line(resource, line_id, extra_game_states)) + + # If we are the first of a list of responses then get the other ones. + if data.type == DMConstants.TYPE_RESPONSE: + # Note: For some reason C# has occasional issues with using the responses property directly + # so instead we use set and get here. + line.set(&"responses", await _get_responses(data.get(&"responses", []), resource, id_trail, extra_game_states)) + return line + + # Inject the next node's responses if they have any. + if resource.lines.has(line.next_id): + var next_line: Dictionary = resource.lines.get(line.next_id) + + # If the response line is marked as a title then make sure to emit the passed_title signal. + if line.next_id in resource.titles.values(): + passed_title.emit(resource.titles.find_key(line.next_id)) + + # If the responses come from a snippet then we need to come back here afterwards. + if next_line.type == DMConstants.TYPE_GOTO and next_line.is_snippet and not id_trail.begins_with("|" + next_line.next_id_after): + id_trail = "|" + next_line.next_id_after + id_trail + + # If the next line is a title then check where it points to see if that is a set of responses. + while [DMConstants.TYPE_TITLE, DMConstants.TYPE_GOTO].has(next_line.type) and resource.lines.has(next_line.next_id): + next_line = resource.lines.get(next_line.next_id) + + if next_line != null and next_line.type == DMConstants.TYPE_RESPONSE: + # Note: For some reason C# has occasional issues with using the responses property directly + # so instead we use set and get here. + line.set(&"responses", await _get_responses(next_line.get(&"responses", []), resource, id_trail, extra_game_states)) + + line.next_id = "|".join(stack) if line.next_id == DMConstants.ID_NULL else line.next_id + id_trail + return line + +## Replace any variables, etc in the text. +func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> DMResolvedLineData: var text: String = translate(data) # Resolve variables - for replacement in data.text_replacements: - var value = await resolve(replacement.expression.duplicate(true), extra_game_states) + var text_replacements: Array[Dictionary] = data.get(&"text_replacements", [] as Array[Dictionary]) + if text_replacements.size() == 0 and "{{" in text: + # This line is translated but has expressions that didn't exist in the base text. + text_replacements = _expression_parser.extract_replacements(text, 0) + + for replacement in text_replacements: + if replacement.has("error"): + assert(false, "%s \"%s\"" % [DMConstants.get_error_message(replacement.get("error")), text]) + + var value = await _resolve(replacement.expression.duplicate(true), extra_game_states) var index: int = text.find(replacement.value_in_text) if index == -1: # The replacement wasn't found but maybe the regular quotes have been replaced @@ -152,20 +324,20 @@ func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> if index > -1: text = text.substr(0, index) + str(value) + text.substr(index + replacement.value_in_text.length()) - var parser: DialogueManagerParser = DialogueManagerParser.new() + var compilation: DMCompilation = DMCompilation.new() # Resolve random groups - for found in parser.INLINE_RANDOM_REGEX.search_all(text): + for found in compilation.regex.INLINE_RANDOM_REGEX.search_all(text): var options = found.get_string(&"options").split(&"|") text = text.replace(&"[[%s]]" % found.get_string(&"options"), options[randi_range(0, options.size() - 1)]) # Do a pass on the markers to find any conditionals - var markers: ResolvedLineData = parser.extract_markers(text) + var markers: DMResolvedLineData = DMResolvedLineData.new(text) # Resolve any conditionals and update marker positions as needed - if data.type == DialogueConstants.TYPE_DIALOGUE: + if data.type in [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE]: var resolved_text: String = markers.text - var conditionals: Array[RegExMatch] = parser.INLINE_CONDITIONALS_REGEX.search_all(resolved_text) + var conditionals: Array[RegExMatch] = compilation.regex.INLINE_CONDITIONALS_REGEX.search_all(resolved_text) var replacements: Array = [] for conditional in conditionals: var condition_raw: String = conditional.strings[conditional.names.condition] @@ -175,9 +347,9 @@ func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> var bits = body.split(&"[else]") body = bits[0] body_else = bits[1] - var condition: Dictionary = parser.extract_condition("if " + condition_raw, false, 0) + var condition: Dictionary = compilation.extract_condition("if " + condition_raw, false, 0) # If the condition fails then use the else of "" - if not await check_condition({ condition = condition }, extra_game_states): + if not await _check_condition({ condition = condition }, extra_game_states): body = body_else replacements.append({ start = conditional.get_start(), @@ -186,12 +358,12 @@ func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> body = body }) - for i in range(replacements.size() -1, -1, -1): + for i in range(replacements.size() - 1, -1, -1): var r: Dictionary = replacements[i] resolved_text = resolved_text.substr(0, r.start) + r.body + resolved_text.substr(r.end, 9999) # Move any other markers now that the text has changed var offset: int = r.end - r.start - r.body.length() - for key in [&"pauses", &"speeds", &"time"]: + for key in [&"speeds", &"time"]: if markers.get(key) == null: continue var marker = markers.get(key) var next_marker: Dictionary = {} @@ -213,8 +385,6 @@ func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> markers.text = resolved_text - parser.free() - return markers @@ -224,7 +394,7 @@ func get_resolved_character(data: Dictionary, extra_game_states: Array = []) -> # Resolve variables for replacement in data.get(&"character_replacements", []): - var value = await resolve(replacement.expression.duplicate(true), extra_game_states) + var value = await _resolve(replacement.expression.duplicate(true), extra_game_states) var index: int = character.find(replacement.value_in_text) if index > -1: character = character.substr(0, index) + str(value) + character.substr(index + replacement.value_in_text.length()) @@ -241,48 +411,43 @@ func get_resolved_character(data: Dictionary, extra_game_states: Array = []) -> ## Generate a dialogue resource on the fly from some text func create_resource_from_text(text: String) -> Resource: - var parser: DialogueManagerParser = DialogueManagerParser.new() - parser.parse(text, "") - var results: DialogueManagerParseResult = parser.get_data() - var errors: Array[Dictionary] = parser.get_errors() - parser.free() + var result: DMCompilerResult = DMCompiler.compile_string(text, "") - if errors.size() > 0: - printerr(DialogueConstants.translate(&"runtime.errors").format({ count = errors.size() })) - for error in errors: - printerr(DialogueConstants.translate(&"runtime.error_detail").format({ + if result.errors.size() > 0: + printerr(DMConstants.translate(&"runtime.errors").format({ count = result.errors.size() })) + for error in result.errors: + printerr(DMConstants.translate(&"runtime.error_detail").format({ line = error.line_number + 1, - message = DialogueConstants.get_error_message(error.error) + message = DMConstants.get_error_message(error.error) })) - assert(false, DialogueConstants.translate(&"runtime.errors_see_details").format({ count = errors.size() })) + assert(false, DMConstants.translate(&"runtime.errors_see_details").format({ count = result.errors.size() })) var resource: DialogueResource = DialogueResource.new() - resource.using_states = results.using_states - resource.titles = results.titles - resource.first_title = results.first_title - resource.character_names = results.character_names - resource.lines = results.lines + resource.using_states = result.using_states + resource.titles = result.titles + resource.first_title = result.first_title + resource.character_names = result.character_names + resource.lines = result.lines resource.raw_text = text return resource +#region Balloon helpers + + ## Show the example balloon func show_example_dialogue_balloon(resource: DialogueResource, title: String = "", extra_game_states: Array = []) -> CanvasLayer: var balloon: Node = load(_get_example_balloon_path()).instantiate() - get_current_scene.call().add_child(balloon) - balloon.start(resource, title, extra_game_states) - dialogue_started.emit(resource) - + _start_balloon.call_deferred(balloon, resource, title, extra_game_states) return balloon ## Show the configured dialogue balloon func show_dialogue_balloon(resource: DialogueResource, title: String = "", extra_game_states: Array = []) -> Node: - var balloon_path: String = DialogueSettings.get_setting(&"balloon_path", _get_example_balloon_path()) + var balloon_path: String = DMSettings.get_setting(DMSettings.BALLOON_PATH, _get_example_balloon_path()) if not ResourceLoader.exists(balloon_path): balloon_path = _get_example_balloon_path() - dialogue_started.emit(resource) return show_dialogue_balloon_scene(balloon_path, resource, title, extra_game_states) @@ -294,14 +459,35 @@ func show_dialogue_balloon_scene(balloon_scene, resource: DialogueResource, titl balloon_scene = balloon_scene.instantiate() var balloon: Node = balloon_scene - get_current_scene.call().add_child.call_deferred(balloon) + _start_balloon.call_deferred(balloon, resource, title, extra_game_states) + return balloon + + +## Resolve a static line ID to an actual line ID +func static_id_to_line_id(resource: DialogueResource, static_id: String) -> String: + var ids = static_id_to_line_ids(resource, static_id) + if ids.size() == 0: return "" + return ids[0] + + +## Resolve a static line ID to any actual line IDs that match +func static_id_to_line_ids(resource: DialogueResource, static_id: String) -> PackedStringArray: + return resource.lines.values().filter(func(l): return l.get(&"translation_key", "") == static_id).map(func(l): return l.id) + + +# Call "start" on the given balloon. +func _start_balloon(balloon: Node, resource: DialogueResource, title: String, extra_game_states: Array) -> void: + get_current_scene.call().add_child(balloon) + if balloon.has_method(&"start"): balloon.start(resource, title, extra_game_states) elif balloon.has_method(&"Start"): balloon.Start(resource, title, extra_game_states) else: - assert(false, DialogueConstants.translate(&"runtime.dialogue_balloon_missing_start_method")) - return balloon + assert(false, DMConstants.translate(&"runtime.dialogue_balloon_missing_start_method")) + + dialogue_started.emit(resource) + bridge_dialogue_started.emit(resource) # Get the path to the example balloon @@ -311,147 +497,70 @@ func _get_example_balloon_path() -> String: return get_script().resource_path.get_base_dir() + balloon_path -### Dotnet bridge +#endregion + +#region dotnet bridge -func _get_dotnet_dialogue_manager() -> Node: - return load(get_script().resource_path.get_base_dir() + "/DialogueManager.cs").new() +func _get_dotnet_dialogue_manager() -> RefCounted: + if not is_instance_valid(_dotnet_dialogue_manager): + _dotnet_dialogue_manager = load(get_script().resource_path.get_base_dir() + "/DialogueManager.cs").new() + return _dotnet_dialogue_manager func _bridge_get_new_instance() -> Node: - return new() + # For some reason duplicating the node with its signals doesn't work so we have to copy them over manually + var instance = new() + for s: Dictionary in dialogue_started.get_connections(): + instance.dialogue_started.connect(s.callable) + for s: Dictionary in passed_title.get_connections(): + instance.passed_title.connect(s.callable) + for s: Dictionary in got_dialogue.get_connections(): + instance.got_dialogue.connect(s.callable) + for s: Dictionary in mutated.get_connections(): + instance.mutated.connect(s.callable) + for s: Dictionary in dialogue_ended.get_connections(): + instance.dialogue_ended.connect(s.callable) + instance.get_current_scene = get_current_scene + return instance -func _bridge_get_next_dialogue_line(resource: DialogueResource, key: String, extra_game_states: Array = []) -> void: +func _bridge_get_next_dialogue_line(resource: DialogueResource, key: String, extra_game_states: Array = [], mutation_behaviour: int = DMConstants.MutationBehaviour.Wait) -> void: # dotnet needs at least one await tick of the signal gets called too quickly await Engine.get_main_loop().process_frame - - var line = await get_next_dialogue_line(resource, key, extra_game_states) + var line = await _get_next_dialogue_line(resource, key, extra_game_states, mutation_behaviour) bridge_get_next_dialogue_line_completed.emit(line) + if line == null: + # End the conversation + dialogue_ended.emit(resource) + + +func _bridge_get_line(resource: DialogueResource, key: String, extra_game_states: Array = []) -> void: + # dotnet needs at least one await tick of the signal gets called too quickly + await Engine.get_main_loop().process_frame + var line = await get_line(resource, key, extra_game_states) + bridge_get_line_completed.emit(line) func _bridge_mutate(mutation: Dictionary, extra_game_states: Array, is_inline_mutation: bool = false) -> void: - await mutate(mutation, extra_game_states, is_inline_mutation) + await _mutate(mutation, extra_game_states, is_inline_mutation) bridge_mutated.emit() -### Helpers +func _bridge_get_error_message(error: int) -> String: + return DMConstants.get_error_message(error) -# Get a line by its ID -func get_line(resource: DialogueResource, key: String, extra_game_states: Array) -> DialogueLine: - key = key.strip_edges() +#endregion - # See if we were given a stack instead of just the one key - var stack: Array = key.split("|") - key = stack.pop_front() - var id_trail: String = "" if stack.size() == 0 else "|" + "|".join(stack) - - # Key is blank so just use the first title - if key == null or key == "": - key = resource.first_title - - # See if we just ended the conversation - if key in [DialogueConstants.ID_END, DialogueConstants.ID_NULL, null]: - if stack.size() > 0: - return await get_line(resource, "|".join(stack), extra_game_states) - else: - return null - elif key == DialogueConstants.ID_END_CONVERSATION: - return null - - # See if it is a title - if key.begins_with("~ "): - key = key.substr(2) - if resource.titles.has(key): - key = resource.titles.get(key) - - if key in resource.titles.values(): - passed_title.emit(resource.titles.find_key(key)) - - if not resource.lines.has(key): - assert(false, DialogueConstants.translate(&"errors.key_not_found").format({ key = key })) - - var data: Dictionary = resource.lines.get(key) - - # This title key points to another title key so we should jump there instead - if data.type == DialogueConstants.TYPE_TITLE and data.next_id in resource.titles.values(): - return await get_line(resource, data.next_id + id_trail, extra_game_states) - - # Check for weighted random lines - if data.has(&"siblings"): - # Only count siblings that pass their condition (if they have one) - var successful_siblings: Array = data.siblings.filter(func(sibling): return not sibling.has("condition") or await check_condition(sibling, extra_game_states)) - var target_weight: float = randf_range(0, successful_siblings.reduce(func(total, sibling): return total + sibling.weight, 0)) - var cummulative_weight: float = 0 - for sibling in successful_siblings: - if target_weight < cummulative_weight + sibling.weight: - data = resource.lines.get(sibling.id) - break - else: - cummulative_weight += sibling.weight - - # Check condtiions - if data.type == DialogueConstants.TYPE_CONDITION: - # "else" will have no actual condition - if await check_condition(data, extra_game_states): - return await get_line(resource, data.next_id + id_trail, extra_game_states) - else: - return await get_line(resource, data.next_conditional_id + id_trail, extra_game_states) - - # Evaluate jumps - elif data.type == DialogueConstants.TYPE_GOTO: - if data.is_snippet and not id_trail.begins_with("|" + data.next_id_after): - id_trail = "|" + data.next_id_after + id_trail - return await get_line(resource, data.next_id + id_trail, extra_game_states) - - elif data.type == DialogueConstants.TYPE_DIALOGUE: - if not data.has(&"id"): - data.id = key - - # Set up a line object - var line: DialogueLine = await create_dialogue_line(data, extra_game_states) - - # If the jump point somehow has no content then just end - if not line: return null - - # If we are the first of a list of responses then get the other ones - if data.type == DialogueConstants.TYPE_RESPONSE: - # Note: For some reason C# has occasional issues with using the responses property directly - # so instead we use set and get here. - line.set(&"responses", await get_responses(data.get(&"responses", []), resource, id_trail, extra_game_states)) - return line - - # Inject the next node's responses if they have any - if resource.lines.has(line.next_id): - var next_line: Dictionary = resource.lines.get(line.next_id) - - # If the response line is marked as a title then make sure to emit the passed_title signal. - if line.next_id in resource.titles.values(): - passed_title.emit(resource.titles.find_key(line.next_id)) - - # If the responses come from a snippet then we need to come back here afterwards - if next_line.type == DialogueConstants.TYPE_GOTO and next_line.is_snippet and not id_trail.begins_with("|" + next_line.next_id_after): - id_trail = "|" + next_line.next_id_after + id_trail - - # If the next line is a title then check where it points to see if that is a set of responses. - while [DialogueConstants.TYPE_TITLE, DialogueConstants.TYPE_GOTO].has(next_line.type) and resource.lines.has(next_line.next_id): - next_line = resource.lines.get(next_line.next_id) - - if next_line != null and next_line.type == DialogueConstants.TYPE_RESPONSE: - # Note: For some reason C# has occasional issues with using the responses property directly - # so instead we use set and get here. - line.set(&"responses", await get_responses(next_line.get(&"responses", []), resource, id_trail, extra_game_states)) - - line.next_id = "|".join(stack) if line.next_id == DialogueConstants.ID_NULL else line.next_id + id_trail - return line +#region Internal helpers # Show a message or crash with error func show_error_for_missing_state_value(message: String, will_show: bool = true) -> void: if not will_show: return - if DialogueSettings.get_setting(&"ignore_missing_state_values", false): + if DMSettings.get_setting(DMSettings.IGNORE_MISSING_STATE_VALUES, false): push_error(message) elif will_show: # If you're here then you're missing a method or property in your game state. The error @@ -461,49 +570,50 @@ func show_error_for_missing_state_value(message: String, will_show: bool = true) # Translate a string func translate(data: Dictionary) -> String: - if translation_source == TranslationSource.None: + if TranslationServer.get_loaded_locales().size() == 0 or translation_source == DMConstants.TranslationSource.None: return data.text - if data.translation_key == "" or data.translation_key == data.text: + var translation_key: String = data.get(&"translation_key", data.text) + + if translation_key == "" or translation_key == data.text: return tr(data.text) else: # Line IDs work slightly differently depending on whether the translation came from a # CSV or a PO file. CSVs use the line ID (or the line itself) as the translatable string # whereas POs use the ID as context and the line itself as the translatable string. match translation_source: - TranslationSource.PO: - return tr(data.text, StringName(data.translation_key)) + DMConstants.TranslationSource.PO: + return tr(data.text, StringName(translation_key)) - TranslationSource.CSV: - return tr(data.translation_key) + DMConstants.TranslationSource.CSV: + return tr(translation_key) - TranslationSource.Guess: + DMConstants.TranslationSource.Guess: var translation_files: Array = ProjectSettings.get_setting(&"internationalization/locale/translations") if translation_files.filter(func(f: String): return f.get_extension() in [&"po", &"mo"]).size() > 0: # Assume PO - return tr(data.text, StringName(data.translation_key)) + return tr(data.text, StringName(translation_key)) else: # Assume CSV - return tr(data.translation_key) + return tr(translation_key) - return tr(data.translation_key) + return tr(translation_key) # Create a line of dialogue func create_dialogue_line(data: Dictionary, extra_game_states: Array) -> DialogueLine: match data.type: - DialogueConstants.TYPE_DIALOGUE: - var resolved_data: ResolvedLineData = await get_resolved_line_data(data, extra_game_states) + DMConstants.TYPE_DIALOGUE: + var resolved_data: DMResolvedLineData = await get_resolved_line_data(data, extra_game_states) return DialogueLine.new({ id = data.get(&"id", ""), - type = DialogueConstants.TYPE_DIALOGUE, + type = DMConstants.TYPE_DIALOGUE, next_id = data.next_id, character = await get_resolved_character(data, extra_game_states), - character_replacements = data.character_replacements, + character_replacements = data.get(&"character_replacements", [] as Array[Dictionary]), text = resolved_data.text, - text_replacements = data.text_replacements, - translation_key = data.translation_key, - pauses = resolved_data.pauses, + text_replacements = data.get(&"text_replacements", [] as Array[Dictionary]), + translation_key = data.get(&"translation_key", data.text), speeds = resolved_data.speeds, inline_mutations = resolved_data.mutations, time = resolved_data.time, @@ -511,19 +621,19 @@ func create_dialogue_line(data: Dictionary, extra_game_states: Array) -> Dialogu extra_game_states = extra_game_states }) - DialogueConstants.TYPE_RESPONSE: + DMConstants.TYPE_RESPONSE: return DialogueLine.new({ id = data.get(&"id", ""), - type = DialogueConstants.TYPE_RESPONSE, + type = DMConstants.TYPE_RESPONSE, next_id = data.next_id, tags = data.get(&"tags", []), extra_game_states = extra_game_states }) - DialogueConstants.TYPE_MUTATION: + DMConstants.TYPE_MUTATION: return DialogueLine.new({ id = data.get(&"id", ""), - type = DialogueConstants.TYPE_MUTATION, + type = DMConstants.TYPE_MUTATION, next_id = data.next_id, mutation = data.mutation, extra_game_states = extra_game_states @@ -534,23 +644,24 @@ func create_dialogue_line(data: Dictionary, extra_game_states: Array) -> Dialogu # Create a response func create_response(data: Dictionary, extra_game_states: Array) -> DialogueResponse: - var resolved_data: ResolvedLineData = await get_resolved_line_data(data, extra_game_states) + var resolved_data: DMResolvedLineData = await get_resolved_line_data(data, extra_game_states) return DialogueResponse.new({ id = data.get(&"id", ""), - type = DialogueConstants.TYPE_RESPONSE, + type = DMConstants.TYPE_RESPONSE, next_id = data.next_id, is_allowed = data.is_allowed, + condition_as_text = data.get(&"condition_as_text", ""), character = await get_resolved_character(data, extra_game_states), character_replacements = data.get(&"character_replacements", [] as Array[Dictionary]), text = resolved_data.text, - text_replacements = data.text_replacements, + text_replacements = data.get(&"text_replacements", [] as Array[Dictionary]), tags = data.get(&"tags", []), - translation_key = data.translation_key + translation_key = data.get(&"translation_key", data.text), }) # Get the current game states -func get_game_states(extra_game_states: Array) -> Array: +func _get_game_states(extra_game_states: Array) -> Array: if not _has_loaded_autoloads: _has_loaded_autoloads = true # Add any autoloads to a generic state so we can refer to them by name @@ -563,8 +674,8 @@ func get_game_states(extra_game_states: Array) -> Array: _autoloads[child.name] = child game_states = [_autoloads] # Add any other state shortcuts from settings - for node_name in DialogueSettings.get_setting(&"states", []): - var state: Node = Engine.get_main_loop().root.get_node_or_null(node_name) + for node_name in DMSettings.get_setting(DMSettings.STATE_AUTOLOAD_SHORTCUTS, ""): + var state: Node = Engine.get_main_loop().root.get_node_or_null(NodePath(node_name)) if state: game_states.append(state) @@ -577,75 +688,124 @@ func get_game_states(extra_game_states: Array) -> Array: # Check if a condition is met -func check_condition(data: Dictionary, extra_game_states: Array) -> bool: +func _check_condition(data: Dictionary, extra_game_states: Array) -> bool: + return bool(await _resolve_condition_value(data, extra_game_states)) + + +# Resolve a condition's expression value +func _resolve_condition_value(data: Dictionary, extra_game_states: Array) -> Variant: if data.get(&"condition", null) == null: return true if data.condition.is_empty(): return true - return await resolve(data.condition.expression.duplicate(true), extra_game_states) + return await _resolve(data.condition.expression.duplicate(true), extra_game_states) + + +# Check if a match value matches a case value +func _check_case_value(match_value: Variant, data: Dictionary, extra_game_states: Array) -> bool: + if data.get(&"condition", null) == null: return true + if data.condition.is_empty(): return true + + var expression: Array[Dictionary] = data.condition.expression.duplicate(true) + + # Check for multiple values + var expressions_to_check: Array = [] + var previous_comma_index: int = 0 + for i in range(0, expression.size()): + if expression[i].type == DMConstants.TOKEN_COMMA: + expressions_to_check.append(expression.slice(previous_comma_index, i)) + previous_comma_index = i + 1 + elif i == expression.size() - 1: + expressions_to_check.append(expression.slice(previous_comma_index)) + + for expression_to_check in expressions_to_check: + # If the when is a comparison when insert the match value as the first value to compare to + var already_compared: bool = false + if expression_to_check[0].type == DMConstants.TOKEN_COMPARISON: + expression_to_check.insert(0, { + type = DMConstants.TOKEN_VALUE, + value = match_value + }) + already_compared = true + + var resolved_value = await _resolve(expression_to_check, extra_game_states) + if already_compared: + if resolved_value: + return true + else: + if match_value == resolved_value: + return true + + return false + # Make a change to game state or run a method -func mutate(mutation: Dictionary, extra_game_states: Array, is_inline_mutation: bool = false) -> void: +func _mutate(mutation: Dictionary, extra_game_states: Array, is_inline_mutation: bool = false) -> void: var expression: Array[Dictionary] = mutation.expression # Handle built in mutations - if expression[0].type == DialogueConstants.TOKEN_FUNCTION and expression[0].function in [&"wait", &"debug"]: - var args: Array = await resolve_each(expression[0].value, extra_game_states) + if expression[0].type == DMConstants.TOKEN_FUNCTION and expression[0].function in [&"wait", &"Wait", &"debug", &"Debug"]: + var args: Array = await _resolve_each(expression[0].value, extra_game_states) match expression[0].function: - &"wait": - mutated.emit(mutation) - await Engine.get_main_loop().create_timer(float(args[0])).timeout + &"wait", &"Wait": + mutated.emit(mutation.merged({ is_inline = is_inline_mutation })) + if [TYPE_FLOAT, TYPE_INT].has(typeof(args[0])): + await Engine.get_main_loop().create_timer(float(args[0])).timeout + else: + var actions: PackedStringArray = PackedStringArray(args[0] if typeof(args[0]) == TYPE_ARRAY else [args[0]]) + await _wait_for(actions) return - &"debug": + &"debug", &"Debug": prints("Debug:", args) await Engine.get_main_loop().process_frame # Or pass through to the resolver else: - if not mutation_contains_assignment(mutation.expression) and not is_inline_mutation: - mutated.emit(mutation) + if not _mutation_contains_assignment(mutation.expression) and not is_inline_mutation: + mutated.emit(mutation.merged({ is_inline = is_inline_mutation })) if mutation.get("is_blocking", true): - await resolve(mutation.expression.duplicate(true), extra_game_states) + await _resolve(mutation.expression.duplicate(true), extra_game_states) return else: - resolve(mutation.expression.duplicate(true), extra_game_states) + _resolve(mutation.expression.duplicate(true), extra_game_states) # Wait one frame to give the dialogue handler a chance to yield await Engine.get_main_loop().process_frame -func mutation_contains_assignment(mutation: Array) -> bool: +# Wait for a given action +func _wait_for(actions: PackedStringArray) -> void: + var waiter = DMWaiter.new(actions) + add_child(waiter) + await waiter.waited + waiter.queue_free() + + +# Check if a mutation contains an assignment token. +func _mutation_contains_assignment(mutation: Array) -> bool: for token in mutation: - if token.type == DialogueConstants.TOKEN_ASSIGNMENT: + if token.type == DMConstants.TOKEN_ASSIGNMENT: return true return false -func resolve_each(array: Array, extra_game_states: Array) -> Array: - var results: Array = [] - for item in array: - results.append(await resolve(item.duplicate(true), extra_game_states)) - return results - - # Replace an array of line IDs with their response prompts -func get_responses(ids: Array, resource: DialogueResource, id_trail: String, extra_game_states: Array) -> Array[DialogueResponse]: +func _get_responses(ids: Array, resource: DialogueResource, id_trail: String, extra_game_states: Array) -> Array[DialogueResponse]: var responses: Array[DialogueResponse] = [] for id in ids: var data: Dictionary = resource.lines.get(id).duplicate(true) - data.is_allowed = await check_condition(data, extra_game_states) - if DialogueSettings.get_setting(&"include_all_responses", false) or data.is_allowed: - var response: DialogueResponse = await create_response(data, extra_game_states) - response.next_id += id_trail - responses.append(response) + data.is_allowed = await _check_condition(data, extra_game_states) + var response: DialogueResponse = await create_response(data, extra_game_states) + response.next_id += id_trail + responses.append(response) return responses # Get a value on the current scene or game state -func get_state_value(property: String, extra_game_states: Array): +func _get_state_value(property: String, extra_game_states: Array): # Special case for static primitive calls if property == "Color": return Color() @@ -660,13 +820,23 @@ func get_state_value(property: String, extra_game_states: Array): var expression = Expression.new() if expression.parse(property) != OK: - assert(false, DialogueConstants.translate(&"runtime.invalid_expression").format({ expression = property, error = expression.get_error_text() })) + assert(false, DMConstants.translate(&"runtime.invalid_expression").format({ expression = property, error = expression.get_error_text() })) - for state in get_game_states(extra_game_states): + # Warn about possible name collisions + _warn_about_state_name_collisions(property, extra_game_states) + + for state in _get_game_states(extra_game_states): if typeof(state) == TYPE_DICTIONARY: if state.has(property): return state.get(property) else: + # Try for a C# constant first + if state.get_script() \ + and state.get_script().resource_path.ends_with(".cs") \ + and _get_dotnet_dialogue_manager().ThingHasConstant(state, property): + return _get_dotnet_dialogue_manager().ResolveThingConstant(state, property) + + # Otherwise just let Godot try and resolve it. var result = expression.execute([], state, false) if not expression.has_execute_failed(): return result @@ -677,154 +847,224 @@ func get_state_value(property: String, extra_game_states: Array): if include_classes: for class_data in ProjectSettings.get_global_class_list(): if class_data.get(&"class") == property: - return load(class_data.path).new() + return load(class_data.path) - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.property_not_found").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.property_not_found").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) + + +# Print warnings for top-level state name collisions. +func _warn_about_state_name_collisions(target_key: String, extra_game_states: Array) -> void: + # Don't run the check if this is a release build + if not OS.is_debug_build(): return + # Also don't run if the setting is off + if not DMSettings.get_setting(DMSettings.WARN_ABOUT_METHOD_PROPERTY_OR_SIGNAL_NAME_CONFLICTS, false): return + + # Get the list of state shortcuts. + var state_shortcuts: Array = [] + for node_name in DMSettings.get_setting(DMSettings.STATE_AUTOLOAD_SHORTCUTS, ""): + var state: Node = Engine.get_main_loop().root.get_node_or_null(NodePath(node_name)) + if state: + state_shortcuts.append(state) + + # Check any top level names for a collision + var states_with_key: Array = [] + for state in extra_game_states + [get_current_scene.call()] + state_shortcuts: + if state is Dictionary: + if state.keys().has(target_key): + states_with_key.append("Dictionary") + else: + var script: Script = (state as Object).get_script() + if script == null: + continue + + for method in script.get_script_method_list(): + if method.name == target_key and not states_with_key.has(state.name): + states_with_key.append(state.name) + break + + for property in script.get_script_property_list(): + if property.name == target_key and not states_with_key.has(state.name): + states_with_key.append(state.name) + break + + for signal_info in script.get_script_signal_list(): + if signal_info.name == target_key and not states_with_key.has(state.name): + states_with_key.append(state.name) + break + + if states_with_key.size() > 1: + push_warning(DMConstants.translate(&"runtime.top_level_states_share_name").format({ states = ", ".join(states_with_key), key = target_key })) # Set a value on the current scene or game state -func set_state_value(property: String, value, extra_game_states: Array) -> void: - for state in get_game_states(extra_game_states): +func _set_state_value(property: String, value, extra_game_states: Array) -> void: + for state in _get_game_states(extra_game_states): if typeof(state) == TYPE_DICTIONARY: if state.has(property): state[property] = value return - elif thing_has_property(state, property): + elif _thing_has_property(state, property): state.set(property, value) return if property.to_snake_case() != property: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.property_not_found_missing_export").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.property_not_found_missing_export").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.property_not_found").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.property_not_found").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) # Get the list of state shortcut names func _get_state_shortcut_names(extra_game_states: Array) -> String: - var states = get_game_states(extra_game_states) + var states = _get_game_states(extra_game_states) states.erase(_autoloads) return ", ".join(states.map(func(s): return "\"%s\"" % (s.name if "name" in s else s))) -# Collapse any expressions -func resolve(tokens: Array, extra_game_states: Array): - # Handle groups first - for token in tokens: - if token.type == DialogueConstants.TOKEN_GROUP: - token["type"] = "value" - token["value"] = await resolve(token.value, extra_game_states) +# Resolve an array of expressions. +func _resolve_each(array: Array, extra_game_states: Array) -> Array: + var results: Array = [] + for item in array: + if not item[0].type in [DMConstants.TOKEN_BRACE_CLOSE, DMConstants.TOKEN_BRACKET_CLOSE, DMConstants.TOKEN_PARENS_CLOSE]: + results.append(await _resolve(item.duplicate(true), extra_game_states)) + return results - # Then variables/methods + +# Collapse any expressions +func _resolve(tokens: Array, extra_game_states: Array): var i: int = 0 var limit: int = 0 + + # Handle groups first + for token in tokens: + if token.type == DMConstants.TOKEN_GROUP: + token.type = DMConstants.TOKEN_VALUE + token.value = await _resolve(token.value, extra_game_states) + + # Then variables/methods + i = 0 + limit = 0 while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_FUNCTION: + if token.type == DMConstants.TOKEN_NULL_COALESCE: + var caller: Dictionary = tokens[i - 1] + if caller.value == null: + # If the caller is null then the method/property is also null + caller.type = DMConstants.TOKEN_VALUE + caller.value = null + tokens.remove_at(i + 1) + tokens.remove_at(i) + else: + token.type = DMConstants.TOKEN_DOT + + elif token.type == DMConstants.TOKEN_FUNCTION: var function_name: String = token.function - var args = await resolve_each(token.value, extra_game_states) - if tokens[i - 1].type == DialogueConstants.TOKEN_DOT: + var args = await _resolve_each(token.value, extra_game_states) + if tokens[i - 1].type == DMConstants.TOKEN_DOT: # If we are calling a deeper function then we need to collapse the # value into the thing we are calling the function on var caller: Dictionary = tokens[i - 2] if Builtins.is_supported(caller.value): - caller["type"] = "value" - caller["value"] = Builtins.resolve_method(caller.value, function_name, args) + caller.type = DMConstants.TOKEN_VALUE + caller.value = Builtins.resolve_method(caller.value, function_name, args) tokens.remove_at(i) - tokens.remove_at(i-1) + tokens.remove_at(i - 1) i -= 2 - elif thing_has_method(caller.value, function_name, args): - caller["type"] = "value" - caller["value"] = await resolve_thing_method(caller.value, function_name, args) + elif _thing_has_method(caller.value, function_name, args): + caller.type = DMConstants.TOKEN_VALUE + caller.value = await _resolve_thing_method(caller.value, function_name, args) tokens.remove_at(i) - tokens.remove_at(i-1) + tokens.remove_at(i - 1) i -= 2 else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.method_not_callable").format({ method = function_name, object = str(caller.value) })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.method_not_callable").format({ method = function_name, object = str(caller.value) })) else: var found: bool = false match function_name: &"str": - token["type"] = "value" - token["value"] = str(args[0]) + token.type = DMConstants.TOKEN_VALUE + token.value = str(args[0]) found = true &"Vector2": - token["type"] = "value" - token["value"] = Vector2(args[0], args[1]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector2(args[0], args[1]) found = true &"Vector2i": - token["type"] = "value" - token["value"] = Vector2i(args[0], args[1]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector2i(args[0], args[1]) found = true &"Vector3": - token["type"] = "value" - token["value"] = Vector3(args[0], args[1], args[2]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector3(args[0], args[1], args[2]) found = true &"Vector3i": - token["type"] = "value" - token["value"] = Vector3i(args[0], args[1], args[2]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector3i(args[0], args[1], args[2]) found = true &"Vector4": - token["type"] = "value" - token["value"] = Vector4(args[0], args[1], args[2], args[3]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector4(args[0], args[1], args[2], args[3]) found = true &"Vector4i": - token["type"] = "value" - token["value"] = Vector4i(args[0], args[1], args[2], args[3]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector4i(args[0], args[1], args[2], args[3]) found = true &"Quaternion": - token["type"] = "value" - token["value"] = Quaternion(args[0], args[1], args[2], args[3]) + token.type = DMConstants.TOKEN_VALUE + token.value = Quaternion(args[0], args[1], args[2], args[3]) found = true &"Callable": - token["type"] = "value" + token.type = DMConstants.TOKEN_VALUE match args.size(): 0: - token["value"] = Callable() + token.value = Callable() 1: - token["value"] = Callable(args[0]) + token.value = Callable(args[0]) 2: - token["value"] = Callable(args[0], args[1]) + token.value = Callable(args[0], args[1]) found = true &"Color": - token["type"] = "value" + token.type = DMConstants.TOKEN_VALUE match args.size(): 0: - token["value"] = Color() + token.value = Color() 1: - token["value"] = Color(args[0]) + token.value = Color(args[0]) 2: - token["value"] = Color(args[0], args[1]) + token.value = Color(args[0], args[1]) 3: - token["value"] = Color(args[0], args[1], args[2]) + token.value = Color(args[0], args[1], args[2]) 4: - token["value"] = Color(args[0], args[1], args[2], args[3]) + token.value = Color(args[0], args[1], args[2], args[3]) found = true - &"load": - token["type"] = "value" - token["value"] = load(args[0]) + &"load", &"Load": + token.type = DMConstants.TOKEN_VALUE + token.value = load(args[0]) found = true - &"emit": - token["type"] = "value" - token["value"] = resolve_signal(args, extra_game_states) + &"roll_dice", &"RollDice": + token.type = DMConstants.TOKEN_VALUE + token.value = randi_range(1, args[0]) found = true _: - for state in get_game_states(extra_game_states): - if thing_has_method(state, function_name, args): - token["type"] = "value" - token["value"] = await resolve_thing_method(state, function_name, args) + # Check for top level name conflicts + _warn_about_state_name_collisions(function_name, extra_game_states) + + for state in _get_game_states(extra_game_states): + if _thing_has_method(state, function_name, args): + token.type = DMConstants.TOKEN_VALUE + token.value = await _resolve_thing_method(state, function_name, args) found = true break - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.method_not_found").format({ + show_error_for_missing_state_value(DMConstants.translate(&"runtime.method_not_found").format({ method = args[0] if function_name in ["call", "call_deferred"] else function_name, states = _get_state_shortcut_names(extra_game_states) }), not found) - elif token.type == DialogueConstants.TOKEN_DICTIONARY_REFERENCE: + elif token.type == DMConstants.TOKEN_DICTIONARY_REFERENCE: var value - if i > 0 and tokens[i - 1].type == DialogueConstants.TOKEN_DOT: + if i > 0 and tokens[i - 1].type == DMConstants.TOKEN_DOT: # If we are deep referencing then we need to get the parent object. # `parent.value` is the actual object and `token.variable` is the name of # the property within it. @@ -836,123 +1076,126 @@ func resolve(tokens: Array, extra_game_states: Array): i -= 2 else: # Otherwise we can just get this variable as a normal state reference - value = get_state_value(token.variable, extra_game_states) + value = _get_state_value(token.variable, extra_game_states) - var index = await resolve(token.value, extra_game_states) + var index = await _resolve(token.value, extra_game_states) if typeof(value) == TYPE_DICTIONARY: - if tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + if tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # If the next token is an assignment then we need to leave this as a reference # so that it can be resolved once everything ahead of it has been resolved - token["type"] = "dictionary" - token["value"] = value - token["key"] = index + token.type = "dictionary" + token.value = value + token.key = index else: if value.has(index): - token["type"] = "value" - token["value"] = value[index] + token.type = DMConstants.TOKEN_VALUE + token.value = value[index] else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.key_not_found").format({ key = str(index), dictionary = token.variable })) - elif typeof(value) == TYPE_ARRAY: - if tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + show_error_for_missing_state_value(DMConstants.translate(&"runtime.key_not_found").format({ key = str(index), dictionary = token.variable })) + elif typeof(value) in [TYPE_ARRAY, TYPE_PACKED_STRING_ARRAY, TYPE_PACKED_INT32_ARRAY, TYPE_PACKED_INT64_ARRAY, TYPE_PACKED_BYTE_ARRAY, TYPE_PACKED_COLOR_ARRAY, TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY]: + if tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # If the next token is an assignment then we need to leave this as a reference # so that it can be resolved once everything ahead of it has been resolved - token["type"] = "array" - token["value"] = value - token["key"] = index + token.type = "array" + token.value = value + token.key = index else: if index >= 0 and index < value.size(): - token["type"] = "value" - token["value"] = value[index] + token.type = DMConstants.TOKEN_VALUE + token.value = value[index] else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = index, array = token.variable })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = index, array = token.variable })) - elif token.type == DialogueConstants.TOKEN_DICTIONARY_NESTED_REFERENCE: + elif token.type == DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE: var dictionary: Dictionary = tokens[i - 1] - var index = await resolve(token.value, extra_game_states) + var index = await _resolve(token.value, extra_game_states) var value = dictionary.value if typeof(value) == TYPE_DICTIONARY: - if tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + if tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # If the next token is an assignment then we need to leave this as a reference # so that it can be resolved once everything ahead of it has been resolved - dictionary["type"] = "dictionary" - dictionary["key"] = index - dictionary["value"] = value + dictionary.type = "dictionary" + dictionary.key = index + dictionary.value = value tokens.remove_at(i) i -= 1 else: if dictionary.value.has(index): - dictionary["value"] = value.get(index) + dictionary.value = value.get(index) tokens.remove_at(i) i -= 1 else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.key_not_found").format({ key = str(index), dictionary = value })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.key_not_found").format({ key = str(index), dictionary = value })) elif typeof(value) == TYPE_ARRAY: - if tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + if tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # If the next token is an assignment then we need to leave this as a reference # so that it can be resolved once everything ahead of it has been resolved - dictionary["type"] = "array" - dictionary["value"] = value - dictionary["key"] = index + dictionary.type = "array" + dictionary.value = value + dictionary.key = index tokens.remove_at(i) i -= 1 else: if index >= 0 and index < value.size(): - dictionary["value"] = value[index] + dictionary.value = value[index] tokens.remove_at(i) i -= 1 else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = index, array = value })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = index, array = value })) - elif token.type == DialogueConstants.TOKEN_ARRAY: - token["type"] = "value" - token["value"] = await resolve_each(token.value, extra_game_states) + elif token.type == DMConstants.TOKEN_ARRAY: + token.type = DMConstants.TOKEN_VALUE + token.value = await _resolve_each(token.value, extra_game_states) - elif token.type == DialogueConstants.TOKEN_DICTIONARY: - token["type"] = "value" + elif token.type == DMConstants.TOKEN_DICTIONARY: + token.type = DMConstants.TOKEN_VALUE var dictionary = {} for key in token.value.keys(): - var resolved_key = await resolve([key], extra_game_states) + var resolved_key = await _resolve([key], extra_game_states) var preresolved_value = token.value.get(key) if typeof(preresolved_value) != TYPE_ARRAY: preresolved_value = [preresolved_value] - var resolved_value = await resolve(preresolved_value, extra_game_states) + var resolved_value = await _resolve(preresolved_value, extra_game_states) dictionary[resolved_key] = resolved_value - token["value"] = dictionary + token.value = dictionary - elif token.type == DialogueConstants.TOKEN_VARIABLE or token.type == DialogueConstants.TOKEN_NUMBER: + elif token.type == DMConstants.TOKEN_VARIABLE or token.type == DMConstants.TOKEN_NUMBER: if str(token.value) == "null": - token["type"] = "value" - token["value"] = null - elif tokens[i - 1].type == DialogueConstants.TOKEN_DOT: + token.type = DMConstants.TOKEN_VALUE + token.value = null + elif str(token.value) == "self": + token.type = DMConstants.TOKEN_VALUE + token.value = extra_game_states[0].self + elif tokens[i - 1].type == DMConstants.TOKEN_DOT: var caller: Dictionary = tokens[i - 2] var property = token.value - if tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + if tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # If the next token is an assignment then we need to leave this as a reference # so that it can be resolved once everything ahead of it has been resolved - caller["type"] = "property" - caller["property"] = property + caller.type = "property" + caller.property = property else: # If we are requesting a deeper property then we need to collapse the # value into the thing we are referencing from - caller["type"] = "value" + caller.type = DMConstants.TOKEN_VALUE if Builtins.is_supported(caller.value): - caller["value"] = Builtins.resolve_property(caller.value, property) + caller.value = Builtins.resolve_property(caller.value, property) else: - caller["value"] = caller.value.get(property) + caller.value = caller.value.get(property) tokens.remove_at(i) - tokens.remove_at(i-1) + tokens.remove_at(i - 1) i -= 2 - elif tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + elif tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # It's a normal variable but we will be assigning to it so don't resolve # it until everything after it has been resolved - token["type"] = "variable" + token.type = "variable" else: - if token.type == DialogueConstants.TOKEN_NUMBER: - token["type"] = "value" - token["value"] = token.value + if token.type == DMConstants.TOKEN_NUMBER: + token.type = DMConstants.TOKEN_VALUE + token.value = token.value else: - token["type"] = "value" - token["value"] = get_state_value(str(token.value), extra_game_states) + token.type = DMConstants.TOKEN_VALUE + token.value = _get_state_value(str(token.value), extra_game_states) i += 1 @@ -962,16 +1205,16 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_OPERATOR and token.value in ["*", "/", "%"]: - token["type"] = "value" - token["value"] = apply_operation(token.value, tokens[i-1].value, tokens[i+1].value) - tokens.remove_at(i+1) - tokens.remove_at(i-1) + if token.type == DMConstants.TOKEN_OPERATOR and token.value in ["*", "/", "%"]: + token.type = DMConstants.TOKEN_VALUE + token.value = _apply_operation(token.value, tokens[i - 1].value, tokens[i + 1].value) + tokens.remove_at(i + 1) + tokens.remove_at(i - 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) # Then addition and subtraction i = 0 @@ -979,16 +1222,16 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_OPERATOR and token.value in ["+", "-"]: - token["type"] = "value" - token["value"] = apply_operation(token.value, tokens[i-1].value, tokens[i+1].value) - tokens.remove_at(i+1) - tokens.remove_at(i-1) + if token.type == DMConstants.TOKEN_OPERATOR and token.value in ["+", "-"]: + token.type = DMConstants.TOKEN_VALUE + token.value = _apply_operation(token.value, tokens[i - 1].value, tokens[i + 1].value) + tokens.remove_at(i + 1) + tokens.remove_at(i - 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) # Then negations i = 0 @@ -996,15 +1239,15 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_NOT: - token["type"] = "value" - token["value"] = not tokens[i+1].value - tokens.remove_at(i+1) + if token.type == DMConstants.TOKEN_NOT: + token.type = DMConstants.TOKEN_VALUE + token.value = not tokens[i + 1].value + tokens.remove_at(i + 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) # Then comparisons i = 0 @@ -1012,16 +1255,16 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_COMPARISON: - token["type"] = "value" - token["value"] = compare(token.value, tokens[i-1].value, tokens[i+1].value) - tokens.remove_at(i+1) - tokens.remove_at(i-1) + if token.type == DMConstants.TOKEN_COMPARISON: + token.type = DMConstants.TOKEN_VALUE + token.value = _compare(token.value, tokens[i - 1].value, tokens[i + 1].value) + tokens.remove_at(i + 1) + tokens.remove_at(i - 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) # Then and/or i = 0 @@ -1029,16 +1272,16 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_AND_OR: - token["type"] = "value" - token["value"] = apply_operation(token.value, tokens[i-1].value, tokens[i+1].value) - tokens.remove_at(i+1) - tokens.remove_at(i-1) + if token.type == DMConstants.TOKEN_AND_OR: + token.type = DMConstants.TOKEN_VALUE + token.value = _apply_operation(token.value, tokens[i - 1].value, tokens[i + 1].value) + tokens.remove_at(i + 1) + tokens.remove_at(i - 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) # Lastly, resolve any assignments i = 0 @@ -1046,47 +1289,48 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_ASSIGNMENT: + if token.type == DMConstants.TOKEN_ASSIGNMENT: var lhs: Dictionary = tokens[i - 1] var value match lhs.type: &"variable": - value = apply_operation(token.value, get_state_value(lhs.value, extra_game_states), tokens[i+1].value) - set_state_value(lhs.value, value, extra_game_states) + value = _apply_operation(token.value, _get_state_value(lhs.value, extra_game_states), tokens[i + 1].value) + _set_state_value(lhs.value, value, extra_game_states) &"property": - value = apply_operation(token.value, lhs.value.get(lhs.property), tokens[i+1].value) + value = _apply_operation(token.value, lhs.value.get(lhs.property), tokens[i + 1].value) if typeof(lhs.value) == TYPE_DICTIONARY: lhs.value[lhs.property] = value else: lhs.value.set(lhs.property, value) &"dictionary": - value = apply_operation(token.value, lhs.value.get(lhs.key, null), tokens[i+1].value) + value = _apply_operation(token.value, lhs.value.get(lhs.key, null), tokens[i + 1].value) lhs.value[lhs.key] = value &"array": show_error_for_missing_state_value( - DialogueConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = lhs.key, array = lhs.value }), + DMConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = lhs.key, array = lhs.value }), lhs.key >= lhs.value.size() ) - value = apply_operation(token.value, lhs.value[lhs.key], tokens[i+1].value) + value = _apply_operation(token.value, lhs.value[lhs.key], tokens[i + 1].value) lhs.value[lhs.key] = value _: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.left_hand_size_cannot_be_assigned_to")) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.left_hand_size_cannot_be_assigned_to")) - token["type"] = "value" - token["value"] = value - tokens.remove_at(i+1) - tokens.remove_at(i-1) + token.type = DMConstants.TOKEN_VALUE + token.value = value + tokens.remove_at(i + 1) + tokens.remove_at(i - 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) return tokens[0].value -func compare(operator: String, first_value, second_value) -> bool: +# Compare two values. +func _compare(operator: String, first_value, second_value) -> bool: match operator: &"in": if first_value == null or second_value == null: @@ -1141,7 +1385,8 @@ func compare(operator: String, first_value, second_value) -> bool: return false -func apply_operation(operator: String, first_value, second_value): +# Apply an operation from one value to another. +func _apply_operation(operator: String, first_value, second_value): match operator: &"=": return second_value @@ -1160,23 +1405,29 @@ func apply_operation(operator: String, first_value, second_value): &"or": return first_value or second_value - assert(false, DialogueConstants.translate(&"runtime.unknown_operator")) + assert(false, DMConstants.translate(&"runtime.unknown_operator")) -# Check if a dialogue line contains meaningful information -func is_valid(line: DialogueLine) -> bool: +# Check if a dialogue line contains meaningful information. +func _is_valid(line: DialogueLine) -> bool: if line == null: return false - if line.type == DialogueConstants.TYPE_MUTATION and line.mutation == null: + if line.type == DMConstants.TYPE_MUTATION and line.mutation == null: return false - if line.type == DialogueConstants.TYPE_RESPONSE and line.get(&"responses").size() == 0: + if line.type == DMConstants.TYPE_RESPONSE and line.get(&"responses").size() == 0: return false return true -func thing_has_method(thing, method: String, args: Array) -> bool: - if Builtins.is_supported(thing): +# Check that a thing has a given method. +func _thing_has_method(thing, method: String, args: Array) -> bool: + if not is_instance_valid(thing): + return false + + if Builtins.is_supported(thing, method): return thing != _autoloads + elif thing is Dictionary: + return false if method in [&"call", &"call_deferred"]: return thing.has_method(args[0]) @@ -1187,15 +1438,15 @@ func thing_has_method(thing, method: String, args: Array) -> bool: if thing.has_method(method): return true - if method.to_snake_case() != method and DialogueSettings.check_for_dotnet_solution(): + if thing.get_script() and thing.get_script().resource_path.ends_with(".cs"): # If we get this far then the method might be a C# method with a Task return type - return _get_dotnet_dialogue_manager().ThingHasMethod(thing, method) + return _get_dotnet_dialogue_manager().ThingHasMethod(thing, method, args) return false # Check if a given property exists -func thing_has_property(thing: Object, property: String) -> bool: +func _thing_has_property(thing: Object, property: String) -> bool: if thing == null: return false @@ -1206,53 +1457,35 @@ func thing_has_property(thing: Object, property: String) -> bool: if p.name == property: return true + if thing.get_script() and thing.get_script().resource_path.ends_with(".cs"): + # If we get this far then the property might be a C# constant. + return _get_dotnet_dialogue_manager().ThingHasConstant(thing, property) + return false -func resolve_signal(args: Array, extra_game_states: Array): - if args[0] is Signal: - args[0] = args[0].get_name() - - for state in get_game_states(extra_game_states): - if typeof(state) == TYPE_DICTIONARY: - continue - elif state.has_signal(args[0]): - match args.size(): - 1: - state.emit_signal(args[0]) - 2: - state.emit_signal(args[0], args[1]) - 3: - state.emit_signal(args[0], args[1], args[2]) - 4: - state.emit_signal(args[0], args[1], args[2], args[3]) - 5: - state.emit_signal(args[0], args[1], args[2], args[3], args[4]) - 6: - state.emit_signal(args[0], args[1], args[2], args[3], args[4], args[5]) - 7: - state.emit_signal(args[0], args[1], args[2], args[3], args[4], args[5], args[6]) - 8: - state.emit_signal(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]) - return - - # The signal hasn't been found anywhere - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.signal_not_found").format({ signal_name = args[0], states = _get_state_shortcut_names(extra_game_states) })) - - -func get_method_info_for(thing, method: String) -> Dictionary: +func _get_method_info_for(thing: Variant, method: String, args: Array) -> Dictionary: # Use the thing instance id as a key for the caching dictionary. var thing_instance_id: int = thing.get_instance_id() if not _method_info_cache.has(thing_instance_id): var methods: Dictionary = {} for m in thing.get_method_list(): - methods[m.name] = m + methods["%s:%d" % [m.name, m.args.size()]] = m + if not methods.has(m.name): + methods[m.name] = m _method_info_cache[thing_instance_id] = methods - return _method_info_cache.get(thing_instance_id, {}).get(method) + var methods: Dictionary = _method_info_cache.get(thing_instance_id, {}) + var method_key: String = "%s:%d" % [method, args.size()] + if methods.has(method_key): + return methods.get(method_key) + elif methods.has(method): + return methods.get(method) + else: + return _get_method_info_for(thing.new(), method, args) -func resolve_thing_method(thing, method: String, args: Array): +func _resolve_thing_method(thing, method: String, args: Array): if Builtins.is_supported(thing): var result = Builtins.resolve_method(thing, method, args) if not Builtins.has_resolve_method_failed(): @@ -1260,13 +1493,13 @@ func resolve_thing_method(thing, method: String, args: Array): if thing.has_method(method): # Try to convert any literals to the right type - var method_info: Dictionary = get_method_info_for(thing, method) + var method_info: Dictionary = _get_method_info_for(thing, method, args) var method_args: Array = method_info.args if method_info.flags & METHOD_FLAG_VARARG == 0 and method_args.size() < args.size(): - assert(false, DialogueConstants.translate(&"runtime.expected_n_got_n_args").format({ expected = method_args.size(), method = method, received = args.size()})) + assert(false, DMConstants.translate(&"runtime.expected_n_got_n_args").format({ expected = method_args.size(), method = method, received = args.size()})) for i in range(0, min(method_args.size(), args.size())): var m: Dictionary = method_args[i] - var to_type:int = typeof(args[i]) + var to_type: int = typeof(args[i]) if m.type == TYPE_ARRAY: match m.hint_string: &"String": @@ -1281,7 +1514,7 @@ func resolve_thing_method(thing, method: String, args: Array): to_type = TYPE_PACKED_VECTOR3_ARRAY _: if m.hint_string != "": - assert(false, DialogueConstants.translate(&"runtime.unsupported_array_type").format({ type = m.hint_string})) + assert(false, DMConstants.translate(&"runtime.unsupported_array_type").format({ type = m.hint_string})) if typeof(args[i]) != to_type: args[i] = convert(args[i], to_type) diff --git a/addons/dialogue_manager/dialogue_manager.gd.uid b/addons/dialogue_manager/dialogue_manager.gd.uid new file mode 100644 index 0000000..d10762e --- /dev/null +++ b/addons/dialogue_manager/dialogue_manager.gd.uid @@ -0,0 +1 @@ +uid://c3rodes2l3gxb diff --git a/addons/dialogue_manager/dialogue_processor.gd b/addons/dialogue_manager/dialogue_processor.gd new file mode 100644 index 0000000..5de3e9c --- /dev/null +++ b/addons/dialogue_manager/dialogue_processor.gd @@ -0,0 +1,11 @@ +class_name DMDialogueProcessor extends RefCounted + + +## Override to modify the incoming raw string. +func _preprocess_line(raw_line: String) -> String: + return raw_line + + +## Override to modify the outgoing dialogue line. +func _process_line(line: DMCompiledLine) -> void: + pass diff --git a/addons/dialogue_manager/dialogue_processor.gd.uid b/addons/dialogue_manager/dialogue_processor.gd.uid new file mode 100644 index 0000000..a96cc19 --- /dev/null +++ b/addons/dialogue_manager/dialogue_processor.gd.uid @@ -0,0 +1 @@ +uid://m3b28rmso14t diff --git a/addons/dialogue_manager/dialogue_reponses_menu.gd b/addons/dialogue_manager/dialogue_reponses_menu.gd deleted file mode 100644 index d666b2e..0000000 --- a/addons/dialogue_manager/dialogue_reponses_menu.gd +++ /dev/null @@ -1,141 +0,0 @@ -@icon("./assets/responses_menu.svg") - -## A [Container] for dialogue responses provided by [b]Dialogue Manager[/b]. -class_name DialogueResponsesMenu extends Container - - -## Emitted when a response is selected. -signal response_selected(response) - - -## Optionally specify a control to duplicate for each response -@export var response_template: Control - -## The action for accepting a response (is possibly overridden by parent dialogue balloon). -@export var next_action: StringName = &"" - -## The list of dialogue responses. -var responses: Array = []: - get: - return responses - set(value): - responses = value - - # Remove any current items - for item in get_children(): - if item == response_template: continue - - remove_child(item) - item.queue_free() - - # Add new items - if responses.size() > 0: - for response in responses: - var item: Control - if is_instance_valid(response_template): - item = response_template.duplicate(DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_SIGNALS) - item.show() - else: - item = Button.new() - item.name = "Response%d" % get_child_count() - if not response.is_allowed: - item.name = String(item.name) + "Disallowed" - item.disabled = true - - # If the item has a response property then use that - if "response" in item: - item.response = response - # Otherwise assume we can just set the text - else: - item.text = response.text - - item.set_meta("response", response) - - add_child(item) - - _configure_focus() - - -func _ready() -> void: - visibility_changed.connect(func(): - if visible and get_menu_items().size() > 0: - get_menu_items()[0].grab_focus() - ) - - if is_instance_valid(response_template): - response_template.hide() - - -## Get the selectable items in the menu. -func get_menu_items() -> Array: - var items: Array = [] - for child in get_children(): - if not child.visible: continue - if "Disallowed" in child.name: continue - items.append(child) - - return items - - -## [b]DEPRECATED[/b]. Do not use. -func set_responses(next_responses: Array) -> void: - self.responses = next_responses - - -#region Internal - - -# Prepare the menu for keyboard and mouse navigation. -func _configure_focus() -> void: - var items = get_menu_items() - for i in items.size(): - var item: Control = items[i] - - item.focus_mode = Control.FOCUS_ALL - - item.focus_neighbor_left = item.get_path() - item.focus_neighbor_right = item.get_path() - - if i == 0: - item.focus_neighbor_top = item.get_path() - item.focus_previous = item.get_path() - else: - item.focus_neighbor_top = items[i - 1].get_path() - item.focus_previous = items[i - 1].get_path() - - if i == items.size() - 1: - item.focus_neighbor_bottom = item.get_path() - item.focus_next = item.get_path() - else: - item.focus_neighbor_bottom = items[i + 1].get_path() - item.focus_next = items[i + 1].get_path() - - item.mouse_entered.connect(_on_response_mouse_entered.bind(item)) - item.gui_input.connect(_on_response_gui_input.bind(item, item.get_meta("response"))) - - items[0].grab_focus() - - -#endregion - -#region Signals - - -func _on_response_mouse_entered(item: Control) -> void: - if "Disallowed" in item.name: return - - item.grab_focus() - - -func _on_response_gui_input(event: InputEvent, item: Control, response) -> void: - if "Disallowed" in item.name: return - - if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT: - get_viewport().set_input_as_handled() - response_selected.emit(response) - elif event.is_action_pressed(&"ui_accept" if next_action.is_empty() else next_action) and item in get_menu_items(): - get_viewport().set_input_as_handled() - response_selected.emit(response) - - -#endregion diff --git a/addons/dialogue_manager/dialogue_resource.gd b/addons/dialogue_manager/dialogue_resource.gd index 68f52a0..29ade7b 100644 --- a/addons/dialogue_manager/dialogue_resource.gd +++ b/addons/dialogue_manager/dialogue_resource.gd @@ -5,7 +5,6 @@ class_name DialogueResource extends Resource -const _DialogueManager = preload("./dialogue_manager.gd") const DialogueLine = preload("./dialogue_line.gd") ## A list of state shortcuts @@ -30,7 +29,7 @@ const DialogueLine = preload("./dialogue_line.gd") ## Get the next printable line of dialogue, starting from a referenced line ([code]title[/code] can ## be a title string or a stringified line number). Runs any mutations along the way and then returns ## the first dialogue line encountered. -func get_next_dialogue_line(title: String, extra_game_states: Array = [], mutation_behaviour: _DialogueManager.MutationBehaviour = _DialogueManager.MutationBehaviour.Wait) -> DialogueLine: +func get_next_dialogue_line(title: String = "", extra_game_states: Array = [], mutation_behaviour: DMConstants.MutationBehaviour = DMConstants.MutationBehaviour.Wait) -> DialogueLine: return await Engine.get_singleton("DialogueManager").get_next_dialogue_line(self, title, extra_game_states, mutation_behaviour) diff --git a/addons/dialogue_manager/dialogue_resource.gd.uid b/addons/dialogue_manager/dialogue_resource.gd.uid new file mode 100644 index 0000000..27b95d0 --- /dev/null +++ b/addons/dialogue_manager/dialogue_resource.gd.uid @@ -0,0 +1 @@ +uid://dbs4435dsf3ry diff --git a/addons/dialogue_manager/dialogue_response.gd b/addons/dialogue_manager/dialogue_response.gd index 92cec24..479b81c 100644 --- a/addons/dialogue_manager/dialogue_response.gd +++ b/addons/dialogue_manager/dialogue_response.gd @@ -2,14 +2,11 @@ class_name DialogueResponse extends RefCounted -const _DialogueConstants = preload("./constants.gd") - - ## The ID of this response var id: String ## The internal type of this dialogue object, always set to [code]TYPE_RESPONSE[/code]. -var type: String = _DialogueConstants.TYPE_RESPONSE +var type: String = DMConstants.TYPE_RESPONSE ## The next line ID to use if this response is selected by the player. var next_id: String = "" @@ -17,6 +14,9 @@ var next_id: String = "" ## [code]true[/code] if the condition of this line was met. var is_allowed: bool = true +## The original condition text. +var condition_as_text: String = "" + ## A character (depending on the "characters in responses" behaviour setting). var character: String = "" @@ -48,6 +48,7 @@ func _init(data: Dictionary = {}) -> void: text_replacements = data.text_replacements tags = data.tags translation_key = data.translation_key + condition_as_text = data.condition_as_text func _to_string() -> String: diff --git a/addons/dialogue_manager/dialogue_response.gd.uid b/addons/dialogue_manager/dialogue_response.gd.uid new file mode 100644 index 0000000..9b4532a --- /dev/null +++ b/addons/dialogue_manager/dialogue_response.gd.uid @@ -0,0 +1 @@ +uid://cm0xpfeywpqid diff --git a/addons/dialogue_manager/dialogue_responses_menu.gd b/addons/dialogue_manager/dialogue_responses_menu.gd new file mode 100644 index 0000000..db36edb --- /dev/null +++ b/addons/dialogue_manager/dialogue_responses_menu.gd @@ -0,0 +1,178 @@ +@icon("./assets/responses_menu.svg") + +## A [Container] for dialogue responses provided by [b]Dialogue Manager[/b]. +class_name DialogueResponsesMenu extends Container + + +## Emitted when a response is focused. +signal response_focused(response: Control) + +## Emitted when a response is selected. +signal response_selected(response: Control) + + +## Optionally specify a control to duplicate for each response +@export var response_template: Control + +## The action for accepting a response (is possibly overridden by parent dialogue balloon). +@export var next_action: StringName = &"" + +## Automatically set up focus neighbours when the responses list changes. +@export var auto_configure_focus: bool = true + +## Automatically focus the first item when showing. +@export var auto_focus_first_item: bool = true + +## Hide any responses where [code]is_allowed[/code] is false +@export var hide_failed_responses: bool = false + +## The list of dialogue responses. +var responses: Array = []: + set(value): + responses = value + _apply_responses() + get: + return responses + +# The previously focused item in this menu. +var _previously_focused_item: Control = null + + +func _ready() -> void: + visibility_changed.connect(func(): + if auto_focus_first_item and visible and get_menu_items().size() > 0: + var first_item: Control = get_menu_items()[0] + if first_item.is_inside_tree(): + first_item.grab_focus() + ) + + if is_instance_valid(response_template): + response_template.hide() + + get_viewport().gui_focus_changed.connect(_on_focus_changed) + + +## Get the selectable items in the menu. +func get_menu_items() -> Array: + var items: Array = [] + for child in get_children(): + if not child.visible: continue + if "Disallowed" in child.name: continue + items.append(child) + + return items + + +## Prepare the menu for keyboard and mouse navigation. +func configure_focus() -> void: + var items = get_menu_items() + for i in items.size(): + var item: Control = items[i] + + item.focus_mode = Control.FOCUS_ALL + + item.focus_neighbor_left = item.get_path() + item.focus_neighbor_right = item.get_path() + + if i == 0: + item.focus_neighbor_top = item.get_path() + item.focus_neighbor_left = item.get_path() + item.focus_previous = item.get_path() + else: + item.focus_neighbor_top = items[i - 1].get_path() + item.focus_neighbor_left = items[i - 1].get_path() + item.focus_previous = items[i - 1].get_path() + + if i == items.size() - 1: + item.focus_neighbor_bottom = item.get_path() + item.focus_neighbor_right = item.get_path() + item.focus_next = item.get_path() + else: + item.focus_neighbor_bottom = items[i + 1].get_path() + item.focus_neighbor_right = items[i + 1].get_path() + item.focus_next = items[i + 1].get_path() + + item.mouse_entered.connect(_on_response_mouse_entered.bind(item)) + item.gui_input.connect(_on_response_gui_input.bind(item, item.get_meta("response"))) + + _previously_focused_item = items[0] + + if auto_focus_first_item: + items[0].grab_focus() + + +#region Internal + + +# Set up the visual side of things. +func _apply_responses() -> void: + # Remove any current items + for item in get_children(): + if item == response_template: continue + + remove_child(item) + item.queue_free() + + # Add new items + if responses.size() > 0: + for response in responses: + if hide_failed_responses and not response.is_allowed: continue + + var item: Control + if is_instance_valid(response_template): + item = response_template.duplicate(DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_SIGNALS) + item.show() + else: + item = Button.new() + item.name = "Response%d" % get_child_count() + if not response.is_allowed: + item.name = item.name + &"Disallowed" + item.disabled = true + + # If the item has a response property then use that + if "response" in item: + item.response = response + # Otherwise assume we can just set the text + else: + item.text = response.text + + item.set_meta("response", response) + + add_child(item) + + if auto_configure_focus: + configure_focus() + + +#endregion + +#region Signals + + +func _on_focus_changed(control: Control) -> void: + if "Disallowed" in control.name: return + if not control in get_menu_items(): return + + if _previously_focused_item != control: + _previously_focused_item = control + response_focused.emit(control) + + +func _on_response_mouse_entered(item: Control) -> void: + if "Disallowed" in item.name: return + + item.grab_focus() + + +func _on_response_gui_input(event: InputEvent, item: Control, response) -> void: + if "Disallowed" in item.name: return + + if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT: + get_viewport().set_input_as_handled() + response_selected.emit(response) + elif event.is_action_pressed(&"ui_accept" if next_action.is_empty() else next_action) and item in get_menu_items(): + get_viewport().set_input_as_handled() + response_selected.emit(response) + + +#endregion diff --git a/addons/dialogue_manager/dialogue_responses_menu.gd.uid b/addons/dialogue_manager/dialogue_responses_menu.gd.uid new file mode 100644 index 0000000..0ae73d9 --- /dev/null +++ b/addons/dialogue_manager/dialogue_responses_menu.gd.uid @@ -0,0 +1 @@ +uid://bb52rsfwhkxbn diff --git a/addons/dialogue_manager/editor_translation_parser_plugin.gd b/addons/dialogue_manager/editor_translation_parser_plugin.gd index 16a1833..137ab70 100644 --- a/addons/dialogue_manager/editor_translation_parser_plugin.gd +++ b/addons/dialogue_manager/editor_translation_parser_plugin.gd @@ -1,43 +1,52 @@ -extends EditorTranslationParserPlugin +class_name DMTranslationParserPlugin extends EditorTranslationParserPlugin -const DialogueConstants = preload("./constants.gd") -const DialogueSettings = preload("./settings.gd") -const DialogueManagerParser = preload("./components/parser.gd") -const DialogueManagerParseResult = preload("./components/parse_result.gd") +## Cached result of parsing a dialogue file. +var data: DMCompilerResult +## List of characters that were added. +var translated_character_names: PackedStringArray = [] +var translated_lines: Array[Dictionary] = [] -func _parse_file(path: String, msgids: Array, msgids_context_plural: Array) -> void: +func _parse_file(path: String) -> Array[PackedStringArray]: + var msgs: Array[PackedStringArray] = [] var file: FileAccess = FileAccess.open(path, FileAccess.READ) var text: String = file.get_as_text() - var data: DialogueManagerParseResult = DialogueManagerParser.parse_string(text, path) + data = DMCompiler.compile_string(text, path) + var known_keys: PackedStringArray = PackedStringArray([]) # Add all character names if settings ask for it - if DialogueSettings.get_setting("export_characters_in_translation", true): - var character_names: PackedStringArray = data.character_names - for character_name in character_names: + if DMSettings.get_setting(DMSettings.INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST, true): + translated_character_names = [] as Array[DialogueLine] + for character_name: String in data.character_names: if character_name in known_keys: continue known_keys.append(character_name) - msgids_context_plural.append([character_name.replace('"', '\"'), "dialogue", ""]) + translated_character_names.append(character_name) + msgs.append(PackedStringArray([character_name.replace('"', '\"'), "dialogue", "", DMConstants.translate("translation_plugin.character_name")])) # Add all dialogue lines and responses var dialogue: Dictionary = data.lines - for key in dialogue.keys(): + for key: String in dialogue.keys(): var line: Dictionary = dialogue.get(key) - if not line.type in [DialogueConstants.TYPE_DIALOGUE, DialogueConstants.TYPE_RESPONSE]: continue - if line.translation_key in known_keys: continue + if not line.type in [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE]: continue - known_keys.append(line.translation_key) + var translation_key: String = line.get(&"translation_key", line.text) - if line.translation_key == "" or line.translation_key == line.text: - msgids_context_plural.append([line.text.replace('"', '\"'), "", ""]) + if translation_key in known_keys: continue + + known_keys.append(translation_key) + translated_lines.append(line) + if translation_key == line.text: + msgs.append(PackedStringArray([line.text.replace('"', '\"'), "", "", line.get("notes", "")])) else: - msgids_context_plural.append([line.text.replace('"', '\"'), line.translation_key.replace('"', '\"'), ""]) + msgs.append(PackedStringArray([line.text.replace('"', '\"'), line.translation_key.replace('"', '\"'), "", line.get("notes", "")])) + + return msgs func _get_recognized_extensions() -> PackedStringArray: diff --git a/addons/dialogue_manager/editor_translation_parser_plugin.gd.uid b/addons/dialogue_manager/editor_translation_parser_plugin.gd.uid new file mode 100644 index 0000000..22ddbe9 --- /dev/null +++ b/addons/dialogue_manager/editor_translation_parser_plugin.gd.uid @@ -0,0 +1 @@ +uid://c6bya881h1egb diff --git a/addons/dialogue_manager/example_balloon/ExampleBalloon.cs b/addons/dialogue_manager/example_balloon/ExampleBalloon.cs index 2835804..084c3f4 100644 --- a/addons/dialogue_manager/example_balloon/ExampleBalloon.cs +++ b/addons/dialogue_manager/example_balloon/ExampleBalloon.cs @@ -5,6 +5,9 @@ namespace DialogueManagerRuntime { public partial class ExampleBalloon : CanvasLayer { + [Export] public Resource DialogueResource; + [Export] public string StartFromTitle = ""; + [Export] public bool AutoStart = false; [Export] public string NextAction = "ui_accept"; [Export] public string SkipAction = "ui_cancel"; @@ -13,8 +16,8 @@ namespace DialogueManagerRuntime RichTextLabel characterLabel; RichTextLabel dialogueLabel; VBoxContainer responsesMenu; + Polygon2D progress; - Resource resource; Array<Variant> temporaryGameStates = new Array<Variant>(); bool isWaitingForInput = false; bool willHideBalloon = false; @@ -25,21 +28,27 @@ namespace DialogueManagerRuntime get => dialogueLine; set { - isWaitingForInput = false; - balloon.FocusMode = Control.FocusModeEnum.All; - balloon.GrabFocus(); - + // Dialogue has finished so close the balloon if (value == null) { - QueueFree(); + if (Owner == null) + { + QueueFree(); + } + else + { + Hide(); + } return; } dialogueLine = value; - UpdateDialogue(); + ApplyDialogueLine(); } } + Timer MutationCooldown = new Timer(); + public override void _Ready() { @@ -47,6 +56,7 @@ namespace DialogueManagerRuntime characterLabel = GetNode<RichTextLabel>("%CharacterLabel"); dialogueLabel = GetNode<RichTextLabel>("%DialogueLabel"); responsesMenu = GetNode<VBoxContainer>("%ResponsesMenu"); + progress = GetNode<Polygon2D>("%Progress"); balloon.Hide(); @@ -88,7 +98,28 @@ namespace DialogueManagerRuntime Next(response.NextId); })); + + // Hide the balloon when a mutation is running + MutationCooldown.Timeout += () => + { + if (willHideBalloon) + { + willHideBalloon = false; + balloon.Hide(); + } + }; + AddChild(MutationCooldown); + DialogueManager.Mutated += OnMutated; + + if (AutoStart) + { + if (!IsInstanceValid(DialogueResource)) + { + throw new System.Exception(DialogueManager.GetErrorMessage(143)); + } + Start(); + } } @@ -111,7 +142,7 @@ namespace DialogueManagerRuntime if (what == NotificationTranslationChanged && IsInstanceValid(dialogueLabel)) { float visibleRatio = dialogueLabel.VisibleRatio; - DialogueLine = await DialogueManager.GetNextDialogueLine(resource, DialogueLine.Id, temporaryGameStates); + DialogueLine = await DialogueManager.GetNextDialogueLine(DialogueResource, DialogueLine.Id, temporaryGameStates); if (visibleRatio < 1.0f) { dialogueLabel.Call("skip_typing"); @@ -120,36 +151,52 @@ namespace DialogueManagerRuntime } - public async void Start(Resource dialogueResource, string title, Array<Variant> extraGameStates = null) + public override void _Process(double delta) { - if (!IsNodeReady()) + base._Process(delta); + + if (IsInstanceValid(dialogueLine)) { - await ToSignal(this, SignalName.Ready); + progress.Visible = !(bool)dialogueLabel.Get("is_typing") && dialogueLine.Responses.Count == 0 && !dialogueLine.HasTag("voice"); + } + } + + + public async void Start(Resource dialogueResource = null, string title = "", Array<Variant> extraGameStates = null) + { + temporaryGameStates = new Array<Variant> { this } + (extraGameStates ?? new Array<Variant>()); + isWaitingForInput = false; + + if (IsInstanceValid(dialogueResource)) + { + DialogueResource = dialogueResource; + } + if (title != "") + { + StartFromTitle = title; } - temporaryGameStates = extraGameStates ?? new Array<Variant>(); - isWaitingForInput = false; - resource = dialogueResource; - - DialogueLine = await DialogueManager.GetNextDialogueLine(resource, title, temporaryGameStates); + DialogueLine = await DialogueManager.GetNextDialogueLine(DialogueResource, StartFromTitle, temporaryGameStates); + Show(); } public async void Next(string nextId) { - DialogueLine = await DialogueManager.GetNextDialogueLine(resource, nextId, temporaryGameStates); + DialogueLine = await DialogueManager.GetNextDialogueLine(DialogueResource, nextId, temporaryGameStates); } #region Helpers - private async void UpdateDialogue() + private async void ApplyDialogueLine() { - if (!IsNodeReady()) - { - await ToSignal(this, SignalName.Ready); - } + MutationCooldown.Stop(); + + isWaitingForInput = false; + balloon.FocusMode = Control.FocusModeEnum.All; + balloon.GrabFocus(); // Set up the character name characterLabel.Visible = !string.IsNullOrEmpty(dialogueLine.Character); @@ -208,14 +255,7 @@ namespace DialogueManagerRuntime { isWaitingForInput = false; willHideBalloon = true; - GetTree().CreateTimer(0.1f).Timeout += () => - { - if (willHideBalloon) - { - willHideBalloon = false; - balloon.Hide(); - } - }; + MutationCooldown.Start(0.1f); } diff --git a/addons/dialogue_manager/example_balloon/ExampleBalloon.cs.uid b/addons/dialogue_manager/example_balloon/ExampleBalloon.cs.uid new file mode 100644 index 0000000..4b3783a --- /dev/null +++ b/addons/dialogue_manager/example_balloon/ExampleBalloon.cs.uid @@ -0,0 +1 @@ +uid://5b3w40kwakl3 diff --git a/addons/dialogue_manager/example_balloon/example_balloon.gd b/addons/dialogue_manager/example_balloon/example_balloon.gd index a3dc9df..75c2d51 100644 --- a/addons/dialogue_manager/example_balloon/example_balloon.gd +++ b/addons/dialogue_manager/example_balloon/example_balloon.gd @@ -1,14 +1,25 @@ -class_name DialogueManagerExampleBalloon extends CanvasLayer +#class_name DialogueManagerExampleBalloon extends CanvasLayer ## A basic dialogue balloon for use with Dialogue Manager. +extends CanvasLayer + +## The dialogue resource +@export var dialogue_resource: DialogueResource + +## Start from a given title when using balloon as a [Node] in a scene. +@export var start_from_title: String = "" + +## If running as a [Node] in a scene then auto start the dialogue. +@export var auto_start: bool = false + ## 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 +## A sound player for voice lines (if they exist). +@onready var audio_stream_player: AudioStreamPlayer = %AudioStreamPlayer ## Temporary game states var temporary_game_states: Array = [] @@ -26,55 +37,22 @@ 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) + set(value): + if value: + dialogue_line = value + apply_dialogue_line() else: - is_waiting_for_input = true - balloon.focus_mode = Control.FOCUS_ALL - balloon.grab_focus() + # The dialogue has finished so close the balloon + if owner == null: + queue_free() + else: + hide() get: return dialogue_line +## A cooldown timer for delaying the balloon hide when encountering a mutation. +var mutation_cooldown: Timer = Timer.new() + ## The base balloon anchor @onready var balloon: Control = %Balloon @@ -87,6 +65,9 @@ var dialogue_line: DialogueLine: ## The menu of responses @onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu +## Indicator to show that player can progress dialogue. +@onready var progress: Polygon2D = %Progress + func _ready() -> void: balloon.hide() @@ -96,6 +77,19 @@ func _ready() -> void: if responses_menu.next_action.is_empty(): responses_menu.next_action = next_action + mutation_cooldown.timeout.connect(_on_mutation_cooldown_timeout) + add_child(mutation_cooldown) + + if auto_start: + if not is_instance_valid(dialogue_resource): + assert(false, DMConstants.get_error_message(DMConstants.ERR_MISSING_RESOURCE_FOR_AUTOSTART)) + start() + + +func _process(delta: float) -> void: + if is_instance_valid(dialogue_line): + progress.visible = not dialogue_label.is_typing and dialogue_line.responses.size() == 0 and not dialogue_line.has_tag("voice") + func _unhandled_input(_event: InputEvent) -> void: # Only the balloon is allowed to handle input while it's showing @@ -107,37 +101,88 @@ func _notification(what: int) -> void: 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) + dialogue_line = await dialogue_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 +func start(with_dialogue_resource: DialogueResource = null, title: String = "", extra_game_states: Array = []) -> void: + 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) + if is_instance_valid(with_dialogue_resource): + dialogue_resource = with_dialogue_resource + if not title.is_empty(): + start_from_title = title + dialogue_line = await dialogue_resource.get_next_dialogue_line(start_from_title, temporary_game_states) + show() + + +## Apply any changes to the balloon given a new [DialogueLine]. +func apply_dialogue_line() -> void: + mutation_cooldown.stop() + + progress.hide() + is_waiting_for_input = false + balloon.focus_mode = Control.FOCUS_ALL + balloon.grab_focus() + + 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.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 next line + if dialogue_line.has_tag("voice"): + audio_stream_player.stream = load(dialogue_line.get_tag_value("voice")) + audio_stream_player.play() + await audio_stream_player.finished + next(dialogue_line.next_id) + elif 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() ## 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) + dialogue_line = await dialogue_resource.get_next_dialogue_line(next_id, temporary_game_states) #region Signals +func _on_mutation_cooldown_timeout() -> void: + if will_hide_balloon: + will_hide_balloon = false + balloon.hide() + + 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() - ) + if not _mutation.is_inline: + is_waiting_for_input = false + will_hide_balloon = true + mutation_cooldown.start(0.1) func _on_balloon_gui_input(event: InputEvent) -> void: diff --git a/addons/dialogue_manager/example_balloon/example_balloon.gd.uid b/addons/dialogue_manager/example_balloon/example_balloon.gd.uid new file mode 100644 index 0000000..6327f9b --- /dev/null +++ b/addons/dialogue_manager/example_balloon/example_balloon.gd.uid @@ -0,0 +1 @@ +uid://d1wt4ma6055l8 diff --git a/addons/dialogue_manager/example_balloon/example_balloon.tscn b/addons/dialogue_manager/example_balloon/example_balloon.tscn index 4ad0cb4..ce720a4 100644 --- a/addons/dialogue_manager/example_balloon/example_balloon.tscn +++ b/addons/dialogue_manager/example_balloon/example_balloon.tscn @@ -1,8 +1,178 @@ [gd_scene load_steps=9 format=3 uid="uid://73jm5qjy52vq"] -[ext_resource type="Script" path="res://addons/dialogue_manager/example_balloon/example_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="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="3_72ixx"] + +[sub_resource type="GDScript" id="GDScript_i0vys"] +script/source = "class_name DialogueManagerExampleBalloon 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 +" [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"] bg_color = Color(0, 0, 0, 1) @@ -63,9 +233,154 @@ MarginContainer/constants/margin_right = 30 MarginContainer/constants/margin_top = 15 Panel/styles/panel = SubResource("StyleBoxFlat_uy0d5") +[sub_resource type="GDScript" id="GDScript_v0j00"] +script/source = "@icon(\"./assets/responses_menu.svg\") + +## A [Container] for dialogue responses provided by [b]Dialogue Manager[/b]. +class_name DialogueResponsesMenu extends Container + + +## Emitted when a response is selected. +signal response_selected(response) + + +## Optionally specify a control to duplicate for each response +@export var response_template: Control + +## The action for accepting a response (is possibly overridden by parent dialogue balloon). +@export var next_action: StringName = &\"\" + +## The list of dialogue responses. +var responses: Array = []: + get: + return responses + set(value): + responses = value + + # Remove any current items + for item in get_children(): + if item == response_template: continue + + remove_child(item) + item.queue_free() + + # Add new items + if responses.size() > 0: + for response in responses: + var item: Control + if is_instance_valid(response_template): + item = response_template.duplicate(DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_SIGNALS) + item.show() + else: + item = Button.new() + item.name = \"Response%d\" % get_child_count() + if not response.is_allowed: + item.name = String(item.name) + \"Disallowed\" + item.disabled = true + + # If the item has a response property then use that + if \"response\" in item: + item.response = response + # Otherwise assume we can just set the text + else: + item.text = response.text + + item.set_meta(\"response\", response) + + add_child(item) + + _configure_focus() + + + +func _ready() -> void: + visibility_changed.connect(func(): + if visible and get_menu_items().size() > 0: + get_menu_items()[0].grab_focus() + ) + + if is_instance_valid(response_template): + response_template.hide() + + +## Get the selectable items in the menu. +func get_menu_items() -> Array: + var items: Array = [] + for child in get_children(): + if not child.visible: continue + if \"Disallowed\" in child.name: continue + items.append(child) + + return items + + +## [b]DEPRECATED[/b]. Do not use. +func set_responses(next_responses: Array) -> void: + self.responses = next_responses + + +#region Internal + + +# Prepare the menu for keyboard and mouse navigation. +func _configure_focus() -> void: + var items = get_menu_items() + for i in items.size(): + var item: Control = items[i] + + item.focus_mode = Control.FOCUS_ALL + + item.focus_neighbor_left = item.get_path() + item.focus_neighbor_right = item.get_path() + + if i == 0: + item.focus_neighbor_top = item.get_path() + item.focus_previous = item.get_path() + else: + item.focus_neighbor_top = items[i - 1].get_path() + item.focus_previous = items[i - 1].get_path() + + if i == items.size() - 1: + item.focus_neighbor_bottom = item.get_path() + item.focus_next = item.get_path() + else: + item.focus_neighbor_bottom = items[i + 1].get_path() + item.focus_next = items[i + 1].get_path() + + item.mouse_entered.connect(_on_response_mouse_entered.bind(item)) + item.gui_input.connect(_on_response_gui_input.bind(item, item.get_meta(\"response\"))) + + items[0].grab_focus() + + +#endregion + +#region Signals + + +func _on_response_mouse_entered(item: Control) -> void: + if \"Disallowed\" in item.name: return + + item.grab_focus() + + +func _on_response_gui_input(event: InputEvent, item: Control, response) -> void: + if \"Disallowed\" in item.name: return + + if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT: + get_viewport().set_input_as_handled() + response_selected.emit(response) + elif event.is_action_pressed(&\"ui_accept\" if next_action.is_empty() else next_action) and item in get_menu_items(): + get_viewport().set_input_as_handled() + response_selected.emit(response) + + +#endregion +" + [node name="ExampleBalloon" type="CanvasLayer"] layer = 100 -script = ExtResource("1_36de5") +script = SubResource("GDScript_i0vys") [node name="Balloon" type="Control" parent="."] unique_name_in_owner = true @@ -132,7 +447,7 @@ unique_name_in_owner = true layout_mode = 2 size_flags_vertical = 8 theme_override_constants/separation = 2 -script = ExtResource("3_72ixx") +script = SubResource("GDScript_v0j00") response_template = NodePath("ResponseExample") [node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"] @@ -140,4 +455,3 @@ 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"] diff --git a/addons/dialogue_manager/example_balloon/small_example_balloon.tscn b/addons/dialogue_manager/example_balloon/small_example_balloon.tscn index 3011833..0aab50b 100644 --- a/addons/dialogue_manager/example_balloon/small_example_balloon.tscn +++ b/addons/dialogue_manager/example_balloon/small_example_balloon.tscn @@ -1,8 +1,8 @@ -[gd_scene load_steps=10 format=3 uid="uid://13s5spsk34qu"] +[gd_scene load_steps=12 format=3 uid="uid://13s5spsk34qu"] -[ext_resource type="Script" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_s2gbs"] +[ext_resource type="Script" uid="uid://d1wt4ma6055l8" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_s2gbs"] [ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_hfvdi"] -[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="3_1j1j0"] +[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="3_1j1j0"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_235ry"] content_margin_left = 6.0 @@ -66,7 +66,7 @@ corner_radius_top_right = 3 corner_radius_bottom_right = 3 corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uy0d5"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i6nbm"] bg_color = Color(0, 0, 0, 1) border_width_left = 1 border_width_top = 1 @@ -87,7 +87,28 @@ MarginContainer/constants/margin_bottom = 4 MarginContainer/constants/margin_left = 8 MarginContainer/constants/margin_right = 8 MarginContainer/constants/margin_top = 4 -Panel/styles/panel = SubResource("StyleBoxFlat_uy0d5") +PanelContainer/styles/panel = SubResource("StyleBoxFlat_i6nbm") + +[sub_resource type="Animation" id="Animation_i6nbm"] +resource_name = "progress" +loop_mode = 1 +step = 0.1 +tracks/0/type = "bezier" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Progress:position:y") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"handle_modes": PackedInt32Array(0, 0, 0, 0, 0), +"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.2, -0.0157438, -1, -0.2, 0.0112069, 0.25, 0, -1, -0.25, 0, 0.25, 0, 0, -0.2, 0.00299701, 0.25, 0), +"times": PackedFloat32Array(0, 0.1, 0.5, 0.6, 1) +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_6b6c8"] +_data = { +&"progress": SubResource("Animation_i6nbm") +} [node name="ExampleBalloon" type="CanvasLayer"] layer = 100 @@ -103,31 +124,32 @@ grow_horizontal = 2 grow_vertical = 2 theme = SubResource("Theme_qq3yp") -[node name="Panel" type="Panel" parent="Balloon"] +[node name="MarginContainer" type="MarginContainer" parent="Balloon"] layout_mode = 1 anchors_preset = 12 anchor_top = 1.0 anchor_right = 1.0 anchor_bottom = 1.0 -offset_left = 3.0 -offset_top = -62.0 -offset_right = -4.0 -offset_bottom = -4.0 +offset_top = -71.0 grow_horizontal = 2 grow_vertical = 0 -[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="PanelContainer" type="PanelContainer" parent="Balloon/MarginContainer"] +clip_children = 2 +layout_mode = 2 +mouse_filter = 1 -[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/Panel/Dialogue"] +[node name="MarginContainer" type="MarginContainer" parent="Balloon/MarginContainer/PanelContainer"] layout_mode = 2 -[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/Panel/Dialogue/VBoxContainer"] +[node name="HBoxContainer" type="HBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer"] unique_name_in_owner = true modulate = Color(1, 1, 1, 0.501961) layout_mode = 2 @@ -137,37 +159,52 @@ text = "Character" fit_content = true scroll_active = false -[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/VBoxContainer" instance=ExtResource("2_hfvdi")] +[node name="DialogueLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer" instance=ExtResource("2_hfvdi")] unique_name_in_owner = true layout_mode = 2 size_flags_vertical = 3 text = "Dialogue..." -skip_pause_at_abbreviations = PackedStringArray("Mr", "Mrs", "Ms", "Dr", "etc", "eg", "ex") -[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 = -124.0 -offset_top = -218.0 -offset_right = 125.0 -offset_bottom = -50.0 -grow_horizontal = 2 -grow_vertical = 0 - -[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/Responses"] -unique_name_in_owner = true +[node name="Control" type="Control" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer"] +custom_minimum_size = Vector2(5, 3) layout_mode = 2 size_flags_vertical = 8 + +[node name="Progress" type="Polygon2D" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control"] +unique_name_in_owner = true +position = Vector2(0, -0.455998) +polygon = PackedVector2Array(0, 0, 2.5, 3, 5, 0) + +[node name="AnimationPlayer" type="AnimationPlayer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control"] +libraries = { +&"": SubResource("AnimationLibrary_6b6c8") +} +autoplay = "progress" + +[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -116.5 +offset_top = -9.0 +offset_right = 116.5 +offset_bottom = 9.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_vertical = 8 theme_override_constants/separation = 2 script = ExtResource("3_1j1j0") -[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"] +[node name="ResponseExample" type="Button" parent="Balloon/ResponsesMenu"] layout_mode = 2 text = "Response Example" +[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] +unique_name_in_owner = true + [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"] +[connection signal="response_selected" from="Balloon/ResponsesMenu" to="." method="_on_responses_menu_response_selected"] diff --git a/addons/dialogue_manager/export_plugin.gd b/addons/dialogue_manager/export_plugin.gd new file mode 100644 index 0000000..ef65500 --- /dev/null +++ b/addons/dialogue_manager/export_plugin.gd @@ -0,0 +1,26 @@ +class_name DMExportPlugin extends EditorExportPlugin + +const IGNORED_PATHS = [ + "/assets", + "/components", + "/views", + "inspector_plugin", + "test_scene" +] + + +func _get_name() -> String: + return "Dialogue Manager Export Plugin" + + +func _export_file(path: String, type: String, features: PackedStringArray) -> void: + var plugin_path: String = Engine.get_meta("DialogueManagerPlugin").get_plugin_path() + + # Ignore any editor stuff + for ignored_path: String in IGNORED_PATHS: + if path.begins_with(plugin_path + ignored_path): + skip() + + # Ignore C# stuff it not using dotnet + if path.begins_with(plugin_path) and not DMSettings.check_for_dotnet_solution() and path.ends_with(".cs"): + skip() diff --git a/addons/dialogue_manager/export_plugin.gd.uid b/addons/dialogue_manager/export_plugin.gd.uid new file mode 100644 index 0000000..efaa0c6 --- /dev/null +++ b/addons/dialogue_manager/export_plugin.gd.uid @@ -0,0 +1 @@ +uid://sa55ra11ji2q diff --git a/addons/dialogue_manager/import_plugin.gd b/addons/dialogue_manager/import_plugin.gd index 4d3103e..9c4ad78 100644 --- a/addons/dialogue_manager/import_plugin.gd +++ b/addons/dialogue_manager/import_plugin.gd @@ -1,20 +1,19 @@ @tool -extends EditorImportPlugin +class_name DMImportPlugin extends EditorImportPlugin signal compiled_resource(resource: Resource) -const DialogueResource = preload("./dialogue_resource.gd") -const DialogueManagerParser = preload("./components/parser.gd") -const DialogueManagerParseResult = preload("./components/parse_result.gd") - -const compiler_version = 13 +const COMPILER_VERSION = 15 func _get_importer_name() -> String: - # NOTE: A change to this forces a re-import of all dialogue - return "dialogue_manager_compiler_%s" % compiler_version + return "dialogue_manager" + + +func _get_format_version() -> int: + return COMPILER_VERSION func _get_visible_name() -> String: @@ -63,7 +62,7 @@ func _get_option_visibility(path: String, option_name: StringName, options: Dict func _import(source_file: String, save_path: String, options: Dictionary, platform_variants: Array[String], gen_files: Array[String]) -> Error: - var cache = Engine.get_meta("DialogueCache") + var cache = Engine.get_meta("DMCache") # Get the raw file contents if not FileAccess.file_exists(source_file): return ERR_FILE_NOT_FOUND @@ -73,17 +72,12 @@ func _import(source_file: String, save_path: String, options: Dictionary, platfo cache.file_content_changed.emit(source_file, raw_text) - # Parse the text - var parser: DialogueManagerParser = DialogueManagerParser.new() - var err: Error = parser.parse(raw_text, source_file) - var data: DialogueManagerParseResult = parser.get_data() - var errors: Array[Dictionary] = parser.get_errors() - parser.free() - - if err != OK: - printerr("%d errors found in %s" % [errors.size(), source_file]) - cache.add_errors_to_file(source_file, errors) - return err + # Compile the text + var result: DMCompilerResult = DMCompiler.compile_string(raw_text, source_file) + if result.errors.size() > 0: + printerr("%d errors found in %s" % [result.errors.size(), source_file]) + cache.add_errors_to_file(source_file, result.errors) + return OK # Get the current addon version var config: ConfigFile = ConfigFile.new() @@ -94,17 +88,17 @@ func _import(source_file: String, save_path: String, options: Dictionary, platfo var resource: DialogueResource = DialogueResource.new() resource.set_meta("dialogue_manager_version", version) - resource.using_states = data.using_states - resource.titles = data.titles - resource.first_title = data.first_title - resource.character_names = data.character_names - resource.lines = data.lines - resource.raw_text = data.raw_text + resource.using_states = result.using_states + resource.titles = result.titles + resource.first_title = result.first_title + resource.character_names = result.character_names + resource.lines = result.lines + resource.raw_text = result.raw_text # Clear errors and possibly trigger any cascade recompiles - cache.add_file(source_file, data) + cache.add_file(source_file, result) - err = ResourceSaver.save(resource, "%s.%s" % [save_path, _get_save_extension()]) + var err: Error = ResourceSaver.save(resource, "%s.%s" % [save_path, _get_save_extension()]) compiled_resource.emit(resource) diff --git a/addons/dialogue_manager/import_plugin.gd.uid b/addons/dialogue_manager/import_plugin.gd.uid new file mode 100644 index 0000000..e98bfab --- /dev/null +++ b/addons/dialogue_manager/import_plugin.gd.uid @@ -0,0 +1 @@ +uid://dhwpj6ed8soyq diff --git a/addons/dialogue_manager/inspector_plugin.gd b/addons/dialogue_manager/inspector_plugin.gd index e3cb7e5..a293353 100644 --- a/addons/dialogue_manager/inspector_plugin.gd +++ b/addons/dialogue_manager/inspector_plugin.gd @@ -1,5 +1,5 @@ @tool -extends EditorInspectorPlugin +class_name DMInspectorPlugin extends EditorInspectorPlugin const DialogueEditorProperty = preload("./components/editor_property/editor_property.gd") @@ -7,7 +7,7 @@ const DialogueEditorProperty = preload("./components/editor_property/editor_prop func _can_handle(object) -> bool: if object is GDScript: return false - if not object is Node: return false + if not object is Node and not object is Resource: return false if "name" in object and object.name == "Dialogue Manager": return false return true diff --git a/addons/dialogue_manager/inspector_plugin.gd.uid b/addons/dialogue_manager/inspector_plugin.gd.uid new file mode 100644 index 0000000..00c8db8 --- /dev/null +++ b/addons/dialogue_manager/inspector_plugin.gd.uid @@ -0,0 +1 @@ +uid://0x31sbqbikov diff --git a/addons/dialogue_manager/l10n/en.po b/addons/dialogue_manager/l10n/en.po index e4ef3b6..30fa090 100644 --- a/addons/dialogue_manager/l10n/en.po +++ b/addons/dialogue_manager/l10n/en.po @@ -5,7 +5,7 @@ msgstr "" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: \n" -"Language: de\n" +"Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -33,11 +33,17 @@ msgstr "Clear recent files" msgid "save_all_files" msgstr "Save all files" +msgid "all" +msgstr "All" + msgid "find_in_files" -msgstr "Find in files..." +msgstr "Find in Dialogue..." msgid "test_dialogue" -msgstr "Test dialogue" +msgstr "Test dialogue from start of file" + +msgid "test_dialogue_from_line" +msgstr "Test dialogue from current line" msgid "search_for_text" msgstr "Search for text" @@ -48,9 +54,6 @@ msgstr "Insert" msgid "translations" msgstr "Translations" -msgid "settings" -msgstr "Settings" - msgid "sponsor" msgstr "Sponsor" @@ -105,6 +108,15 @@ msgstr "End dialogue" msgid "generate_line_ids" msgstr "Generate line IDs" +msgid "use_uuid_only_for_ids" +msgstr "Use UUID only for IDs" + +msgid "set_id_prefix_length" +msgstr "Set ID prefix length" + +msgid "id_prefix_length" +msgstr "ID prefix length:" + msgid "save_characters_to_csv" msgstr "Save character names to CSV..." @@ -144,87 +156,12 @@ msgstr "Copy file path" msgid "buffer.show_in_filesystem" msgstr "Show in FileSystem" -msgid "settings.invalid_test_scene" -msgstr "\"{path}\" does not extend BaseDialogueTestScene." - -msgid "settings.revert_to_default_test_scene" -msgstr "Revert to default test scene" - -msgid "settings.default_balloon_hint" -msgstr "Custom balloon to use when calling \"DialogueManager.show_balloon()\"" - -msgid "settings.revert_to_default_balloon" -msgstr "Revert to default balloon" - -msgid "settings.default_balloon_path" -msgstr "<example balloon>" - -msgid "settings.autoload" -msgstr "Autoload" - -msgid "settings.path" -msgstr "Path" - -msgid "settings.new_template" -msgstr "New dialogue files will start with template text" - -msgid "settings.missing_keys" -msgstr "Treat missing translation keys as errors" - -msgid "settings.missing_keys_hint" -msgstr "If you are using static translation keys then having this enabled will help you find any lines that you haven't added a key to yet." - -msgid "settings.characters_translations" -msgstr "Export character names in translation files" - -msgid "settings.wrap_long_lines" -msgstr "Wrap long lines" - -msgid "settings.include_failed_responses" -msgstr "Include responses with failed conditions" - -msgid "settings.ignore_missing_state_values" -msgstr "Skip over missing state value errors (not recommended)" - -msgid "settings.custom_test_scene" -msgstr "Custom test scene (must extend BaseDialogueTestScene)" - -msgid "settings.default_csv_locale" -msgstr "Default CSV Locale" - -msgid "settings.states_shortcuts" -msgstr "State Shortcuts" - -msgid "settings.states_message" -msgstr "If an autoload is enabled here you can refer to its properties, methods, and signals without having to use its name." - -msgid "settings.states_hint" -msgstr "ie. Instead of \"SomeState.some_property\" you could just use \"some_property\"" - -msgid "settings.recompile_warning" -msgstr "Changing these settings will force a recompile of all dialogue. Only change them if you know what you are doing." - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "Create child dialogue line for responses with character names in them" - -msgid "settings.open_in_external_editor" -msgstr "Open dialogue files in external editor" - -msgid "settings.external_editor_warning" -msgstr "Note: Syntax highlighting and detailed error checking are not supported in external editors." - -msgid "settings.include_characters_in_translations" -msgstr "Include character names in translation exports" - -msgid "settings.include_notes_in_translations" -msgstr "Include notes (## comments) in translation exports" - -msgid "settings.check_for_updates" -msgstr "Check for updates" - msgid "n_of_n" msgstr "{index} of {total}" +msgid "search.find_in_dialogue" +msgstr "Find in Dialogue" + msgid "search.find" msgstr "Find:" @@ -384,6 +321,30 @@ msgstr "Invalid index." msgid "errors.unexpected_assignment" msgstr "Unexpected assignment." +msgid "errors.expected_when_or_else" +msgstr "Expecting a when or an else case." + +msgid "errors.only_one_else_allowed" +msgstr "Only one else case is allowed per match." + +msgid "errors.when_must_belong_to_match" +msgstr "When statements can only appear as children of match statements." + +msgid "errors.concurrent_line_without_origin" +msgstr "Concurrent lines need an origin line that doesn't start with \"| \"." + +msgid "errors.goto_not_allowed_on_concurrect_lines" +msgstr "Goto references are not allowed on concurrent dialogue lines." + +msgid "errors.unexpected_syntax_on_nested_dialogue_line" +msgstr "Nested dialogue lines may only contain dialogue." + +msgid "errors.err_nested_dialogue_invalid_jump" +msgstr "Only the last line of nested dialogue is allowed to include a jump." + +msgid "errors.missing_resource_for_autostart" +msgstr "You need to specify a dialogue resource when using auto-start." + msgid "errors.unknown" msgstr "Unknown syntax." @@ -478,4 +439,10 @@ msgid "runtime.unsupported_array_type" msgstr "Array[{type}] isn't supported in mutations. Use Array as a type instead." msgid "runtime.dialogue_balloon_missing_start_method" -msgstr "Your dialogue balloon is missing a \"start\" or \"Start\" method." \ No newline at end of file +msgstr "Your dialogue balloon is missing a \"start\" or \"Start\" method." + +msgid "runtime.top_level_states_share_name" +msgstr "Multiple top-level states ({states}) share method/property/signal name \"{key}\". Only the first occurance is accessible to dialogue." + +msgid "translation_plugin.character_name" +msgstr "Character name" \ No newline at end of file diff --git a/addons/dialogue_manager/l10n/es.po b/addons/dialogue_manager/l10n/es.po index 3cbaa32..6c6bffb 100644 --- a/addons/dialogue_manager/l10n/es.po +++ b/addons/dialogue_manager/l10n/es.po @@ -41,9 +41,6 @@ msgstr "Insertar" msgid "translations" msgstr "Traducciones" -msgid "settings" -msgstr "Ajustes" - msgid "show_support" msgstr "Contribuye con Dialogue Manager" @@ -95,6 +92,15 @@ msgstr "Finalizar diálogo" msgid "generate_line_ids" msgstr "Generar IDs de línea" +msgid "use_uuid_only_for_ids" +msgstr "Usar solo UUID como ID" + +msgid "set_id_prefix_length" +msgstr "Establecer la longitud del prefijo de ID" + +msgid "id_prefix_length" +msgstr "Longitud del prefijo de ID:" + msgid "save_characters_to_csv" msgstr "Guardar los nombres de los personajes en un archivo CSV..." @@ -134,82 +140,6 @@ msgstr "Copiar la ruta del archivo" msgid "buffer.show_in_filesystem" msgstr "Mostrar en el sistema de archivos" -msgid "settings.invalid_test_scene" -msgstr "\"{path}\" no extiende BaseDialogueTestScene." - -msgid "settings.revert_to_default_test_scene" -msgstr "Revertir a la escena de prueba por defecto" - -msgid "settings.default_balloon_hint" -msgstr "" -"Globo personalizado para usar al llamar a \"DialogueManager.show_balloon()\"" - -msgid "settings.revert_to_default_balloon" -msgstr "Volver al globo predeterminado" - -msgid "settings.default_balloon_path" -msgstr "<globo de ejemplo>" - -msgid "settings.autoload" -msgstr "Autocarga" - -msgid "settings.path" -msgstr "Ruta" - -msgid "settings.new_template" -msgstr "Los nuevos archivos de diálogo empezarán con una plantilla" - -msgid "settings.missing_keys" -msgstr "Tratar las claves de traducción faltantes como errores" - -msgid "settings.missing_keys_hint" -msgstr "Si estás utilizando claves de traducción estáticas, tener esta opción habilitada te ayudará a encontrar cualquier línea a la que aún no le hayas añadido una clave." - -msgid "settings.characters_translations" -msgstr "Exportar nombres de personajes en archivos de traducción" - -msgid "settings.wrap_long_lines" -msgstr "Romper líneas largas" - -msgid "settings.include_failed_responses" -msgstr "Incluir respuestas con condiciones fallidas" - -msgid "settings.ignore_missing_state_values" -msgstr "Omitir errores de valores de estado faltantes (no recomendado)" - -msgid "settings.custom_test_scene" -msgstr "Escena de prueba personalizada (debe extender BaseDialogueTestScene)" - -msgid "settings.default_csv_locale" -msgstr "Localización CSV por defecto" - -msgid "settings.states_shortcuts" -msgstr "Atajos de teclado" - -msgid "settings.states_message" -msgstr "Si un autoload está habilitado aquí, puedes referirte a sus propiedades y métodos sin tener que usar su nombre." - -msgid "settings.states_hint" -msgstr "ie. En lugar de \"SomeState.some_property\" podría simplemente usar \"some_property\"" - -msgid "settings.recompile_warning" -msgstr "Cambiar estos ajustes obligará a recompilar todo el diálogo. Hazlo solo si sabes lo que estás haciendo." - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "Crear línea de diálogo para respuestas con nombres de personajes dentro." - -msgid "settings.open_in_external_editor" -msgstr "Abrir archivos de diálogo en el editor externo" - -msgid "settings.external_editor_warning" -msgstr "Nota: El resaltado de sintaxis y la verificación detallada de errores no están soportados en editores externos." - -msgid "settings.include_characters_in_translations" -msgstr "Incluir nombres de personajes en las exportaciones de traducción" - -msgid "settings.include_notes_in_translations" -msgstr "Incluir notas (## comentarios) en las exportaciones de traducción" - msgid "n_of_n" msgstr "{index} de {total}" diff --git a/addons/dialogue_manager/l10n/translations.pot b/addons/dialogue_manager/l10n/translations.pot index b6c3197..b2ab349 100644 --- a/addons/dialogue_manager/l10n/translations.pot +++ b/addons/dialogue_manager/l10n/translations.pot @@ -26,12 +26,18 @@ msgstr "" msgid "save_all_files" msgstr "" +msgid "all" +msgstr "" + msgid "find_in_files" msgstr "" msgid "test_dialogue" msgstr "" +msgid "test_dialogue_from_line" +msgstr "" + msgid "search_for_text" msgstr "" @@ -41,9 +47,6 @@ msgstr "" msgid "translations" msgstr "" -msgid "settings" -msgstr "" - msgid "sponsor" msgstr "" @@ -98,6 +101,15 @@ msgstr "" msgid "generate_line_ids" msgstr "" +msgid "use_uuid_only_for_ids" +msgstr "" + +msgid "set_id_prefix_length" +msgstr "" + +msgid "id_prefix_length" +msgstr "" + msgid "save_to_csv" msgstr "" @@ -134,87 +146,12 @@ msgstr "" msgid "buffer.show_in_filesystem" msgstr "" -msgid "settings.invalid_test_scene" -msgstr "" - -msgid "settings.revert_to_default_test_scene" -msgstr "" - -msgid "settings.default_balloon_hint" -msgstr "" - -msgid "settings.revert_to_default_balloon" -msgstr "" - -msgid "settings.default_balloon_path" -msgstr "" - -msgid "settings.autoload" -msgstr "" - -msgid "settings.path" -msgstr "" - -msgid "settings.new_template" -msgstr "" - -msgid "settings.missing_keys" -msgstr "" - -msgid "settings.missing_keys_hint" -msgstr "" - -msgid "settings.characters_translations" -msgstr "" - -msgid "settings.wrap_long_lines" -msgstr "" - -msgid "settings.include_failed_responses" -msgstr "" - -msgid "settings.ignore_missing_state_values" -msgstr "" - -msgid "settings.custom_test_scene" -msgstr "" - -msgid "settings.default_csv_locale" -msgstr "" - -msgid "settings.states_shortcuts" -msgstr "" - -msgid "settings.states_message" -msgstr "" - -msgid "settings.states_hint" -msgstr "" - -msgid "settings.recompile_warning" -msgstr "" - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "" - -msgid "settings.open_in_external_editor" -msgstr "" - -msgid "settings.external_editor_warning" -msgstr "" - -msgid "settings.include_characters_in_translations" -msgstr "" - -msgid "settings.include_notes_in_translations" -msgstr "" - -msgid "settings.check_for_updates" -msgstr "" - msgid "n_of_n" msgstr "" +msgid "search.find_in_dialogue" +msgstr "" + msgid "search.find" msgstr "" @@ -374,6 +311,30 @@ msgstr "" msgid "errors.unexpected_assignment" msgstr "" +msgid "errors.expected_when_or_else" +msgstr "" + +msgid "errors.only_one_else_allowed" +msgstr "" + +msgid "errors.when_must_belong_to_match" +msgstr "" + +msgid "errors.concurrent_line_without_origin" +msgstr "" + +msgid "errors.goto_not_allowed_on_concurrect_lines" +msgstr "" + +msgid "errors.unexpected_syntax_on_nested_dialogue_line" +msgstr "" + +msgid "errors.err_nested_dialogue_invalid_jump" +msgstr "" + +msgid "errors.missing_resource_for_autostart" +msgstr "" + msgid "errors.unknown" msgstr "" @@ -468,4 +429,10 @@ msgid "runtime.unsupported_array_type" msgstr "" msgid "runtime.dialogue_balloon_missing_start_method" +msgstr "" + +msgid "runtime.top_level_states_share_name" +msgstr "" + +msgid "translation_plugin.character_name" msgstr "" \ No newline at end of file diff --git a/addons/dialogue_manager/l10n/uk.po b/addons/dialogue_manager/l10n/uk.po index e6404d7..43dd51b 100644 --- a/addons/dialogue_manager/l10n/uk.po +++ b/addons/dialogue_manager/l10n/uk.po @@ -3,13 +3,13 @@ msgstr "" "Project-Id-Version: Dialogue Manager\n" "POT-Creation-Date: \n" "PO-Revision-Date: \n" -"Last-Translator: veydzh3r <veydzherdgswift008@gmail.com>\n" +"Last-Translator: \n" "Language-Team: \n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.5\n" +"X-Generator: Poedit 3.2.2\n" msgid "start_a_new_file" msgstr "Створити новий файл" @@ -36,7 +36,10 @@ msgid "find_in_files" msgstr "Знайти у файлах..." msgid "test_dialogue" -msgstr "Відтворити діалог" +msgstr "Протестувати діалог з початку файлу" + +msgid "test_dialogue_from_line" +msgstr "Протестувати діалог з поточного рядка" msgid "search_for_text" msgstr "Пошук тексту" @@ -47,9 +50,6 @@ msgstr "Вставити" msgid "translations" msgstr "Переклади" -msgid "settings" -msgstr "Налаштування" - msgid "sponsor" msgstr "Спонсор" @@ -104,6 +104,15 @@ msgstr "Кінець діалогу" msgid "generate_line_ids" msgstr "Згенерувати ідентифікатори рядків" +msgid "use_uuid_only_for_ids" +msgstr "Використовувати лише UUID як ID" + +msgid "set_id_prefix_length" +msgstr "Встановити довжину префікса ID" + +msgid "id_prefix_length" +msgstr "Довжина префікса ID:" + msgid "save_characters_to_csv" msgstr "Зберегти імена персонажів в CSV..." @@ -143,97 +152,6 @@ msgstr "Копіювати шлях файлу" msgid "buffer.show_in_filesystem" msgstr "Показати у файловій системі" -msgid "settings.invalid_test_scene" -msgstr "«{path}» не у розширенні BaseDialogueTestScene." - -msgid "settings.revert_to_default_test_scene" -msgstr "Повернути до типової тестової сцени" - -msgid "settings.default_balloon_hint" -msgstr "" -"Нестандартна куля для використання під час виклику «DialogueManager." -"show_balloon()»" - -msgid "settings.revert_to_default_balloon" -msgstr "Повернути до типової кулі" - -msgid "settings.default_balloon_path" -msgstr "<приклад кулі>" - -msgid "settings.autoload" -msgstr "Автозавантаження" - -msgid "settings.path" -msgstr "Шлях" - -msgid "settings.new_template" -msgstr "Нові файли діалогів починатимуться з шаблонного тексту" - -msgid "settings.missing_keys" -msgstr "Вважати відсутні ключі перекладу як помилками" - -msgid "settings.missing_keys_hint" -msgstr "" -"Якщо ви використовуєте статичні ключі перекладу, увімкнення цього параметра " -"допоможе вам знайти рядки, до яких ви ще не додали ключ." - -msgid "settings.characters_translations" -msgstr "Експортовувати імена персонажів у файли перекладу" - -msgid "settings.wrap_long_lines" -msgstr "Переносити довгі рядки" - -msgid "settings.include_failed_responses" -msgstr "Включати відповіді з невдалими умовами" - -msgid "settings.ignore_missing_state_values" -msgstr "Пропускати помилки пропущених значень стану (не рекомендується)" - -msgid "settings.custom_test_scene" -msgstr "" -"Користувацька тестова сцена (має мати розширення «BaseDialogueTestScene»)" - -msgid "settings.default_csv_locale" -msgstr "Типова мова файлу CSV" - -msgid "settings.states_shortcuts" -msgstr "Скорочення станів" - -msgid "settings.states_message" -msgstr "" -"Якщо автозавантаження увімкнено, ви можете звертатися до його властивостей і " -"методів без необхідності використовувати його назву." - -msgid "settings.states_hint" -msgstr "" -"Тобто, замість «ЯкийсьСтан.якась_властивість» ви можете просто " -"використовувати «якась_властивість»" - -msgid "settings.recompile_warning" -msgstr "" -"Зміна цих параметрів призведе до перекомпіляції усіх діалогів. Змінюйте їх, " -"тільки якщо ви знаєте, що робите." - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "Створювати дочірній рядок діалогу для відповідей з іменами персонажів" - -msgid "settings.open_in_external_editor" -msgstr "Відкрити файли діалогів у зовнішньому редакторі" - -msgid "settings.external_editor_warning" -msgstr "" -"Примітка: підсвічування синтаксису та детальна перевірка помилок не " -"підтримуються у зовнішніх редакторах." - -msgid "settings.include_characters_in_translations" -msgstr "Включати імена персонажів до експорту перекладу" - -msgid "settings.include_notes_in_translations" -msgstr "Включати примітки (## коментарі) до експорту перекладу" - -msgid "settings.check_for_updates" -msgstr "Перевіряти наявність оновлень" - msgid "n_of_n" msgstr "{index} з {total}" @@ -289,8 +207,7 @@ msgid "errors_in_script" msgstr "У вашому скрипті є помилки. Виправте їх і спробуйте ще раз." msgid "errors_with_build" -msgstr "" -"Вам потрібно виправити помилки в діалогах, перш ніж ви зможете запустити гру." +msgstr "Вам потрібно виправити помилки в діалогах, перш ніж ви зможете запустити гру." msgid "errors.import_errors" msgstr "В імпортованому файлі є помилки." @@ -397,6 +314,27 @@ msgstr "Недійсний індекс." msgid "errors.unexpected_assignment" msgstr "Несподіване призначення." +msgid "errors.expected_when_or_else" +msgstr "Очікувався випадок «when» або «else»." + +msgid "errors.only_one_else_allowed" +msgstr "Для кожного «match» допускається лише один випадок «else»." + +msgid "errors.when_must_belong_to_match" +msgstr "Оператори «when» можуть з’являтися лише як дочірні операторів «match»." + +msgid "errors.concurrent_line_without_origin" +msgstr "Паралельні рядки потребують початкового рядка, який не починається з «|»." + +msgid "errors.goto_not_allowed_on_concurrect_lines" +msgstr "У паралельних діалогових рядках не допускаються Goto посилання." + +msgid "errors.unexpected_syntax_on_nested_dialogue_line" +msgstr "Вкладені рядки діалогу можуть містити лише діалог." + +msgid "errors.err_nested_dialogue_invalid_jump" +msgstr "Лише останній рядок вкладеного діалогу може містити перехід." + msgid "errors.unknown" msgstr "Невідомий синтаксис." @@ -446,9 +384,7 @@ msgid "runtime.error_detail" msgstr "Рядок {line}: {message}" msgid "runtime.errors_see_details" -msgstr "" -"У тексті діалогу було виявлено помилки ({count}). Див. детальніше у розділі " -"«Вивід»." +msgstr "У тексті діалогу було виявлено помилки ({count}). Див. детальніше у розділі «Вивід»." msgid "runtime.invalid_expression" msgstr "«{expression}» не є допустимим виразом: {error}" @@ -463,29 +399,16 @@ msgid "runtime.key_not_found" msgstr "Ключ «{key}» не знайдено у словнику «{dictionary}»" msgid "runtime.property_not_found" -msgstr "" -"«{property}» не знайдено. Стани з безпосередньо доступними властивостями/" -"методами/сигналами включають {states}. На автозавантаження потрібно " -"посилатися за їхніми назвами для використання їхніх властивостей." +msgstr "«{property}» не знайдено. Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей." msgid "runtime.property_not_found_missing_export" -msgstr "" -"«{property}» не знайдено. Можливо, вам слід додати декоратор «[Export]». " -"Стани з безпосередньо доступними властивостями/методами/сигналами включають " -"{states}. На автозавантаження потрібно посилатися за їхніми назвами для " -"використання їхніх властивостей." +msgstr "«{property}» не знайдено. Можливо, вам слід додати декоратор «[Export]». Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей." msgid "runtime.method_not_found" -msgstr "" -"Метод «{method}» не знайдено. Стани з безпосередньо доступними властивостями/" -"методами/сигналами включають {states}. На автозавантаження потрібно " -"посилатися за їхніми назвами для використання їхніх властивостей." +msgstr "Метод «{method}» не знайдено. Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей." msgid "runtime.signal_not_found" -msgstr "" -"Сигнал «{signal_name}» не знайдено. Стани з безпосередньо доступними " -"властивостями/методами/сигналами включають {states}. На автозавантаження " -"потрібно посилатися за їхніми назвами для використання їхніх властивостей." +msgstr "Сигнал «{signal_name}» не знайдено. Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей." msgid "runtime.method_not_callable" msgstr "«{method}» не є методом, який можна викликати в «{object}»" @@ -500,14 +423,16 @@ msgid "runtime.something_went_wrong" msgstr "Щось пішло не так." msgid "runtime.expected_n_got_n_args" -msgstr "" -"«{method}» було викликано з аргументами «{received}», але воно має лише " -"«{expected}»." +msgstr "«{method}» було викликано з аргументами «{received}», але воно має лише «{expected}»." msgid "runtime.unsupported_array_type" -msgstr "" -"Array[{type}] не підтримується у модифікаціях. Натомість використовуйте " -"Array як тип." +msgstr "Array[{type}] не підтримується у модифікаціях. Натомість використовуйте Array як тип." msgid "runtime.dialogue_balloon_missing_start_method" msgstr "У вашій кулі діалогу відсутній метод «start» або «Start»." + +msgid "runtime.top_level_states_share_name" +msgstr "Кілька станів верхнього рівня ({states}) мають спільну назву методу/властивості/сигналу «{key}». Для діалогу доступний лише перший випадок." + +msgid "translation_plugin.character_name" +msgstr "Ім’я персонажа" diff --git a/addons/dialogue_manager/l10n/zh.po b/addons/dialogue_manager/l10n/zh.po index e381944..2a48a51 100644 --- a/addons/dialogue_manager/l10n/zh.po +++ b/addons/dialogue_manager/l10n/zh.po @@ -44,9 +44,6 @@ msgstr "插入" msgid "translations" msgstr "翻译" -msgid "settings" -msgstr "设置" - msgid "show_support" msgstr "支持 Dialogue Manager" @@ -98,6 +95,15 @@ msgstr "结束对话" msgid "generate_line_ids" msgstr "生成行 ID" +msgid "use_uuid_only_for_ids" +msgstr "仅使用UUID作为ID" + +msgid "set_id_prefix_length" +msgstr "设置ID前缀长度" + +msgid "id_prefix_length" +msgstr "ID前缀长度:" + msgid "save_characters_to_csv" msgstr "保存角色到 CSV" @@ -137,69 +143,6 @@ msgstr "复制文件路径" msgid "buffer.show_in_filesystem" msgstr "在 Godot 侧边栏中显示" -msgid "settings.revert_to_default_test_scene" -msgstr "重置测试场景设定" - -msgid "settings.default_balloon_hint" -msgstr "设置调用 \"DialogueManager.show_balloon()\" 时使用的对话框" - -msgid "settings.autoload" -msgstr "Autoload" - -msgid "settings.path" -msgstr "路径" - -msgid "settings.new_template" -msgstr "新建文件时自动插入模板" - -msgid "settings.missing_keys" -msgstr "将翻译键缺失视为错误" - -msgid "settings.missing_keys_hint" -msgstr "如果你使用静态键,这将会帮助你寻找未添加至翻译文件的键。" - -msgid "settings.characters_translations" -msgstr "在翻译文件中导出角色名" - -msgid "settings.wrap_long_lines" -msgstr "文本编辑器自动换行" - -msgid "settings.include_failed_responses" -msgstr "在判断条件失败时仍显示回复选项" - -msgid "settings.ignore_missing_state_values" -msgstr "忽略全局变量缺失错误(不建议)" - -msgid "settings.custom_test_scene" -msgstr "自定义测试场景(必须继承自BaseDialogueTestScene)" - -msgid "settings.default_csv_locale" -msgstr "默认 CSV 区域格式" - -msgid "settings.states_shortcuts" -msgstr "全局变量映射" - -msgid "settings.states_message" -msgstr "当一个 Autoload 在这里被勾选,他的所有成员会被映射为全局变量。" - -msgid "settings.states_hint" -msgstr "比如,当你开启对于“Foo”的映射时,你可以将“Foo.bar”简写成“bar”。" - -msgid "settings.recompile_warning" -msgstr "更改这些选项会强制重新编译所有的对话框,当你清楚在做什么的时候更改。" - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "回复项带角色名时(- char: response),会自动生成为选择后的下一句对话" - -msgid "settings.include_characters_in_translations" -msgstr "导出 CSV 时包括角色名" - -msgid "settings.include_notes_in_translations" -msgstr "导出 CSV 时包括注释(## comments)" - -msgid "settings.check_for_updates" -msgstr "检查升级" - msgid "n_of_n" msgstr "第{index}个,共{total}个" diff --git a/addons/dialogue_manager/l10n/zh_TW.po b/addons/dialogue_manager/l10n/zh_TW.po index f4ce038..1be9813 100644 --- a/addons/dialogue_manager/l10n/zh_TW.po +++ b/addons/dialogue_manager/l10n/zh_TW.po @@ -44,9 +44,6 @@ msgstr "插入" msgid "translations" msgstr "翻譯" -msgid "settings" -msgstr "設定" - msgid "show_support" msgstr "支援 Dialogue Manager" @@ -98,6 +95,15 @@ msgstr "結束對話" msgid "generate_line_ids" msgstr "生成行 ID" +msgid "use_uuid_only_for_ids" +msgstr "僅使用 UUID 作為 ID" + +msgid "set_id_prefix_length" +msgstr "設定 ID 前綴長度" + +msgid "id_prefix_length" +msgstr "ID 前綴長度:" + msgid "save_characters_to_csv" msgstr "保存角色到 CSV" @@ -137,69 +143,6 @@ msgstr "複製檔案位置" msgid "buffer.show_in_filesystem" msgstr "在 Godot 側邊欄中顯示" -msgid "settings.revert_to_default_test_scene" -msgstr "重置測試場景設定" - -msgid "settings.default_balloon_hint" -msgstr "設置使用 \"DialogueManager.show_balloon()\" 时的对话框" - -msgid "settings.autoload" -msgstr "Autoload" - -msgid "settings.path" -msgstr "路徑" - -msgid "settings.new_template" -msgstr "新建檔案時自動插入模板" - -msgid "settings.missing_keys" -msgstr "將翻譯鍵缺失視爲錯誤" - -msgid "settings.missing_keys_hint" -msgstr "如果你使用靜態鍵,這將會幫助你尋找未添加至翻譯檔案的鍵。" - -msgid "settings.wrap_long_lines" -msgstr "自動折行" - -msgid "settings.characters_translations" -msgstr "在翻譯檔案中匯出角色名。" - -msgid "settings.include_failed_responses" -msgstr "在判斷條件失敗時仍顯示回復選項" - -msgid "settings.ignore_missing_state_values" -msgstr "忽略全局變量缺失錯誤(不建議)" - -msgid "settings.custom_test_scene" -msgstr "自訂測試場景(必須繼承自BaseDialogueTestScene)" - -msgid "settings.default_csv_locale" -msgstr "預設 CSV 區域格式" - -msgid "settings.states_shortcuts" -msgstr "全局變量映射" - -msgid "settings.states_message" -msgstr "當一個 Autoload 在這裏被勾選,他的所有成員會被映射爲全局變量。" - -msgid "settings.states_hint" -msgstr "比如,當你開啓對於“Foo”的映射時,你可以將“Foo.bar”簡寫成“bar”。" - -msgid "settings.recompile_warning" -msgstr "更改這些選項會強制重新編譯所有的對話框,當你清楚在做什麼的時候更改。" - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "回覆項目帶角色名稱時(- char: response),會自動產生為選擇後的下一句對話" - -msgid "settings.include_characters_in_translations" -msgstr "匯出 CSV 時包含角色名" - -msgid "settings.include_notes_in_translations" -msgstr "匯出 CSV 時包括註解(## comments)" - -msgid "settings.check_for_updates" -msgstr "檢查升級" - msgid "n_of_n" msgstr "第{index}個,共{total}個" diff --git a/addons/dialogue_manager/plugin.cfg b/addons/dialogue_manager/plugin.cfg index a6538d2..40dad2c 100644 --- a/addons/dialogue_manager/plugin.cfg +++ b/addons/dialogue_manager/plugin.cfg @@ -1,7 +1,7 @@ [plugin] name="Dialogue Manager" -description="A simple but powerful branching dialogue system" +description="A powerful nonlinear dialogue system" author="Nathan Hoad" -version="2.45.0" +version="3.9.0" script="plugin.gd" diff --git a/addons/dialogue_manager/plugin.cfg.uid b/addons/dialogue_manager/plugin.cfg.uid new file mode 100644 index 0000000..312d0cf --- /dev/null +++ b/addons/dialogue_manager/plugin.cfg.uid @@ -0,0 +1 @@ +uid://hrny2utekhei diff --git a/addons/dialogue_manager/plugin.gd b/addons/dialogue_manager/plugin.gd index f89fad4..6f1f5d9 100644 --- a/addons/dialogue_manager/plugin.gd +++ b/addons/dialogue_manager/plugin.gd @@ -2,93 +2,69 @@ extends EditorPlugin -const DialogueConstants = preload("./constants.gd") -const DialogueImportPlugin = preload("./import_plugin.gd") -const DialogueInspectorPlugin = preload("./inspector_plugin.gd") -const DialogueTranslationParserPlugin = preload("./editor_translation_parser_plugin.gd") -const DialogueSettings = preload("./settings.gd") -const DialogueCache = preload("./components/dialogue_cache.gd") const MainView = preload("./views/main_view.tscn") -const DialogueResource = preload("./dialogue_resource.gd") +const FindInDialogueView = preload("./views/find_in_dialogue_view.tscn") -var import_plugin: DialogueImportPlugin -var inspector_plugin: DialogueInspectorPlugin -var translation_parser_plugin: DialogueTranslationParserPlugin -var main_view -var dialogue_cache: DialogueCache +var import_plugin: DMImportPlugin +var export_plugin: DMExportPlugin +var inspector_plugin: DMInspectorPlugin +var translation_parser_plugin: DMTranslationParserPlugin +var main_view: Control +var find_in_dialogue_view: Control +var dialogue_cache: DMCache + + +func _enable_plugin() -> void: + add_autoload_singleton("DialogueManager", get_plugin_path() + "/dialogue_manager.gd") + _housekeeping() + + +func _disable_plugin() -> void: + remove_autoload_singleton("DialogueManager") func _enter_tree() -> void: - add_autoload_singleton("DialogueManager", get_plugin_path() + "/dialogue_manager.gd") - if Engine.is_editor_hint(): Engine.set_meta("DialogueManagerPlugin", self) - DialogueSettings.prepare() + DMSettings.prepare() - dialogue_cache = DialogueCache.new() - Engine.set_meta("DialogueCache", dialogue_cache) + dialogue_cache = DMCache.new() + Engine.set_meta("DMCache", dialogue_cache) - import_plugin = DialogueImportPlugin.new() + import_plugin = DMImportPlugin.new() add_import_plugin(import_plugin) - inspector_plugin = DialogueInspectorPlugin.new() + export_plugin = DMExportPlugin.new() + add_export_plugin(export_plugin) + + inspector_plugin = DMInspectorPlugin.new() add_inspector_plugin(inspector_plugin) - translation_parser_plugin = DialogueTranslationParserPlugin.new() + translation_parser_plugin = DMTranslationParserPlugin.new() add_translation_parser_plugin(translation_parser_plugin) main_view = MainView.instantiate() - get_editor_interface().get_editor_main_screen().add_child(main_view) + EditorInterface.get_editor_main_screen().add_child(main_view) _make_visible(false) main_view.add_child(dialogue_cache) _update_localization() - get_editor_interface().get_file_system_dock().files_moved.connect(_on_files_moved) - get_editor_interface().get_file_system_dock().file_removed.connect(_on_file_removed) + EditorInterface.get_file_system_dock().files_moved.connect(_on_files_moved) + EditorInterface.get_file_system_dock().file_removed.connect(_on_file_removed) add_tool_menu_item("Create copy of dialogue example balloon...", _copy_dialogue_balloon) - # Make sure the current balloon has a UID unique from the example balloon's - var balloon_path: String = DialogueSettings.get_setting("balloon_path", "") - if balloon_path != "" and FileAccess.file_exists(balloon_path): - var is_small_window: bool = ProjectSettings.get_setting("display/window/size/viewport_width") < 400 - var example_balloon_file_name: String = "small_example_balloon.tscn" if is_small_window else "example_balloon.tscn" - var example_balloon_path: String = get_plugin_path() + "/example_balloon/" + example_balloon_file_name - var example_balloon_uid: String = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(example_balloon_path)) - var balloon_uid: String = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(balloon_path)) - if example_balloon_uid == balloon_uid: - var new_balloon_uid: String = ResourceUID.id_to_text(ResourceUID.create_id()) - var contents: String = FileAccess.get_file_as_string(balloon_path) - contents = contents.replace(example_balloon_uid, new_balloon_uid) - var balloon_file: FileAccess = FileAccess.open(balloon_path, FileAccess.WRITE) - balloon_file.store_string(contents) - balloon_file.close() - - # Prevent the project from showing as unsaved even though it was only just opened - if DialogueSettings.get_setting("try_suppressing_startup_unsaved_indicator", false) \ - and Engine.get_physics_frames() == 0 \ - and get_editor_interface().has_method("save_all_scenes"): - var timer: Timer = Timer.new() - var suppress_unsaved_marker: Callable - suppress_unsaved_marker = func(): - if Engine.get_frames_per_second() >= 10: - timer.stop() - get_editor_interface().call("save_all_scenes") - timer.queue_free() - timer.timeout.connect(suppress_unsaved_marker) - add_child(timer) - timer.start(0.1) - func _exit_tree() -> void: - remove_autoload_singleton("DialogueManager") - remove_import_plugin(import_plugin) import_plugin = null + remove_export_plugin(export_plugin) + export_plugin = null + remove_inspector_plugin(inspector_plugin) inspector_plugin = null @@ -98,11 +74,13 @@ func _exit_tree() -> void: if is_instance_valid(main_view): main_view.queue_free() - Engine.remove_meta("DialogueManagerPlugin") - Engine.remove_meta("DialogueCache") + hide_find_in_dialogue() - get_editor_interface().get_file_system_dock().files_moved.disconnect(_on_files_moved) - get_editor_interface().get_file_system_dock().file_removed.disconnect(_on_file_removed) + Engine.remove_meta("DialogueManagerPlugin") + Engine.remove_meta("DMCache") + + EditorInterface.get_file_system_dock().files_moved.disconnect(_on_files_moved) + EditorInterface.get_file_system_dock().file_removed.disconnect(_on_file_removed) remove_tool_menu_item("Create copy of dialogue example balloon...") @@ -125,10 +103,10 @@ func _get_plugin_icon() -> Texture2D: func _handles(object) -> bool: - var editor_settings: EditorSettings = get_editor_interface().get_editor_settings() + var editor_settings: EditorSettings = EditorInterface.get_editor_settings() var external_editor: String = editor_settings.get_setting("text_editor/external/exec_path") var use_external_editor: bool = editor_settings.get_setting("text_editor/external/use_external_editor") and external_editor != "" - if object is DialogueResource and use_external_editor and DialogueSettings.get_user_value("open_in_external_editor", false): + if object is DialogueResource and use_external_editor and DMSettings.get_user_value("open_in_external_editor", false): var project_path: String = ProjectSettings.globalize_path("res://") var file_path: String = ProjectSettings.globalize_path(object.resource_path) OS.create_process(external_editor, [project_path, file_path]) @@ -148,12 +126,21 @@ func _apply_changes() -> void: _update_localization() +func _save_external_data() -> void: + if is_instance_valid(main_view) and EditorInterface.get_editor_settings().get_setting("run/auto_save/save_before_running"): + main_view.apply_changes() + _update_localization() + + if dialogue_cache != null: + dialogue_cache.reimport_files() + + func _build() -> bool: # If this is the dotnet Godot then we need to check if the solution file exists - DialogueSettings.check_for_dotnet_solution() + DMSettings.check_for_dotnet_solution() # Ignore errors in other files if we are just running the test scene - if DialogueSettings.get_user_value("is_running_test_scene", true): return true + if DMSettings.get_user_value("is_running_test_scene", true): return true if dialogue_cache != null: dialogue_cache.reimport_files() @@ -162,13 +149,29 @@ func _build() -> bool: if files_with_errors.size() > 0: for dialogue_file in files_with_errors: push_error("You have %d error(s) in %s" % [dialogue_file.errors.size(), dialogue_file.path]) - get_editor_interface().edit_resource(load(files_with_errors[0].path)) + EditorInterface.edit_resource(load(files_with_errors[0].path)) main_view.show_build_error_dialog() return false return true +func show_find_in_dialogue() -> void: + if not is_instance_valid(find_in_dialogue_view): + find_in_dialogue_view = FindInDialogueView.instantiate() + find_in_dialogue_view.main_view = main_view + find_in_dialogue_view.result_selected.connect(main_view._on_find_in_files_result_selected) + add_control_to_bottom_panel(find_in_dialogue_view, DMConstants.translate(&"search.find_in_dialogue")) + make_bottom_panel_item_visible(find_in_dialogue_view) + find_in_dialogue_view.prepare() + + +func hide_find_in_dialogue() -> void: + if is_instance_valid(find_in_dialogue_view): + remove_control_from_bottom_panel(find_in_dialogue_view) + find_in_dialogue_view.queue_free() + + ## Get the shortcuts used by the plugin func get_editor_shortcuts() -> Dictionary: var shortcuts: Dictionary = { @@ -206,17 +209,25 @@ func get_editor_shortcuts() -> Dictionary: ], text_size_reset = [ _create_event("Ctrl+0") + ], + + make_bold = [ + _create_event("Ctrl+Shift+b") + ], + make_italic = [ + _create_event("Ctrl+Shift+i") ] } - var paths = get_editor_interface().get_editor_paths() - var settings - if FileAccess.file_exists(paths.get_config_dir() + "/editor_settings-4.3.tres"): - settings = load(paths.get_config_dir() + "/editor_settings-4.3.tres") - elif FileAccess.file_exists(paths.get_config_dir() + "/editor_settings-4.tres"): - settings = load(paths.get_config_dir() + "/editor_settings-4.tres") - else: - return shortcuts + var paths = EditorInterface.get_editor_paths() + var settings: Resource = null + for version in ["4.5", "4.4", "4.3", "4"]: + var path: String = paths.get_config_dir() + "/editor_settings-" + version + ".tres" + if FileAccess.file_exists(path): + settings = load(path) + break + + if settings == null: return shortcuts for s in settings.get("shortcuts"): for key in shortcuts: @@ -283,7 +294,7 @@ func update_import_paths(from_path: String, to_path: String) -> void: # Update the live buffer if main_view.current_file_path == dependent.path: main_view.code_edit.text = main_view.code_edit.text.replace(from_path, to_path) - main_view.pristine_text = main_view.code_edit.text + main_view.open_buffers[main_view.current_file_path].pristine_text = main_view.code_edit.text # Open the file and update the path var file: FileAccess = FileAccess.open(dependent.path, FileAccess.READ) @@ -296,6 +307,9 @@ func update_import_paths(from_path: String, to_path: String) -> void: func _update_localization() -> void: + if not DMSettings.get_setting(DMSettings.UPDATE_POT_FILES_AUTOMATICALLY, true): + return + var dialogue_files = dialogue_cache.get_files() # Add any new files to POT generation @@ -323,7 +337,7 @@ func _update_localization() -> void: func _copy_dialogue_balloon() -> void: - var scale: float = get_editor_interface().get_editor_scale() + var scale: float = EditorInterface.get_editor_scale() var directory_dialog: FileDialog = FileDialog.new() var label: Label = Label.new() label.text = "Dialogue balloon files will be copied into chosen directory." @@ -332,8 +346,8 @@ func _copy_dialogue_balloon() -> void: directory_dialog.min_size = Vector2(600, 500) * scale directory_dialog.dir_selected.connect(func(path): var plugin_path: String = get_plugin_path() + var is_dotnet: bool = DMSettings.check_for_dotnet_solution() - var is_dotnet: bool = DialogueSettings.check_for_dotnet_solution() var balloon_path: String = path + ("/Balloon.tscn" if is_dotnet else "/balloon.tscn") var balloon_script_path: String = path + ("/DialogueBalloon.cs" if is_dotnet else "/balloon.gd") @@ -342,19 +356,12 @@ func _copy_dialogue_balloon() -> void: var example_balloon_file_name: String = "small_example_balloon.tscn" if is_small_window else "example_balloon.tscn" var example_balloon_path: String = plugin_path + "/example_balloon/" + example_balloon_file_name var example_balloon_script_file_name: String = "ExampleBalloon.cs" if is_dotnet else "example_balloon.gd" - var file_contents: String = FileAccess.get_file_as_string(example_balloon_path).replace(plugin_path + "/example_balloon/example_balloon.gd", balloon_script_path) - # Give the balloon a unique UID + var example_balloon_script_uid: String = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(plugin_path + "/example_balloon/example_balloon.gd")) var example_balloon_uid: String = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(example_balloon_path)) - var new_balloon_uid: String = ResourceUID.id_to_text(ResourceUID.create_id()) - file_contents = file_contents.replace(example_balloon_uid, new_balloon_uid) - # Save the new balloon - var file: FileAccess = FileAccess.open(balloon_path, FileAccess.WRITE) - file.store_string(file_contents) - file.close() # Copy the script file - file = FileAccess.open(plugin_path + "/example_balloon/" + example_balloon_script_file_name, FileAccess.READ) - file_contents = file.get_as_text() + var file: FileAccess = FileAccess.open(plugin_path + "/example_balloon/" + example_balloon_script_file_name, FileAccess.READ) + var file_contents: String = file.get_as_text() if is_dotnet: file_contents = file_contents.replace("class ExampleBalloon", "class DialogueBalloon") else: @@ -362,24 +369,98 @@ func _copy_dialogue_balloon() -> void: file = FileAccess.open(balloon_script_path, FileAccess.WRITE) file.store_string(file_contents) file.close() + var new_balloon_script_uid_raw: int = ResourceUID.create_id() + ResourceUID.add_id(new_balloon_script_uid_raw, balloon_script_path) + var new_balloon_script_uid: String = ResourceUID.id_to_text(new_balloon_script_uid_raw) - get_editor_interface().get_resource_filesystem().scan() - get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", balloon_path) + # Save the new balloon + file_contents = FileAccess.get_file_as_string(example_balloon_path) + if "example_balloon.gd" in file_contents: + file_contents = file_contents.replace(plugin_path + "/example_balloon/example_balloon.gd", balloon_script_path) + else: + file_contents = file_contents.replace(plugin_path + "/example_balloon/ExampleBalloon.cs", balloon_script_path) + var new_balloon_uid: String = ResourceUID.id_to_text(ResourceUID.create_id()) + file_contents = file_contents.replace(example_balloon_uid, new_balloon_uid).replace(example_balloon_script_uid, new_balloon_script_uid) + file = FileAccess.open(balloon_path, FileAccess.WRITE) + file.store_string(file_contents) + file.close() - DialogueSettings.set_setting("balloon_path", balloon_path) + EditorInterface.get_resource_filesystem().scan() + EditorInterface.get_file_system_dock().call_deferred("navigate_to_path", balloon_path) + + DMSettings.set_setting(DMSettings.BALLOON_PATH, balloon_path) directory_dialog.queue_free() ) - get_editor_interface().get_base_control().add_child(directory_dialog) + EditorInterface.get_base_control().add_child(directory_dialog) directory_dialog.popup_centered() +# Tidy up some things that may have gotten messy between versions. +func _housekeeping() -> void: + # Automatically swap the script on the example balloon depending on if dotnet is being used. + if not FileAccess.file_exists("res://tests/test_basic_dialogue.gd"): + var plugin_path: String = get_plugin_path() + var balloon_file_names: PackedStringArray = ["example_balloon.tscn", "small_example_balloon.tscn"] + for balloon_file_name: String in balloon_file_names: + var balloon_path: String = plugin_path + "/example_balloon/" + balloon_file_name + var balloon_content: String = FileAccess.get_file_as_string(balloon_path) + if "example_balloon.gd" in balloon_content and DMSettings.check_for_dotnet_solution(): + balloon_content = balloon_content \ + # Replace script path with the C# one + .replace("example_balloon.gd", "ExampleBalloon.cs") \ + # Replace script UID with the C# one + .replace(ResourceUID.id_to_text(ResourceLoader.get_resource_uid(plugin_path + "/example_balloon/example_balloon.gd")), ResourceUID.id_to_text(ResourceLoader.get_resource_uid(plugin_path + "/example_balloon/ExampleBalloon.cs"))) + var balloon_file: FileAccess = FileAccess.open(balloon_path, FileAccess.WRITE) + balloon_file.store_string(balloon_content) + balloon_file.close() + elif "ExampleBalloon.cs" in balloon_content and not DMSettings.check_for_dotnet_solution(): + balloon_content = balloon_content \ + # Replace script path with the GDScript one + .replace("ExampleBalloon.cs", "example_balloon.gd") \ + # Replace script UID with the GDScript one + .replace(ResourceUID.id_to_text(ResourceLoader.get_resource_uid(plugin_path + "/example_balloon/ExampleBalloon.cs")), ResourceUID.id_to_text(ResourceLoader.get_resource_uid(plugin_path + "/example_balloon/example_balloon.gd"))) + var balloon_file: FileAccess = FileAccess.open(balloon_path, FileAccess.WRITE) + balloon_file.store_string(balloon_content) + balloon_file.close() + + # Automatically make any changes to the known custom balloon if there is one. + var balloon_path: String = DMSettings.get_setting(DMSettings.BALLOON_PATH, "") + if balloon_path != "" and FileAccess.file_exists(balloon_path): + var is_small_window: bool = ProjectSettings.get_setting("display/window/size/viewport_width") < 400 + var example_balloon_file_name: String = "small_example_balloon.tscn" if is_small_window else "example_balloon.tscn" + var example_balloon_path: String = get_plugin_path() + "/example_balloon/" + example_balloon_file_name + + var contents: String = FileAccess.get_file_as_string(balloon_path) + var has_changed: bool = false + + # Make sure the current balloon has a UID unique from the example balloon's + var example_balloon_uid: String = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(example_balloon_path)) + var balloon_uid: String = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(balloon_path)) + if example_balloon_uid == balloon_uid: + var new_balloon_uid: String = ResourceUID.id_to_text(ResourceUID.create_id()) + contents = contents.replace(example_balloon_uid, new_balloon_uid) + has_changed = true + + # Make sure the example balloon copy has the correct renaming of the responses menu + if "reponses" in contents: + contents = contents.replace("reponses", "responses") + has_changed = true + + # Save any changes + if has_changed: + var balloon_file: FileAccess = FileAccess.open(balloon_path, FileAccess.WRITE) + balloon_file.store_string(contents) + balloon_file.close() + + + ### Signals func _on_files_moved(old_file: String, new_file: String) -> void: update_import_paths(old_file, new_file) - DialogueSettings.move_recent_file(old_file, new_file) + DMSettings.move_recent_file(old_file, new_file) func _on_file_removed(file: String) -> void: diff --git a/addons/dialogue_manager/plugin.gd.uid b/addons/dialogue_manager/plugin.gd.uid new file mode 100644 index 0000000..40573b0 --- /dev/null +++ b/addons/dialogue_manager/plugin.gd.uid @@ -0,0 +1 @@ +uid://bpv426rpvrafa diff --git a/addons/dialogue_manager/settings.gd b/addons/dialogue_manager/settings.gd index 9554b64..1c6eb8e 100644 --- a/addons/dialogue_manager/settings.gd +++ b/addons/dialogue_manager/settings.gd @@ -1,91 +1,253 @@ @tool -extends Node +class_name DMSettings extends Node -const DialogueConstants = preload("./constants.gd") +#region Editor -### Editor config -const DEFAULT_SETTINGS = { - states = [], - missing_translations_are_errors = false, - export_characters_in_translation = true, - wrap_lines = false, - new_with_template = true, - new_template = "~ this_is_a_node_title\nNathan: [[Hi|Hello|Howdy]], this is some dialogue.\nNathan: Here are some choices.\n- First one\n\tNathan: You picked the first one.\n- Second one\n\tNathan: You picked the second one.\n- Start again => this_is_a_node_title\n- End the conversation => END\nNathan: For more information see the online documentation.\n=> END", - include_all_responses = false, - ignore_missing_state_values = false, - custom_test_scene_path = preload("./test_scene.tscn").resource_path, - default_csv_locale = "en", - balloon_path = "", - create_lines_for_responses_with_characters = true, - include_character_in_translation_exports = false, - include_notes_in_translation_exports = false, - uses_dotnet = false, - try_suppressing_startup_unsaved_indicator = false +## Wrap lines in the dialogue editor. +const WRAP_LONG_LINES = "editor/wrap_long_lines" +## The template to start new dialogue files with. +const NEW_FILE_TEMPLATE = "editor/new_file_template" + +## Show lines without statis IDs as errors. +const MISSING_TRANSLATIONS_ARE_ERRORS = "editor/translations/missing_translations_are_errors" +## Include character names in the list of translatable strings. +const INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST = "editor/translations/include_characters_in_translatable_strings_list" +## The default delimiter to use when exporting CSVs +const DEFAULT_CSV_DELIMITER = "editor/translations/default_csv_delimiter" +## The default locale to use when exporting CSVs +const DEFAULT_CSV_LOCALE = "editor/translations/default_csv_locale" +## Any extra CSV locales to append to the exported translation CSV +const EXTRA_CSV_LOCALES = "editor/translations/extra_csv_locales" +## Includes a "_character" column in CSV exports. +const INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS = "editor/translations/include_character_in_translation_exports" +## Includes a "_notes" column in CSV exports +const INCLUDE_NOTES_IN_TRANSLATION_EXPORTS = "editor/translations/include_notes_in_translation_exports" +## Automatically update the project's list of translatable files when dialogue files are added or removed +const UPDATE_POT_FILES_AUTOMATICALLY = "editor/translations/update_pot_files_automatically" + +## A processor handling special case compilation. +const DIALOGUE_PROCESSOR_PATH = "editor/advanced/dialogue_processor_path" + +## A custom test scene to use when testing dialogue. +const CUSTOM_TEST_SCENE_PATH = "editor/advanced/custom_test_scene_path" +## Extra script files to include in the auto-complete-able list +const EXTRA_AUTO_COMPLETE_SCRIPT_SOURCES = "editor/advanced/extra_auto_complete_script_sources" + +## The custom balloon for this game. +const BALLOON_PATH = "runtime/balloon_path" +## The names of any autoloads to shortcut into all dialogue files (so you don't have to write `using SomeGlobal` in each file). +const STATE_AUTOLOAD_SHORTCUTS = "runtime/state_autoload_shortcuts" +## Check for possible naming conflicts in state shortcuts. +const WARN_ABOUT_METHOD_PROPERTY_OR_SIGNAL_NAME_CONFLICTS = "runtime/warn_about_method_property_or_signal_name_conflicts" + +## Bypass any missing state when running dialogue. +const IGNORE_MISSING_STATE_VALUES = "runtime/advanced/ignore_missing_state_values" +## Whether or not the project is utilising dotnet. +const USES_DOTNET = "runtime/advanced/uses_dotnet" +## Maximum length of text prefix in auto-generated IDs +const AUTO_GENERATED_ID_PREFIX_LENGTH = "editor/translations/auto_generated_id_prefix_length" +## Use only UUID for auto-generated IDs without text prefix +const USE_UUID_ONLY_FOR_IDS = "editor/translations/use_uuid_only_for_ids" + +static var SETTINGS_CONFIGURATION = { + WRAP_LONG_LINES: { + value = false, + type = TYPE_BOOL, + }, + NEW_FILE_TEMPLATE: { + value = "~ start\nNathan: [[Hi|Hello|Howdy]], this is some dialogue.\nNathan: Here are some choices.\n- First one\n\tNathan: You picked the first one.\n- Second one\n\tNathan: You picked the second one.\n- Start again => start\n- End the conversation => END\nNathan: For more information see the online documentation.\n=> END", + type = TYPE_STRING, + hint = PROPERTY_HINT_MULTILINE_TEXT, + }, + + MISSING_TRANSLATIONS_ARE_ERRORS: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, + INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST: { + value = true, + type = TYPE_BOOL, + }, + DEFAULT_CSV_DELIMITER: { + value = "Comma", + type = TYPE_STRING, + hint = PROPERTY_HINT_ENUM, + hint_string = "Comma,Semicolon,Tab" + }, + DEFAULT_CSV_LOCALE: { + value = "en", + type = TYPE_STRING, + hint = PROPERTY_HINT_LOCALE_ID, + }, + EXTRA_CSV_LOCALES: { + value = [], + type = TYPE_PACKED_STRING_ARRAY, + hint = PROPERTY_HINT_TYPE_STRING, + hint_string = "%d:" % [TYPE_STRING], + is_advanced = true + }, + INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, + INCLUDE_NOTES_IN_TRANSLATION_EXPORTS: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, + UPDATE_POT_FILES_AUTOMATICALLY: { + value = true, + type = TYPE_BOOL, + is_advanced = true + }, + + DIALOGUE_PROCESSOR_PATH: { + value = "", + type = TYPE_STRING, + hint = PROPERTY_HINT_FILE, + hint_string = "*.gd,*.cs", + is_advanced = true + }, + + CUSTOM_TEST_SCENE_PATH: { + value = preload("./test_scene.tscn").resource_path, + type = TYPE_STRING, + hint = PROPERTY_HINT_FILE, + is_advanced = true + }, + EXTRA_AUTO_COMPLETE_SCRIPT_SOURCES: { + value = [], + type = TYPE_PACKED_STRING_ARRAY, + hint = PROPERTY_HINT_TYPE_STRING, + hint_string = "%d/%d:*.*" % [TYPE_STRING, PROPERTY_HINT_FILE], + is_advanced = true + }, + + BALLOON_PATH: { + value = "", + type = TYPE_STRING, + hint = PROPERTY_HINT_FILE, + }, + STATE_AUTOLOAD_SHORTCUTS: { + value = [], + type = TYPE_PACKED_STRING_ARRAY, + hint = PROPERTY_HINT_TYPE_STRING, + hint_string = "%d:" % [TYPE_STRING], + }, + WARN_ABOUT_METHOD_PROPERTY_OR_SIGNAL_NAME_CONFLICTS: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, + + IGNORE_MISSING_STATE_VALUES: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, + USES_DOTNET: { + value = false, + type = TYPE_BOOL, + is_hidden = true + }, + AUTO_GENERATED_ID_PREFIX_LENGTH: { + value = 30, + type = TYPE_INT, + hint = PROPERTY_HINT_RANGE, + hint_string = "0,100,1", + is_advanced = true + }, + USE_UUID_ONLY_FOR_IDS: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, } static func prepare() -> void: - # Migrate previous keys - for key in [ - "states", - "missing_translations_are_errors", - "export_characters_in_translation", - "wrap_lines", - "new_with_template", - "include_all_responses", - "custom_test_scene_path" - ]: - if ProjectSettings.has_setting("dialogue_manager/%s" % key): - var value = ProjectSettings.get_setting("dialogue_manager/%s" % key) - ProjectSettings.set_setting("dialogue_manager/%s" % key, null) - set_setting(key, value) + var should_save_settings: bool = false + + # Remap any old settings into their new keys + var legacy_map: Dictionary = { + states = STATE_AUTOLOAD_SHORTCUTS, + missing_translations_are_errors = MISSING_TRANSLATIONS_ARE_ERRORS, + export_characters_in_translation = INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST, + wrap_lines = WRAP_LONG_LINES, + new_with_template = null, + new_template = NEW_FILE_TEMPLATE, + include_all_responses = null, + ignore_missing_state_values = IGNORE_MISSING_STATE_VALUES, + custom_test_scene_path = CUSTOM_TEST_SCENE_PATH, + default_csv_locale = DEFAULT_CSV_LOCALE, + balloon_path = BALLOON_PATH, + create_lines_for_responses_with_characters = null, + include_character_in_translation_exports = INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS, + include_notes_in_translation_exports = INCLUDE_NOTES_IN_TRANSLATION_EXPORTS, + uses_dotnet = USES_DOTNET, + try_suppressing_startup_unsaved_indicator = null + } + + for legacy_key: String in legacy_map: + if ProjectSettings.has_setting("dialogue_manager/general/%s" % legacy_key): + should_save_settings = true + # Remove the old setting + var value = ProjectSettings.get_setting("dialogue_manager/general/%s" % legacy_key) + ProjectSettings.set_setting("dialogue_manager/general/%s" % legacy_key, null) + if legacy_map.get(legacy_key) != null: + prints("Migrating Dialogue Manager setting %s to %s with value %s" % [legacy_key, legacy_map.get(legacy_key), str(value)]) + ProjectSettings.set_setting("dialogue_manager/%s" % [legacy_map.get(legacy_key)], value) # Set up initial settings - for setting in DEFAULT_SETTINGS: - var setting_name: String = "dialogue_manager/general/%s" % setting + for key: String in SETTINGS_CONFIGURATION: + var setting_config: Dictionary = SETTINGS_CONFIGURATION[key] + var setting_name: String = "dialogue_manager/%s" % key if not ProjectSettings.has_setting(setting_name): - set_setting(setting, DEFAULT_SETTINGS[setting]) - ProjectSettings.set_initial_value(setting_name, DEFAULT_SETTINGS[setting]) - if setting.ends_with("_path"): - ProjectSettings.add_property_info({ - "name": setting_name, - "type": TYPE_STRING, - "hint": PROPERTY_HINT_FILE, - }) + ProjectSettings.set_setting(setting_name, setting_config.value) + ProjectSettings.set_initial_value(setting_name, setting_config.value) + ProjectSettings.add_property_info({ + "name" = setting_name, + "type" = setting_config.type, + "hint" = setting_config.get("hint", PROPERTY_HINT_NONE), + "hint_string" = setting_config.get("hint_string", "") + }) + ProjectSettings.set_as_basic(setting_name, not setting_config.has("is_advanced")) + ProjectSettings.set_as_internal(setting_name, setting_config.has("is_hidden")) - # Some settings shouldn't be edited directly in the Project Settings window - ProjectSettings.set_as_internal("dialogue_manager/general/states", true) - ProjectSettings.set_as_internal("dialogue_manager/general/custom_test_scene_path", true) - ProjectSettings.set_as_internal("dialogue_manager/general/uses_dotnet", true) - - ProjectSettings.save() + if should_save_settings: + ProjectSettings.save() static func set_setting(key: String, value) -> void: - ProjectSettings.set_setting("dialogue_manager/general/%s" % key, value) - ProjectSettings.set_initial_value("dialogue_manager/general/%s" % key, DEFAULT_SETTINGS[key]) - ProjectSettings.save() + if get_setting(key, value) != value: + ProjectSettings.set_setting("dialogue_manager/%s" % key, value) + ProjectSettings.set_initial_value("dialogue_manager/%s" % key, SETTINGS_CONFIGURATION[key].value) + ProjectSettings.save() static func get_setting(key: String, default): - if ProjectSettings.has_setting("dialogue_manager/general/%s" % key): - return ProjectSettings.get_setting("dialogue_manager/general/%s" % key) + if ProjectSettings.has_setting("dialogue_manager/%s" % key): + return ProjectSettings.get_setting("dialogue_manager/%s" % key) else: return default static func get_settings(only_keys: PackedStringArray = []) -> Dictionary: var settings: Dictionary = {} - for key in DEFAULT_SETTINGS.keys(): + for key in SETTINGS_CONFIGURATION.keys(): if only_keys.is_empty() or key in only_keys: - settings[key] = get_setting(key, DEFAULT_SETTINGS[key]) + settings[key] = get_setting(key, SETTINGS_CONFIGURATION[key].value) return settings -### User config +#endregion + +#region User static func get_user_config() -> Dictionary: @@ -95,7 +257,7 @@ static func get_user_config() -> Dictionary: recent_files = [], reopen_files = [], most_recent_reopen_file = "", - carets = {}, + file_meta = {}, run_title = "", run_resource_path = "", is_running_test_scene = false, @@ -103,15 +265,15 @@ static func get_user_config() -> Dictionary: open_in_external_editor = false } - if FileAccess.file_exists(DialogueConstants.USER_CONFIG_PATH): - var file: FileAccess = FileAccess.open(DialogueConstants.USER_CONFIG_PATH, FileAccess.READ) + if FileAccess.file_exists(DMConstants.USER_CONFIG_PATH): + var file: FileAccess = FileAccess.open(DMConstants.USER_CONFIG_PATH, FileAccess.READ) user_config.merge(JSON.parse_string(file.get_as_text()), true) return user_config static func save_user_config(user_config: Dictionary) -> void: - var file: FileAccess = FileAccess.open(DialogueConstants.USER_CONFIG_PATH, FileAccess.WRITE) + var file: FileAccess = FileAccess.open(DMConstants.USER_CONFIG_PATH, FileAccess.WRITE) file.store_string(JSON.stringify(user_config)) @@ -121,10 +283,17 @@ static func set_user_value(key: String, value) -> void: save_user_config(user_config) -static func get_user_value(key: String, default = null): +static func get_user_value(key: String, default = null) -> Variant: return get_user_config().get(key, default) +static func forget_path(path: String) -> void: + remove_recent_file(path) + var file_meta: Dictionary = get_user_value("file_meta", {}) + file_meta.erase(path) + set_user_value("file_meta", file_meta) + + static func add_recent_file(path: String) -> void: var recent_files: Array = get_user_value("recent_files", []) if path in recent_files: @@ -158,23 +327,34 @@ static func clear_recent_files() -> void: static func set_caret(path: String, cursor: Vector2) -> void: - var carets: Dictionary = get_user_value("carets", {}) - carets[path] = { - x = cursor.x, - y = cursor.y - } - set_user_value("carets", carets) + var file_meta: Dictionary = get_user_value("file_meta", {}) + file_meta[path] = file_meta.get(path, {}).merged({ cursor = "%d,%d" % [cursor.x, cursor.y] }, true) + set_user_value("file_meta", file_meta) static func get_caret(path: String) -> Vector2: - var carets = get_user_value("carets", {}) - if carets.has(path): - var caret = carets.get(path) - return Vector2(caret.x, caret.y) + var file_meta: Dictionary = get_user_value("file_meta", {}) + if file_meta.has(path): + var cursor: PackedStringArray = file_meta.get(path).get("cursor", "0,0").split(",") + return Vector2(cursor[0].to_int(), cursor[1].to_int()) else: return Vector2.ZERO +static func set_scroll(path: String, scroll_vertical: int) -> void: + var file_meta: Dictionary = get_user_value("file_meta", {}) + file_meta[path] = file_meta.get(path, {}).merged({ scroll_vertical = scroll_vertical }, true) + set_user_value("file_meta", file_meta) + + +static func get_scroll(path: String) -> int: + var file_meta: Dictionary = get_user_value("file_meta", {}) + if file_meta.has(path): + return file_meta.get(path).get("scroll_vertical", 0) + else: + return 0 + + static func check_for_dotnet_solution() -> bool: if Engine.is_editor_hint(): var has_dotnet_solution: bool = false @@ -182,7 +362,10 @@ static func check_for_dotnet_solution() -> bool: var directory: String = ProjectSettings.get("dotnet/project/solution_directory") var file_name: String = ProjectSettings.get("dotnet/project/assembly_name") has_dotnet_solution = FileAccess.file_exists("res://%s/%s.sln" % [directory, file_name]) - set_setting("uses_dotnet", has_dotnet_solution) + set_setting(DMSettings.USES_DOTNET, has_dotnet_solution) return has_dotnet_solution - return get_setting("uses_dotnet", false) + return get_setting(DMSettings.USES_DOTNET, false) + + +#endregion diff --git a/addons/dialogue_manager/settings.gd.uid b/addons/dialogue_manager/settings.gd.uid new file mode 100644 index 0000000..c93da98 --- /dev/null +++ b/addons/dialogue_manager/settings.gd.uid @@ -0,0 +1 @@ +uid://ce1nk88365m52 diff --git a/addons/dialogue_manager/test_scene.gd b/addons/dialogue_manager/test_scene.gd index a808911..e621667 100644 --- a/addons/dialogue_manager/test_scene.gd +++ b/addons/dialogue_manager/test_scene.gd @@ -10,23 +10,28 @@ const DialogueResource = preload("./dialogue_resource.gd") func _ready(): - var screen_index: int = DisplayServer.get_primary_screen() - DisplayServer.window_set_position(Vector2(DisplayServer.screen_get_position(screen_index)) + (DisplayServer.screen_get_size(screen_index) - DisplayServer.window_get_size()) * 0.5) - DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) + if not Engine.is_embedded_in_editor: + var window: Window = get_viewport() + var screen_index: int = DisplayServer.get_primary_screen() + window.position = Vector2(DisplayServer.screen_get_position(screen_index)) + (DisplayServer.screen_get_size(screen_index) - window.size) * 0.5 + window.mode = Window.MODE_WINDOWED # Normally you can just call DialogueManager directly but doing so before the plugin has been # enabled in settings will throw a compiler error here so I'm using `get_singleton` instead. var dialogue_manager = Engine.get_singleton("DialogueManager") dialogue_manager.dialogue_ended.connect(_on_dialogue_ended) - dialogue_manager.show_dialogue_balloon(resource, title) + dialogue_manager.show_dialogue_balloon(resource, title if not title.is_empty() else resource.first_title) func _enter_tree() -> void: DialogueSettings.set_user_value("is_running_test_scene", false) -### Signals +#region Signals func _on_dialogue_ended(_resource: DialogueResource): get_tree().quit() + + +#endregion diff --git a/addons/dialogue_manager/test_scene.gd.uid b/addons/dialogue_manager/test_scene.gd.uid new file mode 100644 index 0000000..1bee7a1 --- /dev/null +++ b/addons/dialogue_manager/test_scene.gd.uid @@ -0,0 +1 @@ +uid://c8e16qdgu40wo diff --git a/addons/dialogue_manager/test_scene.tscn b/addons/dialogue_manager/test_scene.tscn index b47f632..f0786ba 100644 --- a/addons/dialogue_manager/test_scene.tscn +++ b/addons/dialogue_manager/test_scene.tscn @@ -1,6 +1,6 @@ -[gd_scene load_steps=2 format=3 uid="uid://cp33m8d3ha6s"] +[gd_scene load_steps=2 format=3 uid="uid://ugd552efvil0"] -[ext_resource type="Script" path="res://addons/dialogue_manager/test_scene.gd" id="1_yupoh"] +[ext_resource type="Script" uid="uid://c8e16qdgu40wo" path="res://addons/dialogue_manager/test_scene.gd" id="1_yupoh"] [node name="TestScene" type="Node2D"] script = ExtResource("1_yupoh") diff --git a/addons/dialogue_manager/utilities/builtins.gd b/addons/dialogue_manager/utilities/builtins.gd index 97080b7..1f8f8ba 100644 --- a/addons/dialogue_manager/utilities/builtins.gd +++ b/addons/dialogue_manager/utilities/builtins.gd @@ -22,8 +22,32 @@ const SUPPORTED_BUILTIN_TYPES = [ static var resolve_method_error: Error = OK -static func is_supported(thing) -> bool: - return typeof(thing) in SUPPORTED_BUILTIN_TYPES +static func is_supported(thing, with_method: String = "") -> bool: + if not typeof(thing) in SUPPORTED_BUILTIN_TYPES: return false + + # If given a Dictionary and a method then make sure it's a known Dictionary method. + if typeof(thing) == TYPE_DICTIONARY and with_method != "": + return with_method in [ + &"clear", + &"duplicate", + &"erase", + &"find_key", + &"get", + &"get_or_add", + &"has", + &"has_all", + &"hash", + &"is_empty", + &"is_read_only", + &"keys", + &"make_read_only", + &"merge", + &"merged", + &"recursive_equal", + &"size", + &"values"] + + return true static func resolve_property(builtin, property: String): @@ -407,6 +431,15 @@ static func resolve_vector2_property(vector: Vector2, property: String): "DOWN": return Vector2.DOWN + "DOWN_LEFT": + return Vector2(-1, 1) + "DOWN_RIGHT": + return Vector2(1, 1) + "UP_LEFT": + return Vector2(-1, -1) + "UP_RIGHT": + return Vector2(1, -1) + return vector[property] diff --git a/addons/dialogue_manager/utilities/builtins.gd.uid b/addons/dialogue_manager/utilities/builtins.gd.uid new file mode 100644 index 0000000..af8698c --- /dev/null +++ b/addons/dialogue_manager/utilities/builtins.gd.uid @@ -0,0 +1 @@ +uid://bnfhuubdv5k20 diff --git a/addons/dialogue_manager/components/dialogue_cache.gd b/addons/dialogue_manager/utilities/dialogue_cache.gd similarity index 84% rename from addons/dialogue_manager/components/dialogue_cache.gd rename to addons/dialogue_manager/utilities/dialogue_cache.gd index 5aa9a57..53b5efc 100644 --- a/addons/dialogue_manager/components/dialogue_cache.gd +++ b/addons/dialogue_manager/utilities/dialogue_cache.gd @@ -1,9 +1,4 @@ -extends Node - - -const DialogueConstants = preload("../constants.gd") -const DialogueSettings = preload("../settings.gd") -const DialogueManagerParseResult = preload("./parse_result.gd") +class_name DMCache extends Node signal file_content_changed(path: String, new_content: String) @@ -42,30 +37,27 @@ func reimport_files(and_files: PackedStringArray = []) -> void: for file in and_files: if not _files_marked_for_reimport.has(file): _files_marked_for_reimport.append(file) - + if _files_marked_for_reimport.is_empty(): return - var file_system: EditorFileSystem = Engine.get_meta("DialogueManagerPlugin") \ - .get_editor_interface() \ - .get_resource_filesystem() - - file_system.reimport_files(_files_marked_for_reimport) + EditorInterface.get_resource_filesystem().reimport_files(_files_marked_for_reimport) + _files_marked_for_reimport.clear() ## Add a dialogue file to the cache. -func add_file(path: String, parse_results: DialogueManagerParseResult = null) -> void: +func add_file(path: String, compile_result: DMCompilerResult = null) -> void: _cache[path] = { path = path, dependencies = [], errors = [] } - if parse_results != null: - _cache[path].dependencies = Array(parse_results.imported_paths).filter(func(d): return d != path) - _cache[path].parsed_at = Time.get_ticks_msec() + if compile_result != null: + _cache[path].dependencies = Array(compile_result.imported_paths).filter(func(d): return d != path) + _cache[path].compiled_at = Time.get_ticks_msec() # If this is a fresh cache entry, check for dependencies - if parse_results == null and not _update_dependency_paths.has(path): + if compile_result == null and not _update_dependency_paths.has(path): queue_updating_dependencies(path) @@ -126,7 +118,7 @@ func get_files_with_dependency(imported_path: String) -> Array: ## Get any paths that are dependent on a given path func get_dependent_paths_for_reimport(on_path: String) -> PackedStringArray: return get_files_with_dependency(on_path) \ - .filter(func(d): return Time.get_ticks_msec() - d.get("parsed_at", 0) > 3000) \ + .filter(func(d): return Time.get_ticks_msec() - d.get("compiled_at", 0) > 3000) \ .map(func(d): return d.path) @@ -157,7 +149,7 @@ func _get_dialogue_files_in_filesystem(path: String = "res://") -> PackedStringA return files -### Signals +#region Signals func _on_update_dependency_timeout() -> void: @@ -174,3 +166,6 @@ func _on_update_dependency_timeout() -> void: dependencies.append(found.strings[found.names.path]) _cache[path].dependencies = dependencies _update_dependency_paths.clear() + + +#endregion diff --git a/addons/dialogue_manager/utilities/dialogue_cache.gd.uid b/addons/dialogue_manager/utilities/dialogue_cache.gd.uid new file mode 100644 index 0000000..e572006 --- /dev/null +++ b/addons/dialogue_manager/utilities/dialogue_cache.gd.uid @@ -0,0 +1 @@ +uid://d3c83yd6bjp43 diff --git a/addons/dialogue_manager/utilities/translations.gd b/addons/dialogue_manager/utilities/translations.gd new file mode 100644 index 0000000..6de7160 --- /dev/null +++ b/addons/dialogue_manager/utilities/translations.gd @@ -0,0 +1,312 @@ +## A collection of utility functions for working with dialogue translations. +class_name DMTranslationUtilities extends RefCounted + + +## Generate translation keys from some text. +static func generate_translation_keys(text: String) -> String: + var rng: RandomNumberGenerator = RandomNumberGenerator.new() + rng.randomize() + + var lines: PackedStringArray = text.split("\n") + + var key_regex = RegEx.new() + key_regex.compile("\\[ID:(?<key>.*?)\\]") + + var compiled_lines: Dictionary = DMCompiler.compile_string(text, "").lines + + # Make list of known keys + var known_keys = {} + for i in range(0, lines.size()): + var line = lines[i] + var found = key_regex.search(line) + if found: + var translatable_text: String = "" + var l = line.replace(found.strings[0], "").strip_edges().strip_edges() + if l.begins_with("- "): + translatable_text = DMCompiler.extract_translatable_string(l) + elif ":" in l: + translatable_text = l.split(":")[1] + else: + translatable_text = l + known_keys[found.strings[found.names.get("key")]] = translatable_text + + # Add in any that are missing + for i in lines.size(): + var line = lines[i] + var l = line.strip_edges() + + if not [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE].has(DMCompiler.get_line_type(l)): continue + if not compiled_lines.has(str(i)): continue + + if "[ID:" in line: continue + + var translatable_text: String = "" + if l.begins_with("- "): + translatable_text = DMCompiler.extract_translatable_string(l) + else: + translatable_text = l.substr(l.find(":") + 1) + + var key: String = "" + if known_keys.values().has(translatable_text): + key = known_keys.find_key(translatable_text) + else: + var regex: DMCompilerRegEx = DMCompilerRegEx.new() + if DMSettings.get_setting(DMSettings.USE_UUID_ONLY_FOR_IDS, false): + # Generate UUID only + var uuid = str(randi() % 1000000).sha1_text().substr(0, 12) + key = uuid.to_upper() + else: + # Generate text prefix + hash + var prefix_length = DMSettings.get_setting(DMSettings.AUTO_GENERATED_ID_PREFIX_LENGTH, 30) + key = regex.ALPHA_NUMERIC.sub(translatable_text.strip_edges(), "_", true).substr(0, prefix_length) + if key.begins_with("_"): + key = key.substr(1) + if key.ends_with("_"): + key = key.substr(0, key.length() - 1) + + # Make sure key is unique + var hashed_key: String = key + "_" + str(randi() % 1000000).sha1_text().substr(0, 6) + while hashed_key in known_keys and translatable_text != known_keys.get(hashed_key): + hashed_key = key + "_" + str(randi() % 1000000).sha1_text().substr(0, 6) + key = hashed_key.to_upper() + + line = line.replace("\\n", "!NEWLINE!") + translatable_text = translatable_text.replace("\n", "!NEWLINE!") + lines[i] = line.replace(translatable_text, translatable_text + " [ID:%s]" % [key]).replace("!NEWLINE!", "\\n") + + known_keys[key] = translatable_text + + return "\n".join(lines) + + +## Export dialogue and responses to CSV. +static func export_translations_to_csv(to_path: String, text: String, dialogue_path: String) -> void: + var default_locale: String = DMSettings.get_setting(DMSettings.DEFAULT_CSV_LOCALE, "en") + + var file: FileAccess + + # If the file exists, open it first and work out which keys are already in it + var existing_csv: Dictionary = {} + var delimiter: String = get_delimiter_for_csv(to_path) + var column_count: int = 2 + var default_locale_column: int = 1 + var character_column: int = -1 + var notes_column: int = -1 + if FileAccess.file_exists(to_path): + file = FileAccess.open(to_path, FileAccess.READ) + var is_first_line = true + var line: Array + while !file.eof_reached(): + line = file.get_csv_line(delimiter) + if is_first_line: + is_first_line = false + column_count = line.size() + for i in range(1, line.size()): + if line[i] == default_locale: + default_locale_column = i + elif line[i] == "_character": + character_column = i + elif line[i] == "_notes": + notes_column = i + + # Make sure the line isn't empty before adding it + if line.size() > 0 and line[0].strip_edges() != "": + existing_csv[line[0]] = line + + # The character column wasn't found in the existing file but the setting is turned on + if character_column == -1 and DMSettings.get_setting(DMSettings.INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS, false): + character_column = column_count + column_count += 1 + existing_csv["keys"].append("_character") + + # The notes column wasn't found in the existing file but the setting is turned on + if notes_column == -1 and DMSettings.get_setting(DMSettings.INCLUDE_NOTES_IN_TRANSLATION_EXPORTS, false): + notes_column = column_count + column_count += 1 + existing_csv["keys"].append("_notes") + + # Start a new file + file = FileAccess.open(to_path, FileAccess.WRITE) + + if not FileAccess.file_exists(to_path): + var headings: PackedStringArray = ["keys", default_locale] + DMSettings.get_setting(DMSettings.EXTRA_CSV_LOCALES, []) + if DMSettings.get_setting(DMSettings.INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS, false): + character_column = headings.size() + headings.append("_character") + if DMSettings.get_setting(DMSettings.INCLUDE_NOTES_IN_TRANSLATION_EXPORTS, false): + notes_column = headings.size() + headings.append("_notes") + + file.store_csv_line(headings, delimiter) + column_count = headings.size() + + # Write our translations to file + var known_keys: PackedStringArray = [] + + var dialogue = DMCompiler.compile_string(text, dialogue_path).lines + + # Make a list of stuff that needs to go into the file + var lines_to_save = [] + for key in dialogue.keys(): + var line: Dictionary = dialogue.get(key) + + if not line.type in [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE]: continue + + var translation_key: String = line.get(&"translation_key", line.text) + + if translation_key in known_keys: continue + + known_keys.append(translation_key) + + var line_to_save: PackedStringArray = [] + if existing_csv.has(translation_key): + line_to_save = existing_csv.get(translation_key) + line_to_save.resize(column_count) + existing_csv.erase(translation_key) + else: + line_to_save.resize(column_count) + line_to_save[0] = translation_key + + line_to_save[default_locale_column] = line.text + if character_column > -1: + line_to_save[character_column] = "(response)" if line.type == DMConstants.TYPE_RESPONSE else line.character + if notes_column > -1: + line_to_save[notes_column] = line.notes + + lines_to_save.append(line_to_save) + + # Store lines in the file, starting with anything that already exists that hasn't been touched + for line in existing_csv.values(): + file.store_csv_line(line, delimiter) + for line in lines_to_save: + file.store_csv_line(line, delimiter) + + file.close() + + +## Get the delimier used for an existing CSV +static func get_delimiter_for_csv(path: String) -> String: + if FileAccess.file_exists(path): + var import_path: String = "%s.%s" % [path, "import"] + var import_file: ConfigFile = ConfigFile.new() + if import_file.load(import_path) == OK: + match import_file.get_value("params", "delimier", 0): + 0: + return "," + 1: + return ";" + 2: + return "\t" + + match DMSettings.get_setting(DMSettings.DEFAULT_CSV_DELIMITER, "Comma"): + "Comma": + return "," + "Semicolon": + return ";" + "Tab": + return "\t" + + return "," + + +## Save any character names in a file to CSV. +static func export_character_names_to_csv(to_path: String, text: String, dialogue_path: String) -> void: + var file: FileAccess + + # If the file exists, open it first and work out which keys are already in it + var existing_csv = {} + var delimiter: String = get_delimiter_for_csv(to_path) + var commas = [] + if FileAccess.file_exists(to_path): + file = FileAccess.open(to_path, FileAccess.READ) + var is_first_line = true + var line: Array + while !file.eof_reached(): + line = file.get_csv_line(delimiter) + if is_first_line: + is_first_line = false + for i in range(2, line.size()): + commas.append("") + # Make sure the line isn't empty before adding it + if line.size() > 0 and line[0].strip_edges() != "": + existing_csv[line[0]] = line + + # Start a new file + file = FileAccess.open(to_path, FileAccess.WRITE) + + if not file.file_exists(to_path): + file.store_csv_line(["keys", DMSettings.get_setting(DMSettings.DEFAULT_CSV_LOCALE, "en")], delimiter) + + # Write our translations to file + var known_keys: PackedStringArray = [] + + var character_names: PackedStringArray = DMCompiler.compile_string(text, dialogue_path).character_names + + # Make a list of stuff that needs to go into the file + var lines_to_save = [] + for character_name in character_names: + if character_name in known_keys: continue + + known_keys.append(character_name) + + if existing_csv.has(character_name): + var existing_line = existing_csv.get(character_name) + existing_line[1] = character_name + lines_to_save.append(existing_line) + existing_csv.erase(character_name) + else: + lines_to_save.append(PackedStringArray([character_name, character_name] + commas)) + + # Store lines in the file, starting with anything that already exists that hasn't been touched + for line in existing_csv.values(): + file.store_csv_line(line, delimiter) + for line in lines_to_save: + file.store_csv_line(line, delimiter) + + file.close() + + +## Replace translatable lines in some text using an existing CSV. +static func import_translations_from_csv(from_path: String, text: String) -> String: + if not FileAccess.file_exists(from_path): return text + + # Open the CSV file and build a dictionary of the known keys + var delimiter: String = get_delimiter_for_csv(from_path) + var keys: Dictionary = {} + var file: FileAccess = FileAccess.open(from_path, FileAccess.READ) + var csv_line: Array + while !file.eof_reached(): + csv_line = file.get_csv_line(delimiter) + if csv_line.size() > 1: + keys[csv_line[0]] = csv_line[1] + + # Now look over each line in the dialogue and replace the content for matched keys + var lines: PackedStringArray = text.split("\n") + var start_index: int = 0 + var end_index: int = 0 + for i in range(0, lines.size()): + var line: String = lines[i] + var translation_key: String = DMCompiler.get_static_line_id(line) + if keys.has(translation_key): + if DMCompiler.get_line_type(line) == DMConstants.TYPE_DIALOGUE: + start_index = 0 + # See if we need to skip over a character name + line = line.replace("\\:", "!ESCAPED_COLON!") + if ": " in line: + start_index = line.find(": ") + 2 + lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]").replace("!ESCAPED_COLON!", ":") + + elif DMCompiler.get_line_type(line) == DMConstants.TYPE_RESPONSE: + start_index = line.find("- ") + 2 + # See if we need to skip over a character name + line = line.replace("\\:", "!ESCAPED_COLON!") + if ": " in line: + start_index = line.find(": ") + 2 + end_index = line.length() + if " =>" in line: + end_index = line.find(" =>") + if " [if " in line: + end_index = line.find(" [if ") + lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]" + line.substr(end_index)).replace("!ESCAPED_COLON!", ":") + + return "\n".join(lines) diff --git a/addons/dialogue_manager/utilities/translations.gd.uid b/addons/dialogue_manager/utilities/translations.gd.uid new file mode 100644 index 0000000..4c30f49 --- /dev/null +++ b/addons/dialogue_manager/utilities/translations.gd.uid @@ -0,0 +1 @@ +uid://c74q663mmfyk1 diff --git a/addons/dialogue_manager/utilities/waiter.gd b/addons/dialogue_manager/utilities/waiter.gd new file mode 100644 index 0000000..0724561 --- /dev/null +++ b/addons/dialogue_manager/utilities/waiter.gd @@ -0,0 +1,20 @@ +class_name DMWaiter extends Node + + +signal waited() + + +var _actions: PackedStringArray +var _null: String = str(null) + + +func _init(target_actions: PackedStringArray) -> void: + _actions = target_actions + + +func _input(event: InputEvent) -> void: + for action: String in _actions: + if event.is_pressed(): + if action == _null or (InputMap.has_action(action) and event.is_action(action)): + get_viewport().set_input_as_handled() + waited.emit() diff --git a/addons/dialogue_manager/utilities/waiter.gd.uid b/addons/dialogue_manager/utilities/waiter.gd.uid new file mode 100644 index 0000000..95646ea --- /dev/null +++ b/addons/dialogue_manager/utilities/waiter.gd.uid @@ -0,0 +1 @@ +uid://bx7dtro7ywali diff --git a/addons/dialogue_manager/views/find_in_dialogue_view.gd b/addons/dialogue_manager/views/find_in_dialogue_view.gd new file mode 100644 index 0000000..2e09a4f --- /dev/null +++ b/addons/dialogue_manager/views/find_in_dialogue_view.gd @@ -0,0 +1,241 @@ +@tool +extends Control + +signal result_selected(path: String, cursor: Vector2, length: int) + + +const DialogueConstants = preload("../constants.gd") + + +var main_view: Control + +@onready var input: LineEdit = %Input +@onready var search_button: Button = %SearchButton +@onready var match_case_button: CheckBox = %MatchCaseButton +@onready var replace_toggle: CheckButton = %ReplaceToggle +@onready var replace_container: VBoxContainer = %ReplaceContainer +@onready var replace_input: LineEdit = %ReplaceInput +@onready var replace_selected_button: Button = %ReplaceSelectedButton +@onready var replace_all_button: Button = %ReplaceAllButton +@onready var results_container: VBoxContainer = %ResultsContainer +@onready var result_template: HBoxContainer = %ResultTemplate + +var current_results: Dictionary = {}: + set(value): + current_results = value + update_results_view() + if current_results.size() == 0: + replace_selected_button.disabled = true + replace_all_button.disabled = true + else: + replace_selected_button.disabled = false + replace_all_button.disabled = false + get: + return current_results + +var selections: PackedStringArray = [] + + +func _ready() -> void: + remove_child(result_template) + + +func _exit_tree() -> void: + result_template.queue_free() + + +func prepare() -> void: + if not is_node_ready(): + await ready + + input.grab_focus() + + var template_label = result_template.get_node("Label") + template_label.get_theme_stylebox(&"focus").bg_color = main_view.code_edit.theme_overrides.current_line_color + template_label.add_theme_font_override(&"normal_font", main_view.code_edit.get_theme_font(&"font")) + + replace_toggle.set_pressed_no_signal(false) + replace_container.hide() + + $VBoxContainer/HBoxContainer/FindContainer/Label.text = DialogueConstants.translate(&"search.find") + input.placeholder_text = DialogueConstants.translate(&"search.placeholder") + input.text = "" + search_button.text = DialogueConstants.translate(&"search.find_all") + match_case_button.text = DialogueConstants.translate(&"search.match_case") + replace_toggle.text = DialogueConstants.translate(&"search.toggle_replace") + $VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceLabel.text = DialogueConstants.translate(&"search.replace_with") + replace_input.placeholder_text = DialogueConstants.translate(&"search.replace_placeholder") + replace_input.text = "" + replace_all_button.text = DialogueConstants.translate(&"search.replace_all") + replace_selected_button.text = DialogueConstants.translate(&"search.replace_selected") + + selections.clear() + current_results = {} + + +#region helpers + + +func update_results_view() -> void: + for child in results_container.get_children(): + child.queue_free() + + for path in current_results.keys(): + var path_label: Label = Label.new() + path_label.text = path + # Show open files + if main_view.open_buffers.has(path): + path_label.text += "(*)" + results_container.add_child(path_label) + for path_result in current_results.get(path): + var result_item: HBoxContainer = result_template.duplicate() + + var checkbox: CheckBox = result_item.get_node("CheckBox") as CheckBox + var key: String = get_selection_key(path, path_result) + checkbox.toggled.connect(func(is_pressed): + if is_pressed: + if not selections.has(key): + selections.append(key) + else: + if selections.has(key): + selections.remove_at(selections.find(key)) + ) + checkbox.set_pressed_no_signal(selections.has(key)) + checkbox.visible = replace_toggle.button_pressed + + var result_label: RichTextLabel = result_item.get_node("Label") as RichTextLabel + var colors: Dictionary = main_view.code_edit.theme_overrides + var highlight: String = "" + if replace_toggle.button_pressed: + var matched_word: String = "[bgcolor=" + colors.critical_color.to_html() + "][color=" + colors.text_color.to_html() + "]" + path_result.matched_text + "[/color][/bgcolor]" + highlight = "[s]" + matched_word + "[/s][bgcolor=" + colors.notice_color.to_html() + "][color=" + colors.text_color.to_html() + "]" + replace_input.text + "[/color][/bgcolor]" + else: + highlight = "[bgcolor=" + colors.notice_color.to_html() + "][color=" + colors.text_color.to_html() + "]" + path_result.matched_text + "[/color][/bgcolor]" + var text: String = path_result.text.substr(0, path_result.index) + highlight + path_result.text.substr(path_result.index + path_result.query.length()) + result_label.text = "%s: %s" % [str(path_result.line + 1).lpad(4), text] + result_label.gui_input.connect(func(event): + if event is InputEventMouseButton and (event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT and (event as InputEventMouseButton).double_click: + result_selected.emit(path, Vector2(path_result.index, path_result.line), path_result.query.length()) + ) + + results_container.add_child(result_item) + + +func find_in_files() -> Dictionary: + var results: Dictionary = {} + + var q: String = input.text + var cache = Engine.get_meta("DMCache") + var file: FileAccess + for path in cache.get_files(): + var path_results: Array = [] + var lines: PackedStringArray = [] + + if main_view.open_buffers.has(path): + lines = main_view.open_buffers.get(path).text.split("\n") + else: + file = FileAccess.open(path, FileAccess.READ) + lines = file.get_as_text().split("\n") + + for i in range(0, lines.size()): + var index: int = find_in_line(lines[i], q) + while index > -1: + path_results.append({ + line = i, + index = index, + text = lines[i], + matched_text = lines[i].substr(index, q.length()), + query = q + }) + index = find_in_line(lines[i], q, index + q.length()) + + if file != null and file.is_open(): + file.close() + + if path_results.size() > 0: + results[path] = path_results + + return results + + +func get_selection_key(path: String, path_result: Dictionary) -> String: + return "%s-%d-%d" % [path, path_result.line, path_result.index] + + +func find_in_line(line: String, query: String, from_index: int = 0) -> int: + if match_case_button.button_pressed: + return line.find(query, from_index) + else: + return line.findn(query, from_index) + + +func replace_results(only_selected: bool) -> void: + var file: FileAccess + var lines: PackedStringArray = [] + for path in current_results: + if main_view.open_buffers.has(path): + lines = main_view.open_buffers.get(path).text.split("\n") + else: + file = FileAccess.open(path, FileAccess.READ) + lines = file.get_as_text().split("\n") + + # Read the results in reverse because we're going to be modifying them as we go + var path_results: Array = current_results.get(path).duplicate() + path_results.reverse() + for path_result in path_results: + var key: String = get_selection_key(path, path_result) + if not only_selected or (only_selected and selections.has(key)): + lines[path_result.line] = lines[path_result.line].substr(0, path_result.index) + replace_input.text + lines[path_result.line].substr(path_result.index + path_result.matched_text.length()) + + var replaced_text: String = "\n".join(lines) + if file != null and file.is_open(): + file.close() + file = FileAccess.open(path, FileAccess.WRITE) + file.store_string(replaced_text) + file.close() + else: + main_view.open_buffers.get(path).text = replaced_text + if main_view.current_file_path == path: + main_view.code_edit.text = replaced_text + + current_results = find_in_files() + + +#endregion + +#region signals + + +func _on_search_button_pressed() -> void: + selections.clear() + self.current_results = find_in_files() + + +func _on_input_text_submitted(new_text: String) -> void: + _on_search_button_pressed() + + +func _on_replace_toggle_toggled(toggled_on: bool) -> void: + replace_container.visible = toggled_on + if toggled_on: + replace_input.grab_focus() + update_results_view() + + +func _on_replace_input_text_changed(new_text: String) -> void: + update_results_view() + + +func _on_replace_selected_button_pressed() -> void: + replace_results(true) + + +func _on_replace_all_button_pressed() -> void: + replace_results(false) + + +func _on_match_case_button_toggled(toggled_on: bool) -> void: + _on_search_button_pressed() + + +#endregion diff --git a/addons/dialogue_manager/views/find_in_dialogue_view.gd.uid b/addons/dialogue_manager/views/find_in_dialogue_view.gd.uid new file mode 100644 index 0000000..380a491 --- /dev/null +++ b/addons/dialogue_manager/views/find_in_dialogue_view.gd.uid @@ -0,0 +1 @@ +uid://q368fmxxa8sd diff --git a/addons/dialogue_manager/views/find_in_dialogue_view.tscn b/addons/dialogue_manager/views/find_in_dialogue_view.tscn new file mode 100644 index 0000000..302e160 --- /dev/null +++ b/addons/dialogue_manager/views/find_in_dialogue_view.tscn @@ -0,0 +1,139 @@ +[gd_scene load_steps=3 format=3 uid="uid://0n7hwviyyly4"] + +[ext_resource type="Script" uid="uid://q368fmxxa8sd" path="res://addons/dialogue_manager/views/find_in_dialogue_view.gd" id="1_3xicy"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_owohg"] +bg_color = Color(0.266667, 0.278431, 0.352941, 0.243137) +corner_detail = 1 + +[node name="FindInFiles" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_3xicy") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="FindContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/FindContainer"] +layout_mode = 2 +text = "Find:" + +[node name="Input" type="LineEdit" parent="VBoxContainer/HBoxContainer/FindContainer"] +unique_name_in_owner = true +layout_mode = 2 +clear_button_enabled = true + +[node name="FindToolbar" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/FindContainer"] +layout_mode = 2 + +[node name="SearchButton" type="Button" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"] +unique_name_in_owner = true +layout_mode = 2 +text = "Find all..." + +[node name="MatchCaseButton" type="CheckBox" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"] +unique_name_in_owner = true +layout_mode = 2 +text = "Match case" + +[node name="Control" type="Control" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ReplaceToggle" type="CheckButton" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"] +unique_name_in_owner = true +layout_mode = 2 +text = "Replace" + +[node name="ReplaceContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ReplaceLabel" type="Label" parent="VBoxContainer/HBoxContainer/ReplaceContainer"] +layout_mode = 2 +text = "Replace with:" + +[node name="ReplaceInput" type="LineEdit" parent="VBoxContainer/HBoxContainer/ReplaceContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +clear_button_enabled = true + +[node name="ReplaceToolbar" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/ReplaceContainer"] +layout_mode = 2 + +[node name="ReplaceSelectedButton" type="Button" parent="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar"] +unique_name_in_owner = true +layout_mode = 2 +text = "Replace selected" + +[node name="ReplaceAllButton" type="Button" parent="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar"] +unique_name_in_owner = true +layout_mode = 2 +text = "Replace all" + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="ReplaceToolbar" type="HBoxContainer" parent="VBoxContainer/VBoxContainer"] +layout_mode = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +follow_focus = true + +[node name="ResultsContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 0 + +[node name="ResultTemplate" type="HBoxContainer" parent="."] +unique_name_in_owner = true +layout_mode = 0 +offset_left = 155.0 +offset_top = -74.0 +offset_right = 838.0 +offset_bottom = -51.0 + +[node name="CheckBox" type="CheckBox" parent="ResultTemplate"] +layout_mode = 2 + +[node name="Label" type="RichTextLabel" parent="ResultTemplate"] +layout_mode = 2 +size_flags_horizontal = 3 +focus_mode = 2 +theme_override_styles/focus = SubResource("StyleBoxFlat_owohg") +bbcode_enabled = true +text = "Result" +fit_content = true +scroll_active = false + +[connection signal="text_submitted" from="VBoxContainer/HBoxContainer/FindContainer/Input" to="." method="_on_input_text_submitted"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer/FindContainer/FindToolbar/SearchButton" to="." method="_on_search_button_pressed"] +[connection signal="toggled" from="VBoxContainer/HBoxContainer/FindContainer/FindToolbar/MatchCaseButton" to="." method="_on_match_case_button_toggled"] +[connection signal="toggled" from="VBoxContainer/HBoxContainer/FindContainer/FindToolbar/ReplaceToggle" to="." method="_on_replace_toggle_toggled"] +[connection signal="text_changed" from="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceInput" to="." method="_on_replace_input_text_changed"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar/ReplaceSelectedButton" to="." method="_on_replace_selected_button_pressed"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar/ReplaceAllButton" to="." method="_on_replace_all_button_pressed"] diff --git a/addons/dialogue_manager/views/main_view.gd b/addons/dialogue_manager/views/main_view.gd index 0742afb..a5b9de7 100644 --- a/addons/dialogue_manager/views/main_view.gd +++ b/addons/dialogue_manager/views/main_view.gd @@ -2,11 +2,6 @@ extends Control -const DialogueConstants = preload("../constants.gd") -const DialogueSettings = preload("../settings.gd") -const DialogueResource = preload("../dialogue_resource.gd") -const DialogueManagerParser = preload("../components/parser.gd") - const OPEN_OPEN = 100 const OPEN_QUICK = 101 const OPEN_CLEAR = 102 @@ -33,7 +28,13 @@ enum TranslationSource { signal confirmation_closed() -@onready var parse_timer := $ParseTimer +@onready var parse_timer: Timer = $ParseTimer + +# Banner +@onready var banner: CenterContainer = %Banner +@onready var banner_new_button: Button = %BannerNewButton +@onready var banner_quick_open: Button = %BannerQuickOpen +@onready var banner_examples: Button = %BannerExamples # Dialogs @onready var new_dialog: FileDialog = $NewDialog @@ -44,13 +45,9 @@ signal confirmation_closed() @onready var export_dialog: FileDialog = $ExportDialog @onready var import_dialog: FileDialog = $ImportDialog @onready var errors_dialog: AcceptDialog = $ErrorsDialog -@onready var settings_dialog: AcceptDialog = $SettingsDialog -@onready var settings_view := $SettingsDialog/SettingsView @onready var build_error_dialog: AcceptDialog = $BuildErrorDialog @onready var close_confirmation_dialog: ConfirmationDialog = $CloseConfirmationDialog @onready var updated_dialog: AcceptDialog = $UpdatedDialog -@onready var find_in_files_dialog: AcceptDialog = $FindInFilesDialog -@onready var find_in_files: Control = $FindInFilesDialog/FindInFiles # Toolbar @onready var new_button: Button = %NewButton @@ -58,10 +55,10 @@ signal confirmation_closed() @onready var save_all_button: Button = %SaveAllButton @onready var find_in_files_button: Button = %FindInFilesButton @onready var test_button: Button = %TestButton +@onready var test_line_button: Button = %TestLineButton @onready var search_button: Button = %SearchButton @onready var insert_button: MenuButton = %InsertButton @onready var translations_button: MenuButton = %TranslationsButton -@onready var settings_button: Button = %SettingsButton @onready var support_button: Button = %SupportButton @onready var docs_button: Button = %DocsButton @onready var version_label: Label = %VersionLabel @@ -74,7 +71,7 @@ signal confirmation_closed() @onready var files_list := %FilesList @onready var files_popup_menu: PopupMenu = %FilesPopupMenu @onready var title_list := %TitleList -@onready var code_edit := %CodeEdit +@onready var code_edit: DMCodeEdit = %CodeEdit @onready var errors_panel := %ErrorsPanel # The currently open file @@ -85,6 +82,7 @@ var current_file_path: String = "": if current_file_path == "" or not open_buffers.has(current_file_path): save_all_button.disabled = true test_button.disabled = true + test_line_button.disabled = true search_button.disabled = true insert_button.disabled = true translations_button.disabled = true @@ -93,8 +91,11 @@ var current_file_path: String = "": title_list.hide() code_edit.hide() errors_panel.hide() + search_and_replace.hide() + banner.show() else: test_button.disabled = false + test_line_button.disabled = false search_button.disabled = false insert_button.disabled = false translations_button.disabled = false @@ -102,17 +103,25 @@ var current_file_path: String = "": files_list.show() title_list.show() code_edit.show() + banner.hide() + + var cursor: Vector2 = DMSettings.get_caret(current_file_path) + var scroll_vertical: int = DMSettings.get_scroll(current_file_path) code_edit.text = open_buffers[current_file_path].text code_edit.errors = [] code_edit.clear_undo_history() - code_edit.set_cursor(DialogueSettings.get_caret(current_file_path)) + code_edit.set_cursor(cursor) + code_edit.scroll_vertical = scroll_vertical code_edit.grab_focus() _on_code_edit_text_changed() errors_panel.errors = [] code_edit.errors = [] + + if search_and_replace.visible: + search_and_replace.search() get: return current_file_path @@ -137,16 +146,16 @@ func _ready() -> void: version_label.text = "v%s" % plugin.get_version() update_button.on_before_refresh = func on_before_refresh(): # Save everything - DialogueSettings.set_user_value("just_refreshed", { + DMSettings.set_user_value("just_refreshed", { current_file_path = current_file_path, open_buffers = open_buffers }) return true # Did we just load from an addon version refresh? - var just_refreshed = DialogueSettings.get_user_value("just_refreshed", null) + var just_refreshed = DMSettings.get_user_value("just_refreshed", null) if just_refreshed != null: - DialogueSettings.set_user_value("just_refreshed", null) + DMSettings.set_user_value("just_refreshed", null) call_deferred("load_from_version_refresh", just_refreshed) # Hook up the search toolbar @@ -157,35 +166,39 @@ func _ready() -> void: translations_button.get_popup().id_pressed.connect(_on_translations_button_menu_id_pressed) code_edit.main_view = self - code_edit.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY if DialogueSettings.get_setting("wrap_lines", false) else TextEdit.LINE_WRAPPING_NONE - var editor_settings: EditorSettings = plugin.get_editor_interface().get_editor_settings() + var editor_settings: EditorSettings = EditorInterface.get_editor_settings() editor_settings.settings_changed.connect(_on_editor_settings_changed) _on_editor_settings_changed() + ProjectSettings.settings_changed.connect(_on_project_settings_changed) + _on_project_settings_changed() + # Reopen any files that were open when Godot was closed if editor_settings.get_setting("text_editor/behavior/files/restore_scripts_on_load"): - var reopen_files: Array = DialogueSettings.get_user_value("reopen_files", []) + var reopen_files: Array = DMSettings.get_user_value("reopen_files", []) for reopen_file in reopen_files: open_file(reopen_file) - self.current_file_path = DialogueSettings.get_user_value("most_recent_reopen_file", "") + self.current_file_path = DMSettings.get_user_value("most_recent_reopen_file", "") save_all_button.disabled = true - close_confirmation_dialog.ok_button_text = DialogueConstants.translate(&"confirm_close.save") - close_confirmation_dialog.add_button(DialogueConstants.translate(&"confirm_close.discard"), true, "discard") + close_confirmation_dialog.ok_button_text = DMConstants.translate(&"confirm_close.save") + close_confirmation_dialog.add_button(DMConstants.translate(&"confirm_close.discard"), true, "discard") - errors_dialog.dialog_text = DialogueConstants.translate(&"errors_in_script") + errors_dialog.dialog_text = DMConstants.translate(&"errors_in_script") # Update the buffer if a file was modified externally (retains undo step) - Engine.get_meta("DialogueCache").file_content_changed.connect(_on_cache_file_content_changed) + Engine.get_meta("DMCache").file_content_changed.connect(_on_cache_file_content_changed) - plugin.get_editor_interface().get_file_system_dock().files_moved.connect(_on_files_moved) + EditorInterface.get_file_system_dock().files_moved.connect(_on_files_moved) + + code_edit.get_v_scroll_bar().value_changed.connect(_on_code_edit_scroll_changed) func _exit_tree() -> void: - DialogueSettings.set_user_value("reopen_files", open_buffers.keys()) - DialogueSettings.set_user_value("most_recent_reopen_file", self.current_file_path) + DMSettings.set_user_value("reopen_files", open_buffers.keys()) + DMSettings.set_user_value("most_recent_reopen_file", self.current_file_path) func _unhandled_input(event: InputEvent) -> void: @@ -225,13 +238,12 @@ func load_from_version_refresh(just_refreshed: Dictionary) -> void: else: open_buffers = just_refreshed.open_buffers - var interface: EditorInterface = plugin.get_editor_interface() if just_refreshed.current_file_path != "": - interface.edit_resource(load(just_refreshed.current_file_path)) + EditorInterface.edit_resource(load(just_refreshed.current_file_path)) else: - interface.set_main_screen_editor("Dialogue") + EditorInterface.set_main_screen_editor("Dialogue") - updated_dialog.dialog_text = DialogueConstants.translate(&"update.success").format({ version = update_button.get_version() }) + updated_dialog.dialog_text = DMConstants.translate(&"update.success").format({ version = update_button.get_version() }) updated_dialog.popup_centered() @@ -241,12 +253,11 @@ func new_file(path: String, content: String = "") -> void: var file: FileAccess = FileAccess.open(path, FileAccess.WRITE) if content == "": - if DialogueSettings.get_setting("new_with_template", true): - file.store_string(DialogueSettings.get_setting("new_template", "")) + file.store_string(DMSettings.get_setting(DMSettings.NEW_FILE_TEMPLATE, "")) else: file.store_string(content) - plugin.get_editor_interface().get_resource_filesystem().scan() + EditorInterface.get_resource_filesystem().scan() # Open a dialogue resource for editing @@ -267,7 +278,7 @@ func open_file(path: String) -> void: pristine_text = text } - DialogueSettings.add_recent_file(path) + DMSettings.add_recent_file(path) build_open_menu() files_list.files = open_buffers.keys() @@ -276,12 +287,14 @@ func open_file(path: String) -> void: self.current_file_path = path -func show_file_in_filesystem(path: String) -> void: - var file_system_dock: FileSystemDock = plugin \ - .get_editor_interface() \ - .get_file_system_dock() +func quick_open() -> void: + quick_open_files_list.files = Engine.get_meta("DMCache").get_files() + quick_open_dialog.popup_centered() + quick_open_files_list.focus_filter() - file_system_dock.navigate_to_path(path) + +func show_file_in_filesystem(path: String) -> void: + EditorInterface.get_file_system_dock().navigate_to_path(path) # Save any open files @@ -295,7 +308,7 @@ func save_files() -> void: save_file(path, false) if saved_files.size() > 0: - Engine.get_meta("DialogueCache").mark_files_for_reimport(saved_files) + Engine.get_meta("DMCache").mark_files_for_reimport(saved_files) # Save a file @@ -317,10 +330,7 @@ func save_file(path: String, rescan_file_system: bool = true) -> void: file.close() if rescan_file_system: - plugin \ - .get_editor_interface() \ - .get_resource_filesystem()\ - .scan() + EditorInterface.get_resource_filesystem().scan() func close_file(path: String) -> void: @@ -332,7 +342,7 @@ func close_file(path: String) -> void: remove_file_from_open_buffers(path) await get_tree().process_frame else: - close_confirmation_dialog.dialog_text = DialogueConstants.translate(&"confirm_close").format({ path = path.get_file() }) + close_confirmation_dialog.dialog_text = DMConstants.translate(&"confirm_close").format({ path = path.get_file() }) close_confirmation_dialog.popup_centered() await confirmation_closed @@ -355,8 +365,8 @@ func remove_file_from_open_buffers(path: String) -> void: # Apply theme colors and icons to the UI func apply_theme() -> void: if is_instance_valid(plugin) and is_instance_valid(code_edit): - var scale: float = plugin.get_editor_interface().get_editor_scale() - var editor_settings = plugin.get_editor_interface().get_editor_settings() + var scale: float = EditorInterface.get_editor_scale() + var editor_settings = EditorInterface.get_editor_settings() code_edit.theme_overrides = { scale = scale, @@ -371,79 +381,84 @@ func apply_theme() -> void: text_color = editor_settings.get_setting("text_editor/theme/highlighting/text_color"), conditions_color = editor_settings.get_setting("text_editor/theme/highlighting/keyword_color"), mutations_color = editor_settings.get_setting("text_editor/theme/highlighting/function_color"), + mutations_line_color = Color(editor_settings.get_setting("text_editor/theme/highlighting/function_color"), 0.6), members_color = editor_settings.get_setting("text_editor/theme/highlighting/member_variable_color"), strings_color = editor_settings.get_setting("text_editor/theme/highlighting/string_color"), numbers_color = editor_settings.get_setting("text_editor/theme/highlighting/number_color"), symbols_color = editor_settings.get_setting("text_editor/theme/highlighting/symbol_color"), comments_color = editor_settings.get_setting("text_editor/theme/highlighting/comment_color"), - jumps_color = Color(editor_settings.get_setting("text_editor/theme/highlighting/control_flow_keyword_color"), 0.7), + jumps_color = Color(editor_settings.get_setting("text_editor/theme/highlighting/control_flow_keyword_color"), 0.6), font_size = editor_settings.get_setting("interface/editor/code_font_size") } + banner_new_button.icon = get_theme_icon("New", "EditorIcons") + banner_quick_open.icon = get_theme_icon("Load", "EditorIcons") + new_button.icon = get_theme_icon("New", "EditorIcons") - new_button.tooltip_text = DialogueConstants.translate(&"start_a_new_file") + new_button.tooltip_text = DMConstants.translate(&"start_a_new_file") open_button.icon = get_theme_icon("Load", "EditorIcons") - open_button.tooltip_text = DialogueConstants.translate(&"open_a_file") + open_button.tooltip_text = DMConstants.translate(&"open_a_file") save_all_button.icon = get_theme_icon("Save", "EditorIcons") - save_all_button.tooltip_text = DialogueConstants.translate(&"start_all_files") + save_all_button.text = DMConstants.translate(&"all") + save_all_button.tooltip_text = DMConstants.translate(&"start_all_files") find_in_files_button.icon = get_theme_icon("ViewportZoom", "EditorIcons") - find_in_files_button.tooltip_text = DialogueConstants.translate(&"find_in_files") + find_in_files_button.tooltip_text = DMConstants.translate(&"find_in_files") - test_button.icon = get_theme_icon("PlayScene", "EditorIcons") - test_button.tooltip_text = DialogueConstants.translate(&"test_dialogue") + test_button.icon = get_theme_icon("DebugNext", "EditorIcons") + test_button.tooltip_text = DMConstants.translate(&"test_dialogue") + + test_line_button.icon = get_theme_icon("DebugStep", "EditorIcons") + test_line_button.tooltip_text = DMConstants.translate(&"test_dialogue_from_line") search_button.icon = get_theme_icon("Search", "EditorIcons") - search_button.tooltip_text = DialogueConstants.translate(&"search_for_text") + search_button.tooltip_text = DMConstants.translate(&"search_for_text") insert_button.icon = get_theme_icon("RichTextEffect", "EditorIcons") - insert_button.text = DialogueConstants.translate(&"insert") + insert_button.text = DMConstants.translate(&"insert") translations_button.icon = get_theme_icon("Translation", "EditorIcons") - translations_button.text = DialogueConstants.translate(&"translations") - - settings_button.icon = get_theme_icon("Tools", "EditorIcons") - settings_button.tooltip_text = DialogueConstants.translate(&"settings") + translations_button.text = DMConstants.translate(&"translations") support_button.icon = get_theme_icon("Heart", "EditorIcons") - support_button.text = DialogueConstants.translate(&"sponsor") - support_button.tooltip_text = DialogueConstants.translate(&"show_support") + support_button.text = DMConstants.translate(&"sponsor") + support_button.tooltip_text = DMConstants.translate(&"show_support") docs_button.icon = get_theme_icon("Help", "EditorIcons") - docs_button.text = DialogueConstants.translate(&"docs") + docs_button.text = DMConstants.translate(&"docs") update_button.apply_theme() # Set up the effect menu var popup: PopupMenu = insert_button.get_popup() popup.clear() - popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate(&"insert.wave_bbcode"), 0) - popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate(&"insert.shake_bbcode"), 1) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.wave_bbcode"), 0) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.shake_bbcode"), 1) popup.add_separator() - popup.add_icon_item(get_theme_icon("Time", "EditorIcons"), DialogueConstants.translate(&"insert.typing_pause"), 3) - popup.add_icon_item(get_theme_icon("ViewportSpeed", "EditorIcons"), DialogueConstants.translate(&"insert.typing_speed_change"), 4) - popup.add_icon_item(get_theme_icon("DebugNext", "EditorIcons"), DialogueConstants.translate(&"insert.auto_advance"), 5) - popup.add_separator(DialogueConstants.translate(&"insert.templates")) - popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate(&"insert.title"), 6) - popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate(&"insert.dialogue"), 7) - popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate(&"insert.response"), 8) - popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate(&"insert.random_lines"), 9) - popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate(&"insert.random_text"), 10) - popup.add_separator(DialogueConstants.translate(&"insert.actions")) - popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate(&"insert.jump"), 11) - popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate(&"insert.end_dialogue"), 12) + popup.add_icon_item(get_theme_icon("Time", "EditorIcons"), DMConstants.translate(&"insert.typing_pause"), 3) + popup.add_icon_item(get_theme_icon("ViewportSpeed", "EditorIcons"), DMConstants.translate(&"insert.typing_speed_change"), 4) + popup.add_icon_item(get_theme_icon("DebugNext", "EditorIcons"), DMConstants.translate(&"insert.auto_advance"), 5) + popup.add_separator(DMConstants.translate(&"insert.templates")) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.title"), 6) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.dialogue"), 7) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.response"), 8) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.random_lines"), 9) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.random_text"), 10) + popup.add_separator(DMConstants.translate(&"insert.actions")) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.jump"), 11) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.end_dialogue"), 12) # Set up the translations menu popup = translations_button.get_popup() popup.clear() - popup.add_icon_item(get_theme_icon("Translation", "EditorIcons"), DialogueConstants.translate(&"generate_line_ids"), TRANSLATIONS_GENERATE_LINE_IDS) + popup.add_icon_item(get_theme_icon("Translation", "EditorIcons"), DMConstants.translate(&"generate_line_ids"), TRANSLATIONS_GENERATE_LINE_IDS) popup.add_separator() - popup.add_icon_item(get_theme_icon("FileList", "EditorIcons"), DialogueConstants.translate(&"save_characters_to_csv"), TRANSLATIONS_SAVE_CHARACTERS_TO_CSV) - popup.add_icon_item(get_theme_icon("FileList", "EditorIcons"), DialogueConstants.translate(&"save_to_csv"), TRANSLATIONS_SAVE_TO_CSV) - popup.add_icon_item(get_theme_icon("AssetLib", "EditorIcons"), DialogueConstants.translate(&"import_from_csv"), TRANSLATIONS_IMPORT_FROM_CSV) + popup.add_icon_item(get_theme_icon("FileList", "EditorIcons"), DMConstants.translate(&"save_characters_to_csv"), TRANSLATIONS_SAVE_CHARACTERS_TO_CSV) + popup.add_icon_item(get_theme_icon("FileList", "EditorIcons"), DMConstants.translate(&"save_to_csv"), TRANSLATIONS_SAVE_TO_CSV) + popup.add_icon_item(get_theme_icon("AssetLib", "EditorIcons"), DMConstants.translate(&"import_from_csv"), TRANSLATIONS_IMPORT_FROM_CSV) # Dialog sizes new_dialog.min_size = Vector2(600, 500) * scale @@ -452,9 +467,6 @@ func apply_theme() -> void: quick_open_dialog.min_size = Vector2(400, 600) * scale export_dialog.min_size = Vector2(600, 500) * scale import_dialog.min_size = Vector2(600, 500) * scale - settings_dialog.min_size = Vector2(1000, 600) * scale - settings_dialog.max_size = Vector2(1000, 600) * scale - find_in_files_dialog.min_size = Vector2(800, 600) * scale ### Helpers @@ -464,13 +476,13 @@ func apply_theme() -> void: func build_open_menu() -> void: var menu = open_button.get_popup() menu.clear() - menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), DialogueConstants.translate(&"open.open"), OPEN_OPEN) - menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), DialogueConstants.translate(&"open.quick_open"), OPEN_QUICK) + menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), DMConstants.translate(&"open.open"), OPEN_OPEN) + menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), DMConstants.translate(&"open.quick_open"), OPEN_QUICK) menu.add_separator() - var recent_files = DialogueSettings.get_recent_files() + var recent_files = DMSettings.get_recent_files() if recent_files.size() == 0: - menu.add_item(DialogueConstants.translate(&"open.no_recent_files")) + menu.add_item(DMConstants.translate(&"open.no_recent_files")) menu.set_item_disabled(2, true) else: for path in recent_files: @@ -478,7 +490,7 @@ func build_open_menu() -> void: menu.add_icon_item(get_theme_icon("File", "EditorIcons"), path) menu.add_separator() - menu.add_item(DialogueConstants.translate(&"open.clear_recent_files"), OPEN_CLEAR) + menu.add_item(DMConstants.translate(&"open.clear_recent_files"), OPEN_CLEAR) if menu.id_pressed.is_connected(_on_open_menu_id_pressed): menu.id_pressed.disconnect(_on_open_menu_id_pressed) menu.id_pressed.connect(_on_open_menu_id_pressed) @@ -487,95 +499,34 @@ func build_open_menu() -> void: # Get the last place a CSV, etc was exported func get_last_export_path(extension: String) -> String: var filename = current_file_path.get_file().replace(".dialogue", "." + extension) - return DialogueSettings.get_user_value("last_export_path", current_file_path.get_base_dir()) + "/" + filename + return DMSettings.get_user_value("last_export_path", current_file_path.get_base_dir()) + "/" + filename # Check the current text for errors -func parse() -> void: +func compile() -> void: # Skip if nothing to parse if current_file_path == "": return - var parser = DialogueManagerParser.new() - var errors: Array[Dictionary] = [] - if parser.parse(code_edit.text, current_file_path) != OK: - errors = parser.get_errors() - code_edit.errors = errors - errors_panel.errors = errors - parser.free() - + var result: DMCompilerResult = DMCompiler.compile_string(code_edit.text, current_file_path) + code_edit.errors = result.errors + errors_panel.errors = result.errors title_list.titles = code_edit.get_titles() func show_build_error_dialog() -> void: - build_error_dialog.dialog_text = DialogueConstants.translate(&"errors_with_build") + build_error_dialog.dialog_text = DMConstants.translate(&"errors_with_build") build_error_dialog.popup_centered() # Generate translation line IDs for any line that doesn't already have one func generate_translations_keys() -> void: - randomize() - seed(Time.get_unix_time_from_system()) - - var parser = DialogueManagerParser.new() - var cursor: Vector2 = code_edit.get_cursor() - var lines: PackedStringArray = code_edit.text.split("\n") - - var key_regex = RegEx.new() - key_regex.compile("\\[ID:(?<key>.*?)\\]") - - # Make list of known keys - var known_keys = {} - for i in range(0, lines.size()): - var line = lines[i] - var found = key_regex.search(line) - if found: - var text = "" - var l = line.replace(found.strings[0], "").strip_edges().strip_edges() - if l.begins_with("- "): - text = parser.extract_response_prompt(l) - elif ":" in l: - text = l.split(":")[1] - else: - text = l - known_keys[found.strings[found.names.get("key")]] = text - - # Add in any that are missing - for i in lines.size(): - var line = lines[i] - var l = line.strip_edges() - - if parser.is_line_empty(l): continue - if parser.is_condition_line(l, true): continue - if parser.is_title_line(l): continue - if parser.is_mutation_line(l): continue - if parser.is_goto_line(l): continue - if parser.is_import_line(l): continue - - if "[ID:" in line: continue - - var key = "t" + str(randi() % 1000000).sha1_text().substr(0, 10) - while key in known_keys: - key = "t" + str(randi() % 1000000).sha1_text().substr(0, 10) - - var text = "" - if l.begins_with("- "): - text = parser.extract_response_prompt(l) - else: - text = l.substr(l.find(":") + 1) - - text = text.replace("\n", "!NEWLINE!") - line = line.replace("\\n", "!NEWLINE!") - - lines[i] = line.replace(text, text + " [ID:%s]" % key).replace("!NEWLINE!", "\\n") - known_keys[key] = text - - code_edit.text = "\n".join(lines) + var scroll_vertical = code_edit.scroll_vertical + code_edit.text = DMTranslationUtilities.generate_translation_keys(code_edit.text) code_edit.set_cursor(cursor) + code_edit.scroll_vertical = scroll_vertical _on_code_edit_text_changed() - parser.free() - # Add a translation file to the project settings func add_path_to_project_translations(path: String) -> void: @@ -587,168 +538,23 @@ func add_path_to_project_translations(path: String) -> void: # Export dialogue and responses to CSV func export_translations_to_csv(path: String) -> void: - var default_locale: String = DialogueSettings.get_setting("default_csv_locale", "en") + DMTranslationUtilities.export_translations_to_csv(path, code_edit.text, current_file_path) - var file: FileAccess - - # If the file exists, open it first and work out which keys are already in it - var existing_csv: Dictionary = {} - var column_count: int = 2 - var default_locale_column: int = 1 - var character_column: int = -1 - var notes_column: int = -1 - if FileAccess.file_exists(path): - file = FileAccess.open(path, FileAccess.READ) - var is_first_line = true - var line: Array - while !file.eof_reached(): - line = file.get_csv_line() - if is_first_line: - is_first_line = false - column_count = line.size() - for i in range(1, line.size()): - if line[i] == default_locale: - default_locale_column = i - elif line[i] == "_character": - character_column = i - elif line[i] == "_notes": - notes_column = i - - # Make sure the line isn't empty before adding it - if line.size() > 0 and line[0].strip_edges() != "": - existing_csv[line[0]] = line - - # The character column wasn't found in the existing file but the setting is turned on - if character_column == -1 and DialogueSettings.get_setting("include_character_in_translation_exports", false): - character_column = column_count - column_count += 1 - existing_csv["keys"].append("_character") - - # The notes column wasn't found in the existing file but the setting is turned on - if notes_column == -1 and DialogueSettings.get_setting("include_notes_in_translation_exports", false): - notes_column = column_count - column_count += 1 - existing_csv["keys"].append("_notes") - - # Start a new file - file = FileAccess.open(path, FileAccess.WRITE) - - if not FileAccess.file_exists(path): - var headings: PackedStringArray = ["keys", default_locale] - if DialogueSettings.get_setting("include_character_in_translation_exports", false): - character_column = headings.size() - headings.append("_character") - if DialogueSettings.get_setting("include_notes_in_translation_exports", false): - notes_column = headings.size() - headings.append("_notes") - file.store_csv_line(headings) - column_count = headings.size() - - # Write our translations to file - var known_keys: PackedStringArray = [] - - var dialogue: Dictionary = DialogueManagerParser.parse_string(code_edit.text, current_file_path).lines - - # Make a list of stuff that needs to go into the file - var lines_to_save = [] - for key in dialogue.keys(): - var line: Dictionary = dialogue.get(key) - - if not line.type in [DialogueConstants.TYPE_DIALOGUE, DialogueConstants.TYPE_RESPONSE]: continue - if line.translation_key in known_keys: continue - - known_keys.append(line.translation_key) - - var line_to_save: PackedStringArray = [] - if existing_csv.has(line.translation_key): - line_to_save = existing_csv.get(line.translation_key) - line_to_save.resize(column_count) - existing_csv.erase(line.translation_key) - else: - line_to_save.resize(column_count) - line_to_save[0] = line.translation_key - - line_to_save[default_locale_column] = line.text - if character_column > -1: - line_to_save[character_column] = "(response)" if line.type == DialogueConstants.TYPE_RESPONSE else line.character - if notes_column > -1: - line_to_save[notes_column] = line.notes - - lines_to_save.append(line_to_save) - - # Store lines in the file, starting with anything that already exists that hasn't been touched - for line in existing_csv.values(): - file.store_csv_line(line) - for line in lines_to_save: - file.store_csv_line(line) - - file.close() - - plugin.get_editor_interface().get_resource_filesystem().scan() - plugin.get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", path) + EditorInterface.get_resource_filesystem().scan() + EditorInterface.get_file_system_dock().call_deferred("navigate_to_path", path) # Add it to the project l10n settings if it's not already there + var default_locale: String = DMSettings.get_setting(DMSettings.DEFAULT_CSV_LOCALE, "en") var language_code: RegExMatch = RegEx.create_from_string("^[a-z]{2,3}").search(default_locale) var translation_path: String = path.replace(".csv", ".%s.translation" % language_code.get_string()) call_deferred("add_path_to_project_translations", translation_path) func export_character_names_to_csv(path: String) -> void: - var file: FileAccess + DMTranslationUtilities.export_character_names_to_csv(path, code_edit.text, current_file_path) - # If the file exists, open it first and work out which keys are already in it - var existing_csv = {} - var commas = [] - if FileAccess.file_exists(path): - file = FileAccess.open(path, FileAccess.READ) - var is_first_line = true - var line: Array - while !file.eof_reached(): - line = file.get_csv_line() - if is_first_line: - is_first_line = false - for i in range(2, line.size()): - commas.append("") - # Make sure the line isn't empty before adding it - if line.size() > 0 and line[0].strip_edges() != "": - existing_csv[line[0]] = line - - # Start a new file - file = FileAccess.open(path, FileAccess.WRITE) - - if not file.file_exists(path): - file.store_csv_line(["keys", DialogueSettings.get_setting("default_csv_locale", "en")]) - - # Write our translations to file - var known_keys: PackedStringArray = [] - - var character_names: PackedStringArray = DialogueManagerParser.parse_string(code_edit.text, current_file_path).character_names - - # Make a list of stuff that needs to go into the file - var lines_to_save = [] - for character_name in character_names: - if character_name in known_keys: continue - - known_keys.append(character_name) - - if existing_csv.has(character_name): - var existing_line = existing_csv.get(character_name) - existing_line[1] = character_name - lines_to_save.append(existing_line) - existing_csv.erase(character_name) - else: - lines_to_save.append(PackedStringArray([character_name, character_name] + commas)) - - # Store lines in the file, starting with anything that already exists that hasn't been touched - for line in existing_csv.values(): - file.store_csv_line(line) - for line in lines_to_save: - file.store_csv_line(line) - - file.close() - - plugin.get_editor_interface().get_resource_filesystem().scan() - plugin.get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", path) + EditorInterface.get_resource_filesystem().scan() + EditorInterface.get_file_system_dock().call_deferred("navigate_to_path", path) # Add it to the project l10n settings if it's not already there var translation_path: String = path.replace(".csv", ".en.translation") @@ -758,54 +564,9 @@ func export_character_names_to_csv(path: String) -> void: # Import changes back from an exported CSV by matching translation keys func import_translations_from_csv(path: String) -> void: var cursor: Vector2 = code_edit.get_cursor() - - if not FileAccess.file_exists(path): return - - # Open the CSV file and build a dictionary of the known keys - var keys: Dictionary = {} - var file: FileAccess = FileAccess.open(path, FileAccess.READ) - var csv_line: Array - while !file.eof_reached(): - csv_line = file.get_csv_line() - if csv_line.size() > 1: - keys[csv_line[0]] = csv_line[1] - - var parser: DialogueManagerParser = DialogueManagerParser.new() - - # Now look over each line in the dialogue and replace the content for matched keys - var lines: PackedStringArray = code_edit.text.split("\n") - var start_index: int = 0 - var end_index: int = 0 - for i in range(0, lines.size()): - var line: String = lines[i] - var translation_key: String = parser.extract_translation(line) - if keys.has(translation_key): - if parser.is_dialogue_line(line): - start_index = 0 - # See if we need to skip over a character name - line = line.replace("\\:", "!ESCAPED_COLON!") - if ": " in line: - start_index = line.find(": ") + 2 - lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]").replace("!ESCAPED_COLON!", ":") - - elif parser.is_response_line(line): - start_index = line.find("- ") + 2 - # See if we need to skip over a character name - line = line.replace("\\:", "!ESCAPED_COLON!") - if ": " in line: - start_index = line.find(": ") + 2 - end_index = line.length() - if " =>" in line: - end_index = line.find(" =>") - if " [if " in line: - end_index = line.find(" [if ") - lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]" + line.substr(end_index)).replace("!ESCAPED_COLON!", ":") - - code_edit.text = "\n".join(lines) + code_edit.text = DMTranslationUtilities.import_translations_from_csv(path, code_edit.text) code_edit.set_cursor(cursor) - parser.free() - func show_search_form(is_enabled: bool) -> void: if code_edit.last_selected_text: @@ -816,6 +577,16 @@ func show_search_form(is_enabled: bool) -> void: search_and_replace.focus_line_edit() +func run_test_scene(from_key: String) -> void: + DMSettings.set_user_value("run_title", from_key) + DMSettings.set_user_value("is_running_test_scene", true) + DMSettings.set_user_value("run_resource_path", current_file_path) + var test_scene_path: String = DMSettings.get_setting(DMSettings.CUSTOM_TEST_SCENE_PATH, "res://addons/dialogue_manager/test_scene.tscn") + if ResourceUID.has_id(ResourceUID.text_to_id(test_scene_path)): + test_scene_path = ResourceUID.get_id_path(ResourceUID.text_to_id(test_scene_path)) + EditorInterface.play_custom_scene(test_scene_path) + + ### Signals @@ -829,30 +600,32 @@ func _on_files_moved(old_file: String, new_file: String) -> void: func _on_cache_file_content_changed(path: String, new_content: String) -> void: if open_buffers.has(path): var buffer = open_buffers[path] - if buffer.text != new_content: + if buffer.text == buffer.pristine_text and buffer.text != new_content: buffer.text = new_content - buffer.pristine_text = new_content code_edit.text = new_content title_list.titles = code_edit.get_titles() + buffer.pristine_text = new_content func _on_editor_settings_changed() -> void: - var editor_settings: EditorSettings = plugin.get_editor_interface().get_editor_settings() + var editor_settings: EditorSettings = EditorInterface.get_editor_settings() code_edit.minimap_draw = editor_settings.get_setting("text_editor/appearance/minimap/show_minimap") code_edit.minimap_width = editor_settings.get_setting("text_editor/appearance/minimap/minimap_width") code_edit.scroll_smooth = editor_settings.get_setting("text_editor/behavior/navigation/smooth_scrolling") +func _on_project_settings_changed() -> void: + code_edit.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY if DMSettings.get_setting(DMSettings.WRAP_LONG_LINES, false) else TextEdit.LINE_WRAPPING_NONE + + func _on_open_menu_id_pressed(id: int) -> void: match id: OPEN_OPEN: open_dialog.popup_centered() OPEN_QUICK: - quick_open_files_list.files = Engine.get_meta("DialogueCache").get_files() - quick_open_dialog.popup_centered() - quick_open_files_list.focus_filter() + quick_open() OPEN_CLEAR: - DialogueSettings.clear_recent_files() + DMSettings.clear_recent_files() build_open_menu() _: var menu = open_button.get_popup() @@ -915,7 +688,7 @@ func _on_translations_button_menu_id_pressed(id: int) -> void: func _on_export_dialog_file_selected(path: String) -> void: - DialogueSettings.set_user_value("last_export_path", path.get_base_dir()) + DMSettings.set_user_value("last_export_path", path.get_base_dir()) match path.get_extension(): "csv": match translation_source: @@ -926,7 +699,7 @@ func _on_export_dialog_file_selected(path: String) -> void: func _on_import_dialog_file_selected(path: String) -> void: - DialogueSettings.set_user_value("last_export_path", path.get_base_dir()) + DMSettings.set_user_value("last_export_path", path.get_base_dir()) import_translations_from_csv(path) @@ -940,15 +713,16 @@ func _on_main_view_visibility_changed() -> void: func _on_new_button_pressed() -> void: - new_dialog.current_file = "dialogue" + new_dialog.current_file = "untitled" new_dialog.popup_centered() func _on_new_dialog_confirmed() -> void: - if new_dialog.current_file.get_basename() == "": - var path = "res://untitled.dialogue" - new_file(path) - open_file(path) + var path: String = new_dialog.current_path + if path.get_file() == ".dialogue": + path = "%s/untitled.dialogue" % path.get_basename() + new_file(path) + open_file(path) func _on_new_dialog_file_selected(path: String) -> void: @@ -977,8 +751,8 @@ func _on_quick_open_files_list_file_double_clicked(file_path: String) -> void: func _on_quick_open_dialog_confirmed() -> void: - if quick_open_files_list.current_file_path: - open_file(quick_open_files_list.current_file_path) + if quick_open_files_list.last_selected_file_path: + open_file(quick_open_files_list.last_selected_file_path) func _on_save_all_button_pressed() -> void: @@ -986,8 +760,7 @@ func _on_save_all_button_pressed() -> void: func _on_find_in_files_button_pressed() -> void: - find_in_files_dialog.popup_centered() - find_in_files.prepare() + plugin.show_find_in_dialogue() func _on_code_edit_text_changed() -> void: @@ -1000,13 +773,16 @@ func _on_code_edit_text_changed() -> void: parse_timer.start(1) +func _on_code_edit_scroll_changed(value: int) -> void: + DMSettings.set_scroll(current_file_path, code_edit.scroll_vertical) + + func _on_code_edit_active_title_change(title: String) -> void: title_list.select_title(title) - DialogueSettings.set_user_value("run_title", title) func _on_code_edit_caret_changed() -> void: - DialogueSettings.set_caret(current_file_path, code_edit.get_cursor()) + DMSettings.set_caret(current_file_path, code_edit.get_cursor()) func _on_code_edit_error_clicked(line_number: int) -> void: @@ -1020,11 +796,11 @@ func _on_title_list_title_selected(title: String) -> void: func _on_parse_timer_timeout() -> void: parse_timer.stop() - parse() + compile() func _on_errors_panel_error_pressed(line_number: int, column_number: int) -> void: - code_edit.set_caret_line(line_number) + code_edit.set_caret_line(line_number - 1) code_edit.set_caret_column(column_number) code_edit.grab_focus() @@ -1043,35 +819,32 @@ func _on_search_and_replace_close_requested() -> void: code_edit.grab_focus() -func _on_settings_button_pressed() -> void: - settings_view.prepare() - settings_dialog.popup_centered() - - -func _on_settings_view_script_button_pressed(path: String) -> void: - settings_dialog.hide() - plugin.get_editor_interface().edit_resource(load(path)) - - func _on_test_button_pressed() -> void: save_file(current_file_path, false) - Engine.get_meta("DialogueCache").reimport_files([current_file_path]) + Engine.get_meta("DMCache").reimport_files([current_file_path]) if errors_panel.errors.size() > 0: errors_dialog.popup_centered() return - DialogueSettings.set_user_value("is_running_test_scene", true) - DialogueSettings.set_user_value("run_resource_path", current_file_path) - var test_scene_path: String = DialogueSettings.get_setting("custom_test_scene_path", "res://addons/dialogue_manager/test_scene.tscn") - plugin.get_editor_interface().play_custom_scene(test_scene_path) + run_test_scene("") -func _on_settings_dialog_confirmed() -> void: - settings_view.apply_settings_changes() - parse() - code_edit.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY if DialogueSettings.get_setting("wrap_lines", false) else TextEdit.LINE_WRAPPING_NONE - code_edit.grab_focus() +func _on_test_line_button_pressed() -> void: + save_file(current_file_path) + + if errors_panel.errors.size() > 0: + errors_dialog.popup_centered() + return + + # Find next non-empty line + var line_to_run: int = 0 + for i in range(code_edit.get_cursor().y, code_edit.get_line_count()): + if not code_edit.get_line(i).is_empty(): + line_to_run = i + break + + run_test_scene(str(line_to_run)) func _on_support_button_pressed() -> void: @@ -1096,14 +869,14 @@ func _on_files_popup_menu_about_to_popup() -> void: var shortcuts: Dictionary = plugin.get_editor_shortcuts() - files_popup_menu.add_item(DialogueConstants.translate(&"buffer.save"), ITEM_SAVE, OS.find_keycode_from_string(shortcuts.get("save")[0].as_text_keycode())) - files_popup_menu.add_item(DialogueConstants.translate(&"buffer.save_as"), ITEM_SAVE_AS) - files_popup_menu.add_item(DialogueConstants.translate(&"buffer.close"), ITEM_CLOSE, OS.find_keycode_from_string(shortcuts.get("close_file")[0].as_text_keycode())) - files_popup_menu.add_item(DialogueConstants.translate(&"buffer.close_all"), ITEM_CLOSE_ALL) - files_popup_menu.add_item(DialogueConstants.translate(&"buffer.close_other_files"), ITEM_CLOSE_OTHERS) + files_popup_menu.add_item(DMConstants.translate(&"buffer.save"), ITEM_SAVE, OS.find_keycode_from_string(shortcuts.get("save")[0].as_text_keycode())) + files_popup_menu.add_item(DMConstants.translate(&"buffer.save_as"), ITEM_SAVE_AS) + files_popup_menu.add_item(DMConstants.translate(&"buffer.close"), ITEM_CLOSE, OS.find_keycode_from_string(shortcuts.get("close_file")[0].as_text_keycode())) + files_popup_menu.add_item(DMConstants.translate(&"buffer.close_all"), ITEM_CLOSE_ALL) + files_popup_menu.add_item(DMConstants.translate(&"buffer.close_other_files"), ITEM_CLOSE_OTHERS) files_popup_menu.add_separator() - files_popup_menu.add_item(DialogueConstants.translate(&"buffer.copy_file_path"), ITEM_COPY_PATH) - files_popup_menu.add_item(DialogueConstants.translate(&"buffer.show_in_filesystem"), ITEM_SHOW_IN_FILESYSTEM) + files_popup_menu.add_item(DMConstants.translate(&"buffer.copy_file_path"), ITEM_COPY_PATH) + files_popup_menu.add_item(DMConstants.translate(&"buffer.show_in_filesystem"), ITEM_SHOW_IN_FILESYSTEM) func _on_files_popup_menu_id_pressed(id: int) -> void: @@ -1153,3 +926,22 @@ func _on_close_confirmation_dialog_custom_action(action: StringName) -> void: func _on_find_in_files_result_selected(path: String, cursor: Vector2, length: int) -> void: open_file(path) code_edit.select(cursor.y, cursor.x, cursor.y, cursor.x + length) + code_edit.set_line_as_center_visible(cursor.y) + + +func _on_banner_image_gui_input(event: InputEvent) -> void: + if event.is_pressed(): + OS.shell_open("https://bravestcoconut.com/wishlist") + + +func _on_banner_new_button_pressed() -> void: + new_dialog.current_file = "untitled" + new_dialog.popup_centered() + + +func _on_banner_quick_open_pressed() -> void: + quick_open() + + +func _on_banner_examples_pressed() -> void: + OS.shell_open("https://itch.io/c/5226650/godot-dialogue-manager-example-projects") diff --git a/addons/dialogue_manager/views/main_view.gd.uid b/addons/dialogue_manager/views/main_view.gd.uid new file mode 100644 index 0000000..10e66f4 --- /dev/null +++ b/addons/dialogue_manager/views/main_view.gd.uid @@ -0,0 +1 @@ +uid://cipjcc7bkh1pc diff --git a/addons/dialogue_manager/views/main_view.tscn b/addons/dialogue_manager/views/main_view.tscn index 2ab8004..0a1c67a 100644 --- a/addons/dialogue_manager/views/main_view.tscn +++ b/addons/dialogue_manager/views/main_view.tscn @@ -1,17 +1,16 @@ -[gd_scene load_steps=16 format=3 uid="uid://cbuf1q3xsse3q"] +[gd_scene load_steps=15 format=3 uid="uid://cbuf1q3xsse3q"] -[ext_resource type="Script" path="res://addons/dialogue_manager/views/main_view.gd" id="1_h6qfq"] +[ext_resource type="Script" uid="uid://cipjcc7bkh1pc" path="res://addons/dialogue_manager/views/main_view.gd" id="1_h6qfq"] [ext_resource type="PackedScene" uid="uid://civ6shmka5e8u" path="res://addons/dialogue_manager/components/code_edit.tscn" id="2_f73fm"] [ext_resource type="PackedScene" uid="uid://dnufpcdrreva3" path="res://addons/dialogue_manager/components/files_list.tscn" id="2_npj2k"] [ext_resource type="PackedScene" uid="uid://ctns6ouwwd68i" path="res://addons/dialogue_manager/components/title_list.tscn" id="2_onb4i"] [ext_resource type="PackedScene" uid="uid://co8yl23idiwbi" path="res://addons/dialogue_manager/components/update_button.tscn" id="2_ph3vs"] [ext_resource type="PackedScene" uid="uid://gr8nakpbrhby" path="res://addons/dialogue_manager/components/search_and_replace.tscn" id="6_ylh0t"] [ext_resource type="PackedScene" uid="uid://cs8pwrxr5vxix" path="res://addons/dialogue_manager/components/errors_panel.tscn" id="7_5cvl4"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="7_necsa"] -[ext_resource type="PackedScene" uid="uid://cpg4lg1r3ff6m" path="res://addons/dialogue_manager/views/settings_view.tscn" id="9_8bf36"] -[ext_resource type="PackedScene" uid="uid://0n7hwviyyly4" path="res://addons/dialogue_manager/components/find_in_files.tscn" id="10_yold3"] +[ext_resource type="Script" uid="uid://klpiq4tk3t7a" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="7_necsa"] +[ext_resource type="Texture2D" uid="uid://cnm67htuohhlo" path="res://addons/dialogue_manager/assets/banner.png" id="9_y6rqu"] -[sub_resource type="Image" id="Image_68x0r"] +[sub_resource type="Image" id="Image_faxki"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), "format": "RGBA8", @@ -21,9 +20,9 @@ data = { } [sub_resource type="ImageTexture" id="ImageTexture_ka3gk"] -image = SubResource("Image_68x0r") +image = SubResource("Image_faxki") -[sub_resource type="Image" id="Image_j5bhl"] +[sub_resource type="Image" id="Image_y6rqu"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), "format": "RGBA8", @@ -33,9 +32,9 @@ data = { } [sub_resource type="ImageTexture" id="ImageTexture_57eek"] -image = SubResource("Image_j5bhl") +image = SubResource("Image_y6rqu") -[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_bxc68"] +[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_mpdoc"] script = ExtResource("7_necsa") [node name="MainView" type="Control"] @@ -133,7 +132,6 @@ size_flags_vertical = 3 unique_name_in_owner = true visible = false layout_mode = 2 -size_flags_vertical = 3 [node name="FilesPopupMenu" type="PopupMenu" parent="Margin/Content/SidePanel/Bookmarks/FilesList"] unique_name_in_owner = true @@ -159,6 +157,7 @@ text = "Insert" item_count = 15 popup/item_0/text = "Wave BBCode" popup/item_0/icon = SubResource("ImageTexture_57eek") +popup/item_0/id = 0 popup/item_1/text = "Shake BBCode" popup/item_1/icon = SubResource("ImageTexture_57eek") popup/item_1/id = 1 @@ -233,6 +232,9 @@ disabled = true toggle_mode = true flat = true +[node name="Separator2" type="VSeparator" parent="Margin/Content/CodePanel/Toolbar"] +layout_mode = 2 + [node name="TestButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"] unique_name_in_owner = true layout_mode = 2 @@ -240,13 +242,11 @@ tooltip_text = "Test dialogue" disabled = true flat = true -[node name="Separator3" type="VSeparator" parent="Margin/Content/CodePanel/Toolbar"] -layout_mode = 2 - -[node name="SettingsButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"] +[node name="TestLineButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"] unique_name_in_owner = true layout_mode = 2 -tooltip_text = "Settings" +tooltip_text = "Test dialogue" +disabled = true flat = true [node name="Spacer2" type="Control" parent="Margin/Content/CodePanel/Toolbar"] @@ -279,6 +279,7 @@ vertical_alignment = 1 [node name="UpdateButton" parent="Margin/Content/CodePanel/Toolbar" instance=ExtResource("2_ph3vs")] unique_name_in_owner = true layout_mode = 2 +text = "v2.44.1 available" [node name="SearchAndReplace" parent="Margin/Content/CodePanel" instance=ExtResource("6_ylh0t")] unique_name_in_owner = true @@ -290,9 +291,9 @@ visible = false layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 -theme_override_colors/current_line_color = Color(0.266667, 0.278431, 0.352941, 0.243137) -theme_override_colors/background_color = Color(0.156863, 0.164706, 0.211765, 1) theme_override_colors/font_color = Color(0.972549, 0.972549, 0.94902, 1) +theme_override_colors/background_color = Color(0.156863, 0.164706, 0.211765, 1) +theme_override_colors/current_line_color = Color(0.266667, 0.278431, 0.352941, 0.243137) theme_override_font_sizes/font_size = 14 theme_override_colors/bookmark_color = Color(1, 0.333333, 0.333333, 1) text = "~ start @@ -311,12 +312,57 @@ Coco: Meow. => END" scroll_smooth = true -syntax_highlighter = SubResource("SyntaxHighlighter_bxc68") +syntax_highlighter = SubResource("SyntaxHighlighter_mpdoc") [node name="ErrorsPanel" parent="Margin/Content/CodePanel" instance=ExtResource("7_5cvl4")] unique_name_in_owner = true layout_mode = 2 +[node name="Banner" type="CenterContainer" parent="."] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 34.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + +[node name="PanelContainer" type="VBoxContainer" parent="Banner"] +layout_mode = 2 +theme_override_constants/separation = 5 + +[node name="BannerImage" type="TextureRect" parent="Banner/PanelContainer"] +custom_minimum_size = Vector2(600, 200) +layout_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 2 +texture = ExtResource("9_y6rqu") +expand_mode = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="Banner/PanelContainer"] +layout_mode = 2 +theme_override_constants/separation = 5 + +[node name="BannerNewButton" type="Button" parent="Banner/PanelContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "New Dialogue..." + +[node name="BannerQuickOpen" type="Button" parent="Banner/PanelContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Quick open..." + +[node name="BannerExamples" type="Button" parent="Banner/PanelContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Example projects..." + [node name="NewDialog" type="FileDialog" parent="."] size = Vector2i(900, 750) min_size = Vector2i(600, 500) @@ -362,20 +408,6 @@ filters = PackedStringArray("*.csv ; Translation CSV") title = "Error" dialog_text = "You have errors in your script. Fix them and then try again." -[node name="SettingsDialog" type="AcceptDialog" parent="."] -title = "Settings" -size = Vector2i(1000, 600) -unresizable = true -min_size = Vector2i(1000, 600) -max_size = Vector2i(1000, 600) -ok_button_text = "Done" - -[node name="SettingsView" parent="SettingsDialog" instance=ExtResource("9_8bf36")] -offset_left = 8.0 -offset_top = 8.0 -offset_right = -8.0 -offset_bottom = -49.0 - [node name="BuildErrorDialog" type="AcceptDialog" parent="."] title = "Errors" dialog_text = "You need to fix dialogue errors before you can run your game." @@ -389,21 +421,6 @@ title = "Updated" size = Vector2i(191, 100) dialog_text = "You're now up to date!" -[node name="FindInFilesDialog" type="AcceptDialog" parent="."] -title = "Find in files" -size = Vector2i(1200, 900) -min_size = Vector2i(800, 600) -ok_button_text = "Done" - -[node name="FindInFiles" parent="FindInFilesDialog" node_paths=PackedStringArray("main_view", "code_edit") instance=ExtResource("10_yold3")] -custom_minimum_size = Vector2(400, 400) -offset_left = 8.0 -offset_top = 8.0 -offset_right = -8.0 -offset_bottom = -49.0 -main_view = NodePath("../..") -code_edit = NodePath("../../Margin/Content/CodePanel/CodeEdit") - [connection signal="theme_changed" from="." to="." method="_on_main_view_theme_changed"] [connection signal="visibility_changed" from="." to="." method="_on_main_view_visibility_changed"] [connection signal="timeout" from="ParseTimer" to="." method="_on_parse_timer_timeout"] @@ -419,7 +436,7 @@ code_edit = NodePath("../../Margin/Content/CodePanel/CodeEdit") [connection signal="title_selected" from="Margin/Content/SidePanel/Bookmarks/TitleList" to="." method="_on_title_list_title_selected"] [connection signal="toggled" from="Margin/Content/CodePanel/Toolbar/SearchButton" to="." method="_on_search_button_toggled"] [connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/TestButton" to="." method="_on_test_button_pressed"] -[connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/SettingsButton" to="." method="_on_settings_button_pressed"] +[connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/TestLineButton" to="." method="_on_test_line_button_pressed"] [connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/SupportButton" to="." method="_on_support_button_pressed"] [connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/DocsButton" to="." method="_on_docs_button_pressed"] [connection signal="close_requested" from="Margin/Content/CodePanel/SearchAndReplace" to="." method="_on_search_and_replace_close_requested"] @@ -430,6 +447,10 @@ code_edit = NodePath("../../Margin/Content/CodePanel/CodeEdit") [connection signal="external_file_requested" from="Margin/Content/CodePanel/CodeEdit" to="." method="_on_code_edit_external_file_requested"] [connection signal="text_changed" from="Margin/Content/CodePanel/CodeEdit" to="." method="_on_code_edit_text_changed"] [connection signal="error_pressed" from="Margin/Content/CodePanel/ErrorsPanel" to="." method="_on_errors_panel_error_pressed"] +[connection signal="gui_input" from="Banner/PanelContainer/BannerImage" to="." method="_on_banner_image_gui_input"] +[connection signal="pressed" from="Banner/PanelContainer/HBoxContainer/BannerNewButton" to="." method="_on_banner_new_button_pressed"] +[connection signal="pressed" from="Banner/PanelContainer/HBoxContainer/BannerQuickOpen" to="." method="_on_banner_quick_open_pressed"] +[connection signal="pressed" from="Banner/PanelContainer/HBoxContainer/BannerExamples" to="." method="_on_banner_examples_pressed"] [connection signal="confirmed" from="NewDialog" to="." method="_on_new_dialog_confirmed"] [connection signal="file_selected" from="NewDialog" to="." method="_on_new_dialog_file_selected"] [connection signal="file_selected" from="SaveDialog" to="." method="_on_save_dialog_file_selected"] @@ -438,8 +459,5 @@ code_edit = NodePath("../../Margin/Content/CodePanel/CodeEdit") [connection signal="file_double_clicked" from="QuickOpenDialog/QuickOpenFilesList" to="." method="_on_quick_open_files_list_file_double_clicked"] [connection signal="file_selected" from="ExportDialog" to="." method="_on_export_dialog_file_selected"] [connection signal="file_selected" from="ImportDialog" to="." method="_on_import_dialog_file_selected"] -[connection signal="confirmed" from="SettingsDialog" to="." method="_on_settings_dialog_confirmed"] -[connection signal="script_button_pressed" from="SettingsDialog/SettingsView" to="." method="_on_settings_view_script_button_pressed"] [connection signal="confirmed" from="CloseConfirmationDialog" to="." method="_on_close_confirmation_dialog_confirmed"] [connection signal="custom_action" from="CloseConfirmationDialog" to="." method="_on_close_confirmation_dialog_custom_action"] -[connection signal="result_selected" from="FindInFilesDialog/FindInFiles" to="." method="_on_find_in_files_result_selected"] diff --git a/addons/dialogue_manager/views/settings_view.gd b/addons/dialogue_manager/views/settings_view.gd deleted file mode 100644 index 800464d..0000000 --- a/addons/dialogue_manager/views/settings_view.gd +++ /dev/null @@ -1,288 +0,0 @@ -@tool -extends TabContainer - - -signal script_button_pressed(path: String) - - -const DialogueConstants = preload("../constants.gd") -const DialogueSettings = preload("../settings.gd") -const BaseDialogueTestScene = preload("../test_scene.gd") - - -enum PathTarget { - CustomTestScene, - Balloon -} - - -# Editor -@onready var new_template_button: CheckBox = $Editor/NewTemplateButton -@onready var new_template: CodeEdit = $Editor/NewTemplate -@onready var characters_translations_button: CheckBox = $Editor/CharactersTranslationsButton -@onready var wrap_lines_button: Button = $Editor/WrapLinesButton -@onready var default_csv_locale: LineEdit = $Editor/DefaultCSVLocale - -# Runtime -@onready var include_all_responses_button: CheckBox = $Runtime/IncludeAllResponsesButton -@onready var ignore_missing_state_values: CheckBox = $Runtime/IgnoreMissingStateValues -@onready var balloon_path_input: LineEdit = $Runtime/CustomBalloon/BalloonPath -@onready var revert_balloon_button: Button = $Runtime/CustomBalloon/RevertBalloonPath -@onready var load_balloon_button: Button = $Runtime/CustomBalloon/LoadBalloonPath -@onready var states_title: Label = $Runtime/StatesTitle -@onready var globals_list: Tree = $Runtime/GlobalsList - -# Advanced -@onready var check_for_updates: CheckBox = $Advanced/CheckForUpdates -@onready var include_characters_in_translations: CheckBox = $Advanced/IncludeCharactersInTranslations -@onready var include_notes_in_translations: CheckBox = $Advanced/IncludeNotesInTranslations -@onready var open_in_external_editor_button: CheckBox = $Advanced/OpenInExternalEditorButton -@onready var test_scene_path_input: LineEdit = $Advanced/CustomTestScene/TestScenePath -@onready var revert_test_scene_button: Button = $Advanced/CustomTestScene/RevertTestScene -@onready var load_test_scene_button: Button = $Advanced/CustomTestScene/LoadTestScene -@onready var custom_test_scene_file_dialog: FileDialog = $CustomTestSceneFileDialog -@onready var create_lines_for_response_characters: CheckBox = $Advanced/CreateLinesForResponseCharacters -@onready var missing_translations_button: CheckBox = $Advanced/MissingTranslationsButton - -var all_globals: Dictionary = {} -var enabled_globals: Array = [] -var path_target: PathTarget = PathTarget.CustomTestScene - -var _default_test_scene_path: String = preload("../test_scene.tscn").resource_path - -var _recompile_if_changed_settings: Dictionary - - -func _ready() -> void: - new_template_button.text = DialogueConstants.translate(&"settings.new_template") - characters_translations_button.text = DialogueConstants.translate(&"settings.characters_translations") - wrap_lines_button.text = DialogueConstants.translate(&"settings.wrap_long_lines") - $Editor/DefaultCSVLocaleLabel.text = DialogueConstants.translate(&"settings.default_csv_locale") - - include_all_responses_button.text = DialogueConstants.translate(&"settings.include_failed_responses") - ignore_missing_state_values.text = DialogueConstants.translate(&"settings.ignore_missing_state_values") - $Runtime/CustomBalloonLabel.text = DialogueConstants.translate(&"settings.default_balloon_hint") - states_title.text = DialogueConstants.translate(&"settings.states_shortcuts") - $Runtime/StatesMessage.text = DialogueConstants.translate(&"settings.states_message") - $Runtime/StatesHint.text = DialogueConstants.translate(&"settings.states_hint") - - check_for_updates.text = DialogueConstants.translate(&"settings.check_for_updates") - include_characters_in_translations.text = DialogueConstants.translate(&"settings.include_characters_in_translations") - include_notes_in_translations.text = DialogueConstants.translate(&"settings.include_notes_in_translations") - open_in_external_editor_button.text = DialogueConstants.translate(&"settings.open_in_external_editor") - $Advanced/ExternalWarning.text = DialogueConstants.translate(&"settings.external_editor_warning") - $Advanced/CustomTestSceneLabel.text = DialogueConstants.translate(&"settings.custom_test_scene") - $Advanced/RecompileWarning.text = DialogueConstants.translate(&"settings.recompile_warning") - missing_translations_button.text = DialogueConstants.translate(&"settings.missing_keys") - $Advanced/MissingTranslationsHint.text = DialogueConstants.translate(&"settings.missing_keys_hint") - create_lines_for_response_characters.text = DialogueConstants.translate(&"settings.create_lines_for_responses_with_characters") - - current_tab = 0 - - -func prepare() -> void: - _recompile_if_changed_settings = _get_settings_that_require_recompilation() - - test_scene_path_input.placeholder_text = DialogueSettings.get_setting("custom_test_scene_path", _default_test_scene_path) - revert_test_scene_button.visible = test_scene_path_input.placeholder_text != _default_test_scene_path - revert_test_scene_button.icon = get_theme_icon("RotateLeft", "EditorIcons") - revert_test_scene_button.tooltip_text = DialogueConstants.translate(&"settings.revert_to_default_test_scene") - load_test_scene_button.icon = get_theme_icon("Load", "EditorIcons") - - var balloon_path: String = DialogueSettings.get_setting("balloon_path", "") - if not FileAccess.file_exists(balloon_path): - DialogueSettings.set_setting("balloon_path", "") - balloon_path = "" - balloon_path_input.placeholder_text = balloon_path if balloon_path != "" else DialogueConstants.translate(&"settings.default_balloon_path") - revert_balloon_button.visible = balloon_path != "" - revert_balloon_button.icon = get_theme_icon("RotateLeft", "EditorIcons") - revert_balloon_button.tooltip_text = DialogueConstants.translate(&"settings.revert_to_default_balloon") - load_balloon_button.icon = get_theme_icon("Load", "EditorIcons") - - var scale: float = Engine.get_meta("DialogueManagerPlugin").get_editor_interface().get_editor_scale() - custom_test_scene_file_dialog.min_size = Vector2(600, 500) * scale - - states_title.add_theme_font_override("font", get_theme_font("bold", "EditorFonts")) - - check_for_updates.set_pressed_no_signal(DialogueSettings.get_user_value("check_for_updates", true)) - characters_translations_button.set_pressed_no_signal(DialogueSettings.get_setting("export_characters_in_translation", true)) - wrap_lines_button.set_pressed_no_signal(DialogueSettings.get_setting("wrap_lines", false)) - include_all_responses_button.set_pressed_no_signal(DialogueSettings.get_setting("include_all_responses", false)) - ignore_missing_state_values.set_pressed_no_signal(DialogueSettings.get_setting("ignore_missing_state_values", false)) - new_template_button.set_pressed_no_signal(DialogueSettings.get_setting("new_with_template", true)) - new_template.text = DialogueSettings.get_setting("new_template", "") - new_template.visible = DialogueSettings.get_setting("new_with_template", true) - default_csv_locale.text = DialogueSettings.get_setting("default_csv_locale", "en") - - missing_translations_button.set_pressed_no_signal(DialogueSettings.get_setting("missing_translations_are_errors", false)) - create_lines_for_response_characters.set_pressed_no_signal(DialogueSettings.get_setting("create_lines_for_responses_with_characters", true)) - - include_characters_in_translations.set_pressed_no_signal(DialogueSettings.get_setting("include_character_in_translation_exports", false)) - include_notes_in_translations.set_pressed_no_signal(DialogueSettings.get_setting("include_notes_in_translation_exports", false)) - open_in_external_editor_button.set_pressed_no_signal(DialogueSettings.get_user_value("open_in_external_editor", false)) - - var editor_settings: EditorSettings = Engine.get_meta("DialogueManagerPlugin").get_editor_interface().get_editor_settings() - var external_editor: String = editor_settings.get_setting("text_editor/external/exec_path") - var use_external_editor: bool = editor_settings.get_setting("text_editor/external/use_external_editor") and external_editor != "" - if not use_external_editor: - open_in_external_editor_button.hide() - $Advanced/ExternalWarning.hide() - $Advanced/ExternalSeparator.hide() - - var project = ConfigFile.new() - var err = project.load("res://project.godot") - assert(err == OK, "Could not find the project file") - - all_globals.clear() - if project.has_section("autoload"): - for key in project.get_section_keys("autoload"): - if key != "DialogueManager": - all_globals[key] = project.get_value("autoload", key) - - enabled_globals = DialogueSettings.get_setting("states", []).duplicate() - globals_list.clear() - var root = globals_list.create_item() - for name in all_globals.keys(): - var item: TreeItem = globals_list.create_item(root) - item.set_cell_mode(0, TreeItem.CELL_MODE_CHECK) - item.set_checked(0, name in enabled_globals) - item.set_text(0, name) - item.add_button(1, get_theme_icon("Edit", "EditorIcons")) - item.set_text(2, all_globals.get(name, "").replace("*res://", "res://")) - - globals_list.set_column_expand(0, false) - globals_list.set_column_custom_minimum_width(0, 250) - globals_list.set_column_expand(1, false) - globals_list.set_column_custom_minimum_width(1, 40) - globals_list.set_column_titles_visible(true) - globals_list.set_column_title(0, DialogueConstants.translate(&"settings.autoload")) - globals_list.set_column_title(1, "") - globals_list.set_column_title(2, DialogueConstants.translate(&"settings.path")) - - -func apply_settings_changes() -> void: - if _recompile_if_changed_settings != _get_settings_that_require_recompilation(): - Engine.get_meta("DialogueCache").reimport_files() - - -func _get_settings_that_require_recompilation() -> Dictionary: - return DialogueSettings.get_settings([ - "missing_translations_are_errors", - "create_lines_for_responses_with_characters" - ]) - - -### Signals - - -func _on_missing_translations_button_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("missing_translations_are_errors", toggled_on) - - -func _on_characters_translations_button_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("export_characters_in_translation", toggled_on) - - -func _on_wrap_lines_button_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("wrap_lines", toggled_on) - - -func _on_include_all_responses_button_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("include_all_responses", toggled_on) - - -func _on_globals_list_item_selected() -> void: - var item = globals_list.get_selected() - var is_checked = not item.is_checked(0) - item.set_checked(0, is_checked) - - if is_checked: - enabled_globals.append(item.get_text(0)) - else: - enabled_globals.erase(item.get_text(0)) - - DialogueSettings.set_setting("states", enabled_globals) - - -func _on_globals_list_button_clicked(item: TreeItem, column: int, id: int, mouse_button_index: int) -> void: - emit_signal("script_button_pressed", item.get_text(2)) - - -func _on_sample_template_toggled(toggled_on): - DialogueSettings.set_setting("new_with_template", toggled_on) - new_template.visible = toggled_on - - -func _on_revert_test_scene_pressed() -> void: - DialogueSettings.set_setting("custom_test_scene_path", _default_test_scene_path) - test_scene_path_input.placeholder_text = _default_test_scene_path - revert_test_scene_button.visible = test_scene_path_input.placeholder_text != _default_test_scene_path - - -func _on_load_test_scene_pressed() -> void: - path_target = PathTarget.CustomTestScene - custom_test_scene_file_dialog.popup_centered() - - -func _on_custom_test_scene_file_dialog_file_selected(path: String) -> void: - match path_target: - PathTarget.CustomTestScene: - # Check that the test scene is a subclass of BaseDialogueTestScene - var test_scene: PackedScene = load(path) - if test_scene and test_scene.instantiate() is BaseDialogueTestScene: - DialogueSettings.set_setting("custom_test_scene_path", path) - test_scene_path_input.placeholder_text = path - revert_test_scene_button.visible = test_scene_path_input.placeholder_text != _default_test_scene_path - else: - var accept: AcceptDialog = AcceptDialog.new() - accept.dialog_text = DialogueConstants.translate(&"settings.invalid_test_scene").format({ path = path }) - add_child(accept) - accept.popup_centered.call_deferred() - - PathTarget.Balloon: - DialogueSettings.set_setting("balloon_path", path) - balloon_path_input.placeholder_text = path - revert_balloon_button.visible = balloon_path_input.placeholder_text != "" - - -func _on_ignore_missing_state_values_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("ignore_missing_state_values", toggled_on) - - -func _on_default_csv_locale_text_changed(new_text: String) -> void: - DialogueSettings.set_setting("default_csv_locale", new_text) - - -func _on_revert_balloon_path_pressed() -> void: - DialogueSettings.set_setting("balloon_path", "") - balloon_path_input.placeholder_text = DialogueConstants.translate(&"settings.default_balloon_path") - revert_balloon_button.visible = DialogueSettings.get_setting("balloon_path", "") != "" - - -func _on_load_balloon_path_pressed() -> void: - path_target = PathTarget.Balloon - custom_test_scene_file_dialog.popup_centered() - - -func _on_create_lines_for_response_characters_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("create_lines_for_responses_with_characters", toggled_on) - - -func _on_open_in_external_editor_button_toggled(toggled_on: bool) -> void: - DialogueSettings.set_user_value("open_in_external_editor", toggled_on) - - -func _on_include_characters_in_translations_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("include_character_in_translation_exports", toggled_on) - - -func _on_include_notes_in_translations_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("include_notes_in_translation_exports", toggled_on) - - -func _on_keep_up_to_date_toggled(toggled_on: bool) -> void: - DialogueSettings.set_user_value("check_for_updates", toggled_on) - - -func _on_new_template_focus_exited() -> void: - DialogueSettings.set_setting("new_template", new_template.text) diff --git a/addons/dialogue_manager/views/settings_view.tscn b/addons/dialogue_manager/views/settings_view.tscn deleted file mode 100644 index 626010d..0000000 --- a/addons/dialogue_manager/views/settings_view.tscn +++ /dev/null @@ -1,233 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://cpg4lg1r3ff6m"] - -[ext_resource type="Script" path="res://addons/dialogue_manager/views/settings_view.gd" id="1_06uxa"] - -[sub_resource type="Theme" id="Theme_3a8rc"] -HSeparator/constants/separation = 20 - -[node name="SettingsView" type="TabContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_right = -206.0 -offset_bottom = -345.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme = SubResource("Theme_3a8rc") -current_tab = 0 -script = ExtResource("1_06uxa") - -[node name="Editor" type="VBoxContainer" parent="."] -layout_mode = 2 -metadata/_tab_index = 0 - -[node name="NewTemplateButton" type="CheckBox" parent="Editor"] -layout_mode = 2 -button_pressed = true -text = "New dialogue files will start with template text" - -[node name="NewTemplate" type="CodeEdit" parent="Editor"] -custom_minimum_size = Vector2(0, 200) -layout_mode = 2 -size_flags_vertical = 3 - -[node name="HSeparator" type="HSeparator" parent="Editor"] -layout_mode = 2 - -[node name="CharactersTranslationsButton" type="CheckBox" parent="Editor"] -layout_mode = 2 -button_pressed = true -text = "Export character names in translation files" - -[node name="WrapLinesButton" type="CheckBox" parent="Editor"] -layout_mode = 2 -button_pressed = true -text = "Wrap long lines" - -[node name="HSeparator2" type="HSeparator" parent="Editor"] -layout_mode = 2 - -[node name="DefaultCSVLocaleLabel" type="Label" parent="Editor"] -layout_mode = 2 -text = "Default CSV Locale" - -[node name="DefaultCSVLocale" type="LineEdit" parent="Editor"] -layout_mode = 2 - -[node name="Runtime" type="VBoxContainer" parent="."] -visible = false -layout_mode = 2 -metadata/_tab_index = 1 - -[node name="IncludeAllResponsesButton" type="CheckBox" parent="Runtime"] -layout_mode = 2 -text = "Include responses with failed conditions" - -[node name="IgnoreMissingStateValues" type="CheckBox" parent="Runtime"] -layout_mode = 2 -text = "Skip over missing state value errors (not recommended)" - -[node name="HSeparator" type="HSeparator" parent="Runtime"] -layout_mode = 2 - -[node name="CustomBalloonLabel" type="Label" parent="Runtime"] -layout_mode = 2 -text = "Custom balloon to use when calling \"DialogueManager.show_balloon()\"" - -[node name="CustomBalloon" type="HBoxContainer" parent="Runtime"] -layout_mode = 2 - -[node name="BalloonPath" type="LineEdit" parent="Runtime/CustomBalloon"] -layout_mode = 2 -size_flags_horizontal = 3 -focus_mode = 0 -editable = false -shortcut_keys_enabled = false -middle_mouse_paste_enabled = false - -[node name="RevertBalloonPath" type="Button" parent="Runtime/CustomBalloon"] -visible = false -layout_mode = 2 -tooltip_text = "Revert to default test scene" -flat = true - -[node name="LoadBalloonPath" type="Button" parent="Runtime/CustomBalloon"] -layout_mode = 2 - -[node name="HSeparator2" type="HSeparator" parent="Runtime"] -layout_mode = 2 - -[node name="StatesTitle" type="Label" parent="Runtime"] -layout_mode = 2 -text = "State Shortcuts" - -[node name="StatesMessage" type="Label" parent="Runtime"] -layout_mode = 2 -text = "If an autoload is enabled here you can refer to its properties, methods, and signals without having to use its name." - -[node name="StatesHint" type="Label" parent="Runtime"] -modulate = Color(1, 1, 1, 0.501961) -custom_minimum_size = Vector2(10, 0) -layout_mode = 2 -text = "ie. Instead of \"SomeState.some_property\" you could just use \"some_property\"" -autowrap_mode = 3 - -[node name="GlobalsList" type="Tree" parent="Runtime"] -layout_mode = 2 -size_flags_vertical = 3 -columns = 3 -column_titles_visible = true -allow_reselect = true -hide_folding = true -hide_root = true -select_mode = 1 - -[node name="Advanced" type="VBoxContainer" parent="."] -visible = false -layout_mode = 2 -metadata/_tab_index = 2 - -[node name="CheckForUpdates" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Check for updates" - -[node name="HSeparator" type="HSeparator" parent="Advanced"] -layout_mode = 2 - -[node name="IncludeCharactersInTranslations" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Include character names in translation exports" - -[node name="IncludeNotesInTranslations" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Include notes (## comments) in translation exports" - -[node name="ExternalSeparator" type="HSeparator" parent="Advanced"] -layout_mode = 2 - -[node name="OpenInExternalEditorButton" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Open dialogue files in external editor" - -[node name="ExternalWarning" type="Label" parent="Advanced"] -layout_mode = 2 -text = "Note: Syntax highlighting and detailed error checking are not supported in external editors." - -[node name="HSeparator3" type="HSeparator" parent="Advanced"] -layout_mode = 2 - -[node name="CustomTestSceneLabel" type="Label" parent="Advanced"] -layout_mode = 2 -text = "Custom test scene (must extend BaseDialogueTestScene)" - -[node name="CustomTestScene" type="HBoxContainer" parent="Advanced"] -layout_mode = 2 - -[node name="TestScenePath" type="LineEdit" parent="Advanced/CustomTestScene"] -layout_mode = 2 -size_flags_horizontal = 3 -focus_mode = 0 -placeholder_text = "res://addons/dialogue_manager/test_scene.tscn" -editable = false -shortcut_keys_enabled = false -middle_mouse_paste_enabled = false - -[node name="RevertTestScene" type="Button" parent="Advanced/CustomTestScene"] -visible = false -layout_mode = 2 -tooltip_text = "Revert to default test scene" -flat = true - -[node name="LoadTestScene" type="Button" parent="Advanced/CustomTestScene"] -layout_mode = 2 - -[node name="HSeparator4" type="HSeparator" parent="Advanced"] -layout_mode = 2 - -[node name="RecompileWarning" type="Label" parent="Advanced"] -layout_mode = 2 -text = "Changing these settings will force a recompile of all dialogue. Only change them if you know what you are doing." - -[node name="MissingTranslationsButton" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Treat missing translation keys as errors" - -[node name="MissingTranslationsHint" type="Label" parent="Advanced"] -modulate = Color(1, 1, 1, 0.501961) -custom_minimum_size = Vector2(10, 0) -layout_mode = 2 -text = "If you are using static translation keys then having this enabled will help you find any lines that you haven't added a key to yet." -autowrap_mode = 3 - -[node name="CreateLinesForResponseCharacters" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Create child dialogue line for responses with character names in them" - -[node name="CustomTestSceneFileDialog" type="FileDialog" parent="."] -title = "Open a File" -ok_button_text = "Open" -file_mode = 0 -filters = PackedStringArray("*.tscn ; Scene") - -[connection signal="toggled" from="Editor/NewTemplateButton" to="." method="_on_sample_template_toggled"] -[connection signal="focus_exited" from="Editor/NewTemplate" to="." method="_on_new_template_focus_exited"] -[connection signal="toggled" from="Editor/CharactersTranslationsButton" to="." method="_on_characters_translations_button_toggled"] -[connection signal="toggled" from="Editor/WrapLinesButton" to="." method="_on_wrap_lines_button_toggled"] -[connection signal="text_changed" from="Editor/DefaultCSVLocale" to="." method="_on_default_csv_locale_text_changed"] -[connection signal="toggled" from="Runtime/IncludeAllResponsesButton" to="." method="_on_include_all_responses_button_toggled"] -[connection signal="toggled" from="Runtime/IgnoreMissingStateValues" to="." method="_on_ignore_missing_state_values_toggled"] -[connection signal="pressed" from="Runtime/CustomBalloon/RevertBalloonPath" to="." method="_on_revert_balloon_path_pressed"] -[connection signal="pressed" from="Runtime/CustomBalloon/LoadBalloonPath" to="." method="_on_load_balloon_path_pressed"] -[connection signal="button_clicked" from="Runtime/GlobalsList" to="." method="_on_globals_list_button_clicked"] -[connection signal="item_selected" from="Runtime/GlobalsList" to="." method="_on_globals_list_item_selected"] -[connection signal="toggled" from="Advanced/CheckForUpdates" to="." method="_on_keep_up_to_date_toggled"] -[connection signal="toggled" from="Advanced/IncludeCharactersInTranslations" to="." method="_on_include_characters_in_translations_toggled"] -[connection signal="toggled" from="Advanced/IncludeNotesInTranslations" to="." method="_on_include_notes_in_translations_toggled"] -[connection signal="toggled" from="Advanced/OpenInExternalEditorButton" to="." method="_on_open_in_external_editor_button_toggled"] -[connection signal="pressed" from="Advanced/CustomTestScene/RevertTestScene" to="." method="_on_revert_test_scene_pressed"] -[connection signal="pressed" from="Advanced/CustomTestScene/LoadTestScene" to="." method="_on_load_test_scene_pressed"] -[connection signal="toggled" from="Advanced/MissingTranslationsButton" to="." method="_on_missing_translations_button_toggled"] -[connection signal="toggled" from="Advanced/CreateLinesForResponseCharacters" to="." method="_on_create_lines_for_response_characters_toggled"] -[connection signal="file_selected" from="CustomTestSceneFileDialog" to="." method="_on_custom_test_scene_file_dialog_file_selected"] diff --git a/addons/godot-git-plugin/LICENSE b/addons/godot-git-plugin/LICENSE new file mode 100644 index 0000000..f153fb8 --- /dev/null +++ b/addons/godot-git-plugin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2023 The Godot Engine community + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/godot-git-plugin/THIRDPARTY.md b/addons/godot-git-plugin/THIRDPARTY.md new file mode 100644 index 0000000..837488f --- /dev/null +++ b/addons/godot-git-plugin/THIRDPARTY.md @@ -0,0 +1,1349 @@ +# Third-Party Notices + +The Godot Git Plugin source code uses the following third-party source code: + +1. godotengine/godot-cpp - MIT License - https://github.com/godotengine/godot-cpp/tree/02336831735fd6affbe0a6fa252ec98d3e78120c +2. libgit2/libgit2 - GPLv2 with a special Linking Exception - https://github.com/libgit2/libgit2/tree/b7bad55e4bb0a285b073ba5e02b01d3f522fc95d +3. libssh2/libssh2 - BSD-3-Clause License - https://github.com/libssh2/libssh2/tree/635caa90787220ac3773c1d5ba11f1236c22eae8 + +We also link to these third-party libraries (only in the compiled binary form): + +1. OpenSSL - Only on Linux and MacOS - OpenSSL License - http://www.openssl.org/source/openssl-1.1.1s.tar.gz + +## License Texts + +### godotengine/godot-cpp + +``` +# MIT License + +Copyright (c) 2017-2022 Godot Engine contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +### libgit2/libgit2 + +``` + libgit2 is Copyright (C) the libgit2 contributors, + unless otherwise stated. See the AUTHORS file for details. + + Note that the only valid version of the GPL as far as this project + is concerned is _this_ particular version of the license (ie v2, not + v2.2 or v3.x or whatever), unless explicitly otherwise stated. + +---------------------------------------------------------------------- + + LINKING EXCEPTION + + In addition to the permissions in the GNU General Public License, + the authors give you unlimited permission to link the compiled + version of this library into combinations with other programs, + and to distribute those combinations without any restriction + coming from the use of this file. (The General Public License + restrictions do apply in other respects; for example, they cover + modification of the file, and distribution when not linked into + a combined executable.) + +---------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +---------------------------------------------------------------------- + +The bundled ZLib code is licensed under the ZLib license: + +Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +---------------------------------------------------------------------- + +The Clar framework is licensed under the ISC license: + +Copyright (c) 2011-2015 Vicent Marti + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +---------------------------------------------------------------------- + +The regex library (deps/regex/) is licensed under the GNU LGPL +(available at the end of this file). + +Definitions for data structures and routines for the regular +expression library. + +Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003,2005,2006,2008 +Free Software Foundation, Inc. +This file is part of the GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with the GNU C Library; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +---------------------------------------------------------------------- + +The bundled winhttp definition files (deps/winhttp/) are licensed under +the GNU LGPL (available at the end of this file). + +Copyright (C) 2007 Francois Gouget + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +---------------------------------------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + +---------------------------------------------------------------------- + +The bundled SHA1 collision detection code is licensed under the MIT license: + +MIT License + +Copyright (c) 2017: + Marc Stevens + Cryptology Group + Centrum Wiskunde & Informatica + P.O. Box 94079, 1090 GB Amsterdam, Netherlands + marc@marc-stevens.nl + + Dan Shumow + Microsoft Research + danshu@microsoft.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------------------------------------------------------------- + +The bundled wildmatch code is licensed under the BSD license: + +Copyright Rich Salz. +All rights reserved. + +Redistribution and use in any form are permitted provided that the +following restrictions are are met: + +1. Source distributions must retain this entire copyright notice + and comment. +2. Binary distributions must include the acknowledgement ``This + product includes software developed by Rich Salz'' in the + documentation or other materials provided with the + distribution. This must not be represented as an endorsement + or promotion without specific prior written permission. +3. The origin of this software must not be misrepresented, either + by explicit claim or by omission. Credits must appear in the + source and documentation. +4. Altered versions must be plainly marked as such in the source + and documentation and must not be misrepresented as being the + original software. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +---------------------------------------------------------------------- + +Portions of the OpenSSL headers are included under the OpenSSL license: + +Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) +All rights reserved. + +This package is an SSL implementation written +by Eric Young (eay@cryptsoft.com). +The implementation was written so as to conform with Netscapes SSL. + +This library is free for commercial and non-commercial use as long as +the following conditions are aheared to. The following conditions +apply to all code found in this distribution, be it the RC4, RSA, +lhash, DES, etc., code; not just the SSL code. The SSL documentation +included with this distribution is covered by the same copyright terms +except that the holder is Tim Hudson (tjh@cryptsoft.com). + +Copyright remains Eric Young's, and as such any Copyright notices in +the code are not to be removed. +If this package is used in a product, Eric Young should be given attribution +as the author of the parts of the library used. +This can be in the form of a textual message at program startup or +in documentation (online or textual) provided with the package. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + "This product includes cryptographic software written by + Eric Young (eay@cryptsoft.com)" + The word 'cryptographic' can be left out if the rouines from the library + being used are not cryptographic related :-). +4. If you include any Windows specific code (or a derivative thereof) from + the apps directory (application code) you must include an acknowledgement: + "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + +THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +The licence and distribution terms for any publically available version or +derivative of this code cannot be changed. i.e. this code cannot simply be +copied and put under another distribution licence +[including the GNU Public Licence.] + +==================================================================== +Copyright (c) 1998-2007 The OpenSSL Project. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +3. All advertising materials mentioning features or use of this + software must display the following acknowledgment: + "This product includes software developed by the OpenSSL Project + for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + +4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + endorse or promote products derived from this software without + prior written permission. For written permission, please contact + openssl-core@openssl.org. + +5. Products derived from this software may not be called "OpenSSL" + nor may "OpenSSL" appear in their names without prior written + permission of the OpenSSL Project. + +6. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes software developed by the OpenSSL Project + for use in the OpenSSL Toolkit (http://www.openssl.org/)" + +THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY +EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR +ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +### libssh2/libssh2 + +``` +/* Copyright (c) 2004-2007 Sara Golemon <sarag@libssh2.org> + * Copyright (c) 2005,2006 Mikhail Gusarov <dottedmag@dottedmag.net> + * Copyright (c) 2006-2007 The Written Word, Inc. + * Copyright (c) 2007 Eli Fant <elifantu@mail.ru> + * Copyright (c) 2009-2021 Daniel Stenberg + * Copyright (C) 2008, 2009 Simon Josefsson + * Copyright (c) 2000 Markus Friedl + * Copyright (c) 2015 Microsoft Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ +``` + +### OpenSSL + +``` + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a double license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ +``` diff --git a/addons/godot-git-plugin/git_plugin.gdextension b/addons/godot-git-plugin/git_plugin.gdextension new file mode 100644 index 0000000..49fffbf --- /dev/null +++ b/addons/godot-git-plugin/git_plugin.gdextension @@ -0,0 +1,12 @@ +[configuration] + +entry_symbol = "git_plugin_init" +compatibility_minimum = "4.1.0" + +[libraries] + +macos.editor = "macos/libgit_plugin.macos.editor.universal.dylib" +windows.editor.x86_64 = "win64/libgit_plugin.windows.editor.x86_64.dll" +linux.editor.x86_64 = "linux/libgit_plugin.linux.editor.x86_64.so" +linux.editor.arm64 = "linux/libgit_plugin.linux.editor.arm64.so" +linux.editor.rv64 = "" diff --git a/addons/godot-git-plugin/linux/libgit_plugin.linux.editor.x86_64.so b/addons/godot-git-plugin/linux/libgit_plugin.linux.editor.x86_64.so new file mode 100644 index 0000000..8dab6db Binary files /dev/null and b/addons/godot-git-plugin/linux/libgit_plugin.linux.editor.x86_64.so differ diff --git a/addons/godot-git-plugin/macos/libgit_plugin.macos.editor.universal.dylib b/addons/godot-git-plugin/macos/libgit_plugin.macos.editor.universal.dylib new file mode 100644 index 0000000..644aa2a Binary files /dev/null and b/addons/godot-git-plugin/macos/libgit_plugin.macos.editor.universal.dylib differ diff --git a/addons/godot-git-plugin/plugin.cfg b/addons/godot-git-plugin/plugin.cfg new file mode 100644 index 0000000..9c4e36f --- /dev/null +++ b/addons/godot-git-plugin/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Godot Git Plugin" +description="This plugin lets you interact with Git without leaving the Godot editor. More information can be found at https://github.com/godotengine/godot-git-plugin/wiki" +author="twaritwaikar" +version="v3.1.1" +script="godot-git-plugin.gd" diff --git a/addons/godot-git-plugin/win64/libgit_plugin.windows.editor.x86_64.dll b/addons/godot-git-plugin/win64/libgit_plugin.windows.editor.x86_64.dll new file mode 100644 index 0000000..47bbb1d Binary files /dev/null and b/addons/godot-git-plugin/win64/libgit_plugin.windows.editor.x86_64.dll differ diff --git a/addons/godot-git-plugin/win64/libgit_plugin.windows.editor.x86_64.exp b/addons/godot-git-plugin/win64/libgit_plugin.windows.editor.x86_64.exp new file mode 100644 index 0000000..6c68d89 Binary files /dev/null and b/addons/godot-git-plugin/win64/libgit_plugin.windows.editor.x86_64.exp differ diff --git a/addons/godot-git-plugin/win64/libgit_plugin.windows.editor.x86_64.lib b/addons/godot-git-plugin/win64/libgit_plugin.windows.editor.x86_64.lib new file mode 100644 index 0000000..4537929 Binary files /dev/null and b/addons/godot-git-plugin/win64/libgit_plugin.windows.editor.x86_64.lib differ diff --git a/addons/godot-git-plugin/win64/~libgit_plugin.windows.editor.x86_64.dll b/addons/godot-git-plugin/win64/~libgit_plugin.windows.editor.x86_64.dll new file mode 100644 index 0000000..47bbb1d Binary files /dev/null and b/addons/godot-git-plugin/win64/~libgit_plugin.windows.editor.x86_64.dll differ diff --git a/bedroom.gd b/bedroom.gd deleted file mode 100644 index 76fd58b..0000000 --- a/bedroom.gd +++ /dev/null @@ -1,6 +0,0 @@ -extends Control - - -# Called when the node enters the scene tree for the first time. -func _ready() -> void: - self.hide() diff --git a/confirmation_dialog.tscn b/confirmation_dialog.tscn index 65a0589..d177f19 100644 --- a/confirmation_dialog.tscn +++ b/confirmation_dialog.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://c0kyheol06kes"] -[ext_resource type="FontFile" uid="uid://ckg5x0oonnjtn" path="res://Art/XanhMono-Regular.ttf" id="1_6let0"] +[ext_resource type="FontFile" uid="uid://dv8ppra5tgn1t" path="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" id="1_6ucym"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jjq8m"] border_width_left = 2 @@ -44,7 +44,7 @@ layout_mode = 2 layout_mode = 2 size_flags_vertical = 3 size_flags_stretch_ratio = 3.0 -theme_override_fonts/normal_font = ExtResource("1_6let0") +theme_override_fonts/normal_font = ExtResource("1_6ucym") theme_override_font_sizes/normal_font_size = 25 bbcode_enabled = true @@ -61,13 +61,13 @@ theme_override_constants/separation = 70 [node name="YesButton" type="Button" parent="ConfirmationDialog/MarginContainer/VBoxContainer/MarginContainer/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 -theme_override_fonts/font = ExtResource("1_6let0") +theme_override_fonts/font = ExtResource("1_6ucym") theme_override_font_sizes/font_size = 20 text = "YES" [node name="NoButton" type="Button" parent="ConfirmationDialog/MarginContainer/VBoxContainer/MarginContainer/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 -theme_override_fonts/font = ExtResource("1_6let0") +theme_override_fonts/font = ExtResource("1_6ucym") theme_override_font_sizes/font_size = 20 text = "NO" diff --git a/current_patient.gd b/current_patient.gd index ec43588..579103c 100644 --- a/current_patient.gd +++ b/current_patient.gd @@ -1,18 +1,33 @@ extends Node +#Dialogue variables var dialog_tree: Dictionary var marker: Array[int] -var patient_file_content: String +var dialogue_resource: Resource + +#Patient attributes +var texture := TextureRect.new() +var image : Texture2D +var original_color: Color +var emotions : Dictionary +var abnormal: bool + +#Patient file +var file_text : String +var file_title: String # Called when the node enters the scene tree for the first time. func _ready() -> void: + #Setting up dialogue marker marker.resize(10) marker.fill(0) + + original_color = texture.modulate #Saving original modulation color to reset it after modification -func build_dialog_tree(tree_model: Array[int]) -> Dictionary: +func build_dialog_tree(tree_model: Array[int]) -> Dictionary: #Builds the dialog tree for each dialogue var branch : Dictionary var tree : Dictionary @@ -22,3 +37,61 @@ func build_dialog_tree(tree_model: Array[int]) -> Dictionary: tree.merge(branch) return tree + + +func new_patient(): + + var file_path: String + + #Generating patient_id + var patient_id = Globals.patient_list.pick_random() + Globals.patient_id = patient_id + Globals.patient_list.erase(patient_id) #Line to be reenabled when there is more than one patient in the list + Globals.patients_seen.append(patient_id) + + #Loading Patient Files + var config := ConfigFile.new() + file_path = "res://Dialogues/Patients/patient_" + patient_id + "_file.cfg" + config.load(file_path) + + #Setting up Patient File + var patient_name = config.get_value("", "name") + var age = config.get_value("", "age") + var profession = config.get_value("", "profession") + var psychiatric_assessment = config.get_value("", "psychiatric_assessment") + + file_text = "NAME: " + patient_name + "\nAGE: " + age + "\n\nPROFESSION: " + profession + "\n\nPRE-SCREENING PSYCHIATRIC ASSESSMENT:\n\n" + "[color=orange_red]" + psychiatric_assessment + "[/color]" + file_title = "[center]PATIENT FILE #" + patient_id + + #Loading Patient Initial Face + var initial_emotion = config.get_value("", "initial_emotion") + file_path = "res://Art/Patients/patient_" + patient_id + "/patient_" + patient_id + "_" + initial_emotion + ".png" + image = load(file_path) + + #Loading Face Emotions + var face: Resource + var emotion_branch: Dictionary + var emotion_list = ["neutral", "puzzled", "smiling", "laughing", "happy", "elated", "maniac", "irritated", "angry", + "sad", "crying", "worried", "scared", "shocked", "panicked", "suspicious", "abnormal"] + + emotions.clear() #Clearing previous patient emotions + for emotion in emotion_list: + file_path = "res://Art/Patients/patient_" + patient_id + "/patient_" + patient_id + "_" + emotion + ".png" + face = load(file_path) + if face != null: + emotion_branch = {emotion: face} + emotions.merge(emotion_branch) + + #Loading Dialogue Resource + file_path = "res://Dialogues/Patients/patient_" + patient_id + ".dialogue" + dialogue_resource = load(file_path) + + #Randomizing abnormal status if required + var status = config.get_value("", "status") + match status: + "random": + abnormal = randf() < 0.5 #50/50 abnormal/normal + "normal": + abnormal = false + "abnormal": + abnormal = true diff --git a/current_patient.gd.uid b/current_patient.gd.uid new file mode 100644 index 0000000..aa10e1f --- /dev/null +++ b/current_patient.gd.uid @@ -0,0 +1 @@ +uid://by4i7c1foffu3 diff --git a/dialog_main.gd b/dialog_main.gd new file mode 100644 index 0000000..3acc08d --- /dev/null +++ b/dialog_main.gd @@ -0,0 +1,14 @@ +extends Control + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + + var dialogue_resource = load("res://dialogues/mr_orleg.dialogue") + var dialogue_start = "mr_orleg_day_2_dialogue_start" + + var dialogue = DialogueManager.show_dialogue_balloon(dialogue_resource, dialogue_start) + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass diff --git a/dialog_main.tscn b/dialog_main.tscn new file mode 100644 index 0000000..ba710c7 --- /dev/null +++ b/dialog_main.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://cx2nwe1mk2cod"] + +[ext_resource type="Script" path="res://dialog_main.gd" id="1_kjkxp"] + +[node name="DialogMain" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_kjkxp") diff --git a/dialogue_screens.gd b/dialogue_screens.gd deleted file mode 100644 index 56052b7..0000000 --- a/dialogue_screens.gd +++ /dev/null @@ -1,7 +0,0 @@ -extends Control - -var balloon: Node - -# Called when the node enters the scene tree for the first time. -func _ready() -> void: - self.hide() diff --git a/dialogue_screens.tscn b/dialogue_screens.tscn index 6b2cdc6..a5e79ca 100644 --- a/dialogue_screens.tscn +++ b/dialogue_screens.tscn @@ -1,9 +1,18 @@ -[gd_scene load_steps=4 format=3 uid="uid://dixsxwodxuf0b"] +[gd_scene load_steps=4 format=3 uid="uid://bfwic5071n5rn"] [ext_resource type="Texture2D" uid="uid://c3je4gir1qlot" path="res://Art/BlackScreen.png" id="1_0xuu6"] -[ext_resource type="Script" path="res://dialogue_screens.gd" id="1_4k063"] [ext_resource type="Texture2D" uid="uid://h0sbv6vfm8yx" path="res://Art/meeting_room.png" id="3_k36pg"] +[sub_resource type="GDScript" id="GDScript_cvpyv"] +script/source = "extends Control + +var balloon: Node + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + self.hide() +" + [node name="dialogue_screens" type="Control"] layout_mode = 3 anchors_preset = 15 @@ -11,7 +20,7 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -script = ExtResource("1_4k063") +script = SubResource("GDScript_cvpyv") [node name="Background" type="VBoxContainer" parent="."] layout_mode = 1 diff --git a/globals.gd b/globals.gd index be33eeb..e5ee7bc 100644 --- a/globals.gd +++ b/globals.gd @@ -1,12 +1,24 @@ extends Node -var is_transitioning = false -var day_count = 1 -var patient_count = 1 -var daily_patient_limit = 3 -var patient_id = "564" +var is_transitioning : bool = false +var day_count : int = 1 +var floor_count : int = -1 +var patient_count : int = 1 +var diagnoses_quota : int = 4 +var daily_patient_limit : int = 3 +var patient_list = ["48", "49", "50", "2202"] +var patients_seen: Array[String] +var patient_id = patient_list.pick_random() var dialogue_intro = load("res://Dialogues/intro.dialogue") var dialogue_recovery = load("res://Dialogues/recovery.dialogue") +var directives = "[center][font_size=22]INSTRUCTIONS[/font_size][/center] +\n\n\n[color=forest_green]RELEASE[/color] HUMANS, [color=red]TERMINATE[/color] ABNORMALS +\n\nIF A SUBJECT PRESENTS [color=dark_orange]SYMPTOMS[/color] UNDETECTED BY PRE-SCREENING, [color=red]IT IS NOT HUMAN[/color] +\n\nREFER TO THE [color=red]MANUAL[/color] ON THE DESK FOR MORE INFORMATION ON [color=dark_orange]SYMPTOMS[/color] +\n\nMAKE ENOUGH [color=green_yellow]ACCURATE DIAGNOSES[/color] IN A ROW TO GET TO THE NEXT FLOOR" + +signal console_message +signal response_chosen(DialogueResponse) # Called when the node enters the scene tree for the first time. func _ready() -> void: @@ -14,10 +26,17 @@ func _ready() -> void: pass - +#Gets the current time and day of the year func get_save_date() -> String: var dt = Time.get_datetime_dict_from_system() return "%02d/%02d/%04d, %02d:%02d" % [ dt.month, dt.day, dt.year, dt.hour, dt.minute ] + + +#Colorizes a message in BBCode +func colorize(message:String, color: String) -> String: + var colorized_message : String + colorized_message = "[color=" + color + "]" + message + "[/color]" + return colorized_message diff --git a/globals.gd.uid b/globals.gd.uid new file mode 100644 index 0000000..870f623 --- /dev/null +++ b/globals.gd.uid @@ -0,0 +1 @@ +uid://ea5lvcj6nhqg diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..9d8b7fa --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg> \ No newline at end of file diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..ce44a6c --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bf3x4m5567v75" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/main_hub.gd b/main_hub.gd index a07be91..d874160 100644 --- a/main_hub.gd +++ b/main_hub.gd @@ -16,22 +16,16 @@ func _ready() -> void: #Adding Scenes var title_screen = TitleScreen.instantiate() var screening_desk = ScreeningDesk.instantiate() - var bedroom = Bedroom.instantiate() var text_screens = TextScreens.instantiate() - var dialogue_screens = DialogueScreens.instantiate() add_child(title_screen) add_child(screening_desk) - add_child(bedroom) add_child(text_screens) - add_child(dialogue_screens) #Naming Scenes title_screen.name = "TitleScreen" screening_desk.name = "ScreeningDesk" - bedroom.name = "Bedroom" text_screens.name = "TextScreens" - dialogue_screens.name = "DialogueScreens" #Showing TitleScreen title_screen.show() @@ -41,7 +35,6 @@ func _ready() -> void: UI.connect("save_slot_pressed", self._on_save_slot_pressed) UI.connect("load_slot_pressed", self._on_load_slot_pressed) UI.connect("main_menu_pressed", self._on_main_menu_pressed) - screening_desk.connect("recovery_pressed", self._on_recovery_pressed) UI.connect("text_value_changed", self._on_text_value_changed) UI.connect("auto_value_changed", self._on_auto_value_changed) @@ -54,30 +47,19 @@ func _ready() -> void: # Starts a new game func _on_new_game_pressed(): Globals.day_count = 1 + # DISABLED FOR NOW SINCE THERE'S NO INTRO ANYMORE #Offering to skip the introduction - var choice_text = "[center]Do you wish to skip the introduction ?[/center]" - UI.ask_confirmation(start_day, play_intro, choice_text) + #var choice_text = "[center]Do you wish to skip the introduction ?[/center]" + #UI.ask_confirmation(start_day, play_intro, choice_text) + + start_day() #Plays the entire introduction func play_intro(): - #Text Slides - var text_container = $TextScreens/MarginContainer/IntroBoxContainer/TextContainer - - TransitionScreen.transition("big", [$TitleScreen], [$TextScreens, $TextScreens/MarginContainer/IntroBoxContainer/Header, text_container.get_child(0), UI, UI.get_node("UI/SkipContainer")]) - await UI.next_slide - for i in 4: - TransitionScreen.transition("small", [text_container.get_child(i)], [text_container.get_child(i+1)]) - await UI.next_slide - - #Meeting room dialogue - TransitionScreen.transition("big", [$TextScreens, $TextScreens/MarginContainer, text_container.get_child(4), UI, UI.get_node("UI/SkipContainer")], [$DialogueScreens]) - await TransitionScreen.transition_finished - - start_balloon("big", Globals.dialogue_intro, "start") - await DialogueManager.dialogue_ended + #PART IN PROGRESS #Starting the day start_day() @@ -93,20 +75,23 @@ func start_day(): #Transitioning to day screen $TextScreens/Day.text = "[center]DAY " + str(Globals.day_count) + "[/center]" - var scenes_to_hide: Array[Node] = [$DialogueScreens, $TitleScreen, $Bedroom] + var scenes_to_hide: Array[Node] = [$TitleScreen] var scenes_to_show: Array[Node] = [$TextScreens, $TextScreens/Day] TransitionScreen.transition("big", scenes_to_hide, scenes_to_show) await TransitionScreen.transition_finished - await get_tree().create_timer(2.0).timeout + await get_tree().create_timer(1.0).timeout + + #Setting up new patient + $ScreeningDesk.console_display(Globals.directives) + $ScreeningDesk.accurate_diagnoses = 0 #Resetting accurate_diagnoses value at the day's beginning + $ScreeningDesk.new_patient() + $ScreeningDesk.update_middle_screen(Globals.day_count, Globals.floor_count, $ScreeningDesk.accurate_diagnoses, Globals.diagnoses_quota) #Transitioning to the screening desk scenes_to_hide = scenes_to_show - scenes_to_show = [$ScreeningDesk, $ScreeningDesk.alone_controls, UI, UI.get_node("UI/UpperContainer")] + scenes_to_show = [$ScreeningDesk, $ScreeningDesk.next_patient, UI, UI.get_node("UI/UpperContainer")] TransitionScreen.transition("big", scenes_to_hide, scenes_to_show) await TransitionScreen.transition_finished - - #Setting up new patient - $ScreeningDesk.new_patient(Globals.patient_id) @@ -135,8 +120,13 @@ func save_game(save_slot_number:int): #Setting up save data var save_data = { "day_count": Globals.day_count, + "floor_count": Globals.floor_count, "patient_count": Globals.patient_count, + "diagnoses_quota": Globals.diagnoses_quota, + "daily_patient_limit": Globals.daily_patient_limit, + "patient_list": Globals.patient_list, "patient_id": Globals.patient_id, + "patients_seen": Globals.patients_seen, "save_date": Globals.get_save_date() } @@ -185,7 +175,11 @@ func load_game(load_slot_number:int): print(load_data) Globals.day_count = load_data["day_count"] Globals.patient_count = load_data["patient_count"] + Globals.diagnoses_quota = load_data["diagnoses_quota"] + Globals.daily_patient_limit = load_data["daily_patient_limit"] + Globals.patient_list = load_data["patient_list"] Globals.patient_id = load_data["patient_id"] + Globals.patients_seen = load_data["patients_seen"] else: print("Failed to parse Loading Data.") @@ -194,7 +188,7 @@ func load_game(load_slot_number:int): var scenes_to_show = [$ScreeningDesk, $ScreeningDesk.alone_controls, UI.get_node("UI/UpperContainer")] TransitionScreen.transition("big", scenes_to_hide, scenes_to_show) await TransitionScreen.transition_finished - $ScreeningDesk.new_patient(Globals.patient_id) + $ScreeningDesk.new_patient() @@ -271,7 +265,7 @@ func start_balloon(panel_size:String, dialogue_resource: Resource, start_title: get_tree().current_scene.add_child(balloon) #Connects emotion signals if needed - if panel_size == "small": + if panel_size == "test": DialogueManager.got_dialogue.connect($ScreeningDesk._change_emotion) DialogueManager.got_dialogue.connect($ScreeningDesk._recording_text) diff --git a/main_hub.gd.uid b/main_hub.gd.uid new file mode 100644 index 0000000..fe48c30 --- /dev/null +++ b/main_hub.gd.uid @@ -0,0 +1 @@ +uid://c6kqjqj6cs4wa diff --git a/project.godot b/project.godot index c633c71..15d0472 100644 --- a/project.godot +++ b/project.godot @@ -12,19 +12,19 @@ config_version=5 config/name="im_not_sick" run/main_scene="res://MainHub.tscn" -config/features=PackedStringArray("4.3", "Forward Plus") +config/features=PackedStringArray("4.5", "Forward Plus") [autoload] -DialogueManager="*res://addons/dialogue_manager/dialogue_manager.gd" +Globals="*res://globals.gd" TransitionScreen="*res://transition_screen.tscn" UI="*res://ui.tscn" -Globals="*res://globals.gd" -CurrentPatient="*res://current_patient.gd" +Patient="*res://current_patient.gd" +DialogueManager="*res://addons/dialogue_manager/dialogue_manager.gd" [dialogue_manager] -general/balloon_path="res://Dialogues/balloon_big.tscn" +runtime/balloon_path="res://Dialogues/balloon_big.tscn" [display] @@ -55,4 +55,4 @@ ui_select={ [internationalization] -locale/translations_pot_files=PackedStringArray("res://Dialogues/intro.dialogue", "res://Dialogues/mr_orleg.dialogue", "res://Dialogues/intro_stash.dialogue", "res://Dialogues/Patients/patient_1.dialogue", "res://Dialogues/Patients/patient_564.dialogue", "res://Dialogues/end_of_day.dialogue", "res://Dialogues/recovery.dialogue") +locale/translations_pot_files=PackedStringArray("res://Dialogues/intro.dialogue", "res://Dialogues/mr_orleg.dialogue", "res://Dialogues/intro_stash.dialogue", "res://Dialogues/Patients/patient_1.dialogue", "res://Dialogues/end_of_day.dialogue", "res://Dialogues/recovery.dialogue", "res://Dialogues/Patients/patient_2202.dialogue", "res://Dialogues/Patients/patient_50.dialogue", "res://Dialogues/Patients/patient_49.dialogue", "res://Dialogues/Patients/patient_48.dialogue") diff --git a/scrDA88.tmp b/scrDA88.tmp new file mode 100644 index 0000000..63ac340 --- /dev/null +++ b/scrDA88.tmp @@ -0,0 +1,1118 @@ +[gd_scene load_steps=55 format=3 uid="uid://chytk2cctjk3x"] + +[ext_resource type="Script" path="res://screening_desk.gd" id="1_1s7k3"] +[ext_resource type="Texture2D" uid="uid://x52vv4rovnff" path="res://Art/Patients/patient_50/patient_50_neutral.png" id="2_y82mh"] +[ext_resource type="Texture2D" uid="uid://b2epvjeqvmgrs" path="res://Art/Rooms/screening_room/screening_room_glass.png" id="3_nxnrp"] +[ext_resource type="Texture2D" uid="uid://by34irl73n8pb" path="res://Art/Rooms/screening_room/backroom.png" id="4_fij8w"] +[ext_resource type="Texture2D" uid="uid://ds1whnmwt7xjt" path="res://Art/Rooms/screening_room/screening_room.png" id="5_aa8r5"] +[ext_resource type="Texture2D" uid="uid://4qtc0tkq2wba" path="res://Art/Rooms/screening_room/shutter_top.png" id="6_1rj24"] +[ext_resource type="Texture2D" uid="uid://cci0pb12chlps" path="res://Art/Rooms/screening_room/computer.png" id="6_6a8ar"] +[ext_resource type="Texture2D" uid="uid://dj1x60fld5400" path="res://Art/Rooms/screening_room/backwall.png" id="6_5772r"] +[ext_resource type="Texture2D" uid="uid://c3je4gir1qlot" path="res://Art/BlackScreen.png" id="7_106l6"] +[ext_resource type="Texture2D" uid="uid://jychpkygmjib" path="res://Art/Rooms/screening_room/desk.png" id="7_dp545"] +[ext_resource type="Texture2D" uid="uid://diln53wxa3oep" path="res://Art/Rooms/screening_room/journal_open.png" id="7_eqiuk"] +[ext_resource type="Texture2D" uid="uid://cdbiwtt2w37kr" path="res://Art/Rooms/screening_room/shutter_bottom.png" id="7_feog2"] +[ext_resource type="Texture2D" uid="uid://ohew2ijprnjk" path="res://Art/Rooms/screening_room/dialogue_screen_resized.png" id="8_gdng4"] +[ext_resource type="Theme" uid="uid://3k7sqw211ncl" path="res://Art/UI/button_theme.tres" id="8_hxrv4"] +[ext_resource type="BitMap" uid="uid://b1y07k0opbyju" path="res://Art/Rooms/screening_room/button_bitmap.png" id="10_hxph5"] +[ext_resource type="Texture2D" uid="uid://bhqx2kjgn1irf" path="res://Art/Rooms/screening_room/green_button.png" id="10_tmo4o"] +[ext_resource type="Texture2D" uid="uid://bagw75cp302xk" path="res://Art/Rooms/screening_room/green_button_pressed.png" id="11_weq72"] +[ext_resource type="Texture2D" uid="uid://cqn1o6cff1g4k" path="res://Art/Rooms/screening_room/red_button.png" id="12_7mvev"] +[ext_resource type="Texture2D" uid="uid://dja217ihoi5rb" path="res://Art/Rooms/screening_room/green_button_hover.png" id="12_rpmuh"] +[ext_resource type="Texture2D" uid="uid://kfq5b58sicqf" path="res://Art/Rooms/screening_room/red_button_pressed.png" id="13_lq4cy"] +[ext_resource type="FontFile" uid="uid://dv8ppra5tgn1t" path="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" id="14_0pvwy"] +[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="15_l8po4"] +[ext_resource type="Texture2D" uid="uid://c35f0aquvroje" path="res://Art/Rooms/screening_room/red_button_hover.png" id="15_pbvyq"] +[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="16_16ney"] +[ext_resource type="Texture2D" uid="uid://4tgfgi4earv1" path="res://Art/Rooms/screening_room/journal_unpressed.png" id="17_7o31y"] +[ext_resource type="Texture2D" uid="uid://djhhkejop4636" path="res://Art/Rooms/screening_room/tea_cup.png" id="17_son5s"] +[ext_resource type="Texture2D" uid="uid://1djtmkdqtplt" path="res://Art/Rooms/screening_room/journal_pressed.png" id="18_gv516"] +[ext_resource type="Texture2D" uid="uid://ca88ddggm0ka2" path="res://Art/Rooms/screening_room/day_counter.png" id="18_kle2c"] +[ext_resource type="Texture2D" uid="uid://b7tbgcyxjj516" path="res://Art/Rooms/screening_room/journal_hover.png" id="19_ar3nk"] +[ext_resource type="Texture2D" uid="uid://n2k0abd5iwrn" path="res://Art/Rooms/screening_room/release_off.png" id="19_uk261"] +[ext_resource type="BitMap" uid="uid://byqmvpivmvsnl" path="res://Art/Rooms/screening_room/journal_bitmap.png" id="20_5vhu5"] +[ext_resource type="Texture2D" uid="uid://cfi85pwknn8dw" path="res://Art/Rooms/screening_room/release_on.png" id="20_r3rdv"] +[ext_resource type="Texture2D" uid="uid://bvns3403af0no" path="res://Art/Rooms/screening_room/terminate_off.png" id="21_4suf7"] +[ext_resource type="Texture2D" uid="uid://b4fe8gvg86l4f" path="res://Art/Rooms/screening_room/terminate_on.png" id="22_6rr6n"] +[ext_resource type="Texture2D" uid="uid://cg2mywrvfpvad" path="res://Art/Rooms/screening_room/green_backroom.png" id="29_qdgqn"] +[ext_resource type="Texture2D" uid="uid://cs4ofocaj6xdg" path="res://Art/Rooms/screening_room/red_backroom.png" id="30_r58th"] + +[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"] +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_vou70"] +bg_color = Color(0.6, 0.6, 0.6, 0) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +expand_margin_bottom = 6.0 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4ait8"] +bg_color = Color(0, 0, 0, 1) + +[sub_resource type="Theme" id="Theme_kfxnd"] +default_font_size = 20 +Button/constants/outline_size = 3 +Button/styles/disabled = SubResource("StyleBoxFlat_spyqn") +Button/styles/focus = SubResource("StyleBoxFlat_ri4m3") +Button/styles/hover = SubResource("StyleBoxFlat_e0njw") +Button/styles/normal = SubResource("StyleBoxFlat_vou70") +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_4ait8") + +[sub_resource type="Theme" id="Theme_dahej"] +default_font = ExtResource("14_0pvwy") +RichTextLabel/colors/default_color = Color(0, 0, 0, 1) +RichTextLabel/fonts/normal_font = ExtResource("14_0pvwy") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3lob0"] +bg_color = Color(0.843137, 0.843137, 0.843137, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0, 0, 0, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wj8aa"] +bg_color = Color(0.784314, 0.784314, 0.784314, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0, 0, 0, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5jbc5"] +bg_color = Color(0.901961, 0.901961, 0.901961, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0, 0, 0, 1) + +[sub_resource type="Theme" id="Theme_gwogn"] +Button/colors/font_color = Color(0, 0, 0, 1) +Button/fonts/font = ExtResource("14_0pvwy") +Button/styles/hover = SubResource("StyleBoxFlat_3lob0") +Button/styles/normal = SubResource("StyleBoxFlat_wj8aa") +Button/styles/pressed = SubResource("StyleBoxFlat_5jbc5") + +[sub_resource type="Animation" id="Animation_hai4a"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(60, 50)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(-2540, -540)] +} + +[sub_resource type="Animation" id="Animation_l4uk7"] +resource_name = "going_up" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.5, 1), +"update": 0, +"values": [Vector2(-240, 60), Vector2(-240, -540)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.5, 1), +"update": 0, +"values": [Vector2(-240, -1040), Vector2(-240, -540)] +} + +[sub_resource type="Animation" id="Animation_hb6p3"] +resource_name = "incoming" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.4, 1), +"update": 0, +"values": [Vector2(-2540, -540), Vector2(-240, -540)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.4, 1), +"update": 0, +"values": [Vector2(-2540, -540), Vector2(-240, -540)] +} + +[sub_resource type="Animation" id="Animation_el1v6"] +resource_name = "going_down" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -60)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -1140)] +} + +[sub_resource type="Animation" id="Animation_4lwtm"] +resource_name = "going_up" +length = 3.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("../ShutterTop:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 3), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -3000)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 3), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -3000)] +} + +[sub_resource type="Animation" id="Animation_hebek"] +resource_name = "going_down" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("../ShutterTop:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(10, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, 4000)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(10, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, 4000)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_vh6fv"] +_data = { +"RESET": SubResource("Animation_hai4a"), +"close": SubResource("Animation_l4uk7"), +"incoming": SubResource("Animation_hb6p3"), +"open": SubResource("Animation_el1v6"), +"release": SubResource("Animation_4lwtm"), +"terminate": SubResource("Animation_hebek") +} + +[node name="ScreeningDesk" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +script = ExtResource("1_1s7k3") + +[node name="UpperHalf" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_bottom = -396.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + +[node name="ControlGlass" type="Control" parent="UpperHalf"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 76.0 +offset_top = 48.0 +offset_right = -76.0 +offset_bottom = -28.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Patient" type="TextureRect" parent="UpperHalf/ControlGlass"] +z_index = 2 +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -300.0 +offset_top = -450.0 +offset_right = 300.0 +grow_horizontal = 2 +grow_vertical = 0 +texture = ExtResource("2_y82mh") +stretch_mode = 3 + +[node name="Balloon" type="Control" parent="."] +unique_name_in_owner = true +visible = false +z_index = 6 +layout_mode = 2 +anchors_preset = 0 +offset_left = 1267.0 +offset_top = 730.0 +offset_right = 1267.0 +offset_bottom = 730.0 +size_flags_horizontal = 0 +size_flags_vertical = 0 +mouse_filter = 2 +mouse_force_pass_scroll_events = false +theme = SubResource("Theme_kfxnd") + +[node name="Panel" type="Panel" parent="Balloon"] +clip_children = 2 +layout_mode = 1 +offset_left = -11.0 +offset_top = -9.0 +offset_right = 617.0 +offset_bottom = 186.0 +mouse_force_pass_scroll_events = false + +[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 +mouse_filter = 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("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = "Character" +fit_content = true +scroll_active = false + +[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/VBoxContainer" instance=ExtResource("15_l8po4")] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/bold_italics_font_size = 18 +theme_override_font_sizes/italics_font_size = 18 +theme_override_font_sizes/mono_font_size = 18 +theme_override_font_sizes/normal_font_size = 18 +theme_override_font_sizes/bold_font_size = 18 +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 = -11.0 +offset_top = 186.0 +offset_right = 616.0 +offset_bottom = 326.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("16_16ney") +response_template = NodePath("ResponseExample") + +[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("14_0pvwy") +text = "Response example" + +[node name="LowerHalf" type="Control" parent="."] +z_index = 6 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 684.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="DeskTexture" type="TextureRect" parent="LowerHalf"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("7_dp545") + +[node name="Computer" type="Control" parent="LowerHalf"] +anchors_preset = 0 +offset_left = 16.0 +offset_top = 16.0 +offset_right = 656.0 +offset_bottom = 288.0 + +[node name="Texture" type="TextureRect" parent="LowerHalf/Computer"] +layout_mode = 2 +offset_right = 640.0 +offset_bottom = 272.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("6_6a8ar") +stretch_mode = 2 + +[node name="Margin" type="MarginContainer" parent="LowerHalf/Computer"] +layout_mode = 2 +offset_right = 640.0 +offset_bottom = 272.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 24 +theme_override_constants/margin_top = 25 +theme_override_constants/margin_right = 32 +theme_override_constants/margin_bottom = 20 + +[node name="ComputerScreen" type="VBoxContainer" parent="LowerHalf/Computer/Margin"] +layout_mode = 2 +theme_override_constants/separation = 18 + +[node name="Tabs" type="HBoxContainer" parent="LowerHalf/Computer/Margin/ComputerScreen"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.15 + +[node name="PatientFile" type="Button" parent="LowerHalf/Computer/Margin/ComputerScreen/Tabs"] +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("8_hxrv4") +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 16 +toggle_mode = true +text = "PATIENT FILE" + +[node name="Rules" type="Button" parent="LowerHalf/Computer/Margin/ComputerScreen/Tabs"] +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("8_hxrv4") +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 16 +toggle_mode = true +text = "RULES" + +[node name="Recording" type="Button" parent="LowerHalf/Computer/Margin/ComputerScreen/Tabs"] +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("8_hxrv4") +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 16 +toggle_mode = true +text = "RECORDING" + +[node name="ComputerTabs" type="MarginContainer" parent="LowerHalf/Computer/Margin/ComputerScreen"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 0 +theme_override_constants/margin_top = 0 +theme_override_constants/margin_right = 0 +theme_override_constants/margin_bottom = 0 + +[node name="FileContainer" type="VBoxContainer" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs"] +layout_mode = 2 +theme_override_constants/separation = -4 + +[node name="PatientFileTitle" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs/FileContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.9 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = "[center]PATIENT #564[/center]" + +[node name="PatientFile" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs/FileContainer"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 6.0 +theme_override_constants/line_separation = 5 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = "AGE: 29 + +NAME: Gwendoline Gorbado + +PROFESSION: IT Engineer + +PSYCHIATRIC ASSESSMENT: Depression +" + +[node name="Rules" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs"] +visible = false +z_index = 1 +layout_mode = 2 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_fonts/italics_font = ExtResource("14_0pvwy") +theme_override_font_sizes/italics_font_size = 16 +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = " 1- Patients may either suffer from [color=darkgreen]Normal Psychiatric Illnesses[/color], or be [color=darkred]Abnormals[/color]. + + 2- Abnormals may try to [color=darkred]mimic[/color] humans' emotions and actions. + + 3- [color=darkgreen]Release[/color] Normal Psychiatric Patients. + + 4- [color=darkred]Terminate[/color] Abnormals." + +[node name="Recording" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs"] +visible = false +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 6.0 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true + +[node name="NextPatient" type="PanelContainer" parent="LowerHalf/Computer/Margin"] +visible = false +custom_minimum_size = Vector2(400, 100) +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 + +[node name="CallNextPatient" type="Button" parent="LowerHalf/Computer/Margin/NextPatient"] +z_index = 1 +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 25 +text = "CALL NEXT PATIENT" + +[node name="EndDay" type="PanelContainer" parent="LowerHalf/Computer/Margin"] +visible = false +custom_minimum_size = Vector2(200, 100) +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 + +[node name="EndDay" type="Button" parent="LowerHalf/Computer/Margin/EndDay"] +z_index = 1 +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 25 +text = "END DAY" + +[node name="DialogueScreen" type="Control" parent="LowerHalf"] +z_index = 1 +anchors_preset = 0 +offset_left = 1236.0 +offset_top = 16.0 +offset_right = 1912.0 +offset_bottom = 392.0 + +[node name="DialogueScreen" type="TextureRect" parent="LowerHalf/DialogueScreen"] +layout_mode = 0 +offset_right = 676.0 +offset_bottom = 376.0 +texture = ExtResource("8_gdng4") + +[node name="Margin" type="MarginContainer" parent="LowerHalf/DialogueScreen"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 30 +theme_override_constants/margin_top = 30 +theme_override_constants/margin_right = 38 +theme_override_constants/margin_bottom = 30 + +[node name="DialogueText" type="RichTextLabel" parent="LowerHalf/DialogueScreen/Margin"] +custom_minimum_size = Vector2(600, 200) +layout_mode = 2 +size_flags_vertical = 4 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 20 +bbcode_enabled = true +text = "[center][color=dark_orange]- TERMINATING SUBJECT -[/color][/center]" + +[node name="JournalLayer" type="CanvasLayer" parent="LowerHalf"] +layer = 110 +visible = false + +[node name="JournalOpen" type="Control" parent="LowerHalf/JournalLayer"] +z_index = 8 +layout_mode = 3 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -800.0 +offset_top = -800.0 +offset_right = 800.0 +grow_horizontal = 2 +grow_vertical = 0 +theme = SubResource("Theme_dahej") + +[node name="Shader" type="TextureRect" parent="LowerHalf/JournalLayer/JournalOpen"] +modulate = Color(1, 1, 1, 0.52549) +z_index = 2 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -221.0 +offset_top = -356.0 +offset_right = 198.0 +offset_bottom = 44.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("7_106l6") + +[node name="JournalOpenTexture" type="TextureRect" parent="LowerHalf/JournalLayer/JournalOpen"] +z_index = 2 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("7_eqiuk") + +[node name="JournalMargin" type="MarginContainer" parent="LowerHalf/JournalLayer/JournalOpen"] +z_index = 2 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 70 +theme_override_constants/margin_top = 30 +theme_override_constants/margin_right = 70 +theme_override_constants/margin_bottom = 50 + +[node name="PageArea" type="HBoxContainer" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin"] +layout_mode = 2 +theme_override_constants/separation = 90 + +[node name="LeftPage" type="VBoxContainer" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="PageTitle" type="RichTextLabel" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/LeftPage"] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +size_flags_stretch_ratio = 0.1 +theme_override_font_sizes/normal_font_size = 28 +bbcode_enabled = true +text = " +[center]HIGH-RISK SYMPTOMS[/center]" + +[node name="Instructions" type="RichTextLabel" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/LeftPage"] +custom_minimum_size = Vector2(0, 500) +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.0 +theme_override_constants/line_separation = 20 +theme_override_font_sizes/normal_font_size = 20 +bbcode_enabled = true +text = " + + + + +THE FOLLOWING SYMPTOMS HAVE BEEN IDENTIFIED AS MARKERS OF ABNORMALITY CONVERSION + + +IF YOU DETECT ONE THAT WAS NOT ASSESSED BEFOREHAND, [color=red]TERMINATE[/color] THE SUBJECT" + +[node name="CloseManual" type="Button" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/LeftPage"] +custom_minimum_size = Vector2(200, 60) +layout_mode = 2 +size_flags_horizontal = 4 +theme = SubResource("Theme_gwogn") +text = "Close manual" + +[node name="RightPage" type="VBoxContainer" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Symptoms" type="RichTextLabel" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/RightPage"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/line_separation = 5 +theme_override_font_sizes/normal_font_size = 18 +bbcode_enabled = true +text = " + +SUICIDAL IDEATIONS +Persistent thoughts about ending one's own life + +PERSECUTORY DELUSIONS +False conviction of being targeted or harmed + +MEGALOMANIAC DELUSIONS +Pathological exaggeration of self-importance or power + +MYSTICAL DELUSIONS +False certainty of spiritual or divine connection + +MANIA +Excessively elevated mood and increased energy + +AUDITORY HALLUCINATIONS +False perceptions of external sound + +VISUAL HALLUCINATIONS +False perceptions of visual stimuli + +HAPTIC HALLUCINATIONS +False perceptions of touch + +CENESTHETIC HALLUCINATIONS +False internal bodily sensations + +PSYCHIC HALLUCINATIONS +Hallucinations occurring within the mind" + +[node name="GreenButton" type="TextureButton" parent="LowerHalf"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 817.0 +offset_top = 315.0 +offset_right = -1003.0 +offset_bottom = -17.0 +grow_horizontal = 2 +grow_vertical = 2 +toggle_mode = true +texture_normal = ExtResource("10_tmo4o") +texture_pressed = ExtResource("11_weq72") +texture_hover = ExtResource("12_rpmuh") +texture_disabled = ExtResource("11_weq72") +texture_click_mask = ExtResource("10_hxph5") +stretch_mode = 3 + +[node name="RedButton" type="TextureButton" parent="LowerHalf"] +z_index = 1 +layout_mode = 2 +offset_left = 1003.0 +offset_top = 315.0 +offset_right = 1103.0 +offset_bottom = 379.0 +grow_horizontal = 2 +grow_vertical = 2 +toggle_mode = true +texture_normal = ExtResource("12_7mvev") +texture_pressed = ExtResource("13_lq4cy") +texture_hover = ExtResource("15_pbvyq") +texture_disabled = ExtResource("13_lq4cy") +stretch_mode = 3 + +[node name="Journal" type="Control" parent="LowerHalf"] +anchors_preset = 0 +offset_left = 664.0 +offset_top = 304.0 +offset_right = 796.0 +offset_bottom = 384.0 + +[node name="JournalButton" type="TextureButton" parent="LowerHalf/Journal"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture_normal = ExtResource("17_7o31y") +texture_pressed = ExtResource("18_gv516") +texture_hover = ExtResource("19_ar3nk") +texture_click_mask = ExtResource("20_5vhu5") +stretch_mode = 3 + +[node name="TeaCup" type="Control" parent="LowerHalf"] +layout_mode = 3 +anchors_preset = 0 +offset_left = 516.0 +offset_top = 312.0 +offset_right = 628.0 +offset_bottom = 376.0 + +[node name="TeaCupTexture" type="TextureRect" parent="LowerHalf/TeaCup"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("17_son5s") + +[node name="DayCounter" type="Control" parent="LowerHalf"] +z_index = 1 +layout_mode = 3 +anchors_preset = 0 +offset_left = 724.0 +offset_top = 15.0 +offset_right = 1204.0 +offset_bottom = 136.0 + +[node name="DayCounterTexture" type="TextureRect" parent="LowerHalf/DayCounter"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("18_kle2c") + +[node name="MarginContainer" type="MarginContainer" parent="LowerHalf/DayCounter"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 40 +theme_override_constants/margin_top = 42 +theme_override_constants/margin_right = 40 +theme_override_constants/margin_bottom = 30 + +[node name="HBoxContainer" type="HBoxContainer" parent="LowerHalf/DayCounter/MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 30 + +[node name="Day" type="RichTextLabel" parent="LowerHalf/DayCounter/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 40 +bbcode_enabled = true +text = "[center]DAY[/center]" + +[node name="Count" type="RichTextLabel" parent="LowerHalf/DayCounter/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 40 +bbcode_enabled = true +text = "[center]12[/center]" + +[node name="Release" type="Control" parent="LowerHalf"] +layout_mode = 3 +anchors_preset = 0 +offset_left = 788.0 +offset_top = 188.0 +offset_right = 948.0 +offset_bottom = 268.0 + +[node name="ReleaseOff" type="TextureRect" parent="LowerHalf/Release"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("19_uk261") +stretch_mode = 3 + +[node name="ReleaseOn" type="TextureRect" parent="LowerHalf/Release"] +visible = false +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("20_r3rdv") +stretch_mode = 3 + +[node name="Terminate" type="Control" parent="LowerHalf"] +layout_mode = 3 +anchors_preset = 0 +offset_left = 972.0 +offset_top = 188.0 +offset_right = 1149.0 +offset_bottom = 268.0 + +[node name="TerminateOff" type="TextureRect" parent="LowerHalf/Terminate"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("21_4suf7") +stretch_mode = 3 + +[node name="TerminateOn" type="TextureRect" parent="LowerHalf/Terminate"] +visible = false +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("22_6rr6n") +stretch_mode = 3 + +[node name="RoomAssets" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + +[node name="Glass" type="TextureRect" parent="RoomAssets"] +z_index = 4 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("3_nxnrp") +expand_mode = 1 + +[node name="Backroom" type="TextureRect" parent="RoomAssets"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("4_fij8w") + +[node name="Room" type="TextureRect" parent="RoomAssets"] +z_index = 5 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("5_aa8r5") + +[node name="Backwall" type="TextureRect" parent="RoomAssets"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("6_5772r") + +[node name="ShutterTop" type="TextureRect" parent="RoomAssets"] +z_index = 3 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -2540.0 +offset_top = -540.0 +offset_right = -2060.0 +offset_bottom = 540.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 4 +size_flags_vertical = 0 +mouse_filter = 2 +texture = ExtResource("6_1rj24") +stretch_mode = 3 + +[node name="ShutterBot" type="TextureRect" parent="RoomAssets"] +z_index = 3 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 60.0 +offset_top = 50.0 +offset_right = 540.0 +offset_bottom = 1130.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 4 +mouse_filter = 2 +texture = ExtResource("7_feog2") +stretch_mode = 3 + +[node name="ShuttersAnimation" type="AnimationPlayer" parent="RoomAssets"] +root_node = NodePath("../ShutterBot") +libraries = { +"": SubResource("AnimationLibrary_vh6fv") +} + +[node name="GreenBackroom" type="TextureRect" parent="RoomAssets"] +visible = false +modulate = Color(1, 1, 1, 0.588235) +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("29_qdgqn") + +[node name="RedBackroom" type="TextureRect" parent="RoomAssets"] +visible = false +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("30_r58th") diff --git a/scrF37F.tmp b/scrF37F.tmp new file mode 100644 index 0000000..63ac340 --- /dev/null +++ b/scrF37F.tmp @@ -0,0 +1,1118 @@ +[gd_scene load_steps=55 format=3 uid="uid://chytk2cctjk3x"] + +[ext_resource type="Script" path="res://screening_desk.gd" id="1_1s7k3"] +[ext_resource type="Texture2D" uid="uid://x52vv4rovnff" path="res://Art/Patients/patient_50/patient_50_neutral.png" id="2_y82mh"] +[ext_resource type="Texture2D" uid="uid://b2epvjeqvmgrs" path="res://Art/Rooms/screening_room/screening_room_glass.png" id="3_nxnrp"] +[ext_resource type="Texture2D" uid="uid://by34irl73n8pb" path="res://Art/Rooms/screening_room/backroom.png" id="4_fij8w"] +[ext_resource type="Texture2D" uid="uid://ds1whnmwt7xjt" path="res://Art/Rooms/screening_room/screening_room.png" id="5_aa8r5"] +[ext_resource type="Texture2D" uid="uid://4qtc0tkq2wba" path="res://Art/Rooms/screening_room/shutter_top.png" id="6_1rj24"] +[ext_resource type="Texture2D" uid="uid://cci0pb12chlps" path="res://Art/Rooms/screening_room/computer.png" id="6_6a8ar"] +[ext_resource type="Texture2D" uid="uid://dj1x60fld5400" path="res://Art/Rooms/screening_room/backwall.png" id="6_5772r"] +[ext_resource type="Texture2D" uid="uid://c3je4gir1qlot" path="res://Art/BlackScreen.png" id="7_106l6"] +[ext_resource type="Texture2D" uid="uid://jychpkygmjib" path="res://Art/Rooms/screening_room/desk.png" id="7_dp545"] +[ext_resource type="Texture2D" uid="uid://diln53wxa3oep" path="res://Art/Rooms/screening_room/journal_open.png" id="7_eqiuk"] +[ext_resource type="Texture2D" uid="uid://cdbiwtt2w37kr" path="res://Art/Rooms/screening_room/shutter_bottom.png" id="7_feog2"] +[ext_resource type="Texture2D" uid="uid://ohew2ijprnjk" path="res://Art/Rooms/screening_room/dialogue_screen_resized.png" id="8_gdng4"] +[ext_resource type="Theme" uid="uid://3k7sqw211ncl" path="res://Art/UI/button_theme.tres" id="8_hxrv4"] +[ext_resource type="BitMap" uid="uid://b1y07k0opbyju" path="res://Art/Rooms/screening_room/button_bitmap.png" id="10_hxph5"] +[ext_resource type="Texture2D" uid="uid://bhqx2kjgn1irf" path="res://Art/Rooms/screening_room/green_button.png" id="10_tmo4o"] +[ext_resource type="Texture2D" uid="uid://bagw75cp302xk" path="res://Art/Rooms/screening_room/green_button_pressed.png" id="11_weq72"] +[ext_resource type="Texture2D" uid="uid://cqn1o6cff1g4k" path="res://Art/Rooms/screening_room/red_button.png" id="12_7mvev"] +[ext_resource type="Texture2D" uid="uid://dja217ihoi5rb" path="res://Art/Rooms/screening_room/green_button_hover.png" id="12_rpmuh"] +[ext_resource type="Texture2D" uid="uid://kfq5b58sicqf" path="res://Art/Rooms/screening_room/red_button_pressed.png" id="13_lq4cy"] +[ext_resource type="FontFile" uid="uid://dv8ppra5tgn1t" path="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" id="14_0pvwy"] +[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="15_l8po4"] +[ext_resource type="Texture2D" uid="uid://c35f0aquvroje" path="res://Art/Rooms/screening_room/red_button_hover.png" id="15_pbvyq"] +[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="16_16ney"] +[ext_resource type="Texture2D" uid="uid://4tgfgi4earv1" path="res://Art/Rooms/screening_room/journal_unpressed.png" id="17_7o31y"] +[ext_resource type="Texture2D" uid="uid://djhhkejop4636" path="res://Art/Rooms/screening_room/tea_cup.png" id="17_son5s"] +[ext_resource type="Texture2D" uid="uid://1djtmkdqtplt" path="res://Art/Rooms/screening_room/journal_pressed.png" id="18_gv516"] +[ext_resource type="Texture2D" uid="uid://ca88ddggm0ka2" path="res://Art/Rooms/screening_room/day_counter.png" id="18_kle2c"] +[ext_resource type="Texture2D" uid="uid://b7tbgcyxjj516" path="res://Art/Rooms/screening_room/journal_hover.png" id="19_ar3nk"] +[ext_resource type="Texture2D" uid="uid://n2k0abd5iwrn" path="res://Art/Rooms/screening_room/release_off.png" id="19_uk261"] +[ext_resource type="BitMap" uid="uid://byqmvpivmvsnl" path="res://Art/Rooms/screening_room/journal_bitmap.png" id="20_5vhu5"] +[ext_resource type="Texture2D" uid="uid://cfi85pwknn8dw" path="res://Art/Rooms/screening_room/release_on.png" id="20_r3rdv"] +[ext_resource type="Texture2D" uid="uid://bvns3403af0no" path="res://Art/Rooms/screening_room/terminate_off.png" id="21_4suf7"] +[ext_resource type="Texture2D" uid="uid://b4fe8gvg86l4f" path="res://Art/Rooms/screening_room/terminate_on.png" id="22_6rr6n"] +[ext_resource type="Texture2D" uid="uid://cg2mywrvfpvad" path="res://Art/Rooms/screening_room/green_backroom.png" id="29_qdgqn"] +[ext_resource type="Texture2D" uid="uid://cs4ofocaj6xdg" path="res://Art/Rooms/screening_room/red_backroom.png" id="30_r58th"] + +[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"] +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_vou70"] +bg_color = Color(0.6, 0.6, 0.6, 0) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +expand_margin_bottom = 6.0 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4ait8"] +bg_color = Color(0, 0, 0, 1) + +[sub_resource type="Theme" id="Theme_kfxnd"] +default_font_size = 20 +Button/constants/outline_size = 3 +Button/styles/disabled = SubResource("StyleBoxFlat_spyqn") +Button/styles/focus = SubResource("StyleBoxFlat_ri4m3") +Button/styles/hover = SubResource("StyleBoxFlat_e0njw") +Button/styles/normal = SubResource("StyleBoxFlat_vou70") +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_4ait8") + +[sub_resource type="Theme" id="Theme_dahej"] +default_font = ExtResource("14_0pvwy") +RichTextLabel/colors/default_color = Color(0, 0, 0, 1) +RichTextLabel/fonts/normal_font = ExtResource("14_0pvwy") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3lob0"] +bg_color = Color(0.843137, 0.843137, 0.843137, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0, 0, 0, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wj8aa"] +bg_color = Color(0.784314, 0.784314, 0.784314, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0, 0, 0, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5jbc5"] +bg_color = Color(0.901961, 0.901961, 0.901961, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0, 0, 0, 1) + +[sub_resource type="Theme" id="Theme_gwogn"] +Button/colors/font_color = Color(0, 0, 0, 1) +Button/fonts/font = ExtResource("14_0pvwy") +Button/styles/hover = SubResource("StyleBoxFlat_3lob0") +Button/styles/normal = SubResource("StyleBoxFlat_wj8aa") +Button/styles/pressed = SubResource("StyleBoxFlat_5jbc5") + +[sub_resource type="Animation" id="Animation_hai4a"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(60, 50)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(-2540, -540)] +} + +[sub_resource type="Animation" id="Animation_l4uk7"] +resource_name = "going_up" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.5, 1), +"update": 0, +"values": [Vector2(-240, 60), Vector2(-240, -540)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.5, 1), +"update": 0, +"values": [Vector2(-240, -1040), Vector2(-240, -540)] +} + +[sub_resource type="Animation" id="Animation_hb6p3"] +resource_name = "incoming" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.4, 1), +"update": 0, +"values": [Vector2(-2540, -540), Vector2(-240, -540)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.4, 1), +"update": 0, +"values": [Vector2(-2540, -540), Vector2(-240, -540)] +} + +[sub_resource type="Animation" id="Animation_el1v6"] +resource_name = "going_down" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -60)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -1140)] +} + +[sub_resource type="Animation" id="Animation_4lwtm"] +resource_name = "going_up" +length = 3.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("../ShutterTop:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 3), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -3000)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 3), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -3000)] +} + +[sub_resource type="Animation" id="Animation_hebek"] +resource_name = "going_down" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("../ShutterTop:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(10, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, 4000)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(10, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, 4000)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_vh6fv"] +_data = { +"RESET": SubResource("Animation_hai4a"), +"close": SubResource("Animation_l4uk7"), +"incoming": SubResource("Animation_hb6p3"), +"open": SubResource("Animation_el1v6"), +"release": SubResource("Animation_4lwtm"), +"terminate": SubResource("Animation_hebek") +} + +[node name="ScreeningDesk" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +script = ExtResource("1_1s7k3") + +[node name="UpperHalf" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_bottom = -396.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + +[node name="ControlGlass" type="Control" parent="UpperHalf"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 76.0 +offset_top = 48.0 +offset_right = -76.0 +offset_bottom = -28.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Patient" type="TextureRect" parent="UpperHalf/ControlGlass"] +z_index = 2 +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -300.0 +offset_top = -450.0 +offset_right = 300.0 +grow_horizontal = 2 +grow_vertical = 0 +texture = ExtResource("2_y82mh") +stretch_mode = 3 + +[node name="Balloon" type="Control" parent="."] +unique_name_in_owner = true +visible = false +z_index = 6 +layout_mode = 2 +anchors_preset = 0 +offset_left = 1267.0 +offset_top = 730.0 +offset_right = 1267.0 +offset_bottom = 730.0 +size_flags_horizontal = 0 +size_flags_vertical = 0 +mouse_filter = 2 +mouse_force_pass_scroll_events = false +theme = SubResource("Theme_kfxnd") + +[node name="Panel" type="Panel" parent="Balloon"] +clip_children = 2 +layout_mode = 1 +offset_left = -11.0 +offset_top = -9.0 +offset_right = 617.0 +offset_bottom = 186.0 +mouse_force_pass_scroll_events = false + +[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 +mouse_filter = 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("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = "Character" +fit_content = true +scroll_active = false + +[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/VBoxContainer" instance=ExtResource("15_l8po4")] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/bold_italics_font_size = 18 +theme_override_font_sizes/italics_font_size = 18 +theme_override_font_sizes/mono_font_size = 18 +theme_override_font_sizes/normal_font_size = 18 +theme_override_font_sizes/bold_font_size = 18 +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 = -11.0 +offset_top = 186.0 +offset_right = 616.0 +offset_bottom = 326.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("16_16ney") +response_template = NodePath("ResponseExample") + +[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("14_0pvwy") +text = "Response example" + +[node name="LowerHalf" type="Control" parent="."] +z_index = 6 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 684.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="DeskTexture" type="TextureRect" parent="LowerHalf"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("7_dp545") + +[node name="Computer" type="Control" parent="LowerHalf"] +anchors_preset = 0 +offset_left = 16.0 +offset_top = 16.0 +offset_right = 656.0 +offset_bottom = 288.0 + +[node name="Texture" type="TextureRect" parent="LowerHalf/Computer"] +layout_mode = 2 +offset_right = 640.0 +offset_bottom = 272.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("6_6a8ar") +stretch_mode = 2 + +[node name="Margin" type="MarginContainer" parent="LowerHalf/Computer"] +layout_mode = 2 +offset_right = 640.0 +offset_bottom = 272.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 24 +theme_override_constants/margin_top = 25 +theme_override_constants/margin_right = 32 +theme_override_constants/margin_bottom = 20 + +[node name="ComputerScreen" type="VBoxContainer" parent="LowerHalf/Computer/Margin"] +layout_mode = 2 +theme_override_constants/separation = 18 + +[node name="Tabs" type="HBoxContainer" parent="LowerHalf/Computer/Margin/ComputerScreen"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.15 + +[node name="PatientFile" type="Button" parent="LowerHalf/Computer/Margin/ComputerScreen/Tabs"] +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("8_hxrv4") +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 16 +toggle_mode = true +text = "PATIENT FILE" + +[node name="Rules" type="Button" parent="LowerHalf/Computer/Margin/ComputerScreen/Tabs"] +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("8_hxrv4") +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 16 +toggle_mode = true +text = "RULES" + +[node name="Recording" type="Button" parent="LowerHalf/Computer/Margin/ComputerScreen/Tabs"] +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("8_hxrv4") +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 16 +toggle_mode = true +text = "RECORDING" + +[node name="ComputerTabs" type="MarginContainer" parent="LowerHalf/Computer/Margin/ComputerScreen"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 0 +theme_override_constants/margin_top = 0 +theme_override_constants/margin_right = 0 +theme_override_constants/margin_bottom = 0 + +[node name="FileContainer" type="VBoxContainer" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs"] +layout_mode = 2 +theme_override_constants/separation = -4 + +[node name="PatientFileTitle" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs/FileContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.9 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = "[center]PATIENT #564[/center]" + +[node name="PatientFile" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs/FileContainer"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 6.0 +theme_override_constants/line_separation = 5 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = "AGE: 29 + +NAME: Gwendoline Gorbado + +PROFESSION: IT Engineer + +PSYCHIATRIC ASSESSMENT: Depression +" + +[node name="Rules" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs"] +visible = false +z_index = 1 +layout_mode = 2 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_fonts/italics_font = ExtResource("14_0pvwy") +theme_override_font_sizes/italics_font_size = 16 +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = " 1- Patients may either suffer from [color=darkgreen]Normal Psychiatric Illnesses[/color], or be [color=darkred]Abnormals[/color]. + + 2- Abnormals may try to [color=darkred]mimic[/color] humans' emotions and actions. + + 3- [color=darkgreen]Release[/color] Normal Psychiatric Patients. + + 4- [color=darkred]Terminate[/color] Abnormals." + +[node name="Recording" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs"] +visible = false +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 6.0 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true + +[node name="NextPatient" type="PanelContainer" parent="LowerHalf/Computer/Margin"] +visible = false +custom_minimum_size = Vector2(400, 100) +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 + +[node name="CallNextPatient" type="Button" parent="LowerHalf/Computer/Margin/NextPatient"] +z_index = 1 +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 25 +text = "CALL NEXT PATIENT" + +[node name="EndDay" type="PanelContainer" parent="LowerHalf/Computer/Margin"] +visible = false +custom_minimum_size = Vector2(200, 100) +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 + +[node name="EndDay" type="Button" parent="LowerHalf/Computer/Margin/EndDay"] +z_index = 1 +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 25 +text = "END DAY" + +[node name="DialogueScreen" type="Control" parent="LowerHalf"] +z_index = 1 +anchors_preset = 0 +offset_left = 1236.0 +offset_top = 16.0 +offset_right = 1912.0 +offset_bottom = 392.0 + +[node name="DialogueScreen" type="TextureRect" parent="LowerHalf/DialogueScreen"] +layout_mode = 0 +offset_right = 676.0 +offset_bottom = 376.0 +texture = ExtResource("8_gdng4") + +[node name="Margin" type="MarginContainer" parent="LowerHalf/DialogueScreen"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 30 +theme_override_constants/margin_top = 30 +theme_override_constants/margin_right = 38 +theme_override_constants/margin_bottom = 30 + +[node name="DialogueText" type="RichTextLabel" parent="LowerHalf/DialogueScreen/Margin"] +custom_minimum_size = Vector2(600, 200) +layout_mode = 2 +size_flags_vertical = 4 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 20 +bbcode_enabled = true +text = "[center][color=dark_orange]- TERMINATING SUBJECT -[/color][/center]" + +[node name="JournalLayer" type="CanvasLayer" parent="LowerHalf"] +layer = 110 +visible = false + +[node name="JournalOpen" type="Control" parent="LowerHalf/JournalLayer"] +z_index = 8 +layout_mode = 3 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -800.0 +offset_top = -800.0 +offset_right = 800.0 +grow_horizontal = 2 +grow_vertical = 0 +theme = SubResource("Theme_dahej") + +[node name="Shader" type="TextureRect" parent="LowerHalf/JournalLayer/JournalOpen"] +modulate = Color(1, 1, 1, 0.52549) +z_index = 2 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -221.0 +offset_top = -356.0 +offset_right = 198.0 +offset_bottom = 44.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("7_106l6") + +[node name="JournalOpenTexture" type="TextureRect" parent="LowerHalf/JournalLayer/JournalOpen"] +z_index = 2 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("7_eqiuk") + +[node name="JournalMargin" type="MarginContainer" parent="LowerHalf/JournalLayer/JournalOpen"] +z_index = 2 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 70 +theme_override_constants/margin_top = 30 +theme_override_constants/margin_right = 70 +theme_override_constants/margin_bottom = 50 + +[node name="PageArea" type="HBoxContainer" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin"] +layout_mode = 2 +theme_override_constants/separation = 90 + +[node name="LeftPage" type="VBoxContainer" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="PageTitle" type="RichTextLabel" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/LeftPage"] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +size_flags_stretch_ratio = 0.1 +theme_override_font_sizes/normal_font_size = 28 +bbcode_enabled = true +text = " +[center]HIGH-RISK SYMPTOMS[/center]" + +[node name="Instructions" type="RichTextLabel" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/LeftPage"] +custom_minimum_size = Vector2(0, 500) +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.0 +theme_override_constants/line_separation = 20 +theme_override_font_sizes/normal_font_size = 20 +bbcode_enabled = true +text = " + + + + +THE FOLLOWING SYMPTOMS HAVE BEEN IDENTIFIED AS MARKERS OF ABNORMALITY CONVERSION + + +IF YOU DETECT ONE THAT WAS NOT ASSESSED BEFOREHAND, [color=red]TERMINATE[/color] THE SUBJECT" + +[node name="CloseManual" type="Button" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/LeftPage"] +custom_minimum_size = Vector2(200, 60) +layout_mode = 2 +size_flags_horizontal = 4 +theme = SubResource("Theme_gwogn") +text = "Close manual" + +[node name="RightPage" type="VBoxContainer" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Symptoms" type="RichTextLabel" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/RightPage"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/line_separation = 5 +theme_override_font_sizes/normal_font_size = 18 +bbcode_enabled = true +text = " + +SUICIDAL IDEATIONS +Persistent thoughts about ending one's own life + +PERSECUTORY DELUSIONS +False conviction of being targeted or harmed + +MEGALOMANIAC DELUSIONS +Pathological exaggeration of self-importance or power + +MYSTICAL DELUSIONS +False certainty of spiritual or divine connection + +MANIA +Excessively elevated mood and increased energy + +AUDITORY HALLUCINATIONS +False perceptions of external sound + +VISUAL HALLUCINATIONS +False perceptions of visual stimuli + +HAPTIC HALLUCINATIONS +False perceptions of touch + +CENESTHETIC HALLUCINATIONS +False internal bodily sensations + +PSYCHIC HALLUCINATIONS +Hallucinations occurring within the mind" + +[node name="GreenButton" type="TextureButton" parent="LowerHalf"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 817.0 +offset_top = 315.0 +offset_right = -1003.0 +offset_bottom = -17.0 +grow_horizontal = 2 +grow_vertical = 2 +toggle_mode = true +texture_normal = ExtResource("10_tmo4o") +texture_pressed = ExtResource("11_weq72") +texture_hover = ExtResource("12_rpmuh") +texture_disabled = ExtResource("11_weq72") +texture_click_mask = ExtResource("10_hxph5") +stretch_mode = 3 + +[node name="RedButton" type="TextureButton" parent="LowerHalf"] +z_index = 1 +layout_mode = 2 +offset_left = 1003.0 +offset_top = 315.0 +offset_right = 1103.0 +offset_bottom = 379.0 +grow_horizontal = 2 +grow_vertical = 2 +toggle_mode = true +texture_normal = ExtResource("12_7mvev") +texture_pressed = ExtResource("13_lq4cy") +texture_hover = ExtResource("15_pbvyq") +texture_disabled = ExtResource("13_lq4cy") +stretch_mode = 3 + +[node name="Journal" type="Control" parent="LowerHalf"] +anchors_preset = 0 +offset_left = 664.0 +offset_top = 304.0 +offset_right = 796.0 +offset_bottom = 384.0 + +[node name="JournalButton" type="TextureButton" parent="LowerHalf/Journal"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture_normal = ExtResource("17_7o31y") +texture_pressed = ExtResource("18_gv516") +texture_hover = ExtResource("19_ar3nk") +texture_click_mask = ExtResource("20_5vhu5") +stretch_mode = 3 + +[node name="TeaCup" type="Control" parent="LowerHalf"] +layout_mode = 3 +anchors_preset = 0 +offset_left = 516.0 +offset_top = 312.0 +offset_right = 628.0 +offset_bottom = 376.0 + +[node name="TeaCupTexture" type="TextureRect" parent="LowerHalf/TeaCup"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("17_son5s") + +[node name="DayCounter" type="Control" parent="LowerHalf"] +z_index = 1 +layout_mode = 3 +anchors_preset = 0 +offset_left = 724.0 +offset_top = 15.0 +offset_right = 1204.0 +offset_bottom = 136.0 + +[node name="DayCounterTexture" type="TextureRect" parent="LowerHalf/DayCounter"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("18_kle2c") + +[node name="MarginContainer" type="MarginContainer" parent="LowerHalf/DayCounter"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 40 +theme_override_constants/margin_top = 42 +theme_override_constants/margin_right = 40 +theme_override_constants/margin_bottom = 30 + +[node name="HBoxContainer" type="HBoxContainer" parent="LowerHalf/DayCounter/MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 30 + +[node name="Day" type="RichTextLabel" parent="LowerHalf/DayCounter/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 40 +bbcode_enabled = true +text = "[center]DAY[/center]" + +[node name="Count" type="RichTextLabel" parent="LowerHalf/DayCounter/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 40 +bbcode_enabled = true +text = "[center]12[/center]" + +[node name="Release" type="Control" parent="LowerHalf"] +layout_mode = 3 +anchors_preset = 0 +offset_left = 788.0 +offset_top = 188.0 +offset_right = 948.0 +offset_bottom = 268.0 + +[node name="ReleaseOff" type="TextureRect" parent="LowerHalf/Release"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("19_uk261") +stretch_mode = 3 + +[node name="ReleaseOn" type="TextureRect" parent="LowerHalf/Release"] +visible = false +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("20_r3rdv") +stretch_mode = 3 + +[node name="Terminate" type="Control" parent="LowerHalf"] +layout_mode = 3 +anchors_preset = 0 +offset_left = 972.0 +offset_top = 188.0 +offset_right = 1149.0 +offset_bottom = 268.0 + +[node name="TerminateOff" type="TextureRect" parent="LowerHalf/Terminate"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("21_4suf7") +stretch_mode = 3 + +[node name="TerminateOn" type="TextureRect" parent="LowerHalf/Terminate"] +visible = false +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("22_6rr6n") +stretch_mode = 3 + +[node name="RoomAssets" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + +[node name="Glass" type="TextureRect" parent="RoomAssets"] +z_index = 4 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("3_nxnrp") +expand_mode = 1 + +[node name="Backroom" type="TextureRect" parent="RoomAssets"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("4_fij8w") + +[node name="Room" type="TextureRect" parent="RoomAssets"] +z_index = 5 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("5_aa8r5") + +[node name="Backwall" type="TextureRect" parent="RoomAssets"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("6_5772r") + +[node name="ShutterTop" type="TextureRect" parent="RoomAssets"] +z_index = 3 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -2540.0 +offset_top = -540.0 +offset_right = -2060.0 +offset_bottom = 540.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 4 +size_flags_vertical = 0 +mouse_filter = 2 +texture = ExtResource("6_1rj24") +stretch_mode = 3 + +[node name="ShutterBot" type="TextureRect" parent="RoomAssets"] +z_index = 3 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 60.0 +offset_top = 50.0 +offset_right = 540.0 +offset_bottom = 1130.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 4 +mouse_filter = 2 +texture = ExtResource("7_feog2") +stretch_mode = 3 + +[node name="ShuttersAnimation" type="AnimationPlayer" parent="RoomAssets"] +root_node = NodePath("../ShutterBot") +libraries = { +"": SubResource("AnimationLibrary_vh6fv") +} + +[node name="GreenBackroom" type="TextureRect" parent="RoomAssets"] +visible = false +modulate = Color(1, 1, 1, 0.588235) +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("29_qdgqn") + +[node name="RedBackroom" type="TextureRect" parent="RoomAssets"] +visible = false +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("30_r58th") diff --git a/screening_desk.gd b/screening_desk.gd index 018e811..4afb26d 100644 --- a/screening_desk.gd +++ b/screening_desk.gd @@ -1,249 +1,330 @@ extends Control -@onready var panel_container = $Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer -@onready var margin_container = $Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer -@onready var patient_file = $Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/FileContainer/PatientFile -@onready var patient_file_title = $Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/FileContainer/HBoxContainer/PatientFileTitle -@onready var patient_recording = $Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/RecordingContainer/Recording -@onready var patient_recording_title = $Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/RecordingContainer/HBoxContainer/RecordTitle -@onready var face_zoom = $Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/FaceZoom -@onready var alone_controls = $Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Alone -@onready var patient_controls = $Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Patient -@onready var end_of_day_controls = $Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/EndOfDay -@onready var patient_texture = $Background/HBoxContainer/Desk/PatientTextureRect +#DeskAssets +@onready var patient_file = $LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs/FileContainer/PatientFile +@onready var patient_file_title = $LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs/FileContainer/PatientFileTitle +@onready var patient_recording = $LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs/Recording +@onready var patient = $UpperHalf/ControlGlass/Patient +@onready var next_patient = $LowerHalf/Computer/Margin/NextPatient +@onready var end_day = $LowerHalf/Computer/Margin/EndDay +@onready var green_button = $LowerHalf/GreenButton +@onready var red_button = $LowerHalf/RedButton +@onready var journal = $LowerHalf/Journal/JournalButton +@onready var journal_layer = $LowerHalf/JournalLayer +@onready var release = $LowerHalf/DecisionScreen/MarginContainer/HBoxContainer/Release +@onready var terminate = $LowerHalf/DecisionScreen/MarginContainer/HBoxContainer/Terminate +@onready var patient_color: Color +@onready var computer_tabs = $LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs +@onready var computer_screen = $LowerHalf/Computer/Margin/ComputerScreen2 +@onready var tab_patient_file = $LowerHalf/Computer/Margin/ComputerScreen/Tabs/PatientFile +@onready var tab_rules = $LowerHalf/Computer/Margin/ComputerScreen/Tabs/Rules +@onready var tab_recording = $LowerHalf/Computer/Margin/ComputerScreen/Tabs/Recording +@onready var middle_screen = $LowerHalf/MiddleScreen/MarginContainer/MiddleInfo +@onready var console_screen = $LowerHalf/ConsoleScreen/Margin/ConsoleText + +#RoomAssets +@onready var shutter_top = $RoomAssets/ShutterTop +@onready var shutter_bot = $RoomAssets/ShutterBot +@onready var shutters_animation = $RoomAssets/ShuttersAnimation +@onready var backroom = $RoomAssets/Backroom +@onready var red_backroom = $RoomAssets/RedBackroom +@onready var green_backroom = $RoomAssets/GreenBackroom + +#DialogueAssets @onready var patient_dialogue_resource: Resource @onready var end_of_day_dialogue_resource = load("res://Dialogues/end_of_day.dialogue") @onready var balloon: Node @onready var patient_emotions : Dictionary -signal recovery_pressed - var main: Node +var accurate_diagnoses = 0 -# Called when the node enters the scene tree for the first time. func _ready() -> void: #Referencing main node main = get_parent() #Connecting signals - alone_controls.get_node("PatientFile").pressed.connect(self._on_patient_file_pressed) - alone_controls.get_node("Guidebook").pressed.connect(self._on_guidebook_pressed) - alone_controls.get_node("CallNextPatient").pressed.connect(self._on_call_next_patient_pressed) + next_patient.get_node("CallNextPatient").pressed.connect(self._on_call_next_patient_pressed) + green_button.pressed.connect(self._on_green_button_pressed) + red_button.pressed.connect(self._on_red_button_pressed) + journal.pressed.connect(self._on_journal_pressed) + journal_layer.get_node("JournalOpen/JournalMargin/PageArea/LeftPage/CloseManual").pressed.connect(self._on_close_manual_pressed) + tab_patient_file.pressed.connect(self._on_tab_patient_file_pressed) + tab_rules.pressed.connect(self._on_tab_rules_pressed) + tab_recording.pressed.connect(self._on_tab_recording_pressed) + Globals.console_message.connect(self.console_display) + Globals.response_chosen.connect(self._on_response_chosen) - patient_controls.get_node("PatientFile").pressed.connect(self._on_patient_file_pressed) - patient_controls.get_node("Guidebook").pressed.connect(self._on_guidebook_pressed) - patient_controls.get_node("PatientRecording").pressed.connect(self._on_patient_recording_pressed) - patient_controls.get_node("Examine").pressed.connect(self._on_examine_pressed) - patient_controls.get_node("Containment").pressed.connect(self._on_containment_pressed) - patient_controls.get_node("PsychWard").pressed.connect(self._on_psych_ward_pressed) + #Hiding RoomAssets + for room_asset in [shutter_top, shutter_bot, backroom, red_backroom, green_backroom]: + room_asset.hide() - end_of_day_controls.get_node("Recovery").pressed.connect(self._on_recovery_pressed) - - #Hiding everything - self.hide() - #Hiding Top Right Panel - panel_container.hide() - for node in margin_container.get_children(): + #Hiding ComputerScreen, DialogueText and JournalOpen + var nodes_to_hide = [next_patient, end_day, computer_screen, console_screen, journal_layer] + computer_tabs.get_children() + for node in nodes_to_hide: node.hide() - face_zoom.hide() - #Hiding Bottom Right Panel - alone_controls.hide() - patient_controls.hide() - end_of_day_controls.hide() - #Hiding Patient - patient_texture.hide() + + #Setting up Decision screen + release.text = Globals.colorize("RELEASE", "web_gray") + terminate.text = Globals.colorize("TERMINATE", "web_gray") + + for tab_button in [tab_patient_file, tab_rules, tab_recording]: #Hiding Tabs + tab_button.hide() + for button in [green_button, red_button]: #Disabling Buttons + button.set_disabled(true) + + patient.hide() #Hiding Patient + self.hide() #Hiding ScreeningDesk - -#Sets Up New Patient -func new_patient(patient_id): - - #Loading Patient Files - var file_path: String = "res://Dialogues/Patients/patient_" + patient_id + "_file.txt" - var file = FileAccess.open(file_path, FileAccess.READ) - var file_content = file.get_as_text() - - patient_file.text = file_content - patient_file_title.text = "\nPATIENT FILE #" + patient_id - file.close() - - #Loading Patient Recording +func new_patient(): + Patient.new_patient() + patient_file.text = Patient.file_text + patient_file_title.text = Patient.file_title patient_recording.text = "" - patient_recording_title.text = "\nPATIENT RECORDING #" + patient_id + patient.texture = Patient.image + + +func _on_call_next_patient_pressed(): #Brings in next patient and sets up the desk controls - #Loading Patient Texture - file_path = "res://Art/Patients/patient_" + patient_id + "/patient_" + patient_id + "_full.png" - patient_texture.texture = load(file_path) + #Hiding the computer screen + var scenes_to_hide = [next_patient] + for scenes in scenes_to_hide: + scenes.hide() - #Loading Face Emotions - var face: Resource - var emotion_branch: Dictionary + #ShuttersIncoming + shutters_animation.play("incoming") + shutter_top.show() + shutter_bot.show() + await shutters_animation.animation_finished - for emotion in ["neutral", "smiling", "angry", "scared", "sad"]: - file_path = "res://Art/Patients/patient_" + patient_id + "/patient_" + patient_id + "_" + emotion + ".png" - face = load(file_path) - if face != null: - emotion_branch = {emotion: face} - patient_emotions.merge(emotion_branch) + #Showing the Patient and the Backroom + var scenes_to_show = [patient, backroom] + for scene in scenes_to_show: + scene.show() - #Loading Default Face - face_zoom.texture = patient_emotions["neutral"] + #Opening Shutters + shutters_animation.play("open") + await shutters_animation.animation_finished - #Loading Dialogue Resource - var dialogue_resource_path = "res://Dialogues/Patients/patient_" + patient_id + ".dialogue" - patient_dialogue_resource = load(dialogue_resource_path) - - - -#Show patient file -func _on_patient_file_pressed(): + #Activating Buttons + green_button.set_disabled(false) + red_button.set_disabled(false) - for node in margin_container.get_children(): - node.hide() - panel_container.show() - face_zoom.hide() - margin_container.get_node("FileContainer").show() - - - -#Show guidebook -func _on_guidebook_pressed(): + #Coloring Release and Terminate + release.text = Globals.colorize("RELEASE", "dark_green") + terminate.text = Globals.colorize("TERMINATE", "dark_red") - for node in margin_container.get_children(): - node.hide() - panel_container.show() - face_zoom.hide() - margin_container.get_node("Rules").show() - - - -#Brings in next patient -func _on_call_next_patient_pressed(): + #Starting Dialogue and hiding console screen + console_screen.hide() + main.start_balloon("test", Patient.dialogue_resource, "start") - #Hiding and Showing appropriate scenes - var scenes_to_hide = [alone_controls, panel_container] + margin_container.get_children() - var scenes_to_show = [patient_controls,patient_texture,face_zoom] - TransitionScreen.transition("big", scenes_to_hide, scenes_to_show) - await TransitionScreen.transition_finished - - #Starting Dialogue - main.start_balloon("small", patient_dialogue_resource, "start") + #Showing Computer Tabs and showing Patient File by default + for node in [tab_patient_file, tab_rules, tab_recording]: + node.show() + node.set_mouse_filter(Control.MOUSE_FILTER_STOP) + computer_tabs.get_node("FileContainer").show() - -#Shows patient recording -func _on_patient_recording_pressed(): - - for node in margin_container.get_children(): - node.hide() - panel_container.show() - face_zoom.hide() - margin_container.get_node("RecordingContainer").show() - - - -#Records the dialogues -func _recording_text(dialogue_line): +func _recording_text(dialogue_line): #Records the dialogues var line = dialogue_line.text var character = dialogue_line.character - var character_colour : String - + patient_recording.append_text("\n\n") - - #Adjusting Character Colour - if character != "": - if character == "You": - character_colour = "[color=darkgreen]" - else : - character_colour = "[color=darkred]" - patient_recording.append_text(character_colour + character + "[/color]\n") - + patient_recording.append_text("[color=red]" + character + "[/color]" + "\n") patient_recording.append_text(line) - -#Show face zoom -func _on_examine_pressed(): +func _on_response_chosen(response): #Adds protagonist responses to the recording tab - for node in margin_container.get_children(): + patient_recording.append_text("\n\n") + patient_recording.append_text("[color=green]You[/color]" + "\n") + patient_recording.append_text(response.text) + + +func _on_tab_patient_file_pressed(): #Shows the patient file tab + + #Highlighting pressed tab button and unpressing the others + tab_patient_file.set_pressed(true) + tab_patient_file.set_mouse_filter(Control.MOUSE_FILTER_IGNORE) + for tab_button in [tab_rules, tab_recording]: + tab_button.set_pressed(false) + tab_button.set_mouse_filter(Control.MOUSE_FILTER_STOP) + + #Showing desired tab and hiding the others + for node in computer_tabs.get_children(): node.hide() - panel_container.hide() - face_zoom.show() + computer_tabs.get_node("FileContainer").show() +func _on_tab_rules_pressed(): #Shows the rules tab + + #Highlighting pressed tab button and unpressing the others + tab_rules.set_pressed(true) + tab_rules.set_mouse_filter(Control.MOUSE_FILTER_IGNORE) + for tab_button in [tab_patient_file, tab_recording]: + tab_button.set_pressed(false) + tab_button.set_mouse_filter(Control.MOUSE_FILTER_STOP) + + #Showing desired tab and hiding the others + for node in computer_tabs.get_children(): + node.hide() + computer_tabs.get_node("Rules").show() -#Change emotions following dialogue tags -func _change_emotion(dialogue_line) -> void: + +func _on_tab_recording_pressed(): #Shows the recording tab + + #Highlighting pressed tab button and unpressing the others + tab_recording.set_pressed(true) + tab_recording.set_mouse_filter(Control.MOUSE_FILTER_IGNORE) + for tab_button in [tab_patient_file, tab_rules]: + tab_button.set_pressed(false) + tab_button.set_mouse_filter(Control.MOUSE_FILTER_STOP) + + #Showing desired tab and hiding the others + for node in computer_tabs.get_children(): + node.hide() + computer_tabs.get_node("Recording").show() + + +func _change_emotion(dialogue_line) -> void: #Change emotions following dialogue tags var line_tag = dialogue_line.tags - + if line_tag.is_empty() == false: var new_emotion = line_tag[0] + patient.texture = Patient.emotions[String(new_emotion)] print("Emotion changed to ", new_emotion) - face_zoom.texture = patient_emotions[new_emotion] - -func _on_containment_pressed(): +func _on_green_button_pressed(): #Releases the patient - #Ask for confirmation - var lambda = func(): clear_patient("containment") - var choice_text = "[center]Do you really want to send this patient to [color=darkred]Containment[/color] ?[/center]" + release.text = Globals.colorize("RELEASE", "green") #Highlighting the decision - UI.ask_confirmation(lambda, Callable(), choice_text) - - - -func _on_psych_ward_pressed(): + #Greening the vibe + green_backroom.show() + patient.modulate = Color(0.4,1,0.4,1) - #Ask for confirmation - var lambda = func(): clear_patient("psych_ward") - var choice_text = "[center]Do you really want to send this patient to the [color=darkgreen]Psych Ward[/color] ?[/center]" - - UI.ask_confirmation(lambda, Callable(), choice_text) + clear_patient("release") #Clearing the patient +func _on_red_button_pressed(): #Terminates the patient + + terminate.text = Globals.colorize("TERMINATE", "red") #Highlighting the decision + + #Redding the vibe + red_backroom.show() + patient.modulate = Color(1,0,0,1) + + clear_patient("terminate") #Clearing the patient -#Clears patient and transitions towards next patient or end of day -func clear_patient(choice:String): + +func _on_journal_pressed(): #Shows the journal + journal_layer.show() + + +func _on_close_manual_pressed(): #Hides the journal + journal_layer.hide() + + +func clear_patient(choice:String): #Clears patient and transitions towards next patient or end of day - #Hiding Patient Controls and showing Patient Face - patient_controls.hide() - _on_examine_pressed() + #Hiding computer tabs screen and console screen + for node in [tab_patient_file, tab_rules, tab_recording, console_screen] + computer_tabs.get_children(): + node.hide() + + #Disabling Buttons + green_button.set_mouse_filter(Control.MOUSE_FILTER_IGNORE) + red_button.set_mouse_filter(Control.MOUSE_FILTER_IGNORE) + #Setting up computer and console messages + var message: Array[String] + message.resize(3) + if choice == "release": + message[0] = Globals.colorize("- ASSESSMENT MADE -\n\n- LOADING RELEASE PROTOCOL... -", "green_yellow") + message[1] = Globals.colorize("- RELEASING SUBJECT -", "green_yellow") + message[2] = "EVALUATION RECORDED" + "\n\nPATIENT STATUS: " + Globals.colorize("HUMAN", "green") + "\n\nUPDATING DATABASE...\n\n" + Globals.colorize("INITIATING RELEASE SEQUENCE", "green_yellow") + elif choice == "terminate": + message[0] = Globals.colorize("- ASSESSMENT MADE -\n\n- LOADING TERMINATION PROTOCOL... -", "orange_red") + message[1] = Globals.colorize("- TERMINATING SUBJECT -", "orange_red") + message[2] = "EVALUATION RECORDED" + "\n\nPATIENT STATUS: " + Globals.colorize("ABNORMAL", "red") + "\n\nUPDATING DATABASE...\n\n" + Globals.colorize("INITIATING TERMINATION SEQUENCE", "orange_red") + console_display(message[0]) + computer_display(message[2]) + + #Final Patient Dialogue - main.start_balloon("small", patient_dialogue_resource, choice) + main.start_balloon("test", Patient.dialogue_resource, choice) await DialogueManager.dialogue_ended - #Transitioning - var scenes_to_hide = [patient_controls, patient_texture, face_zoom, panel_container] + margin_container.get_children() - var scenes_to_show:Array[Node] + #Updating middle screen info and accurate_diagnoses value + if (Patient.abnormal == true and choice == "terminate") or (Patient.abnormal == false and choice == "release"): + accurate_diagnoses += 1 + update_middle_screen(Globals.day_count, Globals.floor_count, accurate_diagnoses, Globals.diagnoses_quota) + #Showing second console message + console_display(message[1]) + + #Closing Shutters + shutters_animation.play("close") + await shutters_animation.animation_finished + + #Hiding the cell and restoring the patient's original color + for scene in [patient, backroom, red_backroom, green_backroom]: + scene.hide() + patient.modulate = Patient.original_color + + #Hiding Computer screen and resetting Console screen once the animation is done + shutters_animation.play(choice) + await shutters_animation.animation_finished + console_display(Globals.directives) + computer_screen.hide() + + #Updating patient count Globals.patient_count += 1 - if Globals.patient_count < Globals.daily_patient_limit: - scenes_to_show.append(alone_controls) + if Globals.patient_count <= Globals.daily_patient_limit: + print("daily_limit not reached") else: - scenes_to_show.append(end_of_day_controls) + print("daily_limit reached") - TransitionScreen.transition("big", scenes_to_hide, scenes_to_show) - await TransitionScreen.transition_finished - - - if Globals.patient_count < Globals.daily_patient_limit: + if Globals.patient_count <= Globals.daily_patient_limit: #Setting Up Next Patient - new_patient("564") + new_patient() + next_patient.show() else: Globals.patient_count = 1 - - #Starting End of Day Dialogue - var start_title = "end_of_day_" + str(Globals.day_count) - main.start_balloon("small", end_of_day_dialogue_resource, start_title) - - - -func _on_recovery_pressed(): + end_day.show() - main.clear_balloon() - recovery_pressed.emit() + #Reenabling mouse filter and resetting buttons + for button in [green_button, red_button]: + button.set_mouse_filter(Control.MOUSE_FILTER_STOP) + button.set_pressed(false) + button.set_disabled(true) + + #Resetting decision screen + release.text = Globals.colorize("RELEASE", "web_gray") + terminate.text = Globals.colorize("TERMINATE", "web_gray") + + +func console_display(message): #Displays a message on the console screen + var text = message + text = text.replace("\\n", "\n") #Replaces litteral backslashes by actual newlines (necessary because of Dialogue Manager) + console_screen.text = text + console_screen.show() + + +func computer_display(message): #Displays a message on the computer screen + var text = message + text = text.replace("\\n", "\n") #Replaces litteral backslashes by actual newlines (necessary because of Dialogue Manager) + computer_screen.text = text + computer_screen.show() + +func update_middle_screen(day_count, floor_count, diagnoses_count, diagnoses_quota): #Updates Middle Screen Info + var day = Globals.colorize(str(day_count), "dark_orange") + var floor = Globals.colorize(str(floor_count), "dark_orange") + var diagnoses = Globals.colorize(str(diagnoses_count) + "/" + str(diagnoses_quota), "dark_orange") + + var text = "DAY: " + day + "\n\nFLOOR: " + floor + "\n\nACCURATE DIAGNOSES: " + diagnoses + + middle_screen.text = text diff --git a/screening_desk.gd.uid b/screening_desk.gd.uid new file mode 100644 index 0000000..d124ab5 --- /dev/null +++ b/screening_desk.gd.uid @@ -0,0 +1 @@ +uid://b3ms0imu0umn8 diff --git a/screening_desk.tscn b/screening_desk.tscn index a096731..9ea22df 100644 --- a/screening_desk.tscn +++ b/screening_desk.tscn @@ -1,17 +1,313 @@ -[gd_scene load_steps=7 format=3 uid="uid://chytk2cctjk3x"] +[gd_scene load_steps=53 format=3 uid="uid://chytk2cctjk3x"] -[ext_resource type="Script" path="res://screening_desk.gd" id="1_1s7k3"] -[ext_resource type="Texture2D" uid="uid://c3je4gir1qlot" path="res://Art/BlackScreen.png" id="1_r2ji1"] -[ext_resource type="Texture2D" uid="uid://dc2nmwcscjig4" path="res://Art/ScreeningDesk.png" id="3_xhvwd"] -[ext_resource type="FontFile" uid="uid://ckg5x0oonnjtn" path="res://Art/XanhMono-Regular.ttf" id="4_uvrsh"] -[ext_resource type="FontFile" uid="uid://mi5v1uq78g7o" path="res://Art/XanhMono-Italic.ttf" id="5_sybh5"] +[ext_resource type="Script" uid="uid://b3ms0imu0umn8" path="res://screening_desk.gd" id="1_1s7k3"] +[ext_resource type="Texture2D" uid="uid://1lm8p2lk2u5c" path="res://Art/Patients/patient_48/patient_48_neutral.png" id="3_63o5x"] +[ext_resource type="Texture2D" uid="uid://b2epvjeqvmgrs" path="res://Art/Rooms/screening_room/screening_room_glass.png" id="3_nxnrp"] +[ext_resource type="Texture2D" uid="uid://by34irl73n8pb" path="res://Art/Rooms/screening_room/backroom.png" id="4_fij8w"] +[ext_resource type="Texture2D" uid="uid://ds1whnmwt7xjt" path="res://Art/Rooms/screening_room/screening_room.png" id="5_aa8r5"] +[ext_resource type="Texture2D" uid="uid://4qtc0tkq2wba" path="res://Art/Rooms/screening_room/shutter_top.png" id="6_1rj24"] +[ext_resource type="Texture2D" uid="uid://cci0pb12chlps" path="res://Art/Rooms/screening_room/computer.png" id="6_6a8ar"] +[ext_resource type="Texture2D" uid="uid://dj1x60fld5400" path="res://Art/Rooms/screening_room/backwall.png" id="6_5772r"] +[ext_resource type="Texture2D" uid="uid://c3je4gir1qlot" path="res://Art/BlackScreen.png" id="7_106l6"] +[ext_resource type="Texture2D" uid="uid://jychpkygmjib" path="res://Art/Rooms/screening_room/desk.png" id="7_dp545"] +[ext_resource type="Texture2D" uid="uid://diln53wxa3oep" path="res://Art/Rooms/screening_room/journal_open.png" id="7_eqiuk"] +[ext_resource type="Texture2D" uid="uid://cdbiwtt2w37kr" path="res://Art/Rooms/screening_room/shutter_bottom.png" id="7_feog2"] +[ext_resource type="Theme" uid="uid://3k7sqw211ncl" path="res://Art/UI/button_theme.tres" id="8_hxrv4"] +[ext_resource type="Texture2D" uid="uid://c3frxq4vyu0pr" path="res://Art/Rooms/screening_room/console_screen.png" id="9_68gp8"] +[ext_resource type="BitMap" uid="uid://b1y07k0opbyju" path="res://Art/Rooms/screening_room/button_bitmap.png" id="10_hxph5"] +[ext_resource type="Texture2D" uid="uid://bhqx2kjgn1irf" path="res://Art/Rooms/screening_room/green_button.png" id="10_tmo4o"] +[ext_resource type="Texture2D" uid="uid://bagw75cp302xk" path="res://Art/Rooms/screening_room/green_button_pressed.png" id="11_weq72"] +[ext_resource type="Texture2D" uid="uid://cqn1o6cff1g4k" path="res://Art/Rooms/screening_room/red_button.png" id="12_7mvev"] +[ext_resource type="Texture2D" uid="uid://dja217ihoi5rb" path="res://Art/Rooms/screening_room/green_button_hover.png" id="12_rpmuh"] +[ext_resource type="Texture2D" uid="uid://kfq5b58sicqf" path="res://Art/Rooms/screening_room/red_button_pressed.png" id="13_lq4cy"] +[ext_resource type="FontFile" uid="uid://dv8ppra5tgn1t" path="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" id="14_0pvwy"] +[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="15_l8po4"] +[ext_resource type="Texture2D" uid="uid://c35f0aquvroje" path="res://Art/Rooms/screening_room/red_button_hover.png" id="15_pbvyq"] +[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="16_16ney"] +[ext_resource type="Texture2D" uid="uid://4tgfgi4earv1" path="res://Art/Rooms/screening_room/journal_unpressed.png" id="17_7o31y"] +[ext_resource type="Texture2D" uid="uid://djhhkejop4636" path="res://Art/Rooms/screening_room/tea_cup.png" id="17_son5s"] +[ext_resource type="Texture2D" uid="uid://1djtmkdqtplt" path="res://Art/Rooms/screening_room/journal_pressed.png" id="18_gv516"] +[ext_resource type="Texture2D" uid="uid://b7tbgcyxjj516" path="res://Art/Rooms/screening_room/journal_hover.png" id="19_ar3nk"] +[ext_resource type="BitMap" uid="uid://byqmvpivmvsnl" path="res://Art/Rooms/screening_room/journal_bitmap.png" id="20_5vhu5"] +[ext_resource type="Texture2D" uid="uid://b21h7s3820sc8" path="res://Art/Rooms/screening_room/middle_screen.png" id="24_d8gyq"] +[ext_resource type="Texture2D" uid="uid://c6ary7qg7jhc6" path="res://Art/Rooms/screening_room/decision_screen.png" id="29_cobue"] +[ext_resource type="Texture2D" uid="uid://cg2mywrvfpvad" path="res://Art/Rooms/screening_room/green_backroom.png" id="29_qdgqn"] +[ext_resource type="Texture2D" uid="uid://cs4ofocaj6xdg" path="res://Art/Rooms/screening_room/red_backroom.png" id="30_r58th"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1bdh8"] +[sub_resource type="Theme" id="Theme_63o5x"] +RichTextLabel/fonts/normal_font = ExtResource("14_0pvwy") + +[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"] +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_vou70"] +bg_color = Color(0.6, 0.6, 0.6, 0) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +expand_margin_bottom = 6.0 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4ait8"] +bg_color = Color(0, 0, 0, 1) + +[sub_resource type="Theme" id="Theme_kfxnd"] +default_font_size = 20 +Button/constants/outline_size = 3 +Button/styles/disabled = SubResource("StyleBoxFlat_spyqn") +Button/styles/focus = SubResource("StyleBoxFlat_ri4m3") +Button/styles/hover = SubResource("StyleBoxFlat_e0njw") +Button/styles/normal = SubResource("StyleBoxFlat_vou70") +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_4ait8") + +[sub_resource type="Theme" id="Theme_dahej"] +default_font = ExtResource("14_0pvwy") +RichTextLabel/colors/default_color = Color(0, 0, 0, 1) +RichTextLabel/fonts/normal_font = ExtResource("14_0pvwy") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3lob0"] +bg_color = Color(0.843137, 0.843137, 0.843137, 1) border_width_left = 2 border_width_top = 2 border_width_right = 2 border_width_bottom = 2 +border_color = Color(0, 0, 0, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wj8aa"] +bg_color = Color(0.784314, 0.784314, 0.784314, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0, 0, 0, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5jbc5"] +bg_color = Color(0.901961, 0.901961, 0.901961, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0, 0, 0, 1) + +[sub_resource type="Theme" id="Theme_gwogn"] +Button/colors/font_color = Color(0, 0, 0, 1) +Button/fonts/font = ExtResource("14_0pvwy") +Button/styles/hover = SubResource("StyleBoxFlat_3lob0") +Button/styles/normal = SubResource("StyleBoxFlat_wj8aa") +Button/styles/pressed = SubResource("StyleBoxFlat_5jbc5") + +[sub_resource type="Animation" id="Animation_hai4a"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(60, 50)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(-2540, -540)] +} + +[sub_resource type="Animation" id="Animation_l4uk7"] +resource_name = "going_up" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.5, 1), +"update": 0, +"values": [Vector2(-240, 60), Vector2(-240, -540)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.5, 1), +"update": 0, +"values": [Vector2(-240, -1040), Vector2(-240, -540)] +} + +[sub_resource type="Animation" id="Animation_hb6p3"] +resource_name = "incoming" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.4, 1), +"update": 0, +"values": [Vector2(-2540, -540), Vector2(-240, -540)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(0.4, 1), +"update": 0, +"values": [Vector2(-2540, -540), Vector2(-240, -540)] +} + +[sub_resource type="Animation" id="Animation_el1v6"] +resource_name = "going_down" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -60)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("../ShutterTop:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -1140)] +} + +[sub_resource type="Animation" id="Animation_4lwtm"] +resource_name = "going_up" +length = 3.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("../ShutterTop:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 3), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -3000)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 3), +"transitions": PackedFloat32Array(3, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, -3000)] +} + +[sub_resource type="Animation" id="Animation_hebek"] +resource_name = "going_down" +length = 2.0 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("../ShutterTop:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(10, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, 4000)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:position") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 2), +"transitions": PackedFloat32Array(10, 1), +"update": 0, +"values": [Vector2(-240, -540), Vector2(-240, 4000)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_vh6fv"] +_data = { +&"RESET": SubResource("Animation_hai4a"), +&"close": SubResource("Animation_l4uk7"), +&"incoming": SubResource("Animation_hb6p3"), +&"open": SubResource("Animation_el1v6"), +&"release": SubResource("Animation_4lwtm"), +&"terminate": SubResource("Animation_hebek") +} [node name="ScreeningDesk" type="Control"] layout_mode = 3 @@ -21,9 +317,72 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 +theme = SubResource("Theme_63o5x") script = ExtResource("1_1s7k3") -[node name="Background" type="VBoxContainer" parent="."] +[node name="UpperHalf" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_bottom = -396.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + +[node name="ControlGlass" type="Control" parent="UpperHalf"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 76.0 +offset_top = 48.0 +offset_right = -76.0 +offset_bottom = -28.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Patient" type="TextureRect" parent="UpperHalf/ControlGlass"] +z_index = 2 +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -300.0 +offset_top = -450.0 +offset_right = 300.0 +grow_horizontal = 2 +grow_vertical = 0 +texture = ExtResource("3_63o5x") +stretch_mode = 3 + +[node name="Balloon" type="Control" parent="."] +unique_name_in_owner = true +visible = false +z_index = 6 +anchors_preset = 0 +offset_left = 1267.0 +offset_top = 730.0 +offset_right = 1267.0 +offset_bottom = 730.0 +size_flags_horizontal = 0 +size_flags_vertical = 0 +mouse_filter = 2 +mouse_force_pass_scroll_events = false +theme = SubResource("Theme_kfxnd") + +[node name="Panel" type="Panel" parent="Balloon"] +clip_children = 2 +layout_mode = 1 +offset_left = -11.0 +offset_top = -9.0 +offset_right = 617.0 +offset_bottom = 186.0 +mouse_force_pass_scroll_events = false + +[node name="Dialogue" type="MarginContainer" parent="Balloon/Panel"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -31,285 +390,747 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 -theme_override_constants/separation = 0 -[node name="UpperBorder" type="TextureRect" parent="Background"] +[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("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = "Character" +fit_content = true +scroll_active = false + +[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/VBoxContainer" instance=ExtResource("15_l8po4")] layout_mode = 2 size_flags_vertical = 3 -size_flags_stretch_ratio = 0.1 -texture = ExtResource("1_r2ji1") -expand_mode = 1 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 18 +theme_override_font_sizes/bold_font_size = 18 +theme_override_font_sizes/bold_italics_font_size = 18 +theme_override_font_sizes/italics_font_size = 18 +theme_override_font_sizes/mono_font_size = 18 +text = "Dialogue..." -[node name="HBoxContainer" type="HBoxContainer" parent="Background"] +[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 = -11.0 +offset_top = 186.0 +offset_right = 616.0 +offset_bottom = 326.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("16_16ney") +response_template = NodePath("ResponseExample") + +[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"] +layout_mode = 2 +theme_override_fonts/font = ExtResource("14_0pvwy") +text = "Response example" + +[node name="LowerHalf" type="Control" parent="."] +z_index = 6 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 684.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="DeskTexture" type="TextureRect" parent="LowerHalf"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("7_dp545") + +[node name="Computer" type="Control" parent="LowerHalf"] +anchors_preset = 0 +offset_left = 8.0 +offset_top = 12.0 +offset_right = 736.0 +offset_bottom = 312.0 + +[node name="ComputerTexture" type="TextureRect" parent="LowerHalf/Computer"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("6_6a8ar") +stretch_mode = 2 + +[node name="Margin" type="MarginContainer" parent="LowerHalf/Computer"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 24 +theme_override_constants/margin_top = 24 +theme_override_constants/margin_right = 32 +theme_override_constants/margin_bottom = 24 + +[node name="ComputerScreen" type="VBoxContainer" parent="LowerHalf/Computer/Margin"] +layout_mode = 2 + +[node name="Tabs" type="HBoxContainer" parent="LowerHalf/Computer/Margin/ComputerScreen"] layout_mode = 2 size_flags_vertical = 3 -size_flags_stretch_ratio = 1.5 -mouse_filter = 2 -theme_override_constants/separation = 0 +size_flags_stretch_ratio = 0.12 -[node name="Desk" type="TextureRect" parent="Background/HBoxContainer"] +[node name="PatientFile" type="Button" parent="LowerHalf/Computer/Margin/ComputerScreen/Tabs"] layout_mode = 2 size_flags_horizontal = 3 -size_flags_vertical = 3 -size_flags_stretch_ratio = 2.0 -mouse_filter = 2 -texture = ExtResource("3_xhvwd") -expand_mode = 1 +theme = ExtResource("8_hxrv4") +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 16 +toggle_mode = true +text = "PATIENT FILE" -[node name="PatientTextureRect" type="TextureRect" parent="Background/HBoxContainer/Desk"] -layout_mode = 0 -offset_left = 491.0 -offset_top = 314.0 -offset_right = 771.0 -offset_bottom = 632.0 -expand_mode = 1 - -[node name="MarginContainer" type="MarginContainer" parent="Background/HBoxContainer"] +[node name="Rules" type="Button" parent="LowerHalf/Computer/Margin/ComputerScreen/Tabs"] layout_mode = 2 size_flags_horizontal = 3 +theme = ExtResource("8_hxrv4") +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 16 +toggle_mode = true +text = "RULES" -[node name="BackgroundColorRect" type="ColorRect" parent="Background/HBoxContainer/MarginContainer"] +[node name="Recording" type="Button" parent="LowerHalf/Computer/Margin/ComputerScreen/Tabs"] layout_mode = 2 size_flags_horizontal = 3 -size_flags_vertical = 3 -color = Color(0, 0, 0, 1) +theme = ExtResource("8_hxrv4") +theme_override_fonts/font = ExtResource("14_0pvwy") +theme_override_font_sizes/font_size = 16 +toggle_mode = true +text = "RECORDING" -[node name="VBoxContainer" type="VBoxContainer" parent="Background/HBoxContainer/MarginContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_constants/separation = 0 - -[node name="FaceMarginContainer" type="MarginContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer"] +[node name="ComputerTabs" type="MarginContainer" parent="LowerHalf/Computer/Margin/ComputerScreen"] layout_mode = 2 size_flags_vertical = 3 -size_flags_stretch_ratio = 3.0 -theme_override_constants/margin_left = 10 -theme_override_constants/margin_top = 0 -theme_override_constants/margin_right = 10 -theme_override_constants/margin_bottom = 0 +theme_override_constants/margin_left = 4 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 4 +theme_override_constants/margin_bottom = 4 -[node name="PanelContainer" type="PanelContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer"] +[node name="FileContainer" type="VBoxContainer" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs"] +visible = false layout_mode = 2 -size_flags_vertical = 3 -size_flags_stretch_ratio = 3.0 -theme_override_styles/panel = SubResource("StyleBoxFlat_1bdh8") +theme_override_constants/separation = -4 -[node name="MarginContainer" type="MarginContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer"] -layout_mode = 2 -theme_override_constants/margin_left = 30 -theme_override_constants/margin_top = 30 -theme_override_constants/margin_right = 30 -theme_override_constants/margin_bottom = 30 - -[node name="FileContainer" type="VBoxContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer"] -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/FileContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="Control" type="Control" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/FileContainer/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="PatientFileTitle" type="RichTextLabel" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/FileContainer/HBoxContainer"] +[node name="PatientFileTitle" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs/FileContainer"] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 size_flags_stretch_ratio = 0.9 -theme_override_fonts/normal_font = ExtResource("4_uvrsh") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true -text = " +text = "PATIENT #564" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="PatientFile" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs/FileContainer"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 6.0 +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = "AGE: 29 + +NAME: Gwendoline Gorbado + +PROFESSION: IT Engineer + +PRE-SCREENING PSYCHIATRIC ASSESSMENT: + +[color=dark_orange]DEPRESSION + +ANXIETY[/color] " +vertical_alignment = 1 -[node name="Control2" type="Control" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/FileContainer/HBoxContainer"] +[node name="Rules" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs"] +z_index = 1 layout_mode = 2 -size_flags_horizontal = 3 +theme_override_constants/line_separation = 5 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_fonts/italics_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 +theme_override_font_sizes/italics_font_size = 16 +bbcode_enabled = true +text = " 1- Pre-screening psychiatric assessment identified [color=dark_orange]SYMPTOMS[/color] in every patient. -[node name="PatientFile" type="RichTextLabel" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/FileContainer"] + 2- If [color=dark_orange]SYMPTOMS[/color] not included in the pre-screening assessment are ascertained, [color=red]TERMINATE[/color] the patient, otherwise use the [color=green]RELEASE[/color] button. + + 3- Please refer to the [color=royal_blue]MANUAL[/color] on the desk for more information on [color=dark_orange]SYMPTOMS[/color]." +horizontal_alignment = 3 +vertical_alignment = 1 + +[node name="Recording" type="RichTextLabel" parent="LowerHalf/Computer/Margin/ComputerScreen/ComputerTabs"] +visible = false layout_mode = 2 size_flags_vertical = 3 size_flags_stretch_ratio = 6.0 -theme_override_fonts/normal_font = ExtResource("4_uvrsh") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true +vertical_alignment = 1 -[node name="Rules" type="RichTextLabel" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer"] +[node name="NextPatient" type="PanelContainer" parent="LowerHalf/Computer/Margin"] +visible = false +custom_minimum_size = Vector2(400, 100) layout_mode = 2 -theme_override_fonts/normal_font = ExtResource("4_uvrsh") -theme_override_fonts/italics_font = ExtResource("5_sybh5") -theme_override_font_sizes/italics_font_size = 20 -theme_override_font_sizes/normal_font_size = 20 -bbcode_enabled = true -text = " - ABNORMALITY RULES +size_flags_horizontal = 4 +size_flags_vertical = 4 - - - 1- Patients may either be [color=darkgreen]Commons[/color] or [color=darkred]Abnormals[/color]. - - 2- Abnormals [color=darkred]violate the laws of Reality[/color]. - - 3- Abnormals utterly lack [color=darkgreen]emotional faculties[/color]. - - 4- Abnormals [color=darkred]mimic[/color] Commons' emotions and actions. - - 5- Follow your [color=darkgreen]instinct[/color]. - - 6- Send Commons to the [color=darkgreen]Psych Ward[/color]. - - 7- Send Abnormals to [color=darkred]Containment[/color]. - - - - - - - [i]THE REPUBLIC SHELTERS[/i]" - -[node name="RecordingContainer" type="VBoxContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer"] -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/RecordingContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="Control" type="Control" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/RecordingContainer/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="RecordTitle" type="RichTextLabel" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/RecordingContainer/HBoxContainer"] +[node name="CallNextPatient" type="Button" parent="LowerHalf/Computer/Margin/NextPatient"] +z_index = 1 layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 -size_flags_stretch_ratio = 1.4 -theme_override_fonts/normal_font = ExtResource("4_uvrsh") -theme_override_font_sizes/normal_font_size = 20 -bbcode_enabled = true - -[node name="Control2" type="Control" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/RecordingContainer/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="Recording" type="RichTextLabel" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer/PanelContainer/MarginContainer/RecordingContainer"] -layout_mode = 2 -size_flags_vertical = 3 -size_flags_stretch_ratio = 6.0 -theme_override_fonts/normal_font = ExtResource("4_uvrsh") -theme_override_font_sizes/normal_font_size = 20 -bbcode_enabled = true - -[node name="FaceZoom" type="TextureRect" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/FaceMarginContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -size_flags_stretch_ratio = 2.0 -expand_mode = 1 - -[node name="ControlsMarginContainer" type="MarginContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 -theme_override_constants/margin_left = 10 -theme_override_constants/margin_top = 10 -theme_override_constants/margin_right = 10 -theme_override_constants/margin_bottom = 10 - -[node name="Alone" type="GridContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer"] -layout_mode = 2 -columns = 2 - -[node name="PatientFile" type="Button" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Alone"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("4_uvrsh") -theme_override_font_sizes/font_size = 25 -text = "NEXT PATIENT FILE" - -[node name="Guidebook" type="Button" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Alone"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("4_uvrsh") -theme_override_font_sizes/font_size = 25 -text = "GUIDEBOOK" - -[node name="CallNextPatient" type="Button" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Alone"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("4_uvrsh") +theme_override_fonts/font = ExtResource("14_0pvwy") theme_override_font_sizes/font_size = 25 text = "CALL NEXT PATIENT" -[node name="EndOfDay" type="GridContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer"] +[node name="EndDay" type="PanelContainer" parent="LowerHalf/Computer/Margin"] +visible = false +custom_minimum_size = Vector2(200, 100) layout_mode = 2 -columns = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 -[node name="Recovery" type="Button" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/EndOfDay"] +[node name="EndDay" type="Button" parent="LowerHalf/Computer/Margin/EndDay"] +z_index = 1 layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("4_uvrsh") +theme_override_fonts/font = ExtResource("14_0pvwy") theme_override_font_sizes/font_size = 25 -text = "GO TO RECOVERY ROOM" +text = "END DAY" -[node name="Patient" type="GridContainer" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer"] +[node name="ComputerScreen2" type="RichTextLabel" parent="LowerHalf/Computer/Margin"] +visible = false layout_mode = 2 -columns = 2 +bbcode_enabled = true +text = "PATIENT #49 -[node name="Examine" type="Button" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Patient"] +ASSESSMENT: [color=red]ABNORMAL[/color] + +[color=orange_red]LAUNCHING TERMINATION PROTOCOL" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ConsoleScreen" type="Control" parent="LowerHalf"] +z_index = 1 +anchors_preset = 0 +offset_left = 1192.0 +offset_top = 12.0 +offset_right = 1912.0 +offset_bottom = 392.0 + +[node name="ConsoleScreen" type="TextureRect" parent="LowerHalf/ConsoleScreen"] +layout_mode = 0 +offset_right = 676.0 +offset_bottom = 376.0 +texture = ExtResource("9_68gp8") +stretch_mode = 2 + +[node name="Margin" type="MarginContainer" parent="LowerHalf/ConsoleScreen"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 28 +theme_override_constants/margin_top = 28 +theme_override_constants/margin_right = 36 +theme_override_constants/margin_bottom = 28 + +[node name="ConsoleText" type="RichTextLabel" parent="LowerHalf/ConsoleScreen/Margin"] +custom_minimum_size = Vector2(600, 200) +layout_mode = 2 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 20 +bbcode_enabled = true +text = "[center][font_size=22]INSTRUCTIONS[/font_size][/center] + + +[color=forest_green]RELEASE[/color] HUMANS, [color=red]TERMINATE[/color] ABNORMALS + +IF A SUBJECT PRESENTS [color=dark_orange]SYMPTOMS[/color] UNDETECTED BY PRE-SCREENING, [color=red]IT IS NOT HUMAN[/color] + +REFER TO THE [color=royal_blue]MANUAL[/color] ON THE DESK FOR MORE INFORMATION ON [color=dark_orange]SYMPTOMS[/color] + +MAKE ENOUGH [color=green_yellow]ACCURATE DIAGNOSES[/color] IN A ROW TO GET TO THE NEXT FLOOR" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="JournalLayer" type="CanvasLayer" parent="LowerHalf"] +layer = 110 +visible = false + +[node name="JournalOpen" type="Control" parent="LowerHalf/JournalLayer"] +z_index = 8 +layout_mode = 3 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -800.0 +offset_top = -800.0 +offset_right = 800.0 +grow_horizontal = 2 +grow_vertical = 0 +theme = SubResource("Theme_dahej") + +[node name="Shader" type="TextureRect" parent="LowerHalf/JournalLayer/JournalOpen"] +modulate = Color(1, 1, 1, 0.52549) +z_index = 2 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -221.0 +offset_top = -356.0 +offset_right = 198.0 +offset_bottom = 44.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("7_106l6") + +[node name="JournalOpenTexture" type="TextureRect" parent="LowerHalf/JournalLayer/JournalOpen"] +z_index = 2 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("7_eqiuk") + +[node name="JournalMargin" type="MarginContainer" parent="LowerHalf/JournalLayer/JournalOpen"] +z_index = 2 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 70 +theme_override_constants/margin_top = 30 +theme_override_constants/margin_right = 70 +theme_override_constants/margin_bottom = 50 + +[node name="PageArea" type="HBoxContainer" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin"] +layout_mode = 2 +theme_override_constants/separation = 90 + +[node name="LeftPage" type="VBoxContainer" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea"] layout_mode = 2 size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("4_uvrsh") -theme_override_font_sizes/font_size = 25 -text = "EXAMINE" -[node name="Guidebook" type="Button" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Patient"] +[node name="PageTitle" type="RichTextLabel" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/LeftPage"] +custom_minimum_size = Vector2(0, 100) layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("4_uvrsh") -theme_override_font_sizes/font_size = 25 -text = "GUIDEBOOK" - -[node name="PatientFile" type="Button" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Patient"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("4_uvrsh") -theme_override_font_sizes/font_size = 25 -text = "PATIENT FILE" - -[node name="PatientRecording" type="Button" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Patient"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("4_uvrsh") -theme_override_font_sizes/font_size = 25 -text = "PATIENT RECORDING" - -[node name="Containment" type="Button" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Patient"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("4_uvrsh") -theme_override_font_sizes/font_size = 25 -text = "SEND TO CONTAINMENT" - -[node name="PsychWard" type="Button" parent="Background/HBoxContainer/MarginContainer/VBoxContainer/ControlsMarginContainer/Patient"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("4_uvrsh") -theme_override_font_sizes/font_size = 25 -text = "SEND TO PSYCH WARD" - -[node name="LowerBorder" type="TextureRect" parent="Background"] -layout_mode = 2 -size_flags_vertical = 3 size_flags_stretch_ratio = 0.1 -texture = ExtResource("1_r2ji1") +theme_override_font_sizes/normal_font_size = 28 +bbcode_enabled = true +text = " +[center]USER'S GUIDE[/center]" + +[node name="Instructions" type="RichTextLabel" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/LeftPage"] +custom_minimum_size = Vector2(0, 500) +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.0 +theme_override_constants/line_separation = 20 +theme_override_font_sizes/normal_font_size = 20 +bbcode_enabled = true +text = " + + + + +THE FOLLOWING SYMPTOMS HAVE BEEN IDENTIFIED AS MARKERS OF ABNORMALITY CONVERSION + + +IF YOU DETECT ONE THAT WAS NOT ASSESSED IN PRE-SCREENING, [color=red]TERMINATE[/color] THE SUBJECT" + +[node name="CloseManual" type="Button" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/LeftPage"] +custom_minimum_size = Vector2(200, 60) +layout_mode = 2 +size_flags_horizontal = 4 +theme = SubResource("Theme_gwogn") +text = "Close manual" + +[node name="RightPage" type="VBoxContainer" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Symptoms" type="RichTextLabel" parent="LowerHalf/JournalLayer/JournalOpen/JournalMargin/PageArea/RightPage"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/paragraph_separation = 1 +theme_override_font_sizes/normal_font_size = 20 +bbcode_enabled = true +text = "SUICIDAL IDEATIONS +Persistent thoughts about ending one's own life + +PANIC ATTACKS +Acute episodes of severe anxiety + +PERSECUTORY DELUSIONS +False conviction of being targeted or harmed + +MEGALOMANIAC DELUSIONS +False exaggeration of self-importance or power + +MYSTICAL DELUSIONS +False certainty of spiritual, divine connection + +MANIA +Excessively elevated mood and increased energy + +AUDITORY HALLUCINATIONS +False perceptions of external sound + +VISUAL HALLUCINATIONS +False perceptions of visual stimuli + +HAPTIC HALLUCINATIONS +False perceptions of touch + +CENESTHETIC HALLUCINATIONS +False internal bodily sensations + +SELF-MUTILATION +Deliberate injury to one's own body + +HETERO-AGRESSIVE BEHAVIOUR +Hostility, verbal agression, threats" + +[node name="GreenButton" type="TextureButton" parent="LowerHalf"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 817.0 +offset_top = 328.0 +offset_right = -1003.0 +offset_bottom = -4.0 +grow_horizontal = 2 +grow_vertical = 2 +toggle_mode = true +texture_normal = ExtResource("10_tmo4o") +texture_pressed = ExtResource("11_weq72") +texture_hover = ExtResource("12_rpmuh") +texture_disabled = ExtResource("11_weq72") +texture_click_mask = ExtResource("10_hxph5") +stretch_mode = 3 + +[node name="RedButton" type="TextureButton" parent="LowerHalf"] +z_index = 1 +layout_mode = 0 +offset_left = 1003.0 +offset_top = 328.0 +offset_right = 1103.0 +offset_bottom = 392.0 +toggle_mode = true +texture_normal = ExtResource("12_7mvev") +texture_pressed = ExtResource("13_lq4cy") +texture_hover = ExtResource("15_pbvyq") +texture_disabled = ExtResource("13_lq4cy") +stretch_mode = 3 + +[node name="MiddleScreen" type="Control" parent="LowerHalf"] +z_index = 1 +anchors_preset = 0 +offset_left = 744.0 +offset_top = 12.0 +offset_right = 1184.0 +offset_bottom = 236.0 + +[node name="MiddleScreenTexture" type="TextureRect" parent="LowerHalf/MiddleScreen"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("24_d8gyq") +stretch_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="LowerHalf/MiddleScreen"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 28 +theme_override_constants/margin_top = 28 +theme_override_constants/margin_right = 36 +theme_override_constants/margin_bottom = 28 + +[node name="MiddleInfo" type="RichTextLabel" parent="LowerHalf/MiddleScreen/MarginContainer"] +layout_mode = 2 +theme_override_font_sizes/normal_font_size = 21 +bbcode_enabled = true +text = "DAY: [color=dark_orange]1[/color] + +FLOOR: [color=dark_orange]-1[/color] + +ACCURATE DIAGNOSES: [color=dark_orange]0/4[/color] + +" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="DecisionScreen" type="Control" parent="LowerHalf"] +z_index = 1 +anchors_preset = 0 +offset_left = 744.0 +offset_top = 236.0 +offset_right = 1184.0 +offset_bottom = 320.0 + +[node name="MiddleScreenTexture" type="TextureRect" parent="LowerHalf/DecisionScreen"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("29_cobue") +stretch_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="LowerHalf/DecisionScreen"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 24 +theme_override_constants/margin_top = 24 +theme_override_constants/margin_right = 32 +theme_override_constants/margin_bottom = 24 + +[node name="HBoxContainer" type="HBoxContainer" parent="LowerHalf/DecisionScreen/MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 48 + +[node name="Release" type="RichTextLabel" parent="LowerHalf/DecisionScreen/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 22 +bbcode_enabled = true +text = "[color=dark_green]RELEASE[/color]" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Terminate" type="RichTextLabel" parent="LowerHalf/DecisionScreen/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_fonts/normal_font = ExtResource("14_0pvwy") +theme_override_font_sizes/normal_font_size = 22 +bbcode_enabled = true +text = "[color=dark_red]TERMINATE[/color]" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="TeaCup" type="Control" parent="LowerHalf"] +anchors_preset = 0 +offset_left = 532.0 +offset_top = 322.0 +offset_right = 644.0 +offset_bottom = 386.0 + +[node name="TeaCupTexture" type="TextureRect" parent="LowerHalf/TeaCup"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("17_son5s") + +[node name="Journal" type="Control" parent="LowerHalf"] +anchors_preset = 0 +offset_left = 673.0 +offset_top = 314.0 +offset_right = 805.0 +offset_bottom = 394.0 + +[node name="JournalButton" type="TextureButton" parent="LowerHalf/Journal"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture_normal = ExtResource("17_7o31y") +texture_pressed = ExtResource("18_gv516") +texture_hover = ExtResource("19_ar3nk") +texture_click_mask = ExtResource("20_5vhu5") +stretch_mode = 3 + +[node name="RoomAssets" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + +[node name="Glass" type="TextureRect" parent="RoomAssets"] +z_index = 4 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("3_nxnrp") expand_mode = 1 + +[node name="Backroom" type="TextureRect" parent="RoomAssets"] +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("4_fij8w") + +[node name="Room" type="TextureRect" parent="RoomAssets"] +z_index = 5 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("5_aa8r5") + +[node name="Backwall" type="TextureRect" parent="RoomAssets"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("6_5772r") + +[node name="ShutterTop" type="TextureRect" parent="RoomAssets"] +z_index = 3 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -2540.0 +offset_top = -540.0 +offset_right = -2060.0 +offset_bottom = 540.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 4 +size_flags_vertical = 0 +mouse_filter = 2 +texture = ExtResource("6_1rj24") +stretch_mode = 3 + +[node name="ShutterBot" type="TextureRect" parent="RoomAssets"] +z_index = 3 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 60.0 +offset_top = 50.0 +offset_right = 540.0 +offset_bottom = 1130.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 4 +mouse_filter = 2 +texture = ExtResource("7_feog2") +stretch_mode = 3 + +[node name="ShuttersAnimation" type="AnimationPlayer" parent="RoomAssets"] +root_node = NodePath("../ShutterBot") +libraries = { +&"": SubResource("AnimationLibrary_vh6fv") +} + +[node name="GreenBackroom" type="TextureRect" parent="RoomAssets"] +visible = false +modulate = Color(1, 1, 1, 0.588235) +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("29_qdgqn") + +[node name="RedBackroom" type="TextureRect" parent="RoomAssets"] +visible = false +z_index = 1 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +mouse_filter = 2 +texture = ExtResource("30_r58th") diff --git a/tablet.tscn b/tablet.tscn new file mode 100644 index 0000000..59cfd90 --- /dev/null +++ b/tablet.tscn @@ -0,0 +1,193 @@ +[gd_scene load_steps=9 format=3 uid="uid://b8r8skw2tnciq"] + +[ext_resource type="Texture2D" uid="uid://ctmr6cncu5ir5" path="res://Art/UI/tablet.png" id="1_55npq"] +[ext_resource type="FontFile" uid="uid://dv8ppra5tgn1t" path="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" id="4_gaxx3"] + +[sub_resource type="GDScript" id="GDScript_0snv3"] +script/source = "extends CanvasLayer + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass +" + +[sub_resource type="Animation" id="Animation_betlh"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(0, 180)] +} + +[sub_resource type="Animation" id="Animation_2w4o3"] +resource_name = "close" +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.4), +"transitions": PackedFloat32Array(1, 1), +"update": 0, +"values": [Vector2(0, 180), Vector2(0, 1080)] +} + +[sub_resource type="Animation" id="Animation_7qxmi"] +resource_name = "open" +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.4), +"transitions": PackedFloat32Array(1, 1), +"update": 0, +"values": [Vector2(0, 1080), Vector2(0, 180)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_4wx7s"] +_data = { +&"RESET": SubResource("Animation_betlh"), +&"close": SubResource("Animation_2w4o3"), +&"open": SubResource("Animation_7qxmi") +} + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_n43tl"] +bg_color = Color(0.144063, 0.144063, 0.144062, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +expand_margin_bottom = 1.0 + +[node name="Tablet" type="CanvasLayer"] +script = SubResource("GDScript_0snv3") + +[node name="Tablet" type="Control" parent="."] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 180.0 +offset_right = -1320.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="TextureRect" type="TextureRect" parent="Tablet"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("1_55npq") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="Tablet"] +libraries = { +&"": SubResource("AnimationLibrary_4wx7s") +} + +[node name="MarginContainer" type="MarginContainer" parent="Tablet"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 33 +theme_override_constants/margin_top = 33 +theme_override_constants/margin_right = 33 +theme_override_constants/margin_bottom = 33 + +[node name="VBoxContainer" type="VBoxContainer" parent="Tablet/MarginContainer"] +layout_mode = 2 + +[node name="Screen1Container" type="MarginContainer" parent="Tablet/MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 + +[node name="VBoxContainer" type="VBoxContainer" parent="Tablet/MarginContainer/VBoxContainer/Screen1Container"] +layout_mode = 2 +theme_override_constants/separation = 5 + +[node name="HBoxContainer" type="HBoxContainer" parent="Tablet/MarginContainer/VBoxContainer/Screen1Container/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.5 + +[node name="Panel" type="Panel" parent="Tablet/MarginContainer/VBoxContainer/Screen1Container/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.4 +theme_override_styles/panel = SubResource("StyleBoxFlat_n43tl") + +[node name="PatientMugshot" type="TextureRect" parent="Tablet/MarginContainer/VBoxContainer/Screen1Container/VBoxContainer/HBoxContainer/Panel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.4 +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="Tablet/MarginContainer/VBoxContainer/Screen1Container/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="PatientFileTitle" type="RichTextLabel" parent="Tablet/MarginContainer/VBoxContainer/Screen1Container/VBoxContainer/HBoxContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_fonts/normal_font = ExtResource("4_gaxx3") +text = "PATIENT FILE #564" + +[node name="PatientFile" type="RichTextLabel" parent="Tablet/MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/line_separation = 5 +theme_override_fonts/normal_font = ExtResource("4_gaxx3") +bbcode_enabled = true +text = "NAME: Orleg, Vladislav + +AGE: 59 + +PROFESSION: Gardener + +ADMISSION REASON: +Delirium +Verbal agression +Homicidal threats" + +[node name="HSeparator" type="HSeparator" parent="Tablet/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 22 + +[node name="Screen2Container" type="MarginContainer" parent="Tablet/MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..a042389 --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +hello world! diff --git a/text_screens.gd.uid b/text_screens.gd.uid new file mode 100644 index 0000000..8c79927 --- /dev/null +++ b/text_screens.gd.uid @@ -0,0 +1 @@ +uid://t5hihricayvb diff --git a/text_screens.tscn b/text_screens.tscn index 3d335ed..65dfd84 100644 --- a/text_screens.tscn +++ b/text_screens.tscn @@ -1,8 +1,7 @@ -[gd_scene load_steps=4 format=3 uid="uid://igkl2384rxt1"] +[gd_scene load_steps=3 format=3 uid="uid://igkl2384rxt1"] -[ext_resource type="Script" path="res://text_screens.gd" id="1_16laq"] -[ext_resource type="FontFile" uid="uid://ckg5x0oonnjtn" path="res://Art/XanhMono-Regular.ttf" id="2_i0l2q"] -[ext_resource type="FontFile" uid="uid://mi5v1uq78g7o" path="res://Art/XanhMono-Italic.ttf" id="3_i5n2a"] +[ext_resource type="Script" uid="uid://t5hihricayvb" path="res://text_screens.gd" id="1_16laq"] +[ext_resource type="FontFile" uid="uid://dv8ppra5tgn1t" path="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" id="4_fhc1y"] [node name="TextScreens" type="CanvasLayer"] layer = 0 @@ -37,14 +36,11 @@ custom_minimum_size = Vector2(10, 0) layout_mode = 2 size_flags_vertical = 3 size_flags_stretch_ratio = 0.2 -theme_override_fonts/normal_font = ExtResource("2_i0l2q") -theme_override_fonts/mono_font = ExtResource("2_i0l2q") -theme_override_fonts/italics_font = ExtResource("3_i5n2a") +theme_override_font_sizes/normal_font_size = 30 +theme_override_font_sizes/bold_font_size = 30 theme_override_font_sizes/bold_italics_font_size = 30 theme_override_font_sizes/italics_font_size = 30 theme_override_font_sizes/mono_font_size = 30 -theme_override_font_sizes/normal_font_size = 30 -theme_override_font_sizes/bold_font_size = 30 text = "French Republic, Paris, October 12th 2053 78 days since the Abnormality" @@ -63,14 +59,11 @@ size_flags_vertical = 3 clip_contents = false custom_minimum_size = Vector2(10, 0) layout_mode = 2 -theme_override_fonts/normal_font = ExtResource("2_i0l2q") -theme_override_fonts/mono_font = ExtResource("2_i0l2q") -theme_override_fonts/italics_font = ExtResource("3_i5n2a") +theme_override_font_sizes/normal_font_size = 30 +theme_override_font_sizes/bold_font_size = 30 theme_override_font_sizes/bold_italics_font_size = 30 theme_override_font_sizes/italics_font_size = 30 theme_override_font_sizes/mono_font_size = 30 -theme_override_font_sizes/normal_font_size = 30 -theme_override_font_sizes/bold_font_size = 30 text = "It's been almost 3 months already since it all began. We thought it as a mere pandemic, yet another crisis amongst the dozens we had to bear for the past decade. @@ -88,14 +81,11 @@ tab_size = 8 clip_contents = false custom_minimum_size = Vector2(10, 0) layout_mode = 2 -theme_override_fonts/normal_font = ExtResource("2_i0l2q") -theme_override_fonts/mono_font = ExtResource("2_i0l2q") -theme_override_fonts/italics_font = ExtResource("3_i5n2a") +theme_override_font_sizes/normal_font_size = 30 +theme_override_font_sizes/bold_font_size = 30 theme_override_font_sizes/bold_italics_font_size = 30 theme_override_font_sizes/italics_font_size = 30 theme_override_font_sizes/mono_font_size = 30 -theme_override_font_sizes/normal_font_size = 30 -theme_override_font_sizes/bold_font_size = 30 bbcode_enabled = true text = "The nightmares came first. Whispers from afar, soft, humming, comforting even. Slowly seeping into one's mind. And then, the unexplainable. Things that the mind cannot comprehend, cannot write nor speak. @@ -117,14 +107,11 @@ tab_size = 8 clip_contents = false custom_minimum_size = Vector2(10, 0) layout_mode = 2 -theme_override_fonts/normal_font = ExtResource("2_i0l2q") -theme_override_fonts/mono_font = ExtResource("2_i0l2q") -theme_override_fonts/italics_font = ExtResource("3_i5n2a") +theme_override_font_sizes/normal_font_size = 30 +theme_override_font_sizes/bold_font_size = 30 theme_override_font_sizes/bold_italics_font_size = 30 theme_override_font_sizes/italics_font_size = 30 theme_override_font_sizes/mono_font_size = 30 -theme_override_font_sizes/normal_font_size = 30 -theme_override_font_sizes/bold_font_size = 30 bbcode_enabled = true text = "Military curfew all around the globe. Containment attempts by the governments, even though It seems to propagate regardless of any safety measure. @@ -146,14 +133,11 @@ tab_size = 8 clip_contents = false custom_minimum_size = Vector2(10, 0) layout_mode = 2 -theme_override_fonts/normal_font = ExtResource("2_i0l2q") -theme_override_fonts/mono_font = ExtResource("2_i0l2q") -theme_override_fonts/italics_font = ExtResource("3_i5n2a") +theme_override_font_sizes/normal_font_size = 30 +theme_override_font_sizes/bold_font_size = 30 theme_override_font_sizes/bold_italics_font_size = 30 theme_override_font_sizes/italics_font_size = 30 theme_override_font_sizes/mono_font_size = 30 -theme_override_font_sizes/normal_font_size = 30 -theme_override_font_sizes/bold_font_size = 30 bbcode_enabled = true text = "I applied to help with psychiatric research, in a perhaps futile attempt to help. @@ -171,14 +155,11 @@ tab_size = 8 clip_contents = false custom_minimum_size = Vector2(10, 0) layout_mode = 2 -theme_override_fonts/normal_font = ExtResource("2_i0l2q") -theme_override_fonts/mono_font = ExtResource("2_i0l2q") -theme_override_fonts/italics_font = ExtResource("3_i5n2a") +theme_override_font_sizes/normal_font_size = 30 +theme_override_font_sizes/bold_font_size = 30 theme_override_font_sizes/bold_italics_font_size = 30 theme_override_font_sizes/italics_font_size = 30 theme_override_font_sizes/mono_font_size = 30 -theme_override_font_sizes/normal_font_size = 30 -theme_override_font_sizes/bold_font_size = 30 bbcode_enabled = true text = "I am not qualified. @@ -192,17 +173,18 @@ For in these times of strife, I finally get to [i]matter[/i]." tab_size = 8 [node name="Day" type="RichTextLabel" parent="."] +visible = false anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -offset_left = -164.5 -offset_top = -65.0 -offset_right = 164.5 -offset_bottom = 65.0 +offset_left = -240.0 +offset_top = -105.5 +offset_right = 240.0 +offset_bottom = 105.5 grow_horizontal = 2 grow_vertical = 2 -theme_override_fonts/normal_font = ExtResource("2_i0l2q") +theme_override_fonts/normal_font = ExtResource("4_fhc1y") theme_override_font_sizes/normal_font_size = 100 bbcode_enabled = true diff --git a/title_screen.gd.uid b/title_screen.gd.uid new file mode 100644 index 0000000..056a8c9 --- /dev/null +++ b/title_screen.gd.uid @@ -0,0 +1 @@ +uid://bkd300wbk2f8a diff --git a/title_screen.tscn b/title_screen.tscn index 2468daa..05f6cde 100644 --- a/title_screen.tscn +++ b/title_screen.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=4 format=3 uid="uid://cahspuhi43bx7"] [ext_resource type="Texture2D" uid="uid://c3je4gir1qlot" path="res://Art/BlackScreen.png" id="1_3k8ub"] -[ext_resource type="Script" path="res://title_screen.gd" id="1_ejdst"] -[ext_resource type="FontFile" uid="uid://ckg5x0oonnjtn" path="res://Art/XanhMono-Regular.ttf" id="3_xwylp"] +[ext_resource type="Script" uid="uid://bkd300wbk2f8a" path="res://title_screen.gd" id="1_ejdst"] +[ext_resource type="FontFile" uid="uid://dv8ppra5tgn1t" path="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" id="3_vprse"] [node name="TitleScreenLayer" type="CanvasLayer"] script = ExtResource("1_ejdst") @@ -25,50 +25,62 @@ grow_vertical = 2 texture = ExtResource("1_3k8ub") [node name="MenuContainer" type="VBoxContainer" parent="TitleScreen"] -layout_mode = 2 +layout_mode = 0 offset_left = 200.0 offset_top = 370.0 -offset_right = 506.0 +offset_right = 883.0 offset_bottom = 854.0 -metadata/_edit_group_ = true [node name="NewGame" type="Button" parent="TitleScreen/MenuContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("3_xwylp") -theme_override_font_sizes/font_size = 30 +theme_override_fonts/font = ExtResource("3_vprse") +theme_override_font_sizes/font_size = 50 text = "NEW GAME" [node name="Load" type="Button" parent="TitleScreen/MenuContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("3_xwylp") -theme_override_font_sizes/font_size = 30 +theme_override_fonts/font = ExtResource("3_vprse") +theme_override_font_sizes/font_size = 50 text = "LOAD" [node name="Settings" type="Button" parent="TitleScreen/MenuContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("3_xwylp") -theme_override_font_sizes/font_size = 30 +theme_override_fonts/font = ExtResource("3_vprse") +theme_override_font_sizes/font_size = 50 text = "SETTINGS" [node name="QuitGame" type="Button" parent="TitleScreen/MenuContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("3_xwylp") -theme_override_font_sizes/font_size = 30 +theme_override_fonts/font = ExtResource("3_vprse") +theme_override_font_sizes/font_size = 50 text = "QUIT GAME" [node name="GameTitle" type="Label" parent="TitleScreen"] layout_mode = 0 -offset_left = 68.0 -offset_top = 85.0 -offset_right = 642.0 -offset_bottom = 241.0 +offset_left = 31.0 +offset_top = 96.0 +offset_right = 858.0 +offset_bottom = 277.0 theme_type_variation = &"HeaderLarge" -theme_override_fonts/font = ExtResource("3_xwylp") -theme_override_font_sizes/font_size = 71 -text = "I am not sick" +theme_override_font_sizes/font_size = 100 horizontal_alignment = 1 vertical_alignment = 1 + +[node name="RichTextLabel" type="RichTextLabel" parent="TitleScreen/GameTitle"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -11.0 +offset_top = 27.0 +offset_right = 248.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_fonts/normal_font = ExtResource("3_vprse") +theme_override_font_sizes/normal_font_size = 100 +bbcode_enabled = true +text = "[center]I AM NOT SICK" diff --git a/transition_screen.gd.uid b/transition_screen.gd.uid new file mode 100644 index 0000000..ae70051 --- /dev/null +++ b/transition_screen.gd.uid @@ -0,0 +1 @@ +uid://dywfa5vbgtrkb diff --git a/transition_screen.tscn b/transition_screen.tscn index ef2a9b6..0f7a96d 100644 --- a/transition_screen.tscn +++ b/transition_screen.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=10 format=3 uid="uid://drk1l7ijo4xq5"] -[ext_resource type="Script" path="res://transition_screen.gd" id="1_hbjtk"] +[ext_resource type="Script" uid="uid://dywfa5vbgtrkb" path="res://transition_screen.gd" id="1_hbjtk"] [sub_resource type="Animation" id="Animation_n85x3"] length = 0.001 @@ -49,9 +49,9 @@ tracks/0/keys = { [sub_resource type="AnimationLibrary" id="AnimationLibrary_5yx4m"] _data = { -"RESET": SubResource("Animation_n85x3"), -"fade_to_black": SubResource("Animation_20bx3"), -"fade_to_normal": SubResource("Animation_in743") +&"RESET": SubResource("Animation_n85x3"), +&"fade_to_black": SubResource("Animation_20bx3"), +&"fade_to_normal": SubResource("Animation_in743") } [sub_resource type="Animation" id="Animation_igu58"] @@ -103,9 +103,9 @@ tracks/0/keys = { [sub_resource type="AnimationLibrary" id="AnimationLibrary_h2xbl"] _data = { -"RESET": SubResource("Animation_igu58"), -"fade_to_black": SubResource("Animation_3ahpb"), -"fade_to_normal": SubResource("Animation_do6sc") +&"RESET": SubResource("Animation_igu58"), +&"fade_to_black": SubResource("Animation_3ahpb"), +&"fade_to_normal": SubResource("Animation_do6sc") } [node name="transition_screen" type="CanvasLayer"] @@ -124,7 +124,7 @@ color = Color(0, 0, 0, 1) [node name="BigFadingAnimation" type="AnimationPlayer" parent="BigFading"] root_node = NodePath("../..") libraries = { -"": SubResource("AnimationLibrary_5yx4m") +&"": SubResource("AnimationLibrary_5yx4m") } [node name="SmallFading" type="ColorRect" parent="."] @@ -135,7 +135,7 @@ color = Color(0, 0, 0, 1) [node name="SmallFadingAnimation" type="AnimationPlayer" parent="SmallFading"] libraries = { -"": SubResource("AnimationLibrary_h2xbl") +&"": SubResource("AnimationLibrary_h2xbl") } [node name="Mask" type="ColorRect" parent="."] diff --git a/ui.gd.uid b/ui.gd.uid new file mode 100644 index 0000000..256591d --- /dev/null +++ b/ui.gd.uid @@ -0,0 +1 @@ +uid://c0q67t6m1i4dl diff --git a/ui.tscn b/ui.tscn index 846871f..d6c82e4 100644 --- a/ui.tscn +++ b/ui.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=7 format=3 uid="uid://dy40jygog8t3k"] -[ext_resource type="Script" path="res://ui.gd" id="1_mg12k"] -[ext_resource type="FontFile" uid="uid://ckg5x0oonnjtn" path="res://Art/XanhMono-Regular.ttf" id="2_cohu5"] -[ext_resource type="FontFile" uid="uid://mi5v1uq78g7o" path="res://Art/XanhMono-Italic.ttf" id="3_e3v1e"] +[ext_resource type="Script" uid="uid://c0q67t6m1i4dl" path="res://ui.gd" id="1_mg12k"] +[ext_resource type="FontFile" uid="uid://dv8ppra5tgn1t" path="res://Art/Fonts/PixelatedEleganceRegular-ovyAA.ttf" id="2_vb7na"] +[ext_resource type="Texture2D" uid="uid://l7786mlmvet8" path="res://Art/UI/menu_gear.png" id="3_ibj64"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fo2i3"] border_width_left = 2 @@ -11,14 +11,13 @@ border_width_right = 2 border_width_bottom = 2 [sub_resource type="Theme" id="Theme_27bfm"] -default_font = ExtResource("2_cohu5") default_font_size = 18 -OptionButton/fonts/font = ExtResource("2_cohu5") +OptionButton/fonts/font = null [sub_resource type="Theme" id="Theme_k8uqd"] -default_font = ExtResource("2_cohu5") -default_font_size = 18 -OptionButton/fonts/font = ExtResource("2_cohu5") +default_font = ExtResource("2_vb7na") +default_font_size = 16 +OptionButton/fonts/font = ExtResource("2_vb7na") [node name="UILayer" type="CanvasLayer" node_paths=PackedStringArray("text_slider", "auto_slider", "text_counter", "auto_counter", "autoplay", "auto_mask", "display_button", "resolution_button")] layer = 127 @@ -75,42 +74,42 @@ layout_mode = 2 [node name="Resume" type="Button" parent="UI/Menu/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "RESUME" [node name="Save" type="Button" parent="UI/Menu/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "SAVE" [node name="Load" type="Button" parent="UI/Menu/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "LOAD" [node name="Settings" type="Button" parent="UI/Menu/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "SETTINGS" [node name="MainMenu" type="Button" parent="UI/Menu/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "MAIN MENU" [node name="Quit" type="Button" parent="UI/Menu/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "QUIT GAME" @@ -147,7 +146,7 @@ layout_mode = 2 [node name="RichTextLabel" type="RichTextLabel" parent="UI/Save/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/normal_font = ExtResource("2_cohu5") +theme_override_fonts/normal_font = ExtResource("2_vb7na") theme_override_font_sizes/normal_font_size = 30 bbcode_enabled = true text = " @@ -156,28 +155,28 @@ text = " [node name="SaveSlot1" type="Button" parent="UI/Save/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "EMPTY" [node name="SaveSlot2" type="Button" parent="UI/Save/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "EMPTY" [node name="SaveSlot3" type="Button" parent="UI/Save/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "EMPTY" [node name="SaveSlot4" type="Button" parent="UI/Save/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "EMPTY" @@ -186,7 +185,7 @@ custom_minimum_size = Vector2(80, 40) layout_mode = 2 size_flags_horizontal = 4 size_flags_vertical = 6 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 20 text = "BACK" @@ -223,7 +222,7 @@ layout_mode = 2 [node name="RichTextLabel" type="RichTextLabel" parent="UI/Load/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/normal_font = ExtResource("2_cohu5") +theme_override_fonts/normal_font = ExtResource("2_vb7na") theme_override_font_sizes/normal_font_size = 30 bbcode_enabled = true text = " @@ -232,28 +231,28 @@ text = " [node name="LoadSlot1" type="Button" parent="UI/Load/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "EMPTY" [node name="LoadSlot2" type="Button" parent="UI/Load/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "EMPTY" [node name="LoadSlot3" type="Button" parent="UI/Load/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "EMPTY" [node name="LoadSlot4" type="Button" parent="UI/Load/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 27 text = "EMPTY" @@ -278,7 +277,7 @@ grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 4 size_flags_vertical = 6 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 20 text = "BACK" @@ -298,11 +297,12 @@ grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 4 size_flags_vertical = 6 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 20 text = "BACK" [node name="Settings" type="PanelContainer" parent="UI"] +visible = false custom_minimum_size = Vector2(1600, 600) layout_mode = 1 anchors_preset = 8 @@ -336,7 +336,7 @@ theme_override_constants/separation = 40 [node name="Settings" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 -theme_override_fonts/normal_font = ExtResource("2_cohu5") +theme_override_fonts/normal_font = ExtResource("2_vb7na") theme_override_font_sizes/normal_font_size = 35 bbcode_enabled = true text = "[center]SETTINGS[/center]" @@ -368,7 +368,7 @@ layout_mode = 2 [node name="RichTextLabel" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/DialogueContainer/VBoxContainer2/DialogueOptions/VBoxContainer"] custom_minimum_size = Vector2(0, 50) layout_mode = 2 -theme_override_fonts/normal_font = ExtResource("2_cohu5") +theme_override_fonts/normal_font = ExtResource("2_vb7na") theme_override_font_sizes/normal_font_size = 25 bbcode_enabled = true text = "[center]DIALOGUE[/center]" @@ -378,12 +378,12 @@ custom_minimum_size = Vector2(0, 50) layout_mode = 2 [node name="RichTextLabel" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/DialogueContainer/VBoxContainer2/DialogueOptions/VBoxContainer/TextSpeed"] -custom_minimum_size = Vector2(140, 28) +custom_minimum_size = Vector2(160, 28) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("2_vb7na") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true text = "TYPING SPEED" @@ -403,8 +403,8 @@ custom_minimum_size = Vector2(150, 25) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("2_vb7na") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true [node name="Control" type="Control" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/DialogueContainer/VBoxContainer2/DialogueOptions/VBoxContainer"] @@ -421,14 +421,14 @@ grow_horizontal = 2 grow_vertical = 2 [node name="RichTextLabel" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/DialogueContainer/VBoxContainer2/DialogueOptions/VBoxContainer/Control/AutoSpeed"] -custom_minimum_size = Vector2(140, 25) +custom_minimum_size = Vector2(160, 25) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("2_vb7na") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true -text = "ADVANCE DELAY" +text = "AUTO DELAY" [node name="AutoSlider" type="HSlider" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/DialogueContainer/VBoxContainer2/DialogueOptions/VBoxContainer/Control/AutoSpeed"] layout_mode = 2 @@ -446,8 +446,8 @@ custom_minimum_size = Vector2(150, 25) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("2_vb7na") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true [node name="AutoMask" type="ColorRect" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/DialogueContainer/VBoxContainer2/DialogueOptions/VBoxContainer/Control"] @@ -465,13 +465,13 @@ layout_mode = 2 theme_override_constants/separation = 10 [node name="RichTextLabel" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/DialogueContainer/VBoxContainer2/DialogueOptions/VBoxContainer/AutoPlay"] -custom_minimum_size = Vector2(140, 25) +custom_minimum_size = Vector2(160, 25) layout_mode = 2 size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("2_vb7na") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true -text = "AUTOPLAY TEXT" +text = "AUTOPLAY" [node name="AutoPlayCheckBox" type="CheckBox" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/DialogueContainer/VBoxContainer2/DialogueOptions/VBoxContainer/AutoPlay"] layout_mode = 2 @@ -499,7 +499,7 @@ theme_override_constants/separation = 20 [node name="RichTextLabel" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/ScreenContainer/VBoxContainer2/ScreenSettings/VBoxContainer"] custom_minimum_size = Vector2(0, 50) layout_mode = 2 -theme_override_fonts/normal_font = ExtResource("2_cohu5") +theme_override_fonts/normal_font = ExtResource("2_vb7na") theme_override_font_sizes/normal_font_size = 25 bbcode_enabled = true text = "[center]SCREEN[/center]" @@ -509,11 +509,11 @@ layout_mode = 2 theme_override_constants/separation = 20 [node name="RichTextLabel" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/ScreenContainer/VBoxContainer2/ScreenSettings/VBoxContainer/Display"] -custom_minimum_size = Vector2(110, 28) +custom_minimum_size = Vector2(140, 28) layout_mode = 2 size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("2_vb7na") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true text = "DISPLAY" @@ -521,11 +521,12 @@ text = "DISPLAY" custom_minimum_size = Vector2(250, 0) layout_mode = 2 theme = SubResource("Theme_27bfm") -theme_override_fonts/font = ExtResource("2_cohu5") -theme_override_font_sizes/font_size = 18 +theme_override_fonts/font = ExtResource("2_vb7na") +theme_override_font_sizes/font_size = 16 selected = 2 item_count = 3 popup/item_0/text = "WINDOWED" +popup/item_0/id = 0 popup/item_1/text = "WINDOWED FULLSCREEN" popup/item_1/id = 1 popup/item_2/text = "FULLSCREEN" @@ -536,11 +537,11 @@ layout_mode = 2 theme_override_constants/separation = 20 [node name="RichTextLabel" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/ScreenContainer/VBoxContainer2/ScreenSettings/VBoxContainer/Resolution"] -custom_minimum_size = Vector2(110, 28) +custom_minimum_size = Vector2(140, 28) layout_mode = 2 size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("2_vb7na") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true text = "RESOLUTION" @@ -548,7 +549,6 @@ text = "RESOLUTION" custom_minimum_size = Vector2(250, 0) layout_mode = 2 theme = SubResource("Theme_k8uqd") -selected = 0 [node name="SoundContainer" type="MarginContainer" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer"] layout_mode = 2 @@ -571,7 +571,7 @@ layout_mode = 2 [node name="RichTextLabel" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer"] custom_minimum_size = Vector2(0, 50) layout_mode = 2 -theme_override_fonts/normal_font = ExtResource("2_cohu5") +theme_override_fonts/normal_font = ExtResource("2_vb7na") theme_override_font_sizes/normal_font_size = 25 bbcode_enabled = true text = "[center]AUDIO[/center]" @@ -585,8 +585,8 @@ custom_minimum_size = Vector2(140, 28) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("2_vb7na") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true text = "MASTER" @@ -605,83 +605,10 @@ ticks_on_borders = true custom_minimum_size = Vector2(150, 25) layout_mode = 2 size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 +theme_override_fonts/normal_font = ExtResource("2_vb7na") +theme_override_font_sizes/normal_font_size = 16 bbcode_enabled = true -[node name="TextEdit" type="TextEdit" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer/TextSpeed"] -layout_mode = 2 - -[node name="Control" type="Control" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer"] -custom_minimum_size = Vector2(0, 50) -layout_mode = 2 - -[node name="AutoSpeed" type="HBoxContainer" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer/Control"] -custom_minimum_size = Vector2(0, 50) -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="RichTextLabel" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer/Control/AutoSpeed"] -custom_minimum_size = Vector2(140, 25) -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 -bbcode_enabled = true -text = "ADVANCE DELAY" - -[node name="AutoSlider" type="HSlider" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer/Control/AutoSpeed"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 1 -size_flags_stretch_ratio = 4.0 -min_value = 1.0 -max_value = 10.0 -value = 5.0 -tick_count = 10 -ticks_on_borders = true - -[node name="AutoCounter" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer/Control/AutoSpeed"] -custom_minimum_size = Vector2(150, 25) -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 -bbcode_enabled = true - -[node name="AutoMask" type="ColorRect" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer/Control"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -color = Color(0, 0, 0, 0.784314) - -[node name="AutoPlay" type="HBoxContainer" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer"] -custom_minimum_size = Vector2(0, 50) -layout_mode = 2 -theme_override_constants/separation = 10 - -[node name="RichTextLabel" type="RichTextLabel" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer/AutoPlay"] -custom_minimum_size = Vector2(140, 25) -layout_mode = 2 -size_flags_vertical = 4 -theme_override_fonts/normal_font = ExtResource("2_cohu5") -theme_override_font_sizes/normal_font_size = 20 -bbcode_enabled = true -text = "AUTOPLAY TEXT" - -[node name="AutoPlayCheckBox" type="CheckBox" parent="UI/Settings/MarginContainer/VBoxContainer/GridContainer/SoundContainer/VBoxContainer2/DialogueOptions/VBoxContainer/AutoPlay"] -layout_mode = 2 -size_flags_vertical = 4 - [node name="Control" type="Control" parent="UI/Settings/MarginContainer/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 @@ -703,7 +630,7 @@ grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 4 size_flags_vertical = 6 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 20 text = "BACK" @@ -723,35 +650,33 @@ grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 4 size_flags_vertical = 6 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 20 text = "BACK" [node name="UpperContainer" type="HBoxContainer" parent="UI"] -visible = false layout_mode = 0 -offset_left = 28.0 -offset_top = 63.0 -offset_right = 1877.0 -offset_bottom = 148.0 +offset_right = 1849.0 +offset_bottom = 85.0 [node name="Menu" type="Button" parent="UI/UpperContainer"] +custom_minimum_size = Vector2(0, 80) layout_mode = 2 -theme_override_fonts/font = ExtResource("2_cohu5") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 30 -text = " Menu " +theme_override_icons/icon = ExtResource("3_ibj64") [node name="SkipContainer" type="HBoxContainer" parent="UI"] visible = false layout_mode = 0 -offset_left = 1643.0 -offset_top = 999.0 -offset_right = 1881.0 +offset_left = 1565.0 +offset_top = 997.0 +offset_right = 1895.0 offset_bottom = 1059.0 [node name="NextButton" type="Button" parent="UI/SkipContainer"] layout_mode = 2 -theme_override_fonts/font = ExtResource("3_e3v1e") +theme_override_fonts/font = ExtResource("2_vb7na") theme_override_font_sizes/font_size = 20 text = " Press Space to Next "