Merge branch 'develop' into feature/farming_bugfixes_and_magic_word

feature/farming_bugfixes_and_magic_word
kziolkowski 2 months ago
commit 66848af8bf

@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0B2502BD29F5EC4798EEFD2950AA7E06/Description/@EntryValue">Godot Signal</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0B2502BD29F5EC4798EEFD2950AA7E06/Text/@EntryValue">[Signal]
public delegate void $SignalName$EventHandler($END$);</s:String>

@ -1,18 +1,39 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArea2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fefbd3244e8e427e388f389cc304f90548d56b58a375097a197ac2eb8259990bb_003FArea2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArea3D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F8a54226fa2e1c9371a8091f24cfd744aef11fe6869527dc23b9b837623a29b9_003FArea3D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArray_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe37dc1faf08a4d5ea030ad59bdf77522523400_003Fa3_003Fe272a3a7_003FArray_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAudioStreamPlayer2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F848324b1c23114c3f5e8bbb5a42c4ade394c59a7a7a133a66b76581ca571_003FAudioStreamPlayer2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EFieldBehaviour2D_005FScriptMethods_002Egenerated_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F75d11718f1abbc2572fd32e4b83acbec9d79ac_003FBabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EFieldBehaviour2D_005FScriptMethods_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptMethods_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FLocal_003FTemp_003FSourceGeneratedDocuments_003F9509A9D00FD8A232B5E86A84_003FGodot_002ESourceGenerators_003FGodot_002ESourceGenerators_002EScriptMethodsGenerator_003FBabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptMethods_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FLocal_003FTemp_003FSourceGeneratedDocuments_003F9509A9D00FD8A232B5E86A84_003FGodot_002ESourceGenerators_003FGodot_002ESourceGenerators_002EScriptPropertiesGenerator_003FBabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F4298b0f293f987511fc1b7956ee691fd778f8378_003FBabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F4298b0f293f987511fc1b7956ee691fd778f8378_003FBabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EQuest_002EQuestListItemUi_005FScriptMethods_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F48fad7e7f3c9e292b3fdbddf9d363f0d1752aa_003FBabushka_002Escripts_002ECSharp_002ECommon_002EQuest_002EQuestListItemUi_005FScriptMethods_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EQuest_002EQuestManager_005FScriptSignals_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FLocal_003FTemp_003FSourceGeneratedDocuments_003F9509A9D00FD8A232B5E86A84_003FGodot_002ESourceGenerators_003FGodot_002ESourceGenerators_002EScriptSignalsGenerator_003FBabushka_002Escripts_002ECSharp_002ECommon_002EQuest_002EQuestManager_005FScriptSignals_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACanvasItem_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fef7b819b226fab796d1dfe66d415dd7510bcac87675020ddb8f03a828e763_003FCanvasItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACastHelpers_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F3c92637ae2e83da0a63791071c41eae291d594156062866d8621b7ed7245c_003FCastHelpers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACastHelpers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd111abf504bf42b5968a609b168fd093b2e200_003Fbb_003F1c116fcd_003FCastHelpers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACharacterBody2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fbba0bbd7a98ee58286e9484fbe86e01afff6232283f6efd3556eb7116453_003FCharacterBody2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACount_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Ffe5a7cee5a1771b89077bd73292de84439b4f816799e2ad6c2615c6ff5bd748e_003FCount_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADictionary_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fjonathan_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe37dc1faf08a4d5ea030ad59bdf77522523400_003Fd4_003Fbd338aeb_003FDictionary_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExportToolButtonAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe37dc1faf08a4d5ea030ad59bdf77522523400_003F31_003F3e05ef15_003FExportToolButtonAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGD_005Fconstants_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fdf61b6148ca71eef8cf4e56545a9319fd2647f05d2f959dad42d9fcbe95192_003FGD_005Fconstants_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMustBeVariantAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe37dc1faf08a4d5ea030ad59bdf77522523400_003Fda_003Fbb06d681_003FMustBeVariantAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGD_005Fconstants_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F4ef0bac6437b6a9567d44f62ae567d854fa7b8513ef7139ef349b49768bc9df_003FGD_005Fconstants_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Ff1d69ec2da76ccf9bc8a75c8e0fdca9a7ba1adf8c8c9d5047e2fa5991c02eca_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResourceLoader_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F9f4e8eb124d11f8219cb513a19bed22b2120ed29f9d6785ba56e3367b48d581_003FResourceLoader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResourceLoader_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F9f4e8eb124d11f8219cb513a19bed22b2120ed29f9d6785ba56e3367b48d581_003FResourceLoader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc7102cd0ffb8973777e61b1942c3fffac7e14016a511d055c3adf73ff91748_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc7102cd0ffb8973777e61b1942c3fffac7e14016a511d055c3adf73ff91748_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F76fabf6f8acf4a0099cae0bcf8b218467f10_003F7e_003F28cee476_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGD_005Fconstants_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F4ef0bac6437b6a9567d44f62ae567d854fa7b8513ef7139ef349b49768bc9df_003FGD_005Fconstants_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Ff1d69ec2da76ccf9bc8a75c8e0fdca9a7ba1adf8c8c9d5047e2fa5991c02eca_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANullable_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F5acc345db3c207bc9d886a36ff14867ef8d65557432172c2a42f19aeac04d1b_003FNullable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResourceLoader_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F9f4e8eb124d11f8219cb513a19bed22b2120ed29f9d6785ba56e3367b48d581_003FResourceLoader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASceneTree_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F8d6960554e939a669841b1ece03d27df4ab42f92bb80be3767eaec8cdaccf84b_003FSceneTree_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AShape2D_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F3671dbbd9b17cdf2bf9075b468b6bd7e3ab13fc3be7a116484085d3b6cc9fe_003FShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc7102cd0ffb8973777e61b1942c3fffac7e14016a511d055c3adf73ff91748_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=bc5a80e4_002D7ba6_002D4f7d_002Db896_002Dc591eec7ab12/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Tests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;NUnit3x::A6EF2269-9E64-40D4-BA0A-33CB234E2503::net9.0::BabushkaTest.Tests&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>

@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://bcld43daavmrn"]
[ext_resource type="Script" uid="uid://cql8mt5jsmcdl" path="res://scripts/CSharp/Common/Fight/FightSceneSwitcher.cs" id="1_5dt1r"]
[node name="FightSceneSwitcher" type="Node"]
script = ExtResource("1_5dt1r")
_fightRoomScenePath = "res://scenes/Babushka_scene_fight_world_room.tscn"
_fightHappeningScene = "res://scenes/Babushka_scene_fight_happening.tscn"

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://n5cj71bxxjkk"]
[ext_resource type="Script" uid="uid://dqe1i2qmpttwf" path="res://scripts/CSharp/Common/Fight/FightWorld.cs" id="1_tnyce"]
[node name="FightWorldAutoload" type="Node2D"]
script = ExtResource("1_tnyce")

@ -0,0 +1,18 @@
[gd_scene load_steps=4 format=3 uid="uid://0vm3jb1hnkkb"]
[ext_resource type="PackedScene" uid="uid://7jsxokx67gpq" path="res://prefabs/fight/fighterVisuals/vesna_fighter_visual.tscn" id="1_80xdf"]
[ext_resource type="Texture2D" uid="uid://ccrnmx6bd842k" path="res://art/characters/farm fäulnis blobs.png" id="2_ba6tr"]
[sub_resource type="AtlasTexture" id="AtlasTexture_ane0o"]
atlas = ExtResource("2_ba6tr")
region = Rect2(1133.19, 93.65, 460.526, 347.391)
[node name="BlobFighterVisual" instance=ExtResource("1_80xdf")]
[node name="Sprite2D" parent="Visuals" index="0"]
position = Vector2(23, -96)
scale = Vector2(0.547474, 0.547474)
texture = SubResource("AtlasTexture_ane0o")
[node name="Sprite2D" parent="TargetSelection/HoverIndicator" index="0"]
position = Vector2(1, -126)

@ -0,0 +1,72 @@
[gd_scene load_steps=9 format=3 uid="uid://7jsxokx67gpq"]
[ext_resource type="Script" uid="uid://by88f32fou7lh" path="res://scripts/CSharp/Common/Fight/FighterVisual.cs" id="1_hai27"]
[ext_resource type="Texture2D" uid="uid://f7htcxiwvuup" path="res://art/animation/vesna/Side/S01-Idle/0001.png" id="2_6l7g5"]
[ext_resource type="Script" uid="uid://boprnfciqgixf" path="res://scripts/CSharp/Common/Fight/UI/TargetSelectionClick.cs" id="3_wil2y"]
[ext_resource type="Texture2D" uid="uid://qlfwuakhe57t" path="res://art/ui/UI/attack_select_wheel.png" id="4_8ldlc"]
[ext_resource type="Script" uid="uid://b2dx06p6i7pu0" path="res://scripts/CSharp/Common/Fight/UI/FighterHealthBarVisual.cs" id="5_xv37w"]
[sub_resource type="AtlasTexture" id="AtlasTexture_wil2y"]
atlas = ExtResource("2_6l7g5")
region = Rect2(60.818, 51.0213, 660.226, 945.537)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_6l7g5"]
size = Vector2(250, 401)
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_6l7g5"]
radius = 173.0
height = 588.0
[node name="VesnaFighterVisual" type="Node2D" node_paths=PackedStringArray("_visualParent", "_targetSelectionParent", "healthBarVisual")]
script = ExtResource("1_hai27")
_visualParent = NodePath("Visuals")
_targetSelectionParent = NodePath("TargetSelection")
healthBarVisual = NodePath("HealthBar")
[node name="Visuals" type="Node2D" parent="."]
[node name="Sprite2D" type="Sprite2D" parent="Visuals"]
position = Vector2(-31, -199)
scale = Vector2(0.451719, 0.451719)
texture = SubResource("AtlasTexture_wil2y")
[node name="ChacacterSizeIndicator" type="CollisionShape2D" parent="Visuals"]
editor_description = "This is a reference to the space, a normal sized humanoid character should occupy"
visible = false
position = Vector2(0, -200.5)
shape = SubResource("RectangleShape2D_6l7g5")
[node name="TargetSelection" type="Node2D" parent="."]
process_mode = 4
visible = false
[node name="Click" type="Area2D" parent="TargetSelection"]
script = ExtResource("3_wil2y")
[node name="CollisionShape2D" type="CollisionShape2D" parent="TargetSelection/Click"]
position = Vector2(-3, -195)
shape = SubResource("CapsuleShape2D_6l7g5")
[node name="HoverIndicator" type="Node2D" parent="TargetSelection"]
visible = false
[node name="Sprite2D" type="Sprite2D" parent="TargetSelection/HoverIndicator"]
position = Vector2(-3, -227)
scale = Vector2(1.65625, 1.65625)
texture = ExtResource("4_8ldlc")
[node name="HealthBar" type="Node2D" parent="." node_paths=PackedStringArray("_tmpHealthLabel")]
script = ExtResource("5_xv37w")
_tmpHealthLabel = NodePath("tmpLabel")
[node name="tmpLabel" type="Label" parent="HealthBar"]
offset_left = -110.0
offset_top = -528.0
offset_right = 111.0
offset_bottom = -431.0
theme_override_font_sizes/font_size = 71
text = "xx / xx"
[connection signal="TargetSelected" from="TargetSelection/Click" to="." method="ClickedTarget"]
[connection signal="mouse_entered" from="TargetSelection/Click" to="TargetSelection/HoverIndicator" method="show"]
[connection signal="mouse_exited" from="TargetSelection/Click" to="TargetSelection/HoverIndicator" method="hide"]

@ -1,123 +0,0 @@
[gd_scene load_steps=11 format=3 uid="uid://cpanatqdjjpa3"]
[ext_resource type="Script" uid="uid://by88f32fou7lh" path="res://scripts/CSharp/Common/Fight/Fighter.cs" id="1_f3j2x"]
[ext_resource type="Texture2D" uid="uid://om2axn1vfa5o" path="res://art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0001.png" id="2_2ud32"]
[ext_resource type="Texture2D" uid="uid://qlfwuakhe57t" path="res://art/ui/UI/attack_select_wheel.png" id="3_80knd"]
[ext_resource type="AudioStream" uid="uid://ch4c1wh4ghxyo" path="res://audio/sfx/Battle/Vesna/SFX_Battle_Vesna_Defense_08.wav" id="5_4r2vf"]
[ext_resource type="Texture2D" uid="uid://6h85o7fj7gmu" path="res://art/animation/Vesna3D/vesna-more-tools.png" id="5_l04qi"]
[ext_resource type="Texture2D" uid="uid://crak7ton4lab0" path="res://art/ui/UI/EmptyFightButton.png" id="5_rjjub"]
[ext_resource type="Texture2D" uid="uid://c3wht0nakaki1" path="res://art/ui/UI/icons/icon-fruit-tomatoe.png" id="6_l04qi"]
[ext_resource type="AudioStream" uid="uid://ccionrfr6e3lb" path="res://audio/sfx/Battle/Vesna/SFX_Battle_Vesna_Attack_04.wav" id="6_u1ayv"]
[sub_resource type="CircleShape2D" id="CircleShape2D_0tqnl"]
radius = 291.58
[sub_resource type="CircleShape2D" id="CircleShape2D_4r2vf"]
radius = 173.44
[node name="VesnaFighter" type="Node2D" node_paths=PackedStringArray("_attackButtons", "_targetButtons", "_targetMarker", "_healthText", "_visualSprite")]
script = ExtResource("1_f3j2x")
name = "Vesna"
maxHealth = 100
attackStrength = 10
_attackButtons = NodePath("FightButtons")
_targetButtons = NodePath("TargetButtons")
_targetMarker = NodePath("TargetButtons/TargetMarker")
_healthText = NodePath("HealthShow")
_visualSprite = NodePath("Sprite2D")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("2_2ud32")
offset = Vector2(43, -379)
[node name="TargetButtons" type="Node2D" parent="."]
visible = false
[node name="TargetMarker" type="Sprite2D" parent="TargetButtons"]
visible = false
z_index = 200
position = Vector2(8, -122)
scale = Vector2(4.245, 4.245)
texture = ExtResource("3_80knd")
[node name="Area2D" type="Area2D" parent="TargetButtons"]
position = Vector2(3, -76)
[node name="CollisionShape2D" type="CollisionShape2D" parent="TargetButtons/Area2D"]
shape = SubResource("CircleShape2D_0tqnl")
[node name="FightButtons" type="Node2D" parent="."]
position = Vector2(3, -714)
[node name="CircleBackground" type="Sprite2D" parent="FightButtons"]
z_index = 200
scale = Vector2(4.245, 4.245)
texture = ExtResource("3_80knd")
[node name="AttackButton" type="Node2D" parent="FightButtons"]
position = Vector2(0, -536)
[node name="ButtonBackground" type="Sprite2D" parent="FightButtons/AttackButton"]
z_index = 200
scale = Vector2(2.48, 2.48)
texture = ExtResource("5_rjjub")
[node name="Sprite2D" type="Sprite2D" parent="FightButtons/AttackButton/ButtonBackground"]
position = Vector2(6.04839, -10.0806)
rotation = -1.0088
scale = Vector2(0.249373, 0.25079)
texture = ExtResource("5_l04qi")
region_enabled = true
region_rect = Rect2(291.897, 15.8974, 272.82, 479.385)
[node name="Area2D" type="Area2D" parent="FightButtons/AttackButton"]
collision_layer = 16
[node name="CollisionShape2D" type="CollisionShape2D" parent="FightButtons/AttackButton/Area2D"]
shape = SubResource("CircleShape2D_4r2vf")
[node name="HealButton" type="Node2D" parent="FightButtons"]
position = Vector2(427, -334)
[node name="ButtonBackground" type="Sprite2D" parent="FightButtons/HealButton"]
z_index = 200
scale = Vector2(2.48, 2.48)
texture = ExtResource("5_rjjub")
[node name="Sprite2D" type="Sprite2D" parent="FightButtons/HealButton/ButtonBackground"]
position = Vector2(4.83871, 0)
scale = Vector2(0.279343, 0.279343)
texture = ExtResource("6_l04qi")
[node name="Area2D" type="Area2D" parent="FightButtons/HealButton"]
collision_layer = 16
[node name="CollisionShape2D" type="CollisionShape2D" parent="FightButtons/HealButton/Area2D"]
shape = SubResource("CircleShape2D_4r2vf")
[node name="HealthShow" type="Label" parent="."]
z_index = 200
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -197.0
offset_top = -947.0
offset_right = 207.0
offset_bottom = -801.0
grow_horizontal = 2
grow_vertical = 2
theme_override_font_sizes/font_size = 106
text = "100/100"
horizontal_alignment = 1
vertical_alignment = 1
[node name="HitAudio" type="AudioStreamPlayer" parent="."]
stream = ExtResource("5_4r2vf")
[node name="AttackAudio" type="AudioStreamPlayer" parent="."]
stream = ExtResource("6_u1ayv")
[connection signal="Attacking" from="." to="AttackAudio" method="play" binds= [0.0]]
[connection signal="DamageTaken" from="." to="HitAudio" method="play" binds= [0.0]]
[connection signal="input_event" from="FightButtons/AttackButton/Area2D" to="." method="AttackMouseEvent"]
[connection signal="input_event" from="FightButtons/HealButton/Area2D" to="." method="HealMouseEvent"]

