diff --git a/CMakeLists.txt b/CMakeLists.txt index 8621445bdbfa690be179ba784487fd519db22efa..db2c7a24828c07795aaa1b82aea5fdd123129ccc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,7 @@ else() include_directories(${SDL2_IMG_INCLUDE_DIR}) include_directories(${SDL2_TTF_INCLUDE_DIR}) include_directories(${HDF5_INCLUDE_DIRS}) + target_include_directories(advanced_wars PRIVATE ${highfive_SOURCE_DIR}/include) target_link_libraries(advanced_wars ${HDF5_LIBRARIES} @@ -160,19 +161,6 @@ target_include_directories(editor ${highfive_SOURCE_DIR}/include ) - -# writelevel -add_executable(writelevel ${PROJECT_SOURCE_DIR}/src/util/writelevel.cpp) -target_include_directories(writelevel - PRIVATE - ${highfive_SOURCE_DIR}/include -) -target_link_libraries(writelevel - ${HDF5_LIBRARIES} - ) - -target_include_directories(advanced_wars PRIVATE ${highfive_SOURCE_DIR}/include) - # Ressourcen kopieren (plattformübergreifend) foreach(FONT ${FONT_FILES}) add_custom_command( diff --git a/documentation/LevelEditor.md b/documentation/LevelEditor.md index ff2e6111d84f58c495cc0254beef6db7bfe533d9..6ff8889838ae30bf251c397d4f766cb488c7b47f 100644 --- a/documentation/LevelEditor.md +++ b/documentation/LevelEditor.md @@ -12,25 +12,20 @@ To receive Events from EventHandler a class needs to inherit from EventHandler a The available event methods, as well as the info on which class emits which event method and which class overwrites which event method can be seen in the below diagramm. The formless diagramm below demonstrates how the classes form the Application. For more details on the individual classes, take a look at the documentation in their header files. - + ## Level Specification -The level is stored in two datasets named 'metadata', 'tilesarray' in the level hdf5 file (level.h5). -The metadata dataset is in XML format. It provides the level height, width and name. -The tilesarray dataset is a giant array<uint8_t> which defines the level map through an array of tile ids. +The level is stored in a dataset called 'tilesarray' in the level hdf5 file. +The tilesarray dataset is a giant array<uint8_t> which defines the level map through an array of tile ids. +The attached attributes of the dataset define the level metadata. -### 1. metadata dataset (XML) -```xml -<?xml version="1.0" encoding="ASCII"?> -<level> - <width>20</width> <!-- Breite des Levels --> - <height>20</height> <!-- Hoehe des Levels --> - <name>Geiles Level<name><!-- Name des Levels --> -</level> -``` +### 1. tilesarray attributes +- width (int) The width of the level in number of tiles. +- height (int) The height of the level in number of tiles. +- name (string) The name of the level. -### 2. tilesarray dataset +### 2. tilesarray data The length of the tiles array is level width x level height (see level metadata). From the individual values inside the tiles array you can determine the IDs of the buildings and terrains. 0 - 29 are terrain IDs => Mapping see enum in src/game/Tile.hpp diff --git a/documentation/architecture.svg b/documentation/architecture.svg deleted file mode 100644 index 90ec08fc6dccd7e2b5b533866976f4b3b89ec8ed..0000000000000000000000000000000000000000 --- a/documentation/architecture.svg +++ /dev/null @@ -1,3 +0,0 @@ -<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1688.7907986111109 1234.7222222222222" width="1688.7907986111109" height="1234.7222222222222"><!-- svg-source:excalidraw --><metadata></metadata><defs><style class="style-fonts"> - @font-face { font-family: Virgil; src: url(data:font/woff2;base64,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); } - @font-face { font-family: Excalifont; src: url(data:font/woff2;base64,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); }</style></defs><rect x="0" y="0" width="1688.7907986111109" height="1234.7222222222222" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(181.33333333333303 468.429292929293) rotate(0 496.5 313.5)"><path d="M32 0 C392.22 -1.22, 750.78 -1.96, 961 0 M32 0 C350.37 -2.95, 668.6 -2.73, 961 0 M961 0 C981.11 -0.8, 992.43 10.61, 993 32 M961 0 C982.35 -1.02, 994.77 10.24, 993 32 M993 32 C990.27 180.3, 990.44 329.77, 993 595 M993 32 C993.61 250.68, 993.46 469.57, 993 595 M993 595 C994.01 615.74, 983.64 626.88, 961 627 M993 595 C994.03 614.05, 981.9 627.65, 961 627 M961 627 C646.22 628.26, 332.93 627.51, 32 627 M961 627 C711.11 624.58, 460.9 624.42, 32 627 M32 627 C10.59 626.53, 0.77 616.89, 0 595 M32 627 C9.28 624.92, -0.3 617.43, 0 595 M0 595 C0.03 426.99, -0.11 258.91, 0 32 M0 595 C-1.21 479.68, -1.88 365.05, 0 32 M0 32 C1.86 10.09, 9.1 -1, 32 0 M0 32 C0.31 11.97, 10.76 0.02, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(519.9999999999998 397.3181818181819) rotate(0 98.72837829589844 35)"><text x="0" y="24.668" font-family="Virgil, Segoe UI Emoji" font-size="28px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">MainWindow.cpp</text><text x="0" y="59.668" font-family="Virgil, Segoe UI Emoji" font-size="28px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QMainWindow)</text></g><g stroke-linecap="round"><g transform="translate(180.33333333333303 529.4292929292931) rotate(0 497 -1)"><path d="M-0.62 0.95 C165.13 0.91, 827.6 -0.38, 993.57 -0.8 M1.25 0.4 C166.89 0.12, 826.77 -1.62, 992.4 -2.3" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(602.7777777777776 480.429292929293) rotate(0 55.43994903564453 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">TopBar.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QToolBar)</text></g><g stroke-linecap="round" transform="translate(210.33333333333303 482.429292929293) rotate(0 117 17)"><path d="M8.5 0 C82.95 -0.86, 159.8 -1.13, 225.5 0 M8.5 0 C71.09 -0.8, 134.25 -1.35, 225.5 0 M225.5 0 C232.33 0.33, 232.7 2.76, 234 8.5 M225.5 0 C233.15 -1.94, 232.78 3.56, 234 8.5 M234 8.5 C234.21 14.51, 234.75 16.2, 234 25.5 M234 8.5 C234.01 14.79, 233.84 21.05, 234 25.5 M234 25.5 C234.43 31.84, 231.54 35.7, 225.5 34 M234 25.5 C235.6 32.53, 230.89 33.21, 225.5 34 M225.5 34 C143.1 32.42, 65.1 32.35, 8.5 34 M225.5 34 C151.33 35.67, 76.52 36.47, 8.5 34 M8.5 34 C1.77 32.02, 1.93 31.63, 0 25.5 M8.5 34 C3.25 34.52, 1.32 30.23, 0 25.5 M0 25.5 C0.55 19.94, -1 13.59, 0 8.5 M0 25.5 C-0.13 21.51, -0.72 16.51, 0 8.5 M0 8.5 C1.94 3.8, 4.48 -0.8, 8.5 0 M0 8.5 C1.85 3.76, 3.37 -0.05, 8.5 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(217.33333333333303 491.429292929293) rotate(0 0.5 10)"><path d="M0.25 0 C0.42 0.01, 0.61 -0.02, 0.75 0 M0.25 0 C0.35 0, 0.45 0.01, 0.75 0 M0.75 0 C0.89 -0.04, 0.36 0.81, 1 0.25 M0.75 0 C1.55 0.25, 1.22 -0.63, 1 0.25 M1 0.25 C1 7.43, 1.16 12.83, 1 19.75 M1 0.25 C1.04 6.11, 1.06 12.31, 1 19.75 M1 19.75 C1.88 18.99, 0.8 19.82, 0.75 20 M1 19.75 C1.15 20.78, 1.32 19.55, 0.75 20 M0.75 20 C0.64 20.01, 0.47 20, 0.25 20 M0.75 20 C0.64 19.99, 0.54 20.01, 0.25 20 M0.25 20 C-0.51 19.6, 0.64 20.51, 0 19.75 M0.25 20 C0.6 21.07, 0.74 19.62, 0 19.75 M0 19.75 C-0.27 15.59, 0.89 10.28, 0 0.25 M0 19.75 C-0.15 15.03, 0.3 11.21, 0 0.25 M0 0.25 C0.96 -0.63, 0.37 0.04, 0.25 0 M0 0.25 C-1.04 -0.02, 0.34 1.02, 0.25 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(229.33333333333303 372.429292929293) rotate(0 -1 53.5)"><path d="M-0.81 -1.05 C-1.22 16.8, -1.36 89.29, -1.4 107.47 M0.96 1.01 C0.36 18.47, -1.65 88.06, -2.03 105.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(229.33333333333303 372.429292929293) rotate(0 -1 53.5)"><path d="M-9.97 81.88 C-8.59 92.11, -2.98 101.84, -2.03 105.59 M-9.97 81.88 C-6.47 90.15, -4.54 100.94, -2.03 105.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(229.33333333333303 372.429292929293) rotate(0 -1 53.5)"><path d="M7.13 82.33 C1.89 92.27, 0.88 101.83, -2.03 105.59 M7.13 82.33 C4.08 90.57, -0.53 101.19, -2.03 105.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(149.33333333333303 347.429292929293) rotate(0 87.659912109375 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">LevelNameEdit.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QLineEdit)</text></g><g stroke-linecap="round" transform="translate(1063.333333333333 486.429292929293) rotate(0 47.5 12)"><path d="M6 0 C34.28 0.89, 60.18 0.43, 89 0 M6 0 C25.62 0.6, 43.35 0.53, 89 0 M89 0 C94.69 0.9, 93.44 1.88, 95 6 M89 0 C92.78 0.44, 96.27 1.45, 95 6 M95 6 C93.88 9.47, 95.27 10.92, 95 18 M95 6 C95.28 9.11, 95.1 13.08, 95 18 M95 18 C94.21 21.66, 93.22 22.3, 89 24 M95 18 C96.62 23.93, 91.93 23.49, 89 24 M89 24 C56.56 26.15, 25.69 22.71, 6 24 M89 24 C58.42 24.22, 27.9 24.91, 6 24 M6 24 C1.19 22.28, 1.69 21.82, 0 18 M6 24 C2.56 23.95, 2.28 23.17, 0 18 M0 18 C-0.32 13.43, 1.18 8.69, 0 6 M0 18 C-0.43 13.94, 0.08 10.5, 0 6 M0 6 C-1.16 0.67, 0.99 1.82, 6 0 M0 6 C2.11 3.57, 0.77 -2.16, 6 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1085.333333333333 488.429292929293) rotate(0 23.97998046875 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Save</text></g><g stroke-linecap="round"><g transform="translate(1242.333333333333 488.429292929293) rotate(0 -40.5 2)"><path d="M0.21 0.91 C-13.54 1.66, -68.62 4.36, -81.99 4.83 M-1.14 0.34 C-14.57 0.73, -66.61 2.39, -79.79 3.15" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1242.333333333333 488.429292929293) rotate(0 -40.5 2)"><path d="M-56.69 -6.4 C-63.1 -4.72, -71.17 1.88, -79.79 3.15 M-56.69 -6.4 C-63.31 -3.12, -69.07 -0.07, -79.79 3.15" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1242.333333333333 488.429292929293) rotate(0 -40.5 2)"><path d="M-55.96 10.69 C-62.66 6.49, -70.99 7.21, -79.79 3.15 M-55.96 10.69 C-62.69 9.23, -68.65 7.53, -79.79 3.15" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1247.333333333333 477.429292929293) rotate(0 129.68988037109375 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">SaveButton.cpp (QButton)</text></g><g stroke-linecap="round" transform="translate(1005.333333333333 484.429292929293) rotate(0 13.5 17.5)"><path d="M6.75 0 C9.92 -0.2, 16.31 1.38, 20.25 0 M6.75 0 C11.62 -0.38, 16.32 -0.58, 20.25 0 M20.25 0 C26.35 -1.97, 28.5 1.66, 27 6.75 M20.25 0 C24.01 -1.02, 27.24 2.84, 27 6.75 M27 6.75 C28.85 14.28, 28.08 22.09, 27 28.25 M27 6.75 C26.92 14.38, 27.65 22.43, 27 28.25 M27 28.25 C26.26 31.42, 22.96 35.3, 20.25 35 M27 28.25 C29.18 33.03, 25.2 33.24, 20.25 35 M20.25 35 C15.67 35.81, 12.98 35.93, 6.75 35 M20.25 35 C16.03 34.72, 10.87 34.5, 6.75 35 M6.75 35 C2.9 34.03, -0.39 33.16, 0 28.25 M6.75 35 C0.73 33.52, -2.27 30.99, 0 28.25 M0 28.25 C1.12 23.57, 1.49 15.7, 0 6.75 M0 28.25 C0.53 21.91, 0.3 15.73, 0 6.75 M0 6.75 C0.21 2.02, 1.34 -0.13, 6.75 0 M0 6.75 C-0.3 1.59, 1.63 1.72, 6.75 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1012.333333333333 489.429292929293) rotate(0 6.5 12.5)"><text x="6.5" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">+</text></g><g stroke-linecap="round" transform="translate(912.333333333333 484.429292929293) rotate(0 40.5 17.5)"><path d="M8.75 0 C22.56 0.4, 37.31 -1.91, 72.25 0 M8.75 0 C23.05 -1.57, 38.12 0.11, 72.25 0 M72.25 0 C79.86 -0.3, 79.84 2.62, 81 8.75 M72.25 0 C78.7 -1.52, 83.27 4.93, 81 8.75 M81 8.75 C80.09 13.85, 80.67 21.49, 81 26.25 M81 8.75 C81.41 11.99, 80.31 16.06, 81 26.25 M81 26.25 C80.07 33.14, 79.58 34.9, 72.25 35 M81 26.25 C80.75 30.39, 78.08 34.73, 72.25 35 M72.25 35 C51.55 36.58, 32.98 33.29, 8.75 35 M72.25 35 C48.97 35.89, 24.15 35.79, 8.75 35 M8.75 35 C4.36 35.12, -0.4 31.08, 0 26.25 M8.75 35 C3.76 33.6, 1.1 34.33, 0 26.25 M0 26.25 C-1.35 23.1, 1.08 17.8, 0 8.75 M0 26.25 C0.84 21.29, 0.31 17.23, 0 8.75 M0 8.75 C-1.1 1.96, 3.44 -0.83, 8.75 0 M0 8.75 C-0.18 4.03, 2.99 -1.76, 8.75 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(926.8633397420244 489.429292929293) rotate(0 25.969993591308594 12.5)"><text x="25.969993591308594" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">100%</text></g><g stroke-linecap="round" transform="translate(872.333333333333 483.429292929293) rotate(0 14.5 17.5)"><path d="M7.25 0 C10.22 0.81, 16.13 0.62, 21.75 0 M7.25 0 C10.45 0.04, 13.83 -0.2, 21.75 0 M21.75 0 C28.53 -1.66, 30.01 3.94, 29 7.25 M21.75 0 C25.25 -2.12, 27.32 2.65, 29 7.25 M29 7.25 C27.46 15.36, 29.95 22.12, 29 27.75 M29 7.25 C29.49 11.1, 29 15.36, 29 27.75 M29 27.75 C30.06 31.65, 24.8 35.36, 21.75 35 M29 27.75 C30.26 30.8, 26.27 32.76, 21.75 35 M21.75 35 C15.99 33.78, 11.09 33.83, 7.25 35 M21.75 35 C15.58 34.65, 9.83 34.46, 7.25 35 M7.25 35 C3.59 36.96, -1.4 33.4, 0 27.75 M7.25 35 C4.66 34.8, 1.38 33.03, 0 27.75 M0 27.75 C-1.77 20.42, 0.62 11.76, 0 7.25 M0 27.75 C-0.43 21.36, -0.19 15.55, 0 7.25 M0 7.25 C0.64 1.25, 0.77 1.31, 7.25 0 M0 7.25 C1.24 3.09, 4.53 0.82, 7.25 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(882.723340352376 488.429292929293) rotate(0 4.109992980957031 12.5)"><text x="4.109992980957031" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">-</text></g><g stroke-linecap="round"><g transform="translate(1025.333333333333 418.429292929293) rotate(0 0.5 33.5)"><path d="M-0.62 -0.01 C-0.55 11.31, 0.02 56.24, 0.23 67.4 M1.26 -1.07 C1.73 9.93, 2.79 53.94, 2.54 65.49" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1025.333333333333 418.429292929293) rotate(0 0.5 33.5)"><path d="M-6.11 42.04 C-3.77 47.5, -0.6 54.94, 2.54 65.49 M-6.11 42.04 C-2.46 49.71, -1.44 56.21, 2.54 65.49" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1025.333333333333 418.429292929293) rotate(0 0.5 33.5)"><path d="M10.99 41.96 C8.45 47.36, 6.73 54.82, 2.54 65.49 M10.99 41.96 C9.59 49.67, 5.56 56.2, 2.54 65.49" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(948.333333333333 390.429292929293) rotate(0 -1.5 45)"><path d="M0.31 -0.19 C-0.53 14.85, -3.52 75.53, -3.92 90.75 M-0.99 -1.34 C-1.55 13.32, -1.46 73.9, -1.7 89.03" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(948.333333333333 390.429292929293) rotate(0 -1.5 45)"><path d="M-10.09 65.48 C-7.04 73.14, -2.17 85.36, -1.7 89.03 M-10.09 65.48 C-5.72 74.54, -2.43 83.73, -1.7 89.03" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(948.333333333333 390.429292929293) rotate(0 -1.5 45)"><path d="M7.01 65.59 C3.37 73.25, 1.54 85.43, -1.7 89.03 M7.01 65.59 C4.61 74.74, 1.14 83.9, -1.7 89.03" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(857.333333333333 427.429292929293) rotate(0 12.5 27)"><path d="M-0.25 -0.42 C3.93 8.8, 21.72 45.57, 25.74 54.77 M1.81 -1.68 C5.85 7.24, 21.12 43.96, 25.18 53.04" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(857.333333333333 427.429292929293) rotate(0 12.5 27)"><path d="M8.05 34.83 C13.31 42.52, 21.32 45.8, 25.18 53.04 M8.05 34.83 C13.65 41.12, 20.02 48.33, 25.18 53.04" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(857.333333333333 427.429292929293) rotate(0 12.5 27)"><path d="M23.77 28.08 C23.46 38.12, 25.93 43.77, 25.18 53.04 M23.77 28.08 C23.58 36.73, 24.22 46.39, 25.18 53.04" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(975.333333333333 397.429292929293) rotate(0 142.2398681640625 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">ZoomInButton.cpp (QButton)</text></g><g transform="translate(919.333333333333 368.429292929293) rotate(0 154.099853515625 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">ZoomInfoBUtton.cpp (QButton)</text></g><g transform="translate(739.333333333333 400.429292929293) rotate(0 98.07991027832031 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">ZoomOutBUtton.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic"> (QButton)</text></g><g stroke-linecap="round" transform="translate(908.333333333333 528.4292929292931) rotate(0 132.5 282.5)"><path d="M32 0 C107.68 -0.2, 181.93 -0.74, 233 0 M32 0 C84.58 -0.32, 139.55 0.62, 233 0 M233 0 C254.72 0.92, 264.11 9.95, 265 32 M233 0 C252.46 0, 264.98 12.59, 265 32 M265 32 C263.46 146.81, 263.79 261.73, 265 533 M265 32 C264.71 216.17, 264.44 399.83, 265 533 M265 533 C264.79 556.25, 253.75 564.54, 233 565 M265 533 C263.43 553.47, 254.63 565.31, 233 565 M233 565 C181.87 565.95, 131.39 567.04, 32 565 M233 565 C163.75 564.45, 93.71 565.49, 32 565 M32 565 C11.87 566.97, 1.46 553.94, 0 533 M32 565 C11.74 566.93, 0.6 554.32, 0 533 M0 533 C0.22 423.53, -0.28 314.91, 0 32 M0 533 C0.18 386.7, 0.13 240.44, 0 32 M0 32 C1.34 10.17, 11.54 1.36, 32 0 M0 32 C1.77 12.9, 12.25 -0.5, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(1223.333333333333 582.4292929292931) rotate(0 -23 -1.4999999999999858)"><path d="M-0.37 -0.23 C-8.08 -0.87, -38.83 -2.82, -46.38 -3.35 M0.44 -0.83 C-7.08 -1.44, -37.63 -2.46, -45.34 -2.83" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1223.333333333333 582.4292929292931) rotate(0 -23 -1.4999999999999858)"><path d="M-23.38 -9.83 C-29.77 -8.69, -35.99 -5.47, -45.34 -2.83 M-23.38 -9.83 C-28.55 -8.18, -32.93 -6.81, -45.34 -2.83" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1223.333333333333 582.4292929292931) rotate(0 -23 -1.4999999999999858)"><path d="M-24.02 5.92 C-30.3 2.77, -36.34 1.69, -45.34 -2.83 M-24.02 5.92 C-29.02 4.08, -33.27 1.95, -45.34 -2.83" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1225.333333333333 568.4292929292931) rotate(0 77.17992401123047 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">TileSelector.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QScrollArea)</text></g><g stroke-linecap="round" transform="translate(180.33333333333303 533.4292929292931) rotate(0 363 279.5)"><path d="M32 0 C215.92 1.83, 398.86 2.46, 694 0 M32 0 C180.91 0.52, 329.24 0.39, 694 0 M694 0 C714.71 1.49, 727.21 9.49, 726 32 M694 0 C713.4 -1.3, 728.05 11.18, 726 32 M726 32 C723.76 180.22, 724.95 328.51, 726 527 M726 32 C725.6 187.45, 725.35 343.49, 726 527 M726 527 C727.77 546.98, 713.79 560.64, 694 559 M726 527 C724.33 547.44, 716.28 559.42, 694 559 M694 559 C523.33 555.73, 351.72 556.14, 32 559 M694 559 C519.52 556.36, 345.02 556.09, 32 559 M32 559 C8.95 560.86, 1.46 549.67, 0 527 M32 559 C8.93 559.29, 2.04 546.92, 0 527 M0 527 C-1.75 411.71, -0.93 296.78, 0 32 M0 527 C-1.1 389.49, -1.21 251.32, 0 32 M0 32 C-0.11 9.35, 9.88 0.55, 32 0 M0 32 C0.53 12.21, 12.51 -0.56, 32 0" stroke="#e03131" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(512.333333333333 1130.429292929293) rotate(0 0 -16.5)"><path d="M-0.52 -0.22 C-0.41 -5.75, 0.34 -27.1, 0.47 -32.65 M0.22 -0.81 C0.31 -6.27, 0.31 -28.04, 0.28 -33.44" stroke="#e03131" stroke-width="1" fill="none"></path></g><g transform="translate(512.333333333333 1130.429292929293) rotate(0 0 -16.5)"><path d="M5.96 -17.94 C4.03 -21.41, 3.36 -25.88, 0.28 -33.44 M5.96 -17.94 C4.57 -20.88, 3.41 -24.45, 0.28 -33.44" stroke="#e03131" stroke-width="1" fill="none"></path></g><g transform="translate(512.333333333333 1130.429292929293) rotate(0 0 -16.5)"><path d="M-5.33 -17.92 C-4.24 -21.34, -1.87 -25.81, 0.28 -33.44 M-5.33 -17.92 C-4.39 -20.85, -3.22 -24.43, 0.28 -33.44" stroke="#e03131" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(454.33333333333303 1133.429292929293) rotate(0 74.98992156982422 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#e03131" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">LevelView.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#e03131" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QGraphicsView)</text></g><g stroke-linecap="round" transform="translate(315.33333333333303 578.4292929292931) rotate(0 241 228.5)"><path d="M32 0 C146.33 -1.42, 257.76 -0.74, 450 0 M32 0 C115.45 1.81, 200.12 1.28, 450 0 M450 0 C471.46 -0.74, 482.45 8.99, 482 32 M450 0 C471.62 -1.64, 480.4 12.52, 482 32 M482 32 C480.83 137.55, 482.65 241.93, 482 425 M482 32 C481.31 143.38, 481.08 253.68, 482 425 M482 425 C483.61 444.62, 472.21 457.89, 450 457 M482 425 C483.83 445.93, 470.94 456.08, 450 457 M450 457 C293.51 457.25, 135.32 457.52, 32 457 M450 457 C297.48 456.29, 143.84 456.54, 32 457 M32 457 C11.7 455.78, 1 448.29, 0 425 M32 457 C9.59 455.68, 2.19 444.24, 0 425 M0 425 C0.5 336.06, 0.98 247.97, 0 32 M0 425 C-0.88 281.81, -0.96 139.85, 0 32 M0 32 C1.23 12.56, 12.45 -0.4, 32 0 M0 32 C-1.03 11.67, 9 1.82, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(738.333333333333 1131.429292929293) rotate(0 -1 -48)"><path d="M-1.1 -1.19 C-1.57 -17.1, -1.85 -80.14, -2.02 -95.86 M0.52 0.8 C-0.18 -14.85, -2.4 -77.84, -2.98 -94.24" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(738.333333333333 1131.429292929293) rotate(0 -1 -48)"><path d="M6.4 -71.07 C1.06 -79.68, -0.15 -88.38, -2.98 -94.24 M6.4 -71.07 C3.17 -78.61, 0.8 -86.65, -2.98 -94.24" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(738.333333333333 1131.429292929293) rotate(0 -1 -48)"><path d="M-10.69 -70.46 C-9.77 -79.14, -4.72 -88.06, -2.98 -94.24 M-10.69 -70.46 C-8.09 -78.14, -4.63 -86.39, -2.98 -94.24" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(669.333333333333 1135.429292929293) rotate(0 82.77991485595703 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">LevelScene.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QGraphicsScene)</text></g><g stroke-linecap="round" transform="translate(320.33333333333303 588.4292929292931) rotate(0 21.5 18.5)"><path d="M9.25 0 C20.01 0.76, 26 0.03, 33.75 0 M9.25 0 C19.41 -0.24, 28.62 0.61, 33.75 0 M33.75 0 C40.46 1.11, 42.64 3.02, 43 9.25 M33.75 0 C37.82 1.09, 45.27 3.31, 43 9.25 M43 9.25 C43.43 14.75, 41.78 17.92, 43 27.75 M43 9.25 C42.23 14.12, 42.47 17.97, 43 27.75 M43 27.75 C42.57 33.29, 38.49 35.69, 33.75 37 M43 27.75 C42.31 36, 39.42 35.78, 33.75 37 M33.75 37 C27.37 37.9, 18.55 36.99, 9.25 37 M33.75 37 C27.4 37.24, 20.28 36.69, 9.25 37 M9.25 37 C3.16 35.98, -0.62 34.77, 0 27.75 M9.25 37 C4.45 38.28, 0.77 35.29, 0 27.75 M0 27.75 C-0.84 21.83, -1.11 18.45, 0 9.25 M0 27.75 C-0.9 22.6, -0.63 17.73, 0 9.25 M0 9.25 C1.28 2.3, 2.36 0.79, 9.25 0 M0 9.25 C-2.17 0.79, 1.37 -1.65, 9.25 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(366.33333333333303 590.4292929292931) rotate(0 19.5 17.5)"><path d="M8.75 0 C14.48 0.81, 22.09 1.13, 30.25 0 M8.75 0 C12.41 -0.36, 17.27 -0.4, 30.25 0 M30.25 0 C34.66 -1.31, 38.4 4.72, 39 8.75 M30.25 0 C35.58 -1.22, 38.69 2.57, 39 8.75 M39 8.75 C38.03 13.32, 38.56 17.7, 39 26.25 M39 8.75 C38.91 14.66, 39.09 20.93, 39 26.25 M39 26.25 C38.38 32.93, 37.27 36.11, 30.25 35 M39 26.25 C39.77 33.46, 35.99 33.98, 30.25 35 M30.25 35 C20.98 36.73, 13.64 35.46, 8.75 35 M30.25 35 C22.23 34.84, 16.11 34.56, 8.75 35 M8.75 35 C2.19 35.79, -1.89 30.09, 0 26.25 M8.75 35 C1.2 33.35, 2.23 34.3, 0 26.25 M0 26.25 C1.13 21.77, 0.87 18.25, 0 8.75 M0 26.25 C0.17 20.64, 0.41 15.47, 0 8.75 M0 8.75 C0.37 1, 0.95 -0.63, 8.75 0 M0 8.75 C-2.27 1.68, 4.22 0.85, 8.75 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(407.33333333333303 592.4292929292931) rotate(0 19 17.5)"><path d="M8.75 0 C12.21 -2.07, 17.95 -1.52, 29.25 0 M8.75 0 C16.34 -0.65, 25.62 -0.32, 29.25 0 M29.25 0 C35.97 -0.96, 38.17 2.3, 38 8.75 M29.25 0 C33.65 0.31, 37.44 3.84, 38 8.75 M38 8.75 C36.31 13.46, 36.3 18.34, 38 26.25 M38 8.75 C37.77 15.63, 37.75 20.41, 38 26.25 M38 26.25 C38.81 33.71, 34.44 36.29, 29.25 35 M38 26.25 C36.95 33.87, 35.7 34.47, 29.25 35 M29.25 35 C23.99 34.96, 19.58 35.62, 8.75 35 M29.25 35 C21.64 35.48, 13.28 35.95, 8.75 35 M8.75 35 C3.6 36.06, -0.03 32.15, 0 26.25 M8.75 35 C5.01 36.72, 1.66 32.54, 0 26.25 M0 26.25 C-0.4 20.69, -0.62 14.43, 0 8.75 M0 26.25 C0.97 20.51, -0.01 14.56, 0 8.75 M0 8.75 C-1.06 2.61, 1.97 -0.09, 8.75 0 M0 8.75 C0.27 2.26, 2.31 1.34, 8.75 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(443.33333333333303 593.4292929292931) rotate(0 23.5 17)"><path d="M8.5 0 C17.98 -0.54, 28.48 -0.79, 38.5 0 M8.5 0 C19.1 1.21, 28.52 0.08, 38.5 0 M38.5 0 C42.26 -1.06, 46.69 1.89, 47 8.5 M38.5 0 C44.07 0.27, 46.34 2.23, 47 8.5 M47 8.5 C46.21 15.46, 47.84 21.78, 47 25.5 M47 8.5 C47.86 14.93, 46.52 21.43, 47 25.5 M47 25.5 C45.53 32.33, 42.39 35.3, 38.5 34 M47 25.5 C46.76 33.3, 44.05 32.47, 38.5 34 M38.5 34 C26.79 33.78, 16.8 33.96, 8.5 34 M38.5 34 C27.54 33.62, 17.45 34.1, 8.5 34 M8.5 34 C2.5 34.19, -1.45 32.42, 0 25.5 M8.5 34 C0.87 34.71, -0.54 31.19, 0 25.5 M0 25.5 C-1.11 20.88, -0.96 15.77, 0 8.5 M0 25.5 C0.22 22.14, 0.65 18.26, 0 8.5 M0 8.5 C1.52 1.2, 3.35 -0.48, 8.5 0 M0 8.5 C0.5 1.77, 2.59 1.99, 8.5 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(90.33333333333303 598.4292929292931) rotate(0 114.5 3.5)"><path d="M-0.45 0.03 C37.84 1.39, 190.79 5.94, 229.13 7.13 M1.52 -1.01 C39.7 0.66, 190.32 7.14, 228.25 8.74" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(90.33333333333303 598.4292929292931) rotate(0 114.5 3.5)"><path d="M204.41 16.28 C210.51 14.68, 216.69 13.51, 228.25 8.74 M204.41 16.28 C211.84 13.8, 219.78 11.53, 228.25 8.74" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(90.33333333333303 598.4292929292931) rotate(0 114.5 3.5)"><path d="M205.14 -0.8 C210.91 1.89, 216.9 5.01, 228.25 8.74 M205.14 -0.8 C212.18 2.06, 219.89 5.13, 228.25 8.74" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(10.33333333333303 578.4292929292931) rotate(0 111.31987762451172 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Tile.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QGraphicsPixmapItem)</text></g><g stroke-linecap="round" transform="translate(922.4242424242425 541.7929292929294) rotate(0 39.545454545454504 38.18181818181817)"><path d="M19.09 0 C31.99 0.05, 47.15 -0.53, 60 0 M19.09 0 C29.75 0.09, 39.48 -0.85, 60 0 M60 0 C70.78 -0.88, 78.34 7.86, 79.09 19.09 M60 0 C74.4 -2.27, 77.32 8.05, 79.09 19.09 M79.09 19.09 C80.58 28.26, 77.27 36.91, 79.09 57.27 M79.09 19.09 C79.94 28.61, 79.51 37.47, 79.09 57.27 M79.09 57.27 C79.29 69.54, 73.18 75.68, 60 76.36 M79.09 57.27 C77.93 70.21, 73.15 75.2, 60 76.36 M60 76.36 C48.22 76.64, 36.6 74.93, 19.09 76.36 M60 76.36 C45.42 76.38, 28.85 75.34, 19.09 76.36 M19.09 76.36 C5.78 75.7, 1.48 71.62, 0 57.27 M19.09 76.36 C7.67 76.59, 1.84 68.94, 0 57.27 M0 57.27 C1.15 50.14, 0.65 40.5, 0 19.09 M0 57.27 C0.89 49.78, -0.19 41.24, 0 19.09 M0 19.09 C1.96 5.63, 7.56 -1.36, 19.09 0 M0 19.09 C0.71 8.1, 6.47 0.09, 19.09 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(1014.2424242424242 544.5202020202021) rotate(0 44.09090909090912 39.09090909090909)"><path d="M19.55 0 C31.27 1.47, 41.22 -0.04, 68.64 0 M19.55 0 C30.45 0.13, 39.41 -0.6, 68.64 0 M68.64 0 C83.62 -0.73, 89.38 5.16, 88.18 19.55 M68.64 0 C82.38 1.74, 88.28 6.61, 88.18 19.55 M88.18 19.55 C88.44 29.55, 88.97 39.15, 88.18 58.64 M88.18 19.55 C88.86 30.86, 87.55 41.71, 88.18 58.64 M88.18 58.64 C86.87 73.14, 81.14 78.81, 68.64 78.18 M88.18 58.64 C90.11 71.8, 83.64 77.52, 68.64 78.18 M68.64 78.18 C50.29 76.91, 30.26 79.58, 19.55 78.18 M68.64 78.18 C58.95 78.16, 48.2 78.52, 19.55 78.18 M19.55 78.18 C6.42 77.91, -1.93 73.64, 0 58.64 M19.55 78.18 C5.16 76.73, 1.25 73.78, 0 58.64 M0 58.64 C-0.15 44.9, -1.77 29.35, 0 19.55 M0 58.64 C-0.53 47.68, -0.01 39.17, 0 19.55 M0 19.55 C0.14 6.1, 5.94 -0.56, 19.55 0 M0 19.55 C1.96 5.38, 6.93 1.77, 19.55 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(931.5151515151517 629.0656565656567) rotate(0 37.727272727272634 46.81818181818181)"><path d="M18.86 0 C28.49 -1.64, 41.56 1.46, 56.59 0 M18.86 0 C32.64 0.5, 45.79 -0.05, 56.59 0 M56.59 0 C68.38 1.18, 73.81 5.68, 75.45 18.86 M56.59 0 C67.39 0.5, 77.53 6.45, 75.45 18.86 M75.45 18.86 C77 33.49, 75.66 51.63, 75.45 74.77 M75.45 18.86 C75.63 37.14, 76.59 53.14, 75.45 74.77 M75.45 74.77 C75.74 89.12, 67.65 92, 56.59 93.64 M75.45 74.77 C74.97 87.93, 70.99 94.76, 56.59 93.64 M56.59 93.64 C46.03 93.4, 29.78 92.11, 18.86 93.64 M56.59 93.64 C43.36 94.39, 30.19 93.63, 18.86 93.64 M18.86 93.64 C4.94 93.41, 1.49 86.93, 0 74.77 M18.86 93.64 C7.83 93.32, -1.17 87.17, 0 74.77 M0 74.77 C2.1 57.55, 0.34 38.86, 0 18.86 M0 74.77 C-1.16 57.05, 0.43 41.23, 0 18.86 M0 18.86 C1.7 5.85, 5.21 0.68, 18.86 0 M0 18.86 C-1.03 6.63, 4.97 1, 18.86 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(1023.3333333333335 634.5202020202021) rotate(0 54.545454545454504 50)"><path d="M25 0 C38.24 -0.34, 52.52 -0.4, 84.09 0 M25 0 C43.22 -0.14, 60.61 -0.07, 84.09 0 M84.09 0 C99.74 -0.15, 109.55 6.65, 109.09 25 M84.09 0 C101.51 1.87, 110.05 8.18, 109.09 25 M109.09 25 C111.11 39.03, 107.98 57.73, 109.09 75 M109.09 25 C108.98 44.18, 109.07 62.82, 109.09 75 M109.09 75 C107.94 92.54, 102.02 101.39, 84.09 100 M109.09 75 C110.7 92.61, 103 99.39, 84.09 100 M84.09 100 C71.63 101.14, 57.62 98.14, 25 100 M84.09 100 C70.64 99.85, 58.84 100.65, 25 100 M25 100 C6.4 99.65, 1.6 93.59, 0 75 M25 100 C6.74 101.89, -2.03 93.21, 0 75 M0 75 C-0.09 58.47, 0.35 46.96, 0 25 M0 75 C-0.44 56.37, 0.39 39.04, 0 25 M0 25 C1.64 6.41, 7.17 1.87, 25 0 M0 25 C-0.39 7.74, 6.9 1.81, 25 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(1222.4242424242425 672.7020202020203) rotate(0 -43.18181818181813 0.45454545454543904)"><path d="M-0.83 0.99 C-15.56 1.08, -73.16 1.91, -87.42 1.72 M0.94 0.46 C-13.52 0.72, -70.58 0, -85.27 0.02" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1222.4242424242425 672.7020202020203) rotate(0 -43.18181818181813 0.45454545454543904)"><path d="M-61.72 -8.39 C-68.95 -7.12, -78.2 -2.25, -85.27 0.02 M-61.72 -8.39 C-67.58 -6.74, -70.89 -4.31, -85.27 0.02" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1222.4242424242425 672.7020202020203) rotate(0 -43.18181818181813 0.45454545454543904)"><path d="M-61.83 8.71 C-69.18 3.48, -78.39 1.85, -85.27 0.02 M-61.83 8.71 C-67.58 6.77, -70.87 5.62, -85.27 0.02" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1226.9696969696972 658.1565656565658) rotate(0 125.56987762451172 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">TileButton.cpp (QButton)</text></g><g transform="translate(1318.8888888888887 918.6111111111111) rotate(0 53.07018280029297 17.5)"><text x="0" y="24.668" font-family="Virgil, Segoe UI Emoji" font-size="28px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">main.cpp</text></g><g transform="translate(1284.4444444444448 785.8333333333335) rotate(0 139.84982299804688 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">SpriteProvider.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(stores all QPixmap Sprites)</text></g><g stroke-linecap="round"><g transform="translate(1268.888888888889 792.5000000000001) rotate(0 -78.8888888888888 -39.44444444444446)"><path d="M-0.72 1.11 C-27.09 -11.74, -131.06 -65.01, -157.21 -78.23 M1.11 0.65 C-25.46 -12.45, -131.29 -66.53, -157.86 -80.01" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1268.888888888889 792.5000000000001) rotate(0 -78.8888888888888 -39.44444444444446)"><path d="M-133.05 -76.97 C-140.74 -77.35, -151.08 -79.2, -157.86 -80.01 M-133.05 -76.97 C-142.62 -78.36, -149.77 -80.05, -157.86 -80.01" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1268.888888888889 792.5000000000001) rotate(0 -78.8888888888888 -39.44444444444446)"><path d="M-140.81 -61.73 C-145.36 -67.94, -152.71 -75.68, -157.86 -80.01 M-140.81 -61.73 C-147.57 -68.47, -151.97 -75.57, -157.86 -80.01" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1183.3333333333335 726.9444444444446) rotate(0 112.28987121582031 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">QPixmap getSprite(id)</text></g><g transform="translate(933.3333333333333 818.0555555555557) rotate(0 112.28987121582031 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">QPixmap getSprite(id)</text></g><g stroke-linecap="round"><g transform="translate(1272.2222222222224 836.784364824992) rotate(0 -393.33333333333337 -97.142182412496)"><path d="M0.45 0.38 C-130.53 -32.13, -655.33 -161.77, -786.53 -194.17 M-0.77 -0.46 C-131.85 -32.84, -656.28 -160.56, -787.41 -192.57" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1272.2222222222224 836.784364824992) rotate(0 -393.33333333333337 -97.142182412496)"><path d="M-762.56 -195.31 C-768.09 -194.31, -774.72 -193.98, -787.41 -192.57 M-762.56 -195.31 C-769.74 -193.76, -777.78 -192.73, -787.41 -192.57" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1272.2222222222224 836.784364824992) rotate(0 -393.33333333333337 -97.142182412496)"><path d="M-766.62 -178.69 C-771.26 -181.85, -776.88 -185.66, -787.41 -192.57 M-766.62 -178.69 C-772.53 -182.55, -779.25 -186.94, -787.41 -192.57" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(1034.4444444444446 784.7222222222223) rotate(0 1.6666666666667425 15.555555555555543)"><path d="M0.18 0.32 C0.72 5.52, 2.69 26.35, 3.2 31.54 M-0.38 0.01 C0.08 5.03, 2.16 25.7, 2.7 30.79" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(1351.1111111111113 908.0555555555557) rotate(0 10.201238247079914 -28.333333333333258)"><path d="M-0.46 0.92 C2.78 -8.25, 16.75 -46.17, 20.2 -55.79 M1.5 0.36 C4.49 -9.08, 16.16 -48.13, 19.14 -57.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1351.1111111111113 908.0555555555557) rotate(0 10.201238247079914 -28.333333333333258)"><path d="M20.4 -32.48 C21.63 -41.95, 21.1 -51, 19.14 -57.45 M20.4 -32.48 C20.49 -39.78, 19.35 -46.28, 19.14 -57.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1351.1111111111113 908.0555555555557) rotate(0 10.201238247079914 -28.333333333333258)"><path d="M4.06 -37.51 C11.24 -45.08, 16.68 -52.29, 19.14 -57.45 M4.06 -37.51 C8.96 -43.4, 12.59 -48.42, 19.14 -57.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1361.1111111111109 878.1000000000004) rotate(0 158.83984375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">initialize (load hdf5 spritesheet)</text></g><g stroke-linecap="round"><g transform="translate(1305.5555555555557 936.9444444444446) rotate(0 -63.33333333333337 -0.5555555555555429)"><path d="M-0.43 0.11 C-21.48 -0.3, -105.83 -1.88, -126.74 -2.19 M1.55 -0.87 C-19.63 -1.19, -106.43 -1.24, -127.72 -1.22" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1305.5555555555557 936.9444444444446) rotate(0 -63.33333333333337 -0.5555555555555429)"><path d="M-104.23 -9.77 C-110.24 -7.71, -118.44 -3.97, -127.72 -1.22 M-104.23 -9.77 C-109.28 -8.07, -116.44 -5.49, -127.72 -1.22" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1305.5555555555557 936.9444444444446) rotate(0 -63.33333333333337 -0.5555555555555429)"><path d="M-104.23 7.33 C-110.28 4.87, -118.48 4.08, -127.72 -1.22 M-104.23 7.33 C-109.11 4.8, -116.27 3.14, -127.72 -1.22" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1203.3333333333335 930.2777777777778) rotate(0 55.729949951171875 25)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">construct</text><text x="0" y="42.62" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">MainWindow</text></g><g transform="translate(542.2222222222224 121.38888888888903) rotate(0 113.79843139648438 17.5)"><text x="0" y="24.668" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="28px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">EventHandler.cpp</text></g><g transform="translate(511.1111111111113 154.16666666666674) rotate(0 155.94985961914062 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onLevelNameUpdated(new_name)</text></g><g transform="translate(834.4444444444443 1134.1666666666667) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(560 378.61111111111126) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(835.5555555555554 1156.388888888889) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(835.5555555555554 1179.7222222222222) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#2f9e44" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(835.5555555555554 1199.7222222222222) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#e03131" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(508.88888888888914 182.5000000000001) rotate(0 164.08982849121094 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#2f9e44" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onLevelWriteRequested(file_path)</text></g><g transform="translate(564.4444444444448 209.16666666666686) rotate(0 102.21990203857422 37.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onTileEntered(index)</text><text x="0" y="42.62" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onTileExited(index)</text><text x="0" y="67.62" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onTileClicked(index)</text></g><g stroke-linecap="round" transform="translate(467.77777777777806 95.83333333333348) rotate(0 190 127.2222222222222)"><path d="M32 0 C99.31 1.79, 168.77 1.21, 348 0 M32 0 C134.98 1.12, 238.33 0.36, 348 0 M348 0 C368.49 1.03, 378.15 12.6, 380 32 M348 0 C367.35 0.61, 377.88 9.94, 380 32 M380 32 C380.1 84.74, 379.31 141.3, 380 222.44 M380 32 C379.97 96.79, 379.78 163.12, 380 222.44 M380 222.44 C379.4 245.48, 370.62 255.32, 348 254.44 M380 222.44 C380.23 242.57, 371.38 252.49, 348 254.44 M348 254.44 C272.94 254.7, 197.6 254.59, 32 254.44 M348 254.44 C245.83 254.63, 142.8 255.15, 32 254.44 M32 254.44 C10.8 252.83, -1.78 243.8, 0 222.44 M32 254.44 C12.95 253.63, 0.02 245.45, 0 222.44 M0 222.44 C-2.03 149.77, -3.34 73.88, 0 32 M0 222.44 C-0.58 178.42, 0.62 131.73, 0 32 M0 32 C-1.19 9.19, 11.9 -1.93, 32 0 M0 32 C0.07 9.73, 11.54 -1.03, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(544.4444444444446 281.38888888888897) rotate(0 118.32988739013672 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#e03131" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onNewTileIdSelected(id)</text></g><g transform="translate(572.2222222222222 305.83333333333354) rotate(0 83.0199203491211 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onZoomed(delta)</text></g><g transform="translate(186.66666666666674 321.38888888888897) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(1274.4444444444446 680.2777777777778) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#e03131" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(10 552.5000000000001) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(1306.6666666666665 499.16666666666674) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#2f9e44" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(786.6666666666665 381.38888888888897) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(1048.8888888888887 418.05555555555566) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(462.2222222222224 1178.0555555555557) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(962.2222222222224 342.50000000000017) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(532.2222222222224 55.83333333333326) rotate(0 133.99624633789062 20)"><text x="0" y="14.096" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">connects emitters and overwriters</text><text x="0" y="34.096000000000004" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">makes interaction possible</text></g><g transform="translate(20.000000000000114 191.38888888888897) rotate(0 224.7923583984375 40)"><text x="0" y="14.096" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Legende:</text><text x="0" y="34.096000000000004" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits] -> dispatches event handler event method</text><text x="0" y="54.096000000000004" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites] -> inherits from EventHandler and</text><text x="0" y="74.096" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic"> overwrites event method</text></g><g transform="translate(40.00000000000023 10) rotate(0 184.9500732421875 67.5)"><text x="0" y="31.716" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Formless </text><text x="0" y="76.71600000000001" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">System Architecture </text><text x="0" y="121.71600000000001" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Diagramm</text></g></svg> \ No newline at end of file diff --git a/documentation/system-architecture.svg b/documentation/system-architecture.svg new file mode 100644 index 0000000000000000000000000000000000000000..6b3df198b24eb070956a23fa09e7d7e7309f1e40 --- /dev/null +++ b/documentation/system-architecture.svg @@ -0,0 +1,3 @@ +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1688.7907986111109 1234.7222222222222" width="1688.7907986111109" height="1234.7222222222222"><!-- svg-source:excalidraw --><metadata></metadata><defs><style class="style-fonts"> + @font-face { font-family: Virgil; src: url(data:font/woff2;base64,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); } + @font-face { font-family: Excalifont; src: url(data:font/woff2;base64,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); }</style></defs><rect x="0" y="0" width="1688.7907986111109" height="1234.7222222222222" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(181.33333333333303 468.429292929293) rotate(0 496.5 313.5)"><path d="M32 0 C392.22 -1.22, 750.78 -1.96, 961 0 M32 0 C350.37 -2.95, 668.6 -2.73, 961 0 M961 0 C981.11 -0.8, 992.43 10.61, 993 32 M961 0 C982.35 -1.02, 994.77 10.24, 993 32 M993 32 C990.27 180.3, 990.44 329.77, 993 595 M993 32 C993.61 250.68, 993.46 469.57, 993 595 M993 595 C994.01 615.74, 983.64 626.88, 961 627 M993 595 C994.03 614.05, 981.9 627.65, 961 627 M961 627 C646.22 628.26, 332.93 627.51, 32 627 M961 627 C711.11 624.58, 460.9 624.42, 32 627 M32 627 C10.59 626.53, 0.77 616.89, 0 595 M32 627 C9.28 624.92, -0.3 617.43, 0 595 M0 595 C0.03 426.99, -0.11 258.91, 0 32 M0 595 C-1.21 479.68, -1.88 365.05, 0 32 M0 32 C1.86 10.09, 9.1 -1, 32 0 M0 32 C0.31 11.97, 10.76 0.02, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(519.9999999999998 397.3181818181819) rotate(0 98.72837829589844 35)"><text x="0" y="24.668" font-family="Virgil, Segoe UI Emoji" font-size="28px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">MainWindow.cpp</text><text x="0" y="59.668" font-family="Virgil, Segoe UI Emoji" font-size="28px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QMainWindow)</text></g><g stroke-linecap="round"><g transform="translate(180.33333333333303 529.4292929292931) rotate(0 497 -1)"><path d="M-0.62 0.95 C165.13 0.91, 827.6 -0.38, 993.57 -0.8 M1.25 0.4 C166.89 0.12, 826.77 -1.62, 992.4 -2.3" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(602.7777777777776 480.429292929293) rotate(0 55.43994903564453 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">TopBar.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QToolBar)</text></g><g stroke-linecap="round" transform="translate(210.33333333333303 482.429292929293) rotate(0 117 17)"><path d="M8.5 0 C82.95 -0.86, 159.8 -1.13, 225.5 0 M8.5 0 C71.09 -0.8, 134.25 -1.35, 225.5 0 M225.5 0 C232.33 0.33, 232.7 2.76, 234 8.5 M225.5 0 C233.15 -1.94, 232.78 3.56, 234 8.5 M234 8.5 C234.21 14.51, 234.75 16.2, 234 25.5 M234 8.5 C234.01 14.79, 233.84 21.05, 234 25.5 M234 25.5 C234.43 31.84, 231.54 35.7, 225.5 34 M234 25.5 C235.6 32.53, 230.89 33.21, 225.5 34 M225.5 34 C143.1 32.42, 65.1 32.35, 8.5 34 M225.5 34 C151.33 35.67, 76.52 36.47, 8.5 34 M8.5 34 C1.77 32.02, 1.93 31.63, 0 25.5 M8.5 34 C3.25 34.52, 1.32 30.23, 0 25.5 M0 25.5 C0.55 19.94, -1 13.59, 0 8.5 M0 25.5 C-0.13 21.51, -0.72 16.51, 0 8.5 M0 8.5 C1.94 3.8, 4.48 -0.8, 8.5 0 M0 8.5 C1.85 3.76, 3.37 -0.05, 8.5 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(217.33333333333303 491.429292929293) rotate(0 0.5 10)"><path d="M0.25 0 C0.42 0.01, 0.61 -0.02, 0.75 0 M0.25 0 C0.35 0, 0.45 0.01, 0.75 0 M0.75 0 C0.89 -0.04, 0.36 0.81, 1 0.25 M0.75 0 C1.55 0.25, 1.22 -0.63, 1 0.25 M1 0.25 C1 7.43, 1.16 12.83, 1 19.75 M1 0.25 C1.04 6.11, 1.06 12.31, 1 19.75 M1 19.75 C1.88 18.99, 0.8 19.82, 0.75 20 M1 19.75 C1.15 20.78, 1.32 19.55, 0.75 20 M0.75 20 C0.64 20.01, 0.47 20, 0.25 20 M0.75 20 C0.64 19.99, 0.54 20.01, 0.25 20 M0.25 20 C-0.51 19.6, 0.64 20.51, 0 19.75 M0.25 20 C0.6 21.07, 0.74 19.62, 0 19.75 M0 19.75 C-0.27 15.59, 0.89 10.28, 0 0.25 M0 19.75 C-0.15 15.03, 0.3 11.21, 0 0.25 M0 0.25 C0.96 -0.63, 0.37 0.04, 0.25 0 M0 0.25 C-1.04 -0.02, 0.34 1.02, 0.25 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(229.33333333333303 372.429292929293) rotate(0 -1 53.5)"><path d="M-0.81 -1.05 C-1.22 16.8, -1.36 89.29, -1.4 107.47 M0.96 1.01 C0.36 18.47, -1.65 88.06, -2.03 105.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(229.33333333333303 372.429292929293) rotate(0 -1 53.5)"><path d="M-9.97 81.88 C-8.59 92.11, -2.98 101.84, -2.03 105.59 M-9.97 81.88 C-6.47 90.15, -4.54 100.94, -2.03 105.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(229.33333333333303 372.429292929293) rotate(0 -1 53.5)"><path d="M7.13 82.33 C1.89 92.27, 0.88 101.83, -2.03 105.59 M7.13 82.33 C4.08 90.57, -0.53 101.19, -2.03 105.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(149.33333333333303 347.429292929293) rotate(0 87.659912109375 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">LevelNameEdit.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QLineEdit)</text></g><g stroke-linecap="round" transform="translate(1063.333333333333 486.429292929293) rotate(0 47.5 12)"><path d="M6 0 C34.28 0.89, 60.18 0.43, 89 0 M6 0 C25.62 0.6, 43.35 0.53, 89 0 M89 0 C94.69 0.9, 93.44 1.88, 95 6 M89 0 C92.78 0.44, 96.27 1.45, 95 6 M95 6 C93.88 9.47, 95.27 10.92, 95 18 M95 6 C95.28 9.11, 95.1 13.08, 95 18 M95 18 C94.21 21.66, 93.22 22.3, 89 24 M95 18 C96.62 23.93, 91.93 23.49, 89 24 M89 24 C56.56 26.15, 25.69 22.71, 6 24 M89 24 C58.42 24.22, 27.9 24.91, 6 24 M6 24 C1.19 22.28, 1.69 21.82, 0 18 M6 24 C2.56 23.95, 2.28 23.17, 0 18 M0 18 C-0.32 13.43, 1.18 8.69, 0 6 M0 18 C-0.43 13.94, 0.08 10.5, 0 6 M0 6 C-1.16 0.67, 0.99 1.82, 6 0 M0 6 C2.11 3.57, 0.77 -2.16, 6 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1085.333333333333 488.429292929293) rotate(0 23.97998046875 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Save</text></g><g stroke-linecap="round"><g transform="translate(1242.333333333333 488.429292929293) rotate(0 -40.5 2)"><path d="M0.21 0.91 C-13.54 1.66, -68.62 4.36, -81.99 4.83 M-1.14 0.34 C-14.57 0.73, -66.61 2.39, -79.79 3.15" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1242.333333333333 488.429292929293) rotate(0 -40.5 2)"><path d="M-56.69 -6.4 C-63.1 -4.72, -71.17 1.88, -79.79 3.15 M-56.69 -6.4 C-63.31 -3.12, -69.07 -0.07, -79.79 3.15" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1242.333333333333 488.429292929293) rotate(0 -40.5 2)"><path d="M-55.96 10.69 C-62.66 6.49, -70.99 7.21, -79.79 3.15 M-55.96 10.69 C-62.69 9.23, -68.65 7.53, -79.79 3.15" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1247.333333333333 477.429292929293) rotate(0 129.68988037109375 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">SaveButton.cpp (QButton)</text></g><g stroke-linecap="round" transform="translate(1005.333333333333 484.429292929293) rotate(0 13.5 17.5)"><path d="M6.75 0 C9.92 -0.2, 16.31 1.38, 20.25 0 M6.75 0 C11.62 -0.38, 16.32 -0.58, 20.25 0 M20.25 0 C26.35 -1.97, 28.5 1.66, 27 6.75 M20.25 0 C24.01 -1.02, 27.24 2.84, 27 6.75 M27 6.75 C28.85 14.28, 28.08 22.09, 27 28.25 M27 6.75 C26.92 14.38, 27.65 22.43, 27 28.25 M27 28.25 C26.26 31.42, 22.96 35.3, 20.25 35 M27 28.25 C29.18 33.03, 25.2 33.24, 20.25 35 M20.25 35 C15.67 35.81, 12.98 35.93, 6.75 35 M20.25 35 C16.03 34.72, 10.87 34.5, 6.75 35 M6.75 35 C2.9 34.03, -0.39 33.16, 0 28.25 M6.75 35 C0.73 33.52, -2.27 30.99, 0 28.25 M0 28.25 C1.12 23.57, 1.49 15.7, 0 6.75 M0 28.25 C0.53 21.91, 0.3 15.73, 0 6.75 M0 6.75 C0.21 2.02, 1.34 -0.13, 6.75 0 M0 6.75 C-0.3 1.59, 1.63 1.72, 6.75 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1012.333333333333 489.429292929293) rotate(0 6.5 12.5)"><text x="6.5" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">+</text></g><g stroke-linecap="round" transform="translate(912.333333333333 484.429292929293) rotate(0 40.5 17.5)"><path d="M8.75 0 C22.56 0.4, 37.31 -1.91, 72.25 0 M8.75 0 C23.05 -1.57, 38.12 0.11, 72.25 0 M72.25 0 C79.86 -0.3, 79.84 2.62, 81 8.75 M72.25 0 C78.7 -1.52, 83.27 4.93, 81 8.75 M81 8.75 C80.09 13.85, 80.67 21.49, 81 26.25 M81 8.75 C81.41 11.99, 80.31 16.06, 81 26.25 M81 26.25 C80.07 33.14, 79.58 34.9, 72.25 35 M81 26.25 C80.75 30.39, 78.08 34.73, 72.25 35 M72.25 35 C51.55 36.58, 32.98 33.29, 8.75 35 M72.25 35 C48.97 35.89, 24.15 35.79, 8.75 35 M8.75 35 C4.36 35.12, -0.4 31.08, 0 26.25 M8.75 35 C3.76 33.6, 1.1 34.33, 0 26.25 M0 26.25 C-1.35 23.1, 1.08 17.8, 0 8.75 M0 26.25 C0.84 21.29, 0.31 17.23, 0 8.75 M0 8.75 C-1.1 1.96, 3.44 -0.83, 8.75 0 M0 8.75 C-0.18 4.03, 2.99 -1.76, 8.75 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(926.8633397420244 489.429292929293) rotate(0 25.969993591308594 12.5)"><text x="25.969993591308594" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">100%</text></g><g stroke-linecap="round" transform="translate(872.333333333333 483.429292929293) rotate(0 14.5 17.5)"><path d="M7.25 0 C10.22 0.81, 16.13 0.62, 21.75 0 M7.25 0 C10.45 0.04, 13.83 -0.2, 21.75 0 M21.75 0 C28.53 -1.66, 30.01 3.94, 29 7.25 M21.75 0 C25.25 -2.12, 27.32 2.65, 29 7.25 M29 7.25 C27.46 15.36, 29.95 22.12, 29 27.75 M29 7.25 C29.49 11.1, 29 15.36, 29 27.75 M29 27.75 C30.06 31.65, 24.8 35.36, 21.75 35 M29 27.75 C30.26 30.8, 26.27 32.76, 21.75 35 M21.75 35 C15.99 33.78, 11.09 33.83, 7.25 35 M21.75 35 C15.58 34.65, 9.83 34.46, 7.25 35 M7.25 35 C3.59 36.96, -1.4 33.4, 0 27.75 M7.25 35 C4.66 34.8, 1.38 33.03, 0 27.75 M0 27.75 C-1.77 20.42, 0.62 11.76, 0 7.25 M0 27.75 C-0.43 21.36, -0.19 15.55, 0 7.25 M0 7.25 C0.64 1.25, 0.77 1.31, 7.25 0 M0 7.25 C1.24 3.09, 4.53 0.82, 7.25 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(882.723340352376 488.429292929293) rotate(0 4.109992980957031 12.5)"><text x="4.109992980957031" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">-</text></g><g stroke-linecap="round"><g transform="translate(1025.333333333333 418.429292929293) rotate(0 0.5 33.5)"><path d="M-0.62 -0.01 C-0.55 11.31, 0.02 56.24, 0.23 67.4 M1.26 -1.07 C1.73 9.93, 2.79 53.94, 2.54 65.49" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1025.333333333333 418.429292929293) rotate(0 0.5 33.5)"><path d="M-6.11 42.04 C-3.77 47.5, -0.6 54.94, 2.54 65.49 M-6.11 42.04 C-2.46 49.71, -1.44 56.21, 2.54 65.49" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1025.333333333333 418.429292929293) rotate(0 0.5 33.5)"><path d="M10.99 41.96 C8.45 47.36, 6.73 54.82, 2.54 65.49 M10.99 41.96 C9.59 49.67, 5.56 56.2, 2.54 65.49" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(948.333333333333 390.429292929293) rotate(0 -1.5 45)"><path d="M0.31 -0.19 C-0.53 14.85, -3.52 75.53, -3.92 90.75 M-0.99 -1.34 C-1.55 13.32, -1.46 73.9, -1.7 89.03" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(948.333333333333 390.429292929293) rotate(0 -1.5 45)"><path d="M-10.09 65.48 C-7.04 73.14, -2.17 85.36, -1.7 89.03 M-10.09 65.48 C-5.72 74.54, -2.43 83.73, -1.7 89.03" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(948.333333333333 390.429292929293) rotate(0 -1.5 45)"><path d="M7.01 65.59 C3.37 73.25, 1.54 85.43, -1.7 89.03 M7.01 65.59 C4.61 74.74, 1.14 83.9, -1.7 89.03" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(857.333333333333 427.429292929293) rotate(0 12.5 27)"><path d="M-0.25 -0.42 C3.93 8.8, 21.72 45.57, 25.74 54.77 M1.81 -1.68 C5.85 7.24, 21.12 43.96, 25.18 53.04" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(857.333333333333 427.429292929293) rotate(0 12.5 27)"><path d="M8.05 34.83 C13.31 42.52, 21.32 45.8, 25.18 53.04 M8.05 34.83 C13.65 41.12, 20.02 48.33, 25.18 53.04" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(857.333333333333 427.429292929293) rotate(0 12.5 27)"><path d="M23.77 28.08 C23.46 38.12, 25.93 43.77, 25.18 53.04 M23.77 28.08 C23.58 36.73, 24.22 46.39, 25.18 53.04" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(975.333333333333 397.429292929293) rotate(0 142.2398681640625 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">ZoomInButton.cpp (QButton)</text></g><g transform="translate(919.333333333333 368.429292929293) rotate(0 154.099853515625 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">ZoomInfoBUtton.cpp (QButton)</text></g><g transform="translate(739.333333333333 400.429292929293) rotate(0 98.07991027832031 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">ZoomOutBUtton.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic"> (QButton)</text></g><g stroke-linecap="round" transform="translate(908.333333333333 528.4292929292931) rotate(0 132.5 282.5)"><path d="M32 0 C107.68 -0.2, 181.93 -0.74, 233 0 M32 0 C84.58 -0.32, 139.55 0.62, 233 0 M233 0 C254.72 0.92, 264.11 9.95, 265 32 M233 0 C252.46 0, 264.98 12.59, 265 32 M265 32 C263.46 146.81, 263.79 261.73, 265 533 M265 32 C264.71 216.17, 264.44 399.83, 265 533 M265 533 C264.79 556.25, 253.75 564.54, 233 565 M265 533 C263.43 553.47, 254.63 565.31, 233 565 M233 565 C181.87 565.95, 131.39 567.04, 32 565 M233 565 C163.75 564.45, 93.71 565.49, 32 565 M32 565 C11.87 566.97, 1.46 553.94, 0 533 M32 565 C11.74 566.93, 0.6 554.32, 0 533 M0 533 C0.22 423.53, -0.28 314.91, 0 32 M0 533 C0.18 386.7, 0.13 240.44, 0 32 M0 32 C1.34 10.17, 11.54 1.36, 32 0 M0 32 C1.77 12.9, 12.25 -0.5, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(1223.333333333333 582.4292929292931) rotate(0 -23 -1.4999999999999858)"><path d="M-0.37 -0.23 C-8.08 -0.87, -38.83 -2.82, -46.38 -3.35 M0.44 -0.83 C-7.08 -1.44, -37.63 -2.46, -45.34 -2.83" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1223.333333333333 582.4292929292931) rotate(0 -23 -1.4999999999999858)"><path d="M-23.38 -9.83 C-29.77 -8.69, -35.99 -5.47, -45.34 -2.83 M-23.38 -9.83 C-28.55 -8.18, -32.93 -6.81, -45.34 -2.83" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1223.333333333333 582.4292929292931) rotate(0 -23 -1.4999999999999858)"><path d="M-24.02 5.92 C-30.3 2.77, -36.34 1.69, -45.34 -2.83 M-24.02 5.92 C-29.02 4.08, -33.27 1.95, -45.34 -2.83" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1225.333333333333 568.4292929292931) rotate(0 77.17992401123047 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">TileSelector.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QScrollArea)</text></g><g stroke-linecap="round" transform="translate(180.33333333333303 533.4292929292931) rotate(0 363 279.5)"><path d="M32 0 C215.92 1.83, 398.86 2.46, 694 0 M32 0 C180.91 0.52, 329.24 0.39, 694 0 M694 0 C714.71 1.49, 727.21 9.49, 726 32 M694 0 C713.4 -1.3, 728.05 11.18, 726 32 M726 32 C723.76 180.22, 724.95 328.51, 726 527 M726 32 C725.6 187.45, 725.35 343.49, 726 527 M726 527 C727.77 546.98, 713.79 560.64, 694 559 M726 527 C724.33 547.44, 716.28 559.42, 694 559 M694 559 C523.33 555.73, 351.72 556.14, 32 559 M694 559 C519.52 556.36, 345.02 556.09, 32 559 M32 559 C8.95 560.86, 1.46 549.67, 0 527 M32 559 C8.93 559.29, 2.04 546.92, 0 527 M0 527 C-1.75 411.71, -0.93 296.78, 0 32 M0 527 C-1.1 389.49, -1.21 251.32, 0 32 M0 32 C-0.11 9.35, 9.88 0.55, 32 0 M0 32 C0.53 12.21, 12.51 -0.56, 32 0" stroke="#e03131" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(512.333333333333 1130.429292929293) rotate(0 0 -16.5)"><path d="M-0.52 -0.22 C-0.41 -5.75, 0.34 -27.1, 0.47 -32.65 M0.22 -0.81 C0.31 -6.27, 0.31 -28.04, 0.28 -33.44" stroke="#e03131" stroke-width="1" fill="none"></path></g><g transform="translate(512.333333333333 1130.429292929293) rotate(0 0 -16.5)"><path d="M5.96 -17.94 C4.03 -21.41, 3.36 -25.88, 0.28 -33.44 M5.96 -17.94 C4.57 -20.88, 3.41 -24.45, 0.28 -33.44" stroke="#e03131" stroke-width="1" fill="none"></path></g><g transform="translate(512.333333333333 1130.429292929293) rotate(0 0 -16.5)"><path d="M-5.33 -17.92 C-4.24 -21.34, -1.87 -25.81, 0.28 -33.44 M-5.33 -17.92 C-4.39 -20.85, -3.22 -24.43, 0.28 -33.44" stroke="#e03131" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(454.33333333333303 1133.429292929293) rotate(0 74.98992156982422 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#e03131" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">LevelView.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#e03131" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QGraphicsView)</text></g><g stroke-linecap="round" transform="translate(315.33333333333303 578.4292929292931) rotate(0 241 228.5)"><path d="M32 0 C146.33 -1.42, 257.76 -0.74, 450 0 M32 0 C115.45 1.81, 200.12 1.28, 450 0 M450 0 C471.46 -0.74, 482.45 8.99, 482 32 M450 0 C471.62 -1.64, 480.4 12.52, 482 32 M482 32 C480.83 137.55, 482.65 241.93, 482 425 M482 32 C481.31 143.38, 481.08 253.68, 482 425 M482 425 C483.61 444.62, 472.21 457.89, 450 457 M482 425 C483.83 445.93, 470.94 456.08, 450 457 M450 457 C293.51 457.25, 135.32 457.52, 32 457 M450 457 C297.48 456.29, 143.84 456.54, 32 457 M32 457 C11.7 455.78, 1 448.29, 0 425 M32 457 C9.59 455.68, 2.19 444.24, 0 425 M0 425 C0.5 336.06, 0.98 247.97, 0 32 M0 425 C-0.88 281.81, -0.96 139.85, 0 32 M0 32 C1.23 12.56, 12.45 -0.4, 32 0 M0 32 C-1.03 11.67, 9 1.82, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(738.333333333333 1131.429292929293) rotate(0 -1 -48)"><path d="M-1.1 -1.19 C-1.57 -17.1, -1.85 -80.14, -2.02 -95.86 M0.52 0.8 C-0.18 -14.85, -2.4 -77.84, -2.98 -94.24" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(738.333333333333 1131.429292929293) rotate(0 -1 -48)"><path d="M6.4 -71.07 C1.06 -79.68, -0.15 -88.38, -2.98 -94.24 M6.4 -71.07 C3.17 -78.61, 0.8 -86.65, -2.98 -94.24" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(738.333333333333 1131.429292929293) rotate(0 -1 -48)"><path d="M-10.69 -70.46 C-9.77 -79.14, -4.72 -88.06, -2.98 -94.24 M-10.69 -70.46 C-8.09 -78.14, -4.63 -86.39, -2.98 -94.24" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(669.333333333333 1135.429292929293) rotate(0 82.77991485595703 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">LevelScene.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QGraphicsScene)</text></g><g stroke-linecap="round" transform="translate(320.33333333333303 588.4292929292931) rotate(0 21.5 18.5)"><path d="M9.25 0 C20.01 0.76, 26 0.03, 33.75 0 M9.25 0 C19.41 -0.24, 28.62 0.61, 33.75 0 M33.75 0 C40.46 1.11, 42.64 3.02, 43 9.25 M33.75 0 C37.82 1.09, 45.27 3.31, 43 9.25 M43 9.25 C43.43 14.75, 41.78 17.92, 43 27.75 M43 9.25 C42.23 14.12, 42.47 17.97, 43 27.75 M43 27.75 C42.57 33.29, 38.49 35.69, 33.75 37 M43 27.75 C42.31 36, 39.42 35.78, 33.75 37 M33.75 37 C27.37 37.9, 18.55 36.99, 9.25 37 M33.75 37 C27.4 37.24, 20.28 36.69, 9.25 37 M9.25 37 C3.16 35.98, -0.62 34.77, 0 27.75 M9.25 37 C4.45 38.28, 0.77 35.29, 0 27.75 M0 27.75 C-0.84 21.83, -1.11 18.45, 0 9.25 M0 27.75 C-0.9 22.6, -0.63 17.73, 0 9.25 M0 9.25 C1.28 2.3, 2.36 0.79, 9.25 0 M0 9.25 C-2.17 0.79, 1.37 -1.65, 9.25 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(366.33333333333303 590.4292929292931) rotate(0 19.5 17.5)"><path d="M8.75 0 C14.48 0.81, 22.09 1.13, 30.25 0 M8.75 0 C12.41 -0.36, 17.27 -0.4, 30.25 0 M30.25 0 C34.66 -1.31, 38.4 4.72, 39 8.75 M30.25 0 C35.58 -1.22, 38.69 2.57, 39 8.75 M39 8.75 C38.03 13.32, 38.56 17.7, 39 26.25 M39 8.75 C38.91 14.66, 39.09 20.93, 39 26.25 M39 26.25 C38.38 32.93, 37.27 36.11, 30.25 35 M39 26.25 C39.77 33.46, 35.99 33.98, 30.25 35 M30.25 35 C20.98 36.73, 13.64 35.46, 8.75 35 M30.25 35 C22.23 34.84, 16.11 34.56, 8.75 35 M8.75 35 C2.19 35.79, -1.89 30.09, 0 26.25 M8.75 35 C1.2 33.35, 2.23 34.3, 0 26.25 M0 26.25 C1.13 21.77, 0.87 18.25, 0 8.75 M0 26.25 C0.17 20.64, 0.41 15.47, 0 8.75 M0 8.75 C0.37 1, 0.95 -0.63, 8.75 0 M0 8.75 C-2.27 1.68, 4.22 0.85, 8.75 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(407.33333333333303 592.4292929292931) rotate(0 19 17.5)"><path d="M8.75 0 C12.21 -2.07, 17.95 -1.52, 29.25 0 M8.75 0 C16.34 -0.65, 25.62 -0.32, 29.25 0 M29.25 0 C35.97 -0.96, 38.17 2.3, 38 8.75 M29.25 0 C33.65 0.31, 37.44 3.84, 38 8.75 M38 8.75 C36.31 13.46, 36.3 18.34, 38 26.25 M38 8.75 C37.77 15.63, 37.75 20.41, 38 26.25 M38 26.25 C38.81 33.71, 34.44 36.29, 29.25 35 M38 26.25 C36.95 33.87, 35.7 34.47, 29.25 35 M29.25 35 C23.99 34.96, 19.58 35.62, 8.75 35 M29.25 35 C21.64 35.48, 13.28 35.95, 8.75 35 M8.75 35 C3.6 36.06, -0.03 32.15, 0 26.25 M8.75 35 C5.01 36.72, 1.66 32.54, 0 26.25 M0 26.25 C-0.4 20.69, -0.62 14.43, 0 8.75 M0 26.25 C0.97 20.51, -0.01 14.56, 0 8.75 M0 8.75 C-1.06 2.61, 1.97 -0.09, 8.75 0 M0 8.75 C0.27 2.26, 2.31 1.34, 8.75 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(443.33333333333303 593.4292929292931) rotate(0 23.5 17)"><path d="M8.5 0 C17.98 -0.54, 28.48 -0.79, 38.5 0 M8.5 0 C19.1 1.21, 28.52 0.08, 38.5 0 M38.5 0 C42.26 -1.06, 46.69 1.89, 47 8.5 M38.5 0 C44.07 0.27, 46.34 2.23, 47 8.5 M47 8.5 C46.21 15.46, 47.84 21.78, 47 25.5 M47 8.5 C47.86 14.93, 46.52 21.43, 47 25.5 M47 25.5 C45.53 32.33, 42.39 35.3, 38.5 34 M47 25.5 C46.76 33.3, 44.05 32.47, 38.5 34 M38.5 34 C26.79 33.78, 16.8 33.96, 8.5 34 M38.5 34 C27.54 33.62, 17.45 34.1, 8.5 34 M8.5 34 C2.5 34.19, -1.45 32.42, 0 25.5 M8.5 34 C0.87 34.71, -0.54 31.19, 0 25.5 M0 25.5 C-1.11 20.88, -0.96 15.77, 0 8.5 M0 25.5 C0.22 22.14, 0.65 18.26, 0 8.5 M0 8.5 C1.52 1.2, 3.35 -0.48, 8.5 0 M0 8.5 C0.5 1.77, 2.59 1.99, 8.5 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(90.33333333333303 598.4292929292931) rotate(0 114.5 3.5)"><path d="M-0.45 0.03 C37.84 1.39, 190.79 5.94, 229.13 7.13 M1.52 -1.01 C39.7 0.66, 190.32 7.14, 228.25 8.74" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(90.33333333333303 598.4292929292931) rotate(0 114.5 3.5)"><path d="M204.41 16.28 C210.51 14.68, 216.69 13.51, 228.25 8.74 M204.41 16.28 C211.84 13.8, 219.78 11.53, 228.25 8.74" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(90.33333333333303 598.4292929292931) rotate(0 114.5 3.5)"><path d="M205.14 -0.8 C210.91 1.89, 216.9 5.01, 228.25 8.74 M205.14 -0.8 C212.18 2.06, 219.89 5.13, 228.25 8.74" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(10.33333333333303 578.4292929292931) rotate(0 111.31987762451172 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Tile.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(QGraphicsPixmapItem)</text></g><g stroke-linecap="round" transform="translate(922.4242424242425 541.7929292929294) rotate(0 39.545454545454504 38.18181818181817)"><path d="M19.09 0 C31.99 0.05, 47.15 -0.53, 60 0 M19.09 0 C29.75 0.09, 39.48 -0.85, 60 0 M60 0 C70.78 -0.88, 78.34 7.86, 79.09 19.09 M60 0 C74.4 -2.27, 77.32 8.05, 79.09 19.09 M79.09 19.09 C80.58 28.26, 77.27 36.91, 79.09 57.27 M79.09 19.09 C79.94 28.61, 79.51 37.47, 79.09 57.27 M79.09 57.27 C79.29 69.54, 73.18 75.68, 60 76.36 M79.09 57.27 C77.93 70.21, 73.15 75.2, 60 76.36 M60 76.36 C48.22 76.64, 36.6 74.93, 19.09 76.36 M60 76.36 C45.42 76.38, 28.85 75.34, 19.09 76.36 M19.09 76.36 C5.78 75.7, 1.48 71.62, 0 57.27 M19.09 76.36 C7.67 76.59, 1.84 68.94, 0 57.27 M0 57.27 C1.15 50.14, 0.65 40.5, 0 19.09 M0 57.27 C0.89 49.78, -0.19 41.24, 0 19.09 M0 19.09 C1.96 5.63, 7.56 -1.36, 19.09 0 M0 19.09 C0.71 8.1, 6.47 0.09, 19.09 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(1014.2424242424242 544.5202020202021) rotate(0 44.09090909090912 39.09090909090909)"><path d="M19.55 0 C31.27 1.47, 41.22 -0.04, 68.64 0 M19.55 0 C30.45 0.13, 39.41 -0.6, 68.64 0 M68.64 0 C83.62 -0.73, 89.38 5.16, 88.18 19.55 M68.64 0 C82.38 1.74, 88.28 6.61, 88.18 19.55 M88.18 19.55 C88.44 29.55, 88.97 39.15, 88.18 58.64 M88.18 19.55 C88.86 30.86, 87.55 41.71, 88.18 58.64 M88.18 58.64 C86.87 73.14, 81.14 78.81, 68.64 78.18 M88.18 58.64 C90.11 71.8, 83.64 77.52, 68.64 78.18 M68.64 78.18 C50.29 76.91, 30.26 79.58, 19.55 78.18 M68.64 78.18 C58.95 78.16, 48.2 78.52, 19.55 78.18 M19.55 78.18 C6.42 77.91, -1.93 73.64, 0 58.64 M19.55 78.18 C5.16 76.73, 1.25 73.78, 0 58.64 M0 58.64 C-0.15 44.9, -1.77 29.35, 0 19.55 M0 58.64 C-0.53 47.68, -0.01 39.17, 0 19.55 M0 19.55 C0.14 6.1, 5.94 -0.56, 19.55 0 M0 19.55 C1.96 5.38, 6.93 1.77, 19.55 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(931.5151515151517 629.0656565656567) rotate(0 37.727272727272634 46.81818181818181)"><path d="M18.86 0 C28.49 -1.64, 41.56 1.46, 56.59 0 M18.86 0 C32.64 0.5, 45.79 -0.05, 56.59 0 M56.59 0 C68.38 1.18, 73.81 5.68, 75.45 18.86 M56.59 0 C67.39 0.5, 77.53 6.45, 75.45 18.86 M75.45 18.86 C77 33.49, 75.66 51.63, 75.45 74.77 M75.45 18.86 C75.63 37.14, 76.59 53.14, 75.45 74.77 M75.45 74.77 C75.74 89.12, 67.65 92, 56.59 93.64 M75.45 74.77 C74.97 87.93, 70.99 94.76, 56.59 93.64 M56.59 93.64 C46.03 93.4, 29.78 92.11, 18.86 93.64 M56.59 93.64 C43.36 94.39, 30.19 93.63, 18.86 93.64 M18.86 93.64 C4.94 93.41, 1.49 86.93, 0 74.77 M18.86 93.64 C7.83 93.32, -1.17 87.17, 0 74.77 M0 74.77 C2.1 57.55, 0.34 38.86, 0 18.86 M0 74.77 C-1.16 57.05, 0.43 41.23, 0 18.86 M0 18.86 C1.7 5.85, 5.21 0.68, 18.86 0 M0 18.86 C-1.03 6.63, 4.97 1, 18.86 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(1023.3333333333335 634.5202020202021) rotate(0 54.545454545454504 50)"><path d="M25 0 C38.24 -0.34, 52.52 -0.4, 84.09 0 M25 0 C43.22 -0.14, 60.61 -0.07, 84.09 0 M84.09 0 C99.74 -0.15, 109.55 6.65, 109.09 25 M84.09 0 C101.51 1.87, 110.05 8.18, 109.09 25 M109.09 25 C111.11 39.03, 107.98 57.73, 109.09 75 M109.09 25 C108.98 44.18, 109.07 62.82, 109.09 75 M109.09 75 C107.94 92.54, 102.02 101.39, 84.09 100 M109.09 75 C110.7 92.61, 103 99.39, 84.09 100 M84.09 100 C71.63 101.14, 57.62 98.14, 25 100 M84.09 100 C70.64 99.85, 58.84 100.65, 25 100 M25 100 C6.4 99.65, 1.6 93.59, 0 75 M25 100 C6.74 101.89, -2.03 93.21, 0 75 M0 75 C-0.09 58.47, 0.35 46.96, 0 25 M0 75 C-0.44 56.37, 0.39 39.04, 0 25 M0 25 C1.64 6.41, 7.17 1.87, 25 0 M0 25 C-0.39 7.74, 6.9 1.81, 25 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(1222.4242424242425 672.7020202020203) rotate(0 -43.18181818181813 0.45454545454543904)"><path d="M-0.83 0.99 C-15.56 1.08, -73.16 1.91, -87.42 1.72 M0.94 0.46 C-13.52 0.72, -70.58 0, -85.27 0.02" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1222.4242424242425 672.7020202020203) rotate(0 -43.18181818181813 0.45454545454543904)"><path d="M-61.72 -8.39 C-68.95 -7.12, -78.2 -2.25, -85.27 0.02 M-61.72 -8.39 C-67.58 -6.74, -70.89 -4.31, -85.27 0.02" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1222.4242424242425 672.7020202020203) rotate(0 -43.18181818181813 0.45454545454543904)"><path d="M-61.83 8.71 C-69.18 3.48, -78.39 1.85, -85.27 0.02 M-61.83 8.71 C-67.58 6.77, -70.87 5.62, -85.27 0.02" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1226.9696969696972 658.1565656565658) rotate(0 125.56987762451172 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">TileButton.cpp (QButton)</text></g><g transform="translate(1318.8888888888887 918.6111111111111) rotate(0 53.07018280029297 17.5)"><text x="0" y="24.668" font-family="Virgil, Segoe UI Emoji" font-size="28px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">main.cpp</text></g><g transform="translate(1284.4444444444448 785.8333333333335) rotate(0 139.84982299804688 25)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">SpriteProvider.cpp</text><text x="0" y="42.62" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">(stores all QPixmap Sprites)</text></g><g stroke-linecap="round"><g transform="translate(1268.888888888889 792.5000000000001) rotate(0 -78.8888888888888 -39.44444444444446)"><path d="M-0.72 1.11 C-27.09 -11.74, -131.06 -65.01, -157.21 -78.23 M1.11 0.65 C-25.46 -12.45, -131.29 -66.53, -157.86 -80.01" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1268.888888888889 792.5000000000001) rotate(0 -78.8888888888888 -39.44444444444446)"><path d="M-133.05 -76.97 C-140.74 -77.35, -151.08 -79.2, -157.86 -80.01 M-133.05 -76.97 C-142.62 -78.36, -149.77 -80.05, -157.86 -80.01" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1268.888888888889 792.5000000000001) rotate(0 -78.8888888888888 -39.44444444444446)"><path d="M-140.81 -61.73 C-145.36 -67.94, -152.71 -75.68, -157.86 -80.01 M-140.81 -61.73 C-147.57 -68.47, -151.97 -75.57, -157.86 -80.01" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1183.3333333333335 726.9444444444446) rotate(0 112.28987121582031 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">QPixmap get_sprite(id)</text></g><g transform="translate(933.3333333333333 818.0555555555557) rotate(0 112.28987121582031 12.5)"><text x="0" y="17.619999999999997" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">QPixmap get_sprite(id)</text></g><g stroke-linecap="round"><g transform="translate(1272.2222222222224 836.784364824992) rotate(0 -393.33333333333337 -97.142182412496)"><path d="M0.45 0.38 C-130.53 -32.13, -655.33 -161.77, -786.53 -194.17 M-0.77 -0.46 C-131.85 -32.84, -656.28 -160.56, -787.41 -192.57" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1272.2222222222224 836.784364824992) rotate(0 -393.33333333333337 -97.142182412496)"><path d="M-762.56 -195.31 C-768.09 -194.31, -774.72 -193.98, -787.41 -192.57 M-762.56 -195.31 C-769.74 -193.76, -777.78 -192.73, -787.41 -192.57" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1272.2222222222224 836.784364824992) rotate(0 -393.33333333333337 -97.142182412496)"><path d="M-766.62 -178.69 C-771.26 -181.85, -776.88 -185.66, -787.41 -192.57 M-766.62 -178.69 C-772.53 -182.55, -779.25 -186.94, -787.41 -192.57" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(1034.4444444444446 784.7222222222223) rotate(0 1.6666666666667425 15.555555555555543)"><path d="M0.18 0.32 C0.72 5.52, 2.69 26.35, 3.2 31.54 M-0.38 0.01 C0.08 5.03, 2.16 25.7, 2.7 30.79" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(1351.1111111111113 908.0555555555557) rotate(0 10.201238247079914 -28.333333333333258)"><path d="M-0.46 0.92 C2.78 -8.25, 16.75 -46.17, 20.2 -55.79 M1.5 0.36 C4.49 -9.08, 16.16 -48.13, 19.14 -57.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1351.1111111111113 908.0555555555557) rotate(0 10.201238247079914 -28.333333333333258)"><path d="M20.4 -32.48 C21.63 -41.95, 21.1 -51, 19.14 -57.45 M20.4 -32.48 C20.49 -39.78, 19.35 -46.28, 19.14 -57.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1351.1111111111113 908.0555555555557) rotate(0 10.201238247079914 -28.333333333333258)"><path d="M4.06 -37.51 C11.24 -45.08, 16.68 -52.29, 19.14 -57.45 M4.06 -37.51 C8.96 -43.4, 12.59 -48.42, 19.14 -57.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1361.1111111111109 878.1000000000004) rotate(0 158.83984375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">initialize (load hdf5 spritesheet)</text></g><g stroke-linecap="round"><g transform="translate(1305.5555555555557 936.9444444444446) rotate(0 -63.33333333333337 -0.5555555555555429)"><path d="M-0.43 0.11 C-21.48 -0.3, -105.83 -1.88, -126.74 -2.19 M1.55 -0.87 C-19.63 -1.19, -106.43 -1.24, -127.72 -1.22" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1305.5555555555557 936.9444444444446) rotate(0 -63.33333333333337 -0.5555555555555429)"><path d="M-104.23 -9.77 C-110.24 -7.71, -118.44 -3.97, -127.72 -1.22 M-104.23 -9.77 C-109.28 -8.07, -116.44 -5.49, -127.72 -1.22" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(1305.5555555555557 936.9444444444446) rotate(0 -63.33333333333337 -0.5555555555555429)"><path d="M-104.23 7.33 C-110.28 4.87, -118.48 4.08, -127.72 -1.22 M-104.23 7.33 C-109.11 4.8, -116.27 3.14, -127.72 -1.22" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(1203.3333333333335 930.2777777777778) rotate(0 55.729949951171875 25)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">construct</text><text x="0" y="42.62" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">MainWindow</text></g><g transform="translate(542.2222222222224 121.38888888888903) rotate(0 113.79843139648438 17.5)"><text x="0" y="24.668" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="28px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">EventHandler.cpp</text></g><g transform="translate(511.1111111111113 154.16666666666674) rotate(0 155.94985961914062 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onLevelNameUpdated(new_name)</text></g><g transform="translate(834.4444444444443 1134.1666666666667) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(560 378.61111111111126) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(835.5555555555554 1156.388888888889) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(835.5555555555554 1179.7222222222222) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#2f9e44" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(835.5555555555554 1199.7222222222222) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#e03131" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(546.8888888888891 185.5000000000001) rotate(0 120.02986907958984 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#2f9e44" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onLevelWriteRequested()</text></g><g transform="translate(564.4444444444448 209.16666666666686) rotate(0 102.21990203857422 37.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onTileEntered(index)</text><text x="0" y="42.62" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onTileExited(index)</text><text x="0" y="67.62" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onTileClicked(index)</text></g><g stroke-linecap="round" transform="translate(467.77777777777806 95.83333333333348) rotate(0 190 127.2222222222222)"><path d="M32 0 C99.31 1.79, 168.77 1.21, 348 0 M32 0 C134.98 1.12, 238.33 0.36, 348 0 M348 0 C368.49 1.03, 378.15 12.6, 380 32 M348 0 C367.35 0.61, 377.88 9.94, 380 32 M380 32 C380.1 84.74, 379.31 141.3, 380 222.44 M380 32 C379.97 96.79, 379.78 163.12, 380 222.44 M380 222.44 C379.4 245.48, 370.62 255.32, 348 254.44 M380 222.44 C380.23 242.57, 371.38 252.49, 348 254.44 M348 254.44 C272.94 254.7, 197.6 254.59, 32 254.44 M348 254.44 C245.83 254.63, 142.8 255.15, 32 254.44 M32 254.44 C10.8 252.83, -1.78 243.8, 0 222.44 M32 254.44 C12.95 253.63, 0.02 245.45, 0 222.44 M0 222.44 C-2.03 149.77, -3.34 73.88, 0 32 M0 222.44 C-0.58 178.42, 0.62 131.73, 0 32 M0 32 C-1.19 9.19, 11.9 -1.93, 32 0 M0 32 C0.07 9.73, 11.54 -1.03, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(544.4444444444446 281.38888888888897) rotate(0 118.32988739013672 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#e03131" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onNewTileIdSelected(id)</text></g><g transform="translate(572.2222222222222 305.83333333333354) rotate(0 83.0199203491211 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onZoomed(delta)</text></g><g transform="translate(186.66666666666674 321.38888888888897) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(1274.4444444444446 680.2777777777778) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#e03131" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(10 552.5000000000001) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(1306.6666666666665 499.16666666666674) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#2f9e44" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(786.6666666666665 381.38888888888897) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(1048.8888888888887 418.05555555555566) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(462.2222222222224 1178.0555555555557) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(962.2222222222224 342.50000000000017) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g><g transform="translate(532.2222222222224 55.83333333333326) rotate(0 133.99624633789062 20)"><text x="0" y="14.096" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">connects emitters and overwriters</text><text x="0" y="34.096000000000004" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">makes interaction possible</text></g><g transform="translate(20.000000000000114 191.38888888888897) rotate(0 224.7923583984375 40)"><text x="0" y="14.096" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Legende:</text><text x="0" y="34.096000000000004" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits] -> dispatches event handler event method</text><text x="0" y="54.096000000000004" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites] -> inherits from EventHandler and</text><text x="0" y="74.096" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic"> overwrites event method</text></g><g transform="translate(40.00000000000023 10) rotate(0 184.9500732421875 67.5)"><text x="0" y="31.716" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Formless </text><text x="0" y="76.71600000000001" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">System Architecture </text><text x="0" y="121.71600000000001" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Diagramm</text></g><g stroke-linecap="round" transform="translate(804.1199645996094 484.888888888889) rotate(0 15 16.5)"><path d="M7.5 0 C13.59 -0.73, 19.01 -0.13, 22.5 0 M7.5 0 C12.35 -0.82, 18.94 0.2, 22.5 0 M22.5 0 C27.45 -1.05, 29.87 1.93, 30 7.5 M22.5 0 C25.2 -0.19, 29.33 4.52, 30 7.5 M30 7.5 C29.75 11.42, 31.77 18.93, 30 25.5 M30 7.5 C29.67 13.07, 29.45 18.9, 30 25.5 M30 25.5 C30.01 31.74, 28.25 32.79, 22.5 33 M30 25.5 C32.24 30.97, 25.76 31.57, 22.5 33 M22.5 33 C16.01 32.35, 11.01 33.16, 7.5 33 M22.5 33 C17.4 32.88, 13.78 32.34, 7.5 33 M7.5 33 C3.16 32.07, 1.38 32.33, 0 25.5 M7.5 33 C2.97 32.81, -0.97 28.24, 0 25.5 M0 25.5 C-0.1 18.53, 1.65 10.85, 0 7.5 M0 25.5 C-0.34 20.98, -0.33 16.16, 0 7.5 M0 7.5 C-1.57 1.02, 1.65 -0.36, 7.5 0 M0 7.5 C-0.15 2.43, 4.01 1.09, 7.5 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(810.1199645996094 505.888888888889) rotate(0 5 5.5)"><path d="M0.25 -0.27 C2.07 1.62, 8.82 8.93, 10.39 10.82 M-0.28 0.77 C1.54 2.79, 8.58 9.61, 10.17 11.42" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(820.1199645996094 515.888888888889) rotate(0 4 -11)"><path d="M0.29 0.26 C1.57 -3.43, 6.84 -18.05, 8.16 -21.79 M-0.23 -0.09 C0.95 -3.97, 6.44 -19.05, 7.81 -22.65" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(902.1199645996094 325.888888888889) rotate(0 -31 83)"><path d="M0.81 -1.02 C-9.51 26.85, -51.6 139.17, -62 166.93 M-0.23 1.06 C-10.7 28.63, -52.63 137.7, -62.95 165.3" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(902.1199645996094 325.888888888889) rotate(0 -31 83)"><path d="M-62.6 140.31 C-64.45 147.65, -61.31 155.63, -62.95 165.3 M-62.6 140.31 C-62.02 147.05, -62.71 154.43, -62.95 165.3" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(902.1199645996094 325.888888888889) rotate(0 -31 83)"><path d="M-46.61 146.38 C-53.15 152.05, -54.7 158.24, -62.95 165.3 M-46.61 146.38 C-50.31 151.37, -55.24 157.14, -62.95 165.3" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(901.1199645996094 291.888888888889) rotate(0 210.4097900390625 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">AdvancedPlacementSwitch.cpp (QCheckBox)</text></g><g transform="translate(515.1199645996094 326.888888888889) rotate(0 151.55984497070312 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#9c36b5" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">onAdvancedPlacementToggled()</text></g><g transform="translate(1021.5601196289062 273.388888888889) rotate(0 35.119964599609375 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#9c36b5" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[emits]</text></g><g transform="translate(833 1113.3888888888891) rotate(0 60.47992706298828 12.5)"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#9c36b5" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">[overwrites]</text></g></svg> \ No newline at end of file diff --git a/level-spezifikation.md b/level-spezifikation.md deleted file mode 100644 index 1f087e54abc5225b023b263701732589063a3788..0000000000000000000000000000000000000000 --- a/level-spezifikation.md +++ /dev/null @@ -1,36 +0,0 @@ -# Levelspezifikation - -Das Level wird ueber 2 Datasets in der HDF5-Datei repraesentiert. -Ein Dataset in XML Format, das die Level Metadaten (Breite, Hoehe, Dataset ID Tiles Array) angibt. -Ein weiters Dataset, das letztlich ein array<uint8_t> ist, welches die Levelmap ueber ein Array von Tile IDs definiert. - -## 1. XML Dataset mit Level Metadaten -```xml -<?xml version="1.0" encoding="ASCII"?> -<level> - <width>20</width> <!-- Breite des Levels --> - <height>20</height> <!-- Hoehe des Levels --> - <name>Geiles Level<name><!-- Name des Levels --> -</level> -``` - -## 2. Tiles Array -Das Tiles Array wird als array<uint8_t> in einem Dataset in der HDF5-Datei gespeichert. -Die Laenge des Arrays ist Breite X Hoehe (kann man aus XML Leveldefinition entnehmen). -Aus den einzelnen Werte in dem Array lassen sich die IDs der entsprechenden Gebaudes/Terrains ableiten. -0 - 29 sind Terrain IDs => Zuordnung siehe entsprechendes Enum in Tile.hpp -30-49 sind undefiniert -50-79 sind Gebaeude IDs: -50 => Faction ID 0, Gebaeude ID 0 -51 => Faction ID 0, Gebaeude ID 1 -... -55 => Faction ID 1, Gebaeude ID 0 -56 => Faction ID 1, Gebaeude ID 1 -57 => Faction ID 1, Gebaeude ID 2 -... -Allgemein: -Sei t ein Wert im Tiles Array, dann gillt -falls t < 30: Terrain ID = t -falls t >= 50: Faction ID = (t - 50) / 5 Gebaeude ID = (t - 50) % 5 -t wird ermittelt mit entweder t = Terrain ID fuer Terrains -oder t = 50 + 5*Faction Id + Gebaeude ID fuer Gebaeude \ No newline at end of file diff --git a/res/level.h5 b/res/level.h5 index aa5ba5b60aae9dc4927a4e818e5bb480796ee5a0..121e122f46d9b6010fe278739e02a9f570b29214 100644 Binary files a/res/level.h5 and b/res/level.h5 differ diff --git a/res/map_split_island.h5 b/res/map_split_island.h5 index cfaaa1b32769273462ff045ff0eb1834c2309b2f..6ea30413cd50fa6f858d35eb817f5b96ec27b503 100644 Binary files a/res/map_split_island.h5 and b/res/map_split_island.h5 differ diff --git a/src/editor/AdvancedPlacementSwitch.cpp b/src/editor/AdvancedPlacementSwitch.cpp new file mode 100644 index 0000000000000000000000000000000000000000..080c9aea78bf9a6371f6f24960f2bc4f99e60905 --- /dev/null +++ b/src/editor/AdvancedPlacementSwitch.cpp @@ -0,0 +1,39 @@ +/** + * AdvancedPlacementSwitch.cpp + * + * @date 06.02.2025 + * @author Nils Jonathan Friedrich Eckardt implementation + * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) small changes + */ + +#include "AdvancedPlacementSwitch.hpp" +#include "EventHandler.hpp" +#include <QEvent> +#include <QMessageBox> + +#include <iostream> + +namespace editor +{ + +AdvancedPlacementSwitch::AdvancedPlacementSwitch( + const QString text, const bool doesPlacement, QWidget* parent) + : QCheckBox(text, parent), isPlacement(doesPlacement) +{ + setChecked(false); +} + +void AdvancedPlacementSwitch::mousePressEvent(QMouseEvent* event) +{ + QCheckBox::mousePressEvent(event); + if (isPlacement) + { + EventHandler::send([](EventHandler* e) { e->onAdvancedPlacementToggled(); }); + } + else + { + EventHandler::send([](EventHandler* e) { e->onSymmetryToggled(); }); + } +} + +} // namespace editor \ No newline at end of file diff --git a/src/editor/AdvancedPlacementSwitch.hpp b/src/editor/AdvancedPlacementSwitch.hpp new file mode 100644 index 0000000000000000000000000000000000000000..63409278e0c7619059240b11d0bdc1cb0d7fce7a --- /dev/null +++ b/src/editor/AdvancedPlacementSwitch.hpp @@ -0,0 +1,48 @@ +/** + * AdvancedPlacementSwitch.hpp + * + * @date 06.02.2025 + * @author Nils Jonathan Friedrich Eckardt implementation + * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) small changes, doc comments + */ + +#pragma once + +#include <QCheckBox> +#include <QEvent> +#include <QString> + +namespace editor +{ + +/** + * The AdvancedPlacementSwitch is displayed in the TopBar and allows the user to switch between + * regular tile placement and advanced tile placement. + */ +class AdvancedPlacementSwitch : public QCheckBox +{ + public: + /** + * Creates an AdvancedPlacementSwitch as QCheckBox using the provided text and parent. + * @param text The description to display next to the CheckBox. + * @param doesPlacement Which of the 2 checkboxes this one is: symetry or placement + * @param parent The parent for this widget. + */ + AdvancedPlacementSwitch(const QString text, bool doesPlacement, QWidget* parent = nullptr); + + protected: + /** + * Receives a QMouseEvent when the QCheckBox is pressed and subsequently emits + * the onAdvancedPlacementToggled event method. + * @param event The QMouseEvent for the CheckBox Pressing. + */ + void mousePressEvent(QMouseEvent* event) override; + + private: + /** + * Set at creation, controlls if the switch is used for Placement or Symmetry + */ + bool isPlacement; +}; + +} // namespace editor \ No newline at end of file diff --git a/src/editor/AutomateButton.cpp b/src/editor/AutomateButton.cpp deleted file mode 100644 index bf549696a5d34962d5d3f877040f6652c503949a..0000000000000000000000000000000000000000 --- a/src/editor/AutomateButton.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/** - * TileSelector.cpp - * - * @date 06.02.2025 - * @author Nils Jonathan Friedrich Eckardt implementation - */ - -#include "AutomateButton.hpp" -#include "EventHandler.hpp" -#include <QEvent> -#include <QMessageBox> - -namespace editor -{ - -AutomateButton::AutomateButton(const QString text, QWidget* parent) : QCheckBox(text, parent) -{ - setChecked(false); -} - -void AutomateButton::mousePressEvent(QMouseEvent* event) -{ - QCheckBox::mousePressEvent(event); - EventHandler::send([](EventHandler* e) { e->onCheckBoxToggled(); }); -} - -} // namespace editor \ No newline at end of file diff --git a/src/editor/AutomateButton.hpp b/src/editor/AutomateButton.hpp deleted file mode 100644 index 1163d0cb44f286db3c23f5b83ddea20863bd8d42..0000000000000000000000000000000000000000 --- a/src/editor/AutomateButton.hpp +++ /dev/null @@ -1,24 +0,0 @@ -/** -* TileSelector.hpp -* -* @date 06.02.2025 -* @author Nils Jonathan Friedrich Eckardt implementation -*/ - -#pragma once - -#include <QCheckBox> -#include <QString> -#include <QEvent> - -namespace editor -{ - -class AutomateButton : public QCheckBox { -public: - AutomateButton(const QString text, QWidget *parent = nullptr); -protected: - void mousePressEvent(QMouseEvent *event) override; -}; - -} // namespace editor \ No newline at end of file diff --git a/src/editor/EventHandler.cpp b/src/editor/EventHandler.cpp index ca80bada0e24da9c8a8ab666f65b034b7179ca04..0f77e6c79f16c25804b84f5c64879118ffc774b1 100644 --- a/src/editor/EventHandler.cpp +++ b/src/editor/EventHandler.cpp @@ -1,9 +1,9 @@ /** -* EventHandler.cpp -* -* @date 29.01.2025 -* @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) -*/ + * EventHandler.cpp + * + * @date 29.01.2025 + * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) + */ #include "EventHandler.hpp" diff --git a/src/editor/EventHandler.hpp b/src/editor/EventHandler.hpp index 91a3357116e1ec6179b18ffac675a13dffbcda2a..a95b39e760fa331f4c1a504716a18022ab7b4246 100644 --- a/src/editor/EventHandler.hpp +++ b/src/editor/EventHandler.hpp @@ -1,9 +1,9 @@ /** - * EventHandler.cpp + * EventHandler.hpp * * @date 29.01.2025 * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) - * @author Nils Jonathan Friedrich Eckardt onCheckBoxToggle added + * @author Nils Jonathan Friedrich Eckardt onAdvancedPlaement, onSymmetryToggled added */ #pragma once @@ -18,9 +18,6 @@ namespace editor { -// forward declaration of Tile -class Tile; - /** * EventHandler is the backbone in the architecture that makes interaction possible. * @@ -66,44 +63,48 @@ class EventHandler /** * Overwrite this event method to handle the dispatch of a request to write * the level to disk by the save button. - * @param file_path The path to the file into which the level should be written. */ - virtual void onLevelWriteRequested(QString /*filePath*/){}; + virtual void onLevelWriteRequested(){}; /** * Overwrite this event method to handle the dispatch of a new tile index due * to the mouse entering its boundaries. * @param index The index of the entered tile. */ - virtual void onTileEntered(int /*index*/) {}; + virtual void onTileEntered(int /*index*/){}; /** * Overwrite this event method to handle the dispatch of a new tile index due * to the mouse leaving its boundaries. * @param index The index of the exited tile. */ - virtual void onTileExited(int /*index*/) {}; + virtual void onTileExited(int /*index*/){}; /** * Overwrite this event method to handle the dispatch of a new tile index due * to the user clicking it. * @param index The index of the clicked tile. */ - virtual void onTileClicked(int /*index*/) {}; + virtual void onTileClicked(int /*index*/){}; /** * Overwrite this event method to handle the dispatch of a new tile_id due to * the user selecting a different tile type in the TileSelector on the right. * @param tile_id Id of the tile to use when an existing tile is clicked on the levelmap. */ - virtual void onNewTileIdSelected(uint8_t /*tileId*/) {}; + virtual void onNewTileIdSelected(uint8_t /*tileId*/){}; /** * Overwrite this event method to handle the change of tile placement method due - * to the user selecting automatic tile placement assisstance. - * @param isToggled Boolean that enables tile placing assisstment. + * to the user toggling advanced palacement on or off. + */ + virtual void onAdvancedPlacementToggled(){}; + + /** + * Overwrite this event method to handle the change of symmetric tile placement + * due to the user toggling map symmetry on or off. */ - virtual void onCheckBoxToggled() {}; + virtual void onSymmetryToggled(){}; /** * Overwrite this event method to handle the dispatch of a new delta due to diff --git a/src/editor/LevelNameEdit.cpp b/src/editor/LevelNameEdit.cpp index a1611a4c198d7524105634cd6ec4f336444da513..25571a7c1f759fa23242d2be1f5ca7e34f09a614 100644 --- a/src/editor/LevelNameEdit.cpp +++ b/src/editor/LevelNameEdit.cpp @@ -1,5 +1,5 @@ /** - * EventHandler.cpp + * LevelNameEdit.cpp * * @date 29.01.2025 * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) diff --git a/src/editor/LevelNameEdit.hpp b/src/editor/LevelNameEdit.hpp index 3cc97c308364d8a41886a80d03adcd4d7e29fb84..0758f9b4eb044879f47aaf109bb34a53b81841c7 100644 --- a/src/editor/LevelNameEdit.hpp +++ b/src/editor/LevelNameEdit.hpp @@ -1,5 +1,5 @@ /** - * EventHandler.cpp + * LevelNameEdit.hpp * * @date 29.01.2025 * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) @@ -22,12 +22,17 @@ class LevelNameEdit : public QLineEdit /** * Creates a LevelNameEdit Widget prefilling the provided level_name. * The parent of the LevelNameEdit Widget is set to the provided parent. - * @param level_name The initial name of the level. + * @param levelName The initial name of the level. * @param parent The Widget that should be set as the parent of this Widget. */ LevelNameEdit(const std::string& levelName, QWidget* parent = nullptr); protected: + /** + * Receives a QKeyEvent when the user presses a key to change the content of the QLineEdit. + * The onLevelNameUpdated event method is subsequently emitted. + * @param event The QKeyEvent for the Key Press. + */ void keyPressEvent(QKeyEvent* event) override; }; diff --git a/src/editor/LevelScene.cpp b/src/editor/LevelScene.cpp index e1f6fd6bad6592c722fab459a3db84231070ffda..a066f28190806f4dbe29fa9ee6316d770105e347 100644 --- a/src/editor/LevelScene.cpp +++ b/src/editor/LevelScene.cpp @@ -3,18 +3,26 @@ * * @date 28.01.2025 * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) - * @author Nils Jonathan Friedrich Eckardt implemented advanced placement + * @author Nils Jonathan Friedrich Eckardt implemented symmetry, advanced placement + * (onTileClicked, setTile, placeCliff, placeRoad, calcDir) */ #include "LevelScene.hpp" +#include <QFileDialog> #include <QGraphicsPixmapItem> +#include <QMessageBox> #include <QPixmap> #include <QPoint> +#include <algorithm> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/xml_parser.hpp> +#include <iostream> +#include <stdexcept> +#include <string> #include "SpriteProvider.hpp" +#include "Tile.hpp" #include "highfive/H5File.hpp" namespace editor @@ -22,90 +30,93 @@ namespace editor LevelScene::LevelScene( const std::string& name, int width, int height, std::vector<uint8_t> tile_ids, - const std::string& filePath, QWidget* parent) - : QGraphicsScene(parent), m_selected_tile_id(2), m_name(name), m_advanced_tile_placement(false), - m_width(width), m_height(height), m_tile_ids(tile_ids), m_tile_occupants({}), - m_file_path(filePath) + const std::string& file_path, QWidget* parent) + : QGraphicsScene(parent), m_name(name), m_width(width), m_height(height), m_tileIds(tile_ids), + m_filePath(file_path), m_selectedTileId(2), m_advancedTilePlacement(false), + m_symmetricTilePlacement(false) { - setSceneRect(0, 0, m_width * 16, m_height * 16); + setSceneRect(0, 0, m_width * 16, m_height * 16); // set dimensions of the QGraphicsScene - m_tile_occupants.reserve(tile_ids.size()); - for (int index = 0; index < tile_ids.size(); index++) + // iterate over all of the level map's tile id and build the two layers + // bottom layer (static) -> Tile classes (QGraphicsPixmapItem descendants) + // top layer (dynamic) -> direct QGraphicsPixmapItems (also stored in m_tile_occupants) + m_tileOccupants = + {}; // holds QGraphicsPixmapItems of top layer (nullptr if top pximap item not needed) + m_tileOccupants.reserve(tile_ids.size()); // size will be same as tile ids array at the end + for (size_t index = 0; index < tile_ids.size(); index++) { int x = (index % m_width) * 16; int y = (index / m_width) * 16; Tile* tile = new Tile(index, isBorder(index) ? tile_ids[index] : 0); addItem(tile); - tile->setZValue(0); + tile->setZValue(0); // bottom layer always has z value 0 tile->setPos(x, y); if (!isBorder(index) && tile_ids[index] > 0) { - m_tile_occupants.push_back(occupyTile(index, tile_ids[index])); + // if index is not on border and tile id is not grass the top layer is needed + // the top QGraphicsPixmapItem also needs to be stored to be able to switch + // it out for a different one later + m_tileOccupants.push_back(occupyTile(index, tile_ids[index])); } else { - m_tile_occupants.push_back(nullptr); + // top layer not needed + m_tileOccupants.push_back(nullptr); } } } LevelScene* LevelScene::empty(const std::string& name, int width, int height, QWidget* parent) { - // + 2 because of surrounding the map with cliffs - std::vector<uint8_t> tileIds; - tileIds.reserve(width * height); + std::vector<uint8_t> tile_ids; + tile_ids.reserve(width * height); // create top row with cliffs - tileIds.push_back(22); // cliff corner top left + tile_ids.push_back(22); // cliff corner top left for (int i = 0; i < width - 2; i++) { - tileIds.push_back(19); // cliff bottom + tile_ids.push_back(19); // cliff bottom } - tileIds.push_back(23); // cliff corner top right + tile_ids.push_back(23); // cliff corner top right - // create main rows with cliff at start and end + // create main rows with cliff at start and end grass in the middle for (int i = 0; i < height - 2; i++) { - tileIds.push_back(21); // cliff right + tile_ids.push_back(21); // cliff right for (int j = 0; j < width - 2; j++) { - tileIds.push_back(0); // pleins + tile_ids.push_back(0); // pleins } - tileIds.push_back(20); // cliff left + tile_ids.push_back(20); // cliff left } // create bottom row with cliffs - tileIds.push_back(24); // cliff corner bottom left + tile_ids.push_back(24); // cliff corner bottom left for (int i = 0; i < width - 2; i++) { - tileIds.push_back(18); // cliff top + tile_ids.push_back(18); // cliff top } - tileIds.push_back(25); // cliff corner bottom right + tile_ids.push_back(25); // cliff corner bottom right - return new LevelScene(name, width, height, tileIds, "../res/level_new.h5", parent); + return new LevelScene(name, width, height, tile_ids, "", parent); } -LevelScene* LevelScene::fromFile(const std::string& filePath, QWidget* parent) +LevelScene* LevelScene::fromFile(const std::string& file_path, QWidget* parent) { - HighFive::File file(filePath, HighFive::File::ReadOnly); + HighFive::File file(file_path, HighFive::File::ReadOnly); + HighFive::DataSet tilesarray_set = file.getDataSet("tilesarray"); - // read level metadata - std::string levelMetadata; - file.getDataSet("metadata").read(levelMetadata); + int width; + int height; + std::string name; + std::vector<uint8_t> level_tilesarray; - // read tilesarray - std::vector<uint8_t> levelTilesarray; - file.getDataSet("tilesarray").read(levelTilesarray); + tilesarray_set.getAttribute("width").read(width); + tilesarray_set.getAttribute("height").read(height); + tilesarray_set.getAttribute("name").read(name); + tilesarray_set.read(level_tilesarray); - // extract metadata from xml - std::istringstream xmlStream(levelMetadata); - boost::property_tree::ptree pt; - boost::property_tree::read_xml(xmlStream, pt); - int width = pt.get<int>("level.width"); - int height = pt.get<int>("level.height"); - std::string name = pt.get<std::string>("level.name"); - - return new LevelScene(name, width, height, levelTilesarray, filePath, parent); + return new LevelScene(name, width, height, level_tilesarray, file_path, parent); } std::string LevelScene::getName() @@ -142,127 +153,163 @@ bool LevelScene::isWaterTile(uint8_t id) return false; } -void LevelScene::onLevelNameUpdated(std::string newName) +void LevelScene::onLevelNameUpdated(std::string new_name) { - m_name = newName; + m_name = new_name; } -void LevelScene::onLevelWriteRequested(QString filePath) +void LevelScene::onLevelWriteRequested() { - boost::property_tree::ptree pt; - - // Add data to the property tree - pt.put("level.width", m_width); - pt.put("level.height", m_height); - pt.put("level.name", m_name); - - // convert property tree to xml string - std::ostringstream xmlStream; - boost::property_tree::write_xml(xmlStream, pt); - std::string xmlData = xmlStream.str(); - - // write level to hdf5 - HighFive::File file(filePath.toStdString(), HighFive::File::Truncate); - file.createDataSet<std::string>("metadata", HighFive::DataSpace::From(xmlStream)) - .write(xmlData); - file.createDataSet<uint8_t>("tilesarray", HighFive::DataSpace::From(m_tile_ids)) - .write(m_tile_ids); + if (m_filePath.empty()) + { + // create a nice file name from the levelname + // it will be displayed in the file save dialog + std::string filepath = m_name; + std::replace(filepath.begin(), filepath.end(), ' ', '-'); + std::transform(filepath.begin(), filepath.end(), filepath.begin(), std::towlower); + filepath = "/" + filepath + ".h5"; + + // get path at which the level should be written to + m_filePath = + QFileDialog::getSaveFileName( + (QWidget*)parent(), "Level Speichern", + QDir::currentPath() + QString(filepath.c_str()), "HDF5 Files (*.h5 *.hdf5)") + .toStdString(); + } + if (m_filePath.empty()) + { + return; // if still empty dialog was canceled + } + + // write level to hdf5 file + try + { + HighFive::File file(m_filePath, HighFive::File::Overwrite); + HighFive::DataSet tilesarray_set = + file.createDataSet<std::vector<uint8_t>>("tilesarray", m_tileIds); + tilesarray_set.write(m_tileIds); + tilesarray_set.createAttribute<int>("width", m_width).write(m_width); + tilesarray_set.createAttribute<int>("height", m_height).write(m_height); + tilesarray_set.createAttribute<std::string>("name", m_name).write(m_name); + QMessageBox::information((QWidget*)parent(), "Saved", "Level map saved successfully."); + } + catch (const HighFive::Exception& e) + { + QMessageBox::critical( + (QWidget*)parent(), "Save failed", + "Saving the level map failed.\nPlease make sure that you have write permissions for " + "the selected path."); + throw e; + } } void LevelScene::onTileEntered(int index) { - if (m_selected_tile_id == m_tile_ids[index]) + if (m_selectedTileId == m_tileIds[index]) { return; } - if (isBorder(index) && !isWaterTile(m_selected_tile_id)) + if (isBorder(index) && !isWaterTile(m_selectedTileId)) { + // only water tiles can be set on the border return; } - if (m_tile_occupants[index] != nullptr) + if (m_tileOccupants[index] != nullptr) { - removeItem(m_tile_occupants[index]); - delete m_tile_occupants[index]; - m_tile_occupants[index] = nullptr; + removeItem(m_tileOccupants[index]); + delete m_tileOccupants[index]; + m_tileOccupants[index] = nullptr; } - if ((!isBorder(index) && m_selected_tile_id > 0) || isBorder(index)) + if ((!isBorder(index) && m_selectedTileId > 0) || isBorder(index)) { - m_tile_occupants[index] = occupyTile(index, m_selected_tile_id); + m_tileOccupants[index] = occupyTile(index, m_selectedTileId); } } void LevelScene::onTileExited(int index) { - if (m_selected_tile_id == m_tile_ids[index]) + if (m_selectedTileId == m_tileIds[index]) { return; } - if (m_tile_occupants[index] != nullptr) + if (m_tileOccupants[index] != nullptr) { - removeItem(m_tile_occupants[index]); - delete m_tile_occupants[index]; - m_tile_occupants[index] = nullptr; + removeItem(m_tileOccupants[index]); + // Interestingly QGraphicsScene does take ownership of the QGraphicsPixmapItems that + // are added to it, but inside removeItem it gives that ownership back without destroying + // it. I first assumed that removeItem would also destroy the Pixmap Items, but valgrind + // taught me otherwise. :) So therefore we delete it explicitly. That is probably the only + // place in the entire application where we explicitly free memory. + delete m_tileOccupants[index]; + m_tileOccupants[index] = nullptr; } - if (m_tile_ids[index] > 0) + if (m_tileIds[index] > 0) { - m_tile_occupants[index] = occupyTile(index, m_tile_ids[index]); + m_tileOccupants[index] = occupyTile(index, m_tileIds[index]); } } void LevelScene::setTile(int index, uint8_t id) { - m_tile_ids[index] = id; - if (m_tile_occupants[index] != nullptr) - { - removeItem(m_tile_occupants[index]); - delete m_tile_occupants[index]; - m_tile_occupants[index] = nullptr; - } - if (m_tile_ids[index] > 0) - { - m_tile_occupants[index] = occupyTile(index, id); - } - - /* gespiegeltes Setzen - index = m_width * m_height - 1 -index; - int swapID[22] ={11,10,9,8,12,16,15,14,13,17,19,18,21,20,25,24,23,22,29,28,27,26}; - if(id > 7 && id < 30) id = swapID[ id-8 ]; - - m_tile_ids[index]=id; - if (m_tile_occupants[index] != nullptr) + m_tileIds[index] = id; + if (m_tileOccupants[index] != nullptr) { - removeItem(m_tile_occupants[index]); - delete m_tile_occupants[index]; - m_tile_occupants[index] = nullptr; + removeItem(m_tileOccupants[index]); + delete m_tileOccupants[index]; + m_tileOccupants[index] = nullptr; } - if (m_tile_ids[index] > 0) + if (m_tileIds[index] > 0) { - m_tile_occupants[index] = occupy_tile(index, id); + m_tileOccupants[index] = occupyTile(index, id); } - */ } void LevelScene::onTileClicked(int index) { - if (isBorder(index) && !isWaterTile(m_selected_tile_id)) + if (isBorder(index) && !isWaterTile(m_selectedTileId)) { - return; + return; // no Placement allowed } - if (!m_advanced_tile_placement) + m_tileIds[index] = m_selectedTileId; // normal Placement + + if (m_advancedTilePlacement) { - m_tile_ids[index] = m_selected_tile_id; + placeAdvancedTiles(index); // advanced Placement + } + + if (!m_symmetricTilePlacement) return; + + uint8_t id = m_selectedTileId; + index = m_width * m_height - 1 - index; + int swapID[22] = {11, 10, 9, 8, 12, 16, 15, 14, 13, 17, 19, + 18, 21, 20, 25, 24, 23, 22, 29, 28, 27, 26}; + if (id > 7 && id < 30) + id = swapID[id - 8]; + if (id > 49 && id < 55) + id += 5; + else if (id > 54 && id < 60) + id -= 5; + + setTile(index, m_selectedTileId); // symmetric Placement + + if (m_advancedTilePlacement) + { + placeAdvancedTiles(index); // symmetric advanced Placement } +} - if (m_selected_tile_id > 5 && m_selected_tile_id < 17) +void LevelScene::placeAdvancedTiles(int index) +{ + if (m_selectedTileId > 5 && m_selectedTileId < 17) { // Straße plaziert placeRoad(index, true); return; } - if ((m_selected_tile_id > 16 && m_selected_tile_id < 30) || m_selected_tile_id == 1) + if ((m_selectedTileId > 16 && m_selectedTileId < 30) || m_selectedTileId == 1) { // Wasser plaziert - if (m_selected_tile_id == 17) + if (m_selectedTileId == 17) { setTile(index, 17); } @@ -273,34 +320,40 @@ void LevelScene::onTileClicked(int index) placeCliff(false, index); return; } - if (m_selected_tile_id == 0 || m_selected_tile_id == 2 || m_selected_tile_id == 3) + if (m_selectedTileId == 0 || m_selectedTileId == 2 || m_selectedTileId == 3) { // Land plaziert - setTile(index, m_selected_tile_id); + setTile(index, m_selectedTileId); placeCliff(true, index); return; } - setTile(index, m_selected_tile_id); // Gebäude plaziert + setTile(index, m_selectedTileId); // Gebäude plaziert } -void LevelScene::onNewTileIdSelected(uint8_t tileId) +void LevelScene::onNewTileIdSelected(uint8_t tile_id) { - m_selected_tile_id = tileId; + m_selectedTileId = tile_id; } -void LevelScene::onCheckBoxToggled() +void LevelScene::onAdvancedPlacementToggled() { - m_advanced_tile_placement = !m_advanced_tile_placement; + m_advancedTilePlacement = !m_advancedTilePlacement; } -QGraphicsPixmapItem* LevelScene::occupyTile(int index, uint8_t tileId) +void LevelScene::onSymmetryToggled() +{ + m_symmetricTilePlacement = !m_symmetricTilePlacement; +} + +QGraphicsPixmapItem* LevelScene::occupyTile(int index, uint8_t tile_id) { int x = (index % m_width) * 16; int y = (index / m_width) * 16; - QPixmap tileOccupant = SpriteProvider::getSprite(tileId); - QGraphicsPixmapItem* tileOccupantItem = addPixmap(tileOccupant); - tileOccupantItem->setZValue(tileId < 50 ? 1 : 2 + index); - tileOccupantItem->setPos(x, tileId < 50 ? y : y - 16); - return tileOccupantItem; + QPixmap tile_occupant = SpriteProvider::getSprite(tile_id); + QGraphicsPixmapItem* tile_occupant_item = addPixmap(tile_occupant); + bool is_building_tile = tile_id >= 50; + tile_occupant_item->setZValue(is_building_tile ? 2 + index : 1); + tile_occupant_item->setPos(x, is_building_tile ? y - 16 : y); + return tile_occupant_item; } void LevelScene::placeCliff(bool placedLand, int index) @@ -310,9 +363,35 @@ void LevelScene::placeCliff(bool placedLand, int index) int16_t surroundingIDs[8] = {-2, -2, -2, -2, -2, -2, -2, -2}; //-2 = uninitialisiert, -1 = kartenrand - bool marchingSquares[8][4] = {{false}}; + bool marchingSquares[8][4] = {false}; + + /* + FUNKTIONSWEISE: + 1. Laden der umliegenden 8 tile IDs + Hierfür wird zuerst geprüft, ob diese sich im Kartenrand befinden. In diesem fall + werden sie auf -1 gesetzt (daher int16_t nicht uint8_t). Anschließend werden die IDs + der tiles geladen. + 2. Die geladenen Ids müssen gefiltert werden. Nicht alle IDs sind direkt verwendbar. + Riffe, Wälder und Gebirge werden als Land oder Wasser angesehen. Straßen und Gebäude + werden als Land behandelt. (dies kann einfach geändert werden, indem man diese auf -1 + setzt). + 3. Die IDs werden remapped. Die Tiles haben die IDs 1,18-29. Um sie sich nutzbar machen + zu können müssen diese in 0-15 gemapped werden. Auf diese Weise kann aus einer ID ein + tile und umgekehrt berechnet werden. + 4. Berechnung der neuen IDs Der Ansatz der Berechnung ist vom Marching Cubes Algorithmus + (hier 2D als Marching Squares) abgeleitet. Darin ist jede Ecke eines Tiles entweder Innen + oder Außen, hier: Land (true) oder Wasser (false). Nun wird das Land/Wasser an den Ecken + hinzugefügt. + 5. Aus den Berechneten Marching Squares werden nun die neuen IDs berechnet. Die Reihenfolge + der Ecken: TopLeft, TopRight, BottomRight, BottomLeft + 6. Remappe die IDs zurück zu setzbaren IDs für die Karte. 0-15 werden zu 1,18-29 gemapped. + Für den Edgecase TopLeft, BottomRight existiert keine Textur. Stattdessen wir eine einzelne + TopLeft Ecke verwendet. + 7.Setzen der neu berechneten Files. Wichtig hier ist, dass Riffe, die nicht verändert + wurden auch nicht verändert werden. + */ - // Überprüfe Kartenränder, setze diese auf -1 + //Überprüfe Kartenränder, setze diese auf -1 if (index < m_width) { // oberer Kartenrand surroundingIDs[7] = -1; @@ -340,77 +419,42 @@ void LevelScene::placeCliff(bool placedLand, int index) // Erhalte IDs von den umliegenden 8 Feldern, die kein Kartenrand sind // startet in der mitte Oben, gehe im Uhrzeigersinn - if (surroundingIDs[0] == -2) - { - surroundingIDs[0] = (int16_t)m_tile_ids[index - m_width]; - } - if (surroundingIDs[1] == -2) - { - surroundingIDs[1] = (int16_t)m_tile_ids[index - m_width + 1]; - } - if (surroundingIDs[2] == -2) - { - surroundingIDs[2] = (int16_t)m_tile_ids[index + 1]; - } - if (surroundingIDs[3] == -2) - { - surroundingIDs[3] = (int16_t)m_tile_ids[index + m_width + 1]; - } - if (surroundingIDs[4] == -2) - { - surroundingIDs[4] = (int16_t)m_tile_ids[index + m_width]; - } - if (surroundingIDs[5] == -2) - { - surroundingIDs[5] = (int16_t)m_tile_ids[index + m_width - 1]; - } - if (surroundingIDs[6] == -2) - { - surroundingIDs[6] = (int16_t)m_tile_ids[index - 1]; - } - if (surroundingIDs[7] == -2) + for (int i = 0; i < 8; i++) { - surroundingIDs[7] = (int16_t)m_tile_ids[index - m_width - 1]; + if (surroundingIDs[i] == -2) + surroundingIDs[i] = (int16_t)m_tileIds[calcDir(i, index)]; } // Ids können nicht lesbare Tiles enthalten, diese Herausfiltern - for (short& surroundingID : surroundingIDs) - { - if (surroundingID == 17) - { - surroundingID = 1; // Riff als Wasser angesehen - } - if (surroundingID == 2 || surroundingID == 3) - { - surroundingID = 0; // Wald, Gebirge als Land angesehen - } - if (surroundingID > 3 && surroundingID < 17) - { - surroundingID = -1; // Straßen werden nicht verändert - } - if (surroundingID > 29) - { - surroundingID = 0; // Gebäude werden als Land angesehen - } + for (int i = 0; i < 8; i++) + { // 1 = Wasser, 0 = Land, -1 = unverändert + if (surroundingIDs[i] == 17) + surroundingIDs[i] = 1; // Riff als Wasser angesehen + if (surroundingIDs[i] == 2 || surroundingIDs[i] == 3) + surroundingIDs[i] = 0; // Wald, Gebirge als Land angesehen + if (surroundingIDs[i] > 3 && surroundingIDs[i] < 17) + surroundingIDs[i] = 0; // Straßen werden als Land angesehen + if (surroundingIDs[i] > 29) + surroundingIDs[i] = 0; // Gebäude werden als Land angesehen } // ID remapping um damit arbeiten zu können - for (short& surroundingID : surroundingIDs) + for (int i = 0; i < 8; i++) { - if (surroundingID == -1) + if (surroundingIDs[i] == -1) { } - else if (surroundingID == 0) + else if (surroundingIDs[i] == 0) { - surroundingID = 15; + surroundingIDs[i] = 15; } - else if (surroundingID == 1) + else if (surroundingIDs[i] == 1) { - surroundingID = 0; + surroundingIDs[i] = 0; } else { - surroundingID = IdToSum[surroundingID - 18]; + surroundingIDs[i] = IdToSum[surroundingIDs[i] - 18]; } } @@ -459,21 +503,13 @@ void LevelScene::placeCliff(bool placedLand, int index) { surroundingIDs[i] = 0; if (marchingSquares[i][0]) - { surroundingIDs[i] += 8; - } if (marchingSquares[i][1]) - { surroundingIDs[i] += 4; - } if (marchingSquares[i][2]) - { surroundingIDs[i] += 2; - } if (marchingSquares[i][3]) - { surroundingIDs[i] += 1; - } } } @@ -481,95 +517,80 @@ void LevelScene::placeCliff(bool placedLand, int index) for (int i = 0; i < 8; i++) { if (surroundingIDs[i] > -1) - { surroundingIDs[i] = SumToId[surroundingIDs[i]]; - } } // Plaziere Tiles for (int i = 0; i < 8; i++) { if (isntIdentical(surroundingIDs[i], calcDir(i, index))) - { setTile(calcDir(i, index), (uint8_t)surroundingIDs[i]); - } } } void LevelScene::placeRoad(int index, bool updateFlag) { - const int16_t IdToSum[] = {5, 10, 13, 14, 11, 7, 15, 6, 3, 12, 9}; + // const int16_t IdToSum[] = {5,10,13,14,11,7,15,6,3,12,9}; const uint8_t SumToId[] = {6, 6, 7, 14, 6, 6, 13, 11, 7, 16, 7, 10, 15, 8, 9, 12}; bool tileDirections[] = {false, false, false, false}; int16_t surroundingIDs[] = {-1, -1, -1, -1}; //-1 = kartenrand oder keine strasse uint8_t placedID = 0; + /* + FUNKTIONSWEISE: + 1. Laden der umliegenden 4 tile IDs falls diese nicht im Kartenrand stecken. + 2. Die geladenen Ids müssen gefiltert werden. Tiles, die nicht Straßen sind, oder + Brücken sind werden nicht behandelt. + 3. Berechnung in welchen Richtungen sich Straßen befinden. + 4. Aus den berechneten Richtungen wird nun eine neue ID gebildet. Reihenfolge der + Richtungen: Up, Right, Down, Left + 5. Die Berechnete ID wird zu einer verwendbaren ID gemapped. 0-15 werden zu 6-16 gemapped. + Straßen, die in weniger als 2 Richtungen zeigen gibt es nicht. Die Textur der Kreuzung, + die in alle 4 Richtungen zeigt ist falsch und zeigt nicht nach unten. + 6. Setzen des neu berechneten Tiles. Im Anschluss wird die selbe Funktion für die + umliegenden 4 Tiles aufgerufen, falls diese Straßen beinhalten. + */ + // Erhalte IDs der umliegenden Felder - im Uhrzeigersinn, start: oben // Vorgehen: überprüfe, ob die obere Zelle im Kartenrand ist. Wenn nicht erhalte index der // oberen Zelle if (!(index < m_width)) - { - surroundingIDs[0] = (int16_t)m_tile_ids[index - m_width]; - } + surroundingIDs[0] = (int16_t)m_tileIds[index - m_width]; if (!((index + 1) % m_width == 0)) - { - surroundingIDs[1] = (int16_t)m_tile_ids[index + 1]; - } + surroundingIDs[1] = (int16_t)m_tileIds[index + 1]; if (!(index > m_width * (m_height - 1))) - { - surroundingIDs[2] = (int16_t)m_tile_ids[index + m_width]; - } + surroundingIDs[2] = (int16_t)m_tileIds[index + m_width]; if (!(index % m_width == 0)) - { - surroundingIDs[3] = (int16_t)m_tile_ids[index - 1]; - } + surroundingIDs[3] = (int16_t)m_tileIds[index - 1]; - // Umformen der IDs oder ID ist keine Straße + // Filtern der IDs, die keine Straßen sind for (int i = 0; i < 4; i++) { - if (surroundingIDs[i] < 6 || surroundingIDs[i] > 16) - { - surroundingIDs[i] = -1; // ID ist keine Strasse - } - else - { - surroundingIDs[i] = IdToSum[surroundingIDs[i] - 6]; // id remapping - } + if (surroundingIDs[i] < 4 || surroundingIDs[i] > 16) + surroundingIDs[i] = -1; } // Berechne in welche Richtungen das neue Tile Zeigt for (int i = 0; i < 4; i++) { if (surroundingIDs[i] > -1) - { tileDirections[i] = true; - } } // Berechne die Remapped ID placedID = 0; if (tileDirections[0]) - { placedID += 8; - } if (tileDirections[1]) - { placedID += 4; - } if (tileDirections[2]) - { placedID += 2; - } if (tileDirections[3]) - { placedID += 1; - } if (placedID == 0) - { updateFlag = false; // Umliegende Tiles müssen nicht geupdated werden, da keine Strassen - } placedID = SumToId[placedID]; // Berechnete Summe in Valide ID umformen setTile(index, placedID); // Tile setzen @@ -577,67 +598,47 @@ void LevelScene::placeRoad(int index, bool updateFlag) if (updateFlag) { if (tileDirections[0]) - { placeRoad((index - m_width), false); // update oben - } if (tileDirections[1]) - { placeRoad((index + 1), false); // update rechts - } if (tileDirections[2]) - { placeRoad((index + m_width), false); // update unten - } if (tileDirections[3]) - { placeRoad((index - 1), false); // update links - } } } int LevelScene::calcDir(int i, int index) { - if (i == 0) + switch (i) { + case (0): return (index - m_width); - } - if (i == 1) - { + case (1): return (index - m_width + 1); - } - if (i == 2) - { + case (2): return (index + 1); - } - if (i == 3) - { + case (3): return (index + m_width + 1); - } - if (i == 4) - { + case (4): return (index + m_width); - } - if (i == 5) - { + case (5): return (index + m_width - 1); - } - if (i == 6) - { + case (6): return (index - 1); - } - if (i == 7) - { + case (7): return (index - m_width - 1); } + throw std::invalid_argument("i has to be 0 <= i <= 7"); } bool LevelScene::isntIdentical(int16_t id, int index) { bool flag = - (id != -1 && // kartenrand - (id != 0 || m_tile_ids[index] != 2) && // wald - (id != 0 || m_tile_ids[index] != 3) && // gebirge - (id != 1 || m_tile_ids[index] != 17)); // riff + (!(id == -1 || // kartenrand + (id == 0 && m_tileIds[index] == 2) || // wald + (id == 0 && m_tileIds[index] == 3) || // gebirge + (id == 1 && m_tileIds[index] == 17))); // riff return flag; } diff --git a/src/editor/LevelScene.hpp b/src/editor/LevelScene.hpp index 89ad83038edab4a546d01373f0643b8c8be9bf54..a466a9d3deb53c48c6159716212dd6cc5e664edb 100644 --- a/src/editor/LevelScene.hpp +++ b/src/editor/LevelScene.hpp @@ -3,60 +3,301 @@ * * @date 28.01.2025 * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) - * @author Nils Jonathan Friedrich Eckardt minor changes + * @author Nils Jonathan Friedrich Eckardt implementing symmetry, advanced placement + * (and onTileClicked, setTile, placeCliff, placeRoad, calcDir) */ #pragma once +#include <QGraphicsPixmapItem> #include <QGraphicsRectItem> #include <QGraphicsScene> -#include <QGraphicsSceneMouseEvent> -#include <QMouseEvent> -#include <QWidget> #include "EventHandler.hpp" -#include "Tile.hpp" namespace editor { +/** + * The LevelScene is a Container for all of the individual tiles (QGraphicsPixmapItems) + * of the level map. + * LevelScene is responsible for rendering/updating the map on user actions. + * It also privdes the static factory methods empty, fromFile for creating empty level maps, + * laoding a level map from a hdf5 file. + * + * LevelScene inherits from EventHandler overwriting many event methods: + * - onLevelNameUpdated + * To keep the current level name for when the level map should be saved. + * - onLevelWriteRequested + * To write the level map to a hdf5 file when the save button is pressed. + * - onTileEntered + * To render the currently selected tile from the tile selector on the entered tile. + * - onTileExited + * To render the old tile again if the currently selected tile id was not set on this tile. + * - onTileClicked + * To set the currently selected tile id on that tile. Maybe do advanced placement changes. + * - onNewTileIdSelected + * To update the currently selected tile id. + * - onAdvancedPlacmentToggled + * To toggle advanced placement on or off. + * + * How does the rendering work? + * LevelScene implements a two layer approach. + * The bottom layer is static. It is created out of Tile (Tile.cpp) objects. These Tile objects + * inherit from QGraphicsPixmapItems to override onmouse enter, exit and press. + * LevelScene receives those events in onTileEntered, onTileExited, onTileClicked. + * The bottom layer is only grass and water cliffs on the borders. + * The bottom layer always gets a z value of 0. + * For non grass tile ids, QGraphicsPixmapItems are created on the second layer with a z value of 1 + * for terrain tiles and 2 + index for building tiles. + * These Items are also saved in m_tileOccupants. The second layer is dynamic, by keeping pointers + * to its Pixmap Items we can remove them and switch them around. + */ class LevelScene : public QGraphicsScene, public EventHandler { public: + /** + * It builds a LevelScene from the provided vector of tile ids and width and height. + * It creates the bottom layer of Tile objects and builds a second layer of + * QGraphicsPixmapItems on top, if the tile id is > 0 and the tile is not on the border. + * @param name The name of the level. + * @param width The width of the level. Unit: number of tiles + * @param height The height of the level. Unit: number of tiles + * @param tileIds A vector of tile ids that define the level map. + * @param filePath The path to the file from which the level was loaded, empty string if + * level was just created. + * @param parent The parent to set for the LevelScene. + */ LevelScene( const std::string& name, int width, int height, std::vector<uint8_t> tileIds, const std::string& filePath, QWidget* parent = nullptr); + + /** + * Uses the LevelScene constructor internally to create an empty LevelScene (only grass, + * water cliffs on the borders). + * @param name The name of the new level. + * @param width The width of the new level. Unit: number of tiles + * @param height The height of the new level. Unit: number of tiles + * @param parent The parent to set for the newly created LevelScene. + * @return An empty LevelScene. + */ static LevelScene* empty(const std::string& name, int width, int height, QWidget* parent = nullptr); + + /** + * Uses the LevelScene constructor internally to create a LevelScene from an hdf5 file. + * @param filePath The path to the hdf5 file from which the LevelScene should be loaded. + * @param parent The parent to set for the newly created LevelScene. + * @return A LevelScene that was loaded from a file. + */ static LevelScene* fromFile(const std::string& filePath, QWidget* parent = nullptr); - std::string getName(); - int getWidth(); - int getHeight(); + + /** + * Gets the name of the level. + * @return The current name of the level. + */ + std::string getName(); + + /** + * Gets the width of the level. + * @return The width of the level. Unit: number of tiles. + */ + int getWidth(); + + /** + * Gets the height of the level. + * @return The height of the level. Unit: number of tiles. + */ + int getHeight(); private: - bool isBorder(int index); - bool isWaterTile(uint8_t id); - bool isntIdentical(int16_t id, int index); - void onLevelNameUpdated(std::string newName) override; - void onLevelWriteRequested(QString filePath) override; - void onTileEntered(int index) override; - void onTileExited(int index) override; - void onTileClicked(int index) override; - void onNewTileIdSelected(uint8_t tileId) override; - void onCheckBoxToggled() override; - void setTile(int index, uint8_t id); - void placeCliff(bool placedLand, int index); - void placeRoad(int index, bool updateFlag); - int calcDir(int i, int index); - QGraphicsPixmapItem* occupyTile(int index, uint8_t tileId); - uint8_t m_selected_tile_id; - std::string m_name; - bool m_advanced_tile_placement; - int m_width; - int m_height; - std::vector<uint8_t> m_tile_ids; - std::vector<QGraphicsPixmapItem*> m_tile_occupants; - std::string m_file_path; + /** + * Checks if the given index corresponds to a border tile. + * @param index The position of an individual tile if you linearized the level map. + * @return True or False if the given index corresponds to a border tile. + */ + bool isBorder(int index); + + /** + * Checks if the tile id corresponds to a water tile. + * @param id The tile id to check. + * @return True or false if the given tile id corresponds to a water tile. + */ + bool isWaterTile(uint8_t id); + + /** + * Updates the level name stored in LevelScene to newName. + * The onLevelNameUpdated event method is emitted by the LevelNameEdit Widget. + * @param newName The new name of the level. + */ + void onLevelNameUpdated(std::string newName) override; + + /** + * Writes the level to the file from which it was loaded initially. + * If it was created from scratch, a path to the desired location is + * obtained from the QT file save dialog. + * The onLevelWriteRequested event method is emitted by the SaveButton. + */ + void onLevelWriteRequested() override; + + /** + * Renders the currently selcted tile (m_selectedTileId) on the provided index. + * The onTileEntered event method is emitted by the Tile object. + * @param index The position of an individual tile if you were to linearized the level map. + */ + void onTileEntered(int index) override; + + /** + * Renders the old tile id from m_tileIds at index again if the selected tile id was + * not set onto that tile. + * The onTileExited event method is emitted by the Tile object. + * @param index The position of an individual tile if you were to linearized the level map. + */ + void onTileExited(int index) override; + + /** + * Evaluates how to procede once a tile has been placed by clicking on it. + * On the border only water tiles can be placed. + * If the option for advanced tile placement is checked, it will be executed from here. + * If the option for symmetric tile placement is checked, it will also be executed from + * here. When a red building is selected, symmetric tile placement will turn the mirrored + * building into a blue one and vice versa. + * @param index The index of the placed tile. Used to calculate both the previous id + * and the position on the map. + */ + void onTileClicked(int index) override; + + /** + * Evaluates what tile has been placed, then calls the corresponding function. + * When this function is called advanced placement of tiles is enabled. + * If any street tile except bridges were placed placeRoad is called. + * If any Water or Land tiles are placed placeCliff is called. + * If any building is placed setTile is called. + */ + void placeAdvancedTiles(int index); + + /** + * Saves the newly selected tileId inside LevelScene. + * The onNewTileIdSelected event method is emitted by the TileButton which + * is part of the TileSelector on the right of the level editor. + * @param tileId The newly selected tile id. + */ + void onNewTileIdSelected(uint8_t tileId) override; + + /** + * Toggles LevelScene's internal m_advancedTilePlacement member. + * This event method is emitted by the Advanced Placement CheckBox in the TopBar. + */ + void onAdvancedPlacementToggled() override; + /** + * Toggles LevelScene's internal m_symmetricTilePlacement member. + * This event method is emitted by the Advanced Placement CheckBox in the TopBar. + */ + void onSymmetryToggled() override; + /** + * This places a tile on the map. + * @param index Position on the map where the Tile is placed. + * @param id Id of the placed tile. + */ + void setTile(int index, uint8_t id); + /** + * A function of advanced placement used to determain whether the 8 surrounding tiles + * change when any land or water is placed. In this case it automaticly calculates the + * correct shape of the new cliffs and places them. + * @param placedLand If Land was placed - not water. Needed to evaluate if land should + * be added or removed around the placed tile. + * @param index Position of the tile water or land is placed on. The placed cliffs are the + * surrounding 8 tiles. + */ + void placeCliff(bool placedLand, int index); + /** + * A recursive function of advanced placement used to adjust the roads around the placed + * one. The surrounding roads above, bellow, to the left and right are detected and used to + * calculate the shape the newly placed road should have. After that the function is again + * called for those up to 4 roads around. + * @param index The position of the newly placed road. + * @param updateFlag Used in the recursion. When true it calls itself on the 4 surrounding + * tiles. When false the recursion ends and it doesnt call itself again. + */ + void placeRoad(int index, bool updateFlag); + /** + * A technical helper function for placeCliff. When calculating the Direction of the + * surrounding 8 tiles different calculations have to be done due to the map being + * represented as a 1D array rather then a 2D one. As a result it would not be easily + * possible to iterate through index of the 8 surrounding tiles, starting with the one + * above, then turning clockwise. This function is used for this to prevent code + * duplication. + */ + int calcDir(int i, int index); + /** + * A technical helper function for placeCliff. When evaluating whether land or water has + * been placed trees and mountains are treated as terrain, while reefs are treated as water. + * However once the placement of new cliffs has been determained reefs should not be + * replaced by water when there should not be made any change to the cell. This function + * fixes a bug where this would happen. + * @param id The id of a tile, this bug could trigger on. + * @param index The position the old tile is sitting on, possibly to be replaced by a new + * one. + */ + bool isntIdentical(int16_t id, int index); + + /** + * Places a new QGraphicsPixmapItem using the given tileId at the given index. + * It correclty sets its position and z value (1 -> terrain tile, 2 + index -> building + * tile) + * @param index The position of an individual tile if you were to linearized the level map. + * @param tileId The id that corresponds to a type of tile to use. + * @return A pointer to the QGraphicsPixmapItem, that was added to the level scene. + */ + QGraphicsPixmapItem* occupyTile(int index, uint8_t tileId); + + /** + * The name of the level. + */ + std::string m_name; + + /** + * The width of the level. Unit: number of tiles. + */ + int m_width; + + /** + * The height of the level. Unit: number of tiles. + */ + int m_height; + + /** + * The tile id of every tile. (linearized representation) + */ + std::vector<uint8_t> m_tileIds; + + /** + * The path from which the LevelScene was loaded (empty if created from scratch). + */ + std::string m_filePath; + + /** + * The currently selected tile id. Tells the LevelScene which tiles need to be set + * if a tile is clicked. + */ + uint8_t m_selectedTileId; + + /** + * Activates/deactivates adavanced placement. + */ + bool m_advancedTilePlacement; + + /** + * Activates/deactivates adavanced placement. + */ + bool m_symmetricTilePlacement; + + /** + * Holds pointers to all of the QGraphicsPixmapItems of the top layer. + * It holds nullptrs in places at which just the bottom layer is present. + * The length of m_tileOccupants is the same as m_tileIds. + */ + std::vector<QGraphicsPixmapItem*> m_tileOccupants; }; } // namespace editor \ No newline at end of file diff --git a/src/editor/MainWindow.hpp b/src/editor/MainWindow.hpp index dea2a5587d6572ba87b9ed50c432ed1bc4f27878..f4cf77bc0c9868b1a0b61df6274b7bb0debef3cb 100644 --- a/src/editor/MainWindow.hpp +++ b/src/editor/MainWindow.hpp @@ -15,15 +15,43 @@ namespace editor { +/** + * MainWindow is the top level Widget of the LevelEditor. + * All other Widgets are children of MainWindow. + * MainWindow is instantiated on the stack in main.cpp. + * Thus, MainWindow is freed automatically at application exit together + * with all of the other widgets (descendants), because of the way memory management works in QT. + * MainWindow inherits from EventHandler so that it can override onLevelNameUpadated in order + * to change the window title when the level name is changed. + */ class MainWindow : public QMainWindow, public EventHandler { public: + /** + * Creates a MainWindow from the given LevelScene and parent. + * @param level The LevelScene of the level that should be edited. + * @param parent The parent widget for the MainWindow. + */ MainWindow(LevelScene* level, QWidget* parent = nullptr); private: + /** + * Updates the Window Title with the new_name. + * @param new_name The new level name. + */ void onLevelNameUpdated(std::string newName) override; - int m_levelWidth; - int m_levelHeight; + + /** + * The widht of the level that is being edited. + * Unit: number of tiles. + */ + int m_levelWidth; + + /** + * The height of the level that is being edited. + * Unit: number of tiles. + */ + int m_levelHeight; }; } // namespace editor \ No newline at end of file diff --git a/src/editor/SaveButton.cpp b/src/editor/SaveButton.cpp index dc94c84d4ba64523e29f83ec3db005ccfc6bff1e..6c96287ebed32d5d42760aa3ca5bfe859e68de09 100644 --- a/src/editor/SaveButton.cpp +++ b/src/editor/SaveButton.cpp @@ -19,15 +19,9 @@ SaveButton::SaveButton(const std::string& title, QWidget* parent) { } -void SaveButton::mousePressEvent(QMouseEvent* event) +void SaveButton::mousePressEvent(QMouseEvent*) { - QPushButton::mousePressEvent(event); - QString filePath = QFileDialog::getSaveFileName( - this, "Level Speichern", QDir::currentPath(), "HDF5 Files (*.h5)"); - if (!filePath.isEmpty()) - { - EventHandler::send([filePath](EventHandler* e) { e->onLevelWriteRequested(filePath); }); - } + EventHandler::send([](EventHandler* e) { e->onLevelWriteRequested(); }); } } // namespace editor \ No newline at end of file diff --git a/src/editor/SaveButton.hpp b/src/editor/SaveButton.hpp index f1399613e6bfb8af3c7f7ef9700e6658f58baeac..98993a3f606706884d7e6aa081bddc47ee9d3c6b 100644 --- a/src/editor/SaveButton.hpp +++ b/src/editor/SaveButton.hpp @@ -1,9 +1,9 @@ /** -* SaveButton.cpp -* -* @date 29.01.2025 -* @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) -*/ + * SaveButton.cpp + * + * @date 29.01.2025 + * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) + */ #pragma once @@ -12,11 +12,30 @@ namespace editor { -class SaveButton : public QPushButton { -public: - SaveButton(const std::string& title, QWidget *parent = nullptr); -protected: - void mousePressEvent(QMouseEvent* event) override; +/** + * The Save Button is displayed on the top right in the TopBar. + * Clicking it opens the file save dialog. + * It is not responsible for writing the level itself. + * Instead it collects the desired file path from the file save dialog and + * emits the onLevelWriteRequested event method which is overwritten by the LevelScene. + */ +class SaveButton : public QPushButton +{ + public: + /** + * Creates a SaveButton with the given title and parent. + * @param title The title to display inside the SaveButton. + * @param parent The parent that should be set for the SaveButton. + */ + SaveButton(const std::string& title, QWidget* parent = nullptr); + + protected: + /** + * When a mouse press event is received, + * it emits the onLevelWriteRequested event method. + * onLevelWriteRequested is overwritten by LevelScene. + */ + void mousePressEvent(QMouseEvent*) override; }; } // namespace editor \ No newline at end of file diff --git a/src/editor/SpriteProvider.cpp b/src/editor/SpriteProvider.cpp index a9824fafe98be85aa33227935b573a09f3ff07bb..f00416f723d7d76541715a02c43a1df2e8d9acfc 100644 --- a/src/editor/SpriteProvider.cpp +++ b/src/editor/SpriteProvider.cpp @@ -36,19 +36,64 @@ void SpriteProvider::initialize(const std::string& path) return; } + /** + * Provides all terrain tile names. + * They are necessary to create the correct path to + * the hdf5 dataset that contains the pixels for a given terrain tile. + */ + const std::vector<std::string> tileNames( + {"plain", + "water", + "forest", + "mountain", + "bridge_horizontal", + "bridge_vertical", + "street_horizontal", + "street_vertical", + "street_crossing", + "street_junction_right", + "street_junction_left", + "street_junction_down", + "street_junction_up", + "street_corner_top_left", + "street_corner_top_right", + "street_corner_bottom_left", + "street_corner_bottom_right", + "riff", + "cliff_top", + "cliff_bottom", + "cliff_left", + "cliff_right", + "cliff_corner_top_left", + "cliff_corner_top_right", + "cliff_corner_bottom_left", + "cliff_corner_bottom_right", + "cliff_inverse_corner_top_left", + "cliff_inverse_corner_top_right", + "cliff_inverse_corner_bottom_left", + "cliff_inverse_corner_bottom_right"}); + + /** + * Provides the names for all factions in the game. + * The names can be used to create the path to the hdf5 dataset + * containing the buildings for a given faction. + */ + const std::vector<std::string> factionNames( + {"red", "blue", "green", "yellow", "purple", "neutral"}); + HighFive::File file(path, HighFive::File::ReadOnly); sprites.reserve(60); // we now that we will load 60 sprites // load terrains - for (size_t i = 0; i < TILE_NAMES.size(); i++) + for (size_t i = 0; i < tileNames.size(); i++) { // size_t? std::vector<std::vector<std::vector<uint32_t>>> pixels; - file.getDataSet("tiles/" + TILE_NAMES[i]).read(pixels); + file.getDataSet("tiles/" + tileNames[i]).read(pixels); sprites.push_back(SpriteProvider::loadPixmap(pixels, 0)); } // load buildings - for (const std::string& factionName : FACTION_NAMES) + for (const std::string& factionName : factionNames) { std::vector<std::vector<std::vector<uint32_t>>> pixels; file.getDataSet("buildings/" + factionName).read(pixels); diff --git a/src/editor/SpriteProvider.hpp b/src/editor/SpriteProvider.hpp index 6b8cfb1b6901ae79194372707d1acfaf625d4959..0c32d7a3a389c8ef9c5808b71249052dd7f547df 100644 --- a/src/editor/SpriteProvider.hpp +++ b/src/editor/SpriteProvider.hpp @@ -15,51 +15,6 @@ namespace editor { -/** - * Provides all tile names. - * They are necessary to create the correct path to - * the hdf5 dataset that contains the pixels for a given tile. - */ -const std::vector<std::string> TILE_NAMES( - {"plain", - "water", - "forest", - "mountain", - "bridge_horizontal", - "bridge_vertical", - "street_horizontal", - "street_vertical", - "street_crossing", - "street_junction_right", - "street_junction_left", - "street_junction_down", - "street_junction_up", - "street_corner_top_left", - "street_corner_top_right", - "street_corner_bottom_left", - "street_corner_bottom_right", - "riff", - "cliff_top", - "cliff_bottom", - "cliff_left", - "cliff_right", - "cliff_corner_top_left", - "cliff_corner_top_right", - "cliff_corner_bottom_left", - "cliff_corner_bottom_right", - "cliff_inverse_corner_top_left", - "cliff_inverse_corner_top_right", - "cliff_inverse_corner_bottom_left", - "cliff_inverse_corner_bottom_right"}); - -/** - * Provides the names for all factions in the game. - * The names can be used to create the path to the hdf5 dataset - * containing the buildings for a given faction. - */ -const std::vector<std::string> - FACTION_NAMES({"red", "blue", "yellow", "green", "purple", "neutral"}); - /** * SpriteProvider is only used in a static context. * SpriteProvider allows us to get the QPixmap for a given tile type diff --git a/src/editor/Tile.hpp b/src/editor/Tile.hpp index c8429458d281d7655cd41df8670875c66b25edb5..c108f96de42afcb896af3dddc66622ec32d1c7f4 100644 --- a/src/editor/Tile.hpp +++ b/src/editor/Tile.hpp @@ -1,9 +1,9 @@ /** -* Tile.hpp -* -* @date 27.01.2025 -* @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) -*/ + * Tile.hpp + * + * @date 27.01.2025 + * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) + */ #pragma once @@ -12,15 +12,51 @@ namespace editor { -class Tile : public QGraphicsPixmapItem { -public: - Tile(int index, uint8_t id); +/** + * The bottom (static) Layer of the LevelScene consists of Tile instances. + * The purpose of the Tile object is to notify the QGraphicsScene when the mouse + * enters/leaves/presses a specific tile of the map. + * Thus Tile subclasses QGraphicsPixmapItem and overrides hoverEnterEvent, hoverLeaveEvent + * and mousePressEvent. + * Every Tile is instantiated with its index so that it can send that index to the LevelScene + * in case it registers a hover/enter/press event. + */ +class Tile : public QGraphicsPixmapItem +{ + public: + /** + * Creates a Tile instance for the given index with the given tile id. + * @param index The index of the tile if you were to linearize the level map. + * @param id The tile id, e.g. 0 -> grass, 1-> water, ... + */ + Tile(int index, uint8_t id); + + private: + /** + * When a hover enter event is received it + * emits the onTileEntered event method with the tile's index. + * The LevelScene overwrites the onTileEntered event method. + */ + void hoverEnterEvent(QGraphicsSceneHoverEvent*) override; + + /** + * When a hover leave event is received it + * emits the onTileExited event method with the tile's index. + * The LevelScene overwrites the onTileExited event method. + */ + void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; + + /** + * When a mouse press event is received it + * emits the onTileClicked event method with the tile's index. + * The LevelScene overwrites the onTileClicked event method. + */ + void mousePressEvent(QGraphicsSceneMouseEvent*) override; -private: - void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; - void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; - void mousePressEvent(QGraphicsSceneMouseEvent *event) override; - int m_index; + /** + * The index of the tile if you were to linearize the level map. + */ + int m_index; }; } // namespace editor \ No newline at end of file diff --git a/src/editor/TileButton.cpp b/src/editor/TileButton.cpp index 731c6172cf5fed3e1680074e8d6a3a28d733115e..7ff5e3445f57b89a686ae22f89eb2f45bfb19e71 100644 --- a/src/editor/TileButton.cpp +++ b/src/editor/TileButton.cpp @@ -1,5 +1,5 @@ /** - * TileSelector.cpp + * TileButton.cpp * * @date 29.01.2025 * @author Nils Jonathan Friedrich Eckardt implementation diff --git a/src/editor/TileButton.hpp b/src/editor/TileButton.hpp index 52e1fca4341ba856f5be3a7977c07dcfd3f5b7b2..488db9be4ca1c749be8c7b383aad7665171663a1 100644 --- a/src/editor/TileButton.hpp +++ b/src/editor/TileButton.hpp @@ -1,10 +1,10 @@ /** -* TileSelector.hpp -* -* @date 29.01.2025 -* @author Nils Jonathan Friedrich Eckardt implementation -* @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) minor changes -*/ + * TileButton.hpp + * + * @date 29.01.2025 + * @author Nils Jonathan Friedrich Eckardt implementation + * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) minor changes + */ #pragma once @@ -12,14 +12,33 @@ namespace editor { +/** + * TileButtons are the Selection Buttons within the TileSelector. + * They listen for a mouse press and emit the onNewTileSelected event methode + * and transmits with it the selected id of the selected tile. + */ +class TileButton : public QPushButton +{ + public: + /** + * Creates a TileButton with the texture of a tile and the provided parent. + * @param id The id of the tile on this button. + * @param parent The parent to set for this button. + */ + TileButton(const uint8_t id, QWidget* parent = nullptr); + + protected: + /** + * When a mouse press event is received, + * it emits the onNewTileIdSelected event with its tile's id. + */ + void mousePressEvent(QMouseEvent* event) override; -class TileButton : public QPushButton { -public: - TileButton(const uint8_t id, QWidget *parent = nullptr); -protected: - void mousePressEvent(QMouseEvent* event) override; -private: - uint8_t m_id; + private: + /** + * The id of this button. + */ + uint8_t m_id; }; } // namespace editor \ No newline at end of file diff --git a/src/editor/TileSelector.cpp b/src/editor/TileSelector.cpp index 1dcbe7bc7bbeb82cb15f5ca7f0a17c9142abcbbb..26a1e33d47ca2d8e4b409eeacd381fd5fbbd27e1 100644 --- a/src/editor/TileSelector.cpp +++ b/src/editor/TileSelector.cpp @@ -61,11 +61,13 @@ QLabel* TileSelector::createNewLabel(QWidget* parent, const char* text) return label; } -template <typename T> -void TileSelector::sectionLayout(QGridLayout*& layout, int usedIdCounter, QWidget* parent, T id) +template <typename... T> +QGridLayout* TileSelector::creatSectionLayout(QWidget* parent, T... ids) { - TileButton* button = new TileButton(id, parent); - layout->addWidget(button, usedIdCounter / 3, usedIdCounter % 3); + QGridLayout* layout = new QGridLayout(); + int usedIdCounter = 0; + sectionLayout(layout, usedIdCounter, parent, ids...); + return layout; } template <typename T, typename... Rest> @@ -78,13 +80,11 @@ void TileSelector::sectionLayout( sectionLayout(layout, usedIdCounter, parent, ids...); } -template <typename... T> -QGridLayout* TileSelector::creatSectionLayout(QWidget* parent, T... ids) +template <typename T> +void TileSelector::sectionLayout(QGridLayout*& layout, int usedIdCounter, QWidget* parent, T id) { - QGridLayout* layout = new QGridLayout(); - int usedIdCounter = 0; - sectionLayout(layout, usedIdCounter, parent, ids...); - return layout; + TileButton* button = new TileButton(id, parent); + layout->addWidget(button, usedIdCounter / 3, usedIdCounter % 3); } } // namespace editor \ No newline at end of file diff --git a/src/editor/TileSelector.hpp b/src/editor/TileSelector.hpp index 8a0d077345d3c02008439024af968346da271cd9..372c51264de6055e7b8f1325adf975bde28b35f0 100644 --- a/src/editor/TileSelector.hpp +++ b/src/editor/TileSelector.hpp @@ -2,8 +2,8 @@ * TileSelector.cpp * * @date 28.01.2025 - * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) skeleton * @author Nils Jonathan Friedrich Eckardt implementation + * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) skeleton */ #pragma once @@ -15,22 +15,67 @@ namespace editor { - +/** + * The TileSelector is part of the MainWindow. + * It displays the tiles that can be selected within categories, to be placed on the + * map of the LevelScene. + */ class TileSelector : public QScrollArea { public: + /** + * Creates the TileSelector with the provided parent. + * The appearance of the selector is hardcoded by the arguments it provides its functions. + * There should not be created more then one of these at a time. + * @param parent The parent to set for the TileSelector + */ TileSelector(QWidget* parent = nullptr); private: + /** + * Creates a label of a category to be displayed. + * @param parent The parent to set for the QLabel. + * @param text The text of the label. + */ QLabel* createNewLabel(QWidget* parent, const char* text); - - template <typename T> - void sectionLayout(QGridLayout*& layout, int usedIdCounter, QWidget* parent, T id); + /** + * Creates a section of the TileSelector. + * Before this there should always be a category label be created. + * The section contains TileButtons listed in the same order, that the ids for them + * are provided. After 3 buttons the next line is used. Reordering the TileButtons + * can simply be done by changing the order in which their ids are provided. + * @param parent The parent to set for this QGridLayout. + * @param ids A list of the ids for which TileButtons will be created. + */ + template <typename... T> + QGridLayout* creatSectionLayout(QWidget* parent, T... ids); + /** + * A recursive helper function for createSectionLayout. + * Each time it is called it adds a TileButton for the first of the provided ids to the + * QGridLayout. The id then is discarded and this function is called again with the + * remaining ids. + * @param layout A link to the QGridLayout the TileButtons are added to. + * @param usedIdCounter Contains what the number of the current used id is. Needed to + * add the TileButton to the correct position of the grid. + * @param parent The parent the TileButtons will be set to. + * @param id The current id a TileButton will be created for. + * @param ids The remaining ids for which a TileButton will be created in a future + * function call. + */ template <typename T, typename... Rest> void sectionLayout(QGridLayout*& layout, int usedIdCounter, QWidget* parent, T id, Rest... ids); - template <typename... T> - QGridLayout* creatSectionLayout(QWidget* parent, T... ids); + /** + * The end of the recursive helper function sectionLayout. + * When this is called no more but 1 id is remaining for which a TileButton will be created. + * @param layout A link to the QGridLayout the TileButtons are added to. + * @param usedIdCounter Contains what the number of the current used id is. Needed to + * add the TileButton to the correct position of the grid. + * @param parent The parent the TileButtons will be set to. + * @param id The current id a TileButton will be created for. + */ + template <typename T> + void sectionLayout(QGridLayout*& layout, int usedIdCounter, QWidget* parent, T id); }; } // namespace editor \ No newline at end of file diff --git a/src/editor/TopBar.cpp b/src/editor/TopBar.cpp index 5637b45b2b684bfb87fbf13bfe50e4c31a4d2795..41a20d5805db63507297938741cbaf0bf3231d04 100644 --- a/src/editor/TopBar.cpp +++ b/src/editor/TopBar.cpp @@ -12,7 +12,7 @@ #include <QLabel> #include <QSize> -#include "AutomateButton.hpp" +#include "AdvancedPlacementSwitch.hpp" #include "LevelNameEdit.hpp" #include "SaveButton.hpp" #include "ZoomInButton.hpp" @@ -37,12 +37,17 @@ TopBar::TopBar(const std::string& levelName, QWidget* parent) : QToolBar(parent) leftLayout->addWidget(textField); leftLayout->addStretch(); - QPushButton* saveButton = new SaveButton("Speichern", rightContainer); - QPushButton* zoomIn = new ZoomInButton(rightContainer); - QPushButton* zoomOut = new ZoomOutButton(rightContainer); - QPushButton* zoomInfo = new ZoomInfo(rightContainer); - auto* checkedBox = new AutomateButton("Kacheln automatisch setzen", container); - leftLayout->addWidget(checkedBox); + QPushButton* saveButton = new SaveButton("Speichern", rightContainer); + QPushButton* zoomIn = new ZoomInButton(rightContainer); + QPushButton* zoomOut = new ZoomOutButton(rightContainer); + QPushButton* zoomInfo = new ZoomInfo(rightContainer); + AdvancedPlacementSwitch* symmetryBox = + new AdvancedPlacementSwitch("symmetrisches Bearbeiten", false, container); + leftLayout->addWidget( + symmetryBox); // diese Reihenfolge vermeidet einen Bug, bei dem False zu true wird + AdvancedPlacementSwitch* placementBox = + new AdvancedPlacementSwitch("Kacheln automatisch setzen", true, container); + leftLayout->addWidget(placementBox); rightLayout->addStretch(); rightLayout->addWidget(zoomOut); rightLayout->addWidget(zoomInfo); diff --git a/src/editor/TopBar.hpp b/src/editor/TopBar.hpp index 6f97a49fd9a42755efd6fc4476f2e8a2805f638e..48520773bef2efa64fa2e12c600fe493176bf7aa 100644 --- a/src/editor/TopBar.hpp +++ b/src/editor/TopBar.hpp @@ -12,9 +12,26 @@ namespace editor { +/** + * The TopBar provides key functionality for the LevelEditor: + * - LineEdit for setting the level name + * - CheckBox for toggling advanced placement on and off + * - ZoomInButton + * - ZoomOutButton + * - ZoomInfo (displays current zoom percentage) + * - SaveButon + * TopBar inherits from QToolBar to ensure the QT look and fell. + */ class TopBar : public QToolBar { public: + /** + * Creates a TopBar with the provided levelName and parent. + * The levelName is needed to prefill the LineEdit in the TopBar with + * the current level name. + * @param levelName The current name of the level. + * @param parent The parent to set for the TopBar. + */ TopBar(const std::string& levelName, QWidget* parent = nullptr); }; diff --git a/src/editor/ZoomInButton.hpp b/src/editor/ZoomInButton.hpp index 99332ab07aee4f08ece57b79ee92c6beba3a02f3..daba616e5b12b200df0f6137923a45b6b08db8ba 100644 --- a/src/editor/ZoomInButton.hpp +++ b/src/editor/ZoomInButton.hpp @@ -1,9 +1,9 @@ /** -* ZoomInButton.hpp -* -* @date 02.02.2025 -* @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) -*/ + * ZoomInButton.hpp + * + * @date 02.02.2025 + * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) + */ #pragma once @@ -12,11 +12,26 @@ namespace editor { -class ZoomInButton : public QPushButton { -public: - ZoomInButton(QWidget* parent = nullptr); -private: - void mousePressEvent(QMouseEvent* event) override; +/** + * The ZoomInButton is a part of the TopBar. + * It listens to mouse presses and emits the onZoomed event method + * with a delta of 0.25 in case of a mouse press. + */ +class ZoomInButton : public QPushButton +{ + public: + /** + * Creates a ZoomInButton with the provided parent. + * @param parent The parent to set for the ZoomInButton. + */ + ZoomInButton(QWidget* parent = nullptr); + + private: + /** + * When a mouse press event is received, + * it emits the onZoomed event method with a delta of 0.25. + */ + void mousePressEvent(QMouseEvent*) override; }; } // namespace editor \ No newline at end of file diff --git a/src/editor/ZoomInfo.hpp b/src/editor/ZoomInfo.hpp index ad5a3e86186a8af9976a71313d44a7385aba7272..edf4412bba86f47df99b96862db47752893500c6 100644 --- a/src/editor/ZoomInfo.hpp +++ b/src/editor/ZoomInfo.hpp @@ -14,14 +14,28 @@ namespace editor { +/** + * ZoomInfo inherits from QPushButton, but doesn't actually act as a button. + * It just displays the current zoom percentage. + * To update the text that is displayed in case of a zoom change, it + * inherits from EventHandler and overwrites the onZoomed event method. + */ class ZoomInfo : public QPushButton, public EventHandler { public: ZoomInfo(QWidget* parent = nullptr); private: + /** + * The percentage by which the LevelMap is zoomed in/out at the moment. + */ double m_currentScale; - void onZoomed(double delta) override; + + /** + * Updates the zoom info text to correctly display the updated zoom percentage. + * @param delta The percentage to add/subtract to the current zoom percentage. + */ + void onZoomed(double delta) override; }; } // namespace editor \ No newline at end of file diff --git a/src/editor/ZoomOutButton.hpp b/src/editor/ZoomOutButton.hpp index e13c570350677e64b95aa6eddbd3c20a94d4e137..886fb1bf50f9668e9ea136a99428bfa8242f44e5 100644 --- a/src/editor/ZoomOutButton.hpp +++ b/src/editor/ZoomOutButton.hpp @@ -1,9 +1,9 @@ /** -* ZoomOutButton.hpp -* -* @date 02.02.2025 -* @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) -*/ + * ZoomOutButton.hpp + * + * @date 02.02.2025 + * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) + */ #pragma once @@ -12,11 +12,26 @@ namespace editor { -class ZoomOutButton : public QPushButton { -public: - ZoomOutButton(QWidget* parent = nullptr); -private: - void mousePressEvent(QMouseEvent* event) override; +/** + * The ZoomOutButton is a part of the TopBar. + * It listens to mouse presses and emits the onZoomed event method + * with a delta of -0.25 in case of a mouse press. + */ +class ZoomOutButton : public QPushButton +{ + public: + /** + * Creates a ZoomInButton with the provided parent. + * @param parent The parent to set for the ZoomInButton. + */ + ZoomOutButton(QWidget* parent = nullptr); + + private: + /** + * When a mouse press event is received, + * it emits the onZoomed event method with a delta of 0.25. + */ + void mousePressEvent(QMouseEvent*) override; }; } // namespace editor \ No newline at end of file diff --git a/src/editor/main.cpp b/src/editor/main.cpp index d845619992526d555d917c44c879ccd566a57b84..adc6ab49f8705578ad2ccdcfba806ddaea724917 100644 --- a/src/editor/main.cpp +++ b/src/editor/main.cpp @@ -6,54 +6,192 @@ */ #include <QApplication> +#include <QCommandLineParser> +#include <filesystem> #include "LevelScene.hpp" #include "MainWindow.hpp" #include "SpriteProvider.hpp" +#include "highfive/H5File.hpp" using namespace editor; /** - * The main function starts the program. - * It checks if any command line arguments were provided. - * If not it terminates. - * Otherwise if a path to a hdf5 level file was provided it - * starts the level editor and loads the level from the file for editing. - * If a width and height was provided, it create a blank level with the given dimensions. + * For launching the level editor, we implemented a full fledged command line application + * with the help of QCommandLineParser. + * The command line application that is used for launching the level editor supports command like: + * ./editor --help + * ./editor --version + * ./editor --width + * ./editor --width 20 --height 20 + * ./editor --edit ../res/level-file-to-edit.h5 */ -int main(int argc, char* argv[]) +/** + * Holds the launch options for the level editor. + * They are constructed from the provided command line options + * in the loadLevelOptions function. + */ +struct LevelOptions { - if (argc < 2) - { // no arguments provided - std::cerr << "Bitte uebergben Sie den Pfad zu dem Level, das sie bearbeiten wollen oder " - "die Breite und Hoehe des Levels, das sie neu erstellen wollen." - << "\n"; - return 1; + int width; + int height; + std::string levelFilepath; +}; + +/** + * loadLevelOptions implements a full fledged command line application for retrieving the + * level options. + * For that purpose it creates a QCoreApplication and QCommandLineParser object. + * QCoreApplication is just used for loading the level options. + * It is destroyed at function exit. + * Through QCommandLineParser we support commands like --help, --version + * loadLevelOptions also makes sure that the optionally provided arguments for width, height and + * level h5 file path are valid. + * All of these arguments have default values: width/height: 20, level h5 file path: '' + * @param argc The number of command line arguments provided by the user. + * @param argv All of the command line arguments provided by the user. + * @returns If the user provided options are valid, they will be returned inside an optional, + * else an empty optional will be returned. + */ +std::optional<LevelOptions> loadLevelOptions(int argc, char* argv[]) +{ + // Sperately from the QApplication we use for the Level Editor UI, we use QCoreApplication for + // the command line application for launching the editor. + // The reason is that using QApplication is very slow, because it has to initialize all of the + // internal UI stuff. That is not what you want if only a simple ./editor --help was executed. + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationVersion("1.0.0"); + QCommandLineParser parser; + + // application description with examples for help output of ./editor --help + std::string topSection = R"(QT Level Editor for creating/editing Maps of Advanced Wars. + +Examples: + ./editor Create new level map (default 20x20). + ./editor --width 15 --height 15 Create new level map (15x15). + ./editor --edit ./level.h5 Edit existing level map.)"; + + parser.addVersionOption(); + parser.addHelpOption(); + parser.setApplicationDescription(topSection.c_str()); + parser.addOption(QCommandLineOption( + "width", "The width of the new level map (default 20, minimum: 3).", "number-of-tiles", + "20")); + parser.addOption(QCommandLineOption( + "height", "The height of the new level map (default 20, minimum: 3).", "number-of-tiles", + "20")); + parser.addOption(QCommandLineOption( + "edit", + "The path to the HDF5 level map file that you want to edit. If edit is not set, a new " + "level map is created.", + "filepath")); + parser.process(app); + + // QT Parser does not exit when unexpected positional arguments are provided, + // so have to do it by hand. 0 positional arguments are expected. + if (parser.positionalArguments().size() > 0) + { + std::cerr << "Command not recognized.\n" << std::endl; + parser.showHelp(1); // shows the help info and quits the application with exit code 1 + return std::nullopt; + } + + // make sure that provided with, height and level file path are valid + // if yes add them to the level options struct + bool widthOk; + bool heightOk; + LevelOptions options; + options.width = parser.value("width").toUInt(&widthOk); + options.height = parser.value("height").toUInt(&heightOk); + if (!widthOk || options.width < 3) + { + std::cerr << "You provided an invalid level width: '" << parser.value("width").toStdString() + << "'\nThe level width has to be a valid number >= 3." << std::endl; + return std::nullopt; } - if (argc > 3) - { // more than 2 arguments provided - std::cerr << "Zuviele Kommandozeilenargumente." << "\n"; + if (!heightOk || options.height < 3) + { + std::cerr << "You provided an invalid level height: '" + << parser.value("height").toStdString() + << "'\nThe level height has to be a valid number >= 3." << std::endl; + return std::nullopt; + } + options.levelFilepath = parser.value("edit").toStdString(); + if (!options.levelFilepath.empty() && !std::filesystem::exists(options.levelFilepath)) + { + std::cerr << "The level map file path: '" << options.levelFilepath << "' does not exist." + << std::endl; + return std::nullopt; + } + + return options; +} + +int main(int argc, char* argv[]) +{ + std::optional<LevelOptions> levelOptions = loadLevelOptions(argc, argv); + if (!levelOptions.has_value()) + { return 1; } - // programm starts QApplication app(argc, argv); // SpriteProvider is initialized once and can then be used from anywhere in the application // to get the QGraphicsPixmap for a specific tile. - SpriteProvider::initialize("../res/spritesheet.h5"); + if (!std::filesystem::exists("../res/spritesheet.h5")) + { + std::cerr << "The spritesheet was not found.\nIt was expected in: ../res/spritesheet.h5" + << std::endl; + return 1; + } + try + { + SpriteProvider::initialize("../res/spritesheet.h5"); + } + catch (const HighFive::Exception&) + { + std::cerr << "Loading the spritesheet failed.\nPlease make sure that it is not " + "corrupted.\nIt was loaded from: ../res/spritesheet.h5" + << std::endl; + return 1; + } - LevelScene* level = nullptr; - if (argc == 2) - { // 1 argument provided => create Level from file - level = LevelScene::fromFile(argv[1]); + // initialize LevelScene + LevelScene* level; + if (!levelOptions.value().levelFilepath.empty()) + { + try + { + level = LevelScene::fromFile(levelOptions.value().levelFilepath); + } + catch (const HighFive::Exception&) + { + std::cerr << "Loading an existing level map from '" + << levelOptions.value().levelFilepath + << "' failed.\nPlease make sure that the hdf5 level file is not corrupted." + << std::endl; + return 1; + } } else - { // 2 arguments provided => create blank level with given width and height - level = LevelScene::empty("Mein Level", std::stoi(argv[1]), std::stoi(argv[2])); + { + level = + LevelScene::empty("My Level", levelOptions.value().width, levelOptions.value().height); } + // We create the MainWindow on the Stack. Thus it will be destructed + // automatically at exit. The way memory management works in QT is that + // you can set a parent on every Widget class you create. If the parent gets + // destroyed all its children are destroyed as well. It is acutally save in both + // directions. It doesn't matter if you destroy the child first and the parent later. + // In our case, because all of the widgets in our application are eventual descendants of + // MainWindow, they are all automatically cleaned up at program exit. For that reason + // memory management for the level editor is very simple. + // For more info on QT Object Trees & Ownership + // @see https://doc.qt.io/qt-5/objecttrees.html + MainWindow window(level); window.resize(1300, 800); window.show(); diff --git a/src/game/level/Level.cpp b/src/game/level/Level.cpp index 5bde2defb45e51a55c3f773d3ace3318b995c993..99cee073e13235cebed06e352633ee8273e09e6a 100644 --- a/src/game/level/Level.cpp +++ b/src/game/level/Level.cpp @@ -43,22 +43,15 @@ Level::Level(const std::string& path, Engine& engine) m_physicsEngine = std::make_unique<PhysicsEngine>(); m_combatEngine = std::make_unique<CombatEngine>(); - HighFive::File file(path, HighFive::File::ReadOnly); + HighFive::File file(path, HighFive::File::ReadOnly); + HighFive::DataSet tilesarraySet = file.getDataSet("tilesarray"); - // read level metadata - std::string levelMetadata; - file.getDataSet("metadata").read(levelMetadata); - - // read tilesarray std::vector<uint8_t> levelTilesarray; - file.getDataSet("tilesarray").read(levelTilesarray); - std::cout << "tilesarray size: " << levelTilesarray.size() << "\n"; - // extract metadata from xml - std::istringstream xmlStream(levelMetadata); - boost::property_tree::ptree pt; - boost::property_tree::read_xml(xmlStream, pt); - m_width = pt.get<int>("level.width"); - m_height = pt.get<int>("level.height"); + + tilesarraySet.getAttribute("width").read(m_width); + tilesarraySet.getAttribute("height").read(m_height); + tilesarraySet.getAttribute("name").read(m_name); + tilesarraySet.read(levelTilesarray); // if level is smaler than 20x20 surround with water tiles if (m_width < 20 || m_height < 20) diff --git a/src/util/writelevel.cpp b/src/util/writelevel.cpp deleted file mode 100644 index a4c8c249aa5623cfdad9cac9e732361e41b2f7f1..0000000000000000000000000000000000000000 --- a/src/util/writelevel.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include <boost/property_tree/ptree.hpp> -#include <boost/property_tree/xml_parser.hpp> -#include <highfive/H5File.hpp> - -int main() -{ - boost::property_tree::ptree pt; - - // Add data to the property tree - pt.put("level.width", "15"); - pt.put("level.height", "10"); - pt.put("level.name", "Alpha"); - - std::ostringstream xmlStream; - boost::property_tree::write_xml(xmlStream, pt); - std::string xml_data = xmlStream.str(); - std::vector<uint8_t> levelmap = { - 2, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 1, 0, 0, 0, 0, 2, 3, 0, 0, 0, 3, 3, 3, 3, - 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 76, - 76, 51, 3, 0, 0, 0, 1, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 57, 1, 3, 0, 0, - 50, 0, 0, 0, 76, 77, 0, 3, 0, 0, 0, 1, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, - 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 1, 0, 3, 3, 2, 3, 3, 0, 0, - 0, 0, 0, 0, 0, 0, 4, 0, 0, 2, 3, 3, 3, 3, 0, 3, 2, 2, 3, 2, 3, 1, 3, 2, 2, - }; - - HighFive::File file("level.h5", HighFive::File::Truncate); - file.createDataSet<std::string>("metadata", HighFive::DataSpace::From(xmlStream)) - .write(xml_data); - file.createDataSet<uint8_t>("tilesarray", HighFive::DataSpace::From(levelmap)).write(levelmap); -}