diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..b8143ab4a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,766 @@
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = false
+max_line_length = 120
+tab_width = 4
+ij_continuation_indent_size = 8
+ij_formatter_off_tag = @formatter:off
+ij_formatter_on_tag = @formatter:on
+ij_formatter_tags_enabled = false
+ij_smart_tabs = false
+ij_visual_guides = none
+ij_wrap_on_typing = false
+
+[*.css]
+ij_css_align_closing_brace_with_properties = false
+ij_css_blank_lines_around_nested_selector = 1
+ij_css_blank_lines_between_blocks = 1
+ij_css_block_comment_add_space = false
+ij_css_brace_placement = end_of_line
+ij_css_enforce_quotes_on_format = false
+ij_css_hex_color_long_format = false
+ij_css_hex_color_lower_case = false
+ij_css_hex_color_short_format = false
+ij_css_hex_color_upper_case = false
+ij_css_keep_blank_lines_in_code = 2
+ij_css_keep_indents_on_empty_lines = false
+ij_css_keep_single_line_blocks = false
+ij_css_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
+ij_css_space_after_colon = true
+ij_css_space_before_opening_brace = true
+ij_css_use_double_quotes = true
+ij_css_value_alignment = do_not_align
+
+[*.feature]
+indent_size = 2
+ij_gherkin_keep_indents_on_empty_lines = false
+
+[*.haml]
+indent_size = 2
+ij_haml_keep_indents_on_empty_lines = false
+
+[*.less]
+indent_size = 2
+ij_less_align_closing_brace_with_properties = false
+ij_less_blank_lines_around_nested_selector = 1
+ij_less_blank_lines_between_blocks = 1
+ij_less_block_comment_add_space = false
+ij_less_brace_placement = 0
+ij_less_enforce_quotes_on_format = false
+ij_less_hex_color_long_format = false
+ij_less_hex_color_lower_case = false
+ij_less_hex_color_short_format = false
+ij_less_hex_color_upper_case = false
+ij_less_keep_blank_lines_in_code = 2
+ij_less_keep_indents_on_empty_lines = false
+ij_less_keep_single_line_blocks = false
+ij_less_line_comment_add_space = false
+ij_less_line_comment_at_first_column = false
+ij_less_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
+ij_less_space_after_colon = true
+ij_less_space_before_opening_brace = true
+ij_less_use_double_quotes = true
+ij_less_value_alignment = 0
+
+[*.pp]
+indent_size = 2
+tab_width = 2
+ij_continuation_indent_size = 2
+ij_puppet_keep_indents_on_empty_lines = false
+
+[*.properties]
+ij_properties_align_group_field_declarations = false
+ij_properties_keep_blank_lines = false
+ij_properties_key_value_delimiter = equals
+ij_properties_spaces_around_key_value_delimiter = false
+
+[*.sass]
+indent_size = 2
+ij_sass_align_closing_brace_with_properties = false
+ij_sass_blank_lines_around_nested_selector = 1
+ij_sass_blank_lines_between_blocks = 1
+ij_sass_brace_placement = 0
+ij_sass_enforce_quotes_on_format = false
+ij_sass_hex_color_long_format = false
+ij_sass_hex_color_lower_case = false
+ij_sass_hex_color_short_format = false
+ij_sass_hex_color_upper_case = false
+ij_sass_keep_blank_lines_in_code = 2
+ij_sass_keep_indents_on_empty_lines = false
+ij_sass_keep_single_line_blocks = false
+ij_sass_line_comment_add_space = false
+ij_sass_line_comment_at_first_column = false
+ij_sass_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
+ij_sass_space_after_colon = true
+ij_sass_space_before_opening_brace = true
+ij_sass_use_double_quotes = true
+ij_sass_value_alignment = 0
+
+[*.scss]
+indent_size = 2
+ij_scss_align_closing_brace_with_properties = false
+ij_scss_blank_lines_around_nested_selector = 1
+ij_scss_blank_lines_between_blocks = 1
+ij_scss_block_comment_add_space = false
+ij_scss_brace_placement = 0
+ij_scss_enforce_quotes_on_format = false
+ij_scss_hex_color_long_format = false
+ij_scss_hex_color_lower_case = false
+ij_scss_hex_color_short_format = false
+ij_scss_hex_color_upper_case = false
+ij_scss_keep_blank_lines_in_code = 2
+ij_scss_keep_indents_on_empty_lines = false
+ij_scss_keep_single_line_blocks = false
+ij_scss_line_comment_add_space = false
+ij_scss_line_comment_at_first_column = false
+ij_scss_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
+ij_scss_space_after_colon = true
+ij_scss_space_before_opening_brace = true
+ij_scss_use_double_quotes = true
+ij_scss_value_alignment = 0
+
+[*.styl]
+indent_size = 2
+ij_stylus_align_closing_brace_with_properties = false
+ij_stylus_blank_lines_around_nested_selector = 1
+ij_stylus_blank_lines_between_blocks = 1
+ij_stylus_brace_placement = 0
+ij_stylus_enforce_quotes_on_format = false
+ij_stylus_hex_color_long_format = false
+ij_stylus_hex_color_lower_case = false
+ij_stylus_hex_color_short_format = false
+ij_stylus_hex_color_upper_case = false
+ij_stylus_keep_blank_lines_in_code = 2
+ij_stylus_keep_indents_on_empty_lines = false
+ij_stylus_keep_single_line_blocks = false
+ij_stylus_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
+ij_stylus_space_after_colon = true
+ij_stylus_space_before_opening_brace = true
+ij_stylus_use_double_quotes = true
+ij_stylus_value_alignment = 0
+
+[.editorconfig]
+ij_editorconfig_align_group_field_declarations = false
+ij_editorconfig_space_after_colon = false
+ij_editorconfig_space_after_comma = true
+ij_editorconfig_space_before_colon = false
+ij_editorconfig_space_before_comma = false
+ij_editorconfig_spaces_around_assignment_operators = true
+
+[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.qrc,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
+ij_xml_align_attributes = true
+ij_xml_align_text = false
+ij_xml_attribute_wrap = normal
+ij_xml_block_comment_add_space = false
+ij_xml_block_comment_at_first_column = true
+ij_xml_keep_blank_lines = 2
+ij_xml_keep_indents_on_empty_lines = false
+ij_xml_keep_line_breaks = true
+ij_xml_keep_line_breaks_in_text = true
+ij_xml_keep_whitespaces = false
+ij_xml_keep_whitespaces_around_cdata = preserve
+ij_xml_keep_whitespaces_inside_cdata = false
+ij_xml_line_comment_at_first_column = true
+ij_xml_space_after_tag_name = false
+ij_xml_space_around_equals_in_attribute = false
+ij_xml_space_inside_empty_tag = false
+ij_xml_text_wrap = normal
+
+[{*.ats,*.cts,*.mts,*.ts}]
+ij_continuation_indent_size = 4
+ij_typescript_align_imports = false
+ij_typescript_align_multiline_array_initializer_expression = false
+ij_typescript_align_multiline_binary_operation = false
+ij_typescript_align_multiline_chained_methods = false
+ij_typescript_align_multiline_extends_list = false
+ij_typescript_align_multiline_for = true
+ij_typescript_align_multiline_parameters = true
+ij_typescript_align_multiline_parameters_in_calls = false
+ij_typescript_align_multiline_ternary_operation = false
+ij_typescript_align_object_properties = 0
+ij_typescript_align_union_types = false
+ij_typescript_align_var_statements = 0
+ij_typescript_array_initializer_new_line_after_left_brace = false
+ij_typescript_array_initializer_right_brace_on_new_line = false
+ij_typescript_array_initializer_wrap = off
+ij_typescript_assignment_wrap = off
+ij_typescript_binary_operation_sign_on_next_line = false
+ij_typescript_binary_operation_wrap = off
+ij_typescript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/**
+ij_typescript_blank_lines_after_imports = 1
+ij_typescript_blank_lines_around_class = 1
+ij_typescript_blank_lines_around_field = 0
+ij_typescript_blank_lines_around_field_in_interface = 0
+ij_typescript_blank_lines_around_function = 1
+ij_typescript_blank_lines_around_method = 1
+ij_typescript_blank_lines_around_method_in_interface = 1
+ij_typescript_block_brace_style = end_of_line
+ij_typescript_block_comment_add_space = false
+ij_typescript_block_comment_at_first_column = true
+ij_typescript_call_parameters_new_line_after_left_paren = false
+ij_typescript_call_parameters_right_paren_on_new_line = false
+ij_typescript_call_parameters_wrap = off
+ij_typescript_catch_on_new_line = false
+ij_typescript_chained_call_dot_on_new_line = true
+ij_typescript_class_brace_style = end_of_line
+ij_typescript_comma_on_new_line = false
+ij_typescript_do_while_brace_force = never
+ij_typescript_else_on_new_line = false
+ij_typescript_enforce_trailing_comma = keep
+ij_typescript_enum_constants_wrap = on_every_item
+ij_typescript_extends_keyword_wrap = off
+ij_typescript_extends_list_wrap = off
+ij_typescript_field_prefix = _
+ij_typescript_file_name_style = relaxed
+ij_typescript_finally_on_new_line = false
+ij_typescript_for_brace_force = never
+ij_typescript_for_statement_new_line_after_left_paren = false
+ij_typescript_for_statement_right_paren_on_new_line = false
+ij_typescript_for_statement_wrap = off
+ij_typescript_force_quote_style = false
+ij_typescript_force_semicolon_style = false
+ij_typescript_function_expression_brace_style = end_of_line
+ij_typescript_if_brace_force = never
+ij_typescript_import_merge_members = global
+ij_typescript_import_prefer_absolute_path = global
+ij_typescript_import_sort_members = true
+ij_typescript_import_sort_module_name = false
+ij_typescript_import_use_node_resolution = true
+ij_typescript_imports_wrap = on_every_item
+ij_typescript_indent_case_from_switch = true
+ij_typescript_indent_chained_calls = true
+ij_typescript_indent_package_children = 0
+ij_typescript_jsdoc_include_types = false
+ij_typescript_jsx_attribute_value = braces
+ij_typescript_keep_blank_lines_in_code = 2
+ij_typescript_keep_first_column_comment = true
+ij_typescript_keep_indents_on_empty_lines = false
+ij_typescript_keep_line_breaks = true
+ij_typescript_keep_simple_blocks_in_one_line = false
+ij_typescript_keep_simple_methods_in_one_line = false
+ij_typescript_line_comment_add_space = true
+ij_typescript_line_comment_at_first_column = false
+ij_typescript_method_brace_style = end_of_line
+ij_typescript_method_call_chain_wrap = off
+ij_typescript_method_parameters_new_line_after_left_paren = false
+ij_typescript_method_parameters_right_paren_on_new_line = false
+ij_typescript_method_parameters_wrap = off
+ij_typescript_object_literal_wrap = on_every_item
+ij_typescript_parentheses_expression_new_line_after_left_paren = false
+ij_typescript_parentheses_expression_right_paren_on_new_line = false
+ij_typescript_place_assignment_sign_on_next_line = false
+ij_typescript_prefer_as_type_cast = false
+ij_typescript_prefer_explicit_types_function_expression_returns = false
+ij_typescript_prefer_explicit_types_function_returns = false
+ij_typescript_prefer_explicit_types_vars_fields = false
+ij_typescript_prefer_parameters_wrap = false
+ij_typescript_reformat_c_style_comments = false
+ij_typescript_space_after_colon = true
+ij_typescript_space_after_comma = true
+ij_typescript_space_after_dots_in_rest_parameter = false
+ij_typescript_space_after_generator_mult = true
+ij_typescript_space_after_property_colon = true
+ij_typescript_space_after_quest = true
+ij_typescript_space_after_type_colon = true
+ij_typescript_space_after_unary_not = false
+ij_typescript_space_before_async_arrow_lparen = true
+ij_typescript_space_before_catch_keyword = true
+ij_typescript_space_before_catch_left_brace = true
+ij_typescript_space_before_catch_parentheses = true
+ij_typescript_space_before_class_lbrace = true
+ij_typescript_space_before_class_left_brace = true
+ij_typescript_space_before_colon = true
+ij_typescript_space_before_comma = false
+ij_typescript_space_before_do_left_brace = true
+ij_typescript_space_before_else_keyword = true
+ij_typescript_space_before_else_left_brace = true
+ij_typescript_space_before_finally_keyword = true
+ij_typescript_space_before_finally_left_brace = true
+ij_typescript_space_before_for_left_brace = true
+ij_typescript_space_before_for_parentheses = true
+ij_typescript_space_before_for_semicolon = false
+ij_typescript_space_before_function_left_parenth = true
+ij_typescript_space_before_generator_mult = false
+ij_typescript_space_before_if_left_brace = true
+ij_typescript_space_before_if_parentheses = true
+ij_typescript_space_before_method_call_parentheses = false
+ij_typescript_space_before_method_left_brace = true
+ij_typescript_space_before_method_parentheses = false
+ij_typescript_space_before_property_colon = false
+ij_typescript_space_before_quest = true
+ij_typescript_space_before_switch_left_brace = true
+ij_typescript_space_before_switch_parentheses = true
+ij_typescript_space_before_try_left_brace = true
+ij_typescript_space_before_type_colon = false
+ij_typescript_space_before_unary_not = false
+ij_typescript_space_before_while_keyword = true
+ij_typescript_space_before_while_left_brace = true
+ij_typescript_space_before_while_parentheses = true
+ij_typescript_spaces_around_additive_operators = true
+ij_typescript_spaces_around_arrow_function_operator = true
+ij_typescript_spaces_around_assignment_operators = true
+ij_typescript_spaces_around_bitwise_operators = true
+ij_typescript_spaces_around_equality_operators = true
+ij_typescript_spaces_around_logical_operators = true
+ij_typescript_spaces_around_multiplicative_operators = true
+ij_typescript_spaces_around_relational_operators = true
+ij_typescript_spaces_around_shift_operators = true
+ij_typescript_spaces_around_unary_operator = false
+ij_typescript_spaces_within_array_initializer_brackets = false
+ij_typescript_spaces_within_brackets = false
+ij_typescript_spaces_within_catch_parentheses = false
+ij_typescript_spaces_within_for_parentheses = false
+ij_typescript_spaces_within_if_parentheses = false
+ij_typescript_spaces_within_imports = false
+ij_typescript_spaces_within_interpolation_expressions = false
+ij_typescript_spaces_within_method_call_parentheses = false
+ij_typescript_spaces_within_method_parentheses = false
+ij_typescript_spaces_within_object_literal_braces = false
+ij_typescript_spaces_within_object_type_braces = true
+ij_typescript_spaces_within_parentheses = false
+ij_typescript_spaces_within_switch_parentheses = false
+ij_typescript_spaces_within_type_assertion = false
+ij_typescript_spaces_within_union_types = true
+ij_typescript_spaces_within_while_parentheses = false
+ij_typescript_special_else_if_treatment = true
+ij_typescript_ternary_operation_signs_on_next_line = false
+ij_typescript_ternary_operation_wrap = off
+ij_typescript_union_types_wrap = on_every_item
+ij_typescript_use_chained_calls_group_indents = false
+ij_typescript_use_double_quotes = true
+ij_typescript_use_explicit_js_extension = auto
+ij_typescript_use_path_mapping = always
+ij_typescript_use_public_modifier = false
+ij_typescript_use_semicolon_after_statement = true
+ij_typescript_var_declaration_wrap = normal
+ij_typescript_while_brace_force = never
+ij_typescript_while_on_new_line = false
+ij_typescript_wrap_comments = false
+
+[{*.bash,*.sh,*.zsh}]
+indent_size = 2
+tab_width = 2
+ij_shell_binary_ops_start_line = false
+ij_shell_keep_column_alignment_padding = false
+ij_shell_minify_program = false
+ij_shell_redirect_followed_by_space = false
+ij_shell_switch_cases_indented = false
+ij_shell_use_unix_line_separator = true
+
+[{*.cjs,*.js}]
+ij_continuation_indent_size = 4
+ij_javascript_align_imports = false
+ij_javascript_align_multiline_array_initializer_expression = false
+ij_javascript_align_multiline_binary_operation = false
+ij_javascript_align_multiline_chained_methods = false
+ij_javascript_align_multiline_extends_list = false
+ij_javascript_align_multiline_for = true
+ij_javascript_align_multiline_parameters = true
+ij_javascript_align_multiline_parameters_in_calls = false
+ij_javascript_align_multiline_ternary_operation = false
+ij_javascript_align_object_properties = 0
+ij_javascript_align_union_types = false
+ij_javascript_align_var_statements = 0
+ij_javascript_array_initializer_new_line_after_left_brace = false
+ij_javascript_array_initializer_right_brace_on_new_line = false
+ij_javascript_array_initializer_wrap = off
+ij_javascript_assignment_wrap = off
+ij_javascript_binary_operation_sign_on_next_line = false
+ij_javascript_binary_operation_wrap = off
+ij_javascript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/**
+ij_javascript_blank_lines_after_imports = 1
+ij_javascript_blank_lines_around_class = 1
+ij_javascript_blank_lines_around_field = 0
+ij_javascript_blank_lines_around_function = 1
+ij_javascript_blank_lines_around_method = 1
+ij_javascript_block_brace_style = end_of_line
+ij_javascript_block_comment_add_space = false
+ij_javascript_block_comment_at_first_column = true
+ij_javascript_call_parameters_new_line_after_left_paren = false
+ij_javascript_call_parameters_right_paren_on_new_line = false
+ij_javascript_call_parameters_wrap = off
+ij_javascript_catch_on_new_line = false
+ij_javascript_chained_call_dot_on_new_line = true
+ij_javascript_class_brace_style = end_of_line
+ij_javascript_comma_on_new_line = false
+ij_javascript_do_while_brace_force = never
+ij_javascript_else_on_new_line = false
+ij_javascript_enforce_trailing_comma = keep
+ij_javascript_extends_keyword_wrap = off
+ij_javascript_extends_list_wrap = off
+ij_javascript_field_prefix = _
+ij_javascript_file_name_style = relaxed
+ij_javascript_finally_on_new_line = false
+ij_javascript_for_brace_force = never
+ij_javascript_for_statement_new_line_after_left_paren = false
+ij_javascript_for_statement_right_paren_on_new_line = false
+ij_javascript_for_statement_wrap = off
+ij_javascript_force_quote_style = false
+ij_javascript_force_semicolon_style = false
+ij_javascript_function_expression_brace_style = end_of_line
+ij_javascript_if_brace_force = never
+ij_javascript_import_merge_members = global
+ij_javascript_import_prefer_absolute_path = global
+ij_javascript_import_sort_members = true
+ij_javascript_import_sort_module_name = false
+ij_javascript_import_use_node_resolution = true
+ij_javascript_imports_wrap = on_every_item
+ij_javascript_indent_case_from_switch = true
+ij_javascript_indent_chained_calls = true
+ij_javascript_indent_package_children = 0
+ij_javascript_jsx_attribute_value = braces
+ij_javascript_keep_blank_lines_in_code = 2
+ij_javascript_keep_first_column_comment = true
+ij_javascript_keep_indents_on_empty_lines = false
+ij_javascript_keep_line_breaks = true
+ij_javascript_keep_simple_blocks_in_one_line = false
+ij_javascript_keep_simple_methods_in_one_line = false
+ij_javascript_line_comment_add_space = true
+ij_javascript_line_comment_at_first_column = false
+ij_javascript_method_brace_style = end_of_line
+ij_javascript_method_call_chain_wrap = off
+ij_javascript_method_parameters_new_line_after_left_paren = false
+ij_javascript_method_parameters_right_paren_on_new_line = false
+ij_javascript_method_parameters_wrap = off
+ij_javascript_object_literal_wrap = on_every_item
+ij_javascript_parentheses_expression_new_line_after_left_paren = false
+ij_javascript_parentheses_expression_right_paren_on_new_line = false
+ij_javascript_place_assignment_sign_on_next_line = false
+ij_javascript_prefer_as_type_cast = false
+ij_javascript_prefer_explicit_types_function_expression_returns = false
+ij_javascript_prefer_explicit_types_function_returns = false
+ij_javascript_prefer_explicit_types_vars_fields = false
+ij_javascript_prefer_parameters_wrap = false
+ij_javascript_reformat_c_style_comments = false
+ij_javascript_space_after_colon = true
+ij_javascript_space_after_comma = true
+ij_javascript_space_after_dots_in_rest_parameter = false
+ij_javascript_space_after_generator_mult = true
+ij_javascript_space_after_property_colon = true
+ij_javascript_space_after_quest = true
+ij_javascript_space_after_type_colon = true
+ij_javascript_space_after_unary_not = false
+ij_javascript_space_before_async_arrow_lparen = true
+ij_javascript_space_before_catch_keyword = true
+ij_javascript_space_before_catch_left_brace = true
+ij_javascript_space_before_catch_parentheses = true
+ij_javascript_space_before_class_lbrace = true
+ij_javascript_space_before_class_left_brace = true
+ij_javascript_space_before_colon = true
+ij_javascript_space_before_comma = false
+ij_javascript_space_before_do_left_brace = true
+ij_javascript_space_before_else_keyword = true
+ij_javascript_space_before_else_left_brace = true
+ij_javascript_space_before_finally_keyword = true
+ij_javascript_space_before_finally_left_brace = true
+ij_javascript_space_before_for_left_brace = true
+ij_javascript_space_before_for_parentheses = true
+ij_javascript_space_before_for_semicolon = false
+ij_javascript_space_before_function_left_parenth = true
+ij_javascript_space_before_generator_mult = false
+ij_javascript_space_before_if_left_brace = true
+ij_javascript_space_before_if_parentheses = true
+ij_javascript_space_before_method_call_parentheses = false
+ij_javascript_space_before_method_left_brace = true
+ij_javascript_space_before_method_parentheses = false
+ij_javascript_space_before_property_colon = false
+ij_javascript_space_before_quest = true
+ij_javascript_space_before_switch_left_brace = true
+ij_javascript_space_before_switch_parentheses = true
+ij_javascript_space_before_try_left_brace = true
+ij_javascript_space_before_type_colon = false
+ij_javascript_space_before_unary_not = false
+ij_javascript_space_before_while_keyword = true
+ij_javascript_space_before_while_left_brace = true
+ij_javascript_space_before_while_parentheses = true
+ij_javascript_spaces_around_additive_operators = true
+ij_javascript_spaces_around_arrow_function_operator = true
+ij_javascript_spaces_around_assignment_operators = true
+ij_javascript_spaces_around_bitwise_operators = true
+ij_javascript_spaces_around_equality_operators = true
+ij_javascript_spaces_around_logical_operators = true
+ij_javascript_spaces_around_multiplicative_operators = true
+ij_javascript_spaces_around_relational_operators = true
+ij_javascript_spaces_around_shift_operators = true
+ij_javascript_spaces_around_unary_operator = false
+ij_javascript_spaces_within_array_initializer_brackets = false
+ij_javascript_spaces_within_brackets = false
+ij_javascript_spaces_within_catch_parentheses = false
+ij_javascript_spaces_within_for_parentheses = false
+ij_javascript_spaces_within_if_parentheses = false
+ij_javascript_spaces_within_imports = false
+ij_javascript_spaces_within_interpolation_expressions = false
+ij_javascript_spaces_within_method_call_parentheses = false
+ij_javascript_spaces_within_method_parentheses = false
+ij_javascript_spaces_within_object_literal_braces = false
+ij_javascript_spaces_within_object_type_braces = true
+ij_javascript_spaces_within_parentheses = false
+ij_javascript_spaces_within_switch_parentheses = false
+ij_javascript_spaces_within_type_assertion = false
+ij_javascript_spaces_within_union_types = true
+ij_javascript_spaces_within_while_parentheses = false
+ij_javascript_special_else_if_treatment = true
+ij_javascript_ternary_operation_signs_on_next_line = false
+ij_javascript_ternary_operation_wrap = off
+ij_javascript_union_types_wrap = on_every_item
+ij_javascript_use_chained_calls_group_indents = false
+ij_javascript_use_double_quotes = true
+ij_javascript_use_explicit_js_extension = auto
+ij_javascript_use_path_mapping = always
+ij_javascript_use_public_modifier = false
+ij_javascript_use_semicolon_after_statement = true
+ij_javascript_var_declaration_wrap = normal
+ij_javascript_while_brace_force = never
+ij_javascript_while_on_new_line = false
+ij_javascript_wrap_comments = false
+
+[{*.cjsx,*.coffee}]
+indent_size = 2
+tab_width = 2
+ij_continuation_indent_size = 2
+ij_coffeescript_align_function_body = false
+ij_coffeescript_align_imports = false
+ij_coffeescript_align_multiline_array_initializer_expression = true
+ij_coffeescript_align_multiline_parameters = true
+ij_coffeescript_align_multiline_parameters_in_calls = false
+ij_coffeescript_align_object_properties = 0
+ij_coffeescript_align_union_types = false
+ij_coffeescript_align_var_statements = 0
+ij_coffeescript_array_initializer_new_line_after_left_brace = false
+ij_coffeescript_array_initializer_right_brace_on_new_line = false
+ij_coffeescript_array_initializer_wrap = normal
+ij_coffeescript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/**
+ij_coffeescript_blank_lines_around_function = 1
+ij_coffeescript_call_parameters_new_line_after_left_paren = false
+ij_coffeescript_call_parameters_right_paren_on_new_line = false
+ij_coffeescript_call_parameters_wrap = normal
+ij_coffeescript_chained_call_dot_on_new_line = true
+ij_coffeescript_comma_on_new_line = false
+ij_coffeescript_enforce_trailing_comma = keep
+ij_coffeescript_field_prefix = _
+ij_coffeescript_file_name_style = relaxed
+ij_coffeescript_force_quote_style = false
+ij_coffeescript_force_semicolon_style = false
+ij_coffeescript_function_expression_brace_style = end_of_line
+ij_coffeescript_import_merge_members = global
+ij_coffeescript_import_prefer_absolute_path = global
+ij_coffeescript_import_sort_members = true
+ij_coffeescript_import_sort_module_name = false
+ij_coffeescript_import_use_node_resolution = true
+ij_coffeescript_imports_wrap = on_every_item
+ij_coffeescript_indent_chained_calls = true
+ij_coffeescript_indent_package_children = 0
+ij_coffeescript_jsx_attribute_value = braces
+ij_coffeescript_keep_blank_lines_in_code = 2
+ij_coffeescript_keep_first_column_comment = true
+ij_coffeescript_keep_indents_on_empty_lines = false
+ij_coffeescript_keep_line_breaks = true
+ij_coffeescript_keep_simple_methods_in_one_line = false
+ij_coffeescript_method_parameters_new_line_after_left_paren = false
+ij_coffeescript_method_parameters_right_paren_on_new_line = false
+ij_coffeescript_method_parameters_wrap = off
+ij_coffeescript_object_literal_wrap = on_every_item
+ij_coffeescript_prefer_as_type_cast = false
+ij_coffeescript_prefer_explicit_types_function_expression_returns = false
+ij_coffeescript_prefer_explicit_types_function_returns = false
+ij_coffeescript_prefer_explicit_types_vars_fields = false
+ij_coffeescript_reformat_c_style_comments = false
+ij_coffeescript_space_after_comma = true
+ij_coffeescript_space_after_dots_in_rest_parameter = false
+ij_coffeescript_space_after_generator_mult = true
+ij_coffeescript_space_after_property_colon = true
+ij_coffeescript_space_after_type_colon = true
+ij_coffeescript_space_after_unary_not = false
+ij_coffeescript_space_before_async_arrow_lparen = true
+ij_coffeescript_space_before_class_lbrace = true
+ij_coffeescript_space_before_comma = false
+ij_coffeescript_space_before_function_left_parenth = true
+ij_coffeescript_space_before_generator_mult = false
+ij_coffeescript_space_before_property_colon = false
+ij_coffeescript_space_before_type_colon = false
+ij_coffeescript_space_before_unary_not = false
+ij_coffeescript_spaces_around_additive_operators = true
+ij_coffeescript_spaces_around_arrow_function_operator = true
+ij_coffeescript_spaces_around_assignment_operators = true
+ij_coffeescript_spaces_around_bitwise_operators = true
+ij_coffeescript_spaces_around_equality_operators = true
+ij_coffeescript_spaces_around_logical_operators = true
+ij_coffeescript_spaces_around_multiplicative_operators = true
+ij_coffeescript_spaces_around_relational_operators = true
+ij_coffeescript_spaces_around_shift_operators = true
+ij_coffeescript_spaces_around_unary_operator = false
+ij_coffeescript_spaces_within_array_initializer_braces = false
+ij_coffeescript_spaces_within_array_initializer_brackets = false
+ij_coffeescript_spaces_within_imports = false
+ij_coffeescript_spaces_within_index_brackets = false
+ij_coffeescript_spaces_within_interpolation_expressions = false
+ij_coffeescript_spaces_within_method_call_parentheses = false
+ij_coffeescript_spaces_within_method_parentheses = false
+ij_coffeescript_spaces_within_object_braces = false
+ij_coffeescript_spaces_within_object_literal_braces = false
+ij_coffeescript_spaces_within_object_type_braces = true
+ij_coffeescript_spaces_within_range_brackets = false
+ij_coffeescript_spaces_within_type_assertion = false
+ij_coffeescript_spaces_within_union_types = true
+ij_coffeescript_union_types_wrap = on_every_item
+ij_coffeescript_use_chained_calls_group_indents = false
+ij_coffeescript_use_double_quotes = true
+ij_coffeescript_use_explicit_js_extension = auto
+ij_coffeescript_use_path_mapping = always
+ij_coffeescript_use_public_modifier = false
+ij_coffeescript_use_semicolon_after_statement = false
+ij_coffeescript_var_declaration_wrap = normal
+
+[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}]
+indent_size = 2
+ij_json_array_wrapping = split_into_lines
+ij_json_keep_blank_lines_in_code = 0
+ij_json_keep_indents_on_empty_lines = false
+ij_json_keep_line_breaks = true
+ij_json_keep_trailing_comma = false
+ij_json_object_wrapping = split_into_lines
+ij_json_property_alignment = do_not_align
+ij_json_space_after_colon = true
+ij_json_space_after_comma = true
+ij_json_space_before_colon = false
+ij_json_space_before_comma = false
+ij_json_spaces_within_braces = false
+ij_json_spaces_within_brackets = false
+ij_json_wrap_long_lines = false
+
+[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}]
+ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3
+ij_html_align_attributes = true
+ij_html_align_text = false
+ij_html_attribute_wrap = normal
+ij_html_block_comment_add_space = false
+ij_html_block_comment_at_first_column = true
+ij_html_do_not_align_children_of_min_lines = 0
+ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p
+ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot
+ij_html_enforce_quotes = false
+ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var
+ij_html_keep_blank_lines = 2
+ij_html_keep_indents_on_empty_lines = false
+ij_html_keep_line_breaks = true
+ij_html_keep_line_breaks_in_text = true
+ij_html_keep_whitespaces = false
+ij_html_keep_whitespaces_inside = span, pre, textarea
+ij_html_line_comment_at_first_column = true
+ij_html_new_line_after_last_attribute = never
+ij_html_new_line_before_first_attribute = never
+ij_html_quote_style = double
+ij_html_remove_new_line_before_tags = br
+ij_html_space_after_tag_name = false
+ij_html_space_around_equality_in_attribute = false
+ij_html_space_inside_empty_tag = false
+ij_html_text_wrap = normal
+
+[{*.markdown,*.md}]
+ij_markdown_force_one_space_after_blockquote_symbol = true
+ij_markdown_force_one_space_after_header_symbol = true
+ij_markdown_force_one_space_after_list_bullet = true
+ij_markdown_force_one_space_between_words = true
+ij_markdown_insert_quote_arrows_on_wrap = true
+ij_markdown_keep_indents_on_empty_lines = false
+ij_markdown_keep_line_breaks_inside_text_blocks = true
+ij_markdown_max_lines_around_block_elements = 1
+ij_markdown_max_lines_around_header = 1
+ij_markdown_max_lines_between_paragraphs = 1
+ij_markdown_min_lines_around_block_elements = 1
+ij_markdown_min_lines_around_header = 1
+ij_markdown_min_lines_between_paragraphs = 1
+ij_markdown_wrap_text_if_long = true
+ij_markdown_wrap_text_inside_blockquotes = true
+
+[{*.py,*.pyw}]
+ij_continuation_indent_size = 4
+ij_python_align_collections_and_comprehensions = true
+ij_python_align_multiline_imports = true
+ij_python_align_multiline_parameters = true
+ij_python_align_multiline_parameters_in_calls = true
+ij_python_blank_line_at_file_end = true
+ij_python_blank_lines_after_imports = 1
+ij_python_blank_lines_after_local_imports = 0
+ij_python_blank_lines_around_class = 1
+ij_python_blank_lines_around_method = 1
+ij_python_blank_lines_around_top_level_classes_functions = 2
+ij_python_blank_lines_before_first_method = 0
+ij_python_call_parameters_new_line_after_left_paren = false
+ij_python_call_parameters_right_paren_on_new_line = false
+ij_python_call_parameters_wrap = normal
+ij_python_dict_alignment = 0
+ij_python_dict_new_line_after_left_brace = false
+ij_python_dict_new_line_before_right_brace = false
+ij_python_dict_wrapping = 1
+ij_python_from_import_new_line_after_left_parenthesis = false
+ij_python_from_import_new_line_before_right_parenthesis = false
+ij_python_from_import_parentheses_force_if_multiline = false
+ij_python_from_import_trailing_comma_if_multiline = false
+ij_python_from_import_wrapping = 1
+ij_python_hang_closing_brackets = false
+ij_python_keep_blank_lines_in_code = 1
+ij_python_keep_blank_lines_in_declarations = 1
+ij_python_keep_indents_on_empty_lines = false
+ij_python_keep_line_breaks = true
+ij_python_method_parameters_new_line_after_left_paren = false
+ij_python_method_parameters_right_paren_on_new_line = false
+ij_python_method_parameters_wrap = normal
+ij_python_new_line_after_colon = false
+ij_python_new_line_after_colon_multi_clause = true
+ij_python_optimize_imports_always_split_from_imports = false
+ij_python_optimize_imports_case_insensitive_order = false
+ij_python_optimize_imports_join_from_imports_with_same_source = false
+ij_python_optimize_imports_sort_by_type_first = true
+ij_python_optimize_imports_sort_imports = true
+ij_python_optimize_imports_sort_names_in_from_imports = false
+ij_python_space_after_comma = true
+ij_python_space_after_number_sign = true
+ij_python_space_after_py_colon = true
+ij_python_space_before_backslash = true
+ij_python_space_before_comma = false
+ij_python_space_before_for_semicolon = false
+ij_python_space_before_lbracket = false
+ij_python_space_before_method_call_parentheses = false
+ij_python_space_before_method_parentheses = false
+ij_python_space_before_number_sign = true
+ij_python_space_before_py_colon = false
+ij_python_space_within_empty_method_call_parentheses = false
+ij_python_space_within_empty_method_parentheses = false
+ij_python_spaces_around_additive_operators = true
+ij_python_spaces_around_assignment_operators = true
+ij_python_spaces_around_bitwise_operators = true
+ij_python_spaces_around_eq_in_keyword_argument = false
+ij_python_spaces_around_eq_in_named_parameter = false
+ij_python_spaces_around_equality_operators = true
+ij_python_spaces_around_multiplicative_operators = true
+ij_python_spaces_around_power_operator = true
+ij_python_spaces_around_relational_operators = true
+ij_python_spaces_around_shift_operators = true
+ij_python_spaces_within_braces = false
+ij_python_spaces_within_brackets = false
+ij_python_spaces_within_method_call_parentheses = false
+ij_python_spaces_within_method_parentheses = false
+ij_python_use_continuation_indent_for_arguments = false
+ij_python_use_continuation_indent_for_collection_and_comprehensions = false
+ij_python_use_continuation_indent_for_parameters = true
+ij_python_wrap_long_lines = false
+
+[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}]
+ij_toml_keep_indents_on_empty_lines = false
+
+[{*.yaml,*.yml}]
+indent_size = 2
+ij_yaml_align_values_properties = do_not_align
+ij_yaml_autoinsert_sequence_marker = true
+ij_yaml_block_mapping_on_new_line = false
+ij_yaml_indent_sequence_value = true
+ij_yaml_keep_indents_on_empty_lines = false
+ij_yaml_keep_line_breaks = true
+ij_yaml_sequence_on_new_line = false
+ij_yaml_space_before_colon = false
+ij_yaml_spaces_within_braces = true
+ij_yaml_spaces_within_brackets = true
diff --git a/server/__init__.py b/server/__init__.py
index 4d8af204a..ccd566db1 100644
--- a/server/__init__.py
+++ b/server/__init__.py
@@ -117,6 +117,7 @@
 from .rating_service.rating_service import RatingService
 from .servercontext import ServerContext
 from .stats.game_stats_service import GameStatsService
+from .tournament_service import TournamentService
 
 __author__ = "Askaholic, Chris Kitching, Dragonfire, Gael Honorez, Jeroen De Dauw, Crotalus, Michael Søndergaard, Michel Jung"
 __contact__ = "admin@faforever.com"
@@ -134,6 +135,7 @@
     "MessageQueueService",
     "OAuthService",
     "PartyService",
+    "TournamentService",
     "PlayerService",
     "RatingService",
     "RatingService",
@@ -145,6 +147,7 @@
     "run_control_server",
 )
 
+
 logger = logging.getLogger("server")
 
 if config.ENABLE_METRICS:
@@ -197,6 +200,7 @@ def __init__(
             party_service=self.services["party_service"],
             rating_service=self.services["rating_service"],
             oauth_service=self.services["oauth_service"],
+            tournament_service=self.services["tournament_service"],
         )
 
     def write_broadcast(
diff --git a/server/game_service.py b/server/game_service.py
index 6f5c6e741..30fb1ea84 100644
--- a/server/game_service.py
+++ b/server/game_service.py
@@ -1,7 +1,7 @@
 """
 Manages the lifecycle of active games
 """
-
+import asyncio
 from collections import Counter
 from typing import Optional, Union, ValuesView
 
@@ -9,7 +9,6 @@
 from sqlalchemy import select
 
 from server.config import config
-
 from . import metrics
 from .core import Service
 from .db import FAFDatabase
@@ -145,7 +144,7 @@ def create_game(
         visibility=VisibilityState.PUBLIC,
         host: Optional[Player] = None,
         name: Optional[str] = None,
-        mapname: Optional[str] = None,
+        map_name: Optional[str] = None,
         password: Optional[str] = None,
         matchmaker_queue_id: Optional[int] = None,
         **kwargs
@@ -159,7 +158,7 @@ def create_game(
             "id_": game_id,
             "host": host,
             "name": name,
-            "map_": mapname,
+            "map_name": map_name,
             "game_mode": game_mode,
             "game_service": self,
             "game_stats_service": self.game_stats_service,
@@ -262,3 +261,8 @@ async def publish_game_results(self, game_results: EndedGameInfo):
             metrics.rated_games.labels(game_results.rating_type).inc()
             # TODO: Remove when rating service starts listening to message queue
             await self._rating_service.enqueue(result_dict)
+
+
+class NotConnectedError(asyncio.TimeoutError):
+    def __init__(self, players: list[Player]):
+        self.players = players
diff --git a/server/gameconnection.py b/server/gameconnection.py
index 71052f81c..514668e19 100644
--- a/server/gameconnection.py
+++ b/server/gameconnection.py
@@ -236,9 +236,8 @@ async def handle_game_option(self, key: str, value: Any):
             raw = repr(value)
             self.game.map_scenario_path = \
                 raw.replace("\\", "/").replace("//", "/").replace("'", "")
-            self.game.map_file_path = "maps/{}.zip".format(
+            self.game.map_name = \
                 self.game.map_scenario_path.split("/")[2].lower()
-            )
         elif key == "Title":
             with contextlib.suppress(ValueError):
                 self.game.name = value
diff --git a/server/games/__init__.py b/server/games/__init__.py
index 87c7603f5..7d885496f 100644
--- a/server/games/__init__.py
+++ b/server/games/__init__.py
@@ -8,6 +8,7 @@
 from .custom_game import CustomGame
 from .game import Game, GameError
 from .ladder_game import LadderGame
+from .tournament_game import TournamentGame
 from .typedefs import (
     FeaturedModType,
     GameConnectionState,
@@ -41,6 +42,7 @@ class FeaturedMod(NamedTuple):
     "GameType",
     "InitMode",
     "LadderGame",
+    "TournamentGame",
     "ValidityState",
     "Victory",
     "VisibilityState",
diff --git a/server/games/game.py b/server/games/game.py
index 7cfdea5fd..3ab763aae 100644
--- a/server/games/game.py
+++ b/server/games/game.py
@@ -1,6 +1,7 @@
 import asyncio
 import json
 import logging
+import re
 import time
 from collections import defaultdict
 from typing import Any, Iterable, Optional
@@ -47,6 +48,9 @@ class GameError(Exception):
     pass
 
 
+MAP_FILE_PATH_PATTERN = re.compile(r"maps/(.+)\.zip")
+
+
 class Game:
     """
     Object that lasts for the lifetime of a game on FAF.
@@ -62,7 +66,7 @@ def __init__(
         game_stats_service: "GameStatsService",
         host: Optional[Player] = None,
         name: str = "None",
-        map_: str = "SCMP_007",
+        map_name: str = "SCMP_007",
         game_mode: str = FeaturedModType.FAF,
         matchmaker_queue_id: Optional[int] = None,
         rating_type: Optional[str] = None,
@@ -89,7 +93,7 @@ def __init__(
         self.host = host
         self.name = name
         self.map_id = None
-        self.map_file_path = f"maps/{map_}.zip"
+        self.map_name = map_name
         self.map_scenario_path = None
         self.password = None
         self._players_at_launch: list[Player] = []
@@ -153,6 +157,30 @@ def set_name_unchecked(self, value: str):
         max_len = game_stats.c.gameName.type.length
         self._name = value[:max_len]
 
+    @property
+    def map_name(self):
+        return self._map_name
+
+    @map_name.setter
+    def map_name(self, name: str):
+        self._map_name = name
+        self._map_file_path = f"maps/{name}.zip"
+
+    @property
+    def map_file_path(self):
+        return self._map_file_path
+
+    @map_file_path.setter
+    def map_file_path(self, path: str):
+        m = re.match(MAP_FILE_PATH_PATTERN, path)
+        if m is None:
+            raise ValueError(
+                "Map path must start with 'maps/' and end with '.zip'"
+            )
+
+        self._map_name = m.group(1)
+        self._map_file_path = path
+
     @property
     def armies(self) -> frozenset[int]:
         return frozenset(
@@ -253,7 +281,7 @@ def get_team_sets(self) -> list[set[Player]]:
             raise GameError(
                 "Missing team for at least one player. (player, team): {}"
                 .format([(player, self.get_player_option(player.id, "Team"))
-                        for player in self.players])
+                         for player in self.players])
             )
 
         teams = defaultdict(set)
@@ -439,7 +467,7 @@ async def on_game_finish(self):
                 await self.process_game_results()
 
                 self._process_pending_army_stats()
-        except Exception:    # pragma: no cover
+        except Exception:  # pragma: no cover
             self._logger.exception("Error during game end")
         finally:
             self.state = GameState.ENDED
@@ -565,6 +593,7 @@ async def persist_results(self):
     def get_basic_info(self) -> BasicGameInfo:
         return BasicGameInfo(
             self.id,
+            self.game_type,
             self.rating_type,
             self.map_id,
             self.game_mode,
@@ -936,10 +965,7 @@ def map_folder_name(self) -> str:
         try:
             return str(self.map_scenario_path.split("/")[2]).lower()
         except (IndexError, AttributeError):
-            if self.map_file_path:
-                return self.map_file_path[5:-4].lower()
-            else:
-                return "scmp_009"
+            return self.map_name
 
     def __eq__(self, other):
         if not isinstance(other, Game):
@@ -955,3 +981,9 @@ def __str__(self) -> str:
             f"Game({self.id}, {self.host.login if self.host else ''}, "
             f"{self.map_file_path})"
         )
+
+    def wait_launched(self, param):
+        pass
+
+    def wait_hosted(self, param):
+        pass
diff --git a/server/games/tournament_game.py b/server/games/tournament_game.py
new file mode 100644
index 000000000..b8bb345dc
--- /dev/null
+++ b/server/games/tournament_game.py
@@ -0,0 +1,12 @@
+import logging
+
+from . import LadderGame
+from .typedefs import GameType
+
+logger = logging.getLogger(__name__)
+
+
+class TournamentGame(LadderGame):
+    """Class for tournament games"""
+
+    game_type = GameType.TOURNAMENT
diff --git a/server/games/typedefs.py b/server/games/typedefs.py
index 8d8142f7d..cc72b910a 100644
--- a/server/games/typedefs.py
+++ b/server/games/typedefs.py
@@ -33,6 +33,7 @@ class GameType(Enum):
     COOP = "coop"
     CUSTOM = "custom"
     MATCHMAKER = "matchmaker"
+    TOURNAMENT = "tournament"
 
 
 @unique
@@ -90,12 +91,14 @@ class BasicGameInfo(NamedTuple):
     Holds basic information about a game that does not change after launch.
     Fields:
      - game_id: id of the game
+     - game_type: type of the game
      - rating_type: str (e.g. "ladder1v1")
      - map_id: id of the map used
      - game_mode: name of the featured mod
     """
 
     game_id: int
+    game_type: GameType
     rating_type: Optional[str]
     map_id: int
     game_mode: str
diff --git a/server/ladder_service/ladder_service.py b/server/ladder_service/ladder_service.py
index 53dfe2f6b..5f23a0520 100644
--- a/server/ladder_service/ladder_service.py
+++ b/server/ladder_service/ladder_service.py
@@ -34,8 +34,8 @@
     matchmaker_queue_map_pool
 )
 from server.decorators import with_logger
-from server.game_service import GameService
-from server.games import InitMode, LadderGame
+from server.game_service import GameService, NotConnectedError
+from server.games import Game, InitMode, LadderGame
 from server.games.ladder_game import GameClosedError
 from server.ladder_service.game_name import game_name
 from server.ladder_service.violation_service import ViolationService
@@ -563,7 +563,7 @@ def get_player_mean(player: Player) -> float:
             if game_options:
                 game.gameOptions.update(game_options)
 
-            mapname = re.match("maps/(.+).zip", map_path).group(1)
+            map_name = re.match("maps/(.+).zip", map_path).group(1)
             # FIXME: Database filenames contain the maps/ prefix and .zip suffix.
             # Really in the future, just send a better description
 
@@ -571,7 +571,7 @@ def get_player_mean(player: Player) -> float:
 
             def make_game_options(player: Player) -> GameLaunchOptions:
                 return GameLaunchOptions(
-                    mapname=mapname,
+                    mapname=map_name,
                     expected_players=len(all_players),
                     game_options=game_options,
                     team=game.get_player_option(player.id, "Team"),
@@ -579,7 +579,7 @@ def make_game_options(player: Player) -> GameLaunchOptions:
                     map_position=game.get_player_option(player.id, "StartSpot")
                 )
 
-            await self.launch_match(game, host, all_guests, make_game_options)
+            await self.launch_server_made_game(game, host, all_guests, make_game_options)
             self._logger.debug("Ladder game launched successfully %s", game)
             metrics.matches.labels(queue.name, MatchLaunch.SUCCESSFUL).inc()
         except Exception as e:
@@ -623,9 +623,9 @@ def make_game_options(player: Player) -> GameLaunchOptions:
                 )
                 self.violation_service.register_violations(abandoning_players)
 
-    async def launch_match(
+    async def launch_server_made_game(
         self,
-        game: LadderGame,
+        game: Game,
         host: Player,
         guests: list[Player],
         make_game_options: Callable[[Player], GameLaunchOptions]
@@ -725,8 +725,3 @@ def on_connection_lost(self, conn: "LobbyConnection") -> None:
     async def shutdown(self):
         for queue in self.queues.values():
             queue.shutdown()
-
-
-class NotConnectedError(asyncio.TimeoutError):
-    def __init__(self, players: list[Player]):
-        self.players = players
diff --git a/server/lobbyconnection.py b/server/lobbyconnection.py
index 9478876e2..9f8df6a2d 100644
--- a/server/lobbyconnection.py
+++ b/server/lobbyconnection.py
@@ -58,6 +58,7 @@
 from .protocol import DisconnectedError, Protocol
 from .rating import InclusiveRange, RatingType
 from .rating_service import RatingService
+from .tournament_service import TournamentService
 from .types import Address, GameLaunchOptions
 
 
@@ -75,6 +76,7 @@ def __init__(
         party_service: PartyService,
         rating_service: RatingService,
         oauth_service: OAuthService,
+        tournament_service: TournamentService,
     ):
         self._db = database
         self.geoip_service = geoip
@@ -86,6 +88,7 @@ def __init__(
         self.party_service = party_service
         self.rating_service = rating_service
         self.oauth_service = oauth_service
+        self.tournament_service = tournament_service
         self._authenticated = False
         self.player: Optional[Player] = None
         self.game_connection: Optional[GameConnection] = None
@@ -946,7 +949,7 @@ async def command_game_host(self, message):
             raise ClientError("Title must contain only ascii characters.")
 
         mod = message.get("mod") or FeaturedModType.FAF
-        mapname = message.get("mapname") or "scmp_007"
+        map_name = message.get("mapname") or "scmp_007"
         password = message.get("password")
         game_mode = mod.lower()
         rating_min = message.get("rating_min")
@@ -965,7 +968,7 @@ async def command_game_host(self, message):
             game_class=game_class,
             host=self.player,
             name=title,
-            mapname=mapname,
+            map_name=map_name,
             password=password,
             rating_type=RatingType.GLOBAL,
             displayed_rating_range=InclusiveRange(rating_min, rating_max),
@@ -973,6 +976,11 @@ async def command_game_host(self, message):
         )
         await self.launch_game(game, is_host=True)
 
+    @player_idle("ready up for a tournament game")
+    async def command_is_ready_response(self, message):
+        assert isinstance(self.player, Player)
+        await self.tournament_service.on_is_ready_response(message, self.player)
+
     async def command_match_ready(self, message):
         """
         Replace with full implementation when implemented in client, see:
diff --git a/server/message_queue_service.py b/server/message_queue_service.py
index ca36ccc55..30cd0531f 100644
--- a/server/message_queue_service.py
+++ b/server/message_queue_service.py
@@ -4,9 +4,11 @@
 
 import asyncio
 import json
+from typing import Any, Callable
 
 import aio_pika
 from aio_pika import DeliveryMode, ExchangeType
+from aio_pika.abc import AbstractIncomingMessage
 from aio_pika.exceptions import ProbableAuthenticationError
 
 from .asyncio_extensions import synchronizedmethod
@@ -29,6 +31,8 @@ class MessageQueueService(Service):
     def __init__(self) -> None:
         self._connection = None
         self._channel = None
+        self._listening_info = []
+        self.default_exchange = None
         self._exchanges = {}
         self._exchange_types = {}
         self._is_ready = False
@@ -48,9 +52,27 @@ async def initialize(self) -> None:
             await self._connect()
         except ConnectionAttemptFailed:
             return
-        self._is_ready = True
 
         await self._declare_exchange(config.MQ_EXCHANGE_NAME, ExchangeType.TOPIC)
+        await self._connect_listening_queues()
+        self._is_ready = True
+
+    async def _connect_listening_queues(self):
+        for listening_info in self._listening_info:
+            await self._declare_listening_queue(**listening_info)
+
+    async def listen_to_message(self, queue_name: str, routing_key: str,
+                                callback: Callable[[AbstractIncomingMessage], Any]):
+        listening_info = {
+            "queue_name": queue_name,
+            "routing_key": routing_key,
+            "callback": callback
+        }
+        self._listening_info.append(
+            listening_info
+        )
+        if self._is_ready:
+            await self._declare_listening_queue(listening_info)
 
     async def _connect(self) -> None:
         try:
@@ -64,6 +86,7 @@ async def _connect(self) -> None:
                 ),
                 loop=asyncio.get_running_loop(),
             )
+
         except ConnectionError as e:
             self._logger.warning(
                 "Unable to connect to RabbitMQ. Is it running?", exc_info=True
@@ -128,6 +151,7 @@ async def publish(
         payload: dict,
         mandatory: bool = False,
         delivery_mode: DeliveryMode = DeliveryMode.PERSISTENT,
+        correlation_id=None
     ) -> None:
         if not self._is_ready:
             self._logger.warning(
@@ -140,7 +164,7 @@ async def publish(
             raise KeyError(f"Unknown exchange {exchange_name}.")
 
         message = aio_pika.Message(
-            json.dumps(payload).encode(), delivery_mode=delivery_mode
+            json.dumps(payload).encode(), delivery_mode=delivery_mode, correlation_id=correlation_id
         )
 
         async with self._channel.transaction():
@@ -167,4 +191,12 @@ async def reconnect(self) -> None:
             await self._declare_exchange(
                 exchange_name, self._exchange_types[exchange_name]
             )
+
+        await self._connect_listening_queues()
         self._is_ready = True
+
+    async def _declare_listening_queue(self, listening_info):
+        queue = await self._channel.declare_queue(listening_info["queue_name"], auto_delete=False, durable=True)
+        # Binding queue
+        await queue.bind(self._exchanges[config.MQ_EXCHANGE_NAME], listening_info["routing_key"])
+        await queue.consume(callback=listening_info["callback"], no_ack=True)
diff --git a/server/metrics.py b/server/metrics.py
index 52b725c1f..ed6d7aef2 100644
--- a/server/metrics.py
+++ b/server/metrics.py
@@ -14,6 +14,15 @@ class MatchLaunch:
 
 info = Info("build", "Information collected on server start")
 
+# ==========
+# Tournament
+# ==========
+matches_tournament = Counter(
+    "server_tournament_matches_total",
+    "Number of tournament matches made",
+    ["status"]
+)
+
 # ==========
 # Matchmaker
 # ==========
diff --git a/server/players.py b/server/players.py
index 13529005e..9800d01d9 100644
--- a/server/players.py
+++ b/server/players.py
@@ -21,6 +21,7 @@ class PlayerState(Enum):
     JOINING = 4
     SEARCHING_LADDER = 5
     STARTING_AUTOMATCH = 6
+    STARTING_TOURNAMENT = 7
 
 
 class Player:
diff --git a/server/rating_service/rating_service.py b/server/rating_service/rating_service.py
index 6b81bd559..611517483 100644
--- a/server/rating_service/rating_service.py
+++ b/server/rating_service/rating_service.py
@@ -20,7 +20,6 @@
 from server.metrics import rating_service_backlog
 from server.player_service import PlayerService
 from server.rating import Leaderboard, PlayerRatings, Rating, RatingType
-
 from .game_rater import AdjustmentGameRater, GameRater, GameRatingError
 from .typedefs import (
     GameRatingResult,
@@ -141,6 +140,10 @@ async def _handle_rating_queue(self) -> None:
     async def _rate(self, summary: GameRatingSummary) -> None:
         assert self._rating_type_ids is not None
 
+        if summary.rating_type is None:
+            self._logger.debug(f"Not processing game {summary.game_id} since it is not rated.")
+            return
+
         if summary.rating_type not in self._rating_type_ids:
             raise GameRatingError(f"Unknown rating type {summary.rating_type}.")
 
diff --git a/server/tournament_service.py b/server/tournament_service.py
new file mode 100644
index 000000000..16c115cd3
--- /dev/null
+++ b/server/tournament_service.py
@@ -0,0 +1,279 @@
+import asyncio
+import datetime
+import json
+
+import aio_pika
+
+from server import games, metrics
+from server.config import config
+
+from .core import Service
+from .decorators import with_logger
+from .exceptions import ClientError
+from .game_service import GameService
+from .games import InitMode
+from .games.ladder_game import GameClosedError
+from .ladder_service.ladder_service import LadderService, NotConnectedError
+from .message_queue_service import MessageQueueService
+from .player_service import PlayerService
+from .players import Player, PlayerState
+from .timing import at_interval, datetime_now
+from .tournaments.tournament_game import TournamentGameInfo, TournamentGameState
+from .types import GameLaunchOptions
+
+
+def _notify_players(game: TournamentGameInfo):
+    for player in game:
+        player.state = PlayerState.STARTING_TOURNAMENT
+        player.write_message(
+            {
+                "command": "is_ready",
+                "featured_mod": game.featured_mod,
+                "request_id": game.request_id,
+                "response_time_seconds": game.response_time_seconds,
+                "game_name": game.name
+            }
+        )
+    game.state = TournamentGameState.CONFIRMATION_PENDING
+
+
+@with_logger
+class TournamentService(Service):
+    """
+    Service responsible for managing the tournament or galactic war games.
+    """
+
+    def __init__(self, game_service: GameService, message_queue_service: MessageQueueService,
+                 player_service: PlayerService, ladder_service: LadderService):
+        self._update_task = None
+        self.game_service = game_service
+        self.ladder_service = ladder_service
+        self.player_service = player_service
+        self.message_queue_service = message_queue_service
+        self._games: set[TournamentGameInfo] = set()
+
+    async def initialize(self):
+        self._update_task = at_interval(5, self.update_dirties)
+        await self.message_queue_service.listen_to_message("faf-lobby.tourneylauncher.createGame",
+                                                           "request.match.create", self._create_game)
+
+    async def shutdown(self):
+        self._update_task.stop()
+
+    async def update_dirties(self):
+        await self._check_for_timed_out_games()
+
+    async def _check_for_timed_out_games(self):
+        for game in self._games.copy():
+            if game.state == TournamentGameState.CONFIRMATION_PENDING and game.is_confirmation_overdue():
+                self._logger.info("Ready responses from players missing, canceling tournament game %s", game.request_id)
+                game.players_causing_cancel = set(player.id for player in game.players) - game.players_ready_ids
+                game.state = TournamentGameState.PLAYER_NOT_CONFIRMING
+                await self._remove_and_cancel_game(game)
+                return
+            if game.state.is_done():
+                self._games.remove(game)
+                return
+            if (datetime_now() - game.created_time).hours > 10:
+                self._games.remove(game)
+                self._logger.warning("Deleting leaked game with id: %s", game.request_id)
+
+    async def _remove_and_cancel_game(self, game: TournamentGameInfo, make_idle=True):
+        for player in game.players:
+            player.write_message({
+                "command": "match_cancelled",
+            })
+        self._games.remove(game)
+        if make_idle:
+            for player in game.players:
+                player.state = PlayerState.IDLE
+        await self.message_queue_service.publish(
+            config.MQ_EXCHANGE_NAME,
+            "tourneylauncher.createGame.failed",
+            {
+                "error_code": game.get_error_code(),
+                "players_causing_cancel": game.players_causing_cancel
+            },
+            correlation_id=game.request_id
+        )
+        metrics.matches_tournament.labels("failed").inc()
+
+    async def _game_created(self, game: games.TournamentGame, tournament_game: TournamentGameInfo):
+        tournament_game.state = TournamentGameState.RUNNING
+        self._games.remove(tournament_game)
+        await self.message_queue_service.publish(
+            config.MQ_EXCHANGE_NAME,
+            "tourneylauncher.createGame.success",
+            {
+                "game_id": game.id
+            },
+            correlation_id=tournament_game.request_id
+        )
+        metrics.matches_tournament.labels("success").inc()
+
+    async def _create_game(self, message: aio_pika.abc.AbstractIncomingMessage):
+        metrics.matches_tournament.labels("requested").inc()
+        try:
+            self._logger.info("Received Tournament game message")
+            await self._process_create_game(message)
+        except Exception:
+            self._logger.exception("Unknown failure creating tournament game")
+            await self.message_queue_service.publish(
+                config.MQ_EXCHANGE_NAME,
+                "tourneylauncher.createGame.failed",
+                {
+                    "request_id": message.correlation_id,
+                    "error_code": "OTHER"
+                },
+            )
+
+    async def _process_create_game(self, message):
+        body = message.body
+        body = json.loads(body)
+        game = TournamentGameInfo(**body)
+        assert game.request_id == message.correlation_id
+        if not await self._fetch_players(game):
+            await self._remove_and_cancel_game(game, make_idle=False)
+            return
+        _notify_players(game)
+
+    async def _fetch_players(self, game):
+        if not game.participants:
+            self._logger.warning("Tournament game requested with empty player list")
+            return False
+        await self.add_tournament_game(game)
+        for participant in game.participants:
+            player_id = participant["player_id"]
+            player = self.player_service[player_id]
+            if player is None:
+                self._logger.warning("Tournament game requested with player id(%s) that could not be found", player_id)
+                game.state = TournamentGameState.PLAYER_NOT_ONLINE
+                game.players_causing_cancel.add(player_id)
+                continue
+            if not player.state == PlayerState.IDLE:
+                self._logger.warning("Tournament game requested with player id(%s), player not idle", player_id)
+                game.state = TournamentGameState.PLAYER_NOT_IDLE
+                game.players_causing_cancel.add(player_id)
+            game.players.append(player)
+        return game.state == TournamentGameState.SCHEDULED
+
+    async def add_tournament_game(self, game):
+        self._games.add(game)
+
+    async def on_is_ready_response(self, message, player):
+        game = await self._get_game_for_request_id(message["request_id"])
+        if game is None:
+            raise ClientError("You try to ready up for a game that does not exist")
+        if game.state != TournamentGameState.CONFIRMATION_PENDING:
+            raise ClientError("You try to ready up for a game that is not waiting for confirmation")
+        if player not in game:
+            raise ClientError("You try to ready up for a game that you are not in")
+        if player.id in game.players_ready_ids:
+            return
+        await self.add_player_to_ready_list(game, player)
+
+    async def add_player_to_ready_list(self, game, player):
+        game.players_ready_ids.add(player.id)
+        if game.is_ready():
+            await self._launch(game)
+
+    async def _get_game_for_request_id(self, request_id) -> TournamentGameInfo:
+        for game in self._games.copy():
+            if game.request_id == request_id:
+                return game
+
+    async def _launch(self, tournament_game: TournamentGameInfo):
+        tournament_game.state = TournamentGameState.STARTING
+        self._logger.debug(
+            "Starting %s game with",
+            tournament_game.name
+        )
+        game = None
+        try:
+            host = tournament_game.players[0]
+            all_players = tournament_game.players
+            all_guests = all_players[1:]
+
+            game = self.game_service.create_game(
+                game_class=games.tournament_game.TournamentGame,
+                game_mode=tournament_game.featured_mod,
+                host=host,
+                name="Matchmaker Game",
+                max_players=len(all_players),
+                map_name=tournament_game.map_name
+            )
+            game.init_mode = InitMode.AUTO_LOBBY
+            game.set_name_unchecked(tournament_game.name)
+
+            for player in all_players:
+                player.state = PlayerState.STARTING_AUTOMATCH
+            for player in all_players:
+                # FA uses lua and lua arrays are 1-indexed
+                slot = tournament_game.get_slot_of_player(player)
+                # 2 if even, 3 if odd
+                team = tournament_game.get_team_of_player(player)
+                player.game = game
+
+                # Set player options without triggering the logic for
+                # determining that players have actually connected to the game.
+                game._player_options[player.id]["Faction"] = tournament_game.get_faction_of_player(player)
+                game._player_options[player.id]["Team"] = team
+                game._player_options[player.id]["StartSpot"] = slot
+                game._player_options[player.id]["Army"] = slot
+                game._player_options[player.id]["Color"] = slot
+
+            game_options = tournament_game.game_options
+            if game_options:
+                game.gameOptions.update(game_options)
+
+            self._logger.debug("Starting tournament game: %s", game)
+
+            def make_game_options(player: Player) -> GameLaunchOptions:
+                return GameLaunchOptions(
+                    mapname=tournament_game.map_name,
+                    expected_players=len(all_players),
+                    game_options=game_options,
+                    team=game.get_player_option(player.id, "Team"),
+                    faction=game.get_player_option(player.id, "Faction"),
+                    map_position=game.get_player_option(player.id, "StartSpot")
+                )
+
+            await self.ladder_service.launch_server_made_game(game, host, all_guests, make_game_options)
+            self._logger.debug("Tournament game launched successfully %s", game)
+            await self._game_created(game, tournament_game)
+        except Exception as e:
+            abandoning_players = []
+            if isinstance(e, NotConnectedError):
+                self._logger.info(
+                    "Tournament game failed to start! %s setup timed out",
+                    game
+                )
+                # TODO: metrics.matches.labels(queue.name, MatchLaunch.TIMED_OUT).inc()
+                abandoning_players = e.players
+                tournament_game.state = TournamentGameState.PLAYER_NOT_ONLINE
+            elif isinstance(e, GameClosedError):
+                self._logger.info(
+                    "Tournament game %s failed to start! "
+                    "Player %s closed their game instance",
+                    game, e.player
+                )
+                # TODO: metrics.matches.labels(queue.name, MatchLaunch.ABORTED_BY_PLAYER).inc()
+                abandoning_players = [e.player]
+                tournament_game.state = TournamentGameState.PLAYER_NOT_STARTING
+            else:
+                # All timeout errors should be transformed by the match starter.
+                assert not isinstance(e, asyncio.TimeoutError)
+
+                self._logger.exception("Tournament game failed to start %s", game)
+                # TODO: metrics.matches.labels(queue.name, MatchLaunch.ERRORED).inc()
+                tournament_game.state = TournamentGameState.PLAYER_NOT_CONNECTING
+            if game:
+                await game.on_game_finish()
+
+            if abandoning_players:
+                self._logger.info(
+                    "Players failed to connect: %s",
+                    abandoning_players
+                )
+                tournament_game.players_causing_cancel = set([player.id for player in abandoning_players])
+            await self._remove_and_cancel_game(tournament_game)
diff --git a/server/tournaments/__init__.py b/server/tournaments/__init__.py
new file mode 100644
index 000000000..eb897648d
--- /dev/null
+++ b/server/tournaments/__init__.py
@@ -0,0 +1,8 @@
+"""
+The tournament related classes
+"""
+from server.tournaments.tournament_game import TournamentGameInfo
+
+__all__ = [
+    "TournamentGameInfo"
+]
diff --git a/server/tournaments/tournament_game.py b/server/tournaments/tournament_game.py
new file mode 100644
index 000000000..159ac6071
--- /dev/null
+++ b/server/tournaments/tournament_game.py
@@ -0,0 +1,77 @@
+from datetime import datetime
+from enum import Enum
+
+from server.players import Player
+from server.timing import datetime_now
+
+
+class TournamentGameState(Enum):
+    SCHEDULED = 1
+    CONFIRMATION_PENDING = 2
+    STARTING = 3
+    RUNNING = 4
+    PLAYER_NOT_ONLINE = 5
+    PLAYER_NOT_IDLE = 6
+    PLAYER_NOT_CONFIRMING = 7
+    PLAYER_NOT_STARTING = 8
+    PLAYER_NOT_CONNECTING = 9
+
+    def is_done(self):
+        return self not in {
+            TournamentGameState.SCHEDULED,
+            TournamentGameState.CONFIRMATION_PENDING,
+            TournamentGameState.STARTING
+        }
+
+
+class TournamentGameInfo:
+    def __init__(
+        self,
+        request_id,
+        game_name,
+        participants,
+        featured_mod,
+        map_name,
+        game_options,
+    ):
+        self.created_time = datetime_now()
+        self.players = []
+        self.players_ready_ids = set()
+        self.state = TournamentGameState.SCHEDULED
+        self.request_id = request_id
+        self.name = game_name
+        self.participants = participants
+        self.featured_mod = featured_mod
+        self.map_name = map_name
+        self.game_options = game_options
+        self.players_causing_cancel = set()
+        self.response_time_seconds = 30
+        self.participants_by_id = {participant.id: participant for participant in participants}
+
+    def __contains__(self, player: Player) -> bool:
+        return player in self.players
+
+    def __iter__(self):
+        return iter(self.players)
+
+    def is_ready(self) -> bool:
+        return set(player.id for player in self.players) == self.players_ready_ids
+
+    def is_confirmation_overdue(self):
+        time_passed = datetime.utcnow() - self.created_time
+        return time_passed.seconds > self.response_time_seconds + 5
+
+    def get_team_of_player(self, player) -> int:
+        return self.get_participant_of_player(player)["team"]
+
+    def get_slot_of_player(self, player) -> int:
+        return self.get_participant_of_player(player)["slot"]
+
+    def get_faction_of_player(self, player) -> int:
+        return self.get_participant_of_player(player)["faction"]
+
+    def get_participant_of_player(self, player):
+        return self.participants_by_id[player.id]
+
+    def get_error_code(self):
+        return self.state.name
diff --git a/tests/conftest.py b/tests/conftest.py
index a837f94f7..d6b6c66df 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -16,6 +16,7 @@
 import hypothesis
 import pytest
 
+from server import LadderService, TournamentService, ViolationService
 from server.api.api_accessor import ApiAccessor
 from server.api.oauth_session import OAuth2Session
 from server.config import TRACE, config
@@ -118,6 +119,7 @@ def caplog_context():
     be reset between examples. Use this fixture instead to ensure that cleanup
     happens every time the test function is called.
     """
+
     @contextmanager
     def make_caplog_context(request):
         result = pytest.LogCaptureFixture(request.node, _ispytest=True)
@@ -148,6 +150,7 @@ async def _test_data():
 async def global_database(request) -> FAFDatabase:
     def opt(val):
         return request.config.getoption(val)
+
     host, user, pw, name, port = (
         opt("--mysql_host"),
         opt("--mysql_username"),
@@ -321,6 +324,29 @@ async def rating_service(database, player_service, message_queue_service):
     await service.shutdown()
 
 
+@pytest.fixture
+async def tournament_service(game_service: GameService, player_service: PlayerService,
+                             message_queue_service: MessageQueueService,
+                             ladder_service: LadderService) -> TournamentService:
+    service = TournamentService(game_service, message_queue_service, player_service, ladder_service)
+    await service.initialize()
+
+    yield service
+
+    await service.shutdown()
+
+
+@pytest.fixture
+async def ladder_service(database, game_service: GameService, player_service: PlayerService,
+                         violation_service: ViolationService) -> LadderService:
+    service = LadderService(database, game_service, violation_service)
+    await service.initialize()
+
+    yield service
+
+    await service.shutdown()
+
+
 @pytest.fixture
 async def message_queue_service():
     service = MessageQueueService()
@@ -381,6 +407,7 @@ def make(
             team_size=team_size,
             **kwargs
         )
+
     return make
 
 
diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py
index bea00ca98..ef3cb02d9 100644
--- a/tests/integration_tests/conftest.py
+++ b/tests/integration_tests/conftest.py
@@ -18,6 +18,7 @@
     OAuthService,
     PartyService,
     ServerInstance,
+    TournamentService,
     ViolationService
 )
 from server.config import config
@@ -60,10 +61,20 @@ async def party_service(game_service):
     await service.shutdown()
 
 
+@pytest.fixture
+async def tournament_service(game_service, message_queue_service, player_service, ladder_service):
+    service = TournamentService(game_service, message_queue_service, player_service, ladder_service)
+    await service.initialize()
+
+    yield service
+
+    await service.shutdown()
+
+
 @pytest.fixture
 async def broadcast_service(
-    message_queue_service,
-    game_service,
+        message_queue_service,
+        game_service,
     player_service,
 ):
     # The reference to the ServerInstance needs to be established later
@@ -119,17 +130,18 @@ async def lobby_server(
     event_loop,
     database,
     broadcast_service,
-    player_service,
-    game_service,
-    geoip_service,
-    ladder_service,
-    rating_service,
-    message_queue_service,
-    party_service,
-    oauth_service,
-    violation_service,
-    policy_server,
-    jwks_server
+        player_service,
+        game_service,
+        geoip_service,
+        ladder_service,
+        rating_service,
+        message_queue_service,
+        party_service,
+        oauth_service,
+        violation_service,
+        policy_server,
+        jwks_server,
+        tournament_service
 ):
     mock_policy = mock.patch(
         "server.lobbyconnection.config.FAF_POLICY_SERVER_BASE_URL",
@@ -153,6 +165,7 @@ async def lobby_server(
                 "party_service": party_service,
                 "oauth_service": oauth_service,
                 "violation_service": violation_service,
+                "tournament_service": tournament_service,
             })
         # Set up the back reference
         broadcast_service.server = instance
diff --git a/tests/integration_tests/test_message_queue_service.py b/tests/integration_tests/test_message_queue_service.py
index d14a5b98b..7f64ebaee 100644
--- a/tests/integration_tests/test_message_queue_service.py
+++ b/tests/integration_tests/test_message_queue_service.py
@@ -1,8 +1,10 @@
 import asyncio
+import json
 from unittest import mock
 
 import aio_pika
 import pytest
+from aio_pika.abc import AbstractIncomingMessage
 
 from server.config import config
 from server.decorators import with_logger
@@ -175,3 +177,21 @@ async def test_declaring_exchange_without_initialization(mq_uninit_service):
     assert service._is_ready
     assert service._connection is not None
     assert service._exchanges.get(exchange_name) is not None
+
+
+async def test_listening_to_incoming_messages(mq_service: MessageQueueService):
+    callback = Callback()
+    await mq_service.listen_to_message("test", "routing.test", callback.callback_listening)
+    await mq_service.declare_exchange(config.MQ_EXCHANGE_NAME)
+    await mq_service.publish(config.MQ_EXCHANGE_NAME, "routing.test", {"test": "test"})
+    await callback.called_future
+
+
+class Callback:
+
+    def __init__(self) -> None:
+        self.called_future = asyncio.Future()
+
+    def callback_listening(self, message: AbstractIncomingMessage):
+        assert json.loads(message.body)["test"] == "test"
+        self.called_future.set_result(None)
diff --git a/tests/integration_tests/test_server_instance.py b/tests/integration_tests/test_server_instance.py
index 52e8e6d7e..17d27124b 100644
--- a/tests/integration_tests/test_server_instance.py
+++ b/tests/integration_tests/test_server_instance.py
@@ -31,6 +31,7 @@ async def test_multiple_contexts(
     party_service,
     rating_service,
     oauth_service,
+    tournament_service,
     event_loop
 ):
     config.USE_POLICY_SERVER = False
@@ -49,7 +50,8 @@ async def test_multiple_contexts(
             "ladder_service": ladder_service,
             "rating_service": rating_service,
             "party_service": party_service,
-            "oauth_service": oauth_service
+            "oauth_service": oauth_service,
+            "tournament_service": tournament_service,
         }
     )
     broadcast_service.server = instance
diff --git a/tests/integration_tests/test_servercontext.py b/tests/integration_tests/test_servercontext.py
index 832e4f625..a8fb349a5 100644
--- a/tests/integration_tests/test_servercontext.py
+++ b/tests/integration_tests/test_servercontext.py
@@ -61,6 +61,7 @@ def make_connection() -> LobbyConnection:
             party_service=mock.Mock(),
             rating_service=mock.Mock(),
             oauth_service=mock.Mock(),
+            tournament_service=mock.Mock(),
         )
 
     ctx = ServerContext("TestServer", make_connection, [mock_service])
diff --git a/tests/integration_tests/test_tournament_service.py b/tests/integration_tests/test_tournament_service.py
new file mode 100644
index 000000000..82e13a172
--- /dev/null
+++ b/tests/integration_tests/test_tournament_service.py
@@ -0,0 +1,46 @@
+import pytest
+
+from server import TournamentService
+from server.config import config
+from server.message_queue_service import MessageQueueService
+from tests.integration_tests.conftest import (
+    connect_and_sign_in,
+    read_until_command
+)
+
+pytestmark = pytest.mark.rabbitmq
+
+
+async def test_create_game_by_message(message_queue_service: MessageQueueService,
+                                      tournament_service: TournamentService, lobby_server):
+    _, _, proto1 = await connect_and_sign_in(
+        ("test", "test_password"), lobby_server
+    )
+    await message_queue_service.declare_exchange(config.MQ_EXCHANGE_NAME)
+    await message_queue_service.publish(config.MQ_EXCHANGE_NAME, "request.match.create",
+                                        {
+                                            "request_id": "9124e8c9-c62f-43c3-bb64-94f3093f2997",
+                                            "game_name": "My game name",
+                                            "participants": [
+                                                {
+                                                    "team": 1,
+                                                    "slot": 1,
+                                                    "faction": 1,
+                                                    "player_id": 1
+                                                }
+                                            ],
+                                            "featured_mod": "faf",
+                                            "map_name": "SCMP_001",
+                                            "game_options": {
+                                                "test": "test"
+                                            }
+                                        }, correlation_id="9124e8c9-c62f-43c3-bb64-94f3093f2997"
+                                        )
+    msg = await read_until_command(proto1, "is_ready")
+    assert msg == {
+        'command': 'is_ready',
+        'featured_mod': 'faf',
+        'game_name': 'My game name',
+        'request_id': '9124e8c9-c62f-43c3-bb64-94f3093f2997',
+        'response_time_seconds': 30
+    }
diff --git a/tests/unit_tests/test_coop_game.py b/tests/unit_tests/test_coop_game.py
index ce16aaf06..78753ebda 100644
--- a/tests/unit_tests/test_coop_game.py
+++ b/tests/unit_tests/test_coop_game.py
@@ -9,7 +9,7 @@ async def test_create_coop_game(database):
         database=database,
         host=mock.Mock(),
         name="Some game",
-        map_="some_map",
+        map_name="some_map",
         game_mode="coop",
         game_service=mock.Mock(),
         game_stats_service=mock.Mock()
diff --git a/tests/unit_tests/test_gameconnection.py b/tests/unit_tests/test_gameconnection.py
index 7d17c82f9..837ec4a38 100644
--- a/tests/unit_tests/test_gameconnection.py
+++ b/tests/unit_tests/test_gameconnection.py
@@ -415,7 +415,8 @@ async def test_handle_action_GameOption(
     assert game.max_players == 7
     # I don't know what these paths actually look like
     await game_connection.handle_action("GameOption", ["ScenarioFile", "C:\\Maps\\Some_Map"])
-    assert game.map_file_path == "maps/some_map.zip"
+    assert game.map_name == "some_map"
+    assert game.map_scenario_path == "C:/Maps/Some_Map"
     await game_connection.handle_action("GameOption", ["Title", "All welcome"])
     assert game.name == "All welcome"
     await game_connection.handle_action("GameOption", ["ArbitraryKey", "ArbitraryValue"])
diff --git a/tests/unit_tests/test_games_service.py b/tests/unit_tests/test_games_service.py
index 6d3525362..51a7b340a 100644
--- a/tests/unit_tests/test_games_service.py
+++ b/tests/unit_tests/test_games_service.py
@@ -25,7 +25,7 @@ async def test_create_game(players, game_service):
         game_mode="faf",
         host=players.hosting,
         name="Test",
-        mapname="SCMP_007",
+        map_name="SCMP_007",
         password=None
     )
     assert game is not None
@@ -42,7 +42,7 @@ async def test_all_games(players, game_service):
         game_mode="faf",
         host=players.hosting,
         name="Test",
-        mapname="SCMP_007",
+        map_name="SCMP_007",
         password=None
     )
     assert game in game_service.pending_games
@@ -68,7 +68,7 @@ async def test_create_game_other_gamemode(players, game_service):
         game_mode="labwars",
         host=players.hosting,
         name="Test",
-        mapname="SCMP_007",
+        map_name="SCMP_007",
         password=None
     )
     assert game is not None
diff --git a/tests/unit_tests/test_lobbyconnection.py b/tests/unit_tests/test_lobbyconnection.py
index fb89572ce..740fc3c6e 100644
--- a/tests/unit_tests/test_lobbyconnection.py
+++ b/tests/unit_tests/test_lobbyconnection.py
@@ -6,6 +6,7 @@
 from aiohttp import web
 from sqlalchemy import and_, select
 
+from server import TournamentService
 from server.config import config
 from server.db.models import ban, friends_and_foes
 from server.exceptions import BanError, ClientError
@@ -104,7 +105,8 @@ def lobbyconnection(
         ladder_service=mock.create_autospec(LadderService),
         party_service=mock.create_autospec(PartyService),
         oauth_service=mock.create_autospec(OAuthService),
-        rating_service=rating_service
+        rating_service=rating_service,
+        tournament_service=mock.create_autospec(TournamentService)
     )
 
     lc.player = mock_player
@@ -250,7 +252,7 @@ async def test_command_game_host_creates_game(
         "host": players.hosting,
         "visibility": VisibilityState.PUBLIC,
         "password": test_game_info["password"],
-        "mapname": test_game_info["mapname"],
+        "map_name": test_game_info["mapname"],
         "rating_type": RatingType.GLOBAL,
         "displayed_rating_range": InclusiveRange(None, None),
         "enforce_rating_range": False
diff --git a/tests/unit_tests/test_tournament_service.py b/tests/unit_tests/test_tournament_service.py
new file mode 100644
index 000000000..e26089674
--- /dev/null
+++ b/tests/unit_tests/test_tournament_service.py
@@ -0,0 +1,195 @@
+from unittest import mock
+
+import pytest
+from aio_pika.abc import AbstractIncomingMessage
+
+from server import (
+    GameService,
+    LadderService,
+    LobbyConnection,
+    MessageQueueService,
+    PlayerService,
+    TournamentService,
+    config
+)
+from server.players import PlayerState
+from server.tournaments.tournament_game import (
+    TournamentGameInfo,
+    TournamentGameState
+)
+
+
+async def test_create_tournament_game(game_service: GameService, message_queue_service: MessageQueueService,
+                                      player_service: PlayerService, ladder_service: LadderService, player_factory):
+    player = player_factory(player_id=1)
+    mock_lconn = mock.create_autospec(LobbyConnection)
+    player.lobby_connection = mock_lconn
+    player_service[player.id] = player
+    tournament_service: TournamentService = TournamentService(game_service, message_queue_service, player_service,
+                                                              ladder_service)
+
+    message = mock.Mock(AbstractIncomingMessage)
+    message.body = """
+        {
+            "request_id": "9124e8c9-c62f-43c3-bb64-94f3093f2997",
+            "game_name": "My game name",
+            "participants": [
+                {
+                    "team": 1,
+                    "slot": 1,
+                    "faction": 1,
+                    "player_id": 1
+                }
+            ],
+            "featured_mod": "faf",
+            "map_name": "SCMP_001",
+            "game_options": {
+                "test": "test"
+            }
+        }
+    """
+    message.correlation_id = "9124e8c9-c62f-43c3-bb64-94f3093f2997"
+    await tournament_service._create_game(
+        message
+    )
+    assert next(iter(tournament_service._games)).name == "My game name"
+    assert next(iter(tournament_service._games)).state == TournamentGameState.CONFIRMATION_PENDING
+    mock_lconn.write.assert_called_with(
+        {
+            'command': 'is_ready',
+            'featured_mod': 'faf',
+            'game_name': 'My game name',
+            'request_id': '9124e8c9-c62f-43c3-bb64-94f3093f2997',
+            'response_time_seconds': 30
+        }
+    )
+    assert not mock_lconn.send_warning.called
+    assert player.state == PlayerState.STARTING_TOURNAMENT
+
+
+async def test_create_tournament_game_player_not_idle(game_service: GameService,
+                                                      player_service: PlayerService, ladder_service: LadderService,
+                                                      player_factory):
+    player = player_factory(player_id=1)
+    player.state = PlayerState.PLAYING
+    mock_lconn = mock.create_autospec(LobbyConnection)
+    player.lobby_connection = mock_lconn
+    player_service[player.id] = player
+    message_queue_mock = mock.create_autospec(MessageQueueService)
+    tournament_service: TournamentService = TournamentService(game_service, message_queue_mock, player_service,
+                                                              ladder_service)
+
+    message = mock.Mock(AbstractIncomingMessage)
+    message.body = """
+        {
+            "request_id": "9124e8c9-c62f-43c3-bb64-94f3093f2997",
+            "game_name": "My game name",
+            "participants": [
+                {
+                    "team": 1,
+                    "slot": 1,
+                    "faction": 1,
+                    "player_id": 1
+                }
+            ],
+            "featured_mod": "faf",
+            "map_name": "SCMP_001",
+            "game_options": {
+                "test": "test"
+            }
+        }
+    """
+    message.correlation_id = "9124e8c9-c62f-43c3-bb64-94f3093f2997"
+    await tournament_service._create_game(
+        message
+    )
+    message_queue_mock.publish.assert_called_with(
+        config.MQ_EXCHANGE_NAME,
+        "tourneylauncher.createGame.failed",
+        {
+            "error_code": "PLAYER_NOT_IDLE",
+            "players_causing_cancel": {1}
+        },
+        correlation_id="9124e8c9-c62f-43c3-bb64-94f3093f2997"
+    )
+    assert player.state == PlayerState.PLAYING
+
+
+@pytest.fixture
+def tournament_game():
+    tournament_game: TournamentGameInfo = mock.create_autospec(TournamentGameInfo)
+    tournament_game.request_id = "9124e8c9-c62f-43c3-bb64-94f3093f2997"
+    tournament_game.name = "My game name"
+    tournament_game.map_name = "SCMP_001"
+    tournament_game.players_causing_cancel = set()
+    tournament_game.featured_mod = "faf"
+    tournament_game.game_options = {}
+    tournament_game.state = TournamentGameState.CONFIRMATION_PENDING
+    tournament_game.players_ready_ids = set()
+    tournament_game.__contains__ = lambda x, y: True
+    return tournament_game
+
+
+class Any:
+    def __eq__(self, other):
+        return True
+
+
+async def test_tournament_on_is_ready_response(game_service: GameService, player_service: PlayerService,
+                                               player_factory, tournament_game: TournamentGameInfo):
+    player = player_factory(player_id=1)
+    mock_lconn = mock.create_autospec(LobbyConnection)
+    player.lobby_connection = mock_lconn
+    player_service[player.id] = player
+    message_queue_mock = mock.create_autospec(MessageQueueService)
+    ladder_service = mock.create_autospec(LadderService)
+    tournament_service: TournamentService = TournamentService(game_service, message_queue_mock, player_service,
+                                                              ladder_service)
+    tournament_service._games.add(tournament_game)
+    tournament_game.players = [player]
+    await tournament_service.on_is_ready_response(
+        {"request_id": "9124e8c9-c62f-43c3-bb64-94f3093f2997"}, player
+    )
+    assert not mock_lconn.send_warning.called
+    ladder_service.launch_server_made_game.assert_called_once()
+    message_queue_mock.publish.assert_called_with(
+        config.MQ_EXCHANGE_NAME,
+        "tourneylauncher.createGame.success",
+        {
+            "game_id": Any()
+        },
+        correlation_id="9124e8c9-c62f-43c3-bb64-94f3093f2997"
+    )
+    assert len(tournament_service._games) == 0
+    assert player.state == PlayerState.STARTING_AUTOMATCH
+
+
+async def test_tournament_timeout_on_ready(game_service: GameService, player_service: PlayerService,
+                                           player_factory, tournament_game: TournamentGameInfo):
+    player = player_factory(player_id=1)
+    mock_lconn = mock.create_autospec(LobbyConnection)
+    player.lobby_connection = mock_lconn
+    player_service[player.id] = player
+    message_queue_mock = mock.create_autospec(MessageQueueService)
+    ladder_service = mock.create_autospec(LadderService)
+    tournament_service: TournamentService = TournamentService(game_service, message_queue_mock, player_service,
+                                                              ladder_service)
+    tournament_game.players = [player]
+    tournament_game.is_confirmation_overdue.return_value = True
+    tournament_game.get_error_code.return_value = "PLAYER_NOT_CONFIRMING"
+    tournament_service._games.add(tournament_game)
+    await tournament_service.update_dirties()
+    assert not mock_lconn.send_warning.called
+    ladder_service.launch_server_made_game.assert_not_called()
+    message_queue_mock.publish.assert_called_with(
+        config.MQ_EXCHANGE_NAME,
+        "tourneylauncher.createGame.failed",
+        {
+            "error_code": "PLAYER_NOT_CONFIRMING",
+            "players_causing_cancel": {1}
+        },
+        correlation_id="9124e8c9-c62f-43c3-bb64-94f3093f2997"
+    )
+    assert len(tournament_service._games) == 0
+    assert tournament_game.state == TournamentGameState.PLAYER_NOT_CONFIRMING
+    assert player.state == PlayerState.IDLE