@ -0,0 +1,51 @@
[gd_scene load_steps=9 format=3 uid="uid://qfdiudt3vpai"]
[ext_resource type="Script" uid="uid://lequnojtar76" path="res://scripts/CSharp/Common/Fight/RoamingEnemyGroup.cs" id="1_t3mrx"]
[ext_resource type="Texture2D" uid="uid://ccrnmx6bd842k" path="res://art/characters/farm fäulnis blobs.png" id="2_6ftwg"]
[ext_resource type="Texture2D" uid="uid://bexymddkb6l0o" path="res://art/characters/Mavka/mavkha.png" id="3_xi5g8"]
[ext_resource type="PackedScene" uid="uid://cqc72e4hq6bcd" path="res://prefabs/interactions/interaction_area_2d.tscn" id="4_xi5g8"]
[sub_resource type="AtlasTexture" id="AtlasTexture_c8fs8"]
atlas = ExtResource("2_6ftwg")
region = Rect2(1747.17, 156.157, 311.249, 280.596)
[sub_resource type="AtlasTexture" id="AtlasTexture_kbgcx"]
atlas = ExtResource("3_xi5g8")
region = Rect2(774.378, 151.512, 1097.48, 1412.67)
[sub_resource type="AtlasTexture" id="AtlasTexture_6fvcb"]
atlas = ExtResource("2_6ftwg")
region = Rect2(1149.47, 92.492, 445.652, 353.692)
[sub_resource type="CircleShape2D" id="CircleShape2D_xi5g8"]
resource_local_to_scene = true
radius = 402.0112
[node name="RoamingEnemyGroup" type="Node2D"]
script = ExtResource("1_t3mrx")
[node name="Visuals" type="Node2D" parent="."]
[node name="Sprite2D" type="Sprite2D" parent="Visuals"]
position = Vector2(99, -222)
texture = SubResource("AtlasTexture_c8fs8")
[node name="Sprite2D3" type="Sprite2D" parent="Visuals"]
position = Vector2(-108, -337)
scale = Vector2(0.43, 0.43)
texture = SubResource("AtlasTexture_kbgcx")
[node name="Sprite2D2" type="Sprite2D" parent="Visuals"]
position = Vector2(-41, -109)
texture = SubResource("AtlasTexture_6fvcb")
[node name="InteractionArea" parent="." node_paths=PackedStringArray("_spritesToOutline") instance=ExtResource("4_xi5g8")]
_spritesToOutline = [NodePath("../Visuals/Sprite2D"), NodePath("../Visuals/Sprite2D3"), NodePath("../Visuals/Sprite2D2")]
[node name="CollisionShape3D" parent="InteractionArea/Area2D" index="0"]
position = Vector2(-49, -174)
shape = SubResource("CircleShape2D_xi5g8")
[connection signal="Interacted" from="InteractionArea" to="." method="StartFight"]
[editable path="InteractionArea"]

@ -1,7 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://hk8ahyp6dgl6"]
[ext_resource type="Script" uid="uid://c76mhhqyk4lgh" path="res://scripts/CSharp/Common/Fight/FightInstance.cs" id="1_ygesl"]
[ext_resource type="Script" uid="uid://oe1uypehqvr7" path="res://scripts/CSharp/Common/Fight/FightStateManager.cs" id="2_b0ivm"]
[ext_resource type="Script" uid="uid://c76mhhqyk4lgh" path="res://scripts/CSharp/Common/FightOld/FightInstance.cs" id="1_ygesl"]
[ext_resource type="Script" uid="uid://oe1uypehqvr7" path="res://scripts/CSharp/Common/FightOld/FightStateManager.cs" id="2_b0ivm"]
[node name="FightBaseScene" type="Node2D" node_paths=PackedStringArray("_friendlyFightSpots", "_enemyFightSpots", "camPositionNode", "_fightStateManager", "_fightEndText")]
script = ExtResource("1_ygesl")

@ -1,7 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://cnjsepvaqdbyq"]
[ext_resource type="Script" uid="uid://j5ge24rk25wm" path="res://scripts/CSharp/Common/Fight/FightManager.cs" id="1_8p7ev"]
[ext_resource type="PackedScene" uid="uid://cpanatqdjjpa3" path="res://prefabs/fight/fighters/vesna_fighter.tscn" id="2_ak1vo"]
[ext_resource type="Script" uid="uid://j5ge24rk25wm" path="res://scripts/CSharp/Common/FightOld/FightManager.cs" id="1_8p7ev"]
[ext_resource type="PackedScene" uid="uid://cpanatqdjjpa3" path="res://prefabs/fightOld/fighters/vesna_fighter.tscn" id="2_ak1vo"]
[node name="FightManagerAutoload" type="Node"]
script = ExtResource("1_8p7ev")

@ -1,7 +1,7 @@
[gd_scene load_steps=10 format=3 uid="uid://bp64p6y72j71w"]
[ext_resource type="Texture2D" uid="uid://ccrnmx6bd842k" path="res://art/characters/farm fäulnis blobs.png" id="1_vem8k"]
[ext_resource type="Script" uid="uid://by88f32fou7lh" path="res://scripts/CSharp/Common/Fight/Fighter.cs" id="2_4w1ab"]
[ext_resource type="Script" path="res://scripts/CSharp/Common/FightOld/Fighter.cs" id="2_4w1ab"]
[ext_resource type="Texture2D" uid="uid://qlfwuakhe57t" path="res://art/ui/UI/attack_select_wheel.png" id="3_v4f83"]
[ext_resource type="AudioStream" uid="uid://x7cc5woop5ec" path="res://audio/sfx/Battle/Enemies/SFX_Slime_Hit_V2_03.wav" id="5_v4f83"]
[ext_resource type="Texture2D" uid="uid://bn56p0ytuo060" path="res://art/ui/UI/AttackButton.png" id="5_vbkts"]
@ -19,6 +19,7 @@ script = ExtResource("2_4w1ab")
name = "Blob"
maxHealth = 20
attackStrength = 10
maxActions = null
_attackButtons = NodePath("FightButtons")
_targetButtons = NodePath("TargetButtons")
_targetMarker = NodePath("TargetButtons/TargetMarker")
@ -95,9 +96,6 @@ stream = ExtResource("6_vbkts")
[node name="AttackAudio" type="AudioStreamPlayer" parent="."]
stream = ExtResource("6_oykh8")
[connection signal="Attacking" from="." to="AttackAudio" method="play" binds= [0.0]]
[connection signal="DamageTaken" from="." to="HitAudio" method="play" binds= [0.0]]
[connection signal="Dying" from="." to="DieAudio" method="play" binds= [0.0]]
[connection signal="input_event" from="TargetButtons/Area2D" to="." method="TargetMouseEvent"]
[connection signal="mouse_entered" from="TargetButtons/Area2D" to="." method="StartHoverTarget"]
[connection signal="mouse_exited" from="TargetButtons/Area2D" to="." method="EndHoverTarget"]

@ -1,6 +1,6 @@
[gd_scene load_steps=10 format=3 uid="uid://cr66tpdr5rma5"]
[ext_resource type="Script" uid="uid://by88f32fou7lh" path="res://scripts/CSharp/Common/Fight/Fighter.cs" id="1_t6th8"]
[ext_resource type="Script" uid="uid://by88f32fou7lh" path="res://scripts/CSharp/Common/Fight/FighterVisual.cs" id="1_t6th8"]
[ext_resource type="Texture2D" uid="uid://bexymddkb6l0o" path="res://art/characters/Mavka/mavkha.png" id="2_shg7p"]
[ext_resource type="Texture2D" uid="uid://qlfwuakhe57t" path="res://art/ui/UI/attack_select_wheel.png" id="3_nkuei"]
[ext_resource type="Texture2D" uid="uid://bn56p0ytuo060" path="res://art/ui/UI/AttackButton.png" id="4_u3hw3"]
@ -14,16 +14,8 @@ radius = 388.063
[sub_resource type="CircleShape2D" id="CircleShape2D_s74nc"]
radius = 173.44
[node name="EnemyMavkhaFighter" type="Node2D" node_paths=PackedStringArray("_attackButtons", "_targetButtons", "_targetMarker", "_healthText", "_visualSprite")]
[node name="EnemyMavkhaFighter" type="Node2D"]
script = ExtResource("1_t6th8")
name = "Mavkha"
maxHealth = 40
attackStrength = 10
_attackButtons = NodePath("FightButtons")
_targetButtons = NodePath("TargetButtons")
_targetMarker = NodePath("TargetButtons/TargetMarker")
_healthText = NodePath("HealthShow")
_visualSprite = NodePath("Visual")
[node name="Visual" type="Node2D" parent="."]

@ -0,0 +1,64 @@
[gd_scene load_steps=7 format=3 uid="uid://cpanatqdjjpa3"]
[ext_resource type="Script" path="res://scripts/CSharp/Common/FightOld/Fighter.cs" id="1_f3j2x"]
[ext_resource type="Texture2D" uid="uid://om2axn1vfa5o" path="res://art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0001.png" id="2_2ud32"]
[ext_resource type="Texture2D" uid="uid://qlfwuakhe57t" path="res://art/ui/UI/attack_select_wheel.png" id="3_80knd"]
[ext_resource type="AudioStream" uid="uid://ch4c1wh4ghxyo" path="res://audio/sfx/Battle/Vesna/SFX_Battle_Vesna_Defense_08.wav" id="5_4r2vf"]
[ext_resource type="AudioStream" uid="uid://ccionrfr6e3lb" path="res://audio/sfx/Battle/Vesna/SFX_Battle_Vesna_Attack_04.wav" id="6_u1ayv"]
[sub_resource type="CircleShape2D" id="CircleShape2D_0tqnl"]
radius = 291.58
[node name="VesnaFighter" type="Node2D" node_paths=PackedStringArray("_attackButtons", "_targetButtons", "_targetMarker", "_healthText", "_visualSprite")]
script = ExtResource("1_f3j2x")
name = "Vesna"
maxHealth = 100
attackStrength = 10
maxActions = null
_attackButtons = NodePath("")
_targetButtons = NodePath("TargetButtons")
_targetMarker = NodePath("TargetButtons/TargetMarker")
_healthText = NodePath("HealthShow")
_visualSprite = NodePath("Sprite2D")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("2_2ud32")
offset = Vector2(43, -379)
[node name="TargetButtons" type="Node2D" parent="."]
visible = false
[node name="TargetMarker" type="Sprite2D" parent="TargetButtons"]
visible = false
z_index = 200
position = Vector2(8, -122)
scale = Vector2(4.245, 4.245)
texture = ExtResource("3_80knd")
[node name="Area2D" type="Area2D" parent="TargetButtons"]
position = Vector2(3, -76)
[node name="CollisionShape2D" type="CollisionShape2D" parent="TargetButtons/Area2D"]
shape = SubResource("CircleShape2D_0tqnl")
[node name="HealthShow" type="Label" parent="."]
z_index = 200
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -197.0
offset_top = -947.0
offset_right = 207.0
offset_bottom = -801.0
grow_horizontal = 2
grow_vertical = 2
theme_override_font_sizes/font_size = 106
text = "100/100"
horizontal_alignment = 1
vertical_alignment = 1
[node name="HitAudio" type="AudioStreamPlayer" parent="."]
stream = ExtResource("5_4r2vf")
[node name="AttackAudio" type="AudioStreamPlayer" parent="."]
stream = ExtResource("6_u1ayv")

@ -0,0 +1,38 @@
[gd_scene load_steps=7 format=3 uid="uid://bydwj3pbvqrhb"]
[ext_resource type="Script" uid="uid://ct7l4er2kljnc" path="res://scripts/CSharp/Common/Minigame/MinigameController.cs" id="1_17v35"]
[ext_resource type="PackedScene" uid="uid://dhfda4o386byp" path="res://prefabs/minigame/region_visual.tscn" id="2_rrvb1"]
[ext_resource type="Script" uid="uid://djkyrp24ljff0" path="res://scripts/CSharp/Common/Minigame/SpinnyArmVisual.cs" id="3_86pvs"]
[ext_resource type="Texture2D" uid="uid://bgn2ci6nu85t5" path="res://addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png" id="3_pe4tw"]
[ext_resource type="Script" uid="uid://dq7gahfp0lk7v" path="res://scripts/CSharp/Common/Util/ClickDetect.cs" id="5_86pvs"]
[sub_resource type="CircleShape2D" id="CircleShape2D_pe4tw"]
radius = 554.923
[node name="Minigame" type="Node2D" node_paths=PackedStringArray("_regionsParent")]
script = ExtResource("1_17v35")
_regionVisualPrefab = ExtResource("2_rrvb1")
_regionsParent = NodePath("RegionsParent")
_baseRegionColor = Color(0.176888, 0.482224, 0.338857, 1)
[node name="RegionsParent" type="Node2D" parent="."]
[node name="SpinnyArm" type="Node2D" parent="."]
script = ExtResource("3_86pvs")
[node name="ArmVisual" type="Node2D" parent="SpinnyArm"]
[node name="Sprite" type="Sprite2D" parent="SpinnyArm/ArmVisual"]
position = Vector2(0, -40)
rotation = -3.14159
scale = Vector2(1, 2.08)
texture = ExtResource("3_pe4tw")
[node name="Area2D" type="Area2D" parent="."]
script = ExtResource("5_86pvs")
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
shape = SubResource("CircleShape2D_pe4tw")
[connection signal="ArmMoved" from="." to="SpinnyArm" method="SetAngle"]
[connection signal="Click" from="Area2D" to="." method="Hit"]

@ -0,0 +1,52 @@
[gd_scene load_steps=5 format=3 uid="uid://dhfda4o386byp"]
[ext_resource type="Script" uid="uid://cdpsa4qrlai31" path="res://scripts/CSharp/Common/Minigame/RegionVisual.cs" id="1_4ymj8"]
[ext_resource type="Shader" uid="uid://d0dayn7dc885j" path="res://shader/minigame_pie_section.gdshader" id="1_8p2xn"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_86pvs"]
resource_local_to_scene = true
shader = ExtResource("1_8p2xn")
shader_parameter/textureSize = 400.0
shader_parameter/fillColor = Color(0.285466, 0.685481, 0.880632, 1)
shader_parameter/borderColor = Color(1, 1, 1, 1)
shader_parameter/borderWidth = 0.025
shader_parameter/angles = Vector2(0.01, 0.18)
shader_parameter/smoothWidth = 0.0052
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_0navw"]
size = Vector2(400, 400)
[node name="RegionVisual" type="Node2D" node_paths=PackedStringArray("_sliceSprite", "_textLabel", "_labelPivot")]
script = ExtResource("1_4ymj8")
_sliceSprite = NodePath("Sprite2D")
_textLabel = NodePath("LabelPivot/Label")
_labelPivot = NodePath("LabelPivot")
_fillColors = Dictionary[int, Color]({
0: Color(0.427493, 0.427493, 0.427493, 1),
1: Color(0.675735, 0.105671, 0.0799616, 1),
2: Color(0.645128, 0.346481, 0.215967, 1),
3: Color(0.754619, 0.665655, 0.384568, 1),
4: Color(0.365769, 0.400285, 0.598083, 1),
5: Color(0.222174, 0.457454, 0.45483, 1),
6: Color(0.316126, 0.448834, 0.312852, 1),
7: Color(0.244391, 0.640687, 0.283315, 1)
})
[node name="Sprite2D" type="Sprite2D" parent="."]
material = SubResource("ShaderMaterial_86pvs")
texture = SubResource("PlaceholderTexture2D_0navw")
[node name="LabelPivot" type="Node2D" parent="."]
[node name="Label" type="Label" parent="LabelPivot"]
custom_minimum_size = Vector2(100, 40)
offset_left = -50.0
offset_top = -240.0
offset_right = 50.0
offset_bottom = -200.0
pivot_offset = Vector2(50, 20)
size_flags_horizontal = 0
theme_override_font_sizes/font_size = 21
text = "test"
horizontal_alignment = 1
vertical_alignment = 1

@ -28,10 +28,10 @@ buses/default_bus_layout="uid://b6dwkmkyb0axk"
SceneTransition="*res://scenes/SceneTransition.tscn"
Dialogic="*res://addons/dialogic/Core/DialogicGameHandler.gd"
InventoryManager="*res://scripts/CSharp/Common/Inventory/InventoryManager.cs"
QuestManager="*res://prefabs/quests/quest_manager_autoload.tscn"
FightManagerAutoload="*res://prefabs/fight/fight_manager_autoload.tscn"
InputService="*res://scripts/CSharp/Common/Services/InputService.cs"
QuestManager="*res://prefabs/quests/quest_manager_autoload.tscn"
Signal_Debugger="*res://addons/SignalVisualizer/Debugger/SignalDebugger.gd"
FightWorldAutoload="*res://prefabs/fight/fight_world_autoload.tscn"
[dialogic]

@ -5,5 +5,6 @@
[node name="BabushkaSceneBootstrap" type="Node2D"]
[node name="BabushkaSceneStartMenu" parent="." instance=ExtResource("1_15ton")]
_sceneNamesToLoad = PackedStringArray("res://scenes/Babushka_scene_fight_world_room.tscn")
[node name="SceneParent" type="Node" parent="."]

@ -0,0 +1,220 @@
[gd_scene load_steps=14 format=3 uid="uid://cjshlwk8ajpnp"]
[ext_resource type="Script" uid="uid://cnhpnn8o0gybd" path="res://scripts/CSharp/Common/Fight/FightHappeningSceneSetup.cs" id="1_fiutj"]
[ext_resource type="Script" uid="uid://c76mhhqyk4lgh" path="res://scripts/CSharp/Common/Fight/FightHappening.cs" id="1_gsk03"]
[ext_resource type="Script" uid="uid://dtf4ejct4m682" path="res://scripts/CSharp/Common/Fight/ActionAnimationController.cs" id="2_7kjgs"]
[ext_resource type="Script" uid="uid://dwsqst8fhhqlc" path="res://scripts/CSharp/Common/Fight/AllFightersVisual.cs" id="2_lu4y4"]
[ext_resource type="PackedScene" uid="uid://bcld43daavmrn" path="res://prefabs/fight/fight_scene_switcher.tscn" id="2_phrlx"]
[ext_resource type="PackedScene" uid="uid://7jsxokx67gpq" path="res://prefabs/fight/fighterVisuals/vesna_fighter_visual.tscn" id="4_qo0gi"]
[ext_resource type="PackedScene" uid="uid://0vm3jb1hnkkb" path="res://prefabs/fight/fighterVisuals/blob_fighter_visual.tscn" id="4_vp8s0"]
[ext_resource type="Script" uid="uid://buiwuf7pjfq8" path="res://scripts/CSharp/Common/Fight/FightHappeningStateReaction.cs" id="4_ydj1i"]
[ext_resource type="PackedScene" uid="uid://bydwj3pbvqrhb" path="res://prefabs/minigame/minigame.tscn" id="8_2b3cf"]
[ext_resource type="Script" uid="uid://byf2ywov34g0x" path="res://scripts/CSharp/Common/Fight/UI/ActionSelectUiSetup.cs" id="8_bkwsr"]
[ext_resource type="Script" uid="uid://bwm0nhvt1083k" path="res://scripts/CSharp/Common/Fight/FightMinigameHandler.cs" id="8_falfe"]
[ext_resource type="Script" uid="uid://d2ugtb3dalrg3" path="res://scripts/CSharp/Common/Fight/FightHappeningStateDebugger.cs" id="8_tv7cl"]
[ext_resource type="Script" uid="uid://2f7rqk50gtdg" path="res://scripts/CSharp/Common/Fight/SwitchSceneOnFightEnd.cs" id="10_qqd8u"]
[node name="BabushkaSceneFightHappening" type="Node2D"]
[node name="FightHappening" type="Node" parent="."]
script = ExtResource("1_gsk03")
[node name="ActionAnimationController" type="Node" parent="." node_paths=PackedStringArray("_allFightersVisual")]
script = ExtResource("2_7kjgs")
_allFightersVisual = NodePath("../FightVisuals")
[node name="StateReactionActionAnimation" type="Node" parent="ActionAnimationController"]
script = ExtResource("4_ydj1i")
_fightState = 10
[node name="Camera2D" type="Camera2D" parent="."]
[node name="FightSetup" type="Node2D" parent="."]
script = ExtResource("1_fiutj")
[node name="FightVisuals" type="Node2D" parent="." node_paths=PackedStringArray("_allyFighters", "_enemyFighters")]
position = Vector2(0, 259)
script = ExtResource("2_lu4y4")
_allyFighters = NodePath("AllyFighters")
_enemyFighters = NodePath("EnemyFighters")
_blobFighterVisual = ExtResource("4_vp8s0")
_vesnaFighterVisual = ExtResource("4_qo0gi")
_positionDistanceFromCenter = PackedFloat32Array(300, 550, 800)
[node name="AllyFighters" type="Node2D" parent="FightVisuals"]
[node name="EnemyFighters" type="Node2D" parent="FightVisuals"]
[node name="EnvironmentVisuals" type="Node2D" parent="."]
[node name="MinigameHandler" type="Node2D" parent="." node_paths=PackedStringArray("_minigameController")]
script = ExtResource("8_falfe")
_minigameController = NodePath("Minigame")
[node name="Minigame" parent="MinigameHandler" instance=ExtResource("8_2b3cf")]
process_mode = 4
visible = false
[node name="SwitchSceneOnFightEnd" type="Node" parent="." node_paths=PackedStringArray("_fightSceneSwitcher")]
script = ExtResource("10_qqd8u")
_fightSceneSwitcher = NodePath("FightSceneSwitcher")
[node name="FightSceneSwitcher" parent="SwitchSceneOnFightEnd" node_paths=PackedStringArray("_sceneRoot") instance=ExtResource("2_phrlx")]
_sceneRoot = NodePath("../..")
[node name="ActionSelect" type="CanvasLayer" parent="." node_paths=PackedStringArray("_attackActionButton", "_summonActionButton", "_talkActionButton", "_fleeActionButton")]
visible = false
script = ExtResource("8_bkwsr")
_attackActionButton = NodePath("BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer/AttackButton")
_summonActionButton = NodePath("BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer2/Summon Button")
_talkActionButton = NodePath("BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer3/Talk Button")
_fleeActionButton = NodePath("BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer4/Flee Button")
[node name="BottomPanel" type="Control" parent="ActionSelect"]
custom_minimum_size = Vector2(0, 200)
layout_mode = 3
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 0
size_flags_vertical = 8
[node name="background" type="ColorRect" parent="ActionSelect/BottomPanel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(1, 1, 1, 0.27451)
[node name="VBoxContainer" type="VBoxContainer" parent="ActionSelect/BottomPanel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="MarginContainer" type="MarginContainer" parent="ActionSelect/BottomPanel/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/margin_left = 200
theme_override_constants/margin_right = 200
[node name="HBoxContainer" type="HBoxContainer" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer"]
layout_mode = 2
alignment = 1
[node name="MarginContainer" type="MarginContainer" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 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="AttackButton" type="Button" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 41
text = "Attack"
[node name="MarginContainer2" type="MarginContainer" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 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="Summon Button" type="Button" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer2"]
layout_mode = 2
theme_override_font_sizes/font_size = 41
text = "Summon"
[node name="MarginContainer3" type="MarginContainer" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 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="Talk Button" type="Button" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer3"]
layout_mode = 2
theme_override_font_sizes/font_size = 41
text = "Talk"
[node name="MarginContainer4" type="MarginContainer" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 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="Flee Button" type="Button" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer4"]
layout_mode = 2
theme_override_font_sizes/font_size = 41
text = "Flee"
[node name="MarginContainer2" type="MarginContainer" parent="ActionSelect/BottomPanel/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/margin_left = 200
theme_override_constants/margin_right = 200
[node name="MarginContainer" type="MarginContainer" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer2"]
layout_mode = 2
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="Label" type="Label" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer2/MarginContainer"]
layout_mode = 2
size_flags_vertical = 1
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 41
text = "This text explains the currently hovered button"
[node name="StateReactionInputActionSelect" type="Node" parent="ActionSelect"]
script = ExtResource("4_ydj1i")
_fightState = 6
[node name="StateMachineDebugger" type="Node" parent="." node_paths=PackedStringArray("_label", "_current")]
script = ExtResource("8_tv7cl")
_label = NodePath("Label")
_current = NodePath("Current")
[node name="Label" type="Label" parent="StateMachineDebugger"]
offset_left = -982.0
offset_top = -497.0
offset_right = -893.0
offset_bottom = -474.0
text = "Hello world"
[node name="Current" type="Label" parent="StateMachineDebugger"]
offset_left = 705.0
offset_top = -495.0
offset_right = 794.0
offset_bottom = -472.0
text = "Hello world"
[connection signal="SignalTransitionState" from="FightHappening" to="ActionAnimationController/StateReactionActionAnimation" method="FightHappeningStateTransitioned"]
[connection signal="SignalTransitionState" from="FightHappening" to="FightVisuals" method="FightHappeningStateChange"]
[connection signal="SignalTransitionState" from="FightHappening" to="ActionSelect/StateReactionInputActionSelect" method="FightHappeningStateTransitioned"]
[connection signal="SignalTransitionState" from="FightHappening" to="StateMachineDebugger" method="StateChange"]
[connection signal="SignalTransitionToState" from="FightHappening" to="MinigameHandler" method="OnStateEnter"]
[connection signal="SignalTransitionToState" from="FightHappening" to="SwitchSceneOnFightEnd" method="OnFightStateEnter"]
[connection signal="OnStateEntered" from="ActionAnimationController/StateReactionActionAnimation" to="ActionAnimationController" method="StateEnter"]
[connection signal="OnStateExited" from="ActionAnimationController/StateReactionActionAnimation" to="ActionAnimationController" method="StateExit"]
[connection signal="pressed" from="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer/AttackButton" to="ActionSelect" method="SelectAction" binds= [1]]
[connection signal="pressed" from="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer2/Summon Button" to="ActionSelect" method="SelectAction" binds= [2]]
[connection signal="pressed" from="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer3/Talk Button" to="ActionSelect" method="SelectAction" binds= [3]]
[connection signal="pressed" from="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer4/Flee Button" to="ActionSelect" method="SelectAction" binds= [4]]
[connection signal="OnStateEntered" from="ActionSelect/StateReactionInputActionSelect" to="ActionSelect" method="show"]
[connection signal="OnStateEntered" from="ActionSelect/StateReactionInputActionSelect" to="ActionSelect" method="StateEntered"]
[connection signal="OnStateExited" from="ActionSelect/StateReactionInputActionSelect" to="ActionSelect" method="hide"]

File diff suppressed because it is too large Load Diff

@ -24,12 +24,12 @@
[ext_resource type="Texture2D" uid="uid://bmjjfd5ngptxe" path="res://art/nature/baum märz 2025/backtreebois team kennedy.png" id="23_uhyft"]
[ext_resource type="Texture2D" uid="uid://bcw6oeovkmiup" path="res://art/nature/baum märz 2025/sonnenblumeseamless.png" id="24_3fmyy"]
[ext_resource type="Shader" uid="uid://xnky830dtfsn" path="res://shader/repeat_texture.gdshader" id="25_sgom5"]
[ext_resource type="Script" uid="uid://di0xxwfw43m0i" path="res://scripts/CSharp/Common/Fight/FightStarter.cs" id="26_gg38r"]
[ext_resource type="PackedScene" uid="uid://hk8ahyp6dgl6" path="res://prefabs/fight/fight_base_scene.tscn" id="27_55b52"]
[ext_resource type="PackedScene" uid="uid://bp64p6y72j71w" path="res://prefabs/fight/fighters/enemy_blob_fighter.tscn" id="27_hfhye"]
[ext_resource type="Script" uid="uid://di0xxwfw43m0i" path="res://scripts/CSharp/Common/FightOld/FightStarter.cs" id="26_gg38r"]
[ext_resource type="PackedScene" uid="uid://hk8ahyp6dgl6" path="res://prefabs/fightOld/fight_base_scene.tscn" id="27_55b52"]
[ext_resource type="PackedScene" uid="uid://bp64p6y72j71w" path="res://prefabs/fightOld/fighters/enemy_blob_fighter.tscn" id="27_hfhye"]
[ext_resource type="PackedScene" uid="uid://c25udixd5m6l0" path="res://prefabs/characters/Player2D.tscn" id="29_3jjxs"]
[ext_resource type="PackedScene" uid="uid://ddpl8cbck7e6s" path="res://prefabs/characters/Chugar.tscn" id="29_26tkn"]
[ext_resource type="PackedScene" uid="uid://cr66tpdr5rma5" path="res://prefabs/fight/fighters/enemy_mavkha_fighter.tscn" id="29_hfhye"]
[ext_resource type="PackedScene" uid="uid://cr66tpdr5rma5" path="res://prefabs/fightOld/fighters/enemy_mavkha_fighter.tscn" id="29_hfhye"]
[ext_resource type="Resource" uid="uid://dlcmqfjvgphqu" path="res://resources/items/rake.tres" id="30_l10vl"]
[ext_resource type="Resource" uid="uid://cndd64batns31" path="res://resources/items/wateringcan.tres" id="31_c2gvt"]
[ext_resource type="Texture2D" uid="uid://dyueumlr5ltvr" path="res://art/nature/baum märz 2025/megaeichel megaast.png" id="37_gg38r"]

@ -105,6 +105,7 @@ _data = {
}
[node name="SceneTransition" type="CanvasLayer" node_paths=PackedStringArray("animationPlayer")]
layer = 100
script = ExtResource("1_e0bkc")
animationPlayer = NodePath("SceneFadeAnimation")

@ -0,0 +1,12 @@
[gd_scene load_steps=3 format=3 uid="uid://c1dsbe7ryaije"]
[ext_resource type="Script" uid="uid://iv0dbf32bfw1" path="res://scripts/CSharp/Common/TestScripts/MinigameTestStarter.cs" id="1_fwf73"]
[ext_resource type="PackedScene" uid="uid://bydwj3pbvqrhb" path="res://prefabs/minigame/minigame.tscn" id="1_wh3re"]
[node name="BabushkaSceneMinigameTest" type="Node2D" node_paths=PackedStringArray("_minigameController")]
script = ExtResource("1_fwf73")
_minigameController = NodePath("Minigame")
[node name="Minigame" parent="." instance=ExtResource("1_wh3re")]
[node name="Camera2D" type="Camera2D" parent="."]

@ -18,10 +18,13 @@ public partial class CameraController : Camera2D
[Export] private Node2D _followNode;
public FightInstance? fightToShow;
public FightHappening? fightToShow;
public override void _Process(double delta)
{
this.GlobalPosition = fightToShow?.camPositionNode.GlobalPosition ?? _followNode.GlobalPosition;
this.GlobalPosition = /*fightToShow?.camPositionNode.GlobalPosition ??*/ _followNode.GlobalPosition;
}
}

@ -1,4 +1,5 @@
using System;
using System.Linq;
using Babushka.scripts.CSharp.Common.Services;
using Godot;
@ -11,19 +12,21 @@ public partial class InteractionArea2D : Node2D
[Export] private bool _active = true;
[Export] private bool _useOutline = true;
[Export] private ShaderMaterial _outlineMaterial;
[Export] private bool _useSprite = true;
[Export] private CanvasItem _spriteToOutline;
[Export] private CanvasItem? _spriteToOutline; // keep to not break old usages. TODO: remove later
[Export] private CanvasItem[] _spritesToOutline;
[Export] private bool _showLabel = true;
[Export] private int _id = -1; // TODO: remove
private Material _backupMaterial;
private Material[] _backupMaterials;
[Signal] public delegate void InteractedToolEventHandler(int id); // TODO: remove
[Signal] public delegate void InteractedEventHandler();
public bool IsActive
{
get => _active;
set => _active = value;
set => _active = value;
}
public void SetActiveInverse(bool active)
@ -33,52 +36,66 @@ public partial class InteractionArea2D : Node2D
public override void _Ready()
{
if (_useSprite && _useOutline)
if (_useOutline)
{
try
{
_backupMaterial = _spriteToOutline.Material;
// support old implementations of the script. If the sprite to outline is set, add it to the array
if (_spriteToOutline != null)
{
Array.Resize(ref _spritesToOutline, _spritesToOutline.Length + 1);
_spritesToOutline[^1] = _spriteToOutline;
}
_backupMaterials = _spritesToOutline.Select(s => s.Material).ToArray();
}
catch(Exception exception)
catch (Exception exception)
{
GD.PrintErr($"No sprite to outline found on: {GetParent().Name}" + exception.Message);
}
}
}
public void OnPlayerEntered(Node2D player)
{
if (!_active || !InputService.Instance.InputEnabled)
return;
if(_showLabel)
if (_showLabel)
_label.Show();
if (!_useSprite || !_useOutline)
if (!_useOutline)
return;
_spriteToOutline.Material = _outlineMaterial;
foreach (var sprite in _spritesToOutline)
{
sprite.Material = _outlineMaterial;
}
}
public void OnPlayerExited(Node2D player)
{
if (!_active)
return;
_label.Hide();
if (!_useSprite || !_useOutline)
if (!_useOutline)
return;
_spriteToOutline.Material = _backupMaterial;
for (var i = 0; i < _spritesToOutline.Length; i++)
{
var sprite = _spritesToOutline[i];
sprite.Material = _backupMaterials[i];
}
}
public override void _Input(InputEvent @event)
{
if (!_active || !InputService.Instance.InputEnabled)
if (!_active || !InputService.Instance.InputEnabled)
return;
if (@event.IsAction("interact") && @event.IsPressed())
{
TryInteract();
@ -95,10 +112,16 @@ public partial class InteractionArea2D : Node2D
if (_area.HasOverlappingAreas())
{
_label.Hide();
if (_useSprite && _useOutline)
_spriteToOutline.Material = _backupMaterial;
if (_useOutline)
{
for (var i = 0; i < _spritesToOutline.Length; i++)
{
var sprite = _spritesToOutline[i];
sprite.Material = _backupMaterials[i];
}
}
EmitSignal(SignalName.InteractedTool, _id);
EmitSignal(SignalName.Interacted);
}
@ -107,7 +130,7 @@ public partial class InteractionArea2D : Node2D
public void SetSpriteActiveState(bool success, int id) // TODO: remove
{
GD.PrintErr("SetSpriteActiveState is being called.");
if(!_active)
if (!_active)
return;
}

@ -0,0 +1,25 @@
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class ActionAnimationController : Node
{
#region Shortcuts
private FightWorld.FightHappeningData HappeningData => FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
#endregion
[Export] private AllFightersVisual _allFightersVisual = null!;
public void StateEnter()
{
_ = HappeningData.actionStaging!.AnimateAction(_allFightersVisual);
}
public void StateExit()
{
}
}

@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace Babushka.scripts.CSharp.Common.Fight.ActionDetails;
public class MinigameActionDetail : FighterAction.FighterActionDetail
{
// settings
// result
public List<int>? damageHits = null;
public MinigameActionDetail()
{
}
public override bool DetailComplete()
{
return damageHits != null;
}
public void ResetResult()
{
damageHits = null;
}
}

@ -0,0 +1,39 @@
using System;
namespace Babushka.scripts.CSharp.Common.Fight.ActionDetails;
public class TargetSelectActionDetail : FighterAction.FighterActionDetail
{
public enum VisualRange
{
Single
}
// settings
public required bool selectEnemy;
public required bool selectAlly;
public VisualRange visualRange = VisualRange.Single;
// result
private FightWorld.Fighter? target;
public override bool DetailComplete()
{
return target != null;
}
public void ResetResult()
{
target = null;
}
public void SetTarget(FightWorld.Fighter fighter)
{
target = fighter;
}
public FightWorld.Fighter GetTarget()
{
return target ?? throw new InvalidOperationException("No target selected");
}
}

@ -0,0 +1,65 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Babushka.scripts.CSharp.Common.Fight.ActionDetails;
using Babushka.scripts.CSharp.Common.Util;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight.Actions;
public class AllyAttackAction : FighterAction
{
// details
public TargetSelectActionDetail targetSelect = new()
{
selectEnemy = true,
selectAlly = false
};
public MinigameActionDetail minigameDetail = new();
public override Variant<float, Func<bool>> GetAnimationEnd()
{
return 1;
}
public override bool NextDetail()
{
return !targetSelect.DetailComplete() || !minigameDetail.DetailComplete();
}
public override FighterActionDetail CurrentDetail()
{
return targetSelect.DetailComplete() ? minigameDetail : targetSelect;
}
public override AllyActionButton BindToActionButton()
{
return AllyActionButton.Attack;
}
public override void Reset()
{
targetSelect.ResetResult();
minigameDetail.ResetResult();
}
public override void ExecuteAction()
{
var totalDamage = minigameDetail.damageHits!.Sum(dh => dh);
targetSelect.GetTarget().AddHealth(-totalDamage);
}
public override async Task AnimateAction(AllFightersVisual allFightersVisual)
{
var currentFighter = HappeningData.fighterTurn.Current;
var targetFighter = targetSelect.GetTarget();
var currentFighterVisual = allFightersVisual.GetVisualForFighter(currentFighter);
var targetFighterVisual = allFightersVisual.GetVisualForFighter(targetFighter);
await currentFighterVisual.AnimatePosToTarget(targetFighterVisual);
_ = targetFighterVisual.AnimateHit();
await currentFighterVisual.AnimatePosToBase();
}
}

@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
using Babushka.scripts.CSharp.Common.Util;
namespace Babushka.scripts.CSharp.Common.Fight.Actions;
public class BlobAttackAction : FighterAction
{
public override Variant<float, Func<bool>> GetAnimationEnd()
{
return 1;
}
public override bool NextDetail()
{
return false;
}
public override void ExecuteAction()
{
FightWorld.Instance.allyFighters.vesnaFighter.AddHealth(-3);
}
public override async Task AnimateAction(AllFightersVisual allFightersVisual)
{
var currentFighter = HappeningData.fighterTurn.Current;
var targetFighter = FightWorld.Instance.allyFighters.vesnaFighter;
var currentFighterVisual = allFightersVisual.GetVisualForFighter(currentFighter);
var targetFighterVisual = allFightersVisual.GetVisualForFighter(targetFighter);
await currentFighterVisual.AnimatePosToTarget(targetFighterVisual);
_ = targetFighterVisual.AnimateHit();
await currentFighterVisual.AnimatePosToBase();
}
}

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Babushka.scripts.CSharp.Common.Fight.ActionDetails;
using Babushka.scripts.CSharp.Common.Util;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class AllFightersVisual : Node
{
[ExportCategory("References")] [Export]
private Node2D _allyFighters = null!;
[Export] private Node2D _enemyFighters = null!;
// TODO: move type to prefab mapping to Resource
[ExportCategory("Fighter Visual Scenes")]
[Export] private PackedScene _blobFighterVisual = null!;
[Export] private PackedScene _bigBlobFighterVisual = null!;
[Export] private PackedScene _mavkaFighterVisual = null!;
[Export] private PackedScene _yourMomFighterVisual = null!;
[Export] private PackedScene _vesnaFighterVisual = null!;
[ExportCategory("Settings")]
[Export(PropertyHint.ArrayType)] private float[] _positionDistanceFromCenter = [10, 20, 30];
private Dictionary<FightWorld.Fighter, FighterVisual> _fighterVisuals = new();
#region Shortcuts
private FightWorld.FightHappeningData HappeningData =>
FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
#endregion
#region State Reactions
public void FightHappeningStateChange(FightHappening.FightState from, FightHappening.FightState to)
{
if (to == FightHappening.FightState.FightersEnterAnim)
{
EnterFighter();
}
if (to == FightHappening.FightState.InputActionDetail)
{
if (HappeningData.actionStaging!.CurrentDetail() is TargetSelectActionDetail targetDetail)
{
ShowTargetSelect(targetDetail);
}
}
if (from == FightHappening.FightState.InputActionDetail)
{
HideTargetSelect();
}
if (from == FightHappening.FightState.ActionAnim)
{
_fighterVisuals.Values.ForEach(fv => fv.UpdateHealthBar());
}
}
public void EnterFighter()
{
if (HappeningData.fightersEnterStaging == null)
return;
if (!HappeningData.fightersEnterStaging.HasAnyToExecute())
return;
foreach (var fighter in HappeningData.fightersEnterStaging.enteringEnemyFighters)
{
var packedScene = fighter.type switch
{
FightWorld.Fighter.Type.Blob => _blobFighterVisual,
FightWorld.Fighter.Type.BigBlob => _bigBlobFighterVisual,
FightWorld.Fighter.Type.Mavka => _mavkaFighterVisual,
FightWorld.Fighter.Type.YourMom => _yourMomFighterVisual,
FightWorld.Fighter.Type.Vesna => _vesnaFighterVisual,
_ => throw new ArgumentOutOfRangeException()
};
var fighterVisual = packedScene.Instantiate<FighterVisual>();
fighterVisual.Initialize(fighter);
_enemyFighters.AddChild(fighterVisual);
fighterVisual.Position = new Vector2(_positionDistanceFromCenter[_enemyFighters.GetChildCount() - 1], 0);
_fighterVisuals.Add(fighter, fighterVisual);
}
foreach (var fighter in HappeningData.fightersEnterStaging.enteringAllyFighters)
{
var packedScene = fighter.type switch
{
FightWorld.Fighter.Type.Blob => _blobFighterVisual,
FightWorld.Fighter.Type.BigBlob => _bigBlobFighterVisual,
FightWorld.Fighter.Type.Mavka => _mavkaFighterVisual,
FightWorld.Fighter.Type.YourMom => _yourMomFighterVisual,
FightWorld.Fighter.Type.Vesna => _vesnaFighterVisual,
_ => throw new ArgumentOutOfRangeException()
};
var fighterVisual = packedScene.Instantiate<FighterVisual>();
fighterVisual.Initialize(fighter);
_allyFighters.AddChild(fighterVisual);
fighterVisual.Position = new Vector2(-_positionDistanceFromCenter[_allyFighters.GetChildCount() - 1], 0);
_fighterVisuals.Add(fighter, fighterVisual);
}
}
private void ShowTargetSelect(TargetSelectActionDetail targetDetail)
{
// TODO: use Event bus
if (targetDetail.selectEnemy)
_fighterVisuals
.Where(kv => kv.Key.IsInFormation(HappeningData.enemyFighterFormation))
.ForEach(kv => kv.Value.SetTargetSelectionActive(true));
if (targetDetail.selectAlly)
_fighterVisuals
.Where(kv => kv.Key.IsInFormation(HappeningData.allyFighterFormation))
.ForEach(kv => kv.Value.SetTargetSelectionActive(true));
}
private void HideTargetSelect()
{
foreach (var visual in _fighterVisuals.Values)
{
visual.SetTargetSelectionActive(false);
}
}
#endregion
public FighterVisual GetVisualForFighter(FightWorld.Fighter fighter)
{
return _fighterVisuals.TryGetValue(fighter, out var visual)
? visual
: throw new InvalidOperationException("No visual for this fighter");
}
}

@ -0,0 +1,30 @@
using Babushka.scripts.CSharp.Common.Fight.Actions;
namespace Babushka.scripts.CSharp.Common.Fight;
public class AllyFighters
{
public FightWorld.Fighter vesnaFighter = new()
{
type = FightWorld.Fighter.Type.Vesna,
maxHealth = 20,
availableActions =
[
new AllyAttackAction()
]
};
public FightWorld.Fighter chuhaFighter = new()
{
type = FightWorld.Fighter.Type.Chuha,
maxHealth = 15,
availableActions =
[
new FighterAction.Skip()
]
};
public bool IsAlive()
{
return vesnaFighter.IsAlive();
}
}

@ -0,0 +1,386 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Babushka.scripts.CSharp.Common.Util;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightHappening : Node
{
/*
To get a visual overview of the FightHappening state machine, refer to the graph on miro:
https://miro.com/app/board/uXjVK8YEprM=/?moveToWidget=3458764640805655262&cot=14
*/
#region Internal Types
public enum FightState
{
None,
FightStartAnim,
FightersEnter,
FightersEnterAnim,
NextFighter,
StateCheck,
InputActionSelect,
ActionCheckDetails,
InputActionDetail,
ActionExecute,
ActionAnim,
EnemyActionSelect,
PlayerWin,
EnemyWin,
}
public class FightersEnterStaging
{
public required List<FightWorld.Fighter> enteringAllyFighters;
public required List<FightWorld.Fighter> enteringEnemyFighters;
public bool HasAnyToExecute()
{
return enteringAllyFighters.Any() || enteringEnemyFighters.Any();
}
}
#endregion
#region Settings
private const float StartAnimationTime = 1;
private const float FightersEnterAnimationTime = 1;
#endregion
#region Shortcuts
private static FightWorld.FightHappeningData HappeningData =>
FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
private static FightWorld.Fighter CurrentFighter => HappeningData.fighterTurn.Current;
#endregion
#region Events
[Signal]
public delegate void SignalTransitionFromStateEventHandler(FightState state);
[Signal]
public delegate void SignalTransitionStateEventHandler(FightState from, FightState to);
[Signal]
public delegate void SignalTransitionToStateEventHandler(FightState state);
#endregion
#region Singleton
public static FightHappening Instance = null!;
private void SetupInstance()
{
Instance = this;
}
#endregion
public override void _Ready()
{
SetupInstance();
StartFight();
}
#region Public Methods
public void StartFight()
{
RequireState(FightState.None);
ChangeState(FightState.FightStartAnim);
}
public void ActionSelect(FighterAction action)
{
RequireState(FightState.InputActionSelect);
HappeningData.actionStaging = action;
action.Reset();
ChangeState(FightState.ActionCheckDetails);
}
public void DetailFilled()
{
RequireState(FightState.InputActionDetail);
ChangeState(FightState.ActionCheckDetails);
}
#endregion
#region State Machine
private bool _inTransition = false;
private FightState? _changeToAfterTransition = null;
private void ChangeState(FightState nextState)
{
_changeToAfterTransition = null;
if (_inTransition)
{
_changeToAfterTransition = nextState;
return;
}
_inTransition = true;
TransitionFromState();
var lastState = HappeningData.fightState;
HappeningData.fightState = nextState;
TransitionToState(nextState);
EmitSignalSignalTransitionFromState(lastState);
EmitSignalSignalTransitionState(lastState, nextState);
EmitSignalSignalTransitionToState(nextState);
_inTransition = false;
if (_changeToAfterTransition.HasValue)
{
ChangeState(_changeToAfterTransition.Value);
}
}
private void TransitionFromState()
{
// fixed behaviour
switch (HappeningData.fightState)
{
default: break;
}
}
private void TransitionToState(FightState nextState)
{
// fixed behaviour
switch (HappeningData.fightState)
{
case FightState.FightStartAnim:
AdvanceToStateInSeconds(FightState.FightersEnter, StartAnimationTime);
break;
case FightState.FightersEnter:
HappeningData.fightersEnterStaging = StageFightersEnter();
if (HappeningData.fightersEnterStaging.HasAnyToExecute())
{
ExecuteFightersEnter();
ChangeState(FightState.FightersEnterAnim);
}
else
{
ChangeState(FightState.NextFighter);
}
break;
case FightState.FightersEnterAnim:
AdvanceToStateInSeconds(FightState.NextFighter, FightersEnterAnimationTime);
break;
case FightState.NextFighter:
ExecuteNextFighter();
ChangeState(FightState.StateCheck);
break;
case FightState.StateCheck:
// restest action staging and fighter enter staging
HappeningData.actionStaging = null;
HappeningData.fightersEnterStaging = null;
if (!FightWorld.Instance.allyFighters.IsAlive())
{
ChangeState(FightState.EnemyWin);
}
else if (HappeningData.enemyGroup.AreAllDead())
{
ChangeState(FightState.PlayerWin);
}
else if (CurrentFighter.actionPointsLeft <= 0)
{
ChangeState(FightState.FightersEnter);
}
else if (CurrentFighter.IsInFormation(HappeningData.enemyFighterFormation))
{
ChangeState(FightState.EnemyActionSelect);
}
else
{
ChangeState(FightState.InputActionSelect);
}
break;
case FightState.InputActionSelect:
// wait for player input
break;
case FightState.ActionCheckDetails:
RequireNotNull(HappeningData.actionStaging);
if (ActionAbort())
ChangeState(FightState.InputActionSelect);
else if (ActionNeededDetail())
ChangeState(FightState.InputActionDetail);
else
ChangeState(FightState.ActionExecute);
break;
case FightState.InputActionDetail:
// wait for player input
break;
case FightState.EnemyActionSelect:
HappeningData.actionStaging = CurrentFighter.AutoSelectAction();
ChangeState(FightState.ActionExecute);
break;
case FightState.ActionExecute:
ExecuteAction();
ChangeState(FightState.ActionAnim);
break;
case FightState.ActionAnim:
var actionTime = GetActionAnimationEnd();
if (actionTime.IsType<float>())
{
AdvanceToStateInSeconds(FightState.StateCheck, actionTime);
}
else
{
_ = AdvanceToStateWhenDone(FightState.StateCheck, actionTime);
}
break;
case FightState.EnemyWin:
// TODO: remove and find proper solution
ReviveVesna();
break;
default: break;
}
}
#endregion
#region Game Logic
private FightersEnterStaging StageFightersEnter()
{
// ally
var enteringAllyFighters = new List<FightWorld.Fighter>();
var allyFighters = FightWorld.Instance.allyFighters;
if (!allyFighters.vesnaFighter.IsInFormation(HappeningData.allyFighterFormation))
{
enteringAllyFighters.Add(allyFighters.vesnaFighter);
}
// enemy
const int totalEnemySpace = 3;
var enemySpaceLeft = HappeningData.enemyFighterFormation.GetEmptySlotCount();
return new FightersEnterStaging
{
enteringAllyFighters = enteringAllyFighters,
enteringEnemyFighters = HappeningData.enemyGroup.fighters
.WhereIsAlive()
.WhereIsNotInFormation(HappeningData.enemyFighterFormation)
.Take(enemySpaceLeft)
.ToList()
};
}
private void ExecuteFightersEnter()
{
Debug.Assert(HappeningData.fightersEnterStaging != null);
foreach (var fighter in HappeningData.fightersEnterStaging.enteringAllyFighters)
{
var emptySlotIndex = HappeningData.allyFighterFormation.GetFirstEmptySlot();
if (emptySlotIndex < 0) throw new Exception("No empty slot for ally fighter to enter");
HappeningData.allyFighterFormation.SetFighterAtPosition(emptySlotIndex, fighter);
HappeningData.fighterTurn.AddAsLast(fighter);
}
foreach (var fighter in HappeningData.fightersEnterStaging.enteringEnemyFighters)
{
var emptySlotIndex = HappeningData.enemyFighterFormation.GetFirstEmptySlot();
if (emptySlotIndex < 0) throw new Exception("No empty slot for enemy fighter to enter");
HappeningData.enemyFighterFormation.SetFighterAtPosition(emptySlotIndex, fighter);
HappeningData.fighterTurn.AddAsLast(fighter);
}
}
private void ExecuteNextFighter()
{
HappeningData.fighterTurn.Next();
CurrentFighter.actionPointsLeft = FightWorld.Fighter.MaxActionPoints;
}
private void ExecuteAction()
{
Debug.Assert(HappeningData.actionStaging != null);
HappeningData.actionStaging.ExecuteAction();
CurrentFighter.actionPointsLeft -= HappeningData.actionStaging.GetActionPointCost();
}
private Variant<float, Func<bool>> GetActionAnimationEnd()
{
Debug.Assert(HappeningData.actionStaging != null);
return HappeningData.actionStaging.GetAnimationEnd();
}
private bool ActionAbort()
{
Debug.Assert(HappeningData.actionStaging != null);
return HappeningData.actionStaging.MarkedForAbort();
}
private bool ActionNeededDetail()
{
Debug.Assert(HappeningData.actionStaging != null);
return HappeningData.actionStaging.NextDetail();
}
// TODO: remove
private void ReviveVesna()
{
var vesnaFighter = FightWorld.Instance.allyFighters.vesnaFighter;
vesnaFighter.health = vesnaFighter.maxHealth;
GD.Print("Vesna has been revived. This is for the current prototype only");
}
#endregion // Game Logic
#region Utility
private void RequireState(params FightState[] states)
{
if (states.Contains(HappeningData.fightState))
return;
throw new Exception(
$"Can not call this Method while in state {HappeningData.fightState}. Only available in {string.Join(" ,", states)}");
}
private void RequireNotNull(Object? o)
{
if (o != null)
return;
throw new Exception("Object must not be null to call this method");
}
private void AdvanceToStateInSeconds(FightState nextState, float seconds)
{
FightWorld.Instance.GetTree().CreateTimer(seconds).Timeout += () => ChangeState(nextState);
}
private async Task AdvanceToStateWhenDone(FightState nextState, Func<bool> isDone)
{
while (!isDone())
{
await FightWorld.Instance.ToSignal(FightWorld.Instance.GetTree(), SceneTree.SignalName.ProcessFrame);
}
ChangeState(nextState);
}
#endregion
}

@ -0,0 +1,15 @@
using Godot;
using System;
using System.Diagnostics;
using Babushka.scripts.CSharp.Common.Fight;
public partial class FightHappeningSceneSetup : Node2D
{
public override void _Ready()
{
var fightHappening = FightWorld.Instance.fightHappeningData;
Debug.Assert(fightHappening != null, "Fight happening scene loaded, without a fight happening");
}
}

@ -0,0 +1,57 @@
using Godot;
using System;
using System.Linq;
using Babushka.scripts.CSharp.Common.Fight;
public partial class FightHappeningStateDebugger : Node
{
[Export] private Label _label;
[Export] private Label _current;
private FightWorld.FightHappeningData Data => FightWorld.Instance.fightHappeningData!;
public void StateChange(FightHappening.FightState from, FightHappening.FightState to)
{
_current.Text = $"{to}";
_label.Text += $"State changed from {from} to {to}\n";
switch (to)
{
case FightHappening.FightState.None:
break;
case FightHappening.FightState.FightStartAnim:
break;
case FightHappening.FightState.FightersEnter:
break;
case FightHappening.FightState.FightersEnterAnim:
_label.Text +=
$" {Data.fightersEnterStaging!.enteringAllyFighters.Count} allies " +
$"and {Data.fightersEnterStaging.enteringEnemyFighters.Count} enemies are entering the fight.\n";
break;
case FightHappening.FightState.NextFighter:
break;
case FightHappening.FightState.StateCheck:
break;
case FightHappening.FightState.InputActionSelect:
break;
case FightHappening.FightState.ActionCheckDetails:
break;
case FightHappening.FightState.InputActionDetail:
break;
case FightHappening.FightState.ActionExecute:
_label.Text += $" Executing action: {Data.actionStaging!.GetType()}\n";
break;
case FightHappening.FightState.ActionAnim:
break;
case FightHappening.FightState.EnemyActionSelect:
break;
case FightHappening.FightState.PlayerWin:
break;
case FightHappening.FightState.EnemyWin:
break;
default:
throw new ArgumentOutOfRangeException(nameof(to), to, null);
}
}
}

@ -0,0 +1,27 @@
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightHappeningStateReaction : Node
{
[Export] private FightHappening.FightState _fightState;
[Signal]
public delegate void OnStateEnteredEventHandler();
[Signal]
public delegate void OnStateExitedEventHandler();
public void FightHappeningStateTransitioned(FightHappening.FightState fromState, FightHappening.FightState toState)
{
if (fromState == _fightState)
{
EmitSignalOnStateExited();
}
if (toState == _fightState)
{
EmitSignalOnStateEntered();
}
}
}

@ -1,351 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Babushka.scripts.CSharp.Common.Camera;
using Babushka.scripts.CSharp.Common.Util;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightInstance : Node2D //TODO: remake
{
[Export(PropertyHint.ArrayType)] private Node2D[] _friendlyFightSpots;
[Export(PropertyHint.ArrayType)] private Node2D[] _enemyFightSpots;
[Export] public Node2D camPositionNode;
[Export] private FightStateManager _fightStateManager;
[Export] private Label _fightEndText;
[Signal]
public delegate void FightStartedEventHandler();
[Signal]
public delegate void FightEndedEventHandler();
private List<Fighter> _friendlyFighters = new();
private List<Fighter> _enemyFighters = new();
private FightAttack? _stagedAttack = null;
public override void _Ready()
{
//_fightStateManager.CurrentFightState = FightStateManager.FightState.FightStartAnim;
_fightStateManager.ExitingTransition += from =>
{
switch (from)
{
case FightStateManager.FightState.None:
CaptureCamera();
Show();
EmitSignalFightStarted();
break;
case FightStateManager.FightState.Input:
HideAttackButtons();
break;
case FightStateManager.FightState.InputTargetSelect:
HideTargetButtons();
break;
case FightStateManager.FightState.FriendAttackAnim:
_stagedAttack = null;
break;
case FightStateManager.FightState.PlayerWinAnim:
case FightStateManager.FightState.EnemyWinAnim:
_fightEndText.Text = "";
break;
}
};
_fightStateManager.EnteringTransition += to =>
{
switch (to)
{
case FightStateManager.FightState.None:
EmitSignalFightEnded();
CleanUp();
Hide();
ReleaseCamera();
break;
case FightStateManager.FightState.Input:
if (CheckWinAndSetState())
break;
if (CheckFriendlyActionsLeftAndSetState())
break;
ShowAttackButtons();
break;
case FightStateManager.FightState.InputTargetSelect:
ShowTargetButtons();
break;
case FightStateManager.FightState.FriendAttackAnim:
ExecuteAttack();
GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Input;
break;
case FightStateManager.FightState.Enemy:
if (CheckWinAndSetState())
break;
if (CheckEnemyActionsLeftAndSetState())
break;
DecideEnemyAttack();
_fightStateManager.CurrentFightState = FightStateManager.FightState.EnemyAttackAnim;
break;
case FightStateManager.FightState.EnemyAttackAnim:
ExecuteAttack();
GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Enemy;
break;
case FightStateManager.FightState.PlayerWinAnim:
_fightEndText.Text = "You Win!";
GetTree().CreateTimer(1.5).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.None;
break;
case FightStateManager.FightState.EnemyWinAnim:
_fightEndText.Text = "You Died :(";
GetTree().CreateTimer(3).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.None;
break;
case FightStateManager.FightState.ChangeSideToEnemy:
ResetEnemyActions();
_fightStateManager.CurrentFightState = FightStateManager.FightState.Enemy;
break;
case FightStateManager.FightState.ChangeSideToFriendly:
ResetFriendlyActions();
_fightStateManager.CurrentFightState = FightStateManager.FightState.Input;
break;
case FightStateManager.FightState.Heal:
Heal();
GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Input;
break;
}
};
}
private void Heal()
{
// TODO: make heal staging system
_friendlyFighters.Where(f => !f.IsDead()).ForEach(f =>
{
f.Health += 50;
f.HealAnimation();
f.DecrementActions();
});
UpdateHealthVisual();
}
private void ResetEnemyActions()
{
_enemyFighters.ForEach(f => f.ResetActions());
}
private void ResetFriendlyActions()
{
_friendlyFighters.ForEach(f => f.ResetActions());
}
/**
* <returns>
* <c>true</c> if the state was changed
* </returns>
*/
private bool CheckFriendlyActionsLeftAndSetState()
{
var hasActionsLeft = _friendlyFighters.Where(f => !f.IsDead()).Any(f => f.HasActionsLeft());
if (hasActionsLeft)
{
return false;
} // else
_fightStateManager.CurrentFightState = FightStateManager.FightState.ChangeSideToEnemy;
return true;
}
/**
* <returns>
* <c>true</c> if the state was changed
* </returns>
*/
private bool CheckEnemyActionsLeftAndSetState()
{
var hasActionsLeft = _enemyFighters.Where(f => !f.IsDead()).Any(f => f.HasActionsLeft());
if (hasActionsLeft)
{
return false;
} // else
_fightStateManager.CurrentFightState = FightStateManager.FightState.ChangeSideToFriendly;
return true;
}
private void CleanUp()
{
_enemyFighters.ForEach(f => f.QueueFree());
_friendlyFighters.ForEach(f => f.QueueFree());
_enemyFighters = new();
_friendlyFighters = new();
}
private void DecideEnemyAttack()
{
var availableEnemyFighters =
_enemyFighters
.Where(f => !f.IsDead())
.Where(f=>f.HasActionsLeft())
.ToList();
var aliveFriendlyFighters =
_friendlyFighters
.Where(f => !f.IsDead())
.ToList();
if (availableEnemyFighters.Count <= 0)
throw new InvalidOperationException("No enemy fighters available for attack.");
if (aliveFriendlyFighters.Count <= 0)
throw new InvalidOperationException("No friendly fighters available to target.");
var fighter = availableEnemyFighters.Random();
var target = aliveFriendlyFighters.Random();
_stagedAttack = new FightAttack
{
attacker = fighter!,
needsSelectedTarget = true,
damage = fighter!.attackStrength,
target = target!
};
}
private void ExecuteAttack()
{
if (_stagedAttack == null)
throw new InvalidOperationException("No staged attack to execute.");
if (!_stagedAttack.needsSelectedTarget)
throw new NotImplementedException("Non-targeted attacks are not implemented yet.");
if (_stagedAttack.needsSelectedTarget && _stagedAttack.target == null)
throw new InvalidOperationException("No target selected for the staged attack.");
_stagedAttack.target!.Health -= _stagedAttack.damage;
_stagedAttack.attacker.DecrementActions();
_stagedAttack.attacker.AttackAnimation(_stagedAttack);
UpdateHealthVisual();
}
private void UpdateHealthVisual()
{
_friendlyFighters
.Concat(_enemyFighters)
.ForEach(f => f.UpdateHealthVisual());
}
private void ReleaseCamera()
{
CameraController.Instance.fightToShow = null;
}
private void CaptureCamera()
{
CameraController.Instance.fightToShow = this;
}
public void Start(FightParty fightParty, PackedScene?[] enemies)
{
if (_fightStateManager.IsRunning())
{
GD.PushWarning("Can not start a running fight");
return;
}
if (fightParty.vesna)
{
InstantiateFighter(_friendlyFightSpots[1], FightManager.Instance.fightingVesnaScene);
}
for (var i = 0; i < Math.Min(_enemyFightSpots.Length, enemies.Length); i++)
{
var enemy = enemies[i];
if (enemy == null)
continue;
InstantiateFighter(_enemyFightSpots[i], enemy, true);
}
_fightStateManager.ToStartAnim();
}
private void InstantiateFighter(Node2D parent, PackedScene fighterScene, bool isEnemy = false)
{
var fighter = fighterScene.Instantiate<Fighter>();
fighter.fightInstance = this;
parent.AddChild(fighter);
if (isEnemy)
{
_enemyFighters.Add(fighter);
}
else
{
_friendlyFighters.Add(fighter);
}
}
public void SelectAttack(Fighter fighter)
{
_stagedAttack = new FightAttack
{
attacker = fighter,
damage = fighter.attackStrength,
needsSelectedTarget = true
};
if (_stagedAttack.needsSelectedTarget)
{
_fightStateManager.CurrentFightState = FightStateManager.FightState.InputTargetSelect;
}
else
{
_fightStateManager.CurrentFightState = FightStateManager.FightState.FriendAttackAnim;
}
}
private void HideAttackButtons()
{
_friendlyFighters.ForEach(f => f.HideAttackButton());
}
private void ShowAttackButtons()
{
_friendlyFighters.ForEach(f => f.ShowAttackButton());
}
private void HideTargetButtons()
{
_enemyFighters.ForEach(f => f.HideTargetButtons());
}
private void ShowTargetButtons()
{
_enemyFighters.Where(f => !f.IsDead()).ForEach(f => f.ShowTargetButtons());
}
public void SelectTargetAndAttack(Fighter fighter)
{
if (_stagedAttack == null)
throw new InvalidOperationException("No staged attack to select target for.");
_stagedAttack.target = fighter;
_fightStateManager.CurrentFightState = FightStateManager.FightState.FriendAttackAnim;
}
public void SelectHeal(Fighter fighter)
{
_fightStateManager.CurrentFightState = FightStateManager.FightState.Heal;
}
public bool CheckWinAndSetState()
{
if (_enemyFighters.All(f => f.IsDead()))
{
_fightStateManager.CurrentFightState = FightStateManager.FightState.PlayerWinAnim;
return true;
}
if (_friendlyFighters.All(f => f.IsDead()))
{
_fightStateManager.CurrentFightState = FightStateManager.FightState.EnemyWinAnim;
return true;
}
return false;
}
}

@ -0,0 +1,43 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Babushka.scripts.CSharp.Common.Fight;
using Babushka.scripts.CSharp.Common.Fight.ActionDetails;
using Babushka.scripts.CSharp.Common.Minigame;
public partial class FightMinigameHandler : Node
{
#region Shortcuts
private FightWorld.FightHappeningData HappeningData => FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
#endregion
[Export] private MinigameController _minigameController;
public void OnStateEnter(FightHappening.FightState to)
{
if(to!=FightHappening.FightState.InputActionDetail) return;
var currentDetail = HappeningData.actionStaging!.CurrentDetail();
if(currentDetail is not MinigameActionDetail minigameDetail) return;
_minigameController.Run(new MinigameController.Builder<int>()
.AddRegion(4).RegionWithText("4").RegionWithTheme(MinigameController.RegionTheme.Normal)
.AddRegion(0).RegionWithProportion(1.5f).RegionWithText("0").RegionWithTheme(MinigameController.RegionTheme.Bad)
.AddRegion(8).RegionWithProportion(0.5f).RegionWithText("8").RegionWithTheme(MinigameController.RegionTheme.VeryGood)
.AddRegion(0).RegionWithProportion(1.5f).RegionWithText("0").RegionWithTheme(MinigameController.RegionTheme.Bad)
.AddRegion(3).RegionWithText("3").RegionWithTheme(MinigameController.RegionTheme.NormalAlt1)
.AddRegion(5).RegionWithText("5").RegionWithTheme(MinigameController.RegionTheme.NormalAlt2)
.WithHitCount(3)
).ContinueWith(task =>
{
minigameDetail.damageHits = task.Result;
//FightHappening.Instance.DetailFilled();
// Apparently ContinueWith spawn a new Thread, but methods on a node only want to be called from the main thread
FightHappening.Instance.CallDeferred("DetailFilled");
});
}
}

@ -0,0 +1,29 @@
using System.Collections.Generic;
using Babushka.scripts.CSharp.Common.Util;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightRoomSceneSetup : Node
{
[Export(PropertyHint.ArrayType)] private Node2D[] _enemyGroupSpawns;
[Export] private PackedScene _roamingEnemyGroupPrefab;
[Export] private FightSceneSwitcher _fightSceneSwitcher;
public override void _Ready()
{
var room = FightWorld.Instance.currentRoom!;
var i = 0;
foreach (var availableParent in _enemyGroupSpawns.Shuffle())
{
var enemyGroup = room.enemyGroups[i];
var roamingEnemyGroup = _roamingEnemyGroupPrefab.Instantiate<RoamingEnemyGroup>();
roamingEnemyGroup.Initialize(enemyGroup, _fightSceneSwitcher);
availableParent.AddChild(roamingEnemyGroup);
if (i >= room.enemyGroups.Count - 1) break;
i++;
}
}
}

@ -0,0 +1,63 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Babushka.scripts.CSharp.Common.SceneManagement;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightSceneSwitcher : Node
{
[Export] private Node _sceneRoot = null!;
[Export] private string _fightRoomScenePath = null!;
[Export] private string _fightHappeningScene = null!;
private void LoadNext()
{
var nextRoom = FightWorld.Instance.currentRoom;
Debug.Assert(nextRoom != null, "nextRoom!=null");
var nextFightHappening = FightWorld.Instance.fightHappeningData;
SceneTransitionThreaded.Instance.ChangeSceneToFile(nextFightHappening != null
? _fightHappeningScene
: _fightRoomScenePath);
_ = UnloadAfterDelay();
}
private async Task UnloadAfterDelay()
{
await ToSignal(GetTree().CreateTimer(1.0f), "timeout"); // 1.0f seconds
//sceneRoot.QueueFree();
}
public void SwitchRoom(int pathIndex)
{
Debug.Assert(FightWorld.Instance.currentRoom != null, "FightWorld.Instance.currentRoom!=null");
if (!FightWorld.Instance.currentRoom.paths.TryGetValue(pathIndex, out var nextRoom))
throw new Exception("Trying to go down a non-existent path");
FightWorld.Instance.currentRoom = nextRoom;
LoadNext();
}
public void SwitchToFight(FightWorld.FighterGroup enemyGroup)
{
if (FightWorld.Instance.fightHappeningData != null)
throw new Exception("Trying to start a fight while already in a fight");
FightWorld.Instance.fightHappeningData = new FightWorld.FightHappeningData
{
enemyGroup = enemyGroup,
};
LoadNext();
}
public void ExitFight()
{
if (FightWorld.Instance.fightHappeningData == null)
throw new Exception("Trying to exit a fight while not in a fight");
FightWorld.Instance.fightHappeningData = null;
LoadNext();
}
}

@ -1,76 +0,0 @@
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightStateManager : Node
{
[Signal]
public delegate void ExitingTransitionEventHandler(FightState exitingState);
[Signal]
public delegate void EnteringTransitionEventHandler(FightState enteringState);
public enum FightState
{
None,
FightStartAnim,
Input,
InputTargetSelect,
FriendAttackAnim,
Enemy,
EnemyAttackAnim,
PlayerWinAnim,
EnemyWinAnim,
ChangeSideToEnemy,
ChangeSideToFriendly,
Heal,
}
private FightState _fightStateBacking = FightState.None;
public FightState CurrentFightState
{
set => Transition(_fightStateBacking, value);
get => _fightStateBacking;
}
private void Transition(FightState from, FightState to)
{
if(from == to)
return;
GD.Print($"Transitioning from {from} to {to}");
ExitTransition(from);
_fightStateBacking = to;
EnterTransition(to);
}
private void ExitTransition(FightState from)
{
EmitSignalExitingTransition(from);
}
private void EnterTransition(FightState to)
{
EmitSignalEnteringTransition(to);
switch (to)
{
case FightState.FightStartAnim:
EnterFightStartAnim();
break;
}
}
private void EnterFightStartAnim()
{
GetTree().CreateTimer(1).Timeout += () => CurrentFightState = FightState.Input;
}
public void ToStartAnim()
{
CurrentFightState = FightState.FightStartAnim;
}
public bool IsRunning()
{
return CurrentFightState != FightState.None;
}
}

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Babushka.scripts.CSharp.Common.Fight;
public static class FightUtils
{
public static IEnumerable<FightWorld.Fighter> WhereIsAlive(this IEnumerable<FightWorld.Fighter> self)
{
return self.Where(e => e.IsAlive());
}
public static IEnumerable<FightWorld.Fighter> WhereIsNotInFormation(this IEnumerable<FightWorld.Fighter> self, FighterFormation formation)
{
return self.Where(e => !e.IsInFormation(formation));
}
public static bool IsAlive(this FightWorld.Fighter self)
{
return self.GetHealth() > 0;
}
public static bool IsDead(this FightWorld.Fighter self)
{
return !self.IsAlive();
}
public static int GetHealth(this FightWorld.Fighter self)
{
return Math.Max(self.health ?? self.maxHealth, 0);
}
public static void AddHealth(this FightWorld.Fighter self, int addHealth)
{
self.health = self.GetHealth() + addHealth;
}
public static bool IsInFormation(this FightWorld.Fighter self, FighterFormation formation)
{
return formation.ContainsFighter(self);
}
public static bool AreAllDead(this FightWorld.FighterGroup self)
{
return self.fighters.All(e => e.IsDead());
}
}

@ -0,0 +1,191 @@
using System.Collections.Generic;
using Babushka.scripts.CSharp.Common.Fight.Actions;
using Babushka.scripts.CSharp.Common.Util;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightWorld : Node
{
public class World
{
public required List<Room> rooms;
}
public class Room
{
public required Dictionary<int, Room> paths;
public required List<FighterGroup> enemyGroups;
}
public class FighterGroup
{
public required List<Fighter> fighters;
}
public class FightHappeningData
{
public required FighterGroup enemyGroup;
public FightHappening.FightState fightState = FightHappening.FightState.None;
public readonly FighterTurn fighterTurn = new();
public readonly FighterFormation allyFighterFormation = new();
public readonly FighterFormation enemyFighterFormation = new();
public FightHappening.FightersEnterStaging? fightersEnterStaging;
public FighterAction? actionStaging;
}
public class Fighter
{
public enum Type
{
Blob,
BigBlob,
Mavka,
YourMom,
Vesna,
Chuha
}
public required Type type;
public required int maxHealth;
public required List<FighterAction> availableActions;
public const int MaxActionPoints = 1;
public int? health = null; // null => initialize to full health on spawn
public int actionPointsLeft;
public FighterAction AutoSelectAction()
{
return availableActions.Random() ?? new FighterAction.Skip();
}
}
#region AutoLoad ( Contains _EnterTree() )
public static FightWorld Instance { get; private set; } = null!;
public override void _EnterTree()
{
Instance = this;
MyEnterTree();
}
#endregion
public World? world = null;
public Room? currentRoom = null;
public FightHappeningData? fightHappeningData = null;
public AllyFighters allyFighters = new();
public void MyEnterTree()
{
Generate();
currentRoom = world!.rooms[0];
}
public void Generate()
{
world = new Generator().GenerateWorld();
}
private class Generator
{
public World GenerateWorld()
{
var world = new World
{
rooms = GenerateRooms()
};
return world;
}
private List<Room> GenerateRooms()
{
var rooms = new List<Room>();
var roomCount = 2;
for (var i = 0; i < roomCount; i++)
{
rooms.Add(GenerateDisconnectedRoom());
}
// Connect rooms linearly
for (var i = 0; i < rooms.Count - 1; i++)
{
rooms[i].paths[0] = rooms[i + 1];
rooms[i + 1].paths[1] = rooms[i];
}
return rooms;
}
private Room GenerateDisconnectedRoom()
{
var room = new Room
{
paths = new Dictionary<int, Room>(),
enemyGroups = GenerateEnemyGroups()
};
return room;
}
private List<FighterGroup> GenerateEnemyGroups()
{
var enemyGroups = new List<FighterGroup>();
var enemyGroupCount = GD.RandRange(1, 3);
for (var i = 0; i < enemyGroupCount; i++)
{
enemyGroups.Add(GenerateSingleEnemyGroup());
}
return enemyGroups;
}
private FighterGroup GenerateSingleEnemyGroup()
{
var enemyGroup = new FighterGroup
{
fighters = []
};
var enemyCount = GD.RandRange(1, 3);
for (var i = 0; i < enemyCount; i++)
{
enemyGroup.fighters.Add(GenerateSingleEnemy());
}
return enemyGroup;
}
private Fighter GenerateSingleEnemy()
{
var typeRoll = GD.RandRange(0, 99);
// Disabled generating different types due to lack of fighter visual type implementation
//var type = typeRoll switch
//{
// < 50 => Fighter.Type.Blob,
// < 75 => Fighter.Type.BigBlob,
// < 90 => Fighter.Type.Mavka,
// _ => Fighter.Type.YourMom
//};
var type = Fighter.Type.Blob;
var enemy = new Fighter
{
type = type,
health = null,
maxHealth = 12,
availableActions =
[
new BlobAttackAction()
]
};
return enemy;
}
}
}

@ -1,181 +0,0 @@
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class Fighter : Node2D
{
[Export] public string name;
[Export] public int maxHealth;
[Export] public int attackStrength;
[Export] public int maxActions = 1;
[ExportCategory("References")]
[Export] private Node2D _attackButtons;
[Export] private Node2D _targetButtons;
[Export] private Node2D _targetMarker;
[Export] private Label _healthText;
[Export] private Node2D _visualSprite;
[Signal] public delegate void DamageTakenEventHandler();
[Signal] public delegate void AttackingEventHandler();
[Signal] public delegate void DyingEventHandler();
[Signal] public delegate void HealedEventHandler();
private int _health;
private int _actions;
public FightInstance fightInstance;
public int Health
{
get => _health;
set
{
_health = value;
if (_health <= 0)
{
_health = 0;
Die();
}
if (_health > maxHealth)
{
_health = maxHealth;
}
}
}
private void Die()
{
_visualSprite.Scale = new Vector2(1, 0.3f);
EmitSignalDying();
}
public override void _Ready()
{
Health = maxHealth;
UpdateHealthVisual();
ResetActions();
}
public void Attack()
{
fightInstance.SelectAttack(this);
}
public void HideAttackButton()
{
_attackButtons.Hide();
}
public void ShowAttackButton()
{
_attackButtons.Show();
}
public void HideTargetButtons()
{
_targetButtons.Hide();
}
public void ShowTargetButtons()
{
_targetButtons.Show();
}
public void TargetMouseEvent(Node viewport, InputEvent inputEvent, int shapeIdx)
{
if (inputEvent.IsPressed())
ClickedTarget();
}
public void AttackMouseEvent(Node viewport, InputEvent inputEvent, int shapeIdx)
{
if (inputEvent.IsPressed())
ClickedAttack();
}
public void HealMouseEvent(Node viewport, InputEvent inputEvent, int shapeIdx)
{
if (inputEvent.IsPressed())
ClickedHeal();
}
private void ClickedAttack()
{
fightInstance.SelectAttack(this);
}
private void ClickedHeal()
{
fightInstance.SelectHeal(this);
}
private void ClickedTarget()
{
fightInstance.SelectTargetAndAttack(this);
}
public void StartHoverTarget()
{
_targetMarker.Visible = true;
}
public void EndHoverTarget()
{
_targetMarker.Visible = false;
}
public void UpdateHealthVisual()
{
_healthText.Text = $"{Health}/{maxHealth}";
}
public void AttackAnimation(FightAttack attack)
{
EmitSignalAttacking();
var tween = GetTree().CreateTween();
tween.TweenProperty(this, "global_position", attack.target.GlobalPosition, 0.15);
tween.TweenCallback(Callable.From(() => attack.target?.HitAnimation(attack)));
tween.TweenProperty(this, "position", new Vector2(0, 0), 0.7)
.SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
}
private void HitAnimation(FightAttack attack)
{
EmitSignalDamageTaken();
var tween = GetTree().CreateTween();
tween.TweenProperty(this, "scale", new Vector2(1.4f, 0.6f), 0.15);
tween.TweenProperty(this, "scale", new Vector2(1, 1), 0.4)
.SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
}
public bool IsDead()
{
return Health <= 0;
}
public void ResetActions()
{
_actions = maxActions;
}
public bool HasActionsLeft()
{
return _actions > 0;
}
public void DecrementActions()
{
_actions--;
}
public void HealAnimation()
{
EmitSignalHealed();
var tween = GetTree().CreateTween();
tween.TweenProperty(this, "scale", new Vector2(0.6f, 1.4f), 0.15);
tween.TweenProperty(this, "scale", new Vector2(1, 1), 0.4)
.SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
}
}

@ -0,0 +1,132 @@
using System;
using System.Threading.Tasks;
using Babushka.scripts.CSharp.Common.Util;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public abstract class FighterAction
{
// enum has explicit values, because they are set in godot signals as integers
// e.g. here: BabushkaSceneFightHappening => ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer/AttackButton
public enum AllyActionButton
{
None,
Attack = 1,
Summon = 2,
Talk = 3,
Flee = 4,
}
public class TargetSelection
{
// ReSharper disable once MemberHidesStaticFromOuterClass
public static readonly TargetSelection Skip = new() { skipTargetSelection = () => true };
public Func<bool> skipTargetSelection = () => false;
}
public abstract class FighterActionDetail
{
public abstract bool DetailComplete();
}
private bool _abort = false;
#region Shortcuts
protected static FightWorld.FightHappeningData HappeningData =>
FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
#endregion
/// <summary>
/// Executes the data modification for the action. This must happen instantly and not via a coroutines or callbacks.
/// To get a multiple hit effect for one action, use appropriate Visual Manipulation functions in AnimateAction.
/// </summary>
public virtual void ExecuteAction()
{
}
/// <summary>
/// Returns a way to determine, when an action animation is done
/// </summary>
/// <returns>
/// A variant that can be <c>float</c> or <c>Func&lt;bool&gt;</c>.<br/>
/// When the return type is <c>float</c>, the animation will take the return value amount of seconds.<br/>
/// When the return type is <c>Func&lt;bool&gt;</c>, the animation will be done, when the function returns true.
/// </returns>
public abstract Variant<float, Func<bool>> GetAnimationEnd();
/// <summary>
/// Animates the action.
/// </summary>
/// <param name="allFightersVisual"></param>
public virtual async Task AnimateAction(AllFightersVisual allFightersVisual)
{
}
public void MarkAbort()
{
_abort = true;
}
public bool MarkedForAbort()
{
return _abort;
}
/// <summary>
/// Returns the FighterActionDetail, that is currently handled.
/// </summary>
/// <returns></returns>
public virtual FighterActionDetail CurrentDetail()
{
throw new Exception("Action has no details to handle");
}
/// <summary>
/// Sets the next Detail to be handled. Returns false, when there are no more details to handle.
/// </summary>
/// <returns></returns>
public abstract bool NextDetail();
/// <summary>
/// Returns the action point cost of this action.
/// Right now, only the values 1 and 0 make sense.
/// </summary>
/// <returns></returns>
public virtual int GetActionPointCost()
{
return 1;
}
/// <summary>
/// Will be called right after the action is selected by the player. Can be used to reset the state of the details
/// </summary>
public virtual void Reset()
{
}
/// <summary>
/// If this action should be bound to an action button in the UI, return the corresponding enum value here.
/// </summary>
/// <returns></returns>
public virtual AllyActionButton BindToActionButton()
{
return AllyActionButton.None;
}
public class Skip : FighterAction
{
public override Variant<float, Func<bool>> GetAnimationEnd()
{
return 0f;
}
public override bool NextDetail()
{
return false;
}
}
}

@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Babushka.scripts.CSharp.Common.Fight;
public class FighterFormation
{
private readonly List<FightWorld.Fighter?> _fighters;
public FighterFormation(int slots = 3)
{
_fighters = [];
for (var i = 0; i < slots; i++)
{
_fighters.Add(null);
}
}
public FightWorld.Fighter? GetFighterAtPosition(int position)
{
Debug.Assert(position >= 0, "position>=0");
Debug.Assert(position < _fighters.Count, "position does not exist");
return _fighters[position];
}
public void SetFighterAtPosition(int position, FightWorld.Fighter value)
{
Debug.Assert(position >= 0, "position>=0");
Debug.Assert(position < _fighters.Count, "position does not exist");
_fighters[position] = value;
}
public bool ContainsFighter(FightWorld.Fighter fighter)
{
return _fighters.Contains(fighter);
}
public int GetFirstEmptySlot()
{
for (var i = 0; i < _fighters.Count; i++)
{
if (_fighters[i] == null)
return i;
}
return -1;
}
public int GetEmptySlotCount()
{
return _fighters.Count(fighter => fighter == null);
}
}

@ -0,0 +1,97 @@
using System;
using System.Diagnostics;
namespace Babushka.scripts.CSharp.Common.Fight;
public class FighterTurn
{
private class Node
{
public required Node next;
public required FightWorld.Fighter fighter;
}
private Node? _currentNode;
public FightWorld.Fighter Current => _currentNode?.fighter ?? throw new InvalidOperationException("No current fighter");
public void Next()
{
Debug.Assert(_currentNode != null, "currentNode!=null");
_currentNode = _currentNode.next;
}
public FightWorld.Fighter PeekNext()
{
Debug.Assert(_currentNode != null, "currentNode!=null");
return _currentNode.next.fighter;
}
public void AddAsLast(FightWorld.Fighter value)
{
// if first node
if (_currentNode == null)
{
_currentNode = new Node { fighter = value, next = null! };
_currentNode.next = _currentNode;
return;
}
var newNode = new Node { fighter = value, next = _currentNode };
var node = _currentNode;
while (node.next != _currentNode)
{
node = node.next;
}
node.next = newNode;
}
public void AddAsNext(FightWorld.Fighter value)
{
// if first node
if (_currentNode == null)
{
AddAsLast(value);
return;
}
var newNode = new Node { fighter = value, next = _currentNode.next };
_currentNode.next = newNode;
}
public bool Remove(FightWorld.Fighter value)
{
if (_currentNode == null) return false;
// if only one node
if (_currentNode.next == _currentNode)
{
if (_currentNode.fighter == value)
{
_currentNode = null;
return true;
}
return false;
}
var node = _currentNode;
do
{
// next is the fighter to remove
if (node.next.fighter == value)
{
// if removing current, keep current
// it will be implicitly deleted by loss of reference on the next Next() call
node.next = node.next.next;
return true;
}
node = node.next;
} while (node != _currentNode);
return false;
}
}

@ -0,0 +1,98 @@
using System;
using System.Threading.Tasks;
using Babushka.scripts.CSharp.Common.Fight.ActionDetails;
using Babushka.scripts.CSharp.Common.Fight.UI;
using Godot;
using Godot.Collections;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FighterVisual : Node2D
{
#region Shortcuts
private FightWorld.FightHappeningData HappeningData =>
FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
#endregion
[ExportCategory("References")]
[Export] private Node2D _visualParent = null!;
[Export] private Node2D _targetSelectionParent = null!;
[Export] public FighterHealthBarVisual healthBarVisual = null!;
private FightWorld.Fighter _boundFighter;
public void Initialize(FightWorld.Fighter fighter)
{
_boundFighter = fighter;
UpdateMirrorState();
UpdateHealthBar();
}
/// <summary>
/// fighter visuals should always look to the right in the scene.
/// This function flips the sprites horizontally, when the fighter is an enemy.
/// </summary>
private void UpdateMirrorState()
{
_visualParent.Scale = new Vector2(_boundFighter.IsInFormation(HappeningData.enemyFighterFormation) ? -1 : 1, 1);
}
public void UpdateHealthBar()
{
healthBarVisual.UpdateHealth(_boundFighter.GetHealth(), _boundFighter.maxHealth);
}
public void SetTargetSelectionActive(bool value)
{
_targetSelectionParent.Visible = value;
_targetSelectionParent.ProcessMode = value ? ProcessModeEnum.Inherit : ProcessModeEnum.Disabled;
}
// listen from inside
public void ClickedTarget()
{
if (HappeningData.actionStaging!.CurrentDetail() is not TargetSelectActionDetail targetDetail)
throw new InvalidOperationException("No target selection needed right now");
targetDetail.SetTarget(_boundFighter);
FightHappening.Instance.DetailFilled();
}
// Animations
public async Task AnimatePosToTarget(FighterVisual targetVisual, double duration = 0.15)
{
var tween = GetTree().CreateTween();
tween.TweenProperty(_visualParent, "global_position", targetVisual.GlobalPosition, 0.15);
await ToSignal(tween, "finished");
}
public async Task AnimatePosToBase(double duration = 0.7)
{
var tween = GetTree().CreateTween();
tween.TweenProperty(_visualParent, "position", new Vector2(0, 0), 0.15);
await ToSignal(tween, "finished");
}
public async Task AnimateHit()
{
var tween = GetTree().CreateTween();
tween.TweenProperty(this, "scale", new Vector2(1.4f, 0.6f), 0.15);
tween.TweenProperty(this, "scale", new Vector2(1, 1), 0.4)
.SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
await ToSignal(tween, "finished");
}
// Keep for reference for new Heal animation
//public void HealAnimation()
//{
// EmitSignalHealed();
// var tween = GetTree().CreateTween();
// tween.TweenProperty(this, "scale", new Vector2(0.6f, 1.4f), 0.15);
// tween.TweenProperty(this, "scale", new Vector2(1, 1), 0.4)
// .SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
//}
}

@ -0,0 +1,19 @@
using Godot;
using System;
using Babushka.scripts.CSharp.Common.Fight;
public partial class NextRoomTrigger : Area2D
{
[Export] private int pathIndex;
public override void _EnterTree()
{
BodyEntered += _OnBodyEnter;
}
private void _OnBodyEnter(Node2D other)
{
var fss = GetNode<FightSceneSwitcher>("%FightSceneSwitcher");
fss.SwitchRoom(pathIndex);
}
}

@ -0,0 +1,7 @@
using System;
namespace Babushka.scripts.CSharp.Common.Fight;
public class NoFightHappeningException() : Exception("No fight happening right now")
{
}

@ -0,0 +1,49 @@
using System.Diagnostics;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class PathSetup : Node
{
[Export] private int pathId;
[ExportCategory("Variants")] [Export] private CanvasItem closedVariant;
[Export] private CanvasItem nextRoomVariant;
public override void _Ready()
{
SetupPathVariant();
}
private void SetupPathVariant()
{
Debug.Assert(FightWorld.Instance.currentRoom != null);
if (FightWorld.Instance.currentRoom.paths.TryGetValue(pathId, out var nextRoom))
{
ShowOnlyVariant(nextRoomVariant);
}
else
{
ShowOnlyVariant(closedVariant);
}
}
private void ShowOnlyVariant(CanvasItem variantToShow)
{
HideVariant(closedVariant);
HideVariant(nextRoomVariant);
ShowVariant(variantToShow);
}
private void ShowVariant(CanvasItem variant)
{
variant.Visible = true;
variant.ProcessMode = ProcessModeEnum.Always;
}
private void HideVariant(CanvasItem variant)
{
variant.Visible = false;
variant.ProcessMode = ProcessModeEnum.Disabled;
}
}

@ -0,0 +1,20 @@
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class RoamingEnemyGroup : Node2D
{
private FightWorld.FighterGroup _boundEnemyGroup;
private FightSceneSwitcher _fightSceneSwitcher;
public void Initialize(FightWorld.FighterGroup enemyGroup, FightSceneSwitcher fightSceneSwitcher)
{
_boundEnemyGroup = enemyGroup;
_fightSceneSwitcher = fightSceneSwitcher;
}
public void StartFight()
{
_fightSceneSwitcher.SwitchToFight(_boundEnemyGroup);
}
}

@ -0,0 +1,22 @@
using System.Threading.Tasks;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class SwitchSceneOnFightEnd : Node
{
[Export] private FightSceneSwitcher _fightSceneSwitcher = null!;
public void OnFightStateEnter(FightHappening.FightState to)
{
if (to is FightHappening.FightState.PlayerWin
or FightHappening.FightState.EnemyWin)
_ = SwitchSceneAfterTime(2.0f);
}
private async Task SwitchSceneAfterTime(float seconds)
{
await ToSignal(GetTree().CreateTimer(seconds), "timeout");
_fightSceneSwitcher.ExitFight();
}
}

@ -0,0 +1,35 @@
using System.Linq;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight.UI;
public partial class ActionSelectUiSetup : CanvasLayer
{
// shortcuts
private FightWorld.FightHappeningData HappeningData =>
FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
private FightWorld.Fighter CurrentFighter => HappeningData.fighterTurn.Current;
// references
[Export] private Button _attackActionButton = null!;
[Export] private Button _summonActionButton = null!;
[Export] private Button _talkActionButton = null!;
[Export] private Button _fleeActionButton = null!;
// gets called from a state reaction enter (InputActionSelect)
public void StateEntered()
{
var actions = CurrentFighter.availableActions;
_attackActionButton.Visible = actions.Any(a => a.BindToActionButton() == FighterAction.AllyActionButton.Attack);
_summonActionButton.Visible = actions.Any(a => a.BindToActionButton() == FighterAction.AllyActionButton.Summon);
_talkActionButton.Visible = actions.Any(a => a.BindToActionButton() == FighterAction.AllyActionButton.Talk);
_fleeActionButton.Visible = actions.Any(a => a.BindToActionButton() == FighterAction.AllyActionButton.Flee);
}
public void SelectAction(FighterAction.AllyActionButton actionButton)
{
var action = CurrentFighter.availableActions.First(a => a.BindToActionButton() == actionButton);
FightHappening.Instance.ActionSelect(action);
}
}

@ -0,0 +1,14 @@
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight.UI;
[GlobalClass]
public partial class FighterHealthBarVisual : Node2D
{
[Export] private Label _tmpHealthLabel = null!;
public void UpdateHealth(int currentHealth, int maxHealth)
{
_tmpHealthLabel.Text = $"{currentHealth} / {maxHealth}";
}
}

@ -0,0 +1,16 @@
using Godot;
using System;
public partial class TargetSelectionClick : Area2D
{
[Signal]
public delegate void TargetSelectedEventHandler();
public override void _InputEvent(Viewport viewport, InputEvent @event, int shapeIdx)
{
if (@event is InputEventMouseButton { Pressed: true, ButtonIndex: MouseButton.Left })
{
EmitSignalTargetSelected();
}
}
}

@ -4,6 +4,6 @@ public class FightAttack
{
public int damage;
public bool needsSelectedTarget;
public Fighter? target;
public Fighter attacker;
public FighterVisual? target;
public FighterVisual attacker;
}

@ -19,10 +19,10 @@ public partial class FightManager : Node
public FightParty fightParty = new();
public void StartFight(PackedScene[] enemies, FightInstance instance)
public void StartFight(PackedScene[] enemies, FightHappening happening)
{
GD.Print("Starting Fight");
instance.Start(fightParty, enemies);
//happening.Start(fightParty, enemies);
}
}

@ -4,7 +4,7 @@ namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightStarter : Node
{
[Export(PropertyHint.ArrayType)] private PackedScene[] enemies;
[Export] private FightInstance _fightInstance;
//[Export] private FightHappening _fightHappening;
[Export] private bool _once = true;
private bool hasBeenStarted = false;
@ -14,6 +14,6 @@ public partial class FightStarter : Node
return;
hasBeenStarted = true;
FightManager.Instance.StartFight(enemies, _fightInstance);
//FightManager.Instance.StartFight(enemies, _fightHappening);
}
}

@ -0,0 +1,212 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Babushka.scripts.CSharp.Common.Util;
using Godot;
namespace Babushka.scripts.CSharp.Common.Minigame;
public partial class MinigameController : Node2D
{
public enum RegionTheme
{
Disabled,
VeryBad,
Bad,
Normal,
NormalAlt1,
NormalAlt2,
God,
VeryGood
}
public class Builder<T>
{
internal class Region
{
public required T value;
public float proportion = 1f;
public string text = "";
public RegionTheme theme = RegionTheme.Normal;
}
public enum DoubleHitHandling
{
Allow,
Disallow,
Remove,
}
internal List<Region> regions = new();
internal DoubleHitHandling doubleHitHandling = DoubleHitHandling.Allow;
internal int maxHitCount = 3;
// add region
public Builder<T> AddRegion(T value)
{
regions.Add(new Region { value = value });
return this;
}
// region settings
public Builder<T> RegionWithProportion(float proportion)
{
if (regions.Count == 0)
throw new InvalidOperationException("No region to set proportion for");
regions.Last().proportion = proportion;
return this;
}
public Builder<T> RegionWithText(string text)
{
if (regions.Count == 0)
throw new InvalidOperationException("No region to set text for");
regions.Last().text = text;
return this;
}
public Builder<T> RegionWithTheme(RegionTheme theme)
{
if (regions.Count == 0)
throw new InvalidOperationException("No region to set theme for");
regions.Last().theme = theme;
return this;
}
// general settings
public Builder<T> WithDoubleHitHandling(DoubleHitHandling handling)
{
if (handling != DoubleHitHandling.Allow)
throw new NotImplementedException();
doubleHitHandling = handling;
return this;
}
public Builder<T> WithHitCount(int hitCount)
{
this.maxHitCount = hitCount;
return this;
}
}
private TaskCompletionSource? _minigameComplete;
private List<int>? _hits;
private float _armPosition = 0f;
private float _armSpeed = 1f;
private List<float>? _regions;
private int _maxHitCount;
[Export] private PackedScene _regionVisualPrefab = null!;
[Export] private Node2D _regionsParent = null!;
[Export] private Color _baseRegionColor = Colors.Red;
[Signal] public delegate void ArmMovedEventHandler(float newPos);
public override void _EnterTree()
{
HideMinigame();
}
public override void _Process(double delta)
{
_armPosition += _armSpeed * (float)delta;
_armPosition = Mathf.PosMod(_armPosition, 1);
EmitSignalArmMoved(_armPosition);
}
public async Task<List<T>> Run<T>(Builder<T> builder)
{
ShowMinigame();
Setup(builder);
await _minigameComplete!.Task;
var returnValue = _hits!.Select(h => builder.regions[h].value).ToList();
Reset();
HideMinigame();
return returnValue;
}
public void Hit()
{
if (_hits == null) return;
int i;
for (i = 0; i < _regions!.Count - 1; i++)
{
if (_armPosition < _regions[i])
{
break;
}
}
_hits.Add(i);
_armSpeed = -_armSpeed;
if (_hits.Count >= _maxHitCount)
{
_minigameComplete!.SetResult();
}
}
private void Setup<T>(Builder<T> builder)
{
_minigameComplete = new();
_hits = [];
_armPosition = 0f;
_armSpeed = 1f;
_regions = [];
SetupRegions(builder);
_maxHitCount = builder.maxHitCount;
}
private void SetupRegions<T>(Builder<T> builder)
{
// common calculations
var totalRegionProportion = builder.regions.Sum(r => r.proportion);
// spawn regions
var regionSum = 0f;
foreach (var region in builder.regions)
{
var regionVisual = _regionVisualPrefab.Instantiate<RegionVisual>();
_regionsParent.AddChild(regionVisual);
var normalisedAngleStart = regionSum / totalRegionProportion;
var normalisedAngleEnd = (regionSum + region.proportion) / totalRegionProportion;
var normalAngles = new Vector2(normalisedAngleStart, normalisedAngleEnd);
regionVisual.Setup(normalAngles, _baseRegionColor.RandomHue(), region.text, region.theme);
regionSum += region.proportion;
_regions!.Add(normalisedAngleEnd);
}
}
private void ShowMinigame()
{
Show();
ProcessMode = ProcessModeEnum.Inherit;
}
private void HideMinigame()
{
Hide();
ProcessMode = ProcessModeEnum.Disabled;
}
private void Reset()
{
_minigameComplete = null;
_hits = null;
_regionsParent.GetChildren().ForEach(c => c.QueueFree());
}
}

@ -0,0 +1,32 @@
using Godot;
using System;
using Babushka.scripts.CSharp.Common.Minigame;
using Godot.Collections;
public partial class RegionVisual : Node
{
[Export] private Sprite2D _sliceSprite;
[Export] private Label _textLabel;
[Export] private Node2D _labelPivot;
[Export(PropertyHint.DictionaryType)] private Dictionary<MinigameController.RegionTheme, Color> _fillColors = new();
public void Setup(Vector2 normalAngles, Color color, string regionText, MinigameController.RegionTheme regionTheme)
{
var mat = (_sliceSprite.Material as ShaderMaterial)!;
mat.SetShaderParameter("angles", normalAngles);
mat.SetShaderParameter("fillColor", GetFillColor(regionTheme));
var averageAngleRadians = (normalAngles.X + normalAngles.Y) * float.Pi; // '/ 2' from the average and '* 2' from the radians cancel out
_labelPivot.Rotation = averageAngleRadians;
_textLabel.Rotation = -averageAngleRadians;
_textLabel.Text = regionText;
}
private Color GetFillColor(MinigameController.RegionTheme theme)
{
if (_fillColors.TryGetValue(theme, out var color)) return color;
throw new InvalidOperationException($"No fill color for theme {theme}");
}
}

@ -0,0 +1,10 @@
using Godot;
using System;
public partial class SpinnyArmVisual : Node2D
{
public void SetAngle(float normalAngle)
{
Rotation = normalAngle * float.Pi * 2;
}
}

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
using Babushka.scripts.CSharp.Common.Minigame;
using Godot;
namespace Babushka.scripts.CSharp.Common.TestScripts;
public partial class MinigameTestStarter : Node
{
[Export] private MinigameController _minigameController = null!;
public override void _Ready()
{
_minigameController.Run(
new MinigameController.Builder<int>()
.WithHitCount(5)
.AddRegion(1)
.AddRegion(2).RegionWithProportion(2)
.AddRegion(3)
.AddRegion(4).RegionWithProportion(2)
.AddRegion(5)
.AddRegion(6).RegionWithProportion(2)
).ContinueWith(task => OnEnd(task.Result));
}
private void OnEnd(List<int> result)
{
GD.Print(string.Join(" ,", result));
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save