Add project data

This commit is contained in:
2025-12-30 02:22:44 +01:00
parent a6316b8b06
commit 747af63a29
2301 changed files with 67690 additions and 1 deletions

239
.editorconfig Normal file
View File

@@ -0,0 +1,239 @@
# Entfernen Sie die folgende Zeile, wenn Sie EDITORCONFIG-Einstellungen von höheren Verzeichnissen vererben möchten.
root = true
# C#-Dateien
[*.cs]
#### Wichtige EditorConfig-Optionen ####
# Einzüge und Abstände
indent_size = 4
indent_style = space
tab_width = 4
# Einstellungen für neue Zeilen
end_of_line = crlf
insert_final_newline = false
#### .NET-Codierungskonventionen ####
# Using-Direktiven organisieren
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this.- und Me.-Einstellungen
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# Einstellungen für Sprachschlüsselwörter und BCL-Typen
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Einstellungen für Klammern
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# Einstellungen für Modifizierer
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Einstellungen für Ausdrucksebene
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true
dotnet_style_prefer_conditional_expression_over_return = true
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Einstellungen für Felder
dotnet_style_readonly_field = true
# Einstellungen für Parameter
dotnet_code_quality_unused_parameters = all
# Unterdrückungseinstellungen
dotnet_remove_unnecessary_suppression_exclusions = none
# Einstellungen für neue Zeilen
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C#-Codierungskonventionen ####
# Var-Einstellungen
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Ausdruckskörpermember
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = false
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# Einstellungen für den Musterabgleich
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true:suggestion
csharp_style_prefer_switch_expression = true
# Einstellungen für NULL-Überprüfung
csharp_style_conditional_delegate_call = true
# Einstellungen für Modifizierer
csharp_prefer_static_local_function = false:silent
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = true
# Einstellungen für Codeblöcke
csharp_prefer_braces = true
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = block_scoped
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_top_level_statements = true
# Einstellungen für Ausdrucksebene
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = true
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true
csharp_style_prefer_tuple_swap = true
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
# Einstellungen für using-Anweisungen
csharp_using_directive_placement = outside_namespace:error
# Einstellungen für neue Zeilen
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C#-Formatierungsregeln ####
# Einstellungen für neue Zeilen
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Einstellungen für Einrückung
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Einstellungen für Abstände
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Umbrucheinstellungen
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Benennungsstile ####
# Benennungsregeln
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.severity = suggestion
dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.style = begins_with__
# Symbolspezifikationen
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Benennungsstile
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.begins_with__.required_prefix = _
dotnet_naming_style.begins_with__.required_suffix =
dotnet_naming_style.begins_with__.word_separator =
dotnet_naming_style.begins_with__.capitalization = camel_case

3
.gitignore vendored
View File

@@ -361,3 +361,6 @@ MigrationBackup/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
# Cache
/Tabletop/obj

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Tom Sassor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

320
MSSQL_DATA.sql Normal file
View File

@@ -0,0 +1,320 @@
-- Deactivate UNIQUE_CHECKS and FOREIGN_KEY_CHECKS
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS;
ALTER DATABASE Tabletop SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE Tabletop SET CONSTRAINT ALL DEFERRED;
-- Database
USE Tabletop;
-- Fractions
INSERT INTO Fractions (Image) VALUES
(NULL),
(NULL),
(NULL),
(NULL);
-- Fraction Descriptions
INSERT INTO FractionDescription (FractionId, Code, Name, ShortName, Description) VALUES
(1, 'en', 'Grand Army of the Republic', 'GAR', 'The Grand Army of the Republic is the main military force of the Galactic Republic. It was formed during the Clone Wars to combat the Separatists. The army consists primarily of clone troopers, who were created using the genetic material of Jango Fett as a basis. It encompasses a variety of troops, including infantry, artillery, and special forces, as well as an extensive fleet of starships. The Grand Army of the Republic is renowned for its effective organization, disciplined training, and tactical prowess.'),
(1, 'de', 'Große Armee der Republik', 'GAR', 'Die Große Armee der Republik ist die Hauptstreitmacht der Galaktischen Republik. Sie wurde während der Klonkriege eingesetzt, um die Separatisten zu bekämpfen. Die Armee besteht hauptsächlich aus Klontruppen, die mit dem genetischen Material von Jango Fett als Grundlage geschaffen wurden. Sie umfasst eine Vielzahl von Truppentypen, darunter Infanterie, Artillerie und Spezialeinheiten, sowie eine umfangreiche Flotte von Raumschiffen. Die Große Armee der Republik ist bekannt für ihre effektive Organisation, disziplinierte Ausbildung und taktische Geschicklichkeit.'),
(2, 'en', 'Confederacy of Independent Systems', 'CIS', 'The Confederation of Independent Systems (CIS) is a military alliance in the galaxy, consisting of renegade planets and organizations that have distanced themselves from the rule of the Galactic Republic. The CIS possesses an impressive military force, primarily composed of droid armies. These automated combat droids are produced in large numbers and pose a dangerous threat due to their numerical superiority.'),
(2, 'de', 'Konföderation der unabhängigen Systeme', 'KUS', 'Die Konföderation unabhängiger Systeme (KUS) ist ein militärisches Bündnis in der Galaxis, bestehend aus abtrünnigen Planeten und Organisationen, die sich von der Herrschaft der Galaktischen Republik distanziert haben. Die KUS verfügt über eine beeindruckende Militärmacht, die hauptsächlich aus Droidenarmeen besteht. Diese automatisierten Kampfdroiden werden in großen Mengen produziert und stellen aufgrund ihrer zahlenmäßigen Überlegenheit eine gefährliche Bedrohung dar.'),
(3, 'en', 'Galactic Empire', 'EMP', 'The Galactic Empire is a military power in the galaxy that emerged after the fall of the Galactic Republic. It possesses a massive military force based on the suppression and control of planets and systems. The Empire employs a variety of troop types. It is renowned for its iron discipline, rigorous training, and ruthless efficiency. It seeks galaxy-wide dominance and pursues a policy of fear and subjugation.'),
(3, 'de', 'Galaktisches Imperium', 'IMP', 'Das Galaktische Imperium ist eine militärische Macht in der Galaxie, die nach dem Fall der Galaktischen Republik entstand. Es verfügt über eine massive Militärmacht, die auf der Unterdrückung und Kontrolle von Planeten und Systemen basiert. Das Imperium setzt verschiedene Arten von Truppen ein. Es ist bekannt für seine eiserne Disziplin, strenge Ausbildung und rücksichtslose Effizienz. Es strebt die Vorherrschaft in der gesamten Galaxie an und verfolgt eine Politik der Angst und Unterwerfung.'),
(4, 'en', 'Rebellion', 'REB', 'The Rebellion is a military resistance movement that fights against the rule of the Galactic Empire. It consists of various factions and planets that rise up against oppression. The Rebellion employs asymmetric warfare and guerrilla tactics to inflict damage upon the Empire. Its forces include both regular troops and partisan units. The Rebellion is known for its determination, belief in freedom, and ability to mobilize resources to challenge the Empire. It also utilizes stolen or improvised starships and weapons to carry out its operations.'),
(4, 'de', 'Rebellion', 'REB', 'Der Widerstand ist eine militärische Widerstandsbewegung, die gegen die Herrschaft des Galaktischen Imperiums kämpft. Er besteht aus verschiedenen Fraktionen und Planeten, die sich gegen die Unterdrückung erheben. Der Widerstand setzt asymmetrische Kriegsführung und Guerillataktiken ein, um dem Imperium Schaden zuzufügen. Seine Streitkräfte umfassen sowohl reguläre Truppen als auch Partisaneneinheiten. Der Widerstand ist bekannt für seine Entschlossenheit, seinen Glauben an die Freiheit und seine Fähigkeit, Ressourcen zu mobilisieren, um das Imperium herauszufordern. Er verwendet auch gestohlene oder improvisierte Raumschiffe und Waffen, um seine Operationen durchzuführen.');
-- Classes
INSERT INTO Classes (ClassId, Quantity) VALUES
(1, 0),
(2, 0),
(3, 0);
-- Class Descriptions
INSERT INTO ClassDescription (ClassId, Code, Name, Description) VALUES
(1, 'en', 'Regular', ''),
(1, 'de', 'Regulär', ''),
(2, 'en', 'Elite', ''),
(2, 'de', 'Elite', ''),
(3, 'en', 'Specialist', ''),
(3, 'de', 'Spezialist', '');
-- Abilities
INSERT INTO Abilities (AbilityId, Quality, Force) VALUES
(1, 1, 0),
(2, 0, 1);
-- Ability Descriptions
INSERT INTO AbilityDescription (AbilityId, Code, Name, Description) VALUES
(1, 'en', '', ''),
(1, 'de', '', ''),
(2, 'en', '', ''),
(2, 'de', '', '');
-- Weapons
INSERT INTO Weapons (Attack, Quality, Range, Dices) VALUES
(5, 5, 60, 1),
(6, 5, 45, 1),
(9, 7, 80, 1),
(4, 4, 30, 1),
(7, 4, 30, 6),
(6, 4, 40, 1),
(10, 7, 80, 1),
(6, 5, 45, 1),
(6, 4, 50, 2),
(7, 4, 30, 4),
(5, 5, 45, 1),
(7, 4, 60, 1),
(9, 7, 80, 1),
(4, 4, 45, 2),
(8, 4, 35, 1),
(4, 4, 30, 1);
-- Weapon Descriptions
INSERT INTO WeaponDescription (WeaponId, Code, Name, Description) VALUES
(1, 'en', 'DC-15A Blaster Rifle', ''),
(1, 'de', 'DC-15A Blastergewehr', ''),
(2, 'en', 'DC-15S Blaster Carbine', ''),
(2, 'de', 'DC-15S Blasterkarabiner', ''),
(3, 'en', 'DC-15x Sniper Rifle', ''),
(3, 'de', 'DC-15x Scharfschützengewehr', ''),
(4, 'en', 'DC-17 Hand Blaster', ''),
(4, 'de', 'DC-17 Handblaster', ''),
(5, 'en', 'Z-6 Rotary Blaster Cannon', ''),
(5, 'de', 'Z-6 Rotationsblaster', ''),
(6, 'en', 'E-5 Blaster Rifle', ''),
(6, 'de', 'E-5 Blastergewehr', ''),
(7, 'en', 'E-5s Sniper Rifle', ''),
(7, 'de', 'E-5s Scharfschützengewehr', ''),
(8, 'en', 'RG-4D Blaster Pistol', ''),
(8, 'de', 'RG-4D Blasterpistole', ''),
(9, 'en', 'Wrist Blaster', ''),
(9, 'de', 'Dual-Blaster', ''),
(10, 'en', 'Droideka', ''),
(10, 'de', 'Droideka', ''),
(11, 'en', 'E-11 Blaster Rifle', ''),
(11, 'de', 'E-11 Blastergewehr', ''),
(12, 'en', 'DLT-19 Heavy Blaster Rifle', ''),
(12, 'de', 'DLT-19 Schweres Blastergewehr', ''),
(13, 'en', 'E-11s Sniper Rifle', ''),
(13, 'de', 'E-11s Scharfschützengewehr', ''),
(14, 'en', 'E-22 Blaster Rifle', ''),
(14, 'de', 'E-22 Blastergewehr', ''),
(15, 'en', 'E-11D Blaster Rifle', ''),
(15, 'de', 'E-11D Blastergewehr', ''),
(16, 'en', 'SE-14 Hand Blaster', ''),
(16, 'de', 'SE-14 Handblaster', '');
-- Units
INSERT INTO Units (FractionId, ClassId, TroopQuantity, Defense, Moving, PrimaryWeaponId, SecondaryWeaponId, FirstAbilityId, SecondAbilityId, Image) VALUES
(1, 1, 6, 6, 16, 2, NULL, NULL, NULL, NULL),
(1, 1, 6, 5, 16, 1, NULL, NULL, NULL, NULL),
(1, 1, 6, 6, 16, 1, NULL, NULL, NULL, NULL),
(1, 1, 6, 6, 16, 2, NULL, NULL, NULL, NULL),
(1, 1, 6, 6, 16, 2, NULL, 1, NULL, NULL),
(1, 2, 4, 6, 22, 4, 4, NULL, NULL, NULL),
(1, 2, 4, 6, 22, 2, NULL, NULL, NULL, NULL),
(1, 3, 1, 7, 10, 5, NULL, NULL, NULL, NULL),
(1, 3, 1, 6, 14, 3, 4, NULL, NULL, NULL),
(2, 1, 8, 2, 12, 6, NULL, NULL, NULL, NULL),
(2, 1, 6, 5, 14, 9, NULL, NULL, NULL, NULL),
(2, 2, 4, 6, 17, 6, NULL, 1, NULL, NULL),
(2, 2, 6, 5, 14, 9, NULL, NULL, NULL, NULL),
(2, 2, 6, 2, 22, 6, NULL, NULL, NULL, NULL),
(2, 3, 1, 10, 22, 15, NULL, NULL, NULL, NULL),
(2, 3, 1, 2, 12, 6, 8, NULL, NULL, NULL),
(3, 1, 6, 5, 16, 11, NULL, NULL, NULL, NULL),
(3, 1, 6, 5, 15, 11, NULL, NULL, NULL, NULL),
(3, 2, 4, 7, 15, 15, NULL, NULL, NULL, NULL),
(3, 1, 6, 5, 16, 14, NULL, NULL, NULL, NULL),
(3, 2, 4, 6, 14, 12, NULL, NULL, NULL, NULL),
(3, 2, 4, 5, 22, 11, NULL, NULL, NULL, NULL),
(3, 3, 1, 4, 15, 13, 16, NULL, NULL, NULL),
(4, 1, 6, 4, 16, 11, NULL, NULL, NULL, NULL),
(4, 2, 4, 4, 16, 11, NULL, NULL, NULL, NULL),
(4, 1, 6, 4, 16, 11, NULL, NULL, NULL, NULL),
(4, 1, 6, 4, 16, 11, NULL, NULL, NULL, NULL),
(4, 2, 4, 4, 16, 11, NULL, NULL, NULL, NULL),
(4, 3, 1, 4, 16, 11, NULL, NULL, NULL, NULL),
(4, 3, 1, 4, 16, 11, NULL, NULL, NULL, NULL);
-- Unit Descriptions
INSERT INTO UnitDescription (UnitId, Code, Name, Description, Mechanic) VALUES
(1, 'en', '41st Ranger Platoon', 'The 41st Ranger Platoon is a clone trooper infantry platoon and strike team within the 41st Elite Corps. The platoon participated in sabotage missions on Separatist supply lines and installations.', ''),
(1, 'de', '41. Ranger Platoon', '', ''),
(2, 'en', '41st Elite Corps', 'The 41st Elite Corps is a military corps of the Grand Army of the Republic. They are specialized in conducting missions on exotic and often primitve worlds. One of their main tasks was to contact and cooperate with the native indigenous population. in addition, the unit was capable of successfully carrying out both infatry and reconnaissance missions.', ''),
(2, 'de', '41. Elitekorps', '', ''),
(3, 'en', '212th Attack Battalion', 'The 212th Attack Battalion is a clone trooper battalion in the Grand Army of the Republic. The Battalion is famous for its bravery, discipline and effectivness in combat. As an extremly versatile infantry unit of the Regular Forces, it is optimally equipped for a variety of different tasks.', ''),
(3, 'de', '212. Angriffsbataillion', '', ''),
(4, 'en', '2nd Airborne Company', 'The 2nd Airborne Company was an aerial infantry company of the 212th Attack Battalion. The company consisted of clone paratroopers and participated in the Clone Wars.', ''),
(4, 'de', '2. Luftlandekompanie', '', ''),
(5, 'en', '501st Legion', 'The 501st Legion is an elite military battalion of clone troopers in the Grand Army of the Galactic Republic. The soldiers of the Legion are known for their courage, unconventional tactics and loyalty to the Republic.', ''),
(5, 'de', '501. Legion', '', ''),
(6, 'en', '501st Jetpack Company', 'The 501st Jetpack Company is an special unit within the 501st Legion whose soldiers are equipped with jetpacks. The jetpack troopers use their jetpacks with limited flight capabilities to quickly cover large distances and gain an advantage over their enemies in the air. Thanks to their exceptional agility, they are hard to hit and can skillfully attack their enemies from behind.', ''),
(6, 'de', '501. Jetpack Kompanie', '', ''),
(7, 'en', '104th Battalion', 'The 104th Battalion, is a military unit within the Galactic Republic. Its primary mission is to perform relief and recovery missions. The battalions distinctive mark is a symbol with a wolfs snout, which is why they are also called the Wolfpack battalion.', ''),
(7, 'de', '104. Bataillion', '', ''),
(8, 'en', '501st Heavy Gunner', 'The 501st Heavy Gunners form an elite special forces unit within the 501st Legion, whose members are equipped with powerful Heavy Guns. These soldiers possess advanced clone trooper armor that offers significant improvements over traditional army clone troopers. Their heavy blasters also give them superior firepower, making them a formidable presence on the battlefield.', ''),
(8, 'de', '501. Schwerer Schütze', '', ''),
(9, 'en', '501st Specialist', 'The 501st Specialist represent an special unit within the 501st Legion. Their expertise lies in precise attacks from a considerable distance, which makes them an extremely dangerous threat. Their primary mission is to conduct patrols and lead reconnaissance missions.', ''),
(9, 'de', '501. Scharfschütze', '', ''),
(10, 'en', 'B1-series Battle Droid', 'The B1 battle droids are developed for mass combat and are known to operate in large numbers against enemy forces. They have a relatively simple artificial intelligence and are often quite clumsy and easy to overcome.', ''),
(10, 'de', 'B1-Serie Kampfdroide', '', ''),
(11, 'en', 'B2-series Super Battle Droid', 'The B2-Series Super Battle Droid is a more advanced and much more durable version of the B1-Series Battle Droid. Equipped with reinforced armor and more advanced weapons, the B2 significantly surpasses its predecessor in terms of combat power. Added to this is its improved artificial intelligence, which makes it an extremely dangerous and efficient combat unit.', ''),
(11, 'de', 'B2-Serie Superkampfdroide', '', ''),
(12, 'en', 'BX-series Commando Droid', 'The BX-series Commando Droid represents a specialized and sophisticated class of combat droids. Their main role is in covert missions and tactical operations. Equipped with advanced cloaking devices that can make them almost invisible, they are capable of carrying out surprise attacks skillfully.', ''),
(12, 'de', 'BX-Serie Kommandodroide', '', ''),
(13, 'en', 'B2-HA series Super Battle Droid', 'The B2-HA Super Battle Droid, also known as the Heavy Assault Super Battle Droid, is a variant of the B2-Series Battle Droid equipped with a missile launcher. Its ability to perform targeted missile attacks makes it an extremely dangerous combat unit in various situations.', ''),
(13, 'de', 'B2-HA Serie Raketenkampfdroide', '', ''),
(14, 'en', 'B1-series Rocket Battle Droid', '', ''),
(14, 'de', 'B1-Serie Jetpack Kampfdroide', '', ''),
(15, 'en', 'Droideka', 'Droidekas are specialized battle droids characterized by their characteristic spherical shape. In this spherical form, they are able to attack, but they are also extremely fast and agile. However, once they assume their attack position, they unfold to reveal their three-legged form. In this upright position, they have devastating energy weapon salvos and are also equipped with powerful energy shields that protect them from enemy fire.', ''),
(15, 'de', 'Droideka', '', ''),
(16, 'en', 'B1-series Sniper Droid', 'The B1-series Sniper Droid is a special variant of the B1-series combat droid. Their gyroscopic stabilizers and detailed target selection programs made them very effective snipers.', ''),
(16, 'de', 'B1-Serie Scharfschützendroide', '', ''),
(17, 'en', 'Imperial Storm Trooper', 'Stormtroopers are the primary unit of the Galactic Empire. In action, the Stormtroopers are capable of a variety of tasks, including counterinsurgency, defense of Imperial installations, as well as assault missions and conducting operations as part of the Galactic Conquest of the Empire.', ''),
(17, 'de', 'Imperialer Sturmtruppe', '', ''),
(18, 'en', 'Imperial Snow Trooper', 'The Snowtroopers are specialized stormtroopers of the Galactic Empire, equipped with special armors designed to protect them from extreme conditions, including cold, ice, and snow. Their main mission is to pursue hostile targets in snow-covered regions.', ''),
(18, 'de', 'Imperialer Schneetruppe', '', ''),
(19, 'en', 'Imperial Death Trooper', 'Death Troopers are elite soldiers assigned to particularly dangerous and clandestine missions. They often serve as bodyguards for high-ranking Imperial officers or perform special tasks where discretion and efficiency are of the utmost importance.', ''),
(19, 'de', 'Imperiale Todestruppe', '', ''),
(20, 'en', 'Imperial Shore Trooper', 'Shore Troopers are specialized stormtroopers equipped with armor optimized for combat in maritime environments. Their main task is to engage hostile forces on beaches or coastlines and secure important locations. Additionally, they are often deployed in the defense of Imperial bases along coastal lines.', ''),
(20, 'de', 'Imperiale Küstentruppe', '', ''),
(21, 'en', 'Imperial Shock Trooper', 'Shock troopers are elite and highly trained Storm Troopers of the Galactic Empire. They are known for wearing red armor. Their role is to maintain imperial order and combat threats to the Empire.', ''),
(21, 'de', 'Imperiale Stocktruppe', '', ''),
(22, 'en', 'Imperial Jetpack Trooper', 'Jetpack Troopers are specialized troops equipped with jetpacks, allowing them to hover in the air and move faster and more agilely in combat, making them dangerous and versatile adversaries.', ''),
(22, 'de', 'Imperiale Jetpack Truppe', '', ''),
(23, 'en', 'Imperial Scout Trooper', 'The Imperial Scout Troopers are specialized stormtroopers who place special emphasis on speed and agility. Therefore, they wear lighter and slimmer armor compared to regular stormtroopers. The main task of this unit is to conduct patrols and perform reconnaissance missions.', ''),
(23, 'de', 'Imperiale Aufklärungstruppe', '', ''),
(24, 'en', 'Rebel Trooper', '', ''),
(24, 'de', 'Rebellentruppe', '', ''),
(25, 'en', 'Rebel Commando Trooper', '', ''),
(25, 'de', 'Kommandotruppe der Rebellion', '', ''),
(26, 'en', 'Rebel Fleet Trooper', '', ''),
(26, 'de', 'Flottentruppe der Rebellion', '', ''),
(27, 'en', 'Rebel Snow Trooper', '', ''),
(27, 'de', 'Schneetruppe der Rebellion', '', ''),
(28, 'en', 'Rebel Jetpack Trooper', '', ''),
(28, 'de', 'Jetpack Truppe der Rebellion', '', ''),
(29, 'en', 'Rebel Sniper', '', ''),
(29, 'de', 'Scharfschütze der Rebellion', '', ''),
(30, 'en', 'Rebel Heavy Gunner', '', ''),
(30, 'de', 'Schwerer Schütze der Rebellion', '', '');
-- Gamemodes
INSERT INTO Gamemodes (Image) VALUES
(NULL),
(NULL),
(NULL),
(NULL);
-- Gamemode Descriptions
INSERT INTO GamemodeDescription (GamemodeId, Code, Name, Description, Mechanic) VALUES
(1, 'en', 'Skirmish', 'The game mode "Skirmish" is the most common of all and focuses on the battle between armies. In this mode, 2-4 armies compete against each other, aiming to eliminate as many units from the opponents field as possible. The game mode is round-limited. The winner is the player who has the most power points remaining on the field at the end.', ''),
(1, 'de', 'Gefecht', 'Der Spielmodus "Gefecht" ist der häufigste aller Spielmodi und konzentriert sich auf die Schlacht zwischen Armeen. In diesem Modus treten 2-4 Armeen gegeneinander an und versuchen so viele Einheiten wie möglich auf dem Spielfeld des Gegners zu eliminieren. Der Spielmodus ist rundenbegrenzt. Der Gewinner ist der Spieler, der am Ende die meisten Macht-Punkte auf dem Spielfeld übrig hat.', ''),
(2, 'en', 'Extraction', 'The game mode "Escape" revolves around both players escorting a VIP and trying to get them out of the area. The conflict arises because there is only one extraction point. It is also possible to eliminate the enemys VIP from the game. Once the enemy VIP has been eliminated or your own VIP has escaped through the evacuation zone, the player wins.', ''),
(2, 'de', 'Flucht', 'Der Spielmodus "Flucht" dreht sich darum, dass beide Spieler einen VIP begleiten und versuchen ihn aus dem Gebiet herauszubringen. Der Konflikt entsteht, weil es nur einen Evakuierungspunkt gibt. Es ist auch möglich, den gegnerischen VIP aus dem Spiel zu eliminieren. Sobald der gegnerische VIP eliminiert wurde oder der eigene VIP durch die Evakuierungszone entkommen ist, gewinnt der Spieler.', ''),
(3, 'en', 'Assault', 'In the game mode "Assault," the objective is to occupy or defend three defense sectors in succession. The attacker begins with their entire army on one side, while the defender distributes their units across the defense sectors. The attacker has six rounds to capture each sector. The round limit resets after a successful capture. The attacker wins after capturing the third sector, while the defender wins if the round limit expires at any of the sectors.', ''),
(3, 'de', 'Ansturm', 'Im Spielmodus "Angriff" besteht das Ziel darin, drei Verteidigungssektoren nacheinander einzunehmen oder zu verteidigen. Der Angreifer startet mit seiner gesamten Armee auf einer Seite, während der Verteidiger seine Einheiten auf die Verteidigungssektoren verteilt. Der Angreifer hat sechs Runden Zeit, um jeden Sektor zu erobern. Die Rundenbegrenzung setzt sich nach einer erfolgreichen Eroberung zurück. Der Angreifer gewinnt, nachdem er den dritten Sektor erobert hat, während der Verteidiger gewinnt, wenn die Rundenbegrenzung in einem der Sektoren abläuft.', ''),
(4, 'en', 'Domination', 'In the game mode "Domination," there are three zones that each side must control.', ''),
(4, 'de', 'Herrschaft', 'Im Spielmodus "Herrschaft" gibt es drei Zonen, die jede Seite kontrollieren muss.', '');
-- Permissions
INSERT INTO Permissions (PermissionId, Identifier) VALUES
(1, 'ADD_USERS'),
(2, 'ADD_UNITS'),
(3, 'ADD_WEAPONS'),
(4, 'ADD_FRACTIONS'),
(5, 'ADD_GAMEMODES'),
(6, 'VIEW_USERS'),
(7, 'VIEW_UNITS'),
(8, 'VIEW_WEAPONS'),
(9, 'VIEW_FRACTIONS'),
(10, 'VIEW_GAMEMODES'),
(11, 'EDIT_USERS'),
(12, 'EDIT_UNITS'),
(13, 'EDIT_WEAPONS'),
(14, 'EDIT_FRACTIONS'),
(15, 'EDIT_GAMEMODES'),
(16, 'DELETE_USERS'),
(17, 'DELETE_UNITS'),
(18, 'DELETE_WEAPONS'),
(19, 'DELETE_FRACTIONS'),
(20, 'DELETE_GAMEMODES');
-- Permission Descriptions
INSERT INTO PermissionDescription (PermissionId, Code, Name, Description) VALUES
(1, 'en', 'Add Users', ''),
(1, 'de', 'Benutzer erstellen', ''),
(2, 'en', 'Add Units', ''),
(2, 'de', 'Einheiten erstellen', ''),
(3, 'en', 'Add Weapons', ''),
(3, 'de', 'Waffen erstellen', ''),
(4, 'en', 'Add Fractions', ''),
(4, 'de', 'Fraktionen erstellen', ''),
(5, 'en', 'Add Gamemodes', ''),
(5, 'de', 'Spielmodi erstellen', ''),
(6, 'en', 'View Users', ''),
(6, 'de', 'Benutzer einsehen', ''),
(7, 'en', 'View Units', ''),
(7, 'de', 'Einheiten einsehen', ''),
(8, 'en', 'View Weapons', ''),
(8, 'de', 'Waffen einsehen', ''),
(9, 'en', 'View Fractions', ''),
(9, 'de', 'Fraktionen einsehen', ''),
(10, 'en', 'View Gamemodes', ''),
(10, 'de', 'Spielmodi einsehen', ''),
(11, 'en', 'Edit Users', ''),
(11, 'de', 'Benutzer bearbeiten', ''),
(12, 'en', 'Edit Units', ''),
(12, 'de', 'Einheiten bearbeiten', ''),
(13, 'en', 'Edit Weapons', ''),
(13, 'de', 'Waffen bearbeiten', ''),
(14, 'en', 'Edit Fractions', ''),
(14, 'de', 'Fraktionen bearbeiten', ''),
(15, 'en', 'Edit Gamemodes', ''),
(15, 'de', 'Spielmodi bearbeiten', ''),
(16, 'en', 'Delete Users', ''),
(16, 'de', 'Benutzer löschen', ''),
(17, 'en', 'Delete Units', ''),
(17, 'de', 'Einheiten löschen', ''),
(18, 'en', 'Delete Weapons', ''),
(18, 'de', 'Waffen löschen', ''),
(19, 'en', 'Delete Fractions', ''),
(19, 'de', 'Fraktionen löschen', ''),
(20, 'en', 'Delete Gamemodes', ''),
(20, 'de', 'Spielmodi löschen', '');
-- Users
INSERT INTO Users (Username, DisplayName, Description, MainFractionId, Password, Salt, LastLogin, Image) VALUES
('Admin', 'Administrator', NULL, NULL, 'AQAAAAIAAYagAAAAECDOJwBpi0sqraVzpbiMs47xjH/gr8+/QcCClDnZ2oHzn1xA1jmU50ym+jODGjAHiQ==', '5Vjt5j4785', NULL, NULL);
-- User Permissions
INSERT INTO UserPermissions (UserId, PermissionId) VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4),
(1, 5),
(1, 6),
(1, 7),
(1, 8),
(1, 9),
(1, 10),
(1, 11),
(1, 12),
(1, 13),
(1, 14),
(1, 15),
(1, 16),
(1, 17),
(1, 18),
(1, 19),
(1, 20);
-- Activate UNIQUE_CHECKS and FOREIGN_KEY_CHECKS
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
ALTER DATABASE tabletop SET MULTI_USER;
ALTER DATABASE tabletop SET CONSTRAINT ALL IMMEDIATE;

511
MSSQL_INSTALL.sql Normal file
View File

@@ -0,0 +1,511 @@
IF EXISTS (SELECT * FROM sys.schemas WHERE name = 'Tabletop')
DROP SCHEMA Tabletop;
CREATE SCHEMA Tabletop AUTHORIZATION dbo;
USE Tabletop;
-- -----------------------------------------------------
-- Table 'Fractions'
-- -----------------------------------------------------
CREATE TABLE Fractions
(
FractionId INT IDENTITY(1,1),
Image VARBINARY(MAX) NULL,
PRIMARY KEY (FractionId)
);
-- -----------------------------------------------------
-- Table 'FractionDescription'
-- -----------------------------------------------------
CREATE TABLE FractionDescription (
FractionId INT NOT NULL,
Code VARCHAR(5) NOT NULL DEFAULT '',
Name VARCHAR(50) NOT NULL,
ShortName VARCHAR(5) NOT NULL,
Description TEXT NULL,
PRIMARY KEY (FractionId, Code),
FOREIGN KEY (FractionId) REFERENCES Fractions(FractionId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Gamemodes'
-- -----------------------------------------------------
CREATE TABLE Gamemodes
(
GamemodeId INT IDENTITY(1,1),
Image VARBINARY(MAX) NULL,
PRIMARY KEY (GamemodeId)
);
-- -----------------------------------------------------
-- Table 'GamemodeDescription'
-- -----------------------------------------------------
CREATE TABLE GamemodeDescription (
GamemodeId INT NOT NULL,
Code VARCHAR(5) NOT NULL DEFAULT '',
Name VARCHAR(50) NOT NULL,
Description TEXT NULL,
Mechanic TEXT NULL,
PRIMARY KEY (GamemodeId, Code),
FOREIGN KEY (GamemodeId) REFERENCES Gamemodes(GamemodeId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Weapons'
-- -----------------------------------------------------
CREATE TABLE Weapons
(
WeaponId INT IDENTITY(1,1),
Attack INT NOT NULL,
Quality INT NOT NULL,
Range INT NOT NULL,
Dices INT NOT NULL,
Image VARBINARY(MAX) NULL,
PRIMARY KEY (WeaponId)
);
-- -----------------------------------------------------
-- Table 'WeaponDescription'
-- -----------------------------------------------------
CREATE TABLE WeaponDescription (
WeaponId INT NOT NULL,
Code VARCHAR(5) NOT NULL DEFAULT '',
Name VARCHAR(50) NOT NULL,
Description TEXT NULL,
PRIMARY KEY (WeaponId, Code),
FOREIGN KEY (WeaponId) REFERENCES Weapons(WeaponId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Classes'
-- -----------------------------------------------------
CREATE TABLE Classes (
ClassId INT NOT NULL,
Quantity INT NOT NULL,
PRIMARY KEY (ClassId)
);
-- -----------------------------------------------------
-- Table 'ClassDescription'
-- -----------------------------------------------------
CREATE TABLE ClassDescription (
ClassId INT NOT NULL,
Code VARCHAR(5) NOT NULL DEFAULT '',
Name VARCHAR(50) NOT NULL,
Description TEXT NULL,
PRIMARY KEY (ClassId, Code),
FOREIGN KEY (ClassId) REFERENCES Classes(ClassId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Abilities'
-- -----------------------------------------------------
CREATE TABLE Abilities (
AbilityId INT,
Quality INT NOT NULL,
Force INT NOT NULL,
PRIMARY KEY (AbilityId)
);
-- -----------------------------------------------------
-- Table 'AbilityDescription'
-- -----------------------------------------------------
CREATE TABLE AbilityDescription (
AbilityId INT NOT NULL,
Code VARCHAR(5) NOT NULL DEFAULT '',
Name VARCHAR(50) NOT NULL,
Description TEXT NULL,
Mechanic TEXT NULL,
PRIMARY KEY (AbilityId, Code),
FOREIGN KEY (AbilityId) REFERENCES Abilities(AbilityId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Units'
-- -----------------------------------------------------
CREATE TABLE Units
(
UnitId INT IDENTITY(1,1),
FractionId INT NOT NULL,
ClassId INT NOT NULL,
TroopQuantity INT NOT NULL,
Defense INT NOT NULL,
Moving INT NOT NULL,
PrimaryWeaponId INT NULL,
SecondaryWeaponId INT NULL,
FirstAbilityId INT NULL,
SecondAbilityId INT NULL,
Image VARBINARY(MAX) NULL,
PRIMARY KEY (UnitId),
FOREIGN KEY (FractionId) REFERENCES Fractions(FractionId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (ClassId) REFERENCES Classes(ClassId),
FOREIGN KEY (PrimaryWeaponId) REFERENCES Weapons(WeaponId),
FOREIGN KEY (SecondaryWeaponId) REFERENCES Weapons(WeaponId),
FOREIGN KEY (FirstAbilityId) REFERENCES Abilities(AbilityId),
FOREIGN KEY (SecondAbilityId) REFERENCES Abilities(AbilityId)
);
-- -----------------------------------------------------
-- Table 'UnitDescription'
-- -----------------------------------------------------
CREATE TABLE UnitDescription (
UnitId INT NOT NULL,
Code VARCHAR(5) NOT NULL DEFAULT '',
Name VARCHAR(50) NOT NULL,
Description TEXT NULL,
Mechanic TEXT NULL,
PRIMARY KEY (UnitId, Code),
FOREIGN KEY (UnitId) REFERENCES Units(UnitId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Users'
-- -----------------------------------------------------
CREATE TABLE Users (
UserId INT IDENTITY(1,1) PRIMARY KEY,
Username VARCHAR(50) NOT NULL,
DisplayName VARCHAR(100) NOT NULL,
Description TEXT NULL,
MainFractionId INT NULL,
Password VARCHAR(255) NOT NULL,
Salt VARCHAR(255) NOT NULL,
LastLogin DATETIME,
RegistrationDate TIMESTAMP,
Image VARBINARY(MAX) NULL,
PRIMARY KEY (UserId),
FOREIGN KEY (MainFractionId) REFERENCES Fractions(FractionId)
);
-- -----------------------------------------------------
-- Table 'Permissions'
-- -----------------------------------------------------
CREATE TABLE Permissions (
PermissionId INT IDENTITY(1,1),
Identifier VARCHAR(50) NOT NULL,
PRIMARY KEY (PermissionId)
);
-- -----------------------------------------------------
-- Table 'PermissionDescription'
-- -----------------------------------------------------
CREATE TABLE PermissionDescription (
PermissionId INT NOT NULL,
Code VARCHAR(5) NOT NULL DEFAULT '',
Name VARCHAR(50) NOT NULL,
Description TEXT NULL,
PRIMARY KEY (PermissionId, Code),
FOREIGN KEY (PermissionId) REFERENCES Permissions(PermissionId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'UserPermissions'
-- -----------------------------------------------------
CREATE TABLE UserPermissions (
UserId INT NOT NULL,
PermissionId INT NOT NULL,
PRIMARY KEY(UserId, PermissionId),
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (PermissionId) REFERENCES Permissions(PermissionId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'UserUnits'
-- -----------------------------------------------------
CREATE TABLE UserUnits
(
UserId INT NOT NULL,
UnitId INT NOT NULL,
Quantity INT NOT NULL,
PRIMARY KEY (UserId, UnitId),
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (UnitId) REFERENCES Units(UnitId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'UserFriends'
-- -----------------------------------------------------
CREATE TABLE UserFriends
(
UserId INT NOT NULL,
FriendId INT NOT NULL,
PRIMARY KEY (UserId, FriendId),
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (FriendId) REFERENCES Users(UserId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Games'
-- -----------------------------------------------------
CREATE TABLE Games
(
GameId INT IDENTITY(1,1) PRIMARY KEY,
GamemodeId INT NOT NULL,
UserId INT NOT NULL,
SurfaceId INT NOT NULL,
LayoutId INT NOT NULL,
Name VARCHAR(50) NOT NULL,
Force INT NOT NULL,
NumberOfRounds INT NOT NULL,
NumberOfTeams INT NOT NULL,
NumberOfPlayers INT NOT NULL,
Date DATETIME NOT NULL,
FOREIGN KEY (GamemodeId) REFERENCES Gamemodes(GamemodeId)
FOREIGN KEY (SurfaceId) REFERENCES Surfaces(SurfaceId),
FOREIGN KEY (LayoutId) REFERENCES Layouts(LayoutId)
);
-- -----------------------------------------------------
-- Table 'Players'
-- -----------------------------------------------------
CREATE TABLE Players
(
PlayerId INT IDENTITY(1,1) PRIMARY KEY,
UserId INT NOT NULL,
GameId INT NOT NULL,
FractionId INT NULL,
TeamId INT NOT NULL,
Color VARCHAR(7) NOT NULL,
UsedForce INT NOT NULL,
Points INT NOT NULL DEFAULT 0,
Order INT NOT NULL,
StartZone INT NOT NULL,
NumberOfEliminations INT NOT NULL DEFAULT 0,
ForcePointsOfEliminations INT NOT NULL DEFAULT 0,
NumberOfRemainingUnits INT NOT NULL DEFAULT 0,
OwnForcePointsLeft INT NOT NULL DEFAULT 0,
FOREIGN KEY (UserId) REFERENCES Users(UserId),
FOREIGN KEY (GameId) REFERENCES Games(GameId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (FractionId) REFERENCES Fractions(FractionId)
);
-- -----------------------------------------------------
-- Table 'PlayerUnits'
-- -----------------------------------------------------
CREATE TABLE PlayerUnits
(
PlayerId INT NOT NULL,
UnitId INT NOT NULL,
Quantity INT NOT NULL,
PRIMARY KEY (PlayerId, UnitId),
FOREIGN KEY (PlayerId) REFERENCES Players(PlayerId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (UnitId) REFERENCES Units(UnitId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Teams'
-- -----------------------------------------------------
CREATE TABLE Teams
(
TeamId INT IDENTITY(1,1) PRIMARY KEY,
GameId INT NOT NULL,
Name VARCHAR(50) NOT NULL,
Color VARCHAR(7) NOT NULL,
Points INT NOT NULL DEFAULT 0,
NumberOfEliminations INT NOT NULL DEFAULT 0,
ForcePointsOfEliminations INT NOT NULL DEFAULT 0,
FOREIGN KEY (GameId) REFERENCES Games(GameId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Moves'
-- -----------------------------------------------------
CREATE TABLE Moves
(
MoveId INT IDENTITY(1,1) PRIMARY KEY,
PlayerId INT NOT NULL,
GameId INT NOT NULL,
TurnNr INT NOT NULL,
MoveNr INT NOT NULL,
StartMove DATETIME,
EndMove DATETIME,
FOREIGN KEY (PlayerId) REFERENCES Players(PlayerId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (GameId) REFERENCES Games(GameId) ON UPDATE CASCADE,
);
-- -----------------------------------------------------
-- Table 'Captures'
-- -----------------------------------------------------
CREATE TABLE Captures
(
CaptureId INT IDENTITY(1,1) PRIMARY KEY,
MoveId INT NOT NULL,
CaptureZoneNr INT NOT NULL,
TeamId INT NOT NULL,
PlayerId INT NOT NULL,
NrOfTurns INT NOT NULL,
PointsReceived INT NOT NULL,
FOREIGN KEY (MoveId) REFERENCES Moves(MoveId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (TeamId) REFERENCES Teams(TeamId),
FOREIGN KEY (PlayerId) REFERENCES Players(PlayerId)
);
-- -----------------------------------------------------
-- Table 'Eliminations'
-- -----------------------------------------------------
CREATE TABLE Eliminations
(
EliminationId INT IDENTITY(1,1) PRIMARY KEY,
MoveId INT NOT NULL,
TeamId INT NOT NULL,
AtkId INT NOT NULL,
AtkUnitId INT NOT NULL,
AtkUnitNr INT NOT NULL,
DefId INT NOT NULL,
DefUnitId INT NOT NULL,
DefUnitNr INT NOT NULL,
EliminationNr INT NOT NULL,
ForcePointsOfEliminations INT NOT NULL,
FOREIGN KEY (TeamId) REFERENCES Teams(TeamId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (MoveId) REFERENCES Moves(MoveId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (AtkId) REFERENCES Players(PlayerId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (AtkUnitId) REFERENCES Units(UnitId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (DefId) REFERENCES Players(PlayerId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (DefUnitId) REFERENCES Units(UnitId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Templates'
-- -----------------------------------------------------
CREATE TABLE Templates
(
TemplateId INT IDENTITY(1,1) PRIMARY KEY,
UserId INT NOT NULL,
FractionId INT NOT NULL,
Name VARCHAR(50) NOT NULL,
Force INT NOT NULL,
UsedForce INT NOT NULL,
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (FractionId) REFERENCES Fractions(FractionId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'TemplateUnits'
-- -----------------------------------------------------
CREATE TABLE TemplateUnits
(
TemplateId INT PRIMARY KEY,
UnitId INT PRIMARY KEY,
Quantity INT NOT NULL,
PRIMARY KEY (TemplateId, UnitId),
FOREIGN KEY (TemplateId) REFERENCES Templates(TemplateId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (UnitId) REFERENCES Units(UnitId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Layouts'
-- -----------------------------------------------------
CREATE TABLE Layouts
(
LayoutId INT IDENTITY(1,1) PRIMARY KEY,
GamemodeId INT NOT NULL,
Teams INT NOT NULL,
Width FLOAT NOT NULL CHECK (Width > 0),
Height FLOAT NOT NULL CHECK (Height > 0),
FOREIGN KEY (GamemodeId) REFERENCES Gamemodes(GamemodeId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Zones'
-- -----------------------------------------------------
CREATE TABLE Zones (
ZoneId INT IDENTITY(1,1) PRIMARY KEY,
X FLOAT NOT NULL CHECK (X >= 0),
Y FLOAT NOT NULL CHECK (Y >= 0),
Width FLOAT NOT NULL CHECK (Width > 0),
Height FLOAT NOT NULL CHECK (Height > 0)
);
-- -----------------------------------------------------
-- Table 'SetupZones'
-- -----------------------------------------------------
CREATE TABLE SetupZones (
SetupZoneId INT NOT NULL,
LayoutId INT NOT NULL,
ZoneId INT NOT NULL,
PRIMARY KEY (SetupZoneId, LayoutId),
FOREIGN KEY (ZoneId) REFERENCES Zones(ZoneId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (LayoutId) REFERENCES Layouts(LayoutId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'CaptureZones'
-- -----------------------------------------------------
CREATE TABLE CaptureZones (
CaptureZoneId INT NOT NULL,
LayoutId INT NOT NULL,
ZoneId INT NOT NULL,
PointsPerRound INT NOT NULL,
PRIMARY KEY (CaptureZoneId, LayoutId),
FOREIGN KEY (ZoneId) REFERENCES Zones(ZoneId) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (LayoutId) REFERENCES Layouts(LayoutId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'Surfaces'
-- -----------------------------------------------------
CREATE TABLE Surfaces (
SurfaceId INT IDENTITY(1,1) PRIMARY KEY,
Image VARBINARY(MAX) NULL
);
-- -----------------------------------------------------
-- Table 'SurfaceDescription'
-- -----------------------------------------------------
CREATE TABLE SurfaceDescription (
SurfaceId INT NOT NULL,
Code VARCHAR(5) NOT NULL DEFAULT '',
Name VARCHAR(50) NOT NULL,
Description TEXT NULL
PRIMARY KEY (SurfaceId, Code),
FOREIGN KEY (SurfaceId) REFERENCES Surfaces(SurfaceId) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table 'ConnectionLogs'
-- -----------------------------------------------------
CREATE TABLE ConnectionLogs
(
ConnectionId INT IDENTITY(1,1) PRIMARY KEY,
IpAddress VARCHAR(50) NOT NULL,
ConnectionTime DATETIME,
UserAgent VARCHAR(300) NOT NULL,
Referrer VARCHAR(300) NOT NULL,
RequestedUrl VARCHAR(300) NOT NULL,
Geolocation VARCHAR(100) NOT NULL,
SessionId VARCHAR(50) NOT NULL,
StatusCode INT NOT NULL,
DeviceType VARCHAR(50) NOT NULL,
OperatingSystem VARCHAR(50) NOT NULL,
);

384
MYSQL_DATA.sql Normal file
View File

@@ -0,0 +1,384 @@
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
USE `tabletop`;
#
# Dumping data for table 'tabletop'.'fractions'
#
INSERT INTO `tabletop`.`fractions` (`fraction_id`, `image`) VALUES
(1, NULL),
(2, NULL),
(3, NULL),
(4, NULL);
# 4 records
#
# Dumping data for table 'tabletop'.'fraction_description'
#
INSERT INTO `tabletop`.`fraction_description` (`fraction_id`, `code`, `name`, `short_name`, `description`) VALUES
(1, 'en', 'Grand Army of the Republic', 'GAR', 'The Grand Army of the Republic is the main military force of the Galactic Republic. It was formed during the Clone Wars to combat the Separatists. The army consists primarily of clone troopers, who were created using the genetic material of Jango Fett as a basis. It encompasses a variety of troops, including infantry, artillery, and special forces, as well as an extensive fleet of starships. The Grand Army of the Republic is renowned for its effective organization, disciplined training, and tactical prowess.'),
(1, 'de', 'Große Armee der Republik', 'GAR', 'Die Große Armee der Republik ist die Hauptstreitmacht der Galaktischen Republik. Sie wurde während der Klonkriege eingesetzt, um die Separatisten zu bekämpfen. Die Armee besteht hauptsächlich aus Klontruppen, die mit dem genetischen Material von Jango Fett als Grundlage geschaffen wurden. Sie umfasst eine Vielzahl von Truppentypen, darunter Infanterie, Artillerie und Spezialeinheiten, sowie eine umfangreiche Flotte von Raumschiffen. Die Große Armee der Republik ist bekannt für ihre effektive Organisation, disziplinierte Ausbildung und taktische Geschicklichkeit.'),
(2, 'en', 'Confederacy of Independent Systems', 'CIS', 'The Confederation of Independent Systems (CIS) is a military alliance in the galaxy, consisting of renegade planets and organizations that have distanced themselves from the rule of the Galactic Republic. The CIS possesses an impressive military force, primarily composed of droid armies. These automated combat droids are produced in large numbers and pose a dangerous threat due to their numerical superiority.'),
(2, 'de', 'Konföderation der unabhängigen Systeme', 'KUS', 'Die Konföderation unabhängiger Systeme (KUS) ist ein militärisches Bündnis in der Galaxis, bestehend aus abtrünnigen Planeten und Organisationen, die sich von der Herrschaft der Galaktischen Republik distanziert haben. Die KUS verfügt über eine beeindruckende Militärmacht, die hauptsächlich aus Droidenarmeen besteht. Diese automatisierten Kampfdroiden werden in großen Mengen produziert und stellen aufgrund ihrer zahlenmäßigen Überlegenheit eine gefährliche Bedrohung dar.'),
(3, 'en', 'Galactic Empire', 'EMP', 'The Galactic Empire is a military power in the galaxy that emerged after the fall of the Galactic Republic. It possesses a massive military force based on the suppression and control of planets and systems. The Empire employs a variety of troop types. It is renowned for its iron discipline, rigorous training, and ruthless efficiency. It seeks galaxy-wide dominance and pursues a policy of fear and subjugation.'),
(3, 'de', 'Galaktisches Imperium', 'IMP', 'Das Galaktische Imperium ist eine militärische Macht in der Galaxie, die nach dem Fall der Galaktischen Republik entstand. Es verfügt über eine massive Militärmacht, die auf der Unterdrückung und Kontrolle von Planeten und Systemen basiert. Das Imperium setzt verschiedene Arten von Truppen ein. Es ist bekannt für seine eiserne Disziplin, strenge Ausbildung und rücksichtslose Effizienz. Es strebt die Vorherrschaft in der gesamten Galaxie an und verfolgt eine Politik der Angst und Unterwerfung.'),
(4, 'en', 'Rebellion', 'REB', 'The Rebellion is a military resistance movement that fights against the rule of the Galactic Empire. It consists of various factions and planets that rise up against oppression. The Rebellion employs asymmetric warfare and guerrilla tactics to inflict damage upon the Empire. Its forces include both regular troops and partisan units. The Rebellion is known for its determination, belief in freedom, and ability to mobilize resources to challenge the Empire. It also utilizes stolen or improvised starships and weapons to carry out its operations.'),
(4, 'de', 'Rebellion', 'REB', 'Der Widerstand ist eine militärische Widerstandsbewegung, die gegen die Herrschaft des Galaktischen Imperiums kämpft. Er besteht aus verschiedenen Fraktionen und Planeten, die sich gegen die Unterdrückung erheben. Der Widerstand setzt asymmetrische Kriegsführung und Guerillataktiken ein, um dem Imperium Schaden zuzufügen. Seine Streitkräfte umfassen sowohl reguläre Truppen als auch Partisaneneinheiten. Der Widerstand ist bekannt für seine Entschlossenheit, seinen Glauben an die Freiheit und seine Fähigkeit, Ressourcen zu mobilisieren, um das Imperium herauszufordern. Er verwendet auch gestohlene oder improvisierte Raumschiffe und Waffen, um seine Operationen durchzuführen.');
# 8 records
#
# Dumping data for table 'tabletop'.'classes'
#
INSERT INTO `tabletop`.`classes` (`class_id`, `quantity`) VALUES
(1, 0),
(2, 0),
(3, 0);
# 3 records
#
# Dumping data for table 'tabletop'.'class_description'
#
INSERT INTO `tabletop`.`class_description` (`class_id`, `code`, `name`, `description`) VALUES
(1, 'en', 'Regular', ''),
(1, 'de', 'Regulär', ''),
(2, 'en', 'Elite', ''),
(2, 'de', 'Elite', ''),
(3, 'en', 'Specialist', ''),
(3, 'de', 'Spezialist', '');
# 6 records
#
# Dumping data for table 'tabletop'.'abilities'
#
INSERT INTO `tabletop`.`abilities` (`ability_id`, `quality`, `force`) VALUES
(1, 1, 0),
(2, 0, 1);
# 2 records
#
# Dumping data for table 'tabletop'.'ability_description'
#
INSERT INTO `tabletop`.`ability_description` (`ability_id`, `code`, `name`, `description`) VALUES
(1, 'en', '', ''),
(1, 'de', '', ''),
(2, 'en', '', ''),
(2, 'de', '', '');
# 4 records
#
# Dumping data for table 'tabletop'.'weapons'
#
INSERT INTO `tabletop`.`weapons` (`weapon_id`, `attack`, `quality`, `range`, `dices`) VALUES
(1, 5, 5, 60, 1),
(2, 6, 5, 45, 1),
(3, 9, 7, 80, 1),
(4, 4, 4, 30, 1),
(5, 7, 4, 30, 6),
(6, 6, 4, 40, 1),
(7, 10, 7, 80, 1),
(8, 6, 5, 45, 1),
(9, 6, 4, 50, 2),
(10, 7, 4, 30, 4),
(11, 5, 5, 45, 1),
(12, 7, 4, 60, 1),
(13, 9, 7, 80, 1),
(14, 4, 4, 45, 2),
(15, 8, 4, 35, 1),
(16, 4, 4, 30, 1);
# 16 records
#
# Dumping data for table 'tabletop'.'weapon_description'
#
INSERT INTO `tabletop`.`weapon_description` (`weapon_id`, `code`, `name`, `description`) VALUES
(1, 'en', 'DC-15A Blaster Rifle', ''),
(1, 'de', 'DC-15A Blastergewehr', ''),
(2, 'en', 'DC-15S Blaster Carbine', ''),
(2, 'de', 'DC-15S Blasterkarabiner', ''),
(3, 'en', 'DC-15x Sniper Rifle', ''),
(3, 'de', 'DC-15x Scharfschützengewehr', ''),
(4, 'en', 'DC-17 Hand Blaster', ''),
(4, 'de', 'DC-17 Handblaster', ''),
(5, 'en', 'Z-6 Rotary Blaster Cannon', ''),
(5, 'de', 'Z-6 Rotationsblaster', ''),
(6, 'en', 'E-5 Blaster Rifle', ''),
(6, 'de', 'E-5 Blastergewehr', ''),
(7, 'en', 'E-5s Sniper Rifle', ''),
(7, 'de', 'E-5s Scharfschützengewehr', ''),
(8, 'en', 'RG-4D Blaster Pistol', ''),
(8, 'de', 'RG-4D Blasterpistole', ''),
(9, 'en', 'Wrist Blaster', ''),
(9, 'de', 'Dual-Blaster', ''),
(10, 'en', 'Droideka', ''),
(10, 'de', 'Droideka', ''),
(11, 'en', 'E-11 Blaster Rifle', ''),
(11, 'de', 'E-11 Blastergewehr', ''),
(12, 'en', 'DLT-19 Heavy Blaster Rifle', ''),
(12, 'de', 'DLT-19 Schweres Blastergewehr', ''),
(13, 'en', 'E-11s Sniper Rifle', ''),
(13, 'de', 'E-11s Scharfschützengewehr', ''),
(14, 'en', 'E-22 Blaster Rifle', ''),
(14, 'de', 'E-22 Blastergewehr', ''),
(15, 'en', 'E-11D Blaster Rifle', ''),
(15, 'de', 'E-11D Blastergewehr', ''),
(16, 'en', 'SE-14 Hand Blaster', ''),
(16, 'de', 'SE-14 Handblaster', '');
# 32 records
#
# Dumping data for table 'tabletop'.'units'
#
INSERT INTO `tabletop`.`units` (`unit_id`, `fraction_id`, `class_id`, `troop_quantity`, `defense`, `moving`, `primary_weapon_id`, `secondary_weapon_id`, `ability_id`, `has_jetpack`, `image`) VALUES
(1, 1, 1, 6, 6, 16, 2, NULL, NULL, FALSE, NULL),
(2, 1, 1, 6, 5, 16, 1, NULL, NULL, FALSE, NULL),
(3, 1, 1, 6, 6, 16, 1, NULL, NULL, FALSE, NULL),
(4, 1, 1, 6, 6, 16, 2, NULL, NULL, FALSE, NULL),
(5, 1, 1, 6, 6, 16, 2, NULL, 1, FALSE, NULL),
(6, 1, 2, 4, 6, 22, 4, 4, NULL, TRUE, NULL),
(7, 1, 2, 4, 6, 22, 2, NULL, NULL, TRUE, NULL),
(8, 1, 3, 1, 7, 10, 5, NULL, NULL, FALSE, NULL),
(9, 1, 3, 1, 6, 14, 3, 4, NULL, FALSE, NULL),
(10, 2, 1, 8, 2, 12, 6, NULL, NULL, FALSE, NULL),
(11, 2, 1, 6, 5, 14, 9, NULL, NULL, FALSE, NULL),
(12, 2, 2, 4, 6, 17, 6, NULL, 1, FALSE, NULL),
(13, 2, 2, 6, 5, 14, 9, NULL, NULL, FALSE, NULL),
(14, 2, 2, 6, 2, 22, 6, NULL, NULL, TRUE, NULL),
(15, 2, 3, 1, 10, 22, 15, NULL, NULL, FALSE, NULL),
(16, 2, 3, 1, 2, 12, 6, 8, NULL, FALSE, NULL),
(17, 3, 1, 6, 5, 16, 11, NULL, NULL, FALSE, NULL),
(18, 3, 1, 6, 5, 15, 11, NULL, NULL, FALSE, NULL),
(19, 3, 2, 4, 7, 15, 15, NULL, NULL, FALSE, NULL),
(20, 3, 1, 6, 5, 16, 14, NULL, NULL, FALSE, NULL),
(21, 3, 2, 4, 6, 14, 12, NULL, NULL, FALSE, NULL),
(22, 3, 2, 4, 5, 22, 11, NULL, NULL, TRUE, NULL),
(23, 3, 3, 1, 4, 15, 13, 16, NULL, FALSE, NULL),
(24, 4, 1, 6, 4, 16, 11, NULL, NULL, FALSE, NULL),
(25, 4, 2, 4, 4, 16, 11, NULL, NULL, FALSE, NULL),
(26, 4, 1, 6, 4, 16, 11, NULL, NULL, FALSE, NULL),
(27, 4, 1, 6, 4, 16, 11, NULL, NULL, FALSE, NULL),
(28, 4, 2, 4, 4, 16, 11, NULL, NULL, TRUE, NULL),
(29, 4, 3, 1, 4, 16, 11, NULL, NULL, FALSE, NULL),
(30, 4, 3, 1, 4, 16, 11, NULL, NULL, FALSE, NULL);
# 30 records
#
# Dumping data for table 'tabletop'.'unit_description'
#
INSERT INTO `tabletop`.`unit_description` (`unit_id`, `code`, `name`, `description`, `mechanic`)
VALUES
(1, 'en', '41st Ranger Platoon', 'The 41st Ranger Platoon is a clone trooper infantry platoon and strike team within the 41st Elite Corps. The platoon participated in sabotage missions on Separatist supply lines and installations.', ''),
(1, 'de', '41. Ranger Platoon', '', ''),
(2, 'en', '41st Elite Corps', 'The 41st Elite Corps is a military corps of the Grand Army of the Republic. They are specialized in conducting missions on exotic and often primitve worlds. One of their main tasks was to contact and cooperate with the native indigenous population. in addition, the unit was capable of successfully carrying out both infatry and reconnaissance missions.', ''),
(2, 'de', '41. Elitekorps', '', ''),
(3, 'en', '212th Attack Battalion', 'The 212th Attack Battalion is a clone trooper battalion in the Grand Army of the Republic. The Battalion is famous for its bravery, discipline and effectivness in combat. As an extremly versatile infantry unit of the Regular Forces, it is optimally equipped for a variety of different tasks.', ''),
(3, 'de', '212. Angriffsbataillion', '', ''),
(4, 'en', '2nd Airborne Company', 'The 2nd Airborne Company was an aerial infantry company of the 212th Attack Battalion. The company consisted of clone paratroopers and participated in the Clone Wars.', ''),
(4, 'de', '2. Luftlandekompanie', '', ''),
(5, 'en', '501st Legion', 'The 501st Legion is an elite military battalion of clone troopers in the Grand Army of the Galactic Republic. The soldiers of the Legion are known for their courage, unconventional tactics and loyalty to the Republic.', ''),
(5, 'de', '501. Legion', '', ''),
(6, 'en', '501st Jetpack Company', 'The 501st Jetpack Company is an special unit within the 501st Legion whose soldiers are equipped with jetpacks. The jetpack troopers use their jetpacks with limited flight capabilities to quickly cover large distances and gain an advantage over their enemies in the air. Thanks to their exceptional agility, they are hard to hit and can skillfully attack their enemies from behind.', ''),
(6, 'de', '501. Jetpack Kompanie', '', ''),
(7, 'en', '104th Battalion', 'The 104th Battalion, is a military unit within the Galactic Republic. Its primary mission is to perform relief and recovery missions. The battalions distinctive mark is a symbol with a wolfs snout, which is why they are also called the Wolfpack battalion.', ''),
(7, 'de', '104. Bataillion', '', ''),
(8, 'en', '501st Heavy Gunner', 'The 501st Heavy Gunners form an elite special forces unit within the 501st Legion, whose members are equipped with powerful Heavy Guns. These soldiers possess advanced clone trooper armor that offers significant improvements over traditional army clone troopers. Their heavy blasters also give them superior firepower, making them a formidable presence on the battlefield.', ''),
(8, 'de', '501. Schwerer Schütze', '', ''),
(9, 'en', '501st Specialist', 'The 501st Specialist represent an special unit within the 501st Legion. Their expertise lies in precise attacks from a considerable distance, which makes them an extremely dangerous threat. Their primary mission is to conduct patrols and lead reconnaissance missions.', ''),
(9, 'de', '501. Scharfschütze', '', ''),
(10, 'en', 'B1-series Battle Droid', 'The B1 battle droids are developed for mass combat and are known to operate in large numbers against enemy forces. They have a relatively simple artificial intelligence and are often quite clumsy and easy to overcome.', ''),
(10, 'de', 'B1-Serie Kampfdroide', '', ''),
(11, 'en', 'B2-series Super Battle Droid', 'The B2-Series Super Battle Droid is a more advanced and much more durable version of the B1-Series Battle Droid. Equipped with reinforced armor and more advanced weapons, the B2 significantly surpasses its predecessor in terms of combat power. Added to this is its improved artificial intelligence, which makes it an extremely dangerous and efficient combat unit.', ''),
(11, 'de', 'B2-Serie Superkampfdroide', '', ''),
(12, 'en', 'BX-series Commando Droid', 'The BX-series Commando Droid represents a specialized and sophisticated class of combat droids. Their main role is in covert missions and tactical operations. Equipped with advanced cloaking devices that can make them almost invisible, they are capable of carrying out surprise attacks skillfully.', ''),
(12, 'de', 'BX-Serie Kommandodroide', '', ''),
(13, 'en', 'B2-HA series Super Battle Droid', 'The B2-HA Super Battle Droid, also known as the Heavy Assault Super Battle Droid, is a variant of the B2-Series Battle Droid equipped with a missile launcher. Its ability to perform targeted missile attacks makes it an extremely dangerous combat unit in various situations.', ''),
(13, 'de', 'B2-HA Serie Raketenkampfdroide', '', ''),
(14, 'en', 'B1-series Rocket Battle Droid', '', ''),
(14, 'de', 'B1-Serie Jetpack Kampfdroide', '', ''),
(15, 'en', 'Droideka', 'Droidekas are specialized battle droids characterized by their characteristic spherical shape. In this spherical form, they are able to attack, but they are also extremely fast and agile. However, once they assume their attack position, they unfold to reveal their three-legged form. In this upright position, they have devastating energy weapon salvos and are also equipped with powerful energy shields that protect them from enemy fire.', ''),
(15, 'de', 'Droideka', '', ''),
(16, 'en', 'B1-series Sniper Droid', 'The B1-series Sniper Droid is a special variant of the B1-series combat droid. Their gyroscopic stabilizers and detailed target selection programs made them very effective snipers.', ''),
(16, 'de', 'B1-Serie Scharfschützendroide', '', ''),
(17, 'en', 'Imperial Storm Trooper', 'Stormtroopers are the primary unit of the Galactic Empire. In action, the Stormtroopers are capable of a variety of tasks, including counterinsurgency, defense of Imperial installations, as well as assault missions and conducting operations as part of the Galactic Conquest of the Empire.', ''),
(17, 'de', 'Imperialer Sturmtruppe', '', ''),
(18, 'en', 'Imperial Snow Trooper', 'The Snowtroopers are specialized stormtroopers of the Galactic Empire, equipped with special armors designed to protect them from extreme conditions, including cold, ice, and snow. Their main mission is to pursue hostile targets in snow-covered regions.', ''),
(18, 'de', 'Imperialer Schneetruppe', '', ''),
(19, 'en', 'Imperial Death Trooper', 'Death Troopers are elite soldiers assigned to particularly dangerous and clandestine missions. They often serve as bodyguards for high-ranking Imperial officers or perform special tasks where discretion and efficiency are of the utmost importance.', ''),
(19, 'de', 'Imperiale Todestruppe', '', ''),
(20, 'en', 'Imperial Shore Trooper', 'Shore Troopers are specialized stormtroopers equipped with armor optimized for combat in maritime environments. Their main task is to engage hostile forces on beaches or coastlines and secure important locations. Additionally, they are often deployed in the defense of Imperial bases along coastal lines.', ''),
(20, 'de', 'Imperiale Küstentruppe', '', ''),
(21, 'en', 'Imperial Shock Trooper', 'Shock troopers are elite and highly trained Storm Troopers of the Galactic Empire. They are known for wearing red armor. Their role is to maintain imperial order and combat threats to the Empire.', ''),
(21, 'de', 'Imperiale Stocktruppe', '', ''),
(22, 'en', 'Imperial Jetpack Trooper', 'Jetpack Troopers are specialized troops equipped with jetpacks, allowing them to hover in the air and move faster and more agilely in combat, making them dangerous and versatile adversaries.', ''),
(22, 'de', 'Imperiale Jetpack Truppe', '', ''),
(23, 'en', 'Imperial Scout Trooper', 'The Imperial Scout Troopers are specialized stormtroopers who place special emphasis on speed and agility. Therefore, they wear lighter and slimmer armor compared to regular stormtroopers. The main task of this unit is to conduct patrols and perform reconnaissance missions.', ''),
(23, 'de', 'Imperiale Aufklärungstruppe', '', ''),
(24, 'en', 'Rebel Trooper', '', ''),
(24, 'de', 'Rebellentruppe', '', ''),
(25, 'en', 'Rebel Commando Trooper', '', ''),
(25, 'de', 'Kommandotruppe der Rebellion', '', ''),
(26, 'en', 'Rebel Fleet Trooper', '', ''),
(26, 'de', 'Flottentruppe der Rebellion', '', ''),
(27, 'en', 'Rebel Snow Trooper', '', ''),
(27, 'de', 'Schneetruppe der Rebellion', '', ''),
(28, 'en', 'Rebel Jetpack Trooper', '', ''),
(28, 'de', 'Jetpack Truppe der Rebellion', '', ''),
(29, 'en', 'Rebel Sniper', '', ''),
(29, 'de', 'Scharfschütze der Rebellion', '', ''),
(30, 'en', 'Rebel Heavy Gunner', '', ''),
(30, 'de', 'Schwerer Schütze der Rebellion', '', '');
# 60 records
#
# Dumping data for table 'tabletop'.'gamemodes'
#
INSERT INTO `tabletop`.`gamemodes` (`gamemode_id`, `image`) VALUES
(1, NULL),
(2, NULL),
(3, NULL),
(4, NULL);
# 4 records
#
# Dumping data for table 'tabletop'.'gamemode_description'
#
INSERT INTO `tabletop`.`gamemode_description` (`gamemode_id`, `code`, `name`, `description`, `mechanic`) VALUES
(1, 'en', 'Skirmish', 'The game mode "Skirmish" is the most common of all and focuses on the battle between armies. In this mode, 2-4 armies compete against each other, aiming to eliminate as many units from the opponents field as possible. The game mode is round-limited. The winner is the player who has the most power points remaining on the field at the end.', ''),
(1, 'de', 'Gefecht', 'Der Spielmodus "Gefecht" ist der häufigste aller Spielmodi und konzentriert sich auf die Schlacht zwischen Armeen. In diesem Modus treten 2-4 Armeen gegeneinander an und versuchen so viele Einheiten wie möglich auf dem Spielfeld des Gegners zu eliminieren. Der Spielmodus ist rundenbegrenzt. Der Gewinner ist der Spieler, der am Ende die meisten Macht-Punkte auf dem Spielfeld übrig hat.', ''),
(2, 'en', 'Extraction', 'The game mode "Escape" revolves around both players escorting a VIP and trying to get them out of the area. The conflict arises because there is only one extraction point. It is also possible to eliminate the enemys VIP from the game. Once the enemy VIP has been eliminated or your own VIP has escaped through the evacuation zone, the player wins.', ''),
(2, 'de', 'Flucht', 'Der Spielmodus "Flucht" dreht sich darum, dass beide Spieler einen VIP begleiten und versuchen ihn aus dem Gebiet herauszubringen. Der Konflikt entsteht, weil es nur einen Evakuierungspunkt gibt. Es ist auch möglich, den gegnerischen VIP aus dem Spiel zu eliminieren. Sobald der gegnerische VIP eliminiert wurde oder der eigene VIP durch die Evakuierungszone entkommen ist, gewinnt der Spieler.', ''),
(3, 'en', 'Assault', 'In the game mode "Assault," the objective is to occupy or defend three defense sectors in succession. The attacker begins with their entire army on one side, while the defender distributes their units across the defense sectors. The attacker has six rounds to capture each sector. The round limit resets after a successful capture. The attacker wins after capturing the third sector, while the defender wins if the round limit expires at any of the sectors.', ''),
(3, 'de', 'Ansturm', 'Im Spielmodus "Angriff" besteht das Ziel darin, drei Verteidigungssektoren nacheinander einzunehmen oder zu verteidigen. Der Angreifer startet mit seiner gesamten Armee auf einer Seite, während der Verteidiger seine Einheiten auf die Verteidigungssektoren verteilt. Der Angreifer hat sechs Runden Zeit, um jeden Sektor zu erobern. Die Rundenbegrenzung setzt sich nach einer erfolgreichen Eroberung zurück. Der Angreifer gewinnt, nachdem er den dritten Sektor erobert hat, während der Verteidiger gewinnt, wenn die Rundenbegrenzung in einem der Sektoren abläuft.', ''),
(4, 'en', 'Domination', 'In the game mode "Domination," there are three zones that each side must control.', ''),
(4, 'de', 'Herrschaft', 'Im Spielmodus "Herrschaft" gibt es drei Zonen, die jede Seite kontrollieren muss.', '');
# 8 records
#
# Dumping data for table 'tabletop'.'permissions'
#
INSERT INTO `tabletop`.`permissions` (`permission_id`, `identifier`) VALUES
(1, 'ADD_USERS'),
(2, 'ADD_UNITS'),
(3, 'ADD_WEAPONS'),
(4, 'ADD_FRACTIONS'),
(5, 'ADD_GAMEMODES'),
(6, 'VIEW_USERS'),
(7, 'VIEW_UNITS'),
(8, 'VIEW_WEAPONS'),
(9, 'VIEW_FRACTIONS'),
(10, 'VIEW_GAMEMODES'),
(11, 'EDIT_USERS'),
(12, 'EDIT_UNITS'),
(13, 'EDIT_WEAPONS'),
(14, 'EDIT_FRACTIONS'),
(15, 'EDIT_GAMEMODES'),
(16, 'DELETE_USERS'),
(17, 'DELETE_UNITS'),
(18, 'DELETE_WEAPONS'),
(19, 'DELETE_FRACTIONS'),
(20, 'DELETE_GAMEMODES');
# 20 records
#
# Dumping data for table 'tabletop'.'permission_description'
#
INSERT INTO `tabletop`.`permission_description` (`permission_id`, `code`, `name`, `description`) VALUES
(1, 'en', 'Add Users', ''),
(1, 'de', 'Benutzer erstellen', ''),
(2, 'en', 'Add Units', ''),
(2, 'de', 'Einheiten erstellen', ''),
(3, 'en', 'Add Weapons', ''),
(3, 'de', 'Waffen erstellen', ''),
(4, 'en', 'Add Fractions', ''),
(4, 'de', 'Fraktionen erstellen', ''),
(5, 'en', 'Add Gamemodes', ''),
(5, 'de', 'Spielmodi erstellen', ''),
(6, 'en', 'View Users', ''),
(6, 'de', 'Benutzer einsehen', ''),
(7, 'en', 'View Units', ''),
(7, 'de', 'Einheiten einsehen', ''),
(8, 'en', 'View Weapons', ''),
(8, 'de', 'Waffen einsehen', ''),
(9, 'en', 'View Fractions', ''),
(9, 'de', 'Fraktionen einsehen', ''),
(10, 'en', 'View Gamemodes', ''),
(10, 'de', 'Spielmodi einsehen', ''),
(11, 'en', 'Edit Users', ''),
(11, 'de', 'Benutzer bearbeiten', ''),
(12, 'en', 'Edit Units', ''),
(12, 'de', 'Einheiten bearbeiten', ''),
(13, 'en', 'Edit Weapons', ''),
(13, 'de', 'Waffen bearbeiten', ''),
(14, 'en', 'Edit Fractions', ''),
(14, 'de', 'Fraktionen bearbeiten', ''),
(15, 'en', 'Edit Gamemodes', ''),
(15, 'de', 'Spielmodi bearbeiten', ''),
(16, 'en', 'Delete Users', ''),
(16, 'de', 'Benutzer löschen', ''),
(17, 'en', 'Delete Units', ''),
(17, 'de', 'Einheiten löschen', ''),
(18, 'en', 'Delete Weapons', ''),
(18, 'de', 'Waffen löschen', ''),
(19, 'en', 'Delete Fractions', ''),
(19, 'de', 'Fraktionen löschen', ''),
(20, 'en', 'Delete Gamemodes', ''),
(20, 'de', 'Spielmodi löschen', '');
# 40 records
#
# Optional: Adds an admin user
#
#------------------------------------------------------------------
#
#
# Dumping data for table 'users'
#
INSERT INTO `tabletop`.`users` (`user_id`, `username`, `display_name`, `description`, `main_fraction_id`, `password`, `salt`, `last_login`, `image`) VALUES (1, 'Admin', 'Administrator', NULL, NULL, 'AQAAAAIAAYagAAAAECDOJwBpi0sqraVzpbiMs47xjH/gr8+/QcCClDnZ2oHzn1xA1jmU50ym+jODGjAHiQ==', '5Vjt5j4785', '0001-01-01 00:00:00', NULL);
# 1 records
#
# Dumping data for table 'tabletop'.'user_permissions'
#
INSERT INTO `tabletop`.`user_permissions` (`user_id`, `permission_id`) VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4),
(1, 5),
(1, 6),
(1, 7),
(1, 8),
(1, 9),
(1, 10),
(1, 11),
(1, 12),
(1, 13),
(1, 14),
(1, 15),
(1, 16),
(1, 17),
(1, 18),
(1, 19),
(1, 20);
# 20 records
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

329
MYSQL_INSTALL.sql Normal file
View File

@@ -0,0 +1,329 @@
DROP SCHEMA IF EXISTS `tabletop`;
CREATE SCHEMA IF NOT EXISTS `tabletop` DEFAULT CHARACTER SET latin2;
USE `tabletop`;
-- -----------------------------------------------------
-- Table `tabletop`.`fractions`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`fractions`
(
`fraction_id` INTEGER NOT NULL AUTO_INCREMENT,
`image` MEDIUMBLOB NULL,
PRIMARY KEY (`fraction_id`)
);
-- -----------------------------------------------------
-- Table `tabletop`.`fraction_description`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`fraction_description` (
`fraction_id` INTEGER NOT NULL,
`code` VARCHAR(5) NOT NULL DEFAULT '',
`name` VARCHAR(50) NOT NULL,
`short_name` VARCHAR(5) NOT NULL,
`description` TEXT NULL,
PRIMARY KEY (`fraction_id`, `code`),
FOREIGN KEY (`fraction_id`) REFERENCES `tabletop`.`fractions`(`fraction_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`gamemodes`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`gamemodes`
(
`gamemode_id` INTEGER NOT NULL AUTO_INCREMENT,
`image` MEDIUMBLOB NULL,
PRIMARY KEY (`gamemode_id`)
);
-- -----------------------------------------------------
-- Table `tabletop`.`gamemode_description`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`gamemode_description` (
`gamemode_id` INTEGER NOT NULL,
`code` VARCHAR(5) NOT NULL DEFAULT '',
`name` VARCHAR(50) NOT NULL,
`description` TEXT NULL,
`mechanic` TEXT NULL,
PRIMARY KEY (`gamemode_id`, `code`),
FOREIGN KEY (`gamemode_id`) REFERENCES `tabletop`.`gamemodes`(`gamemode_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`weapons`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`weapons`
(
`weapon_id` INTEGER NOT NULL AUTO_INCREMENT,
`attack` INTEGER NOT NULL,
`quality` INTEGER NOT NULL,
`range` INTEGER NOT NULL,
`dices` INTEGER NOT NULL,
`image` MEDIUMBLOB NULL,
PRIMARY KEY (`weapon_id`)
);
-- -----------------------------------------------------
-- Table `tabletop`.`weapon_description`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`weapon_description` (
`weapon_id` INTEGER NOT NULL,
`code` VARCHAR(5) NOT NULL DEFAULT '',
`name` VARCHAR(50) NOT NULL,
`description` TEXT NULL,
PRIMARY KEY (`weapon_id`, `code`),
FOREIGN KEY (`weapon_id`) REFERENCES `tabletop`.`weapons`(`weapon_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`classes`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`classes` (
`class_id` INTEGER NOT NULL,
`quantity` INTEGER NOT NULL,
PRIMARY KEY (`class_id`)
);
-- -----------------------------------------------------
-- Table `tabletop`.`class_description`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`class_description` (
`class_id` INTEGER NOT NULL,
`code` VARCHAR(5) NOT NULL DEFAULT '',
`name` VARCHAR(50) NOT NULL,
`description` TEXT NULL,
PRIMARY KEY (`class_id`, `code`),
FOREIGN KEY (`class_id`) REFERENCES `tabletop`.`classes`(`class_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`abilities`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`abilities` (
`ability_id` INTEGER NOT NULL,
`quality` INTEGER NOT NULL,
`force` INTEGER NOT NULL,
PRIMARY KEY (`ability_id`)
);
-- -----------------------------------------------------
-- Table `tabletop`.`ability_description`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`ability_description` (
`ability_id` INTEGER NOT NULL,
`code` VARCHAR(5) NOT NULL DEFAULT '',
`name` VARCHAR(50) NOT NULL,
`description` TEXT NULL,
PRIMARY KEY (`ability_id`, `code`),
FOREIGN KEY (`ability_id`) REFERENCES `tabletop`.`abilities`(`ability_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`units`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`units`
(
`unit_id` INTEGER NOT NULL AUTO_INCREMENT,
`fraction_id` INTEGER NOT NULL,
`class_id` INTEGER NOT NULL,
`troop_quantity` INTEGER NOT NULL,
`defense` INTEGER NOT NULL,
`moving` INTEGER NOT NULL,
`primary_weapon_id` INTEGER NULL,
`secondary_weapon_id` INTEGER NULL,
`ability_id` INTEGER NULL,
`has_jetpack` BOOLEAN NOT NULL DEFAULT FALSE,
`image` MEDIUMBLOB NULL,
PRIMARY KEY (`unit_id`),
FOREIGN KEY (`fraction_id`) REFERENCES `tabletop`.`fractions`(`fraction_id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`class_id`) REFERENCES `tabletop`.`classes`(`class_id`),
FOREIGN KEY (`primary_weapon_id`) REFERENCES `tabletop`.`weapons`(`weapon_id`),
FOREIGN KEY (`secondary_weapon_id`) REFERENCES `tabletop`.`weapons`(`weapon_id`),
FOREIGN KEY (`ability_id`) REFERENCES `tabletop`.`abilities`(`ability_id`)
);
-- -----------------------------------------------------
-- Table `tabletop`.`unit_description`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`unit_description` (
`unit_id` INTEGER NOT NULL,
`code` VARCHAR(5) NOT NULL DEFAULT '',
`name` VARCHAR(50) NOT NULL,
`description` TEXT NULL,
`mechanic` TEXT NULL,
PRIMARY KEY (`unit_id`, `code`),
FOREIGN KEY (`unit_id`) REFERENCES `tabletop`.`units`(`unit_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`users`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`users` (
`user_id` INTEGER NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`display_name` VARCHAR(100) NOT NULL,
`description` TEXT NULL,
`main_fraction_id` INT NULL,
`password` VARCHAR(255) NOT NULL,
`salt` VARCHAR(255) NOT NULL,
`last_login` DATETIME,
`image` MEDIUMBLOB NULL,
PRIMARY KEY(`user_id`),
FOREIGN KEY (`main_fraction_id`) REFERENCES `tabletop`.`fractions`(`fraction_id`)
);
-- -----------------------------------------------------
-- Table `tabletop`.`permissions`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`permissions` (
`permission_id` INTEGER NOT NULL AUTO_INCREMENT,
`identifier` VARCHAR(50) NOT NULL,
PRIMARY KEY (`permission_id`)
);
-- -----------------------------------------------------
-- Table `tabletop`.`permission_description`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`permission_description` (
`permission_id` INTEGER NOT NULL,
`code` VARCHAR(5) NOT NULL DEFAULT '',
`name` VARCHAR(50) NOT NULL,
`description` TEXT NULL,
PRIMARY KEY (`permission_id`, `code`),
FOREIGN KEY (`permission_id`) REFERENCES `tabletop`.`permissions`(`permission_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`user_permissions`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`user_permissions` (
`user_id` INTEGER NOT NULL,
`permission_id` INTEGER NOT NULL,
PRIMARY KEY(`user_id`, `permission_id`),
FOREIGN KEY (`user_id`) REFERENCES `tabletop`.`users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`permission_id`) REFERENCES `tabletop`.`permissions`(`permission_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`user_units`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`user_units`
(
`user_id` INTEGER NOT NULL,
`unit_id` INTEGER NOT NULL,
`quantity` INTEGER NOT NULL,
PRIMARY KEY (`user_id`, `unit_id`),
FOREIGN KEY (`user_id`) REFERENCES `tabletop`.`users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`unit_id`) REFERENCES `tabletop`.`units`(`unit_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`user_friends`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`user_friends`
(
`user_id` INTEGER NOT NULL,
`friend_id` INTEGER NOT NULL,
PRIMARY KEY (`user_id`, `friend_id`),
FOREIGN KEY (`user_id`) REFERENCES `tabletop`.`users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`friend_id`) REFERENCES `tabletop`.`users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`games`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`games`
(
`game_id` INTEGER NOT NULL AUTO_INCREMENT,
`gamemode_id` INTEGER NOT NULL,
`user_id` INTEGER NOT NULL,
`name` VARCHAR(50) NOT NULL,
`rounds` INTEGER NULL,
`force` INTEGER NOT NULL,
`number_of_teams` INTEGER NOT NULL,
`number_of_players` INTEGER NOT NULL,
`date` DATETIME NOT NULL,
PRIMARY KEY (`game_id`),
FOREIGN KEY (`gamemode_id`) REFERENCES `tabletop`.`gamemodes`(`gamemode_id`)
);
-- -----------------------------------------------------
-- Table `tabletop`.`players`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`players`
(
`player_id` INTEGER NOT NULL AUTO_INCREMENT,
`user_id` INTEGER NOT NULL,
`game_id` INTEGER NOT NULL,
`fraction_id` INTEGER NULL,
`team` INTEGER NOT NULL,
`used_force` INTEGER NOT NULL,
PRIMARY KEY (`player_id`),
FOREIGN KEY (`user_id`) REFERENCES `tabletop`.`users`(`user_id`),
FOREIGN KEY (`game_id`) REFERENCES `tabletop`.`games`(`game_id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`fraction_id`) REFERENCES `tabletop`.`fractions`(`fraction_id`)
);
-- -----------------------------------------------------
-- Table `tabletop`.`player_units`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`player_units`
(
`player_id` INTEGER NOT NULL,
`unit_id` INTEGER NOT NULL,
`quantity` INTEGER NOT NULL,
PRIMARY KEY (`player_id`, `unit_id`),
FOREIGN KEY (`player_id`) REFERENCES `tabletop`.`players`(`player_id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`unit_id`) REFERENCES `tabletop`.`units`(`unit_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`templates`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`templates`
(
`template_id` INTEGER NOT NULL AUTO_INCREMENT,
`user_id` INTEGER NOT NULL,
`fraction_id` INTEGER NOT NULL,
`name` VARCHAR(50) NOT NULL,
`force` INTEGER NOT NULL,
`used_force` INTEGER NOT NULL,
PRIMARY KEY (`template_id`),
FOREIGN KEY (`user_id`) REFERENCES `tabletop`.`users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`fraction_id`) REFERENCES `tabletop`.`fractions`(`fraction_id`) ON DELETE CASCADE ON UPDATE CASCADE
);
-- -----------------------------------------------------
-- Table `tabletop`.`template_units`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `tabletop`.`template_units`
(
`template_id` INTEGER NOT NULL,
`unit_id` INTEGER NOT NULL,
`quantity` INTEGER NOT NULL,
PRIMARY KEY (`template_id`, `unit_id`),
FOREIGN KEY (`template_id`) REFERENCES `tabletop`.`templates`(`template_id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`unit_id`) REFERENCES `tabletop`.`units`(`unit_id`) ON DELETE CASCADE ON UPDATE CASCADE
);

17
README.md Normal file
View File

@@ -0,0 +1,17 @@
# Tabletop
Tablebricks is a passionate fan project that we, a small team from Germany, have been developing with dedication since 2019. Inspired by classic tabletop games and the limitless building possibilities that LEGO offers, we have created a unique concept for LEGO® Star Wars. Our goal is to provide a gaming experience that is both accessible and boundless. With minimal effort, you can start playing right away, while also having the opportunity to engage in epic battles with massive armies on impressive landscapes. Tablebricks brings your minifigures to life and lets them compete in exciting game modes.
Our team has worked hard to create a platform that makes Tablebricks accessible and offers players an exciting new experience. On our website, you can share your statistics and use our tools to make your gameplay smarter. We emphasize that the digital component is designed to enhance the gaming experience. For example, in the Army Designer, you can create templates for your next game or use the simulation mode to optimize your moves. However, we place great importance on ensuring that you are not dependent on digital support. Tablebricks can also be played entirely analog, offering a flexible gaming experience for all preferences. Discover how Tablebricks elevates your LEGO® Star Wars adventures to a new level and learn more on our website.
## Features
* Create an account and customize your profile page
* Add friends and see what they do
* Choose your site and show your friends which units do you have
* Create templates of your armies and use it for quicker selection
* Create various games
## Upcoming Features
* Enter statistics in your current game
## NOT READY YET
This Software is still in development and should not be used right now since it's missing some major core functionalities.

View File

@@ -0,0 +1,91 @@
using Tabletop.Core.Constants;
using Tabletop.Core.Models;
using Tabletop.Core.Services;
namespace Tabletop.Core.Calculators
{
public class Calculation
{
public static async Task<int> ForceAsync(Unit unit)
{
double unitForce = unit.Defense * 25 + unit.Moving * 5;
double primaryWeaponForce = 0;
double secondaryWeaponForce = 0;
unit.PrimaryWeapon = AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == unit.PrimaryWeaponId)?.DeepCopyByExpressionTree();
unit.SecondaryWeapon = AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == unit.SecondaryWeaponId)?.DeepCopyByExpressionTree();
unit.FirstAbility = AppdataService.Abilities.FirstOrDefault(x => x.AbilityId == unit.FirstAbilityId)?.DeepCopyByExpressionTree();
unit.SecondAbility = AppdataService.Abilities.FirstOrDefault(x => x.AbilityId == unit.SecondAbilityId)?.DeepCopyByExpressionTree();
if (unit.PrimaryWeapon != null)
{
primaryWeaponForce = unit.PrimaryWeapon.Attack * (unit.PrimaryWeapon.Quality + (unit.FirstAbility?.Quality ?? 0) + (unit.SecondAbility?.Quality ?? 0)) * (unit.PrimaryWeapon.Range / 10) * unit.PrimaryWeapon.Dices;
}
if (unit.SecondaryWeapon != null)
{
secondaryWeaponForce = (unit.SecondaryWeapon.Attack * unit.SecondaryWeapon.Quality + (unit.FirstAbility?.Quality ?? 0) + (unit.SecondAbility?.Quality ?? 0)) * (unit.SecondaryWeapon.Range / 10) * unit.SecondaryWeapon.Dices;
}
return await Task.FromResult(Convert.ToInt32(Math.Round((unitForce + primaryWeaponForce + secondaryWeaponForce) / 25) + (unit.FirstAbility?.Force ?? 0) + (unit.SecondAbility?.Force ?? 0)));
}
public static Task<double> RandomNumberAsync()
{
Random random = new();
double r = random.Next(1, 9);
return Task.FromResult(r);
}
public static async Task<double> ProbabilityAsync(int attackerId, int defenderId, CoverTypes cover)
{
var (value0, value1) = await AttackValueTranslatorAsync(AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.PrimaryWeaponId)?.Attack ?? 0, AppdataService.Units.FirstOrDefault(x => x.UnitId == defenderId)?.Defense ?? 0);
double x = ((AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.PrimaryWeaponId)?.Quality ?? 0) + ((AppdataService.Abilities.FirstOrDefault(x => x.AbilityId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.FirstAbilityId)?.Quality ?? 0) + ((double)(AppdataService.Abilities.FirstOrDefault(x => x.AbilityId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.SecondAbilityId)?.Quality ?? 0)))) / 8 * ((9 - (double)value0) / 8) * (AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.PrimaryWeaponId)?.Dices ?? 1);
if (AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.PrimaryWeaponId) == AppdataService.Weapons.FirstOrDefault(x => x.WeaponId == AppdataService.Units.FirstOrDefault(x => x.UnitId == attackerId)?.SecondaryWeaponId))
{
x *= 2;
}
if (value1 != 0)
{
x *= (9 - (double)value1) / 8;
}
if (Convert.ToInt32(cover) == 1)
{
x *= 0.75;
}
else if (Convert.ToInt32(cover) == 2)
{
x *= 0.625;
}
return x;
}
public static Task<(int, int)> AttackValueTranslatorAsync(int attacker, int defender)
{
int x = 5;
if (attacker > defender)
{
x -= attacker - defender;
}
else if (attacker < defender)
{
x += defender - attacker;
}
if (x > 8)
{
return Task.FromResult((8, x - 6));
}
if (x < 2)
{
x = 2;
}
return Task.FromResult((x, 0));
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Tabletop.Core.Constants
{
public enum CoverTypes
{
None = 0,
Light = 1,
Heavy = 2
}
}

View File

@@ -0,0 +1,17 @@
namespace Tabletop.Core.Constants
{
public static class PlayerColors
{
public static readonly List<string> Colors =
[
"#D32F2F", // Rot
"#1976D2", // Blau
"#388E3C", // Grün
"#FBC02D", // Gelb
"#F57C00", // Orange
"#7B1FA2", // Violett
"#00ACC1", // Türkis
"#E91E63" // Magenta
];
}
}

View File

@@ -0,0 +1,30 @@
namespace Tabletop.Core.Constants
{
public static class Roles
{
public const string ADD_USERS = "ADD_USERS";
public const string ADD_UNITS = "ADD_UNITS";
public const string ADD_ABILITIES = "ADD_ABILITIES";
public const string ADD_WEAPONS = "ADD_WEAPONS";
public const string ADD_FRACTIONS = "ADD_FRACTIONS";
public const string ADD_GAMEMODES = "ADD_GAMEMODES";
public const string VIEW_USERS = "VIEW_USERS";
public const string VIEW_UNITS = "VIEW_UNITS";
public const string VIEW_ABILITIES = "VIEW_ABILITIES";
public const string VIEW_WEAPONS = "VIEW_WEAPONS";
public const string VIEW_FRACTIONS = "VIEW_FRACTIONS";
public const string VIEW_GAMEMODES = "VIEW_GAMEMODES";
public const string EDIT_USERS = "EDIT_USERS";
public const string EDIT_UNITS = "EDIT_UNITS";
public const string EDIT_ABILITIES = "EDIT_ABILITIES";
public const string EDIT_WEAPONS = "EDIT_WEAPONS";
public const string EDIT_FRACTIONS = "EDIT_FRACTIONS";
public const string EDIT_GAMEMODES = "EDIT_GAMEMODES";
public const string DELETE_USERS = "DELETE_USERS";
public const string DELETE_UNITS = "DELETE_UNITS";
public const string DELETE_ABILITIES = "DELETE_ABILITIES";
public const string DELETE_WEAPONS = "DELETE_WEAPONS";
public const string DELETE_FRACTIONS = "DELETE_FRACTIONS";
public const string DELETE_GAMEMODES = "DELETE_GAMEMODES";
}
}

774
Tabletop.Core/DeepCopy.cs Normal file
View File

@@ -0,0 +1,774 @@
using System.Linq.Expressions;
using System.Reflection;
#nullable disable
namespace Tabletop.Core
{
/// <summary>
/// Superfast deep copier class, which uses Expression trees.
/// </summary>
public static class DeepCopyByExpressionTrees
{
private static readonly object _isStructTypeToDeepCopyDictionaryLocker = new();
private static Dictionary<Type, bool> _isStructTypeToDeepCopyDictionary = [];
private static readonly object _compiledCopyFunctionsDictionaryLocker = new();
private static Dictionary<Type, Func<object, Dictionary<object, object>, object>> _compiledCopyFunctionsDictionary =
[];
private static readonly Type _objectType = typeof(object);
private static readonly Type _objectDictionaryType = typeof(Dictionary<object, object>);
/// <summary>
/// Creates a deep copy of an object.
/// </summary>
/// <typeparam name="T">Object type.</typeparam>
/// <param name="original">Object to copy.</param>
/// <param name="copiedReferencesDict">Dictionary of already copied objects (Keys: original objects, Values: their copies).</param>
/// <returns></returns>
public static T DeepCopyByExpressionTree<T>(this T original, Dictionary<object, object> copiedReferencesDict = null)
{
return (T)DeepCopyByExpressionTreeObj(original, false, copiedReferencesDict ?? new Dictionary<object, object>(new ReferenceEqualityComparer()));
}
private static object DeepCopyByExpressionTreeObj(object original, bool forceDeepCopy, Dictionary<object, object> copiedReferencesDict)
{
if (original == null)
{
return null;
}
var type = original.GetType();
if (IsDelegate(type))
{
return null;
}
if (!forceDeepCopy && !IsTypeToDeepCopy(type))
{
return original;
}
if (copiedReferencesDict.TryGetValue(original, out object alreadyCopiedObject))
{
return alreadyCopiedObject;
}
if (type == _objectType)
{
return new object();
}
var compiledCopyFunction = GetOrCreateCompiledLambdaCopyFunction(type);
object copy = compiledCopyFunction(original, copiedReferencesDict);
return copy;
}
private static Func<object, Dictionary<object, object>, object> GetOrCreateCompiledLambdaCopyFunction(Type type)
{
// The following structure ensures that multiple threads can use the dictionary
// even while dictionary is locked and being updated by other thread.
// That is why we do not modify the old dictionary instance but
// we replace it with a new instance everytime.
if (!_compiledCopyFunctionsDictionary.TryGetValue(type, out Func<object, Dictionary<object, object>, object> compiledCopyFunction))
{
lock (_compiledCopyFunctionsDictionaryLocker)
{
if (!_compiledCopyFunctionsDictionary.TryGetValue(type, out compiledCopyFunction))
{
var uncompiledCopyFunction = CreateCompiledLambdaCopyFunctionForType(type);
compiledCopyFunction = uncompiledCopyFunction.Compile();
var dictionaryCopy = _compiledCopyFunctionsDictionary.ToDictionary(pair => pair.Key, pair => pair.Value);
dictionaryCopy.Add(type, compiledCopyFunction);
_compiledCopyFunctionsDictionary = dictionaryCopy;
}
}
}
return compiledCopyFunction;
}
private static Expression<Func<object, Dictionary<object, object>, object>> CreateCompiledLambdaCopyFunctionForType(Type type)
{
///// INITIALIZATION OF EXPRESSIONS AND VARIABLES
InitializeExpressions(type,
out ParameterExpression inputParameter,
out ParameterExpression inputDictionary,
out ParameterExpression outputVariable,
out ParameterExpression boxingVariable,
out LabelTarget endLabel,
out List<ParameterExpression> variables,
out List<Expression> expressions);
///// RETURN NULL IF ORIGINAL IS NULL
IfNullThenReturnNullExpression(inputParameter, endLabel, expressions);
///// MEMBERWISE CLONE ORIGINAL OBJECT
MemberwiseCloneInputToOutputExpression(type, inputParameter, outputVariable, expressions);
///// STORE COPIED OBJECT TO REFERENCES DICTIONARY
if (IsClassOtherThanString(type))
{
StoreReferencesIntoDictionaryExpression(inputParameter, inputDictionary, outputVariable, expressions);
}
///// COPY ALL NONVALUE OR NONPRIMITIVE FIELDS
FieldsCopyExpressions(type,
inputParameter,
inputDictionary,
outputVariable,
boxingVariable,
expressions);
///// COPY ELEMENTS OF ARRAY
if (IsArray(type) && IsTypeToDeepCopy(type.GetElementType()))
{
CreateArrayCopyLoopExpression(type,
inputParameter,
inputDictionary,
outputVariable,
variables,
expressions);
}
///// COMBINE ALL EXPRESSIONS INTO LAMBDA FUNCTION
var lambda = CombineAllIntoLambdaFunctionExpression(inputParameter, inputDictionary, outputVariable, endLabel, variables, expressions);
return lambda;
}
private static void InitializeExpressions(Type type,
out ParameterExpression inputParameter,
out ParameterExpression inputDictionary,
out ParameterExpression outputVariable,
out ParameterExpression boxingVariable,
out LabelTarget endLabel,
out List<ParameterExpression> variables,
out List<Expression> expressions)
{
inputParameter = Expression.Parameter(_objectType);
inputDictionary = Expression.Parameter(_objectDictionaryType);
outputVariable = Expression.Variable(type);
boxingVariable = Expression.Variable(_objectType);
endLabel = Expression.Label();
variables = [];
expressions = [];
variables.Add(outputVariable);
variables.Add(boxingVariable);
}
private static void IfNullThenReturnNullExpression(ParameterExpression inputParameter, LabelTarget endLabel, List<Expression> expressions)
{
///// Intended code:
/////
///// if (input == null)
///// {
///// return null;
///// }
var ifNullThenReturnNullExpression =
Expression.IfThen(
Expression.Equal(
inputParameter,
Expression.Constant(null, _objectType)),
Expression.Return(endLabel));
expressions.Add(ifNullThenReturnNullExpression);
}
private static void MemberwiseCloneInputToOutputExpression(
Type type,
ParameterExpression inputParameter,
ParameterExpression outputVariable,
List<Expression> expressions)
{
///// Intended code:
/////
///// var output = (<type>)input.MemberwiseClone();
var memberwiseCloneMethod = _objectType.GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
var memberwiseCloneInputExpression =
Expression.Assign(
outputVariable,
Expression.Convert(
Expression.Call(
inputParameter,
memberwiseCloneMethod),
type));
expressions.Add(memberwiseCloneInputExpression);
}
private static void StoreReferencesIntoDictionaryExpression(ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
List<Expression> expressions)
{
///// Intended code:
/////
///// inputDictionary[(Object)input] = (Object)output;
var storeReferencesExpression =
Expression.Assign(
Expression.Property(
inputDictionary,
_objectDictionaryType.GetProperty("Item"),
inputParameter),
Expression.Convert(outputVariable, _objectType));
expressions.Add(storeReferencesExpression);
}
private static Expression<Func<object, Dictionary<object, object>, object>> CombineAllIntoLambdaFunctionExpression(
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
LabelTarget endLabel,
List<ParameterExpression> variables,
List<Expression> expressions)
{
expressions.Add(Expression.Label(endLabel));
expressions.Add(Expression.Convert(outputVariable, _objectType));
var finalBody = Expression.Block(variables, expressions);
var lambda = Expression.Lambda<Func<object, Dictionary<object, object>, object>>(finalBody, inputParameter, inputDictionary);
return lambda;
}
private static void CreateArrayCopyLoopExpression(Type type,
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
List<ParameterExpression> variables,
List<Expression> expressions)
{
///// Intended code:
/////
///// int i1, i2, ..., in;
/////
///// int length1 = inputarray.GetLength(0);
///// i1 = 0;
///// while (true)
///// {
///// if (i1 >= length1)
///// {
///// goto ENDLABELFORLOOP1;
///// }
///// int length2 = inputarray.GetLength(1);
///// i2 = 0;
///// while (true)
///// {
///// if (i2 >= length2)
///// {
///// goto ENDLABELFORLOOP2;
///// }
///// ...
///// ...
///// ...
///// int lengthn = inputarray.GetLength(n);
///// in = 0;
///// while (true)
///// {
///// if (in >= lengthn)
///// {
///// goto ENDLABELFORLOOPn;
///// }
///// outputarray[i1, i2, ..., in]
///// = (<elementType>)DeepCopyByExpressionTreeObj(
///// (Object)inputarray[i1, i2, ..., in])
///// in++;
///// }
///// ENDLABELFORLOOPn:
///// ...
///// ...
///// ...
///// i2++;
///// }
///// ENDLABELFORLOOP2:
///// i1++;
///// }
///// ENDLABELFORLOOP1:
var rank = type.GetArrayRank();
var indices = GenerateIndices(rank);
variables.AddRange(indices);
var elementType = type.GetElementType();
var assignExpression = ArrayFieldToArrayFieldAssignExpression(inputParameter, inputDictionary, outputVariable, elementType, type, indices);
Expression forExpression = assignExpression;
for (int dimension = 0; dimension < rank; dimension++)
{
var indexVariable = indices[dimension];
forExpression = LoopIntoLoopExpression(inputParameter, indexVariable, forExpression, dimension);
}
expressions.Add(forExpression);
}
private static List<ParameterExpression> GenerateIndices(int arrayRank)
{
///// Intended code:
/////
///// int i1, i2, ..., in;
var indices = new List<ParameterExpression>();
for (int i = 0; i < arrayRank; i++)
{
var indexVariable = Expression.Variable(typeof(int));
indices.Add(indexVariable);
}
return indices;
}
private static BinaryExpression ArrayFieldToArrayFieldAssignExpression(
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
Type elementType,
Type arrayType,
List<ParameterExpression> indices)
{
///// Intended code:
/////
///// outputarray[i1, i2, ..., in]
///// = (<elementType>)DeepCopyByExpressionTreeObj(
///// (Object)inputarray[i1, i2, ..., in]);
var indexTo = Expression.ArrayAccess(outputVariable, indices);
var indexFrom = Expression.ArrayIndex(Expression.Convert(inputParameter, arrayType), indices);
var forceDeepCopy = elementType != _objectType;
var rightSide =
Expression.Convert(
Expression.Call(
_deepCopyByExpressionTreeObjMethod,
Expression.Convert(indexFrom, _objectType),
Expression.Constant(forceDeepCopy, typeof(bool)),
inputDictionary),
elementType);
var assignExpression = Expression.Assign(indexTo, rightSide);
return assignExpression;
}
private static BlockExpression LoopIntoLoopExpression(
ParameterExpression inputParameter,
ParameterExpression indexVariable,
Expression loopToEncapsulate,
int dimension)
{
///// Intended code:
/////
///// int length = inputarray.GetLength(dimension);
///// i = 0;
///// while (true)
///// {
///// if (i >= length)
///// {
///// goto ENDLABELFORLOOP;
///// }
///// loopToEncapsulate;
///// i++;
///// }
///// ENDLABELFORLOOP:
var lengthVariable = Expression.Variable(typeof(int));
var endLabelForThisLoop = Expression.Label();
var newLoop =
Expression.Loop(
Expression.Block(
[],
Expression.IfThen(
Expression.GreaterThanOrEqual(indexVariable, lengthVariable),
Expression.Break(endLabelForThisLoop)),
loopToEncapsulate,
Expression.PostIncrementAssign(indexVariable)),
endLabelForThisLoop);
var lengthAssignment = GetLengthForDimensionExpression(lengthVariable, inputParameter, dimension);
var indexAssignment = Expression.Assign(indexVariable, Expression.Constant(0));
return Expression.Block(
[lengthVariable],
lengthAssignment,
indexAssignment,
newLoop);
}
private static BinaryExpression GetLengthForDimensionExpression(
ParameterExpression lengthVariable,
ParameterExpression inputParameter,
int i)
{
///// Intended code:
/////
///// length = ((Array)input).GetLength(i);
var getLengthMethod = typeof(Array).GetMethod("GetLength", BindingFlags.Public | BindingFlags.Instance);
var dimensionConstant = Expression.Constant(i);
return Expression.Assign(
lengthVariable,
Expression.Call(
Expression.Convert(inputParameter, typeof(Array)),
getLengthMethod,
[dimensionConstant]));
}
private static void FieldsCopyExpressions(Type type,
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
ParameterExpression boxingVariable,
List<Expression> expressions)
{
var fields = GetAllRelevantFields(type);
var readonlyFields = fields.Where(f => f.IsInitOnly).ToList();
var writableFields = fields.Where(f => !f.IsInitOnly).ToList();
///// READONLY FIELDS COPY (with boxing)
bool shouldUseBoxing = readonlyFields.Count != 0;
if (shouldUseBoxing)
{
var boxingExpression = Expression.Assign(boxingVariable, Expression.Convert(outputVariable, _objectType));
expressions.Add(boxingExpression);
}
foreach (var field in readonlyFields)
{
if (IsDelegate(field.FieldType))
{
ReadonlyFieldToNullExpression(field, boxingVariable, expressions);
}
else
{
ReadonlyFieldCopyExpression(type,
field,
inputParameter,
inputDictionary,
boxingVariable,
expressions);
}
}
if (shouldUseBoxing)
{
var unboxingExpression = Expression.Assign(outputVariable, Expression.Convert(boxingVariable, type));
expressions.Add(unboxingExpression);
}
///// NOT-READONLY FIELDS COPY
foreach (var field in writableFields)
{
if (IsDelegate(field.FieldType))
{
WritableFieldToNullExpression(field, outputVariable, expressions);
}
else
{
WritableFieldCopyExpression(type,
field,
inputParameter,
inputDictionary,
outputVariable,
expressions);
}
}
}
private static FieldInfo[] GetAllRelevantFields(Type type, bool forceAllFields = false)
{
var fieldsList = new List<FieldInfo>();
var typeCache = type;
while (typeCache != null)
{
fieldsList.AddRange(
typeCache
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
.Where(field => forceAllFields || IsTypeToDeepCopy(field.FieldType)));
typeCache = typeCache.BaseType;
}
return [.. fieldsList];
}
private static FieldInfo[] GetAllFields(Type type)
{
return GetAllRelevantFields(type, forceAllFields: true);
}
private static readonly Type _fieldInfoType = typeof(FieldInfo);
private static readonly MethodInfo _setValueMethod = _fieldInfoType.GetMethod("SetValue", [_objectType, _objectType]);
private static void ReadonlyFieldToNullExpression(FieldInfo field, ParameterExpression boxingVariable, List<Expression> expressions)
{
// This option must be implemented by Reflection because of the following:
// https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f
///// Intended code:
/////
///// fieldInfo.SetValue(boxing, <fieldtype>null);
var fieldToNullExpression =
Expression.Call(
Expression.Constant(field),
_setValueMethod,
boxingVariable,
Expression.Constant(null, field.FieldType));
expressions.Add(fieldToNullExpression);
}
private static readonly Type _thisType = typeof(DeepCopyByExpressionTrees);
private static readonly MethodInfo _deepCopyByExpressionTreeObjMethod = _thisType.GetMethod("DeepCopyByExpressionTreeObj", BindingFlags.NonPublic | BindingFlags.Static);
private static void ReadonlyFieldCopyExpression(Type type,
FieldInfo field,
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression boxingVariable,
List<Expression> expressions)
{
// This option must be implemented by Reflection (SetValueMethod) because of the following:
// https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f
///// Intended code:
/////
///// fieldInfo.SetValue(boxing, DeepCopyByExpressionTreeObj((Object)((<type>)input).<field>))
var fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field);
var forceDeepCopy = field.FieldType != _objectType;
var fieldDeepCopyExpression =
Expression.Call(
Expression.Constant(field, _fieldInfoType),
_setValueMethod,
boxingVariable,
Expression.Call(
_deepCopyByExpressionTreeObjMethod,
Expression.Convert(fieldFrom, _objectType),
Expression.Constant(forceDeepCopy, typeof(bool)),
inputDictionary));
expressions.Add(fieldDeepCopyExpression);
}
private static void WritableFieldToNullExpression(FieldInfo field, ParameterExpression outputVariable, List<Expression> expressions)
{
///// Intended code:
/////
///// output.<field> = (<type>)null;
var fieldTo = Expression.Field(outputVariable, field);
var fieldToNullExpression =
Expression.Assign(
fieldTo,
Expression.Constant(null, field.FieldType));
expressions.Add(fieldToNullExpression);
}
private static void WritableFieldCopyExpression(Type type,
FieldInfo field,
ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
List<Expression> expressions)
{
///// Intended code:
/////
///// output.<field> = (<fieldType>)DeepCopyByExpressionTreeObj((Object)((<type>)input).<field>);
var fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field);
var fieldType = field.FieldType;
var fieldTo = Expression.Field(outputVariable, field);
var forceDeepCopy = field.FieldType != _objectType;
var fieldDeepCopyExpression =
Expression.Assign(
fieldTo,
Expression.Convert(
Expression.Call(
_deepCopyByExpressionTreeObjMethod,
Expression.Convert(fieldFrom, _objectType),
Expression.Constant(forceDeepCopy, typeof(bool)),
inputDictionary),
fieldType));
expressions.Add(fieldDeepCopyExpression);
}
private static bool IsArray(Type type)
{
return type.IsArray;
}
private static bool IsDelegate(Type type)
{
return typeof(Delegate).IsAssignableFrom(type);
}
private static bool IsTypeToDeepCopy(Type type)
{
return IsClassOtherThanString(type)
|| IsStructWhichNeedsDeepCopy(type);
}
private static bool IsClassOtherThanString(Type type)
{
return !type.IsValueType && type != typeof(string);
}
private static bool IsStructWhichNeedsDeepCopy(Type type)
{
// The following structure ensures that multiple threads can use the dictionary
// even while dictionary is locked and being updated by other thread.
// That is why we do not modify the old dictionary instance but
// we replace it with a new instance everytime.
if (!_isStructTypeToDeepCopyDictionary.TryGetValue(type, out bool isStructTypeToDeepCopy))
{
lock (_isStructTypeToDeepCopyDictionaryLocker)
{
if (!_isStructTypeToDeepCopyDictionary.TryGetValue(type, out isStructTypeToDeepCopy))
{
isStructTypeToDeepCopy = IsStructWhichNeedsDeepCopy_NoDictionaryUsed(type);
var newDictionary = _isStructTypeToDeepCopyDictionary.ToDictionary(pair => pair.Key, pair => pair.Value);
newDictionary[type] = isStructTypeToDeepCopy;
_isStructTypeToDeepCopyDictionary = newDictionary;
}
}
}
return isStructTypeToDeepCopy;
}
private static bool IsStructWhichNeedsDeepCopy_NoDictionaryUsed(Type type)
{
return IsStructOtherThanBasicValueTypes(type)
&& HasInItsHierarchyFieldsWithClasses(type);
}
private static bool IsStructOtherThanBasicValueTypes(Type type)
{
return type.IsValueType
&& !type.IsPrimitive
&& !type.IsEnum
&& type != typeof(decimal);
}
private static bool HasInItsHierarchyFieldsWithClasses(Type type, HashSet<Type> alreadyCheckedTypes = null)
{
alreadyCheckedTypes ??= [];
alreadyCheckedTypes.Add(type);
var allFields = GetAllFields(type);
var allFieldTypes = allFields.Select(f => f.FieldType).Distinct().ToList();
var hasFieldsWithClasses = allFieldTypes.Any(IsClassOtherThanString);
if (hasFieldsWithClasses)
{
return true;
}
var notBasicStructsTypes = allFieldTypes.Where(IsStructOtherThanBasicValueTypes).ToList();
var typesToCheck = notBasicStructsTypes.Where(t => !alreadyCheckedTypes.Contains(t)).ToList();
foreach (var typeToCheck in typesToCheck)
{
if (HasInItsHierarchyFieldsWithClasses(typeToCheck, alreadyCheckedTypes))
{
return true;
}
}
return false;
}
public class ReferenceEqualityComparer : EqualityComparer<object>
{
public override bool Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
public override int GetHashCode(object obj)
{
if (obj == null)
{
return 0;
}
return obj.GetHashCode();
}
}
}
}

View File

@@ -0,0 +1,25 @@
namespace Tabletop.Core.Extensions
{
public static class StringExtensions
{
/// <summary>
/// Generates a random string
/// </summary>
/// <param name="length">The length of the random string</param>
/// <returns></returns>
public static string RandomString(int length)
{
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[length];
var random = new Random();
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
var finalString = new string(stringChars);
return finalString;
}
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class AbilityFilter : PageFilterBase
{
}
}

View File

@@ -0,0 +1,8 @@
namespace Tabletop.Core.Filters.Abstract
{
public abstract class FilterBase
{
private string _searchPhrase = string.Empty;
public string SearchPhrase { get => _searchPhrase; set => _searchPhrase = value?.ToUpper() ?? string.Empty; }
}
}

View File

@@ -0,0 +1,11 @@
namespace Tabletop.Core.Filters.Abstract
{
public abstract class PageFilterBase : FilterBase
{
private int _pageNumber = 1;
private int _limit = 30;
public int PageNumber { get => _pageNumber; set => _pageNumber = value < 1 ? 1 : value; }
public int Limit { get => _limit; set => _limit = value < 1 ? 1 : value; }
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class FractionFilter : PageFilterBase
{
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class GameFilter : PageFilterBase
{
public int UserId { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class GamemodeFilter : PageFilterBase
{
}
}

View File

@@ -0,0 +1,11 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class TemplateFilter : PageFilterBase
{
public int UserId { get; set; }
public int FractionId { get; set; }
public int Force { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class UnitFilter : PageFilterBase
{
public int FractionId { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class UserFilter : PageFilterBase
{
}
}

View File

@@ -0,0 +1,9 @@
using Tabletop.Core.Filters.Abstract;
namespace Tabletop.Core.Filters
{
public class WeaponFilter : PageFilterBase
{
}
}

View File

@@ -0,0 +1,13 @@
namespace Tabletop.Core.Interfaces
{
/// <summary>
/// Helper interface to provide properties which can be used within the interface <see cref="ILocalizedDbModel{T}"/>
/// </summary>
public interface ILocalizationHelper
{
/// <summary>
/// Gets or sets the ISO 639-1 two letter code for the language
/// </summary>
string Code { get; set; }
}
}

View File

@@ -0,0 +1,45 @@
using DbController.SqlServer;
using DbController;
using Microsoft.AspNetCore.Http;
using Tabletop.Core.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Tabletop.Core.Middlewares
{
public class ConnectionLogMiddleware(RequestDelegate next, IServiceProvider serviceProvider, ILogger<ConnectionLogMiddleware> logger)
{
private readonly RequestDelegate _next = next;
private readonly IServiceProvider _serviceProvider = serviceProvider;
private readonly ILogger<ConnectionLogMiddleware> _logger = logger;
public async Task InvokeAsync(HttpContext context)
{
try
{
// Erstelle ein ServiceScope, um Scoped Services zu verwenden
using var scope = _serviceProvider.CreateScope();
var connectionLogService = scope.ServiceProvider.GetRequiredService<ConnectionLogService>();
// Erstelle das ConnectionLog
var connectionLog = await connectionLogService.GetConnectionLogAsync(context);
if (connectionLog.IpAddress is not ("84.119.112.185" or "127.0.0.1" or "::1"))
{
// Verwende den dbController zur Speicherung des Logs in der DB
using IDbController dbController = new SqlController(AppdataService.ConnectionString);
await ConnectionLogService.CreateAsync(connectionLog, dbController);
}
}
catch (Exception ex)
{
// Fehlerbehandlung und Logging
_logger.LogError($"Fehler beim Erstellen des ConnectionLogs: {ex.Message}", ex);
}
// Weiter zum nächsten Middleware in der Pipeline
await _next(context);
}
}
}

View File

@@ -0,0 +1,64 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Ability : LocalizationModelBase<AbilityDescription>, IDbModel<int?>
{
[CompareField("ability_id")]
public int AbilityId { get; set; }
[CompareField("quality")]
public int Quality { get; set; }
[CompareField("force")]
public int Force { get; set; }
public int? GetIdentifier()
{
return AbilityId > 0 ? AbilityId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "ABILITY_ID", AbilityId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description },
{ "MECHANIC", description.Mechanic }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "ABILITY_ID", AbilityId },
{ "QUALITY", Quality },
{ "FORCE", Force }
};
}
}
public class AbilityDescription : ILocalizationHelper
{
[CompareField("ability_id")]
public int AbilityId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
[CompareField("mechanic")]
public string Mechanic { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,24 @@
using DbController;
namespace Tabletop.Core.Models.Abstract
{
public abstract class ArmyBase
{
[CompareField("fraction_id")]
public int FractionId { get; set; }
[CompareField("used_force")]
public int UsedForce { get; set; }
public List<Unit> Units { get; set; } = [];
public int TotalUnits { get; set; }
public virtual Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "FRACTION_ID", FractionId },
{ "USED_FORCE", UsedForce }
};
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Globalization;
using Tabletop.Core.Interfaces;
namespace Tabletop.Core.Models.Abstract
{
public abstract class LocalizationModelBase<T> where T : ILocalizationHelper
{
public List<T> Description { get; set; } = [];
public T? GetLocalization(CultureInfo culture)
{
var description = Description.FirstOrDefault(x => x.Code.Equals(culture.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase));
return description;
}
}
}

View File

@@ -0,0 +1,27 @@
using DbController;
namespace Tabletop.Core.Models.Abstract
{
public abstract class ZoneBase
{
[CompareField("x")]
public string X { get; set; } = string.Empty;
[CompareField("y")]
public string Y { get; set; } = string.Empty;
[CompareField("width")]
public string Width { get; set; } = string.Empty;
[CompareField("height")]
public string Height { get; set; } = string.Empty;
public virtual Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "X", X },
{ "Y", Y },
{ "WIDTH", Width },
{ "HEIGHT", Height }
};
}
}
}

View File

@@ -0,0 +1,36 @@
using DbController;
namespace Tabletop.Core.Models
{
public class Capture
{
[CompareField("capture_id")]
public int CaptureId { get; set; }
[CompareField("move_id")]
public int MoveId { get; set; }
[CompareField("player_id")]
public int PlayerId { get; set; }
[CompareField("team_id")]
public int TeamId { get; set; }
[CompareField("capture_zone_nr")]
public int CaptureZoneNr { get; set; }
[CompareField("nr_of_turns")]
public int NrOfTurns { get; set; }
[CompareField("points_received")]
public int PointsReceived { get; set; }
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "CAPTURE_ID", CaptureId },
{ "MOVE_ID", MoveId },
{ "PLAYER_ID", PlayerId },
{ "TEAM_ID", TeamId },
{ "CAPTURE_ZONE_NR", CaptureZoneNr },
{ "NR_OF_TURNS", NrOfTurns },
{ "POINTS_RECEIVED", PointsReceived }
};
}
}
}

View File

@@ -0,0 +1,22 @@
using DbController;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class CaptureZone : ZoneBase
{
[CompareField("capturezone_id")]
public int CaptureZoneId { get; set; }
[CompareField("points_per_round")]
public int PointsPerRound { get; set; }
public override Dictionary<string, object?> GetParameters()
{
var baseParams = base.GetParameters();
baseParams["CAPTUREZONE_ID"] = CaptureZoneId;
baseParams["POINTS_PER_ROUNDS"] = PointsPerRound;
return baseParams;
}
}
}

View File

@@ -0,0 +1,57 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Class : LocalizationModelBase<ClassDescription>
{
[CompareField("class_id")]
public int ClassId { get; set; }
[CompareField("quantity")]
public string Quantity { get; set; } = string.Empty;
public int? GetIdentifier()
{
return ClassId > 0 ? ClassId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "CLASS_ID", ClassId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "CLASS_ID", ClassId },
{ "QUANTITY", Quantity }
};
}
}
public class ClassDescription : ILocalizationHelper
{
[CompareField("class_id")]
public int ClassId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,63 @@
using DbController;
namespace Tabletop.Core.Models
{
public enum DeviceType
{
Desktop,
Tablet,
Smartphone,
Other
}
public enum OperatingSystem
{
Windows,
macOS,
Linux,
iOS,
Android,
Other
}
public class ConnectionLog
{
[CompareField("ip_address")]
public string IpAddress { get; set; } = string.Empty; // IP-Adresse des Besuchers
[CompareField("connection_time")]
public DateTime ConnectionTime { get; set; } // Zeitstempel des Zugriffs
[CompareField("user_agent")]
public string UserAgent { get; set; } = string.Empty; // User-Agent (Browser/OS)
[CompareField("referrer")]
public string Referrer { get; set; } = string.Empty; // Referrer (URL der vorherigen Seite)
[CompareField("requested_url")]
public string RequestedUrl { get; set; } = string.Empty; // Angeforderte URL
[CompareField("geolocation")]
public string? Geolocation { get; set; } = null; // Geografische Lage (optional, kann null sein)
[CompareField("session_id")]
public string SessionId { get; set; } = string.Empty; // Sitzungs-ID
[CompareField("status_code")]
public int StatusCode { get; set; } // HTTP-Statuscode der Antwort
[CompareField("device_type")]
public string DeviceType { get; set; } = string.Empty; // Gerätetyp (Desktop/Tablet/Smartphone)
[CompareField("operating_system")]
public string OperatingSystem { get; set; } = string.Empty; // Betriebssystem des Besuchers
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "IP_ADDRESS", IpAddress },
{ "CONNECTION_TIME", ConnectionTime },
{ "USER_AGENT", UserAgent },
{ "REFERRER", Referrer },
{ "REQUESTED_URL", RequestedUrl },
{ "GEOLOCATION", Geolocation },
{ "SESSION_ID", SessionId },
{ "STATUS_CODE", StatusCode },
{ "DEVICE_TYPE", DeviceType },
{ "OPERATING_SYSTEM", OperatingSystem }
};
}
}
}

View File

@@ -0,0 +1,49 @@
using DbController;
namespace Tabletop.Core.Models
{
public class Elimination
{
[CompareField("elimination_id")]
public int EliminationId { get; set; }
[CompareField("move_id")]
public int MoveId { get; set; }
[CompareField("team_id")]
public int TeamId { get; set; }
[CompareField("atk_id")]
public int AtkId { get; set; }
[CompareField("atk_unit_id")]
public int AtkUnitId { get; set; }
[CompareField("atk_unit_nr")]
public int AtkUnitNr { get; set; }
[CompareField("def_id")]
public int DefId { get; set; }
[CompareField("def_unit_id")]
public int DefUnitId { get; set; }
[CompareField("def_unit_nr")]
public int DefUnitNr { get; set; }
[CompareField("elimination_nr")]
public int EliminationNr { get; set; }
[CompareField("ForcePointsEliminations")]
public int ForcePointsEliminations { get; set; }
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "ELIMINATION_ID", EliminationId },
{ "MOVE_ID", MoveId },
{ "TEAM_ID", TeamId },
{ "ATK_ID", AtkId },
{ "ATK_UNIT_ID", AtkUnitId },
{ "ATK_UNIT_NR", AtkUnitNr },
{ "DEF_ID", DefId },
{ "DEF_UNIT_ID", DefUnitId },
{ "DEF_UNIT_NR", DefUnitNr },
{ "ELIMINATION_NR", EliminationNr },
{ "FORCEPOINTSELIMINATIONS", ForcePointsEliminations }
};
}
}
}

View File

@@ -0,0 +1,64 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Fraction : LocalizationModelBase<FractionDescription>, IDbModel<int?>
{
[CompareField("fraction_id")]
public int FractionId { get; set; }
[CompareField("image")]
public byte[]? Image { get; set; }
public string ConvertedImage { get; set; } = string.Empty;
public int? GetIdentifier()
{
return FractionId > 0 ? FractionId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "PERMISSION_ID", FractionId },
{ "NAME", description.Name },
{ "SHORT_NAME", description.ShortName },
{ "DESCRIPTION", description.Description }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "FRACTION_ID", FractionId },
{ "IMAGE", Image }
};
}
}
public class FractionDescription : ILocalizationHelper
{
[CompareField("fraction_id")]
public int FractionId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("short_name")]
public string ShortName { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,62 @@
using DbController;
namespace Tabletop.Core.Models
{
public class Game : IDbModel<int?>
{
[CompareField("game_id")]
public int GameId { get; set; }
[CompareField("gamemode_id")]
public int GamemodeId { get; set; }
[CompareField("user_id")]
public int UserId { get; set; }
[CompareField("surface_id")]
public int SurfaceId { get; set; }
[CompareField("layout_id")]
public int LayoutId { get; set; }
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("number_of_rounds")]
public int? NumberOfRounds { get; set; }
[CompareField("force")]
public int Force { get; set; }
[CompareField("number_of_teams")]
public int NumberOfTeams { get; set; }
[CompareField("number_of_players")]
public int NumberOfPlayers { get; set; }
[CompareField("date")]
public DateTime Date { get; set; } = DateTime.Now;
public string Host { get; set; } = string.Empty;
public List<Player> Players { get; set; } = [];
public List<Move> Rounds { get; set; } = [];
public List<Capture> Captures { get; set; } = [];
public List<Elimination> Elimination { get; set; } = [];
public string GUID { get; set; } = string.Empty;
public int? GetIdentifier()
{
return GameId > 0 ? GameId : null;
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "GAME_ID", GameId },
{ "GAMEMODE_ID", GamemodeId },
{ "USER_ID", UserId },
{ "SURFACE_ID", SurfaceId },
{ "LAYOUT_ID", LayoutId },
{ "NAME", Name },
{ "NUMBER_OF_ROUNDS", NumberOfRounds },
{ "FORCE", Force },
{ "NUMBER_OF_TEAMS", NumberOfTeams },
{ "NUMBER_OF_PLAYERS", NumberOfPlayers },
{ "DATE", Date }
};
}
}
}

View File

@@ -0,0 +1,63 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Gamemode : LocalizationModelBase<GamemodeDescription>, IDbModel<int?>
{
[CompareField("gamemode_id")]
public int GamemodeId { get; set; }
[CompareField("image")]
public byte[]? Image { get; set; }
public string ConvertedImage { get; set; } = string.Empty;
public int? GetIdentifier()
{
return GamemodeId > 0 ? GamemodeId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "PERMISSION_ID", GamemodeId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description },
{ "MECHANIC", description.Mechanic }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "GAMEMODE_ID", GamemodeId },
{ "IMAGE", Image }
};
}
}
public class GamemodeDescription : ILocalizationHelper
{
[CompareField("gamemode_id")]
public int GamemodeId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
[CompareField("mechanic")]
public string Mechanic { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,19 @@
using Newtonsoft.Json;
namespace Tabletop.Core.Models
{
public class Geolocation
{
[JsonProperty("country_name")]
public string CountryName { get; set; } = string.Empty;
[JsonProperty("city")]
public string City { get; set; } = string.Empty;
[JsonProperty("latitude")]
public double Latitude { get; set; }
[JsonProperty("longitude")]
public double Longitude { get; set; }
}
}

View File

@@ -0,0 +1,64 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Layout : LocalizationModelBase<LayoutDescription>, IDbModel<int?>
{
[CompareField("layout_id")]
public int LayoutId { get; set; }
[CompareField("gamemode_id")]
public int GamemodeId { get; set; }
[CompareField("teams")]
public int Teams { get; set; }
[CompareField("width")]
public string Width { get; set; } = string.Empty;
[CompareField("height")]
public string Height { get; set; } = string.Empty;
public List<SetupZone> SetupZones { get; set; } = [];
public List<CaptureZone> CaptureZones { get; set; } = [];
public int? GetIdentifier()
{
return LayoutId > 0 ? LayoutId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "LAYOUT_ID", LayoutId },
{ "NAME", description.Name }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "LAYOUT_ID", LayoutId },
{ "GAMEMODE_ID", GamemodeId },
{ "TEAMS", Teams },
{ "WIDTH", Width },
{ "HEIGHT", Height }
};
}
}
public class LayoutDescription : ILocalizationHelper
{
[CompareField("layout_id")]
public int LayoutId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,36 @@
using DbController;
namespace Tabletop.Core.Models
{
public class Move
{
[CompareField("move_id")]
public int MoveId { get; set; }
[CompareField("player_id")]
public int PlayerId { get; set; }
[CompareField("game_id")]
public int GameId { get; set; }
[CompareField("turn_nr")]
public int TurnNr { get; set; }
[CompareField("move_nr")]
public int MoveNr { get; set; }
[CompareField("start_move")]
public DateTime StartMove { get; set; }
[CompareField("end_move")]
public DateTime EndMove { get; set; }
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "move_id", MoveId },
{ "player_id", PlayerId },
{ "game_id", GameId },
{ "turn_nr", TurnNr },
{ "move_nr", MoveNr },
{ "start_move", StartMove },
{ "end_move", EndMove }
};
}
}
}

View File

@@ -0,0 +1,57 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Permission : LocalizationModelBase<PermissionDescription>
{
[CompareField("permission_id")]
public int PermissionId { get; set; }
[CompareField("identifier")]
public string Identifier { get; set; } = string.Empty;
public int? GetIdentifier()
{
return PermissionId > 0 ? PermissionId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "PERMISSION_ID", PermissionId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "PERMISSION_ID", PermissionId },
{ "IDENTIFIER", Identifier }
};
}
}
public class PermissionDescription : ILocalizationHelper
{
[CompareField("permission_id")]
public int PermissionId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,58 @@
using DbController;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Player : ArmyBase
{
[CompareField("player_id")]
public int PlayerId { get; set; }
[CompareField("user_id")]
public int UserId { get; set; }
[CompareField("game_id")]
public int GameId { get; set; }
[CompareField("team")]
public int Team { get; set; }
[CompareField("color")]
public string Color { get; set; } = string.Empty;
[CompareField("points")]
public int Points { get; set; }
[CompareField("order_nr")]
public int OrderNr { get; set; }
[CompareField("start_zone")]
public int StartZone { get; set; }
[CompareField("number_of_eliminations")]
public int NumberOfEliminations { get; set; }
[CompareField("force_points_of_eliminations")]
public int ForcePointsOfEliminations { get; set; }
[CompareField("number_of_remaining_units")]
public int NumberOfRemainingUnits { get; set; }
[CompareField("own_force_points_left")]
public int OwnForcePointsLeft { get; set; }
public int AllowedForce { get; set; }
public User User { get; set; } = new();
public string ConnectionId { get; set; } = string.Empty; // Verbindung zu SignalR
public bool IsReady { get; set; }
public override Dictionary<string, object?> GetParameters()
{
var baseParams = base.GetParameters();
baseParams["PLAYER_ID"] = PlayerId;
baseParams["USER_ID"] = UserId;
baseParams["GAME_ID"] = GameId;
baseParams["TEAM"] = Team;
baseParams["COLOR"] = Color;
baseParams["ORDER_NR"] = OrderNr;
baseParams["START_ZONE"] = StartZone;
baseParams["NUMBER_OF_ELIMINATIONS"] = NumberOfEliminations;
baseParams["FORCE_POINTS_OF_ELIMINATIONS"] = ForcePointsOfEliminations;
baseParams["NUMBER_OF_REMAINING_UNITS"] = NumberOfRemainingUnits;
baseParams["OWN_FORCE_POINTS_LEFT"] = OwnForcePointsLeft;
return baseParams;
}
}
}

View File

@@ -0,0 +1,20 @@
using DbController;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class SetupZone : ZoneBase
{
[CompareField("setupzone_id")]
public int SetupZoneId { get; set; }
public override Dictionary<string, object?> GetParameters()
{
var baseParams = base.GetParameters();
baseParams["SETUPZONE_ID"] = SetupZoneId;
return baseParams;
}
}
}

View File

@@ -0,0 +1,59 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Surface : LocalizationModelBase<SurfaceDescription>, IDbModel<int?>
{
[CompareField("surface_id")]
public int SurfaceId { get; set; }
[CompareField("image")]
public byte[]? Image { get; set; }
public string ConvertedImage { get; set; } = string.Empty;
public int? GetIdentifier()
{
return SurfaceId > 0 ? SurfaceId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "SURFACE_ID", SurfaceId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "SURFACE_ID", SurfaceId },
{ "IMAGE", Image }
};
}
}
public class SurfaceDescription : ILocalizationHelper
{
[CompareField("surface_id")]
public int SurfaceId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,27 @@
using DbController;
namespace Tabletop.Core.Models
{
public class Team
{
[CompareField("team_id")]
public int TeamId { get; set; }
[CompareField("game_id")]
public int GameId { get; set; }
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("points")]
public int Points { get; set; }
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "TEAM_ID", TeamId },
{ "GAME_ID", GameId },
{ "NAME", Name },
{ "POINTS", Points}
};
}
}
}

View File

@@ -0,0 +1,34 @@
using DbController;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Template : ArmyBase, IDbModel<int?>
{
[CompareField("template_id")]
public int TemplateId { get; set; }
[CompareField("user_id")]
public int UserId { get; set; }
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("force")]
public int Force { get; set; }
public int? GetIdentifier()
{
return TemplateId > 0 ? TemplateId : null;
}
public override Dictionary<string, object?> GetParameters()
{
var baseParams = base.GetParameters();
baseParams["TEMPLATE_ID"] = TemplateId;
baseParams["USER_ID"] = UserId;
baseParams["NAME"] = Name;
baseParams["FORCE"] = Force;
return baseParams;
}
}
}

View File

@@ -0,0 +1,105 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Unit : LocalizationModelBase<UnitDescription>, IDbModel<int?>
{
[CompareField("unit_id")]
public int UnitId { get; set; }
[CompareField("fraction_id")]
public int FractionId { get; set; }
[CompareField("class_id")]
public int ClassId { get; set; }
[CompareField("troop_quantity")]
public int TroopQuantity { get; set; }
[CompareField("defense")]
public int Defense { get; set; }
[CompareField("moving")]
public int Moving { get; set; }
[CompareField("primary_weapon_id")]
public int PrimaryWeaponId { get; set; }
[CompareField("secondary_weapon_id")]
public int? SecondaryWeaponId { get; set; }
[CompareField("first_ability_id")]
public int? FirstAbilityId { get; set; }
[CompareField("second_ability_id")]
public int? SecondAbilityId { get; set; }
[CompareField("image")]
public byte[]? Image { get; set; }
public int Force { get; set; }
public string ConvertedImage { get; set; } = string.Empty;
public Weapon? PrimaryWeapon { get; set; }
public Weapon? SecondaryWeapon { get; set; }
public Class? Class { get; set; }
public Ability? FirstAbility { get; set; }
public Ability? SecondAbility { get; set; }
[CompareField("quantity")]
public int Quantity { get; set; }
public int ForceOfQuantity { get; set; }
public int? GetIdentifier()
{
return UnitId > 0 ? UnitId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "UNIT_ID", UnitId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description },
{ "MECHANIC", description.Mechanic }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "UNIT_ID", UnitId },
{ "FRACTION_ID", FractionId },
{ "CLASS_ID", ClassId },
{ "TROOP_QUANTITY", TroopQuantity },
{ "DEFENSE", Defense },
{ "MOVING", Moving },
{ "PRIMARY_WEAPON_ID", PrimaryWeaponId },
{ "SECONDARY_WEAPON_ID", SecondaryWeaponId },
{ "FIRST_ABILITY_ID", FirstAbilityId },
{ "SECOND_ABILITY_ID" , SecondAbilityId },
{ "QUANTITY", Quantity }
};
}
}
public class UnitDescription : ILocalizationHelper
{
[CompareField("unit_id")]
public int UnitId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
[CompareField("mechanic")]
public string Mechanic { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,56 @@
using DbController;
namespace Tabletop.Core.Models
{
public sealed class User : IDbModel<int?>
{
[CompareField("user_id")]
public int UserId { get; set; }
[CompareField("username")]
public string Username { get; set; } = string.Empty;
[CompareField("display_name")]
public string DisplayName { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
[CompareField("main_fraction_id")]
public int MainFractionId { get; set; }
[CompareField("password")]
public string Password { get; set; } = string.Empty;
[CompareField("salt")]
public string Salt { get; set; } = string.Empty;
[CompareField("last_login")]
public DateTime LastLogin { get; set; }
[CompareField("registration_date")]
public DateTime RegistrationDate { get; set; }
[CompareField("image")]
public byte[]? Image { get; set; }
public string ConvertedImage { get; set; } = string.Empty;
public List<Permission> Permissions { get; set; } = [];
public List<Unit> Units { get; set; } = [];
public int? GetIdentifier()
{
return UserId > 0 ? UserId : null;
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "USER_ID", UserId },
{ "USERNAME", Username },
{ "DISPLAY_NAME", DisplayName },
{ "DESCRIPTION", Description },
{ "MAIN_FRACTION_ID", MainFractionId },
{ "PASSWORD", Password },
{ "SALT", Salt },
{ "LAST_LOGIN", LastLogin },
{ "REGISTRATION_DATE", RegistrationDate },
{ "IMAGE", Image }
};
}
}
}

View File

@@ -0,0 +1,12 @@
using DbController;
namespace Tabletop.Core.Models
{
public sealed class UserPermission
{
[CompareField("user_id")]
public int UserId { get; set; }
[CompareField("permission_id")]
public int PermissionId { get; set; }
}
}

View File

@@ -0,0 +1,69 @@
using DbController;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models.Abstract;
namespace Tabletop.Core.Models
{
public class Weapon : LocalizationModelBase<WeaponDescription>, IDbModel<int?>
{
[CompareField("weapon_id")]
public int WeaponId { get; set; }
[CompareField("attack")]
public int Attack { get; set; }
[CompareField("quality")]
public int Quality { get; set; }
[CompareField("range")]
public int Range { get; set; }
[CompareField("dices")]
public int Dices { get; set; }
public int? GetIdentifier()
{
return WeaponId > 0 ? WeaponId : null;
}
public IEnumerable<Dictionary<string, object?>> GetLocalizedParameters()
{
foreach (var description in Description)
{
yield return new Dictionary<string, object?>
{
{ "PERMISSION_ID", WeaponId },
{ "NAME", description.Name },
{ "DESCRIPTION", description.Description }
};
}
}
public Dictionary<string, object?> GetParameters()
{
return new Dictionary<string, object?>
{
{ "WEAPON_ID", WeaponId },
{ "ATTACK", Attack },
{ "QUALITY", Quality },
{ "RANGE", Range },
{ "DICES", Dices }
};
}
}
public class WeaponDescription : ILocalizationHelper
{
[CompareField("weapon_id")]
public int WeaponId { get; set; }
[CompareField("code")]
public string Code { get; set; } = string.Empty;
[CompareField("name")]
public string Name { get; set; } = string.Empty;
[CompareField("description")]
public string Description { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,209 @@
using DbController;
using System.Globalization;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class AbilityService : IModelService<Ability, int, AbilityFilter>
{
public async Task CreateAsync(Ability input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Fractions
(
Quality,
Force
)
VALUES
(
@QUALITY,
@FORCE
); {dbController.GetLastIdSql()}";
input.AbilityId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO AbilityDescription
(
AbilityId,
Code,
Name,
Description,
Mechanic
)
VALUES
(
@ABILITY_ID,
@CODE,
@NAME,
@DESCRIPTION,
@MECHANIC
)";
var parameters = new
{
ABILTIY_ID = input.AbilityId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Ability input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM Abilities WHERE AbilityId = @ABILITY_ID";
await dbController.QueryAsync(sql, new
{
ABILITY_ID = input.AbilityId
}, cancellationToken);
}
public static async Task<List<Ability>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Abilities";
var list = await dbController.SelectDataAsync<Ability>(sql, cancellationToken: cancellationToken);
await LoadClassDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<Ability?> GetAsync(int abilityId, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"SELECT * FROM Abilities WHERE AbilityId = @ABILITY_ID";
var ability = await dbController.GetFirstAsync<Ability>(sql, new
{
ABILITY_ID = abilityId
}, cancellationToken);
return ability;
}
private static async Task LoadClassDescriptionsAsync(List<Ability> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> abilityIds = list.Select(x => x.AbilityId);
string sql = $"SELECT * FROM AbilityDescription WHERE AbilityId IN ({string.Join(",", abilityIds)})";
List<AbilityDescription> descriptions = await dbController.SelectDataAsync<AbilityDescription>(sql, null, cancellationToken);
foreach (var item in list)
{
item.Description = [.. descriptions.Where(x => x.AbilityId == item.AbilityId)];
}
}
}
public async Task UpdateAsync(Ability input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"UPDATE Abilities SET
Quality = @QUALITY,
Force = @FORCE
WHERE AbilityId = @ABILITY_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"UPDATE AbilityDescription SET
Name = @NAME,
Description = @DESCRIPTION,
Mechanic = @MECHANIC
WHERE AbilityId = @ABILITY_ID AND Code = @CODE";
var parameters = new
{
ABILITY_ID = input.AbilityId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description,
MECHANIC = description.Mechanic
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task<List<Ability>> GetAsync(AbilityFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT ad.Name, a.* " +
"FROM AbilityDescription ad " +
"INNER JOIN Abilities a " +
"ON (a.AbilityId = ad.AbilityId) " +
"WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
sqlBuilder.AppendLine(@$" ORDER BY Name ASC ");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
// Zum Debuggen schreiben wir den Wert einmal als Variabel
string sql = sqlBuilder.ToString();
List<Ability> list = await dbController.SelectDataAsync<Ability>(sql, GetFilterParameter(filter), cancellationToken);
await LoadAbilityDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<int> GetTotalAsync(AbilityFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) AS record_count FROM AbilityDescription WHERE 1 = 1 ");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public string GetFilterWhere(AbilityFilter filter)
{
StringBuilder sqlBuilder = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sqlBuilder.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
string sql = sqlBuilder.ToString();
return sql;
}
public Dictionary<string, object?> GetFilterParameter(AbilityFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "CULTURE", CultureInfo.CurrentCulture.Name }
};
}
private static async Task LoadAbilityDescriptionsAsync(List<Ability> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> abilityIds = list.Select(x => x.AbilityId);
string sql = $"SELECT * FROM AbilityDescription WHERE AbilityId IN ({string.Join(",", abilityIds)})";
List<AbilityDescription> descriptions = await dbController.SelectDataAsync<AbilityDescription>(sql, null, cancellationToken);
foreach (var ability in list)
{
ability.Description = [.. descriptions.Where(x => x.AbilityId == ability.AbilityId)];
}
}
}
}
}

View File

@@ -0,0 +1,128 @@
using DbController;
using DbController.SqlServer;
using Microsoft.Extensions.Configuration;
using System.Globalization;
using Tabletop.Core.Interfaces;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public static class AppdataService
{
public static string[] SupportedCultureCodes => [.. SupportedCultures.Select(x => x.Name)];
public static CultureInfo[] SupportedCultures =>
[
new ("en"),
new ("de")
];
public static bool FirstUserExists { get; set; } = false;
public static List<Permission> Permissions { get; set; } = [];
public static List<Unit> Units { get; set; } = [];
public static List<Weapon> Weapons { get; set; } = [];
public static List<Fraction> Fractions { get; set; } = [];
public static List<Gamemode> Gamemodes { get; set; } = [];
public static List<Class> Classes { get; set; } = [];
public static List<Ability> Abilities { get; set; } = [];
public static List<Layout> Layouts { get; set; } = [];
public static List<Surface> Surfaces { get; set; } = [];
public static readonly Dictionary<string, Game> Games = [];
private static IConfiguration? _configuration;
public static async Task InitAsync(IConfiguration configuration)
{
_configuration = configuration;
using IDbController dbController = new SqlController(ConnectionString);
Permissions = await PermissionService.GetAllAsync(dbController);
FirstUserExists = await UserService.FirstUserExistsAsync(dbController);
Units = await UnitService.GetAllAsync(dbController);
Weapons = await WeaponService.GetAllAsync(dbController);
Fractions = await FractionService.GetAllAsync(dbController);
Gamemodes = await GamemodeService.GetAllAsync(dbController);
Classes = await ClassService.GetAllAsync(dbController);
Abilities = await AbilityService.GetAllAsync(dbController);
Layouts = await LayoutService.GetAllAsync(dbController);
Surfaces = await SurfaceService.GetAllAsync(dbController);
}
/// <summary>
/// Creates or updates an object in the corresponding list fro the type <see cref="T"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input"></param>
public static void UpdateRecord<T>(T input) where T : class, IDbModel<int?>, new()
{
List<T> list = GetList<T>();
T? item = list.FirstOrDefault(x => x.GetIdentifier() == input.GetIdentifier());
if (item is null)
{
list.Add(input);
}
else
{
int index = list.IndexOf(item);
list[index] = input;
}
}
/// <summary>
/// Deletes an item from the corresponding object list.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input"></param>
public static void DeleteRecord<T>(T input) where T : class, IDbModel<int?>, new()
{
List<T> list = GetList<T>();
list.Remove(input);
}
/// <summary>
/// Gets the corresponding list for the type <see cref="T"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>This method never returns null. When no list for <see cref="T"/> is specified, it returns a new empty list</returns>
public static List<T> GetList<T>() where T : class, IDbModel<int?>, new()
{
var input = new T();
object tmp = input switch
{
Permission => Permissions,
_ => new List<T>()
};
List<T> list = (List<T>)tmp;
return list;
}
public static string ConnectionString => _configuration?["Sql:ConnectionString"] ?? string.Empty;
public static int PageLimit => _configuration?.GetValue<int>("Filter:PageLimit") ?? 30;
public static string ApiKey => _configuration?["IpGeolocation:ApiKey"] ?? string.Empty;
public static CultureInfo ToCulture(this ILocalizationHelper helper)
{
var culture = SupportedCultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals(helper.Code, StringComparison.OrdinalIgnoreCase));
if (culture is null)
{
return SupportedCultures[0];
}
else
{
return culture;
}
}
}
}

View File

@@ -0,0 +1,62 @@
using DbController;
using DbController.SqlServer;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class AuthService(AuthenticationStateProvider authenticationStateProvider, UserService userService)
{
/// <summary>
/// Converts the active claims into a <see cref="User"/> object
/// </summary>
/// <returns></returns>
public async Task<User?> GetUserAsync(IDbController? dbController = null)
{
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity is not null && user.Identity.IsAuthenticated)
{
Claim? claim = user.FindFirst("UserId");
if (claim is null)
{
return null;
}
var userId = Convert.ToInt32(claim.Value);
bool shouldDispose = dbController is null;
dbController ??= new SqlController(AppdataService.ConnectionString);
var result = await userService.GetAsync(userId, dbController);
if (shouldDispose)
{
dbController.Dispose();
}
return result;
}
return null;
}
/// <summary>
/// Checks if the currently logged in user as a specific role within it's claims.
/// </summary>
/// <param name="roleName"></param>
/// <returns></returns>
public async Task<bool> HasRole(string roleName)
{
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
return user.IsInRole(roleName);
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tabletop.Core.Services
{
internal class CaptureService
{
}
}

View File

@@ -0,0 +1,53 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class ClassService : IModelService<Class, int>
{
public Task CreateAsync(Class input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task DeleteAsync(Class input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public static async Task<List<Class>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Classes";
var list = await dbController.SelectDataAsync<Class>(sql, cancellationToken: cancellationToken);
await LoadClassDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public Task<Class?> GetAsync(int identifier, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
private static async Task LoadClassDescriptionsAsync(List<Class> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> classIds = list.Select(x => x.ClassId);
string sql = $"SELECT * FROM ClassDescription WHERE ClassId IN ({string.Join(",", classIds)})";
List<ClassDescription> descriptions = await dbController.SelectDataAsync<ClassDescription>(sql, null, cancellationToken);
foreach (var item in list)
{
item.Description = [.. descriptions.Where(x => x.ClassId == item.ClassId)];
}
}
}
public Task UpdateAsync(Class input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,132 @@
using DbController;
using Microsoft.AspNetCore.Http;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class ConnectionLogService(GeolocationService geolocationService)
{
private readonly GeolocationService _geolocationService = geolocationService;
public static async Task CreateAsync(ConnectionLog input, IDbController dbController, CancellationToken cancellationToken = default)
{
// SQL-Insert-Statement für das ConnectionLog
string sql = $@"
INSERT INTO ConnectionLogs
(
IpAddress,
ConnectionTime,
UserAgent,
Referrer,
RequestedUrl,
Geolocation,
SessionId,
StatusCode,
DeviceType,
OperatingSystem
)
VALUES
(
@IP_ADDRESS,
@CONNECTION_TIME,
@USER_AGENT,
@REFERRER,
@REQUESTED_URL,
@GEOLOCATION,
@SESSION_ID,
@STATUS_CODE,
@DEVICE_TYPE,
@OPERATING_SYSTEM
); {dbController.GetLastIdSql()}";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
}
public async Task<ConnectionLog> GetConnectionLogAsync(HttpContext context)
{
// Extrahiere die IP-Adresse
var ipAddress = context.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
// Hole die Geolocation (Stadt, Land, Koordinaten etc.) basierend auf der IP-Adresse
var geolocation = await _geolocationService.GetGeolocationAsync(ipAddress);
// Extrahiere den User-Agent-Header aus dem HttpContext
var userAgent = context.Request.Headers["User-Agent"].FirstOrDefault() ?? "Unknown";
// Bestimmung des DeviceTypes und des Betriebssystems
var deviceType = await GetDeviceType(userAgent);
var operatingSystem = await GetOperatingSystem(userAgent);
// Erstelle das ConnectionLog
return new ConnectionLog
{
IpAddress = ipAddress,
ConnectionTime = DateTime.UtcNow,
UserAgent = userAgent,
Referrer = context.Request.Headers["Referer"].FirstOrDefault() ?? "Unknown",
RequestedUrl = context.Request.Path,
Geolocation = geolocation, // Füge die Geolocation hinzu
SessionId = context.Session?.Id ?? Guid.NewGuid().ToString(),
StatusCode = context.Response.StatusCode,
DeviceType = deviceType,
OperatingSystem = operatingSystem
};
}
// Methode zur Bestimmung des Device-Typs anhand des User-Agent-Strings
public static Task<string> GetDeviceType(string userAgent)
{
string deviceType;
if (userAgent.Contains("Mobi") || userAgent.Contains("Android") || userAgent.Contains("iPhone") || userAgent.Contains("iPad"))
{
deviceType = "Mobile";
}
else if (userAgent.Contains("Tablet") || userAgent.Contains("iPad"))
{
deviceType = "Tablet";
}
else
{
deviceType = "Desktop"; // Default ist Desktop
}
return Task.FromResult(deviceType); // Verpackt den Wert in einem Task
}
// Methode zur Bestimmung des Betriebssystems anhand des User-Agent-Strings
public static Task<string> GetOperatingSystem(string userAgent)
{
string operatingSystem;
if (userAgent.Contains("Windows"))
{
operatingSystem = "Windows";
}
else if (userAgent.Contains("Mac OS"))
{
operatingSystem = "macOS";
}
else if (userAgent.Contains("Linux"))
{
operatingSystem = "Linux";
}
else if (userAgent.Contains("Android"))
{
operatingSystem = "Android";
}
else if (userAgent.Contains("iPhone") || userAgent.Contains("iPad"))
{
operatingSystem = "iOS";
}
else
{
operatingSystem = "Other"; // Default: Unbekanntes Betriebssystem
}
return Task.FromResult(operatingSystem); // Verpackt den Wert in einem Task
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tabletop.Core.Services
{
internal class EliminationService
{
}
}

View File

@@ -0,0 +1,189 @@
using DbController;
using System.Globalization;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class FractionService : IModelService<Fraction, int, FractionFilter>
{
public async Task CreateAsync(Fraction input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Fractions
(
)
VALUES
(
); {dbController.GetLastIdSql()}";
input.FractionId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO FractionDescription
(
FractionId,
Code,
Name,
ShortName,
Description
)
VALUES
(
@FRACTION_ID,
@CODE,
@NAME,
@SHORT_NAME,
@DESCRIPTION
)";
var parameters = new
{
FRACTION_ID = input.FractionId,
CODE = description.Code,
NAME = description.Name,
SHORT_NAME = description.ShortName,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Fraction input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM Fractions WHERE FractionId = @FRACTION_ID";
await dbController.QueryAsync(sql, new
{
FRACTION_ID = input.FractionId
}, cancellationToken);
}
public async Task<Fraction?> GetAsync(int fractionId, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"SELECT * FROM Fractions WHERE FractionId = @FRACTION_ID";
var fraction = await dbController.GetFirstAsync<Fraction>(sql, new
{
FRACTION_ID = fractionId
}, cancellationToken);
return fraction;
}
public static async Task<List<Fraction>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Fractions";
var list = await dbController.SelectDataAsync<Fraction>(sql, cancellationToken: cancellationToken);
await LoadFractionDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task UpdateAsync(Fraction input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"UPDATE Fractions SET
Image = @IMAGE
WHERE FractionId = @FRACTION_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"UPDATE FractionDescription SET
Name = @NAME,
Description = @DESCRIPTION,
ShortName = @SHORT_NAME
WHERE FractionId = @FRACTION_ID AND Code = @CODE";
var parameters = new
{
FRACTION_ID = input.FractionId,
CODE = description.Code,
NAME = description.Name,
SHORT_NAME = description.ShortName,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task<List<Fraction>> GetAsync(FractionFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT fd.Name, f.* " +
"FROM FractionDescription fd " +
"INNER JOIN Fractions f " +
"ON (f.FractionId = fd.FractionId) " +
"WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
sqlBuilder.AppendLine(@$" ORDER BY Name ASC ");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
// Zum Debuggen schreiben wir den Wert einmal als Variabel
string sql = sqlBuilder.ToString();
List<Fraction> list = await dbController.SelectDataAsync<Fraction>(sql, GetFilterParameter(filter), cancellationToken);
await LoadFractionDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<int> GetTotalAsync(FractionFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) AS record_count FROM FractionDescription WHERE 1 = 1 ");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public string GetFilterWhere(FractionFilter filter)
{
StringBuilder sqlBuilder = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sqlBuilder.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
string sql = sqlBuilder.ToString();
return sql;
}
public Dictionary<string, object?> GetFilterParameter(FractionFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "CULTURE", CultureInfo.CurrentCulture.Name }
};
}
private static async Task LoadFractionDescriptionsAsync(List<Fraction> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> fractionIds = list.Select(x => x.FractionId);
string sql = $"SELECT * FROM FractionDescription WHERE FractionId IN ({string.Join(",", fractionIds)})";
List<FractionDescription> descriptions = await dbController.SelectDataAsync<FractionDescription>(sql, null, cancellationToken);
foreach (var fraction in list)
{
fraction.Description = [.. descriptions.Where(x => x.FractionId == fraction.FractionId)];
}
}
}
}
}

View File

@@ -0,0 +1,182 @@
using DbController;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class GameService(PlayerService playerService, UserService userService) : IModelService<Game, int, GameFilter>
{
private readonly PlayerService _playerService = playerService;
private readonly UserService _userService = userService;
public async Task CreateAsync(Game input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = $@"INSERT INTO Games
(
UserId,
GamemodeId,
SurfaceId,
LayoutId,
Name,
NumberOfRounds,
Force,
NumberOfTeams,
NumberOfPlayers,
Date
)
VALUES
(
@USER_ID,
@GAMEMODE_ID,
@SURFACE_ID,
@LAYOUT_ID,
@NAME,
@NUMBER_OF_ROUNDS,
@FORCE,
@NUMBER_OF_TEAMS,
@NUMBER_OF_PLAYERS,
@DATE
); {dbController.GetLastIdSql()}";
input.GameId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var item in input.Players)
{
item.GameId = input.GameId;
await _playerService.CreateAsync(item, dbController, cancellationToken);
}
}
public async Task DeleteAsync(Game input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Games WHERE GameId = @GAME_ID";
await dbController.QueryAsync(sql, new
{
GAME_ID = input.GameId,
}, cancellationToken);
}
public async Task<Game?> GetAsync(int gameId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Games WHERE GameId = @GAME_ID";
var game = await dbController.GetFirstAsync<Game>(sql, new
{
GAME_ID = gameId
}, cancellationToken);
if (game != null)
{
game.Players = await _playerService.GetGamePlayersAsync(game.GameId, dbController, cancellationToken);
}
return game;
}
public static async Task<List<Game>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "SELECT * FROM Games";
var list = await dbController.SelectDataAsync<Game>(sql, cancellationToken: cancellationToken);
return list;
}
public async Task<List<Game>> GetAsync(GameFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sb = new();
sb.AppendLine("SELECT DISTINCT g.* FROM Games g LEFT JOIN Players p ON g.GameId = p.GameId WHERE p.UserId = @USER_ID OR g.UserId = @USER_ID ");
sb.AppendLine(GetFilterWhere(filter));
sb.AppendLine(@$" ORDER BY Date DESC ");
sb.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
string sql = sb.ToString();
List<Game> list = await dbController.SelectDataAsync<Game>(sql, GetFilterParameter(filter), cancellationToken);
if (list.Count != 0)
{
foreach (var item in list)
{
item.Players = await _playerService.GetGamePlayersAsync(item.GameId, dbController, cancellationToken);
item.Host = await _userService.GetUsernameAsync(item.UserId, dbController, cancellationToken);
}
}
return list;
}
public async Task<List<Game>> GetLastGamesOfPlayerAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "SELECT TOP 5 g.GameId, g.GamemodeId, g.Date, g.Force FROM Games g INNER JOIN Players p ON g.GameId = p.GameId WHERE p.UserId = @USER_ID ORDER BY g.Date DESC";
var list = await dbController.SelectDataAsync<Game>(sql, new
{
USER_ID = userId,
}, cancellationToken: cancellationToken);
return list;
}
public Dictionary<string, object?> GetFilterParameter(GameFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "USER_ID", filter.UserId }
};
}
public string GetFilterWhere(GameFilter filter)
{
StringBuilder sb = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sb.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
string sql = sb.ToString();
return sql;
}
public async Task<int> GetTotalAsync(GameFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sb = new();
sb.AppendLine("SELECT COUNT(*) FROM Games WHERE UserId = @USER_ID");
sb.AppendLine(GetFilterWhere(filter));
string sql = sb.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public async Task UpdateAsync(Game input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"UPDATE Games SET
GamemodeId = @GAMEMODE_ID,
SurfaceId = @SURFACE_ID,
LayoutId = @LAYOUT_ID,
Name = @NAME,
NumberOfRounds = @NUMBER_OF_ROUNDS,
Force = @FORCE,
NumberOfTeams = @NUMBER_OF_TEAMS,
NumberOfPlayers = @NUMBER_OF_PLAYERS,
Date = @DATE
WHERE GameId = @GAME_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
}
}
}

View File

@@ -0,0 +1,189 @@
using DbController;
using System.Globalization;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class GamemodeService : IModelService<Gamemode, int, GamemodeFilter>
{
public async Task CreateAsync(Gamemode input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Gamemodes
(
)
VALUES
(
); {dbController.GetLastIdSql()}";
input.GamemodeId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO GamemodeDescription
(
GamemodeId,
Code,
Name,
Description,
Mechanic
)
VALUES
(
@GAMEMODE_ID,
@CODE,
@NAME,
@DESCRIPTION,
@MECHANIC
)";
var parameters = new
{
GAMEMODE_ID = input.GamemodeId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Gamemode input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM Gamemodes WHERE GamemodeId = @GAMEMODE_ID";
await dbController.QueryAsync(sql, new
{
GAMEMODE_ID = input.GamemodeId
}, cancellationToken);
}
public async Task<Gamemode?> GetAsync(int gamemodeId, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"SELECT * FROM Gamemodes WHERE GamemodeId = @GAMEMODE_ID";
var gamemode = await dbController.GetFirstAsync<Gamemode>(sql, new
{
GAMEMODE_ID = gamemodeId
}, cancellationToken);
return gamemode;
}
public static async Task<List<Gamemode>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Gamemodes";
var list = await dbController.SelectDataAsync<Gamemode>(sql, cancellationToken: cancellationToken);
await LoadGamemodeDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task UpdateAsync(Gamemode input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql;
//string sql = @"UPDATE Gamemodes SET
// Image = @IMAGE
// WHERE GamemodeId = @GAMEMODE_ID";
//await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"UPDATE GamemodeDescription SET
Name = @NAME,
Description = @DESCRIPTION,
Mechanic = @MECHANIC
WHERE GamemodeId = @GAMEMODE_ID AND Code = @CODE";
var parameters = new
{
GAMEMODE_ID = input.GamemodeId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description,
MECHANIC = description.Mechanic
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task<List<Gamemode>> GetAsync(GamemodeFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT gd.Name, g.* " +
"FROM GamemodeDescription gd " +
"INNER JOIN Gamemodes g " +
"ON (g.GamemodeId = gd.GamemodeId) " +
"WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
sqlBuilder.AppendLine(@$" ORDER BY Name ASC ");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
// Zum Debuggen schreiben wir den Wert einmal als Variabel
string sql = sqlBuilder.ToString();
List<Gamemode> list = await dbController.SelectDataAsync<Gamemode>(sql, GetFilterParameter(filter), cancellationToken);
await LoadGamemodeDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<int> GetTotalAsync(GamemodeFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) AS record_count FROM GamemodeDescription WHERE 1 = 1 ");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public string GetFilterWhere(GamemodeFilter filter)
{
StringBuilder sqlBuilder = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sqlBuilder.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
string sql = sqlBuilder.ToString();
return sql;
}
public Dictionary<string, object?> GetFilterParameter(GamemodeFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "CULTURE", CultureInfo.CurrentCulture.Name }
};
}
private static async Task LoadGamemodeDescriptionsAsync(List<Gamemode> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> gamemodeIds = list.Select(x => x.GamemodeId);
string sql = $"SELECT * FROM GamemodeDescription WHERE GamemodeId IN ({string.Join(",", gamemodeIds)})";
List<GamemodeDescription> descriptions = await dbController.SelectDataAsync<GamemodeDescription>(sql, null, cancellationToken);
foreach (var gamemode in list)
{
gamemode.Description = [.. descriptions.Where(x => x.GamemodeId == gamemode.GamemodeId)];
}
}
}
}
}

View File

@@ -0,0 +1,51 @@
namespace Tabletop.Core.Services
{
public class GeolocationService(HttpClient httpClient)
{
private readonly HttpClient _httpClient = httpClient;
public Task<string> GetGeolocationAsync(string ipAddress)
{
//// Überprüfen, ob die IP-Adresse lokal ist (127.0.0.1 oder localhost)
//if (await IsLocalIpAddressAsync(ipAddress))
//{
// // Wenn es eine lokale IP-Adresse ist, keine Geolokalisierung durchführen
// return "Lokale IP-Adresse";
//}
//try
//{
// // URL für Geolokalisierung
// var url = $"https://api.ipgeolocation.io/ipgeo?apiKey={AppdataService.ApiKey}&ip={ipAddress}";
// // HTTP-Anfrage senden
// var response = await _httpClient.GetAsync(url);
// // Fehlerbehandlung: Prüfen, ob der Statuscode erfolgreich war
// response.EnsureSuccessStatusCode(); // Wird eine Ausnahme werfen, wenn der Statuscode nicht erfolgreich ist
// // Rückgabe der Geolokalisierungsdaten
// return await response.Content.ReadAsStringAsync();
//}
//catch (HttpRequestException ex)
//{
// // Fehlerbehandlung: Loggen oder Weiterverarbeiten des Fehlers
// throw new Exception($"Fehler bei der Geolokalisierung: {ex.Message}", ex);
//}
return Task.FromResult("Lokale IP-Adresse");
}
// Hilfsmethode, um zu prüfen, ob die IP-Adresse lokal ist
private static Task<bool> IsLocalIpAddressAsync(string ipAddress)
{
// Überprüfen, ob die IP-Adresse eine der folgenden ist
bool isLocal = ipAddress == "127.0.0.1" || ipAddress == "localhost" ||
ipAddress.StartsWith("10.") || ipAddress.StartsWith("192.168.") ||
ipAddress.StartsWith("172.16.");
return Task.FromResult(isLocal); // Rückgabe des Ergebnisses als Task
}
}
}

View File

@@ -0,0 +1,97 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class LayoutService : IModelService<Layout, int>
{
public async Task CreateAsync(Layout input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Layouts
(
GamemodeId,
Teams,
Width,
Height
)
VALUES
(
@GAMEMODE_ID
@TEAMS,
@WIDTH,
@HEIGHT
); {dbController.GetLastIdSql()}";
input.LayoutId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
}
public async Task DeleteAsync(Layout input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM Layouts WHERE LayoutId = @LAYOUT_ID";
await dbController.QueryAsync(sql, new
{
LAYOUT_ID = input.LayoutId
}, cancellationToken);
}
public static async Task<List<SetupZone>> GetSetupZonesAsync(int layoutId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"
SELECT sz.SetupZoneId, sz.LayoutId, z.*
FROM SetupZones sz
INNER JOIN Zones z ON (z.ZoneId = sz.ZoneId)
WHERE sz.LayoutId = @LAYOUT_ID";
var list = await dbController.SelectDataAsync<SetupZone>(sql, new
{
LAYOUT_ID = layoutId
}, cancellationToken);
return list;
}
public static async Task<List<CaptureZone>> GetCaptureZonesAsync(int layoutId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"
SELECT cz.CaptureZoneId, cz.LayoutId, z.*
FROM CaptureZones cz
INNER JOIN Zones z ON (z.ZoneId = cz.ZoneId)
WHERE cz.LayoutId = @LAYOUT_ID";
var list = await dbController.SelectDataAsync<CaptureZone>(sql, new
{
LAYOUT_ID = layoutId
}, cancellationToken);
return list;
}
public static async Task<List<Layout>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Layouts";
var list = await dbController.SelectDataAsync<Layout>(sql, cancellationToken: cancellationToken);
foreach(var layout in list)
{
layout.SetupZones = await GetSetupZonesAsync(layout.LayoutId, dbController, cancellationToken);
layout.CaptureZones = await GetCaptureZonesAsync(layout.LayoutId, dbController, cancellationToken);
}
return list;
}
public Task<Layout?> GetAsync(int battlegroundId, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task UpdateAsync(Layout input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,57 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class MoveService : IModelService<Move, int>
{
public async Task CreateAsync(Move input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Moves
(
PlayerId,
GameId,
TurnNr,
MoveNr,
StartMove,
EndMove
)
VALUES
(
@PLAYER_ID
@GAME_ID,
@TURN_NR,
@MOVE_NR,
@START_MOVE,
@END_END
); {dbController.GetLastIdSql()}";
input.MoveId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
}
public Task DeleteAsync(Move input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task<Move?> GetAsync(int identifier, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public async Task UpdateAsync(Move input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"UPDATE Moves SET
PlayerId = @PLAYER_ID,
GameId = @GAME_ID,
TurnNr = @TURN_NR,
MoveNr = @MOVE_NR,
StartMove = @START_MOVE,
EndMove = @END_END
WHERE MoveId = @MOVE_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
}
}
}

View File

@@ -0,0 +1,84 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class PermissionService
{
public static async Task<List<Permission>> GetUserPermissionsAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT p.*
FROM UserPermissions up
INNER JOIN Permissions p ON (p.PermissionId = up.PermissionId)
WHERE UserId = @USER_ID";
var list = await dbController.SelectDataAsync<Permission>(sql, new
{
USER_ID = userId
}, cancellationToken);
await LoadPermissionDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task UpdateUserPermissionsAsync(User user, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Step 1: Delete all permissions for the user.
string sql = "DELETE FROM UserPermissions WHERE UserId = @USER_ID";
await dbController.QueryAsync(sql, new
{
USER_ID = user.UserId
}, cancellationToken);
// Step 2: Add all permissions from the object back.
foreach (var permission in user.Permissions)
{
sql = @"INSERT INTO UserPermissions
(
UserId,
PermissionId
)
VALUES
(
@USER_ID,
@PERMISSION_ID
)";
await dbController.QueryAsync(sql, new
{
USER_ID = user.UserId,
PERMISSION_ID = permission.PermissionId
}, cancellationToken);
}
}
public static async Task<List<Permission>> GetAllAsync(IDbController dbController)
{
string sql = "SELECT * FROM Permissions";
var list = await dbController.SelectDataAsync<Permission>(sql);
await LoadPermissionDescriptionsAsync(list, dbController);
return list;
}
private static async Task LoadPermissionDescriptionsAsync(List<Permission> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> permissionIds = list.Select(x => x.PermissionId);
string sql = $"SELECT * FROM PermissionDescription WHERE PermissionId IN ({string.Join(",", permissionIds)})";
List<PermissionDescription> descriptions = await dbController.SelectDataAsync<PermissionDescription>(sql, null, cancellationToken);
foreach (var permission in list)
{
permission.Description = [.. descriptions.Where(x => x.PermissionId == permission.PermissionId)];
}
}
}
}
}

View File

@@ -0,0 +1,127 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class PlayerService(UserService userService, UnitService unitService) : IModelService<Player, int>
{
private readonly UserService _userService = userService;
private readonly UnitService _unitService = unitService;
public async Task CreateAsync(Player input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Players
(
UserId,
GameId,
FractionId,
Team,
Color,
UsedForce
)
VALUES
(
@USER_ID,
@GAME_ID,
@FRACTION_ID,
@TEAM,
@COLOR,
@USED_FORCE
); {dbController.GetLastIdSql()}";
input.PlayerId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
}
public async Task DeleteAsync(Player Input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Players WHERE PlayerId = @PLAYER_ID";
await dbController.QueryAsync(sql, new
{
Input_ID = Input.PlayerId
}, cancellationToken);
}
public static async Task DeleteByGameAsync(int gameId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Players WHERE GameId = @GAME_ID";
await dbController.QueryAsync(sql, new
{
GAME_ID = gameId
}, cancellationToken);
}
public Task<Player?> GetAsync(int identifier, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public async Task<List<Player>> GetGamePlayersAsync(int gameId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = ("SELECT * FROM Players WHERE GameId = @GAME_ID");
List<Player> list = await dbController.SelectDataAsync<Player>(sql, new
{
GAME_ID = gameId
}, cancellationToken);
if (list.Count != 0)
{
foreach (var item in list)
{
item.Units = await _unitService.GetPlayerUnitsAsync(item.PlayerId, dbController, cancellationToken);
item.User = await _userService.GetUserInformationAsync(item.UserId, dbController, cancellationToken) ?? new();
}
}
return list;
}
public async Task UpdateAsync(Player player, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await UnitService.DeletePlayerUnitsAsync(player.PlayerId, dbController, cancellationToken);
string sql = @"UPDATE Players SET
Points = @POINTS
WHERE PlayerId = @PLAYER_ID";
await dbController.QueryAsync(sql, player.GetParameters(), cancellationToken);
}
public async Task UpdatePlayerOrderAsync(Player player, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"UPDATE Players SET
OrderNr = @ORDER_NR,
StartZone = @START_ZONE
WHERE PlayerId = @PLAYER_ID";
await dbController.QueryAsync(sql, player.GetParameters(), cancellationToken);
}
public async Task UpdatePlayerArmyAsync(Player player, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await UnitService.DeletePlayerUnitsAsync(player.PlayerId, dbController, cancellationToken);
string sql = @"UPDATE Players SET
UsedForce = @USED_FORCE
WHERE PlayerId = @PLAYER_ID";
await dbController.QueryAsync(sql, player.GetParameters(), cancellationToken);
foreach (var unit in player.Units)
{
await _unitService.CreatePlayerUnitAsync(player, unit, dbController, cancellationToken);
}
}
}
}

View File

@@ -0,0 +1,112 @@
using DbController;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class SurfaceService : IModelService<Surface, int>
{
public async Task CreateAsync(Surface input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Surfaces
(
Image
)
VALUES
(
@IMAGE
); {dbController.GetLastIdSql()}";
input.SurfaceId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO SurfaceDescription
(
SurfaceId,
Code,
Name,
Description
)
VALUES
(
@SURFACE_ID,
@CODE,
@NAME,
@DESCRIPTION
)";
var parameters = new
{
BATTLEGROUND_ID = input.SurfaceId,
CODE = description.Code,
NAME = description.Name,
description = description.Description,
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Surface input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM Surfaces WHERE SurfaceId = @SURFACE_ID";
await dbController.QueryAsync(sql, new
{
SURFACE_ID = input.SurfaceId
}, cancellationToken);
}
public static async Task<List<Surface>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "SELECT * FROM Surfaces";
var list = await dbController.SelectDataAsync<Surface>(sql, cancellationToken: cancellationToken);
foreach(var item in list)
{
if(item.Image != null)
{
string base64String = Convert.ToBase64String(item.Image);
item.ConvertedImage = $"data:image/png;base64,{base64String}";
}
}
await LoadSurfaceDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<Surface?> GetAsync(int surfaceId, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"SELECT * FROM Surfaces WHERE SurfaceId = @SURFACE_ID";
var surface = await dbController.GetFirstAsync<Surface>(sql, new
{
SURFACE_ID = surfaceId
}, cancellationToken);
return surface;
}
private static async Task LoadSurfaceDescriptionsAsync(List<Surface> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> surfaceIds = list.Select(x => x.SurfaceId);
string sql = $"SELECT * FROM SurfaceDescription WHERE SurfaceId IN ({string.Join(",", surfaceIds)})";
List<SurfaceDescription> descriptions = await dbController.SelectDataAsync<SurfaceDescription>(sql, null, cancellationToken);
foreach (var battleLocation in list)
{
battleLocation.Description = [.. descriptions.Where(x => x.SurfaceId == battleLocation.SurfaceId)];
}
}
}
public Task UpdateAsync(Surface input, IDbController dbController, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,171 @@
using DbController;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class TemplateService : IModelService<Template, int, TemplateFilter>
{
public async Task CreateAsync(Template input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = $@"INSERT INTO Templates
(
UserId,
FractionId,
Name,
Force,
UsedForce
)
VALUES
(
@USER_ID,
@FRACTION_ID,
@NAME,
@FORCE,
@USED_FORCE
); {dbController.GetLastIdSql()}";
input.TemplateId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach(var unit in input.Units)
{
await UnitService.CreateTemplateUnitAsync(input.TemplateId, unit, dbController, cancellationToken);
}
}
public async Task DeleteAsync(Template input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await UnitService.DeleteTemplateUnitsAsync(input.TemplateId, dbController, cancellationToken);
string sql = "DELETE FROM Templates WHERE TemplateId = @TEMPLATE_ID";
await dbController.QueryAsync(sql, new
{
TEMPLATE_ID = input.TemplateId
}, cancellationToken);
}
public async Task<Template?> GetAsync(int templateId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Templates WHERE TemplateId = @TEMPLATE_ID";
var template = await dbController.GetFirstAsync<Template>(sql, new
{
TEMPLATE_ID = templateId
}, cancellationToken);
return template;
}
public static async Task<List<Template>> GetTemplateOnForceAsync(int user_id, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "SELECT * FROM Templates WHERE UserId = @USER_ID";
var list = await dbController.SelectDataAsync<Template>(sql, new
{
USER_ID = user_id
}, cancellationToken);
foreach (var item in list)
{
item.Units = await UnitService.GetTemplateUnitsAsync(item.TemplateId, dbController, cancellationToken);
}
return list;
}
public async Task<List<Template>> GetAsync(TemplateFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sb = new();
sb.AppendLine("SELECT * FROM Templates WHERE UserId = @USER_ID");
sb.AppendLine(GetFilterWhere(filter));
sb.AppendLine(@$" ORDER BY Name ASC");
sb.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
string sql = sb.ToString();
List<Template> list = await dbController.SelectDataAsync<Template>(sql, GetFilterParameter(filter), cancellationToken);
foreach(var item in list)
{
item.Units = await UnitService.GetTemplateUnitsAsync(item.TemplateId, dbController, cancellationToken);
}
return list;
}
public Dictionary<string, object?> GetFilterParameter(TemplateFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "USER_ID", filter.UserId },
{ "FRACTION_ID", filter.FractionId },
{ "FORCE", filter.Force }
};
}
public string GetFilterWhere(TemplateFilter filter)
{
StringBuilder sb = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sb.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
if (filter.FractionId != 0)
{
sb.AppendLine(@" AND FractionId = @FRACTION_ID");
}
if (filter.Force != 0)
{
sb.AppendLine(@" AND Force = @FORCE");
}
string sql = sb.ToString();
return sql;
}
public async Task<int> GetTotalAsync(TemplateFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sb = new();
sb.AppendLine("SELECT COUNT(*) FROM Templates WHERE UserId = @USER_ID");
sb.AppendLine(GetFilterWhere(filter));
string sql = sb.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public async Task UpdateAsync(Template input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await UnitService.DeleteTemplateUnitsAsync(input.TemplateId, dbController, cancellationToken);
string sql = @"UPDATE Templates SET
FractionId = @FRACTION_ID,
Name = @NAME,
Force = @FORCE,
UsedForce = @USED_FORCE
WHERE TemplateId = @TEMPLATE_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var unit in input.Units)
{
await UnitService.CreateTemplateUnitAsync(input.TemplateId, unit, dbController, cancellationToken);
}
}
}
}

View File

@@ -0,0 +1,393 @@
using DbController;
using System.Globalization;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class UnitService(WeaponService weaponService) : IModelService<Unit, int, UnitFilter>
{
private readonly WeaponService _weaponService = weaponService;
public async Task CreateAsync(Unit input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = $@"INSERT INTO Units
(
FractionId,
ClassId,
TroopQuantity,
Defense,
Moving,
PrimaryWeaponId,
SecondaryWeaponId,
FistAbilityId,
SecondAbilityId
)
VALUES
(
@FRACTION_ID,
@CLASS_ID,
@TROOP_QUANTITY,
@DEFENSE,
@MOVING,
@PRIMARY_WEAPON_ID,
@SECONDARY_WEAPON_ID,
@FIRST_ABILITY_ID,
@SECOND_ABILITY_ID
); {dbController.GetLastIdSql()}";
input.UnitId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO UnitDescription
(
UnitId,
Code,
Name,
Description
)
VALUES
(
@UNIT_ID,
@CODE,
@NAME,
@DESCRIPTION
)";
var parameters = new
{
UNIT_ID = input.UnitId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Unit input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Units WHERE UnitId = @UNIT_ID";
await dbController.QueryAsync(sql, new
{
UNIT_ID = input.UnitId
}, cancellationToken);
}
public async Task DeleteUnitAsync(int userId, int unitId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM UserUnits WHERE UserId = @USER_ID AND UnitId = @UNIT_ID";
await dbController.QueryAsync(sql, new
{
USER_ID = userId,
UNIT_ID = unitId
}, cancellationToken);
}
public async Task<Unit?> GetAsync(int unitId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Units WHERE UnitId = @UNIT_ID";
var unit = await dbController.GetFirstAsync<Unit>(sql, new
{
UNIT_ID = unitId
}, cancellationToken);
if (unit?.PrimaryWeaponId != null && unit?.SecondaryWeaponId != null)
{
unit.PrimaryWeapon = await _weaponService.GetAsync((int)unit.PrimaryWeaponId, dbController, cancellationToken) ?? new();
unit.SecondaryWeapon = await _weaponService.GetAsync((int)unit.SecondaryWeaponId, dbController, cancellationToken) ?? new();
}
return unit;
}
public static async Task<List<Unit>> GetAllAsync(IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "SELECT * FROM Units";
var list = await dbController.SelectDataAsync<Unit>(sql, cancellationToken: cancellationToken);
await LoadUnitDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<List<Unit>> GetAsync(UnitFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT ud.Name, u.* " +
"FROM UnitDescription ud " +
"INNER JOIN Units u " +
"ON (u.UnitId = ud.UnitId) " +
"WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
sqlBuilder.AppendLine(@$" ORDER BY Name ASC ");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
string sql = sqlBuilder.ToString();
List<Unit> list = await dbController.SelectDataAsync<Unit>(sql, GetFilterParameter(filter), cancellationToken);
await LoadUnitDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public Dictionary<string, object?> GetFilterParameter(UnitFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "FRACTION_ID", filter.FractionId },
{ "CULTURE", CultureInfo.CurrentCulture.Name }
};
}
public string GetFilterWhere(UnitFilter filter)
{
StringBuilder sqlBuilder = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sqlBuilder.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
if (filter.FractionId != 0)
{
sqlBuilder.AppendLine(@" AND FractionId = @FRACTION_ID");
}
string sql = sqlBuilder.ToString();
return sql;
}
public async Task<int> GetTotalAsync(UnitFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) AS record_count FROM UnitDescription ud JOIN Units u ON ud.UnitId = u.UnitId WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public static async Task<List<Unit>> GetUserUnitsAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"
SELECT uu.Quantity, u.*
FROM UserUnits uu
INNER JOIN Units u ON (u.UnitId = uu.UnitId)
WHERE uu.UserId = @USER_ID";
var list = await dbController.SelectDataAsync<Unit>(sql, new
{
USER_ID = userId
}, cancellationToken);
await LoadUnitDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task<List<Unit>> GetPlayerUnitsAsync(int playerId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"
SELECT pu.Quantity, u.*
FROM PlayerUnits pu
INNER JOIN Units u ON (u.UnitId = pu.UnitId)
WHERE pu.PlayerId = @PLAYER_ID";
var list = await dbController.SelectDataAsync<Unit>(sql, new
{
PLAYER_ID = playerId
}, cancellationToken);
await LoadUnitDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public static async Task<List<Unit>> GetTemplateUnitsAsync(int templateId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"
SELECT tu.Quantity, u.*
FROM TemplateUnits tu
INNER JOIN Units u ON (u.UnitId = tu.UnitId)
WHERE tu.TemplateId = @TEMPLATE_ID";
var list = await dbController.SelectDataAsync<Unit>(sql, new
{
TEMPLATE_ID = templateId
}, cancellationToken);
await LoadUnitDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public async Task CreateUserUnitAsync(User user, Unit unit, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM UserUnits WHERE UserId = @USER_ID AND UnitId = @UNIT_ID";
await dbController.QueryAsync(sql, new
{
USER_ID = user.UserId,
UNIT_ID = unit.UnitId
}, cancellationToken);
sql = @"INSERT INTO UserUnits
(
UserId,
UnitId,
Quantity
)
VALUES
(
@USER_ID,
@UNIT_ID,
@QUANTITY
)";
await dbController.QueryAsync(sql, new
{
USER_ID = user.UserId,
UNIT_ID = unit.UnitId,
QUANTITY = unit.Quantity
}, cancellationToken);
}
public static async Task DeleteTemplateUnitsAsync(int templateId, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM TemplateUnits WHERE TemplateId = @TEMPLATE_ID";
await dbController.QueryAsync(sql, new
{
TEMPLATE_ID = templateId
}, cancellationToken);
}
public static async Task DeletePlayerUnitsAsync(int playerid, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = "DELETE FROM PlayerUnits WHERE PlayerId = @PLAYER_ID";
await dbController.QueryAsync(sql, new
{
PLAYER_ID = playerid
}, cancellationToken);
}
public static async Task CreateTemplateUnitAsync(int templateId, Unit unit, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"INSERT INTO TemplateUnits
(
TemplateId,
UnitId,
Quantity
)
VALUES
(
@TEMPLATE_ID,
@UNIT_ID,
@QUANTITY
)";
await dbController.QueryAsync(sql, new
{
TEMPLATE_ID = templateId,
UNIT_ID = unit.UnitId,
QUANTITY = unit.Quantity
}, cancellationToken);
}
public async Task CreatePlayerUnitAsync(Player Input, Unit unit, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"INSERT INTO PlayerUnits
(
PlayerId,
UnitId,
Quantity
)
VALUES
(
@PLAYER_ID,
@UNIT_ID,
@QUANTITY
)";
await dbController.QueryAsync(sql, new
{
PLAYER_ID = Input.PlayerId,
UNIT_ID = unit.UnitId,
QUANTITY = unit.Quantity
}, cancellationToken);
}
public async Task UpdateAsync(Unit input, IDbController dbController, CancellationToken cancellationToken = default)
{
string sql = @"UPDATE Units SET
FractionId = @FRACTION_ID,
ClassId = @CLASS_ID,
TroopQuantity = @TROOP_QUANTITY,
Defense = @DEFENSE,
Moving = @MOVING,
PrimaryWeaponId = @PRIMARY_WEAPON_ID,
SecondaryWeaponId = @SECONDARY_WEAPON_ID,
FirstAbilityId = @FIRST_ABILITY_ID,
SecondAbilityId = @SECOND_ABILITY_ID
WHERE UnitId = @UNIT_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"UPDATE UnitDescription SET
Name = @NAME,
Description = @DESCRIPTION,
Mechanic = @MECHANIC
WHERE UnitId = @UNIT_ID AND Code = @CODE";
var parameters = new
{
UNIT_ID = input.UnitId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description,
MECHANIC = description.Mechanic
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
private static async Task LoadUnitDescriptionsAsync(List<Unit> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> unitIds = list.Select(x => x.UnitId);
string sql = $"SELECT * FROM UnitDescription WHERE UnitId IN ({string.Join(",", unitIds)})";
List<UnitDescription> descriptions = await dbController.SelectDataAsync<UnitDescription>(sql, null, cancellationToken);
foreach (var unit in list)
{
unit.Description = [.. descriptions.Where(x => x.UnitId == unit.UnitId)];
}
}
}
}
}

View File

@@ -0,0 +1,298 @@
using DbController;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class UserService(PermissionService permissionService) : IModelService<User, int, UserFilter>
{
private readonly PermissionService _permissionService = permissionService;
public async Task CreateAsync(User input, IDbController dbController, CancellationToken cancellationToken = default)
{
input.LastLogin = DateTime.Now;
input.RegistrationDate = DateTime.Now;
cancellationToken.ThrowIfCancellationRequested();
string sql = $@"INSERT INTO Users
(
Username,
DisplayName,
Password,
Salt,
LastLogin,
RegistrationDate
)
VALUES
(
@USERNAME,
@DISPLAY_NAME,
@PASSWORD,
@SALT,
@LAST_LOGIN,
@REGISTRATION_DATE
); {dbController.GetLastIdSql()}";
input.UserId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
await _permissionService.UpdateUserPermissionsAsync(input, dbController, cancellationToken);
}
public async Task DeleteAsync(User input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Users WHERE UserId = @USER_ID";
await dbController.QueryAsync(sql, new
{
USER_ID = input.UserId
}, cancellationToken);
}
public static async Task DeleteFriendAsync(int userId, int friendId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM UserFriends WHERE UserId = @USER_ID AND FriendId = @FRIEND_ID";
await dbController.QueryAsync(sql, new
{
USER_ID = userId,
FRIEND_ID = friendId
}, cancellationToken);
}
public async Task<User?> GetAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Users WHERE UserId = @USER_ID";
var user = await dbController.GetFirstAsync<User>(sql, new
{
USER_ID = userId
}, cancellationToken);
if (user is not null)
{
user.Permissions = await PermissionService.GetUserPermissionsAsync(user.UserId, dbController, cancellationToken);
}
return user;
}
public static async Task<User?> GetAsync(string username, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Users WHERE UPPER(username) = UPPER(@USERNAME)";
var user = await dbController.GetFirstAsync<User>(sql, new
{
USERNAME = username
}, cancellationToken);
if (user is not null)
{
user.Permissions = await PermissionService.GetUserPermissionsAsync(user.UserId, dbController, cancellationToken);
}
return user;
}
public async Task<User?> GetUserInformationAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT UserId, Username, DisplayName, Image FROM Users WHERE UserId = @USER_ID";
var user = await dbController.GetFirstAsync<User>(sql, new
{
USER_ID = userId
}, cancellationToken);
return user;
}
public async Task<List<User>> GetAsync(UserFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.Append("SELECT UserId, Username, DisplayName, Description, MainFractionId, LastLogin, RegistrationDate, Image FROM Users WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@$" ORDER BY UserId DESC");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
// Zum Debuggen schreiben wir den Wert einmal als Variabel
string sql = sqlBuilder.ToString();
List<User> list = await dbController.SelectDataAsync<User>(sql, GetFilterParameter(filter), cancellationToken);
// Berechtigungen müssen noch geladen werden
List<Permission> permissions = await PermissionService.GetAllAsync(dbController);
sql = "SELECT * FROM UserPermissions";
List<UserPermission> user_permissions = await dbController.SelectDataAsync<UserPermission>(sql, null, cancellationToken);
foreach (var user in list)
{
List<UserPermission> permissions_for_user = [.. user_permissions.Where(x => x.UserId == user.UserId)];
List<int> permission_ids = [.. permissions_for_user.Select(x => x.PermissionId)];
user.Permissions = [.. permissions.Where(x => permission_ids.Contains(x.PermissionId))];
}
return list;
}
public Dictionary<string, object?> GetFilterParameter(UserFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" }
};
}
public string GetFilterWhere(UserFilter filter)
{
StringBuilder sb = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sb.AppendLine(@" AND
(
UPPER(DisplayName) LIKE @SEARCHPHRASE
OR UPPER(Username) LIKE @SEARCHPHRASE
)");
}
string sql = sb.ToString();
return sql;
}
public async Task<int> GetTotalAsync(UserFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) FROM Users WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public static async Task<List<User>> GetUserFriendsAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT u.UserId, u.Username, u.DisplayName, u.Image
FROM Users AS u
JOIN UserFriends AS uf ON u.UserId = uf.FriendId
WHERE uf.UserId = @USER_ID";
var list = await dbController.SelectDataAsync<User>(sql, new
{
USER_ID = userId
}, cancellationToken);
return list;
}
public static async Task<bool> CheckUserFriendAsync(int userId, int friendId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT
CASE
WHEN EXISTS (
SELECT 1
FROM UserFriends
WHERE UserId = @USER_ID AND FriendId = @FRIEND_ID
) THEN 'true'
ELSE 'false'
END AS IsFriend;";
bool isfriend = await dbController.GetFirstAsync<bool>(sql, new
{
USER_ID = userId,
FRIEND_ID = friendId
}, cancellationToken);
return isfriend;
}
public async Task UpdateAsync(User input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"UPDATE Users SET
Username = @USERNAME,
DisplayName = @DISPLAY_NAME,
Description = @DESCRIPTION,
MainFractionId = @MAIN_FRACTION_ID
WHERE UserId = @USER_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
await _permissionService.UpdateUserPermissionsAsync(input, dbController, cancellationToken);
}
public async Task UpdateLastLoginAsync(User input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
input.LastLogin = DateTime.Now;
string sql = @"UPDATE Users SET
LastLogin = @LAST_LOGIN
WHERE UserId = @USER_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
}
public static async Task<bool> FirstUserExistsAsync(IDbController dbController)
{
string sql = "SELECT * FROM Users";
var tmp = await dbController.GetFirstAsync<object>(sql);
return tmp != null;
}
public async Task<string> GetUsernameAsync(int userId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT Username FROM Users WHERE UserId = @USER_ID";
var user = await dbController.GetFirstAsync<User>(sql, new
{
USER_ID = userId
}, cancellationToken);
if (user != null)
{
return user.Username;
}
else
{
return string.Empty;
}
}
public static async Task CreateUserFriendAsync(int userId, int friendId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"INSERT INTO UserFriends
(
UserId,
FriendId
)
VALUES
(
@USER_ID,
@FRIEND_ID
)";
await dbController.QueryAsync(sql, new
{
USER_ID = userId,
FRIEND_ID = friendId
}, cancellationToken);
}
}
}

View File

@@ -0,0 +1,215 @@
using DbController;
using System.Globalization;
using System.Text;
using Tabletop.Core.Filters;
using Tabletop.Core.Models;
namespace Tabletop.Core.Services
{
public class WeaponService : IModelService<Weapon, int, WeaponFilter>
{
public async Task CreateAsync(Weapon input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = $@"INSERT INTO Weapons
(
Attack,
Quality,
Range,
Dices
)
VALUES
(
@ATTACK,
@QUALITY,
@RANGE,
@DICES
); {dbController.GetLastIdSql()}";
input.WeaponId = await dbController.GetFirstAsync<int>(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"INSERT INTO WeaponDescription
(
WeaponId,
Code,
Name,
Description
)
VALUES
(
@WEAPON_ID,
@CODE,
@NAME,
@DESCRIPTION
)";
var parameters = new
{
WEAPON_ID = input.WeaponId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
public async Task DeleteAsync(Weapon input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = "DELETE FROM Weapons WHERE WeaponId = @WEAPON_ID";
await dbController.QueryAsync(sql, new
{
WEAPON_ID = input.WeaponId
}, cancellationToken);
}
public async Task<Weapon?> GetAsync(int weaponId, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"SELECT * FROM Weapons WHERE WeaponId = @WEAPON_ID";
var weapon = await dbController.GetFirstAsync<Weapon>(sql, new
{
WEAPON_ID = weaponId
}, cancellationToken);
if(weapon != null)
{
await LoadWeaponDescriptionAsync(weapon, dbController, cancellationToken);
}
return weapon;
}
public async Task<List<Weapon>> GetAsync(WeaponFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT wd.Name, w.* " +
"FROM WeaponDescription wd " +
"INNER JOIN Weapons w " +
"ON (w.WeaponId = wd.WeaponId) " +
"WHERE 1 = 1");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
sqlBuilder.AppendLine(@$" ORDER BY Name ASC ");
sqlBuilder.AppendLine(dbController.GetPaginationSyntax(filter.PageNumber, filter.Limit));
string sql = sqlBuilder.ToString();
List<Weapon> list = await dbController.SelectDataAsync<Weapon>(sql, GetFilterParameter(filter), cancellationToken);
await LoadWeaponDescriptionsAsync(list, dbController, cancellationToken);
return list;
}
public Dictionary<string, object?> GetFilterParameter(WeaponFilter filter)
{
return new Dictionary<string, object?>
{
{ "SEARCHPHRASE", $"%{filter.SearchPhrase}%" },
{ "CULTURE", CultureInfo.CurrentCulture.Name }
};
}
public string GetFilterWhere(WeaponFilter filter)
{
StringBuilder sqlBuilder = new();
if (!string.IsNullOrWhiteSpace(filter.SearchPhrase))
{
sqlBuilder.AppendLine(@" AND (UPPER(Name) LIKE @SEARCHPHRASE)");
}
string sql = sqlBuilder.ToString();
return sql;
}
public async Task<int> GetTotalAsync(WeaponFilter filter, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
StringBuilder sqlBuilder = new();
sqlBuilder.AppendLine("SELECT COUNT(*) AS record_count FROM WeaponDescription WHERE 1 = 1 ");
sqlBuilder.AppendLine(GetFilterWhere(filter));
sqlBuilder.AppendLine(@" AND Code = @CULTURE");
string sql = sqlBuilder.ToString();
int result = await dbController.GetFirstAsync<int>(sql, GetFilterParameter(filter), cancellationToken);
return result;
}
public static async Task<List<Weapon>> GetAllAsync(IDbController dbController)
{
string sql = "SELECT * FROM Weapons";
var list = await dbController.SelectDataAsync<Weapon>(sql);
await LoadWeaponDescriptionsAsync(list, dbController);
return list;
}
public async Task UpdateAsync(Weapon input, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = @"UPDATE Weapons SET
Attack = @ATTACK,
Quality = @QUALITY,
Range = @RANGE,
Dices = @DICES
WHERE WeaponId = @WEAPON_ID";
await dbController.QueryAsync(sql, input.GetParameters(), cancellationToken);
foreach (var description in input.Description)
{
sql = @"UPDATE WeaponDescription SET
Name = @NAME,
Description = @DESCRIPTION
WHERE WeaponId = @WEAPON_ID AND Code = @CODE";
var parameters = new
{
WEAPON_ID = input.WeaponId,
CODE = description.Code,
NAME = description.Name,
DESCRIPTION = description.Description
};
await dbController.QueryAsync(sql, parameters, cancellationToken);
}
}
private static async Task LoadWeaponDescriptionsAsync(List<Weapon> list, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (list.Count != 0)
{
IEnumerable<int> weaponIds = list.Select(x => x.WeaponId);
string sql = $"SELECT * FROM WeaponDescription WHERE WeaponId IN ({string.Join(",", weaponIds)})";
List<WeaponDescription> descriptions = await dbController.SelectDataAsync<WeaponDescription>(sql, null, cancellationToken);
foreach (var weapon in list)
{
weapon.Description = descriptions.Where(x => x.WeaponId == weapon.WeaponId).ToList();
}
}
}
private static async Task LoadWeaponDescriptionAsync(Weapon weapon, IDbController dbController, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string sql = $"SELECT * FROM WeaponDescription WHERE WeaponId IN @WEAPON_ID";
weapon.Description = await dbController.SelectDataAsync<WeaponDescription>(sql, new
{
WEAPON_ID = weapon.WeaponId
}, cancellationToken);
}
}
}

View File

@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Pages\**" />
<EmbeddedResource Remove="Pages\**" />
<None Remove="Pages\**" />
</ItemGroup>
<ItemGroup>
<None Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="DbController" Version="5.0.0" />
<PackageReference Include="DbController.SqlServer" Version="5.0.0" />
<PackageReference Include="DbController.TypeHandler" Version="5.0.0" />
<PackageReference Include="FluentValidation" Version="12.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.3.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,26 @@
using FluentValidation;
using System.Globalization;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class AbilityValidator : AbstractValidator<Ability>
{
public AbilityValidator()
{
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Description)
.MaximumLength(1000)
.WithMessage("Description must contain only 1000 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Mechanic)
.MaximumLength(1000)
.WithMessage("Mechanic must contain only 1000 characters.");
}
}
}

View File

@@ -0,0 +1,28 @@
using FluentValidation;
using System.Globalization;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class FractionValidator : AbstractValidator<Fraction>
{
public FractionValidator()
{
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).ShortName)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(3)
.WithMessage("Name must contain only 3 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Description)
.MaximumLength(1000)
.WithMessage("Description must contain only 1000 characters.");
}
}
}

View File

@@ -0,0 +1,32 @@
using FluentValidation;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class GameValidator : AbstractValidator<Game>
{
public GameValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(30)
.WithMessage("Name must contain only 30 characters.");
RuleFor(x => x.GamemodeId)
.NotEmpty()
.WithMessage("Game Mode must be selected");
When(x => x.GamemodeId is 1 or 4, () =>
{
RuleFor(x => x.NumberOfRounds)
.NotNull()
.WithMessage("Rounds must be selected");
});
RuleFor(x => x.Force)
.NotEmpty()
.WithMessage("Force must be selected");
}
}
}

View File

@@ -0,0 +1,26 @@
using FluentValidation;
using System.Globalization;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class GamemodeValidator : AbstractValidator<Gamemode>
{
public GamemodeValidator()
{
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Description)
.MaximumLength(1000)
.WithMessage("Description must contain only 1000 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Mechanic)
.MaximumLength(1000)
.WithMessage("Description must contain only 1000 characters.");
}
}
}

View File

@@ -0,0 +1,19 @@
using FluentValidation;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class PlayerValidator : AbstractValidator<Player>
{
public PlayerValidator()
{
RuleFor(x => x.UsedForce)
.LessThanOrEqualTo(x => x.AllowedForce)
.WithMessage("Force points over limit");
RuleFor(x => x.Units)
.NotEmpty()
.WithMessage("At least one unit must be selected");
}
}
}

View File

@@ -0,0 +1,29 @@
using FluentValidation;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class TemplateValidator : AbstractValidator<Template>
{
public TemplateValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.FractionId)
.NotEmpty()
.WithMessage("Faction must be selected");
RuleFor(x => x.Force)
.NotEmpty()
.WithMessage("Force must be selected");
RuleFor(x => x.UsedForce)
.LessThanOrEqualTo(x => x.Force)
.WithMessage("Force points over limit");
}
}
}

View File

@@ -0,0 +1,42 @@
using FluentValidation;
using System.Globalization;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class UnitValidator : AbstractValidator<Unit>
{
public UnitValidator()
{
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.FractionId)
.NotEmpty()
.WithMessage("Faction must be selected");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Description)
.MaximumLength(500)
.WithMessage("Description must contain only 500 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Mechanic)
.MaximumLength(500)
.WithMessage("Description must contain only 500 characters.");
RuleFor(x => x.Defense)
.NotEmpty()
.WithMessage("Defense must be filled.");
RuleFor(x => x.Moving)
.NotEmpty()
.WithMessage("The field must be filled")
.LessThan(30)
.WithMessage("The field may be a maximum of 30")
.GreaterThan(0)
.WithMessage("The field must not be greater than 0");
}
}
}

View File

@@ -0,0 +1,38 @@
using FluentValidation;
using System.Globalization;
using Tabletop.Core.Models;
namespace Tabletop.Core.Validators
{
public class WeaponValidator : AbstractValidator<Weapon>
{
public WeaponValidator()
{
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Name)
.NotEmpty()
.WithMessage("The field must be filled")
.MaximumLength(50)
.WithMessage("Name must contain only 50 characters.");
RuleFor(x => x.GetLocalization(CultureInfo.CurrentCulture).Description)
.MaximumLength(500)
.WithMessage("Description must contain only 500 characters.");
RuleFor(x => x.Attack)
.NotEmpty()
.WithMessage("Attack must be filled.");
RuleFor(x => x.Quality)
.NotEmpty()
.WithMessage("Quality must be filled");
RuleFor(x => x.Range)
.NotEmpty()
.WithMessage("Range must be filled");
RuleFor(x => x.Dices)
.NotEmpty()
.WithMessage("Dices must be filled");
}
}
}

View File

@@ -0,0 +1,11 @@
{
"Filter": {
"PageLimit": 30
},
"Sql": {
"ConnectionString": "Server=localhost\\SQLEXPRESS;Database=Tabletop;Trusted_Connection=True;TrustServerCertificate=True;"
},
"IpGeolocation": {
"ApiKey": "43d1738db9eb411cb959972585b331f6"
}
}

31
Tabletop.sln Normal file
View File

@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33502.453
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tabletop", "Tabletop\Tabletop.csproj", "{C836FAAA-818F-4929-9EBB-A15D3A4C1531}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tabletop.Core", "Tabletop.Core\Tabletop.Core.csproj", "{F6F1A544-5971-4FE6-8686-D4118892AA46}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C836FAAA-818F-4929-9EBB-A15D3A4C1531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C836FAAA-818F-4929-9EBB-A15D3A4C1531}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C836FAAA-818F-4929-9EBB-A15D3A4C1531}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C836FAAA-818F-4929-9EBB-A15D3A4C1531}.Release|Any CPU.Build.0 = Release|Any CPU
{F6F1A544-5971-4FE6-8686-D4118892AA46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6F1A544-5971-4FE6-8686-D4118892AA46}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6F1A544-5971-4FE6-8686-D4118892AA46}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6F1A544-5971-4FE6-8686-D4118892AA46}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A4F5A8FD-1BC7-423D-8685-3ECEF147EE62}
EndGlobalSection
EndGlobal

19
Tabletop/App.razor Normal file
View File

@@ -0,0 +1,19 @@
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<RedirectToLogin />
</NotAuthorized>
<Authorizing>
<p>Lade Authentifizierungsstatus...</p>
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Seite nicht gefunden.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

View File

@@ -0,0 +1,37 @@
@using System.Globalization
@inject NavigationManager Navigation
<div class="culture-select">
<select @bind="Culture" class="form-select w-auto">
@foreach (var culture in AppdataService.SupportedCultures)
{
<option value="@culture">@culture.NativeName</option>
}
</select>
</div>
@code
{
protected override void OnInitialized()
{
Culture = CultureInfo.CurrentCulture;
}
private CultureInfo Culture
{
get => CultureInfo.CurrentCulture;
set
{
if (CultureInfo.CurrentCulture != value)
{
var uri = new Uri(Navigation.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var cultureEscaped = Uri.EscapeDataString(value.Name);
var uriEscaped = Uri.EscapeDataString(uri);
Navigation.NavigateTo(
$"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
forceLoad: true);
}
}
}
}

View File

@@ -0,0 +1,51 @@
@typeparam T where T : struct
@if (IsEditable)
{
<div class="form-floating mb-3">
<InputSelect TValue="T?"
Value="Value"
ValueChanged="ValueChanged"
ValueExpression="() => Value"
class="form-control"
id="@Id"
@oninput="OnChanged">
@foreach (var option in Options)
{
<option value="@option">@GetLabel(option)</option>
}
</InputSelect>
<label for="@Id">@Label</label>
</div>
}
else
{
<div class="form-floating mb-3">
<div id="@Id" class="form-control">@DisplayValue</div>
<label for="@Id">@Label</label>
</div>
}
@code {
[Parameter] public string Id { get; set; } = string.Empty;
[Parameter] public string Label { get; set; } = string.Empty;
[Parameter] public T? Value { get; set; }
[Parameter] public EventCallback<T?> ValueChanged { get; set; }
[Parameter] public List<T?> Options { get; set; } = new();
[Parameter] public Func<T?, string>? GetLabelFunc { get; set; }
[Parameter] public bool IsEditable { get; set; }
[Parameter] public EventCallback OnChanged { get; set; }
string DisplayValue => GetLabel(Value);
private string GetLabel(T? value)
{
if (value is null) return string.Empty;
if (GetLabelFunc is not null)
return GetLabelFunc(value);
return value?.ToString() ?? string.Empty;
}
}

View File

@@ -0,0 +1,42 @@
<div class="d-flex align-items-center">
<button class="btn">
<i class="fa-solid fa-minus cursor-pointer" @onclick="Decrement"></i>
</button>
<input class="text-right" @bind="Value" max="@MaxValue" readonly style="width:40px" />
<button class="btn">
<i class="fa-solid fa-plus cursor-pointer" @onclick="Increment"></i>
</button>
</div>
@code {
[Parameter]
public int Value { get; set; }
[Parameter]
public int MaxValue { get; set; }
[Parameter]
public EventCallback<int> ValueChanged { get; set; }
private Task Increment()
{
if (Value < MaxValue)
{
Value++;
ValueChanged.InvokeAsync(Value);
}
return Task.CompletedTask;
}
private Task Decrement()
{
if (Value > 0)
{
Value--;
ValueChanged.InvokeAsync(Value);
}
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,121 @@
@inherits LayoutComponentBase
@inject IJSRuntime JSRuntime
@inject AuthService authService
@inject IStringLocalizer<App> localizer
@using System.Runtime.InteropServices
<PageTitle>@localizer["TABLEBRICKS"]</PageTitle>
<div id="app" class="app-background">
<div style="max-width: 1800px; margin: 0 auto;">
<div id="sidebar" class="@(ShowMenu ? "active" : string.Empty)">
<div class="sidebar-wrapper active">
<div class="sidebar-header position-relative">
<div class="d-flex justify-content-between align-items-center">
<div role="button" class="sidebar-toggler x">
<span class="sidebar-hide d-xl-none d-block" @onclick="TriggerMenuAsync"><i class="fa-solid fa-xmark"></i></span>
</div>
</div>
</div>
<div class="sidebar-menu">
<NavMenu />
</div>
</div>
</div>
<div id="main" class="pb-0">
<header class="d-flex align-items-center justify-content-between d-xl-none">
<div>
<span role="button" @onclick="TriggerMenuAsync" class="pt-3 mt-1 burger-btn d-block d-xl-none menu-button">
<i class="fa-solid fa-bars text-white"></i>
</span>
</div>
<div>
<a href="/" class="text-decoration-none">
<h1 class="text-white mb-0 align-items-center">@localizer["TABLEBRICKS_UPPER"]</h1>
</a>
</div>
<div>
@if (_loggedInUser != null)
{
<a href="/Account/Profile/">
<img src="@_loggedInUser.ConvertedImage" height="50" width="50" class="rounded-circle" alt="Logo" title="Logo">
</a>
}
</div>
</header>
<div class="page-content background">
<section class="row">
@Body
</section>
</div>
@* <footer class="footer card">
<div class="card-header p-4">
<div class="row">
<div class="col-lg-5">
<h5>@localizer["INFORMATION"]</h5>
<ul class="list-unstyled">
<li><a class="text-dark text-decoration-none btn" href="/Imprint">@localizer["IMPRINT"]</a></li>
</ul>
</div>
<div class="col-lg-5">
<h5>@localizer["CONTACT"]</h5>
<ul class="list-inline">
<li class="text-dark text-decoration-none btn"><a href="https://github.com/VeLoX15/Tabletop"><i class="fa-brands fa-github"></i></a></li>
<li class="text-dark text-decoration-none btn"><a href="https://www.instagram.com/"><i class="fa-brands fa-instagram"></i></a></li>
<li class="text-dark text-decoration-none btn"><a href="https://twitter.com"><i class="fa-brands fa-twitter"></i></a></li>
</ul>
</div>
<div class="col-lg-2">
<div class="text-center pt-5 pb-5">
@DateTime.Now.Year &copy; @localizer["TABLEBRICKS"]
</div>
</div>
</div>
</div>
</footer> *@
</div>
</div>
</div>
@code {
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("blazorHelpers.startUp");
ShowMenu = await JSRuntime.InvokeAsync<bool>("blazorHelpers.isDesktop");
StateHasChanged();
}
await base.OnAfterRenderAsync(firstRender);
}
private async Task TriggerMenuAsync()
{
bool isDesktop = await JSRuntime.InvokeAsync<bool>("blazorHelpers.isDesktop");
ShowMenu = !ShowMenu;
}
protected override async Task OnParametersSetAsync()
{
_loggedInUser = await authService.GetUserAsync();
if (_loggedInUser?.Image != null)
{
string base64String = Convert.ToBase64String(_loggedInUser.Image);
_loggedInUser.ConvertedImage = $"data:image/png;base64,{base64String}";
}
}
public bool ShowMenu { get; set; }
private User? _loggedInUser;
}

View File

@@ -0,0 +1,77 @@
.test {
background-image: url('/assets/img/logo/background.jpg') !important;
background-size: cover !important;
background-position: center !important;
background-repeat: no-repeat !important;
}
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row:not(.auth) {
display: none;
}
.top-row.auth {
justify-content: space-between;
}
.top-row a, .top-row .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

View File

@@ -0,0 +1,65 @@
@inherits ModalBase
<div class="modal fade show" style="display: block;">
<div class="@ModalSizeClass">
<div class="modal-content @(DoubleOverlay ? "double-overlay" : "")">
<div class="modal-header">
<h5 class="modal-title">@Title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => OnClosed.InvokeAsync()"></button>
</div>
<div class="modal-body">
@ChildContent
</div>
<div class="modal-footer">
<button type="button" class="btn me-1 @CloseBtnClass" data-bs-dismiss="modal" @onclick="() => { OnDeclined.InvokeAsync(); OnClosed.InvokeAsync(); }">@DeclineText</button>
@if (ConfirmBedingung)
{
@if (_confirmIsLoading)
{
<button class="@ConfirmButtonClass" type="button" disabled>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span class="visually-hidden">Loading...</span>
</button>
}
else
{
<button type="button" class="@ConfirmButtonClass" data-bs-dismiss="modal" @onclick="Confirm">@ConfirmText</button>
}
}
else
{
<button type="button" class="@ConfirmButtonClass" disabled>@ConfirmText</button>
}
</div>
</div>
</div>
</div>
<div class="modal-backdrop fade show"></div>
@code {
[Parameter] public EventCallback OnDeclined { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public EventCallback OnConfirmed { get; set; }
[Parameter] public string ConfirmText { get; set; } = "Ja";
[Parameter] public string DeclineText { get; set; } = "Nein";
[Parameter] public bool ConfirmBedingung { get; set; } = true;
[Parameter] public string ConfirmButtonClass { get; set; } = "btn btn-success";
private bool _confirmIsLoading;
private async Task Confirm()
{
_confirmIsLoading = true;
await OnConfirmed.InvokeAsync();
await OnClosed.InvokeAsync();
_confirmIsLoading = false;
}
}

View File

@@ -0,0 +1,26 @@
@inherits ModalBase
<div class="modal show" style="display: block;">
<div class="@ModalSizeClass">
<div class="modal-content @(DoubleOverlay ? "double-overlay" : "")">
<div class="modal-header">
<h5 class="modal-title">@Title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => OnClosed.InvokeAsync()"></button>
</div>
<div class="modal-body">
@ChildContent
</div>
@if (!HideFooter)
{
<div class="modal-footer">
<button type="button" class="btn @CloseBtnClass" data-bs-dismiss="modal" @onclick="() => OnClosed.InvokeAsync()">Schließen</button>
</div>
}
</div>
</div>
</div>
<div class="modal-backdrop fade show"></div>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public bool HideFooter { get; set; }
}

View File

@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Components;
namespace Tabletop.Components.Modals
{
public class ModalBase : ComponentBase
{
[Parameter] public string Title { get; set; } = String.Empty;
[Parameter] public EventCallback OnClosed { get; set; }
protected string ModalSizeClass => ModalSize switch
{
ModalSize.Default => "modal-dialog",
ModalSize.SM => "modal-dialog modal-sm",
ModalSize.LG => "modal-dialog modal-lg",
ModalSize.XL => "modal-dialog modal-xl",
ModalSize.XXL => "modal-dialog modal-xxl",
_ => "modal-dialog"
};
[Parameter] public ModalSize ModalSize { get; set; } = ModalSize.Default;
/// <summary>
/// Gibt an, ob das Modal innerhalb eines weiteren Modals geöffnet wird. Wenn ja, dann erhält das Modal noch zusätzlich die CSS Klasse
/// .double-overlay
/// </summary>
[Parameter] public bool DoubleOverlay { get; set; } = false;
[Parameter] public string CloseBtnClass { get; set; } = "btn-danger";
}
}

View File

@@ -0,0 +1,11 @@
namespace Tabletop.Components.Modals
{
public enum ModalSize
{
Default,
SM,
LG,
XL,
XXL
}
}

View File

@@ -0,0 +1,98 @@
@using System.Globalization
@inject IStringLocalizer<App> localizer
<div class="mb-3">
<button class="btn btn-primary me-2" @onclick="() => { Player = new Player(); showAttackModal = true; }">Attack</button>
<button class="btn btn-secondary" @onclick="() => { Player = new Player(); showCaptureModal = true; }">Capture</button>
</div>
@if (showAttackModal && Player is not null)
{
<Modal Title="Attack"
OnClosed="() => { Player = null; showAttackModal = false; }"
ModalSize="ModalSize.LG"
HideFooter="true">
@* <EditForm Model="Player" @ref="_form1">
<FluentValidationValidator />
<div class="row">
<div class="col-12">
<div class="form-floating mb-3">
<InputSelect id="Input-unit" @bind-Value="" class="form-control">
<option value="0">@localizer["SELECT_CHOOSE"]</option>
@foreach (var item in Player.Units)
{
<option value="@item.UnitId">@item.UnitId</option>
}
</InputSelect>
<label for="Input-unit">@localizer["TEMPLATE"]</label>
</div>
</div>
</div>
</EditForm> *@
<div class="toolbar">
<button type="button" class="btn btn-danger" @onclick="() => { Player = null; showAttackModal = false; }">@localizer["CANCEL"]</button>
<button type="button" class="btn btn-success" @onclick="() => SaveArmyAsync(Player)">@localizer["SAVE"]</button>
</div>
</Modal>
}
@if (showCaptureModal && Player is not null)
{
<Modal Title="Capture"
OnClosed="() => { Player = null; showCaptureModal = false; }"
ModalSize="ModalSize.LG"
HideFooter="true">
@* <EditForm Model="Player" @ref="_form2">
<FluentValidationValidator />
<div class="row">
<div class="col-12">
<div class="form-floating mb-3">
<InputSelect id="Input-template-2" @bind-Value="SelectedTemplate2" @onclick="async () => { await LoadTemplateAsync(); await CalculateArmyDataAsync(Player2); }" class="form-control">
<option value="0">@localizer["SELECT_CHOOSE"]</option>
@foreach (Template item in Templates.Where(x => x.Force == Game.Force && x.FractionId == Player2.FractionId))
{
<option value="@item.TemplateId">@item.Name</option>
}
</InputSelect>
<label for="Input-template-2">@localizer["TEMPLATE"]</label>
</div>
</div>
</div>
</EditForm> *@
<div class="toolbar">
<button type="button" class="btn btn-danger" @onclick="() => { Player = null; showCaptureModal = false; }">@localizer["CANCEL"]</button>
<button type="button" class="btn btn-success" @onclick="() => SaveArmyAsync(Player)">@localizer["SAVE"]</button>
</div>
</Modal>
}
@code {
private bool showAttackModal = false;
private bool showCaptureModal = false;
private Player? Player;
private Game? Game;
private EditForm? _form1;
private EditForm? _form2;
private async Task SaveArmyAsync(Player player)
{
}
private async Task LoadTemplateAsync()
{
}
private async Task CalculateArmyDataAsync(Player player)
{
}
}

View File

@@ -0,0 +1,41 @@
@inject IStringLocalizer<App> localizer
<Modal Title="@Title"
OnClosed="OnClosed"
ModalSize="ModalSize.LG"
HideFooter="true">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>@localizer["NAME"]</th>
<th>@localizer["QUANTITY"]</th>
<th>@localizer["FORCE"]</th>
</tr>
</thead>
<tbody>
@foreach (var unit in UnitList)
{
<tr>
<td class="text-bold-500 text-nowrap">@unit.GetLocalization(CultureInfo.CurrentCulture)?.Name</td>
<td>@unit.Quantity</td>
<td class="text-nowrap">@unit.ForceOfQuantity (@unit.Force)</td>
</tr>
}
<tr>
<th>@localizer["TOTAL"]</th>
<th>@UnitList.Sum(x => x.Quantity)</th>
<th class="text-nowrap">@UsedForce (@Math.Round((double)UsedForce / UnitList.Sum(x => x.Quantity), 0))</th>
</tr>
</tbody>
</table>
</div>
</Modal>
@code {
[Parameter] public string Title { get; set; } = string.Empty;
[Parameter] public List<Unit> UnitList { get; set; } = [];
[Parameter] public int UsedForce { get; set; }
[Parameter] public EventCallback OnClosed { get; set; }
}

View File

@@ -0,0 +1,282 @@
@typeparam T where T : ArmyBase
@using System.Globalization
@using Tabletop.Core.Models.Abstract
@inject IStringLocalizer<App> localizer
@if (Army != null)
{
<div class="table-responsive">
<table class="table table-lg">
<thead>
<tr>
<th>@localizer["NAME"]</th>
<th>@localizer["CLASS"]</th>
<th class="text-center">@localizer["QUANTITY"]</th>
<th>@localizer["FORCE"]</th>
<th>@localizer["ACTION"]</th>
</tr>
</thead>
<tbody>
@foreach (var unit in Army.Units)
{
int maxQuantity = LoggedInUser?.Units?.FirstOrDefault(x => x.UnitId == unit.UnitId)?.Quantity ?? 0;
<tr>
<td class="text-bold-500 text-nowrap">@unit.GetLocalization(CultureInfo.CurrentCulture)?.Name</td>
<td class="text-bold-500">@Classes?.FirstOrDefault(x => x.ClassId == unit.ClassId)?.GetLocalization(CultureInfo.CurrentCulture)?.Name</td>
<td>
<div class="d-flex align-items-center justify-content-center text-nowrap">
<button class="btn fa-solid fa-minus cursor-pointer p-3" @onclick="() => DecrementAsync(unit)"></button>
<button class="btn p-0">@unit.Quantity / @maxQuantity</button>
<button class="btn fa-solid fa-plus cursor-pointer p-3" @onclick="() => IncrementAsync(unit)"></button>
</div>
</td>
<td class="text-nowrap">@unit.ForceOfQuantity (@unit.Force)</td>
<td>
<button class="btn fa-solid fa-trash-can" @onclick="() => ClearUnitAsync(unit)"></button>
</td>
</tr>
}
<tr>
<th class="text-nowrap">
@if (Army.UsedForce > Force)
{
<ValidationMessage For="() => Army.UsedForce" />
}
else if (!Army.Units.Any())
{
<ValidationMessage For="() => Army.Units" />
}
else
{
@localizer["TOTAL"]
}
</th>
<th></th>
<th class="text-center">@Army.TotalUnits</th>
<th>@Army.UsedForce (@Math.Round((double)Army.UsedForce / Army.TotalUnits, 0))</th>
<th>
@if (Army.Units.Any())
{
<button class="btn fa-solid fa-trash-can" @onclick="ClearUnitsAsync"></button>
}
</th>
</tr>
</tbody>
</table>
<div class="input-group w-50">
<select @onchange="UnitSelectionChangedAsync" class="form-select" aria-label="Unit">
@if (SelectedUnit == null)
{
<option value="0" selected>@localizer["SELECT_CHOOSE"]</option>
}
else
{
<option value="0">@localizer["SELECT_CHOOSE"]</option>
}
@if (LoggedInUser != null)
{
@foreach (var unit in LoggedInUser.Units.Where(x => x.FractionId == Army.FractionId))
{
var exists = Army.Units.Any(x => x.UnitId == unit.UnitId);
if (!exists)
{
<option value="@unit.UnitId">@unit.GetLocalization(CultureInfo.CurrentCulture)?.Name</option>
}
}
}
</select>
<button @onclick="AddUnitAsync" class="btn btn-success " type="button" disabled="@(SelectedUnit == null || Force == 0)">
<i class="fa-solid fa-plus"></i>
</button>
</div>
</div>
}
@code {
[Parameter] public T? Army { get; set; }
[Parameter] public User? LoggedInUser { get; set; }
[Parameter] public int Force { get; set; }
[Parameter] public List<Class>? Classes { get; set; }
private Unit? SelectedUnit { get; set; }
private async Task AddUnitAsync()
{
if (Army != null && SelectedUnit != null)
{
Army.Units.Add(SelectedUnit);
SelectedUnit = null;
await CalculateArmyDataAsync(Army);
}
}
private Task UnitSelectionChangedAsync(ChangeEventArgs e)
{
int unitId = Convert.ToInt32(e.Value);
SelectedUnit = LoggedInUser?.Units.FirstOrDefault(x => x.UnitId == unitId)?.DeepCopyByExpressionTree();
if (SelectedUnit != null)
{
SelectedUnit.Quantity = 0;
}
return Task.CompletedTask;
}
private async Task CalculateTotalForceAsync(T army)
{
if (army == null) return;
int totalForce = 0;
foreach (var unit in army.Units)
{
int unitForce = await Calculation.ForceAsync(unit);
totalForce += unitForce * unit.Quantity;
}
army.UsedForce = totalForce;
}
private async Task CalculateForceAsync(T army)
{
if (army == null) return;
foreach (var unit in army.Units)
{
int force = await Calculation.ForceAsync(unit);
unit.Force = force;
unit.ForceOfQuantity = force * unit.Quantity;
}
}
private void CalculateTotalCount(T army)
{
if (army == null) return;
army.TotalUnits = army.Units.Sum(x => x.Quantity);
}
private async Task CalculateArmyDataAsync(T army)
{
await CalculateForceAsync(army);
await CalculateTotalForceAsync(army);
CalculateTotalCount(army);
}
private Task ClearUnitsAsync()
{
if (Army != null)
{
Army.Units.Clear();
Army.UsedForce = 0;
Army.TotalUnits = 0;
}
SelectedUnit = null;
return Task.CompletedTask;
}
private async Task ClearUnitAsync(Unit unit)
{
if (Army == null || unit == null) return;
unit.Quantity = 0;
Army.Units.Remove(unit);
await CalculateArmyDataAsync(Army);
}
private async Task IncrementAsync(Unit unit)
{
if (Army == null || unit == null) return;
if (await CheckTroopSize(unit) && await CheckAllowedUnitsOfClass(unit) && Force > Army.UsedForce)
{
int maxQuantity = LoggedInUser?.Units?.FirstOrDefault(x => x.UnitId == unit.UnitId)?.Quantity ?? 0;
if (unit.Quantity < maxQuantity)
{
if (unit.Quantity % unit.TroopQuantity < unit.TroopQuantity / 2)
{
if (unit.Quantity + unit.TroopQuantity / 2 - 1 < maxQuantity)
{
unit.Quantity += unit.TroopQuantity / 2;
}
}
else
{
unit.Quantity++;
}
await CalculateArmyDataAsync(Army);
}
}
}
private async Task DecrementAsync(Unit unit)
{
if (Army == null || unit == null) return;
if (await CheckTroopSize(unit) && unit.Quantity > 0)
{
if (unit.Quantity % unit.TroopQuantity == unit.TroopQuantity / 2)
{
unit.Quantity -= unit.TroopQuantity % 2 == 0 ? unit.TroopQuantity / 2 : 1;
}
else
{
unit.Quantity--;
}
await CalculateArmyDataAsync(Army);
}
}
private Task<bool> CheckTroopSize(Unit unit)
{
if (Army == null) return Task.FromResult(false);
bool isValid = Army.Units
.Where(x => x.ClassId == unit.ClassId && x != unit)
.All(x => x.Quantity % x.TroopQuantity == 0);
return Task.FromResult(isValid);
}
private Task<bool> CheckAllowedUnitsOfClass(Unit unit)
{
if (Army == null) return Task.FromResult(false);
if (unit.ClassId == 1)
{
return Task.FromResult(true);
}
int maxOfClass = Force / 200;
int numberOfTroops = 0;
foreach (var item in Army.Units.Where(x => x.ClassId == unit.ClassId))
{
int itemTroops = (int)Math.Ceiling((double)item.Quantity / item.TroopQuantity);
if (item == unit)
{
itemTroops = (int)Math.Ceiling(((double)item.Quantity + 1) / item.TroopQuantity);
}
numberOfTroops += itemTroops;
if (numberOfTroops > maxOfClass)
{
return Task.FromResult(false);
}
}
return Task.FromResult(true);
}
}

View File

@@ -0,0 +1,209 @@
@using System.Runtime.InteropServices
@inject IStringLocalizer<App> localizer
<div class="text-center">
<img src="assets/img/logo/logo.png" style="max-width: 80%;" />
</div>
<ul class="menu">
<li class="sidebar-item">
<NavLink href="/" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-house"></i>
<span>@localizer["HOME"]</span>
</NavLink>
</li>
<li class="sidebar-title"><h5>@localizer["INFO"]</h5></li>
<li class="sidebar-item">
<NavLink href="/Rule-Set" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-book-journal-whills"></i>
<span>@localizer["RULE_SET"]</span>
</NavLink>
</li>
<li class="sidebar-item">
<NavLink href="/Fractions" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-brands fa-galactic-senate"></i>
<span>@localizer["FRACTIONS"]</span>
</NavLink>
</li>
<li class="sidebar-item">
<NavLink href="/Game-Modes" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-dice-five"></i>
<span>@localizer["GAME_MODES"]</span>
</NavLink>
</li>
<li class="sidebar-item">
<NavLink href="/Damage-Tables" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-table-cells"></i>
<span>@localizer["TABLES"]</span>
</NavLink>
</li>
<AuthorizeView>
<Authorized>
<li class="sidebar-title"><h5>@localizer["TOOLS"]</h5></li>
<li class="sidebar-item">
<NavLink href="/Tools/Simulation" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-microchip"></i>
<span>@localizer["SIMULATION"]</span>
</NavLink>
</li>
<li class="sidebar-item">
<NavLink href="/Tools/Army-Builder" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-pen"></i>
<span>@localizer["ARMY_BUILDER"]</span>
</NavLink>
</li>
</Authorized>
</AuthorizeView>
<AuthorizeView>
<Authorized>
<li class="sidebar-title"><h5>@localizer["GAMES"]</h5></li>
<li class="sidebar-item">
<NavLink href="/Games" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-play"></i>
<span>@localizer["PLAY"]</span>
</NavLink>
</li>
<li class="sidebar-item">
<NavLink href="/Games/Game-History" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-clock-rotate-left"></i>
<span>@localizer["HISTORY"]</span>
</NavLink>
</li>
<li class="sidebar-item">
<NavLink href="/Games/Statistics" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-chart-column"></i>
<span>@localizer["STATS"]</span>
</NavLink>
</li>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="@($"{Roles.VIEW_USERS}, {Roles.VIEW_UNITS}, {Roles.VIEW_WEAPONS}, {Roles.VIEW_FRACTIONS}, {Roles.VIEW_GAMEMODES}")">
<Authorized>
<li class="sidebar-title"><h5>@localizer["ADMINISTRATION"]</h5></li>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="@(Roles.VIEW_USERS)">
<Authorized>
<li class="sidebar-item">
<NavLink href="/Administration/Users" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-users"></i>
<span>@localizer["USERS"]</span>
</NavLink>
</li>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="@(Roles.VIEW_UNITS)">
<Authorized>
<li class="sidebar-item">
<NavLink href="/Administration/Units" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-person-rifle"></i>
<span>@localizer["UNITS"]</span>
</NavLink>
</li>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="@(Roles.VIEW_ABILITIES)">
<Authorized>
<li class="sidebar-item">
<NavLink href="/Administration/Abilities" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-brain"></i>
<span>@localizer["ABILITIES"]</span>
</NavLink>
</li>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="@(Roles.VIEW_WEAPONS)">
<Authorized>
<li class="sidebar-item">
<NavLink href="/Administration/Weapons" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-gun"></i>
<span>@localizer["WEAPONS"]</span>
</NavLink>
</li>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="@(Roles.VIEW_FRACTIONS)">
<Authorized>
<li class="sidebar-item">
<NavLink href="/Administration/Fractions" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-brands fa-galactic-senate"></i>
<span>@localizer["FRACTIONS"]</span>
</NavLink>
</li>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="@(Roles.VIEW_GAMEMODES)">
<Authorized>
<li class="sidebar-item">
<NavLink href="/Administration/Game-Modes" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-dice-five"></i>
<span>@localizer["GAME_MODES"]</span>
</NavLink>
</li>
</Authorized>
</AuthorizeView>
<li class="sidebar-title"><h5>@localizer["ACCOUNT"]</h5></li>
<AuthorizeView>
<Authorized>
<li class="sidebar-item">
<NavLink href="/Account/Profile" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-user"></i>
<span>@localizer["PROFILE"]</span>
</NavLink>
</li>
<li class="sidebar-item">
<form method="post" action="/Account/Logout">
<button type="submit" class="btn btn-danger btn-block text-left sidebar-link logout-button">
<i class="fa-solid fa-right-from-bracket"></i>
<span>@localizer["LOGOUT"]</span>
</button>
</form>
</li>
</Authorized>
<NotAuthorized>
<li class="sidebar-item">
<NavLink href="/Account/Login" class="sidebar-link blazor-link" Match="NavLinkMatch.All">
<i class="fa-solid fa-right-to-bracket"></i>
<span>@localizer["LOGIN"]</span>
</NavLink>
</li>
</NotAuthorized>
</AuthorizeView>
</ul>
<p class="text-center">
Ver. @Program.GetVersion()
<div>@RuntimeInformation.FrameworkDescription / @RuntimeInformation.ProcessArchitecture</div>
</p>
<div class="d-flex justify-content-center pb-5">
<div class="culture-wrapper">
<CultureSelector />
</div>
</div>

View File

@@ -0,0 +1,70 @@
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.oi {
width: 2rem;
font-size: 1.1rem;
vertical-align: text-top;
top: -2px;
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.25);
color: white;
}
.nav-item ::deep a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
.nav-scrollable {
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}

View File

@@ -0,0 +1,154 @@
@using System.Linq
@inject IStringLocalizer<App> localizer
<div class="card p-4">
@if (SelectionMode is null)
{
<h5>Wie soll die Reihenfolge vergeben werden?</h5>
<button class="btn btn-primary me-2" @onclick="UseManual">🔢 Manuell</button>
<button class="btn btn-secondary" @onclick="UseRandom">🎲 Randomized</button>
}
else if (SelectionMode == "manual")
{
<h5>Manuelle Spielerreihenfolge</h5>
<table class="table table-sm">
<thead>
<tr>
<th>Spieler</th>
<th>Reihenfolge</th>
<th>Startposition</th>
</tr>
</thead>
<tbody>
@foreach (var player in Players)
{
<tr>
<td>@player.User.Username</td>
<td>
<div class="d-inline-block">
<InputSelect class="form-select" @bind-Value="player.OrderNr">
<option value="0">@localizer["SELECT_CHOOSE"]</option>
@foreach (var number in Enumerable.Range(1, Players.Count))
{
bool isUsed = Players.Any(p => p.PlayerId != player.PlayerId && p.OrderNr == number);
<option value="@number" disabled="@(isUsed && player.OrderNr != number)">
@number
</option>
}
</InputSelect>
</div>
</td>
<td>
<div class="d-inline-block">
<InputSelect class="form-select" @bind-Value="player.StartZone">
<option value="0">@localizer["SELECT_CHOOSE"]</option>
@foreach (var number in Enumerable.Range(1, Players.Count))
{
bool isUsed = Players.Any(p => p.PlayerId != player.PlayerId && p.StartZone == number);
<option value="@number" disabled="@(isUsed && player.StartZone != number)">
@number
</option>
}
</InputSelect>
</div>
</td>
</tr>
}
</tbody>
</table>
@if (!string.IsNullOrEmpty(ValidationMessage))
{
<div class="alert alert-danger">@ValidationMessage</div>
}
<button class="btn btn-success" @onclick="FinishManual">✅ Bestätigen</button>
}
else if (SelectionMode == "random")
{
<div class="alert alert-info">🎲 Die Reihenfolge wurde zufällig vergeben.</div>
}
</div>
@code {
[Parameter]
public List<Player> Players { get; set; } = new();
[Parameter]
public EventCallback<List<Player>> OnOrderChanged { get; set; }
private string? SelectionMode = null;
private string? ValidationMessage;
private void UseManual()
{
SelectionMode = "manual";
foreach (var p in Players)
{
p.OrderNr = 0; // zurücksetzen
}
}
private async void UseRandom()
{
var rnd = new Random();
// Zufällige Reihenfolge vergeben
Players = Players.OrderBy(_ => rnd.Next()).ToList();
for (int i = 0; i < Players.Count; i++)
{
Players[i].OrderNr = i + 1;
}
// Startzonen zufällig verteilen
var availableZones = Enumerable.Range(1, Players.Count).OrderBy(_ => rnd.Next()).ToList();
for (int i = 0; i < Players.Count; i++)
{
Players[i].StartZone = availableZones[i];
}
SelectionMode = "random";
await OnOrderChanged.InvokeAsync(Players);
}
private void SetOrder(Player player, string? value)
{
if (int.TryParse(value, out var number))
{
player.OrderNr = number;
}
else
{
player.OrderNr = 0;
}
}
private bool IsNumberUsed(int number)
{
return Players.Any(p => p.OrderNr == number);
}
private async void FinishManual()
{
ValidationMessage = null;
var usedOrders = Players.Select(p => p.OrderNr).Where(o => o > 0).ToList();
if (usedOrders.Count != Players.Count)
{
ValidationMessage = "⚠️ Es wurden noch nicht allen Spielern Nummern zugewiesen.";
return;
}
if (usedOrders.Distinct().Count() != Players.Count)
{
ValidationMessage = "⚠️ Jede Nummer darf nur einmal vergeben werden.";
return;
}
Players = Players.OrderBy(p => p.OrderNr).ToList();
await OnOrderChanged.InvokeAsync(Players);
}
}

View File

@@ -0,0 +1,18 @@
@inject NavigationManager Navigation
@inject AuthenticationStateProvider AuthProvider
@code {
protected override async Task OnInitializedAsync()
{
var auth = await AuthProvider.GetAuthenticationStateAsync();
if (!auth.User.Identity?.IsAuthenticated ?? true)
{
Navigation.NavigateTo("/Account/Login", true); // <- deine Login-Seite
}
else
{
Navigation.NavigateTo("/AccessDenied", true); // Optional
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
namespace Tabletop.Controllers
{
[Route("[controller]/[action]")]
public class CultureController : Controller
{
public IActionResult Set(string culture, string redirectUri)
{
if (culture != null)
{
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture, culture)));
}
return LocalRedirect(redirectUri);
}
}
}

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