diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..d72312d
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,239 @@
+root = true
+
+###############################
+# Core EditorConfig Options #
+###############################
+
+[*]
+charset = utf-8
+indent_style = tab
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+###############################
+# C# Coding Conventions #
+###############################
+
+[*.cs]
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_labels = flush_left
+csharp_indent_switch_labels = true
+
+csharp_new_line_before_catch = false
+csharp_new_line_before_else = false
+csharp_new_line_before_finally = false
+csharp_new_line_before_members_in_anonymous_types = false
+csharp_new_line_before_members_in_object_initializers = false
+csharp_new_line_before_open_brace = none
+csharp_new_line_between_query_expression_clauses = false
+
+csharp_prefer_braces = true:warning
+csharp_prefer_simple_default_expression = true:warning
+csharp_prefer_simple_using_statement = true:warning
+csharp_prefer_static_local_function = true:warning
+
+csharp_preferred_modifier_order = public, protected, internal, private, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async:warning
+
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = false
+
+csharp_space_after_cast = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_square_brackets = false
+
+csharp_style_conditional_delegate_call = true:warning
+csharp_style_deconstructed_variable_declaration = true:warning
+
+csharp_style_expression_bodied_accessors = true:warning
+csharp_style_expression_bodied_constructors = true:warning
+csharp_style_expression_bodied_indexers = true:warning
+csharp_style_expression_bodied_lambdas = true:warning
+csharp_style_expression_bodied_local_functions = true:warning
+csharp_style_expression_bodied_methods = true:warning
+csharp_style_expression_bodied_operators = true:warning
+csharp_style_expression_bodied_properties = true:warning
+
+csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
+csharp_style_inlined_variable_declaration = true:warning
+
+csharp_style_namespace_declarations = file_scoped:warning
+
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+
+csharp_style_prefer_extended_property_pattern = true:warning
+dotnet_style_prefer_foreach_explicit_cast_in_source = always:warning
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_local_over_anonymous_function = true:warning
+csharp_style_prefer_method_group_conversion = true:warning
+csharp_style_prefer_not_pattern = true:warning
+csharp_style_prefer_null_check_over_type_check = true:warning
+csharp_style_prefer_pattern_matching = true:warning
+csharp_style_prefer_primary_constructors = true:warning
+csharp_style_prefer_range_operator = true:warning
+csharp_style_prefer_readonly_struct = true:warning
+csharp_style_prefer_readonly_struct_member = true:warning
+csharp_style_prefer_switch_expression = true:warning
+csharp_style_prefer_top_level_statements = false:warning
+csharp_style_prefer_tuple_swap = true:warning
+csharp_style_prefer_utf8_string_literals = true:warning
+
+csharp_style_throw_expression = true:warning
+
+csharp_style_unused_value_assignment_preference = discard_variable:warning
+csharp_style_unused_value_expression_statement_preference = discard_variable:warning
+
+csharp_style_var_elsewhere = false:warning
+csharp_style_var_for_built_in_types = false:warning
+csharp_style_var_when_type_is_apparent = false:warning
+
+csharp_using_directive_placement = outside_namespace:warning
+
+###############################
+# .NET Coding Conventions #
+###############################
+
+dotnet_analyzer_diagnostic.severity = warning
+
+dotnet_code_quality.ca3003.excluded_symbol_names = BotController
+dotnet_code_quality.ca3012.excluded_symbol_names = BotController|CommandController
+
+dotnet_code_quality_unused_parameters = all:warning
+
+dotnet_diagnostic.ca1028.severity = silent
+dotnet_diagnostic.ca1031.severity = silent
+dotnet_diagnostic.ca1863.severity = silent
+
+# Rule - almost everything
+dotnet_naming_rule.almost_everything_must_be_pascal_case.severity = warning
+dotnet_naming_rule.almost_everything_must_be_pascal_case.style = pascal_case
+dotnet_naming_rule.almost_everything_must_be_pascal_case.symbols = almost_everything
+
+# Rule - enums
+dotnet_naming_rule.enums_must_be_e_pascal_case.severity = warning
+dotnet_naming_rule.enums_must_be_e_pascal_case.style = e_pascal_case
+dotnet_naming_rule.enums_must_be_e_pascal_case.symbols = enums
+
+# Rule - interfaces
+dotnet_naming_rule.interfaces_must_be_i_pascal_case.severity = warning
+dotnet_naming_rule.interfaces_must_be_i_pascal_case.style = i_pascal_case
+dotnet_naming_rule.interfaces_must_be_i_pascal_case.symbols = interfaces
+
+# Rule - local parameters
+dotnet_naming_rule.local_parameters_must_be_camel_case.severity = warning
+dotnet_naming_rule.local_parameters_must_be_camel_case.style = camel_case
+dotnet_naming_rule.local_parameters_must_be_camel_case.symbols = local_parameters
+
+# Rule - type parameters
+dotnet_naming_rule.type_parameters_must_be_t_pascal_case.severity = warning
+dotnet_naming_rule.type_parameters_must_be_t_pascal_case.style = t_pascal_case
+dotnet_naming_rule.type_parameters_must_be_t_pascal_case.symbols = type_parameters
+
+# Style - camelCase
+dotnet_naming_style.camel_case.capitalization = camel_case
+
+# Style - EPascalCase
+dotnet_naming_style.e_pascal_case.capitalization = pascal_case
+dotnet_naming_style.e_pascal_case.required_prefix = E
+
+# Style - IPascalCase
+dotnet_naming_style.i_pascal_case.capitalization = pascal_case
+dotnet_naming_style.i_pascal_case.required_prefix = I
+
+# Style - PascalCase
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+# Style - TPascalCase
+dotnet_naming_style.t_pascal_case.capitalization = pascal_case
+dotnet_naming_style.t_pascal_case.required_prefix = T
+
+# Symbol - almost everything
+dotnet_naming_symbols.almost_everything.applicable_accessibilities = *
+dotnet_naming_symbols.almost_everything.applicable_kinds = namespace, class, struct, property, method, field, event, delegate
+
+# Symbol - enums
+dotnet_naming_symbols.enums.applicable_accessibilities = *
+dotnet_naming_symbols.enums.applicable_kinds = enum
+
+# Symbol - interfaces
+dotnet_naming_symbols.interfaces.applicable_accessibilities = *
+dotnet_naming_symbols.interfaces.applicable_kinds = interface
+
+# Symbol - local parameters
+dotnet_naming_symbols.local_parameters.applicable_accessibilities = *
+dotnet_naming_symbols.local_parameters.applicable_kinds = parameter, local, local_function
+
+# Symbol - type parameters
+dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
+dotnet_naming_symbols.type_parameters.applicable_kinds = type_parameter
+
+dotnet_remove_unnecessary_suppression_exclusions = none:warning
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = true
+
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_collection_initializer = true:warning
+dotnet_style_explicit_tuple_names = true:warning
+dotnet_style_namespace_match_folder = true:warning
+dotnet_style_null_propagation = true:warning
+dotnet_style_object_initializer = true:warning
+
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
+
+dotnet_style_predefined_type_for_locals_parameters_members = true:warning
+dotnet_style_predefined_type_for_member_access = true:warning
+
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_compound_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_return = true:warning
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+dotnet_style_prefer_inferred_tuple_names = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_simplified_boolean_expressions = true:warning
+dotnet_style_prefer_simplified_interpolation = true:warning
+
+dotnet_style_qualification_for_event = false:warning
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_property = false:warning
+
+dotnet_style_readonly_field = true:warning
+dotnet_style_require_accessibility_modifiers = always:warning
+
+###############################
+# JetBrains, IntelliJ/Rider #
+###############################
+
+[*.{csproj,props,resx,xml}]
+ij_xml_keep_blank_lines = 1
+ij_xml_keep_line_breaks = false
+ij_xml_keep_line_breaks_in_text = false
+ij_xml_space_inside_empty_tag = true
+
+[*.{json,json5}]
+ij_json_keep_line_breaks = false
diff --git a/ItemDispenser.sln.DotSettings b/ItemDispenser.sln.DotSettings
new file mode 100644
index 0000000..aa02323
--- /dev/null
+++ b/ItemDispenser.sln.DotSettings
@@ -0,0 +1,807 @@
+
+ True
+ True
+ FullFormat
+ True
+ False
+ True
+ True
+
+ SOLUTION
+ True
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ HINT
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ HINT
+ HINT
+ SUGGESTION
+ WARNING
+
+ SUGGESTION
+ SUGGESTION
+ HINT
+ WARNING
+ SUGGESTION
+
+ True
+ SUGGESTION
+ WARNING
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ HINT
+ HINT
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+
+
+ WARNING
+ WARNING
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ HINT
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ HINT
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+
+
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ SUGGESTION
+ WARNING
+ SUGGESTION
+ WARNING
+ HINT
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ HINT
+ SUGGESTION
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+
+ True
+ WARNING
+ WARNING
+
+
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ SUGGESTION
+ SUGGESTION
+ WARNING
+ WARNING
+ WARNING
+ True
+ SUGGESTION
+ Default
+ Default
+ Default
+
+ <?xml version="1.0" encoding="utf-16"?><Profile name="Archi"><CSReorderTypeMembers>True</CSReorderTypeMembers><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><HtmlReformatCode>True</HtmlReformatCode><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" ArrangeNullCheckingPattern="True" /><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><CssAlphabetizeProperties>True</CssAlphabetizeProperties><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><VBReformatCode>True</VBReformatCode><VBFormatDocComments>True</VBFormatDocComments><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSUpdateFileHeader>True</CSUpdateFileHeader><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppJoinDeclarationAndAssignmentDescriptor>True</CppJoinDeclarationAndAssignmentDescriptor><CppMakeLocalVarConstDescriptor>True</CppMakeLocalVarConstDescriptor><CppMakeMethodConst>True</CppMakeMethodConst><CppMakeMethodStatic>True</CppMakeMethodStatic><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppRemoveRedundantMemberInitializerDescriptor>True</CppRemoveRedundantMemberInitializerDescriptor><CppRemoveRedundantParentheses>True</CppRemoveRedundantParentheses><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppRemoveTemplateArgumentsDescriptor>True</CppRemoveTemplateArgumentsDescriptor><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppRemoveUnreachableCode>True</CppRemoveUnreachableCode><CppRemoveUnusedIncludes>True</CppRemoveUnusedIncludes><CppRemoveUnusedLambdaCaptures>True</CppRemoveUnusedLambdaCaptures><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppCodeStyleCleanupDescriptor ArrangeAuto="True" ArrangeBraces="True" ArrangeCVQualifiers="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeOverridingFunctions="True" ArrangeSlashesInIncludeDirectives="True" ArrangeTypeAliases="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><CppReformatCode>True</CppReformatCode><CppUpdateFileHeader>True</CppUpdateFileHeader><IDEA_SETTINGS><profile version="1.0">
+ <option name="myName" value="Archi" />
+ <inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="JSRemoveUnnecessaryParentheses" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="TypeScriptExplicitMemberType" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true" />
+</profile></IDEA_SETTINGS><ShaderLabReformatCode>True</ShaderLabReformatCode><CppRemoveCastDescriptor>True</CppRemoveCastDescriptor><CppRemoveElaboratedTypeSpecifierDescriptor>True</CppRemoveElaboratedTypeSpecifierDescriptor><CppRemoveRedundantLambdaParameterListDescriptor>True</CppRemoveRedundantLambdaParameterListDescriptor><CppTypeTraitAliasDescriptor>True</CppTypeTraitAliasDescriptor><CppReplaceTieWithStructuredBindingDescriptor>True</CppReplaceTieWithStructuredBindingDescriptor><CppUseAssociativeContainsDescriptor>True</CppUseAssociativeContainsDescriptor><CppUseEraseAlgorithmDescriptor>True</CppUseEraseAlgorithmDescriptor><RIDER_SETTINGS><profile>
+ <Language id="CSS">
+ <Reformat>true</Reformat>
+ <Rearrange>true</Rearrange>
+ </Language>
+ <Language id="EditorConfig">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="HTML">
+ <Reformat>true</Reformat>
+ <Rearrange>true</Rearrange>
+ <OptimizeImports>true</OptimizeImports>
+ </Language>
+ <Language id="HTTP Request">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="Handlebars">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="Ini">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="JSON">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="Jade">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="JavaScript">
+ <Reformat>true</Reformat>
+ <Rearrange>true</Rearrange>
+ <OptimizeImports>true</OptimizeImports>
+ </Language>
+ <Language id="Markdown">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="Properties">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="RELAX-NG">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="SQL">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="VueExpr">
+ <Reformat>true</Reformat>
+ </Language>
+ <Language id="XML">
+ <Reformat>true</Reformat>
+ <Rearrange>true</Rearrange>
+ <OptimizeImports>true</OptimizeImports>
+ </Language>
+ <Language id="yaml">
+ <Reformat>true</Reformat>
+ </Language>
+</profile></RIDER_SETTINGS><FSReformatCode>True</FSReformatCode><CppMakePtrOrRefParameterConst>True</CppMakePtrOrRefParameterConst><CppMakeParameterConst>True</CppMakeParameterConst><CppClangTidyCleanupDescriptor abseil-cleanup-ctad="True" abseil-duration-addition="True" abseil-duration-comparison="True" abseil-duration-conversion-cast="True" abseil-duration-division="True" abseil-duration-factory-float="True" abseil-duration-factory-scale="True" abseil-duration-subtraction="True" abseil-duration-unnecessary-conversion="True" abseil-faster-strsplit-delimiter="True" abseil-redundant-strcat-calls="True" abseil-str-cat-append="True" abseil-string-find-startswith="True" abseil-string-find-str-contains="True" abseil-time-comparison="True" abseil-time-subtraction="True" abseil-upgrade-duration-conversions="True" android-cloexec-accept="True" android-cloexec-creat="True" android-cloexec-dup="True" android-cloexec-epoll-create="True" android-cloexec-epoll-create1="True" android-cloexec-fopen="True" android-cloexec-inotify-init="True" android-cloexec-inotify-init1="True" android-cloexec-memfd-create="True" android-cloexec-open="True" android-cloexec-pipe="True" boost-use-to-string="True" bugprone-argument-comment="True" bugprone-bool-pointer-implicit-conversion="True" bugprone-copy-constructor-init="True" bugprone-implicit-widening-of-multiplication-result="True" bugprone-inaccurate-erase="True" bugprone-macro-parentheses="True" bugprone-misplaced-operator-in-strlen-in-alloc="True" bugprone-misplaced-pointer-arithmetic-in-alloc="True" bugprone-move-forwarding-reference="True" bugprone-not-null-terminated-result="True" bugprone-parent-virtual-call="True" bugprone-posix-return="True" bugprone-redundant-branch-condition="True" bugprone-reserved-identifier="True" bugprone-shared-ptr-array-mismatch="True" bugprone-string-constructor="True" bugprone-string-integer-assignment="True" bugprone-stringview-nullptr="True" bugprone-suspicious-memset-usage="True" bugprone-suspicious-semicolon="True" bugprone-suspicious-string-compare="True" bugprone-swapped-arguments="True" bugprone-terminating-continue="True" bugprone-unused-raii="True" bugprone-virtual-near-miss="True" cert-dcl03-c="True" cert-dcl16-c="True" cert-dcl21-cpp="True" cert-oop11-cpp="True" cppcoreguidelines-explicit-virtual-functions="True" cppcoreguidelines-init-variables="True" cppcoreguidelines-prefer-member-initializer="True" cppcoreguidelines-pro-bounds-constant-array-index="True" cppcoreguidelines-pro-type-cstyle-cast="True" cppcoreguidelines-pro-type-member-init="True" cppcoreguidelines-pro-type-static-cast-downcast="True" cppcoreguidelines-virtual-class-destructor="True" darwin-dispatch-once-nonstatic="True" fuchsia-default-arguments-declarations="True" fuchsia-restrict-system-includes="True" google-build-explicit-make-pair="True" google-explicit-constructor="True" google-objc-avoid-nsobject-new="True" google-objc-function-naming="True" google-readability-braces-around-statements="True" google-readability-casting="True" google-readability-redundant-smartptr-get="True" google-readability-todo="True" google-upgrade-googletest-case="True" hicpp-braces-around-statements="True" hicpp-deprecated-headers="True" hicpp-explicit-conversions="True" hicpp-member-init="True" hicpp-move-const-arg="True" hicpp-named-parameter="True" hicpp-static-assert="True" hicpp-uppercase-literal-suffix="True" hicpp-use-auto="True" hicpp-use-emplace="True" hicpp-use-equals-default="True" hicpp-use-equals-delete="True" hicpp-use-noexcept="True" hicpp-use-nullptr="True" hicpp-use-override="True" llvm-else-after-return="True" llvm-header-guard="True" llvm-include-order="True" llvm-prefer-isa-or-dyn-cast-in-conditionals="True" llvm-prefer-register-over-unsigned="True" llvm-qualified-auto="True" llvm-twine-local="True" llvmlibc-restrict-system-libc-headers="True" misc-const-correctness="True" misc-definitions-in-headers="True" misc-macro-parentheses="True" misc-redundant-expression="True" misc-static-assert="True" misc-string-compare="True" misc-string-integer-assignment="True" misc-suspicious-string-compare="True" misc-uniqueptr-reset-release="True" misc-unused-alias-decls="True" misc-unused-parameters="True" misc-unused-raii="True" misc-unused-using-decls="True" modernize-avoid-bind="True" modernize-concat-nested-namespaces="True" modernize-deprecated-headers="True" modernize-deprecated-ios-base-aliases="True" modernize-loop-convert="True" modernize-macro-to-enum="True" modernize-make-shared="True" modernize-make-unique="True" modernize-pass-by-value="True" modernize-raw-string-literal="True" modernize-redundant-void-arg="True" modernize-replace-auto-ptr="True" modernize-replace-disallow-copy-and-assign-macro="True" modernize-replace-random-shuffle="True" modernize-return-braced-init-list="True" modernize-shrink-to-fit="True" modernize-use-auto="True" modernize-use-bool-literals="True" modernize-use-default-member-init="True" modernize-use-emplace="True" modernize-use-equals-default="True" modernize-use-equals-delete="True" modernize-use-nodiscard="True" modernize-use-noexcept="True" modernize-use-nullptr="True" modernize-use-override="True" modernize-use-trailing-return-type="True" modernize-use-uncaught-exceptions="True" modernize-use-using="True" objc-assert-equals="True" objc-property-declaration="True" objc-super-self="True" performance-faster-string-find="True" performance-for-range-copy="True" performance-inefficient-algorithm="True" performance-inefficient-vector-operation="True" performance-move-const-arg="True" performance-move-constructor-init="True" performance-noexcept-move-constructor="True" performance-trivially-destructible="True" performance-type-promotion-in-math-fn="True" performance-unnecessary-value-param="True" portability-restrict-system-includes="True" readability-braces-around-statements="True" readability-const-return-type="True" readability-container-contains="True" readability-container-data-pointer="True" readability-container-size-empty="True" readability-convert-member-functions-to-static="True" readability-delete-null-pointer="True" readability-duplicate-include="True" readability-else-after-return="True" readability-identifier-naming="True" readability-implicit-bool-conversion="True" readability-inconsistent-declaration-parameter-name="True" readability-isolate-declaration="True" readability-make-member-function-const="True" readability-misplaced-array-index="True" readability-named-parameter="True" readability-non-const-parameter="True" readability-qualified-auto="True" readability-redundant-access-specifiers="True" readability-redundant-control-flow="True" readability-redundant-declaration="True" readability-redundant-function-ptr-dereference="True" readability-redundant-member-init="True" readability-redundant-smartptr-get="True" readability-redundant-string-cstr="True" readability-redundant-string-init="True" readability-simplify-boolean-expr="True" readability-simplify-subscript-expr="True" readability-static-accessed-through-instance="True" readability-static-definition-in-anonymous-namespace="True" readability-string-compare="True" readability-uniqueptr-delete-release="True" readability-uppercase-literal-suffix="True" other-fixits="True" bugprone-standalone-empty="True" bugprone-unique-ptr-array-mismatch="True" cppcoreguidelines-misleading-capture-default-by-value="True" cppcoreguidelines-noexcept-destructor="True" cppcoreguidelines-noexcept-move-operations="True" cppcoreguidelines-noexcept-swap="True" cppcoreguidelines-use-default-member-init="True" llvmlibc-inline-function-decl="True" misc-include-cleaner="True" modernize-type-traits="True" modernize-use-std-print="True" performance-avoid-endl="True" performance-noexcept-destructor="True" performance-noexcept-swap="True" readability-operators-representation="True" /><CSReformatInactiveBranches>True</CSReformatInactiveBranches><FSharpReformatCode>True</FSharpReformatCode><CppMakeClassFinal>True</CppMakeClassFinal><CppMakeVariableConstexpr>True</CppMakeVariableConstexpr><CppPassValueParameterByConstReference>True</CppPassValueParameterByConstReference><CppRedundantDereferences>True</CppRedundantDereferences><CppReplaceIfWithIfConsteval>True</CppReplaceIfWithIfConsteval><CppRemoveRedundantConditionalExpressionDescriptor>True</CppRemoveRedundantConditionalExpressionDescriptor><CppSimplifyConditionalExpressionDescriptor>True</CppSimplifyConditionalExpressionDescriptor></Profile>
+ Archi
+ USE_TABS_ONLY
+ True
+ Required
+ Required
+ Required
+ Required
+ ExpressionBody
+ DefaultExpression
+ ExpressionBody
+ ExpressionBody
+ public protected internal private static extern new virtual abstract sealed override readonly unsafe volatile async
+
+ Arithmetic, Shift, Bitwise, Conditional
+ END_OF_LINE
+ END_OF_LINE
+ USE_TABS_ONLY
+ False
+ False
+
+ END_OF_LINE
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 0
+ END_OF_LINE
+ TOGETHER_SAME_LINE
+ NO_INDENT
+ Tab
+ END_OF_LINE
+ END_OF_LINE
+ 1
+ 1
+ False
+ False
+ False
+ True
+ False
+
+ True
+ 1
+
+ END_OF_LINE
+ True
+ NEVER
+ NEVER
+ False
+ False
+ False
+ NEVER
+ False
+ False
+ NEVER
+ LINE_BREAK
+ LINE_BREAK
+
+ True
+ True
+ False
+ END_OF_LINE
+ False
+ True
+ True
+ True
+ True
+ WRAP_IF_LONG
+ 65535
+ False
+ USE_TABS_ONLY
+ USE_TABS_ONLY
+ Tab
+ False
+ USE_TABS_ONLY
+ USE_TABS_ONLY
+ USE_TABS_ONLY
+ USE_TABS_ONLY
+ USE_TABS_ONLY
+ 4
+ Tab
+ True
+ 2147483646
+ OnSingleLine
+ 4
+ ByFirstAttr
+ OnSingleLine
+ False
+ 10000
+ False
+ USE_TABS_ONLY
+ 4
+ Tab
+ OnSingleLine
+ 4
+ OnSingleLine
+ False
+ 10000
+ False
+ <?xml version="1.0" encoding="utf-16"?>
+<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
+ <TypePattern DisplayName="ArchiPattern" Priority="150">
+ <Entry DisplayName="Public (Events and Delegates)">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Or>
+ <Kind Is="Delegate" />
+ <Kind Is="Event" />
+ </Or>
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Constants">
+ <Entry.Match>
+ <Kind Is="Constant" />
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Static (Fields, Properties and Indexers)">
+ <Entry.Match>
+ <Or>
+ <And>
+ <Kind Is="Field" />
+ <Static />
+ </And>
+ <And>
+ <Kind Is="Autoproperty" />
+ <Static />
+ </And>
+ <And>
+ <Kind Is="Property" />
+ <Static />
+ </And>
+ <And>
+ <Kind Is="Indexer" />
+ <Static />
+ </And>
+ </Or>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Readonly />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Non-static (Fields, Properties and Indexers)">
+ <Entry.Match>
+ <And>
+ <Not>
+ <Static />
+ </Not>
+ <Or>
+ <Kind Is="Field" />
+ <Kind Is="Autoproperty" />
+ <Kind Is="Property" />
+ <Kind Is="Indexer" />
+ </Or>
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Readonly />
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Constructors">
+ <Entry.Match>
+ <Kind Is="Constructor" />
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Interfaces">
+ <Entry.Match>
+ <And>
+ <Kind Is="Member" />
+ <ImplementsInterface />
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Everything else">
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="Nested">
+ <Entry.Match>
+ <Kind Is="Type" />
+ </Entry.Match>
+ <Entry.SortBy>
+ <Access />
+ <Kind Is="Member" />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ </TypePattern>
+ <TypePattern DisplayName="COM interfaces or structs">
+ <TypePattern.Match>
+ <Or>
+ <And>
+ <Kind Is="Interface" />
+ <Or>
+ <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" />
+ <HasAttribute Name="System.Runtime.InteropServices.ComImport" />
+ </Or>
+ </And>
+ <Kind Is="Struct" />
+ </Or>
+ </TypePattern.Match>
+ </TypePattern>
+ <TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All">
+ <TypePattern.Match>
+ <And>
+ <Kind Is="Class" />
+ <HasMember>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="Xunit.FactAttribute" Inherited="True" />
+ </And>
+ </HasMember>
+ </And>
+ </TypePattern.Match>
+ <Entry DisplayName="Setup/Teardown Methods">
+ <Entry.Match>
+ <Or>
+ <Kind Is="Constructor" />
+ <And>
+ <Kind Is="Method" />
+ <ImplementsInterface Name="System.IDisposable" />
+ </And>
+ </Or>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Kind Order="Constructor" />
+ </Entry.SortBy>
+ </Entry>
+ <Entry DisplayName="All other members" />
+ <Entry DisplayName="Test Methods" Priority="100">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="Xunit.FactAttribute" />
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ </TypePattern>
+ <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All">
+ <TypePattern.Match>
+ <And>
+ <Kind Is="Class" />
+ <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" />
+ </And>
+ </TypePattern.Match>
+ <Entry DisplayName="Setup/Teardown Methods">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <Or>
+ <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" />
+ <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" />
+ <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" />
+ <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" />
+ </Or>
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="All other members" />
+ <Entry DisplayName="Test Methods" Priority="100">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="NUnit.Framework.TestAttribute" />
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ </TypePattern>
+</Patterns>
+ UseExplicitType
+ UseExplicitType
+ UseExplicitType
+ False
+ False
+ AES
+ API
+ ASF
+ EWCF
+ FA
+ FS
+ GC
+ GID
+ HTML
+ IASF
+ ID
+ IP
+ IPC
+ OK
+ OS
+ PICS
+ PIN
+ SC
+ SMS
+ TTL
+ URL
+ WCF
+ WS
+ WTF
+ WWW
+ XML
+ False
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy>
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" />
+
+ True
+ OnlyMarkers
+ Never
+ Never
+ True
+ Never
+ False
+ False
+ LIVE_MONITOR
+ LIVE_MONITOR
+ LIVE_MONITOR
+ LIVE_MONITOR
+ LIVE_MONITOR
+ LIVE_MONITOR
+ LIVE_MONITOR
+ LIVE_MONITOR
+ LIVE_MONITOR
+ LIVE_MONITOR
+ LIVE_MONITOR
+ LIVE_MONITOR
+ DO_NOTHING
+ LIVE_MONITOR
+ LIVE_MONITOR
+ NOTIFY
+ NOTIFY
+
+
+ True
+ NOTIFY
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ 8
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
diff --git a/ItemDispenser/ItemDispenser.cs b/ItemDispenser/ItemDispenser.cs
index 7220157..b1724b9 100644
--- a/ItemDispenser/ItemDispenser.cs
+++ b/ItemDispenser/ItemDispenser.cs
@@ -1,101 +1,97 @@
using System;
using System.Collections.Concurrent;
+using System.Collections.Frozen;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
+using System.Text.Json;
using System.Threading.Tasks;
+using ArchiSteamFarm.Core;
+using ArchiSteamFarm.Helpers.Json;
+using ArchiSteamFarm.Plugins.Interfaces;
using ArchiSteamFarm.Steam;
using ArchiSteamFarm.Steam.Cards;
using ArchiSteamFarm.Steam.Data;
using ArchiSteamFarm.Steam.Storage;
-using ArchiSteamFarm.Collections;
-using ArchiSteamFarm.Core;
-using ArchiSteamFarm.Plugins.Interfaces;
-using JetBrains.Annotations;
-using System.Text.Json;
-using ArchiSteamFarm.Helpers.Json;
-namespace ItemDispenser {
- [Export(typeof(IPlugin))]
- public class ItemDispenser : IBotTradeOffer, IBotModules {
+namespace ItemDispenser;
- private readonly ConcurrentDictionary> BotSettings = new();
+[Export(typeof(IPlugin))]
- public string Name => nameof(ItemDispenser);
+internal sealed class ItemDispenser : IBotTradeOffer, IBotModules {
+ private static readonly ConcurrentDictionary>> BotSettings = new();
- public Version Version => typeof(ItemDispenser).Assembly.GetName().Version ?? new Version("0.0.0.0");
+ public string Name => nameof(ItemDispenser);
- public async Task OnBotTradeOffer(Bot bot, TradeOffer tradeOffer) {
- if (tradeOffer == null) {
- ASF.ArchiLogger.LogNullError(tradeOffer);
- return false;
- }
+ public Version Version => typeof(ItemDispenser).Assembly.GetName().Version ?? new Version("0.0.0.0");
- //If we receiveing something in return, and donations is not accepted - ignore.
- if (tradeOffer.ItemsToReceiveReadOnly.Count > 0 && !bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.AcceptDonations)) {
- return false;
- }
- byte? holdDuration = await bot.GetTradeHoldDuration(tradeOffer.OtherSteamID64, tradeOffer.TradeOfferID).ConfigureAwait(false);
+ public Task OnBotInitModules(Bot bot, IReadOnlyDictionary? additionalConfigProperties) {
+ ArgumentNullException.ThrowIfNull(bot);
- if (!holdDuration.HasValue) {
- // If we can't get trade hold duration, ignore
- return false;
- }
+ if (additionalConfigProperties == null) {
+ _ = BotSettings.TryRemove(bot, out _);
+
+ return Task.CompletedTask;
+ }
+
+ if (!additionalConfigProperties.TryGetValue("Rudokhvist.DispenseItems", out JsonElement jsonElement)) {
+ _ = BotSettings.TryRemove(bot, out _);
+
+ return Task.CompletedTask;
+ }
- // If user has a trade hold, we add extra logic
- if (holdDuration.Value > 0) {
- // If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade
- if ((holdDuration.Value > (ASF.GlobalConfig?.MaxTradeHoldDuration ?? 0)) || tradeOffer.ItemsToGiveReadOnly.Any(item => ((item.Type == Asset.EType.FoilTradingCard) || (item.Type == Asset.EType.TradingCard)) && CardsFarmer.SalesBlacklist.Contains(item.RealAppID))) {
- return false;
+ Dictionary<(uint AppID, ulong ContextID), HashSet> botSettings = [];
+
+ try {
+ foreach (DispenseItem? dispenseItem in jsonElement.EnumerateArray().Select(static elem => elem.ToJsonObject())) {
+ if (dispenseItem == null) {
+ continue;
}
- }
- //if we can't get settings for this bot for some reason - ignore
- if (!BotSettings.TryGetValue(bot, out ConcurrentHashSet? ItemsToDispense)) {
- return false;
- }
+ (uint AppID, ulong ContextID) key = (dispenseItem.AppID, dispenseItem.ContextID);
- foreach (Asset item in tradeOffer.ItemsToGiveReadOnly) {
- if (!ItemsToDispense.Any(sample =>
- (sample.AppID == item.AppID) &&
- (sample.ContextID == item.ContextID) &&
- (sample.Types.Count <= 0 || sample.Types.Any(type => type == item.Type))
- )) {
- return false;
+ if (!botSettings.TryGetValue(key, out HashSet? types)) {
+ types = [];
+ botSettings[key] = types;
}
+
+ types.UnionWith(dispenseItem.Types);
}
- return true;
- }
+ BotSettings[bot] = botSettings.ToFrozenDictionary(static kv => kv.Key, static kv => kv.Value.ToFrozenSet());
+ } catch (Exception e) {
+ bot.ArchiLogger.LogGenericException(e);
+ bot.ArchiLogger.LogGenericError("Item Dispenser configuration is wrong!");
- public Task OnLoaded() {
- ASF.ArchiLogger.LogGenericInfo("Item Dispenser Plugin by Rudokhvist, powered by ginger cats");
- return Task.CompletedTask;
+ _ = BotSettings.TryRemove(bot, out _);
}
+ return Task.CompletedTask;
+ }
- public Task OnBotInitModules(Bot bot, IReadOnlyDictionary? additionalConfigProperties) {
-
- if (additionalConfigProperties == null) {
- BotSettings.AddOrUpdate(bot, [], (k, v) => []);
- return Task.CompletedTask;
- }
+ public async Task OnBotTradeOffer(Bot bot, TradeOffer tradeOffer) {
+ ArgumentNullException.ThrowIfNull(bot);
+ ArgumentNullException.ThrowIfNull(tradeOffer);
- if (!additionalConfigProperties.TryGetValue("Rudokhvist.DispenseItems", out JsonElement jToken)) {
- BotSettings.AddOrUpdate(bot, [], (k, v) => []);
- return Task.CompletedTask;
- }
+ if (!BotSettings.TryGetValue(bot, out FrozenDictionary<(uint AppID, ulong ContextID), FrozenSet>? itemsToDispense)) {
+ // Settings not declared for this bot, skip overhead
+ return false;
+ }
- ConcurrentHashSet dispenseItems = [];
- try {
- dispenseItems.UnionWith(jToken.EnumerateArray().Select(static elem => elem.ToJsonObject()).OfType());
- BotSettings.AddOrUpdate(bot, dispenseItems, (k, v) => dispenseItems);
- } catch {
- bot.ArchiLogger.LogGenericError("Item Dispenser configuration is wrong!");
- BotSettings.AddOrUpdate(bot, [], (k, v) => []);
- }
- return Task.CompletedTask;
+ // If we're receiving something in return, and donations is not accepted - ignore
+ if ((tradeOffer.ItemsToReceiveReadOnly.Count > 0) && !bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.AcceptDonations)) {
+ return false;
}
+ byte? holdDuration = await bot.GetTradeHoldDuration(tradeOffer.OtherSteamID64, tradeOffer.TradeOfferID).ConfigureAwait(false);
+
+ return holdDuration != null && (!(holdDuration > 0) || holdDuration.Value <= (ASF.GlobalConfig?.MaxTradeHoldDuration ?? 0)) && !tradeOffer.ItemsToGiveReadOnly.Any(static item => item.Type is Asset.EType.FoilTradingCard or Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID))
+&& tradeOffer.ItemsToGiveReadOnly.All(item => itemsToDispense.TryGetValue((item.AppID, item.ContextID), out FrozenSet? dispense) && ((dispense.Count == 0) || dispense.Contains(item.Type)));
+ }
+
+ public Task OnLoaded() {
+ ASF.ArchiLogger.LogGenericInfo("Item Dispenser Plugin by Rudokhvist, powered by ginger cats");
+
+ return Task.CompletedTask;
}
}