Skip to content

feat: add Items and Item Templates support - #490

Merged
kirre-bylund merged 7 commits into
release/v8.2.0from
feat/items-item-templates
Sep 30, 2026
Merged

kirre-bylund merged 7 commits into
release/v8.2.0from
feat/items-item-templates

Conversation

@kirre-bylund

@kirre-bylund kirre-bylund commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds SDK support for the new inventory system, publicly named Items and Item Templates.

Endpoints added (base player/inventory/v1)

Method Path SDK method
GET / ListItemTemplates
GET /my ListPlayerInventoryItems
GET /{id} GetPlayerInventoryItem
DELETE /{id} DeletePlayerInventoryItem
POST /{id}/consume ConsumePlayerInventoryItem
POST /{id}/split SplitPlayerInventoryItemStack
POST /merge MergePlayerInventoryItemStacks

Nomenclature

  • Item Template — the conceptual base definition (wire: Template / item_template_id).
  • Item — an instantiation of an Item Template held by a player (wire: Inventory / inventory_id).
  • Player Inventory Item — the public SDK name for an Item held by a player.

The public API carries the "inventory" concept through to the end user, so the methods are named
*PlayerInventoryItem*. The metadata source is inventory_item (renamed from inventory; the
numeric enum value is unchanged).

These methods are unrelated to the pre-existing inventory methods. GetInventory,
ListPlayerInventory, ListCharacterInventory, GetHeroInventory,
GrantAssetToPlayerInventory and friends deal with assets and asset instances. The new
*PlayerInventoryItem* methods deal with items and item templates. Both families share the
player/inventory URL prefix (the new one is versioned v1), which is why the doc comments on
both families now state the distinction explicitly.

Item templates are not inventory, so ListItemTemplates and the template/type/sort-field/reward
types keep their names.

Implementation notes

  • Pagination: list responses use LootLockerExtendedPagination. The backend returns an extended offset shape (per_page, offset, total, last_page, current_page, next_page, prev_page, errors), not the simple page/per_page/total.
  • Metadata: typed as LootLockerMetadataEntry[] (the wire shape is an array of {key, value, type, access, tags}).
  • consumable filter: exposed as bool? so "no filter" remains reachable. The backend only applies the filter when the query value is non-empty, so always sending true/false would silently force a filter.
  • granted[] on consume matches the backend GrantResult shape: {source_id, count, type, name, code}.
  • Typed DTOs: item_type is LootLockerItemType, granted[].type is LootLockerRewardKind, and timestamps are DateTime / DateTime?. game_id is omitted — it is redundant in a game-context SDK.
  • Query parameters are only sent when they carry a value, so the backend defaults apply.
  • ConsumePlayerInventoryItem: omitting count consumes one item, not the whole stack. To consume an entire stack, pass the item's current count.
  • All seven methods live directly in LootLockerSDKManager.cs; there is no partial-class indirection.

Functional tests

Tests/LootLockerTests/PlayMode/ItemTests.cs adds 10 PlayMode tests covering the full lifecycle, with all setup performed through the admin API (Tests/LootLockerTestUtils/LootLockerTestConfigurationItem.cs):

  • Items_ListItemTemplates_ReturnsCreatedTemplate — LootLockerCIFast
  • Items_ListPlayerInventoryItems_ReturnsGrantedItem
  • Items_ListPlayerInventoryItems_WithFilters_ReturnsFilteredItems
  • Items_GetPlayerInventoryItem_ReturnsItemWithTemplate
  • Items_ConsumePlayerInventoryItem_PartialStack_DecrementsCount
  • Items_ConsumePlayerInventoryItem_WholeStack_RemovesItem
  • Items_DeletePlayerInventoryItem_RemovesItem
  • Items_DeletePlayerInventoryItem_NotDeletable_ReturnsForbidden
  • Items_SplitPlayerInventoryItemStack_CreatesSecondStack
  • Items_MergePlayerInventoryItemStacks_CombinesCounts

Only the happy-path template test carries LootLockerCIFast; the rest are LootLockerCI.

Verification

  • Functional tests pass against a live local backend: 15/15 (the 10 above plus 5 pre-existing cases matched by the filter).
  • Metadata tests: 8/8 (regression check on the renamed metadata source enum).
  • DTO shapes verified against the authoritative go-backend source rather than the ApiDog spec, which was stale for several of these endpoints.

Note for reviewers: the local devenv database was missing item_template.deletable, so template creation returned HTTP 500 until the column was added. devenv/tables/schema.sql is a stale subset (322 tables) of the production DDL (334 tables); mise run get-ddl in devenv refreshes it. go-backend's 1_ddl.up.sql is already correct and byte-identical to production, so no backend change was needed.

Related

  • Tracking issue: lootlocker/index#1359
  • Companion PRs: lootlocker/unreal-sdk, lootlocker/unreal-server-sdk

Adds SDK support for the new inventory system (internally "Assets 2.0"),
publicly named Items and Item Templates.

Game API endpoints (base player/inventory/v1):
- GET    /             ListItemTemplates
- GET    /my           ListPlayerItems
- GET    /{id}         GetPlayerItem
- DELETE /{id}         DeletePlayerItem
- POST   /{id}/consume ConsumePlayerItem
- POST   /{id}/split   SplitPlayerItemStack
- POST   /merge        MergePlayerItemStacks

Notes:
- List responses use LootLockerExtendedPagination (the backend returns an
  extended offset pagination shape, not the simple page/per_page/total).
- metadata is typed as LootLockerMetadataEntry[].
- consumable is a nullable bool so "no filter" remains reachable; the backend
  only applies the filter when the query value is non-empty.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 25, 2026 08:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Fix nullable count serialization and address the requested metadata, documentation, and test coverage gaps.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity · 2 Low severity

Open (3)
What changed in this PR

Adds Unity SDK support for Items and Item Templates inventory APIs.

Changes:

  • Added item/template DTOs, requests, responses, and pagination.
  • Added list, retrieve, delete, consume, split, and merge APIs.
  • Registered inventory endpoints and public SDK methods.
File Summary
Runtime/​Game/​Requests/​ItemRequest.cs Implements item models and API requests; address nullable count serialization, add the Unity .meta file, and add JSON coverage.
Runtime/​Game/​LootLockerSDKManager.cs Exposes public item APIs; align pagination documentation with callable signatures and add endpoint tests.
Runtime/​Client/​LootLockerEndPoints.cs Defines the new Items endpoint routes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
Comment thread Runtime/Game/LootLockerSDKManager.cs
Comment thread Runtime/Game/LootLockerSDKManager.cs
- Add ShouldSerializecount() to LootLockerConsumeItemRequest so an unset count is
  omitted entirely. This removes the Newtonsoft/ZeroDep asymmetry (only ZeroDep
  skips nulls) and avoids sending {"count":null}, which cannot decode into uint64.
- Correct the consume count documentation: the backend defaults to 1, not the
  whole stack. To consume an entire stack, pass the item's current count.
- Remove the incorrect "Defaults to 1." / "Defaults to 25." claims from the
  page/perPage docs on ListItemTemplates and ListPlayerItems; both are required.
- Add the missing ItemRequest.cs.meta file.
- Add inventory and item_template to LootLockerMetadataSources and correct the
  misleading item comment (item is the legacy source, inventory is Assets 2.0).
- Add 7 serialization/deserialization tests covering the new item endpoints.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kirre-bylund

Copy link
Copy Markdown
Contributor Author

I'm missing functional testing of this feature. We should add some (and use the admin api parts to do the required set up of course, I've attached the current api spec for relevant endpoints). admin api.json

@kirre-bylund kirre-bylund left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bunch of things that need fixing. The same probably goes for the unreal and unreal server implementations as well.

Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
Comment thread Runtime/Game/Requests/ItemRequest.cs Outdated
kirre-bylund and others added 2 commits September 28, 2026 12:31
Address PR review feedback on the Items / Item Templates DTOs:

- Remove game_id from item and item template DTOs; the SDK always
  operates in a game context so the field is redundant.
- Type item_type as LootLockerItemType instead of string.
- Type created_at / updated_at as DateTime (updated_at nullable, since
  the backend omits it when unset).
- Type granted item `type` as the new LootLockerRewardKind enum.
- Add `source` to LootLockerItemSortField.
- Only append query parameters when they carry a meaningful value, so
  unset filters are not sent as empty/zero values.
- Inline the seven item methods from the LootLockerAPIManager partial
  class into LootLockerSDKManager, matching the convention used by the
  rest of the SDK.
- Update the item JSON tests for the new types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add PlayMode coverage for the seven item endpoints against a live backend,
using the admin API for setup (create template with a public audience, grant
to the session player).

- Add admin endpoint constants for the item template and player inventory
  routes.
- Add a LootLockerTestItems helper mirroring the go-backend admin routes
  rather than the published ApiDog spec, which is stale for this feature:
  the list endpoint returns `templates` (not `items`), grant returns `id`
  (not `inventory_id`), consume requires a body and returns a count (not a
  bool), and the player inventory listing uses `template_name` and has no
  `deletable`.
- Add ItemTests with 10 cases covering list templates, list player items
  (with and without filters), get, partial and whole-stack consume, split,
  merge, delete, and the forbidden delete of a non-deletable item.
- Only the happy-path test carries LootLockerCIFast; the rest are
  LootLockerCI only.

Also correct the ConsumePlayerItem doc comment: a null count consumes a
single item, not the whole stack, matching the backend default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kirre-bylund

Copy link
Copy Markdown
Contributor Author

Functional tests added in 28760ee.

Tests/LootLockerTests/PlayMode/ItemTests.cs contains 10 PlayMode tests covering the full lifecycle, with all setup done through the admin API as you suggested (Tests/LootLockerTestUtils/LootLockerTestConfigurationItem.cs wraps the admin item-template endpoints):

  • Items_ListItemTemplates_ReturnsCreatedTemplate — LootLockerCIFast
  • Items_ListPlayerItems_ReturnsGrantedItem
  • Items_ListPlayerItems_WithFilters_ReturnsFilteredItems
  • Items_GetPlayerItem_ReturnsItemWithTemplate
  • Items_ConsumePlayerItem_PartialStack_DecrementsCount
  • Items_ConsumePlayerItem_WholeStack_RemovesItem
  • Items_DeletePlayerItem_RemovesItem
  • Items_DeletePlayerItem_NotDeletable_ReturnsForbidden
  • Items_SplitPlayerItemStack_CreatesSecondStack
  • Items_MergePlayerItemStacks_CombinesCounts

Only the happy-path template test carries LootLockerCIFast; the rest are LootLockerCI.

Verified against a live local backend: 15/15 passed (the 10 above plus 5 pre-existing cases matched by the filter).

One thing worth flagging from that run: the local devenv database was missing item_template.deletable, so template creation returned HTTP 500 until the column was added. devenv/tables/schema.sql is a stale subset (322 tables) of the production DDL (334 tables) — mise run get-ddl in devenv refreshes it. go-backend's 1_ddl.up.sql is already correct and byte-identical to production, so no backend change was needed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved moderate issues remain in the public API and item filter test coverage.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
Resolved since last review (3)
Files not reviewed (3)
  • Runtime/Game/Requests/ItemRequest.cs.meta: Generated file
  • Tests/LootLockerTestUtils/LootLockerTestConfigurationItem.cs.meta: Generated file
  • Tests/LootLockerTests/PlayMode/ItemTests.cs.meta: Generated file

Comment thread Runtime/Game/LootLockerSDKManager.cs
Comment thread Runtime/Game/Requests/MetadataRequests.cs Outdated
kirre-bylund and others added 2 commits September 30, 2026 07:16
Carry the "inventory" concept through to the end user for the Assets 2.0
(Items / Item Templates) surface, and align the metadata source with the
backend rename.

- Metadata source `inventory` -> `inventory_item` (numeric value unchanged).
- Public methods renamed, e.g. `ListPlayerItems` -> `ListPlayerInventoryItems`,
  `GetPlayerItem` -> `GetPlayerInventoryItem`, `ConsumePlayerItem` ->
  `ConsumePlayerInventoryItem`, `DeletePlayerItem` -> `DeletePlayerInventoryItem`,
  `SplitPlayerItemStack` -> `SplitPlayerInventoryItemStack`,
  `MergePlayerItemStacks` -> `MergePlayerInventoryItemStacks`,
  `GrantItemToPlayer` -> `GrantItemToPlayerInventory`.
- DTOs renamed to match, e.g. `LootLockerItem` -> `LootLockerInventoryItem`.
- Endpoint constants and the test surface renamed to match.

These methods deal with items and item templates. They are unrelated to the
pre-existing asset/asset-instance inventory methods (`GetInventory`,
`ListPlayerInventory`, `ListCharacterInventory`, ...), which share the
`player/inventory` URL prefix. Doc comments on both families now state this
explicitly.

Item templates are not inventory, so `ListItemTemplates` and the template,
type, sort-field and reward types keep their names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The generated test project did not set `insecureHttpOption`, so every request
to the local devenv (plain http on localhost:8080) failed with
`InvalidOperationException: Insecure connection not allowed`.

The CI workflow sets this for `TARGET_ENVIRONMENT=LOCAL`, but the local runner
script did not, which meant local functional tests could never reach the local
backend. Set it when `-UseLocalEnv` is passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kirre-bylund

Copy link
Copy Markdown
Contributor Author

After a long discussion we decided to go with the "inventory" naming of the concept and accept the temporary name conflict with the old system (temporary since we intend to legacy that system).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Four review findings remain unresolved, including the documented consume overload and local test configuration behavior.

Review effort: Lite
Findings: 2 Medium severity

Open (2)
Resolved since last review (1)
Files not reviewed (3)
  • Runtime/Game/Requests/ItemRequest.cs.meta: Generated file
  • Tests/LootLockerTestUtils/LootLockerTestConfigurationItem.cs.meta: Generated file
  • Tests/LootLockerTests/PlayMode/ItemTests.cs.meta: Generated file

Comment thread .github/scripts/run-tests.ps1
Comment thread Runtime/Game/LootLockerSDKManager.cs
Add the no-count consume overload so callers can use the documented default.
Apply localhost HTTP and command-line routing settings to reused and custom
Unity projects, not just freshly generated test projects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
item = 8, // This is the source for asset instances (player inventory items), while the "asset" source is for the asset in general
item = 8, // This is the source for legacy items, while the "inventory_item" source is for item instances
inventory_item = 9, // This is the source for item instances (player inventory items)
item_template = 10, // This is the source for item templates

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to say thank you for adding these comments, I foresee that I'll have to go back to this part of the code to double check from time to time which was which :P

@kirre-bylund
kirre-bylund merged commit 52dabb9 into release/v8.2.0 Sep 30, 2026
2 checks passed
@kirre-bylund
kirre-bylund deleted the feat/items-item-templates branch September 30, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants