Skip to content

Commit

Permalink
Add Categories property to Items (#1202)
Browse files Browse the repository at this point in the history
Add a `Categories` property to `Item`. The user can see this category in the items window and is able to use filters against the category property. This will help for finding all pickups, all enemies or for excluding a certain type of item amongst other uses.

The `Categories` field is accessible from Lua so scripts can alter the categories on an item. The categories are taken as a copy of the initial category values from the type list so they can be safely changed by a script.

Closes #1200
  • Loading branch information
chreden authored Feb 4, 2024
1 parent d05367b commit 2e548e7
Show file tree
Hide file tree
Showing 34 changed files with 5,128 additions and 4,183 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ Clear Body | Whether to remove a dead body when a bodybag trigger is triggered
Invisible | Whether this item is invisible
Flags | Entity activation flags
OCB | Used to change entity behaviour
Categories | Categories to which the item is assigned

## Add To Route
Clicking the `Add to Route` button will insert this item at the current position in the route.
Expand Down
1 change: 1 addition & 0 deletions doc/lua/item.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The Item library provides information about an item in a [level](level.md).
| ---- | ---- | ---- | ---- |
| activation_flags | number | R | Bit flags used in combination with trigger flags |
| angle | number | R | Y rotation in range 0-65535 |
| categories | string[] | RW | Categories that the item belongs to. Initially inherited by trview type list but can be overwrritten |
| clear_body | boolean | R | Flag used together with Clear Bodies trigger action to remove the body of dead enemy from the level to conserve resources |
| invisible | boolean | R | Flag that determines whether the item is invisible in-game or not |
| number | number | R | Item number |
Expand Down
22 changes: 17 additions & 5 deletions trview.app.tests/Elements/ItemTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@ namespace
std::shared_ptr<IMeshStorage> mesh_storage = mock_shared<MockMeshStorage>();
trlevel::tr2_entity entity{};
uint32_t index{ 0u };
bool is_pickup{ false };
std::string type{ "Lara" };
TypeInfo type{ .name = "Lara" };
std::vector<std::weak_ptr<ITrigger>> triggers;
std::shared_ptr<ILevel> owning_level{ mock_shared<MockLevel>() };
std::shared_ptr<IRoom> room { mock_shared<MockRoom>() };

std::unique_ptr<Item> build()
{
return std::make_unique<Item>(mesh_source, *level, entity, *mesh_storage, owning_level, index, type, triggers, is_pickup, room);
return std::make_unique<Item>(mesh_source, *level, entity, *mesh_storage, owning_level, index, type, triggers, room);
}

test_module& with_level(const std::shared_ptr<trlevel::ILevel>& level)
Expand All @@ -41,7 +40,14 @@ namespace

test_module& with_pickup(bool value)
{
this->is_pickup = value;
if (value)
{
type.categories.insert("Pickup");
}
else
{
type.categories.erase("Pickup");
}
return *this;
}

Expand All @@ -50,6 +56,12 @@ namespace
this->entity = entity;
return *this;
}

test_module& with_type_info(const TypeInfo& type_info)
{
this->type = type_info;
return *this;
}
};
return test_module{};
}
Expand Down Expand Up @@ -122,4 +134,4 @@ TEST(Item, MutantEggContentsFlags)
ASSERT_EQ(34, mutant_egg_contents(4));
ASSERT_EQ(22, mutant_egg_contents(8));
ASSERT_EQ(20, mutant_egg_contents(1851));
}
}
1 change: 0 additions & 1 deletion trview.app.tests/Elements/LevelTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <trview.app/Mocks/Graphics/ILevelTextureStorage.h>
#include <trview.app/Mocks/Graphics/IMeshStorage.h>
#include <trview.app/Mocks/Graphics/ISelectionRenderer.h>
#include <trview.app/Mocks/Elements/ITypeNameLookup.h>
#include <trview.app/Mocks/Elements/IItem.h>
#include <trview.app/Mocks/Elements/IRoom.h>
#include <trview.app/Mocks/Elements/ITrigger.h>
Expand Down
85 changes: 85 additions & 0 deletions trview.app.tests/Elements/TypeInfoLookupTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <trview.app/Elements/TypeInfoLookup.h>

using namespace trview;
using namespace trlevel;

// Test that looking up an ID gives the correct result.
TEST(TypeInfoLookup, LookupTR1)
{
std::string json = "{\"games\":{\"tr1\":[{\"id\":123,\"name\":\"Test Name\"}]}}";

TypeInfoLookup lookup(json);

ASSERT_EQ("Test Name", lookup.lookup(LevelVersion::Tomb1, 123, 0).name);
}

// Tests that if there are identical entries for different games, the correct result is returned.
TEST(TypeInfoLookup, LookupMultipleGames)
{
std::string json = "{\"games\":{\"tr1\":[{\"id\":123,\"name\":\"Test Name TR1\"}],\"tr2\":[{\"id\":123,\"name\":\"Test Name TR2\"}]}}";

TypeInfoLookup lookup(json);

ASSERT_EQ("Test Name TR1", lookup.lookup(LevelVersion::Tomb1, 123, 0).name);
ASSERT_EQ("Test Name TR2", lookup.lookup(LevelVersion::Tomb2, 123, 0).name);
}

// Tests that if the name is missing, it still returns the number.
TEST(TypeInfoLookup, LookupMissingItem)
{
std::string json = "{}";

TypeInfoLookup lookup(json);

ASSERT_EQ("123", lookup.lookup(LevelVersion::Tomb3, 123, 0).name);
}

TEST(TypeInfoLookup, LookupNormalMutantEggs)
{
std::string json = "{\"games\":{\"tr1\":[{\"id\":163,\"name\":\"Test Name 1\"}]}}";
TypeInfoLookup lookup(json);

auto winged = lookup.lookup(trlevel::LevelVersion::Tomb1, 163, 0).name;
auto shooter = lookup.lookup(trlevel::LevelVersion::Tomb1, 163, 1 << 9).name;
auto centaur = lookup.lookup(trlevel::LevelVersion::Tomb1, 163, 2 << 9).name;
auto torso = lookup.lookup(trlevel::LevelVersion::Tomb1, 163, 4 << 9).name;
auto grounded = lookup.lookup(trlevel::LevelVersion::Tomb1, 163, 8 << 9).name;
auto def = lookup.lookup(trlevel::LevelVersion::Tomb1, 163, 3 << 9).name;

ASSERT_EQ(winged, "Mutant Egg (Winged)");
ASSERT_EQ(shooter, "Mutant Egg (Shooter)");
ASSERT_EQ(centaur, "Mutant Egg (Centaur)");
ASSERT_EQ(torso, "Mutant Egg (Torso)");
ASSERT_EQ(grounded, "Mutant Egg (Grounded)");
ASSERT_EQ(def, "Mutant Egg (Winged)");
}

TEST(TypeInfoLookup, LookupBigMutantEggs)
{
std::string json = "{\"games\":{\"tr1\":[{\"id\":181,\"name\":\"Test Name 2\"}]}}";
TypeInfoLookup lookup(json);

auto winged = lookup.lookup(trlevel::LevelVersion::Tomb1, 181, 0).name;
auto shooter = lookup.lookup(trlevel::LevelVersion::Tomb1, 181, 1 << 9).name;
auto centaur = lookup.lookup(trlevel::LevelVersion::Tomb1, 181, 2 << 9).name;
auto torso = lookup.lookup(trlevel::LevelVersion::Tomb1, 181, 4 << 9).name;
auto grounded = lookup.lookup(trlevel::LevelVersion::Tomb1, 181, 8 << 9).name;
auto def = lookup.lookup(trlevel::LevelVersion::Tomb1, 181, 3 << 9).name;

ASSERT_EQ(winged, "Mutant Egg (Winged)");
ASSERT_EQ(shooter, "Mutant Egg (Shooter)");
ASSERT_EQ(centaur, "Mutant Egg (Centaur)");
ASSERT_EQ(torso, "Mutant Egg (Torso)");
ASSERT_EQ(grounded, "Mutant Egg (Grounded)");
ASSERT_EQ(def, "Mutant Egg (Winged)");
}

TEST(TypeInfoLookup, LookupNormalMutantEggsTR2)
{
std::string json = "{\"games\":{\"tr1\":[{\"id\":163,\"name\":\"Test Name 1\"}],\"tr2\":[{\"id\":163,\"name\":\"Test Name 2\"}]}}";
TypeInfoLookup lookup(json);

auto winged = lookup.lookup(trlevel::LevelVersion::Tomb2, 163, 0).name;

ASSERT_EQ(winged, "Test Name 2");
}
85 changes: 0 additions & 85 deletions trview.app.tests/Elements/TypeNameLookupTests.cpp

This file was deleted.

34 changes: 34 additions & 0 deletions trview.app.tests/Lua/Elements/Lua_ItemTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,25 @@ TEST(Lua_Item, Angle)
ASSERT_EQ(123, lua_tointeger(L, -1));
}

TEST(Lua_Item, Categories)
{
auto item = mock_shared<MockItem>();
EXPECT_CALL(*item, categories).WillRepeatedly(Return<std::unordered_set<std::string>>({ "One", "Two" }));

LuaState L;
lua::create_item(L, item);
lua_setglobal(L, "i");

ASSERT_EQ(0, luaL_dostring(L, "return i.categories"));
ASSERT_EQ(LUA_TTABLE, lua_type(L, -1));
ASSERT_EQ(0, luaL_dostring(L, "return i.categories[1]"));
ASSERT_EQ(LUA_TSTRING, lua_type(L, -1));
ASSERT_STREQ("One", lua_tostring(L, -1));
ASSERT_EQ(0, luaL_dostring(L, "return i.categories[2]"));
ASSERT_EQ(LUA_TSTRING, lua_type(L, -1));
ASSERT_STREQ("Two", lua_tostring(L, -1));
}

TEST(Lua_Item, ClearBody)
{
auto item = mock_shared<MockItem>();
Expand Down Expand Up @@ -201,6 +220,21 @@ TEST(Lua_Item, Visible)
ASSERT_EQ(true, lua_toboolean(L, -1));
}

TEST(Lua_Item, SetCategories)
{
std::unordered_set<std::string> categories;
auto item = mock_shared<MockItem>();
EXPECT_CALL(*item, set_categories).WillOnce(SaveArg<0>(&categories));

LuaState L;
lua::create_item(L, item);
lua_setglobal(L, "i");

ASSERT_EQ(0, luaL_dostring(L, "i.categories = { \"One\", \"Two\" }"));

const std::unordered_set<std::string> expected{ "One", "Two" };
ASSERT_EQ(categories, expected);
}
TEST(Lua_Item, SetVisible)
{
auto level = mock_shared<MockLevel>();
Expand Down
2 changes: 1 addition & 1 deletion trview.app.tests/stdafx.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ namespace testing
#include <trview.app/Mocks/Elements/ILevel.h>
#include <trview.app/Mocks/Elements/IStaticMesh.h>
#include <trview.app/Mocks/Elements/ITrigger.h>
#include <trview.app/Mocks/Elements/ITypeNameLookup.h>
#include <trview.app/Mocks/Elements/ITypeInfoLookup.h>
#include <trview.app/Mocks/Menus/IUpdateChecker.h>
#include <trview.app/Mocks/Routing/IRoute.h>
#include <trview.app/Mocks/Routing/IWaypoint.h>
Expand Down
2 changes: 1 addition & 1 deletion trview.app.tests/trview.app.tests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<ClCompile Include="Elements\SectorTests.cpp" />
<ClCompile Include="Elements\StaticMeshTests.cpp" />
<ClCompile Include="Elements\TriggerTests.cpp" />
<ClCompile Include="Elements\TypeNameLookupTests.cpp" />
<ClCompile Include="Elements\TypeInfoLookupTests.cpp" />
<ClCompile Include="Filters\FiltersTests.cpp" />
<ClCompile Include="FreeCameraTests.cpp" />
<ClCompile Include="Graphics\LevelTextureStorageTests.cpp" />
Expand Down
2 changes: 1 addition & 1 deletion trview.app.tests/trview.app.tests.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<ClCompile Include="UI\CameraPositionTests.cpp">
<Filter>UI</Filter>
</ClCompile>
<ClCompile Include="Elements\TypeNameLookupTests.cpp">
<ClCompile Include="Elements\TypeInfoLookupTests.cpp">
<Filter>Elements</Filter>
</ClCompile>
<ClCompile Include="Elements\LevelTests.cpp">
Expand Down
4 changes: 2 additions & 2 deletions trview.app/Application.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include <trlevel/ILevel.h>

#include <trview.app/Elements/ITypeNameLookup.h>
#include "Elements/ITypeInfoLookup.h"
#include <trview.app/Menus/IFileMenu.h>
#include <trview.app/Menus/IUpdateChecker.h>
#include <trview.app/Menus/ViewMenu.h>
Expand Down Expand Up @@ -149,7 +149,7 @@ namespace trview
HINSTANCE _instance{ nullptr };

// Level data components
std::unique_ptr<ITypeNameLookup> _type_name_lookup;
std::unique_ptr<ITypeInfoLookup> _type_info_lookup;
std::shared_ptr<ILevel> _level;
ILevel::Source _level_source;

Expand Down
9 changes: 4 additions & 5 deletions trview.app/ApplicationCreate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include "Resources/DefaultTextures.h"
#include "Resources/DefaultFonts.h"

#include "Elements/TypeNameLookup.h"
#include "Elements/TypeInfoLookup.h"
#include "Elements/CameraSink/CameraSink.h"
#include "Elements/Item.h"
#include "Elements/Light.h"
Expand Down Expand Up @@ -155,7 +155,7 @@ namespace trview
auto font_factory = std::make_shared<graphics::FontFactory>();

Resource type_list = get_resource_memory(IDR_TYPE_NAMES, L"TEXT");
auto type_name_lookup = std::make_shared<TypeNameLookup>(std::string(type_list.data, type_list.data + type_list.size));
auto type_info_lookup = std::make_shared<TypeInfoLookup>(std::string(type_list.data, type_list.data + type_list.size));

load_default_shaders(device, shader_storage);
load_default_fonts(device, font_factory);
Expand Down Expand Up @@ -210,15 +210,14 @@ namespace trview
auto entity_source = [=](auto&& level, auto&& entity, auto&& index, auto&& triggers, auto&& mesh_storage, auto&& owning_level, auto&& room)
{
return std::make_shared<Item>(mesh_source, level, entity, mesh_storage, owning_level, index,
type_name_lookup->lookup_type_name(level.get_version(), entity.TypeID, entity.Flags),
type_info_lookup->lookup(level.get_version(), entity.TypeID, entity.Flags),
triggers,
type_name_lookup->is_pickup(level.get_version(), entity.TypeID),
room);
};

auto ai_source = [=](auto&& level, auto&& entity, auto&& index, auto&& mesh_storage, auto&& owning_level, auto&& room)
{
return std::make_shared<Item>(mesh_source, level, entity, mesh_storage, owning_level, index, type_name_lookup->lookup_type_name(level.get_version(), entity.type_id, entity.flags), std::vector<std::weak_ptr<ITrigger>>{}, room);
return std::make_shared<Item>(mesh_source, level, entity, mesh_storage, owning_level, index, type_info_lookup->lookup(level.get_version(), entity.type_id, entity.flags), std::vector<std::weak_ptr<ITrigger>>{}, room);
};

auto log = std::make_shared<Log>();
Expand Down
3 changes: 3 additions & 0 deletions trview.app/Elements/IItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <cstdint>
#include <string>
#include <unordered_set>
#include <external/DirectXTK/Inc/SimpleMath.h>
#include <trlevel/ILevel.h>
#include <trlevel/trtypes.h>
Expand Down Expand Up @@ -46,6 +47,8 @@ namespace trview
virtual DirectX::SimpleMath::Vector3 position() const = 0;
virtual std::weak_ptr<ILevel> level() const = 0;
virtual int32_t angle() const = 0;
virtual std::unordered_set<std::string> categories() const = 0;
virtual void set_categories(const std::unordered_set<std::string>& categories) = 0;
};

bool is_mutant_egg(const IItem& item);
Expand Down
Loading

0 comments on commit 2e548e7

Please sign in to comment.