diff --git a/trlevel/tr_lights.cpp b/trlevel/tr_lights.cpp index f6efb67aa..b962f90ff 100644 --- a/trlevel/tr_lights.cpp +++ b/trlevel/tr_lights.cpp @@ -260,7 +260,7 @@ namespace trlevel return 0; } - std::string light_type_name(LightType type) + std::string to_string(LightType type) { switch (type) { diff --git a/trlevel/tr_lights.h b/trlevel/tr_lights.h index 22aa55a3d..eb4509d29 100644 --- a/trlevel/tr_lights.h +++ b/trlevel/tr_lights.h @@ -36,7 +36,7 @@ namespace trlevel float density() const; }; - std::string light_type_name(LightType type); + std::string to_string(LightType type); std::vector convert_lights(std::vector lights); std::vector convert_lights(std::vector lights); diff --git a/trview.app.tests/ApplicationTests.cpp b/trview.app.tests/ApplicationTests.cpp index c71770cae..9b690ed24 100644 --- a/trview.app.tests/ApplicationTests.cpp +++ b/trview.app.tests/ApplicationTests.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include "NullImGuiBackend.h" #include @@ -29,7 +28,6 @@ using namespace trview; using namespace trview::tests; using namespace testing; using namespace trview::mocks; -using namespace trlevel::mocks; using testing::_; namespace @@ -55,7 +53,6 @@ namespace Window window{ create_test_window(L"ApplicationTests") }; std::unique_ptr update_checker{ mock_unique() }; std::unique_ptr settings_loader{ mock_unique() }; - trlevel::ILevel::Source trlevel_source{ [](auto&&...) { return mock_unique(); } }; std::unique_ptr file_menu{ mock_unique() }; std::unique_ptr viewer{ mock_unique() }; IRoute::Source route_source{ [](auto&&...) { return mock_shared(); } }; @@ -73,7 +70,7 @@ namespace std::unique_ptr build() { EXPECT_CALL(*shortcuts, add_shortcut).WillRepeatedly([&](auto, auto) -> Event<>&{ return shortcut_handler; }); - return std::make_unique(window, std::move(update_checker), std::move(settings_loader), trlevel_source, + return std::make_unique(window, std::move(update_checker), std::move(settings_loader), std::move(file_menu), std::move(viewer), route_source, shortcuts, level_source, startup_options, dialogs, files, std::move(imgui_backend), plugins, randomizer_route_source, fonts, std::move(windows), Application::LoadMode::Sync); } @@ -84,12 +81,6 @@ namespace return *this; } - test_module& with_trlevel_source(trlevel::ILevel::Source trlevel_source) - { - this->trlevel_source = trlevel_source; - return *this; - } - test_module& with_file_menu(std::unique_ptr file_menu) { this->file_menu = std::move(file_menu); @@ -191,8 +182,8 @@ TEST(Application, LevelLoadedOnFileOpen) { auto [file_menu_ptr, file_menu] = create_mock(); std::optional called; - auto trlevel_source = [&](auto&& filename)-> std::unique_ptr { called = filename; throw std::exception(); }; - auto application = register_test_module().with_trlevel_source(trlevel_source).with_file_menu(std::move(file_menu_ptr)).build(); + auto level_source = [&](auto&& filename, auto&&)-> std::unique_ptr { called = filename; throw std::exception(); }; + auto application = register_test_module().with_level_source(level_source).with_file_menu(std::move(file_menu_ptr)).build(); file_menu.on_file_open("test_path.tr2"); ASSERT_TRUE(called.has_value()); ASSERT_EQ(called.value(), "test_path.tr2"); @@ -204,8 +195,8 @@ TEST(Application, RecentFilesUpdatedOnFileOpen) EXPECT_CALL(file_menu, set_recent_files(std::list{})).Times(1); EXPECT_CALL(file_menu, set_recent_files(std::list{"test_path.tr2"})).Times(1); std::optional called; - auto trlevel_source = [&](auto&& filename) { called = filename; return mock_unique(); }; - auto application = register_test_module().with_trlevel_source(trlevel_source).with_file_menu(std::move(file_menu_ptr)).build(); + auto level_source = [&](auto&& filename, auto&&) { called = filename; return mock_unique(); }; + auto application = register_test_module().with_level_source(level_source).with_file_menu(std::move(file_menu_ptr)).build(); application->open("test_path.tr2", ILevel::OpenMode::Full); ASSERT_TRUE(called.has_value()); ASSERT_EQ(called.value(), "test_path.tr2"); @@ -215,9 +206,9 @@ TEST(Application, FileOpenedInViewer) { auto [viewer_ptr, viewer] = create_mock(); std::optional called; - auto trlevel_source = [&](auto&& filename) { called = filename; return mock_unique(); }; + auto level_source = [&](auto&& filename, auto&&) { called = filename; return mock_unique(); }; EXPECT_CALL(viewer, open(A&>(), ILevel::OpenMode::Full)).Times(1); - auto application = register_test_module().with_trlevel_source(trlevel_source).with_viewer(std::move(viewer_ptr)).build(); + auto application = register_test_module().with_level_source(level_source).with_viewer(std::move(viewer_ptr)).build(); application->open("test_path.tr2", ILevel::OpenMode::Full); ASSERT_TRUE(called.has_value()); ASSERT_EQ(called.value(), "test_path.tr2"); @@ -226,7 +217,7 @@ TEST(Application, FileOpenedInViewer) TEST(Application, WindowContentsResetBeforeViewerLoaded) { std::optional called; - auto trlevel_source = [&](auto&& filename) { called = filename; return mock_unique(); }; + auto level_source = [&](auto&& filename, auto&&) { called = filename; return mock_unique(); }; auto [viewer_ptr, viewer] = create_mock(); auto [windows_ptr, windows] = create_mock(); auto route = mock_shared(); @@ -239,7 +230,7 @@ TEST(Application, WindowContentsResetBeforeViewerLoaded) EXPECT_CALL(viewer, open(A&>(), ILevel::OpenMode::Full)).Times(1).WillOnce([&](auto&&...) { events.push_back("viewer"); }); auto application = register_test_module() - .with_trlevel_source(trlevel_source) + .with_level_source(level_source) .with_viewer(std::move(viewer_ptr)) .with_route_source([&](auto&&...) {return route; }) .with_windows(std::move(windows_ptr)) @@ -273,10 +264,10 @@ TEST(Application, FileOpenedFromCommandLine) auto startup_options = mock_shared(); ON_CALL(*startup_options, filename).WillByDefault(testing::Return("test.tr2")); std::optional called; - auto trlevel_source = [&](auto&& filename) { called = filename; return mock_unique(); }; + auto level_source = [&](auto&& filename, auto&&) { called = filename; return mock_unique(); }; auto [viewer_ptr, viewer] = create_mock(); EXPECT_CALL(viewer, open).Times(1); - auto application = register_test_module().with_trlevel_source(trlevel_source).with_viewer(std::move(viewer_ptr)).with_startup_options(startup_options).build(); + auto application = register_test_module().with_level_source(level_source).with_viewer(std::move(viewer_ptr)).with_startup_options(startup_options).build(); ASSERT_TRUE(called.has_value()); ASSERT_EQ(called.value(), "test.tr2"); } @@ -284,10 +275,10 @@ TEST(Application, FileOpenedFromCommandLine) TEST(Application, FileNotOpenedWhenNotSpecified) { int32_t times_called = 0; - auto trlevel_source = [&](auto&&...) { ++times_called; return mock_unique(); }; + auto level_source = [&](auto&&...) { ++times_called; return mock_unique(); }; auto [viewer_ptr, viewer] = create_mock(); EXPECT_CALL(viewer, open).Times(0); - auto application = register_test_module().with_trlevel_source(trlevel_source).with_viewer(std::move(viewer_ptr)).build(); + auto application = register_test_module().with_level_source(level_source).with_viewer(std::move(viewer_ptr)).build(); ASSERT_EQ(times_called, 0); } @@ -326,13 +317,13 @@ TEST(Application, DialogShownOnOpenWithUnsavedRouteBlocksOpen) auto dialogs = mock_shared(); auto route_ptr_actual = std::move(route_ptr); int32_t times_called = 0; - auto trlevel_source = [&](auto&&...) { ++times_called; return mock_unique(); }; + auto level_source = [&](auto&&...) { ++times_called; return mock_unique(); }; EXPECT_CALL(route, is_unsaved).WillRepeatedly(Return(true)); auto application = register_test_module() .with_route_source([&](auto&&...) {return std::move(route_ptr_actual); }) .with_dialogs(dialogs) - .with_trlevel_source(trlevel_source) + .with_level_source(level_source) .build(); application->open("", ILevel::OpenMode::Full); @@ -345,14 +336,14 @@ TEST(Application, DialogShownOnOpenWithUnsavedRouteAllowsOpen) auto dialogs = mock_shared(); auto route_ptr_actual = std::move(route_ptr); int32_t times_called = 0; - auto trlevel_source = [&](auto&&...) { ++times_called; return mock_unique(); }; + auto level_source = [&](auto&&...) { ++times_called; return mock_unique(); }; EXPECT_CALL(route, is_unsaved).WillRepeatedly(Return(true)); EXPECT_CALL(*dialogs, message_box).WillRepeatedly(Return(true)); auto application = register_test_module() .with_route_source([&](auto&&...) {return std::move(route_ptr_actual); }) .with_dialogs(dialogs) - .with_trlevel_source(trlevel_source) + .with_level_source(level_source) .build(); application->open("", ILevel::OpenMode::Full); @@ -516,15 +507,14 @@ TEST(Application, LevelLoadedOnReload) { auto [file_menu_ptr, file_menu] = create_mock(); std::vector called; - auto trlevel_source = [&](auto&& filename)-> std::unique_ptr { called.push_back(filename); return mock_unique(); }; - ILevel::Source level_source = [](auto&&...) + ILevel::Source level_source = [&](auto&& filename, auto&&...) { + called.push_back(filename); auto [level_ptr, level] = create_mock(); ON_CALL(level, filename).WillByDefault(Return("reload.tr2")); return std::move(level_ptr); }; auto application = register_test_module() - .with_trlevel_source(trlevel_source) .with_level_source(level_source) .with_file_menu(std::move(file_menu_ptr)) .build(); @@ -542,8 +532,8 @@ TEST(Application, ReloadSyncsProperties) std::vector called; - auto [original_ptr, original] = create_mock(); - auto [reloaded_ptr, reloaded] = create_mock(); + auto original = mock_shared(); + auto reloaded = mock_shared(); std::vector> items; for (int i = 0; i < 5; ++i) @@ -551,70 +541,79 @@ TEST(Application, ReloadSyncsProperties) items.push_back(mock_shared()->with_number(i)); } std::vector> items_weak{ std::from_range, items }; - + + std::vector> items_reloaded; + for (int i = 0; i < 5; ++i) + { + items_reloaded.push_back(mock_shared()->with_number(i)->with_level(reloaded)); + } + std::vector> items_weak_reloaded{ std::from_range, items_reloaded }; + std::vector> triggers; for (int i = 0; i < 5; ++i) { - auto trigger = mock_shared(); - ON_CALL(*trigger, number).WillByDefault(Return(i)); - triggers.push_back(trigger); + triggers.push_back(mock_shared()->with_number(i)); } - std::vector> triggers_weak; - for (const auto& t : triggers) + std::vector> triggers_weak{ std::from_range, triggers }; + + std::vector> triggers_reloaded; + for (int i = 0; i < 5; ++i) { - triggers_weak.push_back(t); + triggers_reloaded.push_back(mock_shared()->with_number(i)->with_level(reloaded)); } + std::vector> triggers_weak_reloaded{ std::from_range, triggers_reloaded }; std::vector> lights; for (int i = 0; i < 5; ++i) { - auto light = mock_shared(); - ON_CALL(*light, number).WillByDefault(Return(i)); - lights.push_back(light); + lights.push_back(mock_shared()->with_number(i)); } - std::vector> lights_weak; - for (const auto& l : lights) + std::vector> lights_weak{ std::from_range, lights }; + + std::vector> lights_reloaded; + for (int i = 0; i < 5; ++i) { - lights_weak.push_back(l); + lights_reloaded.push_back(mock_shared()->with_number(i)->with_level(reloaded)); } + std::vector> lights_weak_reloaded{ std::from_range, lights_reloaded }; auto original_room = mock_shared()->with_number(3); - auto room = mock_shared()->with_number(3); - - ON_CALL(original, items).WillByDefault(Return(items_weak)); - ON_CALL(original, selected_item).WillByDefault(Return(3)); - ON_CALL(original, triggers).WillByDefault(Return(triggers_weak)); - ON_CALL(original, selected_trigger).WillByDefault(Return(3)); - ON_CALL(original, lights).WillByDefault(Return(lights_weak)); - ON_CALL(original, selected_light).WillByDefault(Return(3)); - ON_CALL(original, number_of_rooms).WillByDefault(Return(5)); - ON_CALL(original, selected_room).WillByDefault(Return(original_room)); - - ON_CALL(reloaded, items).WillByDefault(Return(items_weak)); + auto room = mock_shared()->with_number(3)->with_level(reloaded); + + ON_CALL(*original, items).WillByDefault(Return(items_weak)); + ON_CALL(*original, selected_item).WillByDefault(Return(3)); + ON_CALL(*original, triggers).WillByDefault(Return(triggers_weak)); + ON_CALL(*original, selected_trigger).WillByDefault(Return(3)); + ON_CALL(*original, lights).WillByDefault(Return(lights_weak)); + ON_CALL(*original, selected_light).WillByDefault(Return(3)); + ON_CALL(*original, number_of_rooms).WillByDefault(Return(5)); + ON_CALL(*original, selected_room).WillByDefault(Return(original_room)); + + ON_CALL(*reloaded, items).WillByDefault(Return(items_weak_reloaded)); const auto item_matcher = [](auto r) { return std::get<0>(r).lock(); }; - EXPECT_CALL(reloaded, set_selected_item).With(ResultOf(item_matcher, Eq(items[3]))).Times(1); - ON_CALL(reloaded, triggers).WillByDefault(Return(triggers_weak)); - EXPECT_CALL(reloaded, set_selected_trigger(3)).Times(1); - ON_CALL(reloaded, lights).WillByDefault(Return(lights_weak)); - EXPECT_CALL(reloaded, set_selected_light(3)).Times(1); - ON_CALL(reloaded, number_of_rooms).WillByDefault(Return(5)); + EXPECT_CALL(*reloaded, set_selected_item).With(ResultOf(item_matcher, Eq(items_reloaded[3]))).Times(1); + ON_CALL(*reloaded, triggers).WillByDefault(Return(triggers_weak_reloaded)); + EXPECT_CALL(*reloaded, set_selected_trigger(3)).Times(1); + ON_CALL(*reloaded, lights).WillByDefault(Return(lights_weak_reloaded)); + EXPECT_CALL(*reloaded, set_selected_light(3)).Times(1); + ON_CALL(*reloaded, number_of_rooms).WillByDefault(Return(5)); const auto matcher = [](auto r) { return std::get<0>(r).lock(); }; - EXPECT_CALL(reloaded, set_selected_room).With(ResultOf(matcher, Eq(std::shared_ptr{}))).Times(AnyNumber()); - EXPECT_CALL(reloaded, set_selected_room).With(ResultOf(matcher, Eq(room))).Times(1); - EXPECT_CALL(reloaded, trigger).WillRepeatedly(Return(triggers_weak[3])); - EXPECT_CALL(reloaded, light).WillRepeatedly(Return(lights_weak[3])); - EXPECT_CALL(reloaded, item).WillRepeatedly(Return(items[3])); - EXPECT_CALL(reloaded, room(3)).WillRepeatedly(Return(room)); + EXPECT_CALL(*reloaded, set_selected_room).With(ResultOf(matcher, Eq(std::shared_ptr{}))).Times(AnyNumber()); + EXPECT_CALL(*reloaded, set_selected_room).With(ResultOf(matcher, Eq(room))).Times(1); + EXPECT_CALL(*reloaded, trigger).WillRepeatedly(Return(triggers_weak_reloaded[3])); + EXPECT_CALL(*reloaded, light).WillRepeatedly(Return(lights_weak_reloaded[3] )); + EXPECT_CALL(*reloaded, item).WillRepeatedly(Return(items_reloaded[3])); + EXPECT_CALL(*reloaded, room(3)).WillRepeatedly(Return(room)); - std::list> levels; - levels.push_back(std::move(original_ptr)); - levels.push_back(std::move(reloaded_ptr)); + std::list> levels; + levels.push_back(original); + levels.push_back(reloaded); ILevel::Source level_source = [&](auto&&...) { - auto level = std::move(levels.front()); + auto level = levels.front(); levels.pop_front(); - return std::move(level); + return level; }; auto [viewer_ptr, viewer] = create_mock(); @@ -775,43 +774,51 @@ TEST(Application, SceneChangedUpdatesViewer) TEST(Application, CameraSinkSelectedEventForwarded) { auto [windows_ptr, windows] = create_mock(); - auto [level_ptr, level] = create_mock(); - EXPECT_CALL(level, set_selected_camera_sink).Times(1); + auto level = mock_shared(); + EXPECT_CALL(*level, set_selected_camera_sink).Times(1); + + auto camera_sink = mock_shared(); + ON_CALL(*camera_sink, level).WillByDefault(Return(level)); auto application = register_test_module() .with_windows(std::move(windows_ptr)) - .with_level_source([&](auto&&...) { return std::move(level_ptr); }) + .with_level_source([&](auto&&...) { return level; }) .build(); application->open("test_path.tr2", ILevel::OpenMode::Full); - windows.on_camera_sink_selected(mock_shared()); + windows.on_camera_sink_selected(camera_sink); } TEST(Application, TriggerSelectedFromWindows) { auto [windows_ptr, windows] = create_mock(); - auto [level_ptr, level] = create_mock(); - EXPECT_CALL(level, set_selected_trigger).Times(1); + auto level = mock_shared(); + EXPECT_CALL(*level, set_selected_trigger).Times(1); + + auto trigger = mock_shared(); + ON_CALL(*trigger, level).WillByDefault(Return(level)); auto application = register_test_module() .with_windows(std::move(windows_ptr)) - .with_level_source([&](auto&&...) { return std::move(level_ptr); }) + .with_level_source([&](auto&&...) { return level; }) .build(); application->open("test_path.tr2", ILevel::OpenMode::Full); - windows.on_trigger_selected(mock_shared()); + windows.on_trigger_selected(trigger); } TEST(Application, CameraSinkSelectedFromWindows) { - auto sink = mock_shared(); auto [windows_ptr, windows] = create_mock(); - auto [level_ptr, level] = create_mock(); - EXPECT_CALL(level, set_selected_camera_sink).Times(1); + auto level = mock_shared(); + EXPECT_CALL(*level, set_selected_camera_sink).Times(1); + + auto sink = mock_shared(); + ON_CALL(*sink, level).WillByDefault(Return(level)); auto application = register_test_module() .with_windows(std::move(windows_ptr)) - .with_level_source([&](auto&&...) { return std::move(level_ptr); }) + .with_level_source([&](auto&&...) { return level; }) .build(); application->open("test_path.tr2", ILevel::OpenMode::Full); @@ -1059,6 +1066,7 @@ TEST(Application, OnStaticMeshSelected) { auto level = mock_shared(); auto static_mesh = mock_shared(); + ON_CALL(*static_mesh, level).WillByDefault(Return(level)); auto [windows_ptr, windows] = create_mock(); auto [viewer_ptr, viewer] = create_mock(); auto application = register_test_module() diff --git a/trview.app.tests/Elements/RoomTests.cpp b/trview.app.tests/Elements/RoomTests.cpp index 5d85343e4..78a36bb70 100644 --- a/trview.app.tests/Elements/RoomTests.cpp +++ b/trview.app.tests/Elements/RoomTests.cpp @@ -41,7 +41,7 @@ namespace std::shared_ptr build() { auto new_room = std::make_shared(room, mesh_source, level_texture_storage, index, level); - new_room->initialise(*tr_level, room, *mesh_storage, static_mesh_source, static_mesh_position_source, sector_source, Activity(log, "Level", "Room 0")); + new_room->initialise(*tr_level, room, *mesh_storage, static_mesh_source, static_mesh_position_source, sector_source, 0, Activity(log, "Level", "Room 0")); return new_room; } diff --git a/trview.app.tests/Elements/SectorTests.cpp b/trview.app.tests/Elements/SectorTests.cpp index e634ccb02..f32f90420 100644 --- a/trview.app.tests/Elements/SectorTests.cpp +++ b/trview.app.tests/Elements/SectorTests.cpp @@ -23,7 +23,7 @@ TEST(Sector, HighNumberedPortal) tr_room_sector sector { 1, 0xffff, 255, 0, 255, 0 }; auto room = trview::tests::mock_shared(); - Sector s(level, tr_room, sector, 0, room); + Sector s(level, tr_room, sector, 0, room, 0); ASSERT_EQ(s.portal(), 378); } diff --git a/trview.app.tests/Elements/StaticMeshTests.cpp b/trview.app.tests/Elements/StaticMeshTests.cpp index 07e6dcef1..2233b2d01 100644 --- a/trview.app.tests/Elements/StaticMeshTests.cpp +++ b/trview.app.tests/Elements/StaticMeshTests.cpp @@ -16,13 +16,13 @@ TEST(StaticMesh, BoundingBoxRendered) auto bounding_mesh = mock_shared(); EXPECT_CALL(*bounding_mesh, render(A(), A(), A(), A(), A(), A(), A())).Times(1); - StaticMesh mesh({}, {}, actual_mesh, {}, bounding_mesh); + StaticMesh mesh({}, {}, actual_mesh, {}, {}, bounding_mesh); mesh.render_bounding_box(NiceMock{}, NiceMock{}, Colour::White); } TEST(StaticMesh, OnChangedRaised) { - StaticMesh mesh({}, {}, mock_shared(), {}, mock_shared()); + StaticMesh mesh({}, {}, mock_shared(), {}, {}, mock_shared()); bool raised = false; auto token = mesh.on_changed += [&] (){ raised = true; }; mesh.set_visible(false); @@ -31,14 +31,14 @@ TEST(StaticMesh, OnChangedRaised) TEST(StaticMesh, HasCollision) { - StaticMesh collision_mesh({}, { .Flags = 0 }, mock_shared(), {}, mock_shared()); + StaticMesh collision_mesh({}, { .Flags = 0 }, mock_shared(), {}, {}, mock_shared()); ASSERT_EQ(collision_mesh.has_collision(), true); - StaticMesh no_collision_mesh({}, { .Flags = 1 }, mock_shared(), {}, mock_shared()); + StaticMesh no_collision_mesh({}, { .Flags = 1 }, mock_shared(), {}, {}, mock_shared()); ASSERT_EQ(no_collision_mesh.has_collision(), false); } TEST(StaticMesh, SpriteNoCollision) { - StaticMesh collision_mesh({}, {}, {}, mock_shared(), {}); + StaticMesh collision_mesh({}, {}, {}, mock_shared(), {}, {}); ASSERT_EQ(collision_mesh.has_collision(), false); } diff --git a/trview.app.tests/Windows/WindowsTests.cpp b/trview.app.tests/Windows/WindowsTests.cpp index e21f4b6d0..0ac58e892 100644 --- a/trview.app.tests/Windows/WindowsTests.cpp +++ b/trview.app.tests/Windows/WindowsTests.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -42,7 +43,8 @@ namespace std::unique_ptr about_window{ mock_unique() }; std::unique_ptr camera_sinks{ mock_unique() }; std::unique_ptr console_manager{ mock_unique() }; - std::unique_ptr items{ mock_unique() }; + std::unique_ptr diffs{ mock_unique() }; + std::shared_ptr items{ mock_shared() }; std::unique_ptr log{ mock_unique() }; std::unique_ptr lights{ mock_unique() }; std::unique_ptr plugins{ mock_unique() }; @@ -59,7 +61,8 @@ namespace std::move(about_window), std::move(camera_sinks), std::move(console_manager), - std::move(items), + std::move(diffs), + items, std::move(lights), std::move(log), std::move(plugins), @@ -83,6 +86,12 @@ namespace return *this; } + test_module& with_diffs(std::unique_ptr manager) + { + diffs = std::move(manager); + return *this; + } + test_module& with_items(std::unique_ptr manager) { items = std::move(manager); diff --git a/trview.app/Application.cpp b/trview.app/Application.cpp index 446d66359..e2aac9dd1 100644 --- a/trview.app/Application.cpp +++ b/trview.app/Application.cpp @@ -18,6 +18,17 @@ namespace trview fonts.add_font("Default", { .name = "Arial", .filename = "arial.ttf", .size = 12 }); fonts.add_font("Console", { .name = "Consolas", .filename = "consola.ttf", .size = 12 }); } + + template + std::tuple, std::shared_ptr> get_entity_and_level(const std::weak_ptr& entity) + { + if (const auto entity_ptr = entity.lock()) + { + const auto level_ptr = entity_ptr->level().lock(); + return { entity_ptr, level_ptr }; + } + return { nullptr, nullptr }; + } } IApplication::~IApplication() @@ -27,7 +38,6 @@ namespace trview Application::Application(const Window& application_window, std::unique_ptr update_checker, std::shared_ptr settings_loader, - const trlevel::ILevel::Source& trlevel_source, std::unique_ptr file_menu, std::shared_ptr viewer, const IRoute::Source& route_source, @@ -43,8 +53,8 @@ namespace trview std::unique_ptr windows, LoadMode load_mode) : MessageHandler(application_window), _instance(GetModuleHandle(nullptr)), - _file_menu(std::move(file_menu)), _update_checker(std::move(update_checker)), _view_menu(window()), _settings_loader(settings_loader), _trlevel_source(trlevel_source), - _viewer(viewer), _route_source(route_source), _shortcuts(shortcuts), _level_source(level_source), _dialogs(dialogs), _files(files), _timer(default_time_source()), + _file_menu(std::move(file_menu)), _update_checker(std::move(update_checker)), _view_menu(window()), _settings_loader(settings_loader), _viewer(viewer), + _route_source(route_source), _shortcuts(shortcuts), _level_source(level_source), _dialogs(dialogs), _files(files), _timer(default_time_source()), _imgui_backend(std::move(imgui_backend)), _plugins(plugins), _randomizer_route_source(randomizer_route_source), _fonts(fonts), _load_mode(load_mode), _windows(std::move(windows)) { @@ -81,6 +91,29 @@ namespace trview _token_store += _windows->on_new_randomizer_route += [&]() { if (should_discard_changes()) { set_route(_randomizer_route_source(std::nullopt)); } }; _token_store += _windows->on_static_selected += [this](const auto& stat) { select_static_mesh(stat); }; _token_store += _windows->on_sound_source_selected += [this](const auto& sound) { select_sound_source(sound); }; + _token_store += _windows->on_diff_level_selected += [this](auto&& level) { open_diff_level(level); }; + _token_store += _windows->on_sector_selected += [this](auto&& sector) + { + if (const auto s = sector.lock()) + { + if (const auto r = s->room().lock()) + { + select_room(r); + _viewer->set_target(r->sector_centroid(s)); + } + } + }; + _token_store += _windows->on_settings += [this](auto&& settings) + { + _settings = settings; + _viewer->set_settings(_settings); + _windows->set_settings(settings); + lua::set_settings(settings); + if (_level) + { + _level->set_map_colours(settings.map_colours); + } + }; _windows->setup(_settings); setup_viewer(*startup_options); @@ -340,48 +373,44 @@ namespace trview void Application::select_item(std::weak_ptr item) { - if (!_level) - { - return; - } - - auto item_ptr = item.lock(); - if (!item_ptr) + const auto [item_ptr, level] = get_entity_and_level(item); + if (!item_ptr || !level) { return; } + _viewer->open(level, ILevel::OpenMode::Reload); select_room(item_ptr->room()); - _level->set_selected_item(item); + level->set_selected_item(item); _viewer->select_item(item); _windows->select(item); } void Application::select_room(std::weak_ptr room) { - if (_level) + const auto [room_ptr, level] = get_entity_and_level(room); + if (!room_ptr || !level) { - _level->set_selected_room(room); + return; } + + level->set_selected_room(room); + _viewer->open(level, ILevel::OpenMode::Reload); _viewer->select_room(room); _windows->set_room(room); } void Application::select_trigger(const std::weak_ptr& trigger) { - if (!_level) - { - return; - } - - auto trigger_ptr = trigger.lock(); - if (!trigger_ptr) + const auto [trigger_ptr, level] = get_entity_and_level(trigger); + if (!trigger_ptr || !level) { return; } + _viewer->open(level, ILevel::OpenMode::Reload); select_room(trigger_ptr->room()); - _level->set_selected_trigger(trigger_ptr->number()); + level->set_selected_trigger(trigger_ptr->number()); _viewer->select_trigger(trigger); _windows->select(trigger); } @@ -420,19 +449,15 @@ namespace trview void Application::select_light(const std::weak_ptr& light) { - if (!_level) - { - return; - } - - auto light_ptr = light.lock(); - if (!light_ptr) + const auto [light_ptr, level] = get_entity_and_level(light); + if (!light_ptr || !level) { return; } + _viewer->open(level, ILevel::OpenMode::Reload); select_room(light_ptr->room()); - _level->set_selected_light(light_ptr->number()); + level->set_selected_light(light_ptr->number()); _viewer->select_light(light); _windows->select(light); } @@ -771,19 +796,15 @@ namespace trview void Application::select_camera_sink(const std::weak_ptr& camera_sink) { - if (!_level) - { - return; - } - - auto camera_sink_ptr = camera_sink.lock(); - if (!camera_sink_ptr) + const auto [camera_sink_ptr, level] = get_entity_and_level(camera_sink); + if (!camera_sink_ptr || !level) { return; } + _viewer->open(level, ILevel::OpenMode::Reload); select_room(actual_room(*camera_sink_ptr)); - _level->set_selected_camera_sink(camera_sink_ptr->number()); + level->set_selected_camera_sink(camera_sink_ptr->number()); _viewer->select_camera_sink(camera_sink); _windows->select(camera_sink); } @@ -796,7 +817,7 @@ namespace trview std::shared_ptr Application::load(const std::string& filename) { _progress = std::format("Loading {}", filename); - auto level = _level_source(_trlevel_source(filename), + auto level = _level_source(filename, { .on_progress_callback = [&](auto&& p) { _progress = p; } }); @@ -918,17 +939,13 @@ namespace trview void Application::select_static_mesh(const std::weak_ptr& static_mesh) { - if (!_level) - { - return; - } - - auto static_mesh_ptr = static_mesh.lock(); - if (!static_mesh_ptr) + const auto [static_mesh_ptr, level] = get_entity_and_level(static_mesh); + if (!static_mesh_ptr || !level) { return; } + _viewer->open(level, ILevel::OpenMode::Reload); select_room(static_mesh_ptr->room()); _viewer->select_static_mesh(static_mesh_ptr); _windows->select(static_mesh_ptr); @@ -936,6 +953,13 @@ namespace trview void Application::select_sound_source(const std::weak_ptr& sound_source) { + const auto [sound_source_ptr, level] = get_entity_and_level(sound_source); + if (!sound_source_ptr || !level) + { + return; + } + + _viewer->open(level, ILevel::OpenMode::Reload); _viewer->select_sound_source(sound_source); _windows->select(sound_source); } @@ -967,4 +991,9 @@ namespace trview _viewer->set_settings(_settings); set_current_level(op.level, op.open_mode, false); } + + void Application::open_diff_level(const std::weak_ptr& level) + { + _diff_level = level; + } } \ No newline at end of file diff --git a/trview.app/Application.h b/trview.app/Application.h index bed19ada8..a141b1c44 100644 --- a/trview.app/Application.h +++ b/trview.app/Application.h @@ -6,8 +6,6 @@ #include #include -#include - #include "Elements/ITypeInfoLookup.h" #include #include @@ -57,7 +55,6 @@ namespace trview const Window& application_window, std::unique_ptr update_checker, std::shared_ptr settings_loader, - const trlevel::ILevel::Source& trlevel_source, std::unique_ptr file_menu, std::shared_ptr viewer, const IRoute::Source& route_source, @@ -127,13 +124,14 @@ namespace trview void select_static_mesh(const std::weak_ptr& static_mesh); void select_sound_source(const std::weak_ptr& sound_source); void check_load(); + void open_diff_level(const std::weak_ptr& level); + TokenStore _token_store; // Window message related components. std::shared_ptr _settings_loader; UserSettings _settings; - trlevel::ILevel::Source _trlevel_source; std::unique_ptr _file_menu; std::unique_ptr _update_checker; ViewMenu _view_menu; @@ -177,6 +175,7 @@ namespace trview std::future _load; LoadMode _load_mode; std::string _progress; + std::weak_ptr _diff_level; }; std::unique_ptr create_application(HINSTANCE hInstance, int command_show, const std::wstring& command_line); diff --git a/trview.app/ApplicationCreate.cpp b/trview.app/ApplicationCreate.cpp index ccd5828ff..7c5c5ce09 100644 --- a/trview.app/ApplicationCreate.cpp +++ b/trview.app/ApplicationCreate.cpp @@ -39,6 +39,7 @@ #include "Graphics/SectorHighlight.h" #include "Lua/Scriptable/Scriptable.h" #include "Menus/FileMenu.h" +#include "Menus/ImGuiFileMenu.h" #include "Menus/UpdateChecker.h" #include "Routing/Waypoint.h" #include "Routing/RandomizerRoute.h" @@ -81,6 +82,8 @@ #include "Windows/Windows.h" #include "Windows/About/AboutWindowManager.h" #include "Windows/About/AboutWindow.h" +#include "Windows/Diff/DiffWindowManager.h" +#include "Windows/Diff/DiffWindow.h" namespace trview { @@ -241,10 +244,10 @@ namespace trview auto static_mesh_position_source = [=](auto&&... args) { return std::make_shared(args...); }; auto sector_source = [=](auto&&... args) { return std::make_shared(std::forward(args)...); }; auto room_source = [=](const trlevel::ILevel& level, const trlevel::tr3_room& room, - const std::shared_ptr& texture_storage, const IMeshStorage& mesh_storage, uint32_t index, const std::weak_ptr& parent_level, const Activity& activity) + const std::shared_ptr& texture_storage, const IMeshStorage& mesh_storage, uint32_t index, const std::weak_ptr& parent_level, uint32_t sector_base_index, const Activity& activity) { auto new_room = std::make_shared(room, mesh_source, texture_storage, index, parent_level); - new_room->initialise(level, room, mesh_storage, static_mesh_source, static_mesh_position_source, sector_source, activity); + new_room->initialise(level, room, mesh_storage, static_mesh_source, static_mesh_position_source, sector_source, sector_base_index, activity); return new_room; }; auto trigger_source = [=](auto&&... args) { return std::make_shared(args..., mesh_transparent_source); }; @@ -259,8 +262,13 @@ namespace trview const auto sound_source = [=](auto&&... args) { return create_sound(args...); }; const auto sound_source_source = [=](auto&&... args) { return std::make_shared(cube_mesh, texture_storage, args...); }; - auto level_source = [=](auto&& level, auto&& callbacks) + auto decrypter = std::make_shared(); + auto trlevel_source = [=](auto&& filename) { return std::make_shared(filename, files, decrypter, log); }; + + auto level_source = [=](auto&& filename, auto&& callbacks) { + auto level = trlevel_source(filename); + auto level_texture_storage = std::make_shared(device, std::make_unique(device)); int count = 0; callbacks.on_textile_callback = [&](auto&& textile) @@ -351,7 +359,6 @@ namespace trview clipboard, std::make_shared(window.size())); - auto triggers_window_source = [=]() { return std::make_shared(clipboard); }; auto route_window_source = [=]() { return std::make_shared(clipboard, dialogs, files); }; auto rooms_window_source = [=]() { return std::make_shared(map_renderer_source, clipboard); }; @@ -360,20 +367,17 @@ namespace trview auto log_window_source = [=]() { return std::make_shared(log, dialogs, files); }; auto camera_sink_window_source = [=]() { return std::make_shared(clipboard); }; - auto decrypter = std::make_shared(); - - auto trlevel_source = [=](auto&& filename) { return std::make_shared(filename, files, decrypter, log); }; auto textures_window_source = [=]() { return std::make_shared(); }; auto console_source = [=]() { return std::make_shared(dialogs, plugins, fonts); }; auto statics_window_source = [=]() { return std::make_shared(clipboard); }; auto sounds_window_source = [=]() { return std::make_shared(); }; auto about_window_source = [=]() { return std::make_shared(); }; + auto diff_window_source = [=]() { return std::make_shared(dialogs, level_source, std::make_unique(dialogs, files)); }; return std::make_unique( window, std::make_unique(window), settings_loader, - trlevel_source, std::make_unique(window, shortcuts, dialogs, files), std::move(viewer), route_source, @@ -390,6 +394,7 @@ namespace trview std::make_unique(window, about_window_source), std::make_unique(window, shortcuts, camera_sink_window_source), std::make_unique(window, shortcuts, console_source, files), + std::make_unique(window, shortcuts, diff_window_source), items_window_manager, std::make_unique(window, shortcuts, lights_window_source), std::make_unique(window, log_window_source), diff --git a/trview.app/Elements/Floordata.cpp b/trview.app/Elements/Floordata.cpp index c8953febc..25c3dae27 100644 --- a/trview.app/Elements/Floordata.cpp +++ b/trview.app/Elements/Floordata.cpp @@ -87,7 +87,7 @@ namespace trview std::uint16_t setup = data[++i]; auto trigger_type = (TriggerType)subfunction; - meanings.push_back(trigger_type_name(trigger_type)); + meanings.push_back(to_string(trigger_type)); meanings.push_back(" Timer:" + std::to_string(setup & 0xff) + ", Only Once:" + std::to_string((setup & 0x100) >> 8) + ", Mask:" + format_binary((setup & 0x3e00) >> 9)); bool continue_processing = true; diff --git a/trview.app/Elements/ILevel.h b/trview.app/Elements/ILevel.h index 369d165e5..1f70c4f30 100644 --- a/trview.app/Elements/ILevel.h +++ b/trview.app/Elements/ILevel.h @@ -19,7 +19,7 @@ namespace trview struct ILevel { - using Source = std::function(std::shared_ptr, trlevel::ILevel::LoadCallbacks)>; + using Source = std::function(const std::string&, trlevel::ILevel::LoadCallbacks)>; enum class OpenMode { diff --git a/trview.app/Elements/IRoom.h b/trview.app/Elements/IRoom.h index 9e088a285..d1dc883c4 100644 --- a/trview.app/Elements/IRoom.h +++ b/trview.app/Elements/IRoom.h @@ -93,7 +93,7 @@ namespace trview /// Create a new implementation of . /// using Source = std::function(const trlevel::ILevel&, const trlevel::tr3_room&, - const std::shared_ptr&, const IMeshStorage&, uint32_t, const std::weak_ptr&, const Activity& activity)>; + const std::shared_ptr&, const IMeshStorage&, uint32_t, const std::weak_ptr&, uint32_t, const Activity& activity)>; /// /// Destructor for . /// diff --git a/trview.app/Elements/ISector.h b/trview.app/Elements/ISector.h index 245d6dae9..c64ec7eb0 100644 --- a/trview.app/Elements/ISector.h +++ b/trview.app/Elements/ISector.h @@ -18,7 +18,7 @@ namespace trview struct ISector { using Source = std::function(const trlevel::ILevel&, const trlevel::tr3_room&, - const trlevel::tr_room_sector&, int, const std::weak_ptr&)>; + const trlevel::tr_room_sector&, int, const std::weak_ptr&, uint32_t)>; enum class Corner { @@ -127,6 +127,7 @@ namespace trview virtual std::weak_ptr trigger() const = 0; virtual TriangulationDirection ceiling_triangulation() const = 0; + virtual uint32_t number() const = 0; }; bool is_no_space(SectorFlag flags); diff --git a/trview.app/Elements/IStaticMesh.h b/trview.app/Elements/IStaticMesh.h index 2fec8607f..3f6a99032 100644 --- a/trview.app/Elements/IStaticMesh.h +++ b/trview.app/Elements/IStaticMesh.h @@ -12,6 +12,7 @@ namespace trview struct ILevelTextureStorage; struct ITransparencyBuffer; struct IRoom; + struct ILevel; struct IStaticMesh { @@ -21,8 +22,8 @@ namespace trview Sprite }; - using PositionSource = std::function(const trlevel::tr_room_sprite&, const DirectX::SimpleMath::Vector3&, const DirectX::SimpleMath::Matrix&, std::shared_ptr, const std::shared_ptr&)>; - using MeshSource = std::function(const trlevel::tr3_room_staticmesh&, const trlevel::tr_staticmesh&, const std::shared_ptr&, const std::shared_ptr&)>; + using PositionSource = std::function(const trlevel::tr_room_sprite&, const DirectX::SimpleMath::Vector3&, const DirectX::SimpleMath::Matrix&, std::shared_ptr, const std::shared_ptr&, const std::weak_ptr&)>; + using MeshSource = std::function(const trlevel::tr3_room_staticmesh&, const trlevel::tr_staticmesh&, const std::shared_ptr&, const std::shared_ptr&, const std::weak_ptr&)>; virtual ~IStaticMesh() = 0; virtual void render(const ICamera& camera, const ILevelTextureStorage& texture_storage, const DirectX::SimpleMath::Color& colour) = 0; @@ -44,6 +45,7 @@ namespace trview virtual bool visible() const = 0; virtual void set_visible(bool value) = 0; virtual bool has_collision() const = 0; + virtual std::weak_ptr level() const = 0; Event<> on_changed; }; diff --git a/trview.app/Elements/ITrigger.cpp b/trview.app/Elements/ITrigger.cpp index ce32f414c..a02920055 100644 --- a/trview.app/Elements/ITrigger.cpp +++ b/trview.app/Elements/ITrigger.cpp @@ -5,7 +5,7 @@ namespace trview { namespace { - const std::unordered_map trigger_type_names + const std::unordered_map to_strings { { TriggerType::Trigger, "Trigger" }, { TriggerType::Pad, "Pad" }, @@ -60,10 +60,10 @@ namespace trview return std::any_of(types.begin(), types.end(), [&](const auto& type) { return has_command(trigger, type); }); } - std::string trigger_type_name(TriggerType type) + std::string to_string(TriggerType type) { - auto name = trigger_type_names.find(type); - if (name == trigger_type_names.end()) + auto name = to_strings.find(type); + if (name == to_strings.end()) { return "Unknown"; } diff --git a/trview.app/Elements/ITrigger.h b/trview.app/Elements/ITrigger.h index 3cca7382b..5ce4d73f7 100644 --- a/trview.app/Elements/ITrigger.h +++ b/trview.app/Elements/ITrigger.h @@ -46,7 +46,7 @@ namespace trview /// Get the string representation of the trigger type specified. /// @param type The type to test. /// @returns The string version of the enum. - std::string trigger_type_name(TriggerType type); + std::string to_string(TriggerType type); /// Get the string representation of the command type specified. /// @param type The type to test. diff --git a/trview.app/Elements/Level.cpp b/trview.app/Elements/Level.cpp index 61ef36abc..c874ca60c 100644 --- a/trview.app/Elements/Level.cpp +++ b/trview.app/Elements/Level.cpp @@ -467,16 +467,18 @@ namespace trview { Activity generate_rooms_activity(_log, "Level", level.name()); const auto num_rooms = level.num_rooms(); + uint32_t sector_base_index = 0; for (uint32_t i = 0u; i < num_rooms; ++i) { Activity room_activity(generate_rooms_activity, std::format("Room {}", i)); - auto room = room_source(level, level.get_room(i), _texture_storage, mesh_storage, i, shared_from_this(), room_activity); + auto room = room_source(level, level.get_room(i), _texture_storage, mesh_storage, i, shared_from_this(), sector_base_index, room_activity); _token_store += room->on_changed += [&]() { _regenerate_transparency = true; on_level_changed(); }; _rooms.push_back(room); + sector_base_index += static_cast(room->sectors().size()); } std::set alternate_groups; @@ -1486,7 +1488,7 @@ namespace trview detail = details[index]; } } - auto sound_source = sound_source_source(count++, source, detail, _version); + auto sound_source = sound_source_source(count++, source, detail, _version, shared_from_this()); _token_store += sound_source->on_changed += [this]() { content_changed(); }; _sound_sources.push_back(sound_source); } diff --git a/trview.app/Elements/Room.cpp b/trview.app/Elements/Room.cpp index cbe35b114..a7622c7ca 100644 --- a/trview.app/Elements/Room.cpp +++ b/trview.app/Elements/Room.cpp @@ -76,9 +76,9 @@ namespace trview void Room::initialise(const trlevel::ILevel& level, const trlevel::tr3_room& room, const IMeshStorage& mesh_storage, const IStaticMesh::MeshSource& static_mesh_mesh_source, const IStaticMesh::PositionSource& static_mesh_position_source, - const ISector::Source& sector_source, const Activity& activity) + const ISector::Source& sector_source, uint32_t sector_base_index, const Activity& activity) { - generate_sectors(level, room, sector_source); + generate_sectors(level, room, sector_source, sector_base_index); generate_geometry(_mesh_source, room); generate_adjacency(); generate_static_meshes(_mesh_source, level, room, mesh_storage, static_mesh_mesh_source, static_mesh_position_source, activity); @@ -343,7 +343,7 @@ namespace trview activity.log(trview::Message::Status::Error, std::format("Static Mesh {} was requested but not found", room_mesh.mesh_id)); continue; } - _static_meshes.push_back(static_mesh_mesh_source(room_mesh, level_static_mesh.value(), mesh_storage.mesh(level_static_mesh.value().Mesh), shared_from_this())); + _static_meshes.push_back(static_mesh_mesh_source(room_mesh, level_static_mesh.value(), mesh_storage.mesh(level_static_mesh.value().Mesh), shared_from_this(), _level)); } // Also read the room sprites - they're similar enough for now. @@ -356,7 +356,7 @@ namespace trview auto vertex = room.data.vertices[room_sprite.vertex].vertex; auto pos = Vector3(vertex.x / trlevel::Scale_X, vertex.y / trlevel::Scale_Y, vertex.z / trlevel::Scale_Z); pos = Vector3::Transform(pos, _room_offset) + offset; - _static_meshes.push_back(static_mesh_position_source(room_sprite, pos, scale, sprite_mesh, shared_from_this())); + _static_meshes.push_back(static_mesh_position_source(room_sprite, pos, scale, sprite_mesh, shared_from_this(), _level)); } } @@ -458,12 +458,12 @@ namespace trview _camera_sinks.push_back(camera_sink); } - void Room::generate_sectors(const trlevel::ILevel& level, const trlevel::tr3_room& room, const ISector::Source& sector_source) + void Room::generate_sectors(const trlevel::ILevel& level, const trlevel::tr3_room& room, const ISector::Source& sector_source, uint32_t sector_base_index) { for (auto i = 0u; i < room.sector_list.size(); ++i) { const trlevel::tr_room_sector §or = room.sector_list[i]; - _sectors.push_back(sector_source(level, room, sector, i, shared_from_this())); + _sectors.push_back(sector_source(level, room, sector, i, shared_from_this(), sector_base_index + i)); } } diff --git a/trview.app/Elements/Room.h b/trview.app/Elements/Room.h index 568de7bd3..83b7f1c12 100644 --- a/trview.app/Elements/Room.h +++ b/trview.app/Elements/Room.h @@ -91,6 +91,7 @@ namespace trview const IStaticMesh::MeshSource& static_mesh_mesh_source, const IStaticMesh::PositionSource& static_mesh_position_source, const ISector::Source& sector_source, + uint32_t sector_base_index, const Activity& activity); std::vector> static_meshes() const override; private: @@ -100,7 +101,7 @@ namespace trview const IStaticMesh::MeshSource& static_mesh_mesh_source, const IStaticMesh::PositionSource& static_mesh_position_source, const Activity& activity); void render_contained(const ICamera& camera, const DirectX::SimpleMath::Color& colour, RenderFilter render_filter); void get_contained_transparent_triangles(ITransparencyBuffer& transparency, const ICamera& camera, const DirectX::SimpleMath::Color& colour, RenderFilter render_filter); - void generate_sectors(const trlevel::ILevel& level, const trlevel::tr3_room& room, const ISector::Source& sector_source); + void generate_sectors(const trlevel::ILevel& level, const trlevel::tr3_room& room, const ISector::Source& sector_source, uint32_t sector_base_index); ISector* get_trigger_sector(int32_t x, int32_t z); uint32_t get_sector_id(int32_t x, int32_t z) const; diff --git a/trview.app/Elements/Sector.cpp b/trview.app/Elements/Sector.cpp index 836fb26d1..844ecc35b 100644 --- a/trview.app/Elements/Sector.cpp +++ b/trview.app/Elements/Sector.cpp @@ -7,9 +7,9 @@ using namespace DirectX::SimpleMath; namespace trview { - Sector::Sector(const trlevel::ILevel& level, const trlevel::tr3_room& room, const trlevel::tr_room_sector& sector, int sector_id, const std::weak_ptr& room_ptr) + Sector::Sector(const trlevel::ILevel& level, const trlevel::tr3_room& room, const trlevel::tr_room_sector& sector, int sector_id, const std::weak_ptr& room_ptr, uint32_t sector_number) : _sector(sector), _sector_id(static_cast(sector_id)), _room_above(sector.room_above), _room_below(sector.room_below), _room(room_number(room_ptr)), _info(room.info), _room_ptr(room_ptr), - _floordata_index(sector.floordata_index) + _floordata_index(sector.floordata_index), _number(sector_number) { _x = static_cast(sector_id / room.num_z_sectors); _z = static_cast(sector_id % room.num_z_sectors); @@ -733,5 +733,10 @@ namespace trview { return _ceiling_triangulation_function; } + + uint32_t Sector::number() const + { + return _number; + } } diff --git a/trview.app/Elements/Sector.h b/trview.app/Elements/Sector.h index 8581c15a8..7c1ceef1b 100644 --- a/trview.app/Elements/Sector.h +++ b/trview.app/Elements/Sector.h @@ -10,7 +10,7 @@ namespace trview { public: // Constructs sector object and parses floor data automatically - Sector(const trlevel::ILevel& level, const trlevel::tr3_room& room, const trlevel::tr_room_sector& sector, int sector_id, const std::weak_ptr& room_ptr); + Sector(const trlevel::ILevel& level, const trlevel::tr3_room& room, const trlevel::tr_room_sector& sector, int sector_id, const std::weak_ptr& room_ptr, uint32_t sector_number); virtual ~Sector() = default; // Returns the id of the room that this floor data points to virtual std::uint16_t portal() const override; @@ -49,6 +49,7 @@ namespace trview void set_trigger(const std::weak_ptr& trigger) override; std::weak_ptr trigger() const override; TriangulationDirection ceiling_triangulation() const override; + uint32_t number() const override; private: bool parse(const trlevel::ILevel& level); void parse_slope(); @@ -101,5 +102,6 @@ namespace trview uint32_t _floordata_index; trlevel::tr_room_info _info; std::vector _triangles; + uint32_t _number; }; } \ No newline at end of file diff --git a/trview.app/Elements/SoundSource/ISoundSource.h b/trview.app/Elements/SoundSource/ISoundSource.h index 5dffd7e24..e7055543b 100644 --- a/trview.app/Elements/SoundSource/ISoundSource.h +++ b/trview.app/Elements/SoundSource/ISoundSource.h @@ -21,7 +21,7 @@ namespace trview { struct ISoundSource : public IRenderable { - using Source = std::function(uint32_t, const trlevel::tr_sound_source&, const std::optional&, trlevel::LevelVersion)>; + using Source = std::function(uint32_t, const trlevel::tr_sound_source&, const std::optional&, trlevel::LevelVersion, const std::weak_ptr&)>; virtual ~ISoundSource() = 0; virtual uint16_t chance() const = 0; @@ -35,6 +35,7 @@ namespace trview virtual uint8_t range() const = 0; virtual std::optional sample() const = 0; virtual uint16_t volume() const = 0; + virtual std::weak_ptr level() const = 0; Event<> on_changed; }; diff --git a/trview.app/Elements/SoundSource/SoundSource.cpp b/trview.app/Elements/SoundSource/SoundSource.cpp index 8ac55534e..bc16976a2 100644 --- a/trview.app/Elements/SoundSource/SoundSource.cpp +++ b/trview.app/Elements/SoundSource/SoundSource.cpp @@ -10,8 +10,8 @@ namespace trview { } - SoundSource::SoundSource(const std::shared_ptr& mesh, const std::shared_ptr& texture_storage, uint32_t number, const trlevel::tr_sound_source& source, const std::optional& details, trlevel::LevelVersion level_version) - : _mesh(mesh), _flags(source.Flags), _id(source.SoundID), _number(number), _position(source.x / trlevel::Scale, source.y / trlevel::Scale, source.z / trlevel::Scale) + SoundSource::SoundSource(const std::shared_ptr& mesh, const std::shared_ptr& texture_storage, uint32_t number, const trlevel::tr_sound_source& source, const std::optional& details, trlevel::LevelVersion level_version, const std::weak_ptr& level) + : _mesh(mesh), _flags(source.Flags), _id(source.SoundID), _number(number), _position(source.x / trlevel::Scale, source.y / trlevel::Scale, source.z / trlevel::Scale), _level(level) { _sound_texture = texture_storage->lookup("sound_texture"); @@ -135,4 +135,9 @@ namespace trview { return _volume; } + + std::weak_ptr SoundSource::level() const + { + return _level; + } } diff --git a/trview.app/Elements/SoundSource/SoundSource.h b/trview.app/Elements/SoundSource/SoundSource.h index 72c851f5f..cd7a772a2 100644 --- a/trview.app/Elements/SoundSource/SoundSource.h +++ b/trview.app/Elements/SoundSource/SoundSource.h @@ -15,7 +15,8 @@ namespace trview uint32_t number, const trlevel::tr_sound_source& source, const std::optional& details, - trlevel::LevelVersion level_version); + trlevel::LevelVersion level_version, + const std::weak_ptr& level); virtual ~SoundSource() = default; uint16_t chance() const override; uint16_t characteristics() const override; @@ -32,6 +33,7 @@ namespace trview void set_visible(bool value) override; bool visible() const override; uint16_t volume() const override; + std::weak_ptr level() const override; private: uint16_t _chance{ 0u }; uint16_t _characteristics{ 0u }; @@ -46,6 +48,7 @@ namespace trview graphics::Texture _sound_texture; bool _visible{ true }; uint16_t _volume{ 0u }; + std::weak_ptr _level; }; } diff --git a/trview.app/Elements/StaticMesh.cpp b/trview.app/Elements/StaticMesh.cpp index b2e3e6199..5596d921d 100644 --- a/trview.app/Elements/StaticMesh.cpp +++ b/trview.app/Elements/StaticMesh.cpp @@ -24,7 +24,7 @@ namespace trview { } - StaticMesh::StaticMesh(const trlevel::tr3_room_staticmesh& static_mesh, const trlevel::tr_staticmesh& level_static_mesh, const std::shared_ptr& mesh, const std::weak_ptr& room, const std::shared_ptr& bounding_mesh) + StaticMesh::StaticMesh(const trlevel::tr3_room_staticmesh& static_mesh, const trlevel::tr_staticmesh& level_static_mesh, const std::shared_ptr& mesh, const std::weak_ptr& room, const std::weak_ptr& level, const std::shared_ptr& bounding_mesh) : _mesh(mesh), _visibility(from_box(level_static_mesh.VisibilityBox)), _collision(from_box(level_static_mesh.CollisionBox)), @@ -33,13 +33,14 @@ namespace trview _bounding_mesh(bounding_mesh), _room(room), _mesh_texture_id(static_mesh.mesh_id), - _flags(level_static_mesh.Flags) + _flags(level_static_mesh.Flags), + _level(level) { _world = Matrix::CreateRotationY(_rotation) * Matrix::CreateTranslation(_position); } - StaticMesh::StaticMesh(const trlevel::tr_room_sprite& room_sprite, const DirectX::SimpleMath::Vector3& position, const DirectX::SimpleMath::Matrix& scale, std::shared_ptr mesh, const std::weak_ptr& room) - : _position(position), _mesh(mesh), _rotation(0), _scale(scale), _room(room), _mesh_texture_id(room_sprite.texture), _type(Type::Sprite) + StaticMesh::StaticMesh(const trlevel::tr_room_sprite& room_sprite, const DirectX::SimpleMath::Vector3& position, const DirectX::SimpleMath::Matrix& scale, std::shared_ptr mesh, const std::weak_ptr& room, const std::weak_ptr& level) + : _position(position), _mesh(mesh), _rotation(0), _scale(scale), _room(room), _mesh_texture_id(room_sprite.texture), _type(Type::Sprite), _level(level) { using namespace DirectX::SimpleMath; _world = Matrix::CreateRotationY(_rotation) * Matrix::CreateTranslation(_position); @@ -162,6 +163,11 @@ namespace trview return _type == Type::Mesh && (_flags & 0x1) == 0; } + std::weak_ptr StaticMesh::level() const + { + return _level; + } + uint32_t static_mesh_room(const std::shared_ptr& static_mesh) { if (!static_mesh) diff --git a/trview.app/Elements/StaticMesh.h b/trview.app/Elements/StaticMesh.h index ff4bdbe57..faed64559 100644 --- a/trview.app/Elements/StaticMesh.h +++ b/trview.app/Elements/StaticMesh.h @@ -7,8 +7,8 @@ namespace trview class StaticMesh final : public IStaticMesh { public: - StaticMesh(const trlevel::tr3_room_staticmesh& static_mesh, const trlevel::tr_staticmesh& level_static_mesh, const std::shared_ptr& mesh, const std::weak_ptr& room, const std::shared_ptr& bounding_mesh); - StaticMesh(const trlevel::tr_room_sprite& room_sprite, const DirectX::SimpleMath::Vector3& position, const DirectX::SimpleMath::Matrix& scale, std::shared_ptr mesh, const std::weak_ptr& room); + StaticMesh(const trlevel::tr3_room_staticmesh& static_mesh, const trlevel::tr_staticmesh& level_static_mesh, const std::shared_ptr& mesh, const std::weak_ptr& room, const std::weak_ptr& level, const std::shared_ptr& bounding_mesh); + StaticMesh(const trlevel::tr_room_sprite& room_sprite, const DirectX::SimpleMath::Vector3& position, const DirectX::SimpleMath::Matrix& scale, std::shared_ptr mesh, const std::weak_ptr& room, const std::weak_ptr& level); virtual ~StaticMesh() = default; virtual void render(const ICamera& camera, const ILevelTextureStorage& texture_storage, const DirectX::SimpleMath::Color& colour) override; virtual void render_bounding_box(const ICamera& camera, const ILevelTextureStorage& texture_storage, const DirectX::SimpleMath::Color& colour) override; @@ -29,6 +29,7 @@ namespace trview bool visible() const override; void set_visible(bool value) override; bool has_collision() const override; + std::weak_ptr level() const override; private: float _rotation; DirectX::SimpleMath::Vector3 _position; @@ -44,5 +45,6 @@ namespace trview uint16_t _flags{ 0u }; bool _visible{ true }; Type _type{ Type::Mesh }; + std::weak_ptr _level; }; } diff --git a/trview.app/Geometry/PickResult.cpp b/trview.app/Geometry/PickResult.cpp index 26cd9a98a..fc1140625 100644 --- a/trview.app/Geometry/PickResult.cpp +++ b/trview.app/Geometry/PickResult.cpp @@ -105,7 +105,7 @@ namespace trview { if (auto trigger = level.trigger(result.index).lock()) { - stream << trigger_type_name(trigger->type()) << " " << result.index; + stream << to_string(trigger->type()) << " " << result.index; for (const auto command : trigger->commands()) { stream << "\n " << command_type_name(command.type()); @@ -126,7 +126,7 @@ namespace trview { if (const auto light = level.light(result.index).lock()) { - stream << "Light " << result.index << " - " << light_type_name(light->type()); + stream << "Light " << result.index << " - " << to_string(light->type()); } break; } @@ -152,7 +152,7 @@ namespace trview { if (const auto trigger = level.trigger(waypoint->index()).lock()) { - stream << " - " << trigger_type_name(trigger->type()) << " " << waypoint->index(); + stream << " - " << to_string(trigger->type()) << " " << waypoint->index(); } } diff --git a/trview.app/Lua/Elements/Light/Lua_Light.cpp b/trview.app/Lua/Elements/Light/Lua_Light.cpp index 8f162c758..df3eedb8e 100644 --- a/trview.app/Lua/Elements/Light/Lua_Light.cpp +++ b/trview.app/Lua/Elements/Light/Lua_Light.cpp @@ -100,7 +100,7 @@ namespace trview } else if (key == "type") { - lua_pushstring(L, trlevel::light_type_name(light->type()).c_str()); + lua_pushstring(L, trlevel::to_string(light->type()).c_str()); return 1; } else if (key == "visible") diff --git a/trview.app/Lua/Elements/Trigger/Lua_Trigger.cpp b/trview.app/Lua/Elements/Trigger/Lua_Trigger.cpp index 63a1c92a2..08d89b061 100644 --- a/trview.app/Lua/Elements/Trigger/Lua_Trigger.cpp +++ b/trview.app/Lua/Elements/Trigger/Lua_Trigger.cpp @@ -90,7 +90,7 @@ namespace trview } else if (key == "type") { - lua_pushstring(L, trigger_type_name(trigger->type()).c_str()); + lua_pushstring(L, to_string(trigger->type()).c_str()); return 1; } else if (key == "visible") diff --git a/trview.app/Menus/FileMenu.cpp b/trview.app/Menus/FileMenu.cpp index 84c73744f..d13460c8d 100644 --- a/trview.app/Menus/FileMenu.cpp +++ b/trview.app/Menus/FileMenu.cpp @@ -216,4 +216,8 @@ namespace trview on_file_open(found->path); } } + + void FileMenu::render() + { + } } diff --git a/trview.app/Menus/FileMenu.h b/trview.app/Menus/FileMenu.h index 4e770a019..c971e6be8 100644 --- a/trview.app/Menus/FileMenu.h +++ b/trview.app/Menus/FileMenu.h @@ -19,9 +19,10 @@ namespace trview const std::shared_ptr& files); virtual ~FileMenu() = default; std::vector local_levels() const override; - virtual std::optional process_message(UINT message, WPARAM wParam, LPARAM lParam) override; - virtual void open_file(const std::string& filename) override; - virtual void set_recent_files(const std::list& files) override; + std::optional process_message(UINT message, WPARAM wParam, LPARAM lParam) override; + void open_file(const std::string& filename) override; + void render() override; + void set_recent_files(const std::list& files) override; void switch_to(const std::string& filename) override; private: void choose_file(); diff --git a/trview.app/Menus/IFileMenu.h b/trview.app/Menus/IFileMenu.h index 41040abe3..ba3037c58 100644 --- a/trview.app/Menus/IFileMenu.h +++ b/trview.app/Menus/IFileMenu.h @@ -18,6 +18,10 @@ namespace trview /// The file that was opened. virtual void open_file(const std::string& filename) = 0; /// + /// Render in ImGui mode. + /// + virtual void render() = 0; + /// /// Set the list of recent files to display in the menu. /// /// The files to show. diff --git a/trview.app/Menus/ImGuiFileMenu.cpp b/trview.app/Menus/ImGuiFileMenu.cpp new file mode 100644 index 000000000..c1666de77 --- /dev/null +++ b/trview.app/Menus/ImGuiFileMenu.cpp @@ -0,0 +1,70 @@ +#include "ImGuiFileMenu.h" +#include + +namespace trview +{ + ImGuiFileMenu::ImGuiFileMenu(const std::shared_ptr& dialogs, const std::shared_ptr& files) + : _dialogs(dialogs), _files(files) + { + } + + std::vector ImGuiFileMenu::local_levels() const + { + return {}; + } + + void ImGuiFileMenu::open_file(const std::string& filename) + { + _file_switcher = _files->get_files(path_for_filename(filename), default_file_pattern); + } + + void ImGuiFileMenu::render() + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Open")) + { + const auto filename = _dialogs->open_file(L"Open level", { { L"All Tomb Raider Files", { L"*.tr2", L"*.tr4", L"*.trc", L"*.phd", L"*.psx" } } }, OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST); + if (filename.has_value()) + { + on_file_open(filename->filename); + } + } + + if (ImGui::BeginMenu("Open Recent", !_recent_files.empty())) + { + for (const auto& file : _recent_files) + { + if (ImGui::MenuItem(file.c_str())) + { + on_file_open(file); + } + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Switch Level", !_file_switcher.empty())) + { + for (const auto& file : _file_switcher) + { + if (ImGui::MenuItem(file.path.c_str())) + { + on_file_open(file.path); + } + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + } + + void ImGuiFileMenu::set_recent_files(const std::list& files) + { + _recent_files = files | std::ranges::to(); + } + + void ImGuiFileMenu::switch_to(const std::string& filename) + { + filename; + } +} diff --git a/trview.app/Menus/ImGuiFileMenu.h b/trview.app/Menus/ImGuiFileMenu.h new file mode 100644 index 000000000..6fbd685c0 --- /dev/null +++ b/trview.app/Menus/ImGuiFileMenu.h @@ -0,0 +1,28 @@ +#pragma once + +#include "IFileMenu.h" + +#include +#include + +namespace trview +{ + class ImGuiFileMenu final : public IFileMenu + { + public: + explicit ImGuiFileMenu(const std::shared_ptr& dialogs, const std::shared_ptr& files); + virtual ~ImGuiFileMenu() = default; + std::vector local_levels() const override; + void open_file(const std::string& filename) override; + void render() override; + void set_recent_files(const std::list& files) override; + void switch_to(const std::string& filename) override; + private: + static const inline std::string default_file_pattern{ "\\*.TR2*,\\*.TR4*,\\*.TRC*,\\*.PHD,\\*.PSX" }; + + std::shared_ptr _dialogs; + std::shared_ptr _files; + std::vector _file_switcher; + std::vector _recent_files; + }; +} diff --git a/trview.app/Mocks/Elements/IItem.h b/trview.app/Mocks/Elements/IItem.h index 4fe021ec8..a5fff680b 100644 --- a/trview.app/Mocks/Elements/IItem.h +++ b/trview.app/Mocks/Elements/IItem.h @@ -87,6 +87,12 @@ namespace trview ON_CALL(*this, set_visible).WillByDefault([&](auto v) { _visible_state = v; }); return shared_from_this(); } + + std::shared_ptr with_level(std::weak_ptr level) + { + ON_CALL(*this, level).WillByDefault(testing::Return(level)); + return shared_from_this(); + } }; } } diff --git a/trview.app/Mocks/Elements/ILight.h b/trview.app/Mocks/Elements/ILight.h index d84864fc9..52f3301d5 100644 --- a/trview.app/Mocks/Elements/ILight.h +++ b/trview.app/Mocks/Elements/ILight.h @@ -70,6 +70,12 @@ namespace trview ON_CALL(*this, set_visible).WillByDefault([&](auto v) { _visible_state = v; }); return shared_from_this(); } + + std::shared_ptr with_level(std::weak_ptr level) + { + ON_CALL(*this, level).WillByDefault(testing::Return(level)); + return shared_from_this(); + } }; } } diff --git a/trview.app/Mocks/Elements/ISector.h b/trview.app/Mocks/Elements/ISector.h index 106ad459e..f2066b90d 100644 --- a/trview.app/Mocks/Elements/ISector.h +++ b/trview.app/Mocks/Elements/ISector.h @@ -40,6 +40,7 @@ namespace trview MOCK_METHOD(int8_t, tilt_z, (), (const, override)); MOCK_METHOD(std::weak_ptr, trigger, (), (const, override)); MOCK_METHOD(TriangulationDirection, ceiling_triangulation, (), (const, override)); + MOCK_METHOD(uint32_t, number, (), (const, override)); std::shared_ptr with_ceiling_triangulation(TriangulationDirection triangulation) { diff --git a/trview.app/Mocks/Elements/ISoundSource.h b/trview.app/Mocks/Elements/ISoundSource.h index 2cd7f339b..7a2b56950 100644 --- a/trview.app/Mocks/Elements/ISoundSource.h +++ b/trview.app/Mocks/Elements/ISoundSource.h @@ -25,6 +25,7 @@ namespace trview MOCK_METHOD(void, set_visible, (bool), (override)); MOCK_METHOD(bool, visible, (), (const, override)); MOCK_METHOD(uint16_t, volume, (), (const, override)); + MOCK_METHOD(std::weak_ptr, level, (), (const, override)); }; } } \ No newline at end of file diff --git a/trview.app/Mocks/Elements/IStaticMesh.h b/trview.app/Mocks/Elements/IStaticMesh.h index bd7c240d4..471dd13a1 100644 --- a/trview.app/Mocks/Elements/IStaticMesh.h +++ b/trview.app/Mocks/Elements/IStaticMesh.h @@ -28,6 +28,7 @@ namespace trview MOCK_METHOD(void, set_visible, (bool), (override)); MOCK_METHOD(bool, breakable, (), (const, override)); MOCK_METHOD(bool, has_collision, (), (const, override)); + MOCK_METHOD(std::weak_ptr, level, (), (const, override)); std::shared_ptr with_number(uint32_t number) { diff --git a/trview.app/Mocks/Menus/IFileMenu.h b/trview.app/Mocks/Menus/IFileMenu.h index 96f88b64f..f791185bb 100644 --- a/trview.app/Mocks/Menus/IFileMenu.h +++ b/trview.app/Mocks/Menus/IFileMenu.h @@ -12,6 +12,7 @@ namespace trview virtual ~MockFileMenu(); MOCK_METHOD(std::vector, local_levels, (), (const, override)); MOCK_METHOD(void, open_file, (const std::string&), (override)); + MOCK_METHOD(void, render, (), (override)); MOCK_METHOD(void, set_recent_files, (const std::list&), (override)); MOCK_METHOD(void, switch_to, (const std::string&), (override)); }; diff --git a/trview.app/Mocks/Mocks.cpp b/trview.app/Mocks/Mocks.cpp index 2257be948..f07628ec3 100644 --- a/trview.app/Mocks/Mocks.cpp +++ b/trview.app/Mocks/Mocks.cpp @@ -73,6 +73,8 @@ #include "Windows/ISoundsWindowManager.h" #include "Windows/IAboutWindow.h" #include "Windows/IAboutWindowManager.h" +#include "Windows/IDiffWindowManager.h" +#include "Windows/IDiffWindow.h" namespace trview { @@ -293,5 +295,11 @@ namespace trview MockAboutWindow::MockAboutWindow() {}; MockAboutWindow::~MockAboutWindow() {}; + + MockDiffWindow::MockDiffWindow() {}; + MockDiffWindow::~MockDiffWindow() {}; + + MockDiffWindowManager::MockDiffWindowManager() {}; + MockDiffWindowManager::~MockDiffWindowManager() {}; } } \ No newline at end of file diff --git a/trview.app/Mocks/Windows/IDiffWindow.h b/trview.app/Mocks/Windows/IDiffWindow.h new file mode 100644 index 000000000..551592841 --- /dev/null +++ b/trview.app/Mocks/Windows/IDiffWindow.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../../Windows/Diff/IDiffWindow.h" + +namespace trview +{ + namespace mocks + { + struct MockDiffWindow : public IDiffWindow + { + MockDiffWindow(); + virtual ~MockDiffWindow(); + MOCK_METHOD(void, render, (), (override)); + MOCK_METHOD(void, set_number, (int32_t), (override)); + }; + } +} \ No newline at end of file diff --git a/trview.app/Mocks/Windows/IDiffWindowManager.h b/trview.app/Mocks/Windows/IDiffWindowManager.h new file mode 100644 index 000000000..8bce1ee5a --- /dev/null +++ b/trview.app/Mocks/Windows/IDiffWindowManager.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../../Windows/Diff/IDiffWindowManager.h" + +namespace trview +{ + namespace mocks + { + struct MockDiffWindowManager : public IDiffWindowManager + { + public: + MockDiffWindowManager(); + virtual ~MockDiffWindowManager(); + MOCK_METHOD(void, render, (), (override)); + MOCK_METHOD(std::weak_ptr, create_window, (), (override)); + MOCK_METHOD(void, set_level, (const std::weak_ptr&), (override)); + MOCK_METHOD(void, set_settings, (const UserSettings&), (override)); + }; + } +} diff --git a/trview.app/Resources/resource.h b/trview.app/Resources/resource.h index 138b6ca7e..682fccd42 100644 --- a/trview.app/Resources/resource.h +++ b/trview.app/Resources/resource.h @@ -51,6 +51,7 @@ #define ID_WINDOWS_SOUNDS 33028 #define ID_WINDOWS_CAMERA_POSITION 33029 #define IDR_NGPLUS 33030 +#define ID_WINDOWS_DIFF 33031 // Next default values for new objects // diff --git a/trview.app/Resources/trview.app.rc b/trview.app/Resources/trview.app.rc index 124bde951..0641b7570 100644 --- a/trview.app/Resources/trview.app.rc +++ b/trview.app/Resources/trview.app.rc @@ -77,6 +77,7 @@ BEGIN MENUITEM "Plugins\tCtrl+P" ID_WINDOWS_PLUGINS MENUITEM "Statics\tCtrl+S" ID_WINDOWS_STATICS MENUITEM "Sounds" ID_WINDOWS_SOUNDS + MENUITEM "Diff\tCtrl+D" ID_WINDOWS_DIFF MENUITEM SEPARATOR MENUITEM "Camera Position" ID_WINDOWS_CAMERA_POSITION MENUITEM SEPARATOR diff --git a/trview.app/Settings/SettingsLoader.cpp b/trview.app/Settings/SettingsLoader.cpp index 14a7c73dc..74a778efc 100644 --- a/trview.app/Settings/SettingsLoader.cpp +++ b/trview.app/Settings/SettingsLoader.cpp @@ -107,6 +107,7 @@ namespace trview read_attribute(json, settings.fonts, "fonts"); read_attribute(json, settings.statics_startup, "statics_startup"); read_attribute(json, settings.camera_position_window, "camera_position_window"); + read_attribute(json, settings.recent_diff_files, "recent_diff"); settings.recent_files.resize(std::min(settings.recent_files.size(), settings.max_recent_files)); } @@ -176,6 +177,7 @@ namespace trview json["fonts"] = settings.fonts; json["statics_startup"] = settings.statics_startup; json["camera_position_window"] = settings.camera_position_window; + json["recent_diff"] = std::list(settings.recent_diff_files.begin(), std::next(settings.recent_diff_files.begin(), std::min(settings.recent_diff_files.size(), settings.max_recent_files))); _files->save_file(file_path, json.dump()); } catch (...) diff --git a/trview.app/Settings/UserSettings.cpp b/trview.app/Settings/UserSettings.cpp index 327287561..4c7c6966e 100644 --- a/trview.app/Settings/UserSettings.cpp +++ b/trview.app/Settings/UserSettings.cpp @@ -2,23 +2,36 @@ namespace trview { - void UserSettings::add_recent_file(const std::string& file) + namespace { - // If the file already exists in the recent files list, remove it from where it is - // and move it to the to avoid duplicates and to make it ordered by most recent. - auto existing = std::find(recent_files.begin(), recent_files.end(), file); - if (existing != recent_files.end()) + void add_to_files_list(std::list& files, const std::string& file, uint32_t max) { - recent_files.erase(existing); - } + // If the file already exists in the recent files list, remove it from where it is + // and move it to the to avoid duplicates and to make it ordered by most recent. + auto existing = std::find(files.begin(), files.end(), file); + if (existing != files.end()) + { + files.erase(existing); + } - recent_files.push_front(file); - if (recent_files.size() > max_recent_files) - { - recent_files.pop_back(); + files.push_front(file); + if (files.size() > max) + { + files.pop_back(); + } } } + void UserSettings::add_recent_file(const std::string& file) + { + add_to_files_list(recent_files, file, max_recent_files); + } + + void UserSettings::add_recent_diff_file(const std::string& file) + { + add_to_files_list(recent_diff_files, file, max_recent_files); + } + bool UserSettings::operator==(const UserSettings& other) const { return camera_sensitivity == other.camera_sensitivity && @@ -44,6 +57,7 @@ namespace trview camera_sink_startup == other.camera_sink_startup && statics_startup == other.statics_startup && plugin_directories == other.plugin_directories && - camera_position_window == other.camera_position_window; + camera_position_window == other.camera_position_window && + recent_diff_files == other.recent_diff_files; } } diff --git a/trview.app/Settings/UserSettings.h b/trview.app/Settings/UserSettings.h index d847cc095..38acf451b 100644 --- a/trview.app/Settings/UserSettings.h +++ b/trview.app/Settings/UserSettings.h @@ -11,6 +11,7 @@ namespace trview struct UserSettings { void add_recent_file(const std::string& file); + void add_recent_diff_file(const std::string& file); struct RecentRoute { @@ -65,6 +66,7 @@ namespace trview }; bool statics_startup{ false }; bool camera_position_window{ true }; + std::list recent_diff_files; bool operator==(const UserSettings& other) const; }; diff --git a/trview.app/UI/ContextMenu.cpp b/trview.app/UI/ContextMenu.cpp index cb76cfb65..80bdce436 100644 --- a/trview.app/UI/ContextMenu.cpp +++ b/trview.app/UI/ContextMenu.cpp @@ -57,7 +57,7 @@ namespace trview { if (const auto trigger_ptr = trigger.lock()) { - if (ImGui::MenuItem(std::format("{} {}", trigger_type_name(trigger_ptr->type()), trigger_ptr->number()).c_str())) + if (ImGui::MenuItem(std::format("{} {}", to_string(trigger_ptr->type()), trigger_ptr->number()).c_str())) { on_trigger_selected(trigger); } diff --git a/trview.app/UI/ViewerUI.cpp b/trview.app/UI/ViewerUI.cpp index 42b9b72af..d3611e518 100644 --- a/trview.app/UI/ViewerUI.cpp +++ b/trview.app/UI/ViewerUI.cpp @@ -62,7 +62,7 @@ namespace trview const auto triggers = level->triggers() | std::views::transform([](auto&& t) { return t.lock(); }) | std::views::filter([](auto&& t) { return t != nullptr; }) - | std::views::transform([](auto&& t) -> GoTo::GoToItem { return { .number = t->number(), .name = trigger_type_name(t->type()), .item = t }; }) + | std::views::transform([](auto&& t) -> GoTo::GoToItem { return { .number = t->number(), .name = to_string(t->type()), .item = t }; }) | std::ranges::to(); const auto rooms = level->rooms() diff --git a/trview.app/Windows/CameraSink/CameraSinkWindow.cpp b/trview.app/Windows/CameraSink/CameraSinkWindow.cpp index 7715f5238..5b902f185 100644 --- a/trview.app/Windows/CameraSink/CameraSinkWindow.cpp +++ b/trview.app/Windows/CameraSink/CameraSinkWindow.cpp @@ -339,7 +339,7 @@ namespace trview { [](auto&& l, auto&& r) { return l.number() < r.number(); }, [](auto&& l, auto&& r) { return std::tuple(trigger_room(l), l.number()) < std::tuple(trigger_room(r), r.number()); }, - [](auto&& l, auto&& r) { return std::tuple(trigger_type_name(l.type()), l.number()) < std::tuple(trigger_type_name(r.type()), r.number()); }, + [](auto&& l, auto&& r) { return std::tuple(to_string(l.type()), l.number()) < std::tuple(to_string(r.type()), r.number()); }, }, _force_sort); for (auto& trigger : _triggered_by) @@ -362,7 +362,7 @@ namespace trview ImGui::TableNextColumn(); ImGui::Text(std::to_string(trigger_room(trigger_ptr)).c_str()); ImGui::TableNextColumn(); - ImGui::Text(trigger_type_name(trigger_ptr->type()).c_str()); + ImGui::Text(to_string(trigger_ptr->type()).c_str()); } ImGui::EndTable(); diff --git a/trview.app/Windows/Diff/DiffWindow.cpp b/trview.app/Windows/Diff/DiffWindow.cpp new file mode 100644 index 000000000..6ac921299 --- /dev/null +++ b/trview.app/Windows/Diff/DiffWindow.cpp @@ -0,0 +1,900 @@ +#include "DiffWindow.h" +#include +#include +#include +#include "../../UserCancelledException.h" +#include "../../trview_imgui.h" + +#include "../../Elements/ITrigger.h" +#include "../../Elements/ILight.h" +#include "../../Elements/SoundSource/ISoundSource.h" +#include "../../Elements/IRoom.h" +#include "../../Elements/ISector.h" + +namespace trview +{ + namespace + { + using std::to_string; + + constexpr ImVec4 to_colour(DiffWindow::Diff::Type type) noexcept + { + switch (type) + { + case DiffWindow::Diff::Type::Add: + return ImVec4(0, 1, 0, 1); + case DiffWindow::Diff::Type::Update: + return ImVec4(1, 1, 0, 1); + case DiffWindow::Diff::Type::Reindex: + return ImVec4(1, 0, 1, 1); + case DiffWindow::Diff::Type::Delete: + return ImVec4(1, 0, 0, 1); + } + return ImVec4(1, 1, 1, 1); + } + + constexpr std::string to_string(DiffWindow::Diff::Type type) noexcept + { + switch (type) + { + case DiffWindow::Diff::Type::None: + return "None"; + case DiffWindow::Diff::Type::Add: + return "Add"; + case DiffWindow::Diff::Type::Reindex: + return "Reindex"; + case DiffWindow::Diff::Type::Update: + return "Update"; + case DiffWindow::Diff::Type::Delete: + return "Delete"; + } + return "Unknown"; + } + + std::string to_string(const DirectX::SimpleMath::Vector3& value) + { + return std::format("{},{},{}", value.x, value.y, value.z); + } + + std::string to_string(const Colour& colour) + { + return std::format("{},{},{},{}", colour.r, colour.g, colour.b, colour.a); + } + + template + std::string to_string(const std::optional& value) + { + return value.has_value() ? to_string(*value) : ""; + } + + template + void find_direct_matches( + std::vector>& results, + const std::vector>& left_entries, + const std::vector>& right_entries, + std::vector& left_resolved, + std::vector& right_resolved, + std::function&, const std::shared_ptr&)> comparer) + { + using Diff = DiffWindow::Diff; + + for (auto i = 0; i < results.size(); ++i) + { + auto& result = results[i]; + if (result.state == Diff::State::Unresolved && i < left_entries.size()) + { + const auto left = left_entries[i]; + result.left = left; + + if (i < right_entries.size()) + { + const auto right = right_entries[i]; + if (comparer(left, right)) + { + result.type = Diff::Type::None; + result.state = Diff::State::Resolved; + result.right = right; + left_resolved[i] = Diff::State::Resolved; + right_resolved[i] = Diff::State::Resolved; + } + } + } + } + } + + template + void find_moves( + std::vector>& results, + const std::vector>& left_entries, + const std::vector>& right_entries, + std::vector& left_resolved, + std::vector& right_resolved, + std::function&, const std::shared_ptr&)> comparer) + { + using Diff = DiffWindow::Diff; + + for (auto i = 0; i < results.size(); ++i) + { + auto& result = results[i]; + if (result.state == Diff::State::Unresolved && i < left_entries.size()) + { + const auto left = left_entries[i]; + result.left = left; + + // TODO: Multiple items on the same spot, deal with that. + const auto found = + std::ranges::find_if(right_entries, [left, right_entries, right_resolved, comparer](auto&& right) + { + return + right_resolved[std::distance(right_entries.begin(), std::ranges::find(right_entries, right))] == Diff::State::Unresolved && + comparer(left, right); + }); + + if (found != right_entries.end()) + { + result.type = Diff::Type::Reindex; + result.state = Diff::State::Resolved; + result.right = *found; + left_resolved[i] = Diff::State::Resolved; + right_resolved[std::distance(right_entries.begin(), found)] = Diff::State::Resolved; + } + } + } + } + + template + void mark_updated( + std::vector>& results, + const std::vector>& left_entries, + const std::vector>& right_entries, + std::vector& left_resolved, + std::vector& right_resolved) + { + using Diff = DiffWindow::Diff; + + std::vector> new_results; + for (auto i = 0; i < results.size(); ++i) + { + auto& result = results[i]; + if (result.state == Diff::State::Unresolved) + { + if (result.left.lock()) + { + result.type = Diff::Type::Delete; + result.state = Diff::State::Resolved; + result.left = left_entries[i]; + left_resolved[i] = Diff::State::Resolved; + } + } + } + + for (auto i = 0; i < right_entries.size(); ++i) + { + if (right_resolved[i] == Diff::State::Unresolved) + { + // Is the left side also unresolved? Could be an update in this case. + if (i < left_resolved.size() && left_resolved[i] == Diff::State::Resolved && results[i].type == Diff::Type::Delete) + { + results[i].left = left_entries[i]; + results[i].right = right_entries[i]; + results[i].type = Diff::Type::Update; + results[i].state = Diff::State::Resolved; + } + else + { + results.push_back( + { + .state = Diff::State::Resolved, + .type = Diff::Type::Add, + .right = right_entries[i] + }); + } + right_resolved[i] = Diff::State::Resolved; + } + } + } + + struct DiffDetail + { + std::string type; + std::string left; + std::string right; + }; + + void add_if_changed(auto&& results, auto&& name, auto&& left, auto&& right, auto&& left_text, auto&& right_text) + { + if (left != right) + { + results.push_back({ .type = name, .left = left_text, .right = right_text }); + } + } + + void add_if_changed(auto&& results, auto&& name, auto&& left, auto&& right) + { + if constexpr (std::is_same, std::string>::value) + { + add_if_changed(results, name, left, right, left, right); + } + else + { + add_if_changed(results, name, left, right, to_string(left), to_string(right)); + } + } + + std::vector get_details(const std::shared_ptr& left, const std::shared_ptr& right) + { + std::vector results; + add_if_changed(results, "Type", left->type_id(), right->type_id(), left->type(), right->type()); + add_if_changed(results, "Position", left->position() * 1024, right->position() * 1024); + add_if_changed(results, "Angle", left->angle(), right->angle()); + add_if_changed(results, "OCB", left->ocb(), right->ocb()); + add_if_changed(results, "Flags", format_binary(left->activation_flags()), format_binary(right->activation_flags())); + return results; + } + + std::vector get_details(const std::shared_ptr& left, const std::shared_ptr& right) + { + std::vector results; + add_if_changed(results, "Type", left->type(), right->type()); + add_if_changed(results, "Only Once", left->only_once(), right->only_once()); + add_if_changed(results, "Position", left->position() * 1024, right->position() * 1024); + add_if_changed(results, "Flags", format_binary(left->flags()), format_binary(right->flags())); + add_if_changed(results, "Timer", left->timer(), right->timer()); + return results; + } + + std::vector get_details(const std::shared_ptr& left, const std::shared_ptr& right) + { + std::vector results; + add_if_changed(results, "Type", left->type(), right->type()); + add_if_changed(results, "Position", left->position() * 1024, right->position() * 1024); + add_if_changed(results, "Colour", left->colour(), right->colour()); + add_if_changed(results, "Intensity", left->intensity(), right->intensity()); + add_if_changed(results, "Fade", left->fade(), right->fade()); + add_if_changed(results, "Direction", left->direction() * 1024, right->direction() * 1024); + add_if_changed(results, "In", left->in(), right->in()); + add_if_changed(results, "Out", left->out(), right->out()); + add_if_changed(results, "Rad In", left->rad_in(), right->rad_in()); + add_if_changed(results, "Rad Out", left->rad_out(), right->rad_out()); + add_if_changed(results, "Range", left->range(), right->range()); + add_if_changed(results, "Length", left->length(), right->length()); + add_if_changed(results, "Cutoff", left->cutoff(), right->cutoff()); + add_if_changed(results, "Radius", left->radius(), right->radius()); + add_if_changed(results, "Density", left->density(), right->density()); + return results; + } + + std::vector get_details(const std::shared_ptr& left, const std::shared_ptr& right) + { + std::vector results; + add_if_changed(results, "Type", left->type(), right->type()); + add_if_changed(results, "Position", left->position() * 1024, right->position() * 1024); + add_if_changed(results, "Box Index", left->box_index(), right->box_index()); + add_if_changed(results, "Flag", left->flag(), right->flag()); + add_if_changed(results, "Persistent", left->persistent(), right->persistent()); + add_if_changed(results, "Strength", left->strength(), right->strength()); + return results; + } + + std::vector get_details(const std::shared_ptr& left, const std::shared_ptr& right) + { + std::vector results; + add_if_changed(results, "Type", left->type(), right->type()); + add_if_changed(results, "Position", left->position() * 1024, right->position() * 1024); + add_if_changed(results, "Rotation", left->rotation(), right->rotation()); + add_if_changed(results, "ID", left->id(), right->id()); + add_if_changed(results, "Flags", left->flags(), right->flags(), format_binary(left->flags()), format_binary(right->flags())); + add_if_changed(results, "Breakable", left->breakable(), right->breakable()); + add_if_changed(results, "Has Collision", left->has_collision(), right->has_collision()); + return results; + } + + std::vector get_details(const std::shared_ptr& left, const std::shared_ptr& right) + { + std::vector results; + add_if_changed(results, "Position", left->position() * 1024, right->position() * 1024); + add_if_changed(results, "ID", left->id(), right->id()); + add_if_changed(results, "Flags", left->flags(), right->flags(), format_binary(left->flags()), format_binary(right->flags())); + add_if_changed(results, "Chance", left->chance(), right->chance()); + add_if_changed(results, "Characteristics", left->characteristics(), right->characteristics()); + add_if_changed(results, "Pitch", left->pitch(), right->pitch()); + add_if_changed(results, "Range", left->range(), right->range()); + add_if_changed(results, "Volume", left->volume(), right->volume()); + add_if_changed(results, "Sample", left->sample(), right->sample()); + return results; + } + + std::vector get_details(const std::shared_ptr& left, const std::shared_ptr& right) + { + std::vector results; + // left->bounding_box() == right->bounding_box() && + // info + add_if_changed(results, "Alternate Group", left->alternate_group(), right->alternate_group()); + add_if_changed(results, "Alternate Mode", left->alternate_mode(), right->alternate_mode()); + add_if_changed(results, "Ambient", left->ambient(), right->ambient()); + add_if_changed(results, "Ambient Intensity 1", left->ambient_intensity_1(), right->ambient_intensity_1()); + add_if_changed(results, "Ambient Intensity 2", left->ambient_intensity_2(), right->ambient_intensity_2()); + add_if_changed(results, "Centre", left->centre() * 1024, right->centre() * 1024); + add_if_changed(results, "Flags", left->flags(), right->flags()); + add_if_changed(results, "Light Mode", left->light_mode(), right->light_mode()); + add_if_changed(results, "X Sectors", left->num_x_sectors(), right->num_x_sectors()); + add_if_changed(results, "Z Sectors", left->num_z_sectors(), right->num_z_sectors()); + return results; + } + + std::vector get_details(const std::shared_ptr& left, const std::shared_ptr& right) + { + std::vector results; + add_if_changed(results, "X", left->x(), right->x()); + add_if_changed(results, "Z", left->z(), right->z()); + // add_if_changed(results, "Floordata Index", left->floordata_index(), right->floordata_index()); + add_if_changed(results, "Flags", static_cast(left->flags()), static_cast(right->flags())); + add_if_changed(results, "Corner 0", left->corners()[0], right->corners()[0]); + add_if_changed(results, "Corner 1", left->corners()[1], right->corners()[1]); + add_if_changed(results, "Corner 2", left->corners()[2], right->corners()[2]); + add_if_changed(results, "Corner 3", left->corners()[3], right->corners()[3]); + add_if_changed(results, "Ceiling Corner 0", left->ceiling_corners()[0], right->ceiling_corners()[0]); + add_if_changed(results, "Ceiling Corner 1", left->ceiling_corners()[1], right->ceiling_corners()[1]); + add_if_changed(results, "Ceiling Corner 2", left->ceiling_corners()[2], right->ceiling_corners()[2]); + add_if_changed(results, "Ceiling Corner 3", left->ceiling_corners()[3], right->ceiling_corners()[3]); + return results; + } + + template + void show_details(const std::shared_ptr& left, const std::shared_ptr& right) + { + for (const auto& detail : get_details(left, right)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::Text(detail.type.c_str()); + ImGui::TableNextColumn(); + ImGui::Text(detail.left.c_str()); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::Text(detail.right.c_str()); + } + } + + template + auto get_results(auto&& left_entries, auto&& right_entries, auto&& comparer, auto&& extra_check) + { + const auto left_filtered = left_entries + | std::views::transform([](auto&& t) { return t.lock(); }) + | std::views::filter([](auto&& t) { return t != nullptr; }) + | std::views::filter(extra_check) + | std::ranges::to(); + const auto right_filtered = right_entries + | std::views::transform([](auto&& t) { return t.lock(); }) + | std::views::filter([](auto&& t) { return t != nullptr; }) + | std::views::filter(extra_check) + | std::ranges::to(); + + std::vector> results{ left_filtered.size() }; + std::vector left_resolved{ left_filtered.size() }; + std::vector right_resolved{ right_filtered.size() }; + + find_direct_matches(results, left_filtered, right_filtered, left_resolved, right_resolved, comparer); + find_moves(results, left_filtered, right_filtered, left_resolved, right_resolved, comparer); + mark_updated(results, left_filtered, right_filtered, left_resolved, right_resolved); + + std::ranges::sort( + results, [](const auto& l, const auto& r) + { + const auto left_left = l.left.lock(); + const auto left_right = l.right.lock(); + const auto right_left = r.left.lock(); + const auto right_right = r.right.lock(); + const auto left_index = (left_right ? left_right->number() : left_left ? left_left->number() : 0); + const auto right_index = (right_right ? right_right->number() : right_left ? right_left->number() : 0); + return left_index < right_index; + } + ); + + return results; + }; + + template + auto get_results(auto&& left_entries, auto&& right_entries, auto&& comparer) + { + return get_results(left_entries, right_entries, comparer, [](auto&&) { return true; }); + }; + } + + IDiffWindow::~IDiffWindow() + { + } + + DiffWindow::DiffWindow(const std::shared_ptr& dialogs, const ILevel::Source& level_source, std::unique_ptr file_menu) + : _dialogs(dialogs), _level_source(level_source), _file_menu(std::move(file_menu)) + { + _token_store += _file_menu->on_file_open += [this](auto&& filename) { start_load(filename); }; + } + + void DiffWindow::render() + { + if (!render_diff_window()) + { + on_window_closed(); + return; + } + } + + bool DiffWindow::render_diff_window() + { + bool stay_open = true; + ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(520, 400)); + if (ImGui::Begin(_id.c_str(), &stay_open, ImGuiWindowFlags_MenuBar)) + { + if (ImGui::BeginMenuBar()) + { + _file_menu->render(); + ImGui::MenuItem("Only Show Changes", nullptr, &_only_show_changes); + ImGui::EndMenuBar(); + } + + if (loading()) + { + if (!_progress.empty()) + { + ImGui::Text(std::format("Loading: {}", _progress).c_str()); + } + } + else if (_diff) + { + render_diff_details(); + } + } + ImGui::End(); + ImGui::PopStyleVar(); + return stay_open; + } + + void DiffWindow::set_level(const std::weak_ptr& level) + { + _level = level; + if (_diff) + { + const auto left = _level.lock(); + if (left) + { + const auto filename = _diff->filename; + const auto right = _diff->level; + _load = std::async(std::launch::async, [=]() -> LoadOperation + { + LoadOperation operation + { + .level = right, + .filename = filename + }; + operation.diff = do_diff(left, operation.level); + return operation; + }); + } + } + } + + void DiffWindow::set_number(int32_t number) + { + _id = std::format("Diff {}", number); + } + + void DiffWindow::set_settings(const UserSettings& settings) + { + _settings = settings; + _file_menu->set_recent_files(_settings.recent_diff_files); + } + + void DiffWindow::start_load(const std::string& filename) + { + const auto level = _level.lock(); + if (!level) + { + return; + } + + _load = std::async(std::launch::async, [=]() -> LoadOperation + { + LoadOperation operation + { + .filename = filename, + }; + + try + { + operation.level = load_level(filename); + operation.diff = do_diff(level, operation.level); + } + catch (trlevel::LevelEncryptedException&) + { + operation.error = "Level is encrypted and cannot be loaded"; + } + catch (UserCancelledException&) + { + } + catch (std::exception& e) + { + operation.error = std::format("Failed to load level : {}", e.what()); + } + + return operation; + }); + } + + std::shared_ptr DiffWindow::load_level(const std::string& filename) + { + _progress = std::format("Loading {}", filename); + auto level = _level_source(filename, + { + .on_progress_callback = [&](auto&& p) { _progress = p; } + }); + + level->set_filename(filename); + return level; + } + + DiffWindow::Diff DiffWindow::do_diff(const std::shared_ptr& left, const std::shared_ptr& right) + { + // Items: + const auto item_results = get_results(left->items(), right->items(), + [](auto&& left, auto&& right) + { + return left->type_id() == right->type_id() && + left->position() == right->position() && + left->angle() == right->angle() && + left->ocb() == right->ocb() && + left->activation_flags() == right->activation_flags(); + }, + [](auto&& i) { return i && i->ng_plus() != true; }); + + // Triggers: + const auto trigger_results = get_results(left->triggers(), right->triggers(), + [](auto&& left, auto&& right) + { + return + // room? + // x/z + left->type() == right->type() && + left->only_once() == right->only_once() && + left->flags() == right->flags() && + left->timer() == right->timer() && + left->position() == right->position(); + });; + + // Lights: + const auto light_results = get_results(left->lights(), right->lights(), + [](auto&& left, auto&& right) + { + return + left->type() == right->type() && + left->position() == right->position() && + left->colour() == right->colour() && + left->intensity() == right->intensity() && + left->fade() == right->fade() && + left->direction() == right->direction() && + left->in() == right->in() && + left->out() == right->out() && + left->rad_in() == right->rad_in() && + left->rad_out() == right->rad_out() && + left->range() == right->range() && + left->length() == right->length() && + left->cutoff() == right->cutoff() && + left->radius() == right->radius() && + left->density() == right->density(); + }); + + // Camera/Sinks: + const auto camera_sink_results = get_results(left->camera_sinks(), right->camera_sinks(), + [](auto&& left, auto&& right) + { + return + left->type() == right->type() && + left->position() == right->position() && + left->box_index() == right->box_index() && + left->flag() == right->flag() && + left->persistent() == right->persistent() && + left->strength() == right->strength(); + // bounding_box + }); + + // Statics: + const auto static_results = get_results(left->static_meshes(), right->static_meshes(), + [](auto&& left, auto&& right) + { + return + // Room? + // visibility + // collision + left->type() == right->type() && + left->position() == right->position() && + left->rotation() == right->rotation() && + left->id() == right->id() && + left->flags() == right->flags() && + left->breakable() == right->breakable() && + left->has_collision() == right->has_collision(); + }); + + // Sounds: + const auto sound_source_results = get_results(left->sound_sources(), right->sound_sources(), + [](auto&& left, auto&& right) + { + return + left->chance() == right->chance() && + left->characteristics() == right->characteristics() && + left->flags() == right->flags() && + left->id() == right->id() && + left->position() == right->position() && + left->pitch() == right->pitch() && + left->range() == right->range() && + left->volume() == right->volume() && + left->sample() == right->sample(); + }); + + // Rooms: + const auto rooms_results = get_results(left->rooms(), right->rooms(), + [](auto&& left, auto&& right) + { + return + left->alternate_group() == right->alternate_group() && + left->ambient() == right->ambient() && + left->ambient_intensity_1() == right->ambient_intensity_1() && + left->ambient_intensity_2() == right->ambient_intensity_2() && + left->alternate_mode() == right->alternate_mode() && + // left->bounding_box() == right->bounding_box() && + left->centre() == right->centre() && + left->flags() == right->flags() && + // info + left->light_mode() == right->light_mode()&& + left->num_x_sectors() == right->num_x_sectors() && + left->num_z_sectors() == right->num_z_sectors(); + }); + + // Sectors: + const auto left_sectors = std::ranges::join_view( + left->rooms() | + std::views::transform([](auto&& r) { return r.lock(); }) | + std::views::transform([](auto&& r) { return r->sectors(); }) | + std::ranges::to()) | + std::views::transform([](auto&& s) -> std::weak_ptr { return s; }) | + std::ranges::to(); + const auto right_sectors = std::ranges::join_view( + right->rooms() | + std::views::transform([](auto&& r) { return r.lock(); }) | + std::views::transform([](auto&& r) { return r->sectors(); }) | + std::ranges::to()) | + std::views::transform([](auto&& s) -> std::weak_ptr { return s; }) | + std::ranges::to(); + + const auto sectors_results = get_results(left_sectors, right_sectors, + [](auto&& left, auto&& right) + { + return + left->x() == right->x() && + left->z() == right->z() && + // left->floordata_index() == right->floordata_index() && + left->flags() == right->flags() && + left->corners() == right->corners() && + left->ceiling_corners() == right->ceiling_corners(); + }, [](auto&&) { return true; }); + + return Diff + { + .items = item_results, + .triggers = trigger_results, + .lights = light_results, + .camera_sinks = camera_sink_results, + .static_meshes = static_results, + .sound_sources = sound_source_results, + .rooms = rooms_results, + .sectors = sectors_results + }; + } + + bool DiffWindow::loading() + { + if (_load.valid()) + { + if (_load.wait_for(std::chrono::milliseconds(0)) != std::future_status::ready) + { + return true; + } + _diff = _load.get(); + on_level_selected(_diff->level); + if (_diff->level) + { + _file_menu->open_file(_diff->level->filename()); + _settings.add_recent_diff_file(_diff->level->filename()); + _file_menu->set_recent_files(_settings.recent_diff_files); + on_settings(_settings); + } + } + return false; + } + + void DiffWindow::render_diff_details() + { + std::string a_filename; + if (auto level = _level.lock()) + { + a_filename = level->filename(); + } + ImGui::Text(std::format("A: {}", a_filename).c_str()); + ImGui::Text(std::format("B: {}", _diff->filename).c_str()); + + if (ImGui::BeginTabBar("TabBar")) + { + const auto any_diff = [](auto&& range) { return std::ranges::any_of(range, [](auto&& e) { return e.type != Diff::Type::None; }); }; + const bool any_items = any_diff(_diff->diff.items); + const bool any_triggers = any_diff(_diff->diff.triggers); + const bool any_lights = any_diff(_diff->diff.lights); + const bool any_camera_sink = any_diff(_diff->diff.camera_sinks); + const bool any_statics = any_diff(_diff->diff.static_meshes); + const bool any_sounds = any_diff(_diff->diff.sound_sources); + const bool any_rooms = any_diff(_diff->diff.rooms); + const bool any_sectors = any_diff(_diff->diff.sectors); + + const auto show_table = [this]( + const std::string& table_name, + const auto& diff_entries, + auto& on_selected, + const auto& key_get) + { + if (ImGui::BeginTable(table_name.c_str(), 5, ImGuiTableFlags_Sortable | ImGuiTableFlags_ScrollY)) + { + imgui_header_row( + { + { "#", _column_sizer.size(0) }, + { "Type", _column_sizer.size(1) }, + { "Change", _column_sizer.size(2) }, + { "#", _column_sizer.size(3) }, + { "Type", _column_sizer.size(4) }, + }); + + for (const auto item_diff : diff_entries) + { + if (_only_show_changes && item_diff.type == Diff::Type::None) + { + continue; + } + + const auto left_item = item_diff.left.lock(); + const auto right_item = item_diff.right.lock(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + const std::string row_text = left_item ? std::to_string(left_item->number()) : ""; + bool open = false; + if (item_diff.type == Diff::Type::Update) + { + open = ImGui::TreeNodeEx(row_text.c_str(), ImGuiTreeNodeFlags_OpenOnArrow); + } + else + { + ImGui::Text(row_text.c_str()); + } + + ImGui::TableNextColumn(); + if (left_item) + { + if (ImGui::Selectable(std::format("{}##left-{}", key_get(left_item), left_item->number()).c_str())) + { + on_selected(left_item); + } + } + + ImGui::TableNextColumn(); + ImGui::TextColored(to_colour(item_diff.type), to_string(item_diff.type).c_str()); + + ImGui::TableNextColumn(); + if (right_item) + { + ImGui::Text(std::to_string(right_item->number()).c_str()); + } + + ImGui::TableNextColumn(); + if (right_item) + { + if (ImGui::Selectable(std::format("{}##right-{}", key_get(right_item), right_item->number()).c_str())) + { + on_level_selected(_diff->level); + on_selected(right_item); + } + } + + if (open && left_item && right_item) + { + show_details(left_item, right_item); + ImGui::TreePop(); + } + } + + ImGui::EndTable(); + } + }; + + if (ImGui::BeginTabItem(std::format("Items{}", any_items ? "*" : "").c_str())) + { + show_table("Items List", _diff->diff.items, on_item_selected, [](auto&& i) { return i->type(); }); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem(std::format("Triggers{}", any_triggers ? "*" : "").c_str())) + { + show_table("Triggers List", _diff->diff.triggers, on_trigger_selected, [](auto&& t) { return to_string(t->type()); }); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem(std::format("Lights{}", any_lights ? "*" : "").c_str())) + { + show_table("Lights List", _diff->diff.lights, on_light_selected, [](auto&& l) { return to_string(l->type()); }); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem(std::format("Camera/Sink{}", any_camera_sink ? "*" : "").c_str())) + { + show_table("CameraSink List", _diff->diff.camera_sinks, on_camera_sink_selected, [](auto&& c) { return to_string(c->type()); }); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem(std::format("Statics{}", any_statics ? "*" : "").c_str())) + { + show_table("Statics List", _diff->diff.static_meshes, on_static_mesh_selected, [](auto&& c) { return to_string(c->type()); }); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem(std::format("Sound Sources{}", any_sounds ? "*" : "").c_str())) + { + show_table("Sounds List", _diff->diff.sound_sources, on_sound_source_selected, [](auto&&) { return "Sound"; }); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem(std::format("Rooms{}", any_rooms ? "*" : "").c_str())) + { + show_table("Rooms List", _diff->diff.rooms, on_room_selected, [](auto&&) { return "Room"; }); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem(std::format("Sectors{}", any_sectors ? "*" : "").c_str())) + { + show_table("Sectors List", _diff->diff.sectors, on_sector_selected, [](auto&&) { return "Sector"; }); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + } + + void DiffWindow::calculate_column_widths() + { + if (!_diff) + { + return; + } + + _column_sizer.reset(); + _column_sizer.measure("#__", 0); + _column_sizer.measure("Type__", 1); + _column_sizer.measure("Change__", 2); + _column_sizer.measure("#__", 3); + _column_sizer.measure("Type__", 4); + + for (const auto& item : _diff->diff.items) + { + if (auto item_ptr = item.left.lock()) + { + _column_sizer.measure(std::to_string(item_ptr->number()), 0); + _column_sizer.measure(item_ptr->type(), 1); + } + + _column_sizer.measure(to_string(item.type), 2); + + if (auto item_ptr = item.right.lock()) + { + _column_sizer.measure(std::to_string(item_ptr->number()), 3); + _column_sizer.measure(item_ptr->type(), 4); + } + } + } +} diff --git a/trview.app/Windows/Diff/DiffWindow.h b/trview.app/Windows/Diff/DiffWindow.h new file mode 100644 index 000000000..1e098cca9 --- /dev/null +++ b/trview.app/Windows/Diff/DiffWindow.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include + +#include "../../Menus/IFileMenu.h" +#include "../../Elements/ILevel.h" +#include "IDiffWindow.h" +#include "../ColumnSizer.h" +#include "../../Settings/UserSettings.h" + +namespace trview +{ + class DiffWindow final : public IDiffWindow + { + public: + struct Names + { + }; + + explicit DiffWindow(const std::shared_ptr& dialogs, const ILevel::Source& level_source, std::unique_ptr file_menu); + virtual ~DiffWindow() = default; + void render() override; + void set_level(const std::weak_ptr& level) override; + void set_number(int32_t number) override; + void set_settings(const UserSettings& settings) override; + + struct Diff + { + enum class State + { + Unresolved, + Resolved + }; + + enum class Type + { + None, + Add, + Update, + Reindex, + Delete + }; + + template + struct Item + { + State state{ State::Unresolved }; + Type type{ Type::None }; + std::weak_ptr left; + std::weak_ptr right; + }; + + std::vector> items; + std::vector> triggers; + std::vector> lights; + std::vector> camera_sinks; + std::vector> static_meshes; + std::vector> sound_sources; + std::vector> rooms; + std::vector> sectors; + }; + private: + struct LoadOperation + { + std::shared_ptr level; + std::string filename; + std::optional error; + Diff diff; + }; + + void render_diff_details(); + bool render_diff_window(); + void start_load(const std::string& filename); + std::shared_ptr load_level(const std::string& filename); + Diff do_diff(const std::shared_ptr& left, const std::shared_ptr& right); + bool loading(); + void calculate_column_widths(); + + std::string _id{ "Diff 0" }; + std::string _progress; + ILevel::Source _level_source; + std::future _load; + std::optional _diff; + std::shared_ptr _dialogs; + std::weak_ptr _level; + ColumnSizer _column_sizer; + std::unique_ptr _file_menu; + TokenStore _token_store; + UserSettings _settings; + bool _only_show_changes{ false }; + }; +} diff --git a/trview.app/Windows/Diff/DiffWindowManager.cpp b/trview.app/Windows/Diff/DiffWindowManager.cpp new file mode 100644 index 000000000..53996491e --- /dev/null +++ b/trview.app/Windows/Diff/DiffWindowManager.cpp @@ -0,0 +1,66 @@ +#include "DiffWindowManager.h" +#include "../../Resources/resource.h" + +namespace trview +{ + IDiffWindowManager::~IDiffWindowManager() + { + } + + DiffWindowManager::DiffWindowManager(const Window& window, const std::shared_ptr& shortcuts, const IDiffWindow::Source& diff_window_source) + : _diff_window_source(diff_window_source), MessageHandler(window) + { + _token_store += shortcuts->add_shortcut(true, 'D') += [&]() { create_window(); }; + } + + std::weak_ptr DiffWindowManager::create_window() + { + const auto window = _diff_window_source(); + window->set_level(_level); + window->set_settings(_settings); + window->on_item_selected += on_item_selected; + window->on_light_selected += on_light_selected; + window->on_trigger_selected += on_trigger_selected; + window->on_level_selected += on_level_selected; + window->on_camera_sink_selected += on_camera_sink_selected; + window->on_static_mesh_selected += on_static_mesh_selected; + window->on_sound_source_selected += on_sound_source_selected; + window->on_room_selected += on_room_selected; + window->on_sector_selected += on_sector_selected; + window->on_settings += on_settings; + return add_window(window); + } + + std::optional DiffWindowManager::process_message(UINT message, WPARAM wParam, LPARAM) + { + if (message == WM_COMMAND && LOWORD(wParam) == ID_WINDOWS_DIFF) + { + create_window(); + } + return {}; + } + + void DiffWindowManager::render() + { + WindowManager::render(); + } + + void DiffWindowManager::set_level(const std::weak_ptr& level) + { + _level = level; + for (auto& window : _windows) + { + window.second->set_level(level); + } + } + + void DiffWindowManager::set_settings(const UserSettings& settings) + { + _settings = settings; + for (auto& window : _windows) + { + window.second->set_settings(settings); + } + } +} + diff --git a/trview.app/Windows/Diff/DiffWindowManager.h b/trview.app/Windows/Diff/DiffWindowManager.h new file mode 100644 index 000000000..d59983365 --- /dev/null +++ b/trview.app/Windows/Diff/DiffWindowManager.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include "../WindowManager.h" +#include "IDiffWindowManager.h" +#include "IDiffWindow.h" +#include "../../Settings/UserSettings.h" + +namespace trview +{ + class DiffWindowManager final : public IDiffWindowManager, public WindowManager, public MessageHandler + { + public: + DiffWindowManager(const Window& window, const std::shared_ptr& shortcuts, const IDiffWindow::Source& diff_window_source); + virtual ~DiffWindowManager() = default; + std::weak_ptr create_window() override; + std::optional process_message(UINT message, WPARAM wParam, LPARAM lParam) override; + void render() override; + void set_level(const std::weak_ptr& level) override; + void set_settings(const UserSettings& settings) override; + private: + IDiffWindow::Source _diff_window_source; + std::weak_ptr _level; + UserSettings _settings; + }; +} diff --git a/trview.app/Windows/Diff/IDiffWindow.h b/trview.app/Windows/Diff/IDiffWindow.h new file mode 100644 index 000000000..f3c276db5 --- /dev/null +++ b/trview.app/Windows/Diff/IDiffWindow.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace trview +{ + struct ICameraSink; + struct IItem; + struct ILevel; + struct ILight; + struct ISoundSource; + struct ITrigger; + struct IRoom; + struct ISector; + struct UserSettings; + + struct IDiffWindow + { + using Source = std::function()>; + + virtual ~IDiffWindow() = 0; + virtual void render() = 0; + virtual void set_level(const std::weak_ptr& level) = 0; + virtual void set_number(int32_t number) = 0; + virtual void set_settings(const UserSettings& settings) = 0; + /// + /// Event raised when the window is closed. + /// + Event<> on_window_closed; + + Event> on_item_selected; + Event> on_light_selected; + Event> on_trigger_selected; + Event> on_level_selected; + Event> on_camera_sink_selected; + Event> on_static_mesh_selected; + Event> on_sound_source_selected; + Event> on_room_selected; + Event> on_sector_selected; + Event on_settings; + }; +} \ No newline at end of file diff --git a/trview.app/Windows/Diff/IDiffWindowManager.h b/trview.app/Windows/Diff/IDiffWindowManager.h new file mode 100644 index 000000000..a4a4b078c --- /dev/null +++ b/trview.app/Windows/Diff/IDiffWindowManager.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +namespace trview +{ + struct ICameraSink; + struct IDiffWindow; + struct IItem; + struct ILevel; + struct ILight; + struct ISoundSource; + struct IStaticMesh; + struct ITrigger; + struct IRoom; + struct ISector; + struct UserSettings; + + struct IDiffWindowManager + { + virtual ~IDiffWindowManager() = 0; + virtual std::weak_ptr create_window() = 0; + virtual void render() = 0; + virtual void set_level(const std::weak_ptr& level) = 0; + virtual void set_settings(const UserSettings& settings) = 0; + + Event> on_item_selected; + Event> on_light_selected; + Event> on_trigger_selected; + Event> on_level_selected; + Event> on_camera_sink_selected; + Event> on_static_mesh_selected; + Event> on_sound_source_selected; + Event> on_room_selected; + Event> on_sector_selected; + Event on_settings; + }; +} diff --git a/trview.app/Windows/IWindows.h b/trview.app/Windows/IWindows.h index 551cea0f6..030efdaad 100644 --- a/trview.app/Windows/IWindows.h +++ b/trview.app/Windows/IWindows.h @@ -38,6 +38,7 @@ namespace trview virtual void setup(const UserSettings& settings) = 0; Event> on_camera_sink_selected; + Event> on_diff_level_selected; Event> on_item_selected; Event on_level_switch; Event> on_light_selected; @@ -53,7 +54,9 @@ namespace trview Event> on_static_selected; Event> on_trigger_selected; Event> on_waypoint_selected; + Event> on_sector_selected; Event<> on_route_window_created; Event<> on_scene_changed; + Event on_settings; }; } diff --git a/trview.app/Windows/ItemsWindow.cpp b/trview.app/Windows/ItemsWindow.cpp index 09f153ae9..d1ed147c2 100644 --- a/trview.app/Windows/ItemsWindow.cpp +++ b/trview.app/Windows/ItemsWindow.cpp @@ -307,7 +307,7 @@ namespace trview { [](auto&& l, auto&& r) { return l.number() < r.number(); }, [](auto&& l, auto&& r) { return std::tuple(trigger_room(l), l.number()) < std::tuple(trigger_room(r), r.number()); }, - [](auto&& l, auto&& r) { return std::tuple(trigger_type_name(l.type()), l.number()) < std::tuple(trigger_type_name(r.type()), r.number()); }, + [](auto&& l, auto&& r) { return std::tuple(to_string(l.type()), l.number()) < std::tuple(to_string(r.type()), r.number()); }, }, _force_sort); for (auto& trigger : _triggered_by) @@ -330,7 +330,7 @@ namespace trview ImGui::TableNextColumn(); ImGui::Text(std::to_string(trigger_room(trigger_ptr)).c_str()); ImGui::TableNextColumn(); - ImGui::Text(trigger_type_name(trigger_ptr->type()).c_str()); + ImGui::Text(to_string(trigger_ptr->type()).c_str()); } ImGui::EndTable(); diff --git a/trview.app/Windows/LightsWindow.cpp b/trview.app/Windows/LightsWindow.cpp index ed117d0a5..f51bc5842 100644 --- a/trview.app/Windows/LightsWindow.cpp +++ b/trview.app/Windows/LightsWindow.cpp @@ -133,7 +133,7 @@ namespace trview { [](auto&& l, auto&& r) { return l.number() < r.number(); }, [](auto&& l, auto&& r) { return std::tuple(light_room(l), l.number()) < std::tuple(light_room(r), r.number()); }, - [](auto&& l, auto&& r) { return std::tuple(light_type_name(l.type()), l.number()) < std::tuple(light_type_name(r.type()), r.number()); }, + [](auto&& l, auto&& r) { return std::tuple(to_string(l.type()), l.number()) < std::tuple(to_string(r.type()), r.number()); }, [](auto&& l, auto&& r) { return std::tuple(l.visible(), l.number()) < std::tuple(r.visible(), r.number()); } }, _force_sort); @@ -168,7 +168,7 @@ namespace trview ImGui::TableNextColumn(); ImGui::Text(std::to_string(light_room(light)).c_str()); ImGui::TableNextColumn(); - ImGui::Text(light_type_name(light->type()).c_str()); + ImGui::Text(to_string(light->type()).c_str()); ImGui::TableNextColumn(); bool hidden = !light->visible(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); @@ -234,7 +234,7 @@ namespace trview return std::format("R: {}, G: {}, B: {}", static_cast(colour.r * 255), static_cast(colour.g * 255), static_cast(colour.b * 255)); }; - add_stat("Type", light_type_name(selected_light->type())); + add_stat("Type", to_string(selected_light->type())); add_stat("#", selected_light->number()); add_stat("Room", light_room(selected_light)); @@ -320,10 +320,10 @@ namespace trview { if (auto light_ptr = light.lock()) { - available_types.insert(light_type_name(light_ptr->type())); + available_types.insert(to_string(light_ptr->type())); } } - _filters.add_getter("Type", { available_types.begin(), available_types.end() }, [](auto&& light) { return light_type_name(light.type()); }); + _filters.add_getter("Type", { available_types.begin(), available_types.end() }, [](auto&& light) { return to_string(light.type()); }); _filters.add_getter("#", [](auto&& light) { return static_cast(light.number()); }); _filters.add_getter("Room", [](auto&& light) { return static_cast(light_room(light)); }); _filters.add_getter("X", [](auto&& light) { return light.position().x * trlevel::Scale_X; }, has_position); @@ -379,7 +379,7 @@ namespace trview { _column_sizer.measure(std::format("{0}", light_ptr->number()), 0); _column_sizer.measure(std::to_string(light_room(*light_ptr)), 1); - _column_sizer.measure(light_type_name(light_ptr->type()), 2); + _column_sizer.measure(to_string(light_ptr->type()), 2); } } } diff --git a/trview.app/Windows/RoomsWindow.cpp b/trview.app/Windows/RoomsWindow.cpp index 9f2e54ad6..86765f6a7 100644 --- a/trview.app/Windows/RoomsWindow.cpp +++ b/trview.app/Windows/RoomsWindow.cpp @@ -614,7 +614,7 @@ namespace trview { if (auto trigger_ptr = trigger.lock()) { - available_trigger_types.insert(trigger_type_name(trigger_ptr->type())); + available_trigger_types.insert(to_string(trigger_ptr->type())); } } } @@ -626,7 +626,7 @@ namespace trview { if (const auto trigger_ptr = trigger.lock()) { - results.push_back(trigger_type_name(trigger_ptr->type())); + results.push_back(to_string(trigger_ptr->type())); } } return results; @@ -913,7 +913,7 @@ namespace trview imgui_sort_weak(_triggers, { [](auto&& l, auto&& r) { return l.number() < r.number(); }, - [&](auto&& l, auto&& r) { return std::tuple(trigger_type_name(l.type()), l.number()) < std::tuple(trigger_type_name(r.type()), r.number()); } + [&](auto&& l, auto&& r) { return std::tuple(to_string(l.type()), l.number()) < std::tuple(to_string(r.type()), r.number()); } }, _force_sort); for (const auto& trigger : _triggers) @@ -941,7 +941,7 @@ namespace trview } ImGui::TableNextColumn(); - ImGui::Text(trigger_type_name(trigger_ptr->type()).c_str()); + ImGui::Text(to_string(trigger_ptr->type()).c_str()); } } @@ -1130,7 +1130,7 @@ namespace trview imgui_sort_weak(_lights, { [](auto&& l, auto&& r) { return l.number() < r.number(); }, - [&](auto&& l, auto&& r) { return std::tuple(light_type_name(l.type()), l.number()) < std::tuple(light_type_name(r.type()), r.number()); } + [&](auto&& l, auto&& r) { return std::tuple(to_string(l.type()), l.number()) < std::tuple(to_string(r.type()), r.number()); } }, _force_sort); for (const auto& light : _lights) @@ -1158,7 +1158,7 @@ namespace trview } ImGui::TableNextColumn(); - ImGui::Text(light_type_name(light_ptr->type()).c_str()); + ImGui::Text(to_string(light_ptr->type()).c_str()); } } diff --git a/trview.app/Windows/RouteWindow.cpp b/trview.app/Windows/RouteWindow.cpp index 8267c1ce5..9fd2376b8 100644 --- a/trview.app/Windows/RouteWindow.cpp +++ b/trview.app/Windows/RouteWindow.cpp @@ -557,7 +557,7 @@ namespace trview { if (auto trigger = _all_triggers[waypoint.index()].lock()) { - return trigger_type_name(trigger->type()); + return to_string(trigger->type()); } } else diff --git a/trview.app/Windows/TriggersWindow.cpp b/trview.app/Windows/TriggersWindow.cpp index 6c93b0dc8..685cca1ba 100644 --- a/trview.app/Windows/TriggersWindow.cpp +++ b/trview.app/Windows/TriggersWindow.cpp @@ -203,7 +203,7 @@ namespace trview { [](auto&& l, auto&& r) { return l.number() < r.number(); }, [](auto&& l, auto&& r) { return std::tuple(trigger_room(l), l.number()) < std::tuple(trigger_room(r), r.number()); }, - [](auto&& l, auto&& r) { return std::tuple(trigger_type_name(l.type()), l.number()) < std::tuple(trigger_type_name(r.type()), r.number()); }, + [](auto&& l, auto&& r) { return std::tuple(to_string(l.type()), l.number()) < std::tuple(to_string(r.type()), r.number()); }, [](auto&& l, auto&& r) { return std::tuple(l.visible(), l.number()) < std::tuple(r.visible(), r.number()); } }, _force_sort); @@ -242,7 +242,7 @@ namespace trview ImGui::TableNextColumn(); ImGui::Text(std::to_string(trigger_room(trigger_ptr)).c_str()); ImGui::TableNextColumn(); - ImGui::Text(trigger_type_name(trigger_ptr->type()).c_str()); + ImGui::Text(to_string(trigger_ptr->type()).c_str()); ImGui::TableNextColumn(); bool hidden = !trigger_ptr->visible(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); @@ -319,7 +319,7 @@ namespace trview return std::format("{:.0f}, {:.0f}, {:.0f}", p.x, p.y, p.z); }; - add_stat("Type", trigger_type_name(selected_trigger->type())); + add_stat("Type", to_string(selected_trigger->type())); add_stat("#", selected_trigger->number()); add_stat("Position", position_text()); add_stat("Room", trigger_room(selected_trigger)); @@ -462,10 +462,10 @@ namespace trview { if (auto trigger_ptr = trigger.lock()) { - available_types.insert(trigger_type_name(trigger_ptr->type())); + available_types.insert(to_string(trigger_ptr->type())); } } - _filters.add_getter("Type", { available_types.begin(), available_types.end() }, [](auto&& trigger) { return trigger_type_name(trigger.type()); }); + _filters.add_getter("Type", { available_types.begin(), available_types.end() }, [](auto&& trigger) { return to_string(trigger.type()); }); _filters.add_getter("#", [](auto&& trigger) { return static_cast(trigger.number()); }); _filters.add_getter("Room", [](auto&& trigger) { return static_cast(trigger_room(trigger)); }); _filters.add_getter("Flags", [](auto&& trigger) { return format_binary(trigger.flags()); }); @@ -580,7 +580,7 @@ namespace trview { _column_sizer.measure(std::to_string(room->number()), 1); } - _column_sizer.measure(trigger_type_name(trigger_ptr->type()), 2); + _column_sizer.measure(to_string(trigger_ptr->type()), 2); } } } diff --git a/trview.app/Windows/Viewer.cpp b/trview.app/Windows/Viewer.cpp index 586721a33..83cbec47a 100644 --- a/trview.app/Windows/Viewer.cpp +++ b/trview.app/Windows/Viewer.cpp @@ -641,6 +641,12 @@ namespace trview { auto old_level = _level.lock(); auto new_level = level.lock(); + + if (old_level == new_level) + { + return; + } + _level = level; _level_token_store.clear(); diff --git a/trview.app/Windows/Windows.cpp b/trview.app/Windows/Windows.cpp index 1c8115bd4..1339a4388 100644 --- a/trview.app/Windows/Windows.cpp +++ b/trview.app/Windows/Windows.cpp @@ -6,6 +6,7 @@ #include "About/AboutWindowManager.h" #include "CameraSink/ICameraSinkWindowManager.h" #include "Console/IConsoleManager.h" +#include "Diff/IDiffWindowManager.h" #include "IItemsWindowManager.h" #include "ILightsWindowManager.h" #include "Log/ILogWindowManager.h" @@ -29,6 +30,7 @@ namespace trview std::unique_ptr about_window_manager, std::unique_ptr camera_sink_windows, std::unique_ptr console_manager, + std::unique_ptr diff_window_manager, std::shared_ptr items_window_manager, std::unique_ptr lights_window_manager, std::unique_ptr log_window_manager, @@ -40,15 +42,26 @@ namespace trview std::unique_ptr textures_window_manager, std::unique_ptr triggers_window_manager) : _about_windows(std::move(about_window_manager)), _camera_sink_windows(std::move(camera_sink_windows)), _console_manager(std::move(console_manager)), - _items_windows(items_window_manager), _lights_windows(std::move(lights_window_manager)), _log_windows(std::move(log_window_manager)), - _plugins_windows(std::move(plugins_window_manager)), _rooms_windows(std::move(rooms_window_manager)), _route_window(std::move(route_window_manager)), - _sounds_windows(std::move(sounds_window_manager)), _statics_windows(std::move(statics_window_manager)), _textures_windows(std::move(textures_window_manager)), - _triggers_windows(std::move(triggers_window_manager)) + _diff_windows(std::move(diff_window_manager)), _items_windows(items_window_manager), _lights_windows(std::move(lights_window_manager)), + _log_windows(std::move(log_window_manager)), _plugins_windows(std::move(plugins_window_manager)), _rooms_windows(std::move(rooms_window_manager)), + _route_window(std::move(route_window_manager)), _sounds_windows(std::move(sounds_window_manager)), _statics_windows(std::move(statics_window_manager)), + _textures_windows(std::move(textures_window_manager)), _triggers_windows(std::move(triggers_window_manager)) { _camera_sink_windows->on_camera_sink_selected += on_camera_sink_selected; _camera_sink_windows->on_trigger_selected += on_trigger_selected; _camera_sink_windows->on_scene_changed += on_scene_changed; + _diff_windows->on_item_selected += on_item_selected; + _diff_windows->on_light_selected += on_light_selected; + _diff_windows->on_trigger_selected += on_trigger_selected; + _diff_windows->on_level_selected += on_diff_level_selected; + _diff_windows->on_camera_sink_selected += on_camera_sink_selected; + _diff_windows->on_static_mesh_selected += on_static_selected; + _diff_windows->on_sound_source_selected += on_sound_source_selected; + _diff_windows->on_room_selected += on_room_selected; + _diff_windows->on_sector_selected += on_sector_selected; + _diff_windows->on_settings += on_settings; + _token_store += _items_windows->on_add_to_route += [this](auto item) { if (auto item_ptr = item.lock()) @@ -124,6 +137,7 @@ namespace trview _about_windows->render(); _camera_sink_windows->render(); _console_manager->render(); + _diff_windows->render(); _items_windows->render(); _lights_windows->render(); _log_windows->render(); @@ -184,6 +198,7 @@ namespace trview } _camera_sink_windows->set_camera_sinks(new_level->camera_sinks()); + _diff_windows->set_level(new_level); _items_windows->set_items(new_level->items()); _items_windows->set_triggers(new_level->triggers()); _items_windows->set_level_version(new_level->version()); @@ -236,6 +251,7 @@ namespace trview void Windows::set_settings(const UserSettings& settings) { + _diff_windows->set_settings(settings); _rooms_windows->set_map_colours(settings.map_colours); _route_window->set_randomizer_enabled(settings.randomizer_tools); _route_window->set_randomizer_settings(settings.randomizer); diff --git a/trview.app/Windows/Windows.h b/trview.app/Windows/Windows.h index be284dcee..96f3dc456 100644 --- a/trview.app/Windows/Windows.h +++ b/trview.app/Windows/Windows.h @@ -10,6 +10,7 @@ namespace trview struct IAboutWindowManager; struct ICameraSinkWindowManager; struct IConsoleManager; + struct IDiffWindowManager; struct IItemsWindowManager; struct ILightsWindowManager; struct ILogWindowManager; @@ -28,6 +29,7 @@ namespace trview std::unique_ptr about_window_manager, std::unique_ptr camera_sink_windows, std::unique_ptr console_manager, + std::unique_ptr diff_window_manager, std::shared_ptr items_window_manager, std::unique_ptr lights_window_manager, std::unique_ptr log_window_manager, @@ -62,6 +64,7 @@ namespace trview std::unique_ptr _about_windows; std::unique_ptr _camera_sink_windows; std::unique_ptr _console_manager; + std::unique_ptr _diff_windows; std::shared_ptr _items_windows; std::unique_ptr _lights_windows; std::unique_ptr _log_windows; diff --git a/trview.app/trview.app.vcxproj b/trview.app/trview.app.vcxproj index d6d4d4ef1..f24ff939c 100644 --- a/trview.app/trview.app.vcxproj +++ b/trview.app/trview.app.vcxproj @@ -69,6 +69,7 @@ copy ""$(OutDir)*.cso"" ""$(ProjectDir)Resources\Generated"" + /bigobj %(AdditionalOptions) /bigobj %(AdditionalOptions) @@ -81,6 +82,8 @@ copy ""$(OutDir)*.cso"" ""$(ProjectDir)Resources\Generated"" + + @@ -91,6 +94,7 @@ copy ""$(OutDir)*.cso"" ""$(ProjectDir)Resources\Generated"" + @@ -98,6 +102,8 @@ copy ""$(OutDir)*.cso"" ""$(ProjectDir)Resources\Generated"" + + @@ -222,6 +228,10 @@ copy ""$(OutDir)*.cso"" ""$(ProjectDir)Resources\Generated"" + + + + diff --git a/trview.app/trview.app.vcxproj.filters b/trview.app/trview.app.vcxproj.filters index 2e1d220a0..491a18ecf 100644 --- a/trview.app/trview.app.vcxproj.filters +++ b/trview.app/trview.app.vcxproj.filters @@ -362,6 +362,15 @@ Windows\About + + Windows\Diff + + + Windows\Diff + + + Menus + @@ -1201,6 +1210,27 @@ Mocks\Windows + + Windows\Diff + + + Windows\Diff + + + Windows\Diff + + + Windows\Diff + + + Mocks\Windows + + + Mocks\Windows + + + Menus + @@ -1419,6 +1449,9 @@ {9a5c8748-0920-46fd-a893-d0e0f1d748d8} + + {9903f794-d62d-4218-bd6a-23463418328c} + diff --git a/trview.common/Colour.h b/trview.common/Colour.h index 9ab3d51fd..c3e536c13 100644 --- a/trview.common/Colour.h +++ b/trview.common/Colour.h @@ -1,5 +1,6 @@ #pragma once +#include #include namespace trview