From b8ce13f1d2af5a96d8fd068739d1f2052de6166e Mon Sep 17 00:00:00 2001 From: Zeeshaun Date: Thu, 19 Mar 2026 17:24:10 -0500 Subject: [PATCH] Adding inventory stacking and overflow to gather mechanic --- game/scenes/Levels/location_level.gd | 256 ++++++++++++++---- .../Controllers/InventoryController.cs | 7 +- .../Models/InventoryOwnerResponse.cs | 6 + .../InventoryApi/Services/InventoryStore.cs | 109 +++++--- .../Controllers/LocationsController.cs | 60 ++++ .../Models/InteractLocationObjectResponse.cs | 4 + 6 files changed, 344 insertions(+), 98 deletions(-) diff --git a/game/scenes/Levels/location_level.gd b/game/scenes/Levels/location_level.gd index 3d44459..1e26373 100644 --- a/game/scenes/Levels/location_level.gd +++ b/game/scenes/Levels/location_level.gd @@ -1,9 +1,10 @@ extends Node3D -const CHARACTER_API_URL := "https://pchar.ranaze.com/api/Characters" -const LOCATION_API_URL := "https://ploc.ranaze.com/api/Locations" +const CHARACTER_API_URL := "https://pchar.ranaze.com/api/Characters" +const LOCATION_API_URL := "https://ploc.ranaze.com/api/Locations" +const INVENTORY_API_URL := "https://pinv.ranaze.com/api/inventory" -@export var tile_size := 16.0 +@export var tile_size := 8.0 @export var block_height := 1.0 @export_range(1, 8, 1) var tile_radius := 3 @export var tracked_node_path: NodePath @@ -151,11 +152,12 @@ func _spawn_tile(coord: Vector2i, location_data: Dictionary) -> void: tile_body.add_child(tile) tile.add_child(_create_tile_border()) - if show_tile_labels: - tile_root.add_child(_create_tile_label(String(location_data.get("name", "")))) - _update_tile_object(tile_root, location_data) - - _tile_nodes[coord] = tile_root + if show_tile_labels: + tile_root.add_child(_create_tile_label(String(location_data.get("name", "")))) + _update_tile_object(tile_root, location_data) + _update_tile_inventory(tile_root, location_data) + + _tile_nodes[coord] = tile_root func _update_tile(coord: Vector2i, location_data: Dictionary) -> void: @@ -163,47 +165,77 @@ func _update_tile(coord: Vector2i, location_data: Dictionary) -> void: if tile_root == null: return - if show_tile_labels: - var label := tile_root.get_node_or_null("LocationNameLabel") as Label3D - if label: - label.text = String(location_data.get("name", "")) - - _update_tile_object(tile_root, location_data) + if show_tile_labels: + var label := tile_root.get_node_or_null("LocationNameLabel") as Label3D + if label: + label.text = String(location_data.get("name", "")) + + _update_tile_object(tile_root, location_data) + _update_tile_inventory(tile_root, location_data) -func _update_tile_object(tile_root: Node3D, location_data: Dictionary) -> void: - var existing := tile_root.get_node_or_null("LocationObject") - if existing: - existing.queue_free() - - var object_data_variant: Variant = location_data.get("locationObject", {}) - if typeof(object_data_variant) != TYPE_DICTIONARY: - return - - var object_data := object_data_variant as Dictionary - if object_data.is_empty(): - return - - var object_root := Node3D.new() - object_root.name = "LocationObject" - object_root.position = Vector3(0.0, (block_height * 0.5) + 0.6, 0.0) - tile_root.add_child(object_root) - - var object_mesh := MeshInstance3D.new() - object_mesh.name = "ObjectMesh" - object_mesh.mesh = SphereMesh.new() - object_mesh.scale = Vector3(0.6, 0.4, 0.6) - object_mesh.material_override = _create_object_material(String(object_data.get("objectKey", ""))) - object_root.add_child(object_mesh) - - var object_label := Label3D.new() - object_label.name = "ObjectLabel" - object_label.text = _build_object_label(object_data) - object_label.position = Vector3(0.0, 0.6, 0.0) - object_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED - object_label.pixel_size = 0.01 - object_label.outline_size = 10 - object_root.add_child(object_label) +func _update_tile_object(tile_root: Node3D, location_data: Dictionary) -> void: + var object_data_variant: Variant = location_data.get("locationObject", {}) + if typeof(object_data_variant) != TYPE_DICTIONARY: + var existing_missing := tile_root.get_node_or_null("LocationObject") + if existing_missing: + existing_missing.queue_free() + return + + var object_data := object_data_variant as Dictionary + if object_data.is_empty(): + var existing_empty := tile_root.get_node_or_null("LocationObject") + if existing_empty: + existing_empty.queue_free() + return + + var object_root := tile_root.get_node_or_null("LocationObject") as Node3D + if object_root == null: + object_root = Node3D.new() + object_root.name = "LocationObject" + object_root.position = Vector3(0.0, (block_height * 0.5) + 0.6, 0.0) + tile_root.add_child(object_root) + + var object_mesh := object_root.get_node_or_null("ObjectMesh") as MeshInstance3D + if object_mesh == null: + object_mesh = MeshInstance3D.new() + object_mesh.name = "ObjectMesh" + object_mesh.mesh = SphereMesh.new() + object_mesh.scale = Vector3(0.6, 0.4, 0.6) + object_root.add_child(object_mesh) + object_mesh.material_override = _create_object_material(String(object_data.get("objectKey", ""))) + + var object_label := object_root.get_node_or_null("ObjectLabel") as Label3D + if object_label == null: + object_label = Label3D.new() + object_label.name = "ObjectLabel" + object_label.position = Vector3(0.0, 0.6, 0.0) + object_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED + object_label.pixel_size = 0.01 + object_label.outline_size = 10 + object_root.add_child(object_label) + object_label.text = _build_object_label(object_data) + + +func _update_tile_inventory(tile_root: Node3D, location_data: Dictionary) -> void: + var floor_items: Array = location_data.get("floorItems", []) + var existing_label := tile_root.get_node_or_null("FloorInventoryLabel") as Label3D + if floor_items.is_empty(): + if existing_label: + existing_label.queue_free() + return + + if existing_label == null: + existing_label = Label3D.new() + existing_label.name = "FloorInventoryLabel" + existing_label.position = Vector3(0.0, (block_height * 0.5) + 1.25, 0.0) + existing_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED + existing_label.pixel_size = 0.01 + existing_label.outline_size = 10 + existing_label.modulate = Color(1.0, 0.95, 0.75, 1.0) + tile_root.add_child(existing_label) + + existing_label.text = _build_floor_inventory_label(floor_items) func _create_tile_border() -> MeshInstance3D: @@ -273,15 +305,37 @@ func _create_object_material(object_key: String) -> StandardMaterial3D: return material -func _build_object_label(object_data: Dictionary) -> String: +func _build_object_label(object_data: Dictionary) -> String: var object_name := String(object_data.get("name", "")).strip_edges() var state_variant: Variant = object_data.get("state", {}) var remaining_quantity := 0 if typeof(state_variant) == TYPE_DICTIONARY: remaining_quantity = int((state_variant as Dictionary).get("remainingQuantity", 0)) - if object_name.is_empty(): - object_name = "Object" - return "%s x%d" % [object_name, remaining_quantity] + if object_name.is_empty(): + object_name = "Object" + return "%s x%d" % [object_name, remaining_quantity] + + +func _build_floor_inventory_label(floor_items: Array) -> String: + var parts: Array[String] = [] + for entry in floor_items: + if typeof(entry) != TYPE_DICTIONARY: + continue + var item := entry as Dictionary + parts.append("%s x%d" % [ + String(item.get("itemKey", "")).strip_edges(), + int(item.get("quantity", 0)) + ]) + if parts.size() >= 3: + break + + if parts.is_empty(): + return "" + + var label := "Floor: %s" % ", ".join(parts) + if floor_items.size() > parts.size(): + label += " ..." + return label func _ensure_selected_location_exists(coord: Vector2i) -> void: @@ -291,7 +345,8 @@ func _ensure_selected_location_exists(coord: Vector2i) -> void: "id": "", "name": _selected_location_name(coord), "biomeKey": "plains", - "locationObject": {} + "locationObject": {}, + "floorItems": [] } @@ -367,13 +422,16 @@ func _load_existing_locations() -> void: "id": String(location.get("id", "")).strip_edges(), "name": location_name, "biomeKey": String(location.get("biomeKey", "plains")).strip_edges(), - "locationObject": _parse_location_object(location.get("locationObject", {})) + "locationObject": _parse_location_object(location.get("locationObject", {})), + "floorItems": [] } loaded_count += 1 - - print("LocationLevel loaded %d visible locations for character %s." % [loaded_count, _character_id]) - if loaded_count == 0: - push_warning("Visible locations request succeeded but returned 0 locations for character %s." % _character_id) + + print("LocationLevel loaded %d visible locations for character %s." % [loaded_count, _character_id]) + if loaded_count == 0: + push_warning("Visible locations request succeeded but returned 0 locations for character %s." % _character_id) + + await _load_visible_location_inventories() _locations_loaded = true _locations_refresh_in_flight = false @@ -467,9 +525,10 @@ func _interact_with_location_async(location_id: String, object_id: String) -> vo _interact_in_flight = false return - var interaction := parsed as Dictionary - _apply_interaction_result(location_id, interaction) - _interact_in_flight = false + var interaction := parsed as Dictionary + _apply_interaction_result(location_id, interaction) + await _refresh_location_inventory(location_id) + _interact_in_flight = false func _apply_interaction_result(location_id: String, interaction: Dictionary) -> void: @@ -562,6 +621,83 @@ func _parse_location_object(value: Variant) -> Dictionary: "name": String(object_data.get("name", "")).strip_edges(), "state": state } + + +func _parse_floor_inventory_items(value: Variant) -> Array: + var items: Array = [] + if typeof(value) != TYPE_ARRAY: + return items + + for entry in value: + if typeof(entry) != TYPE_DICTIONARY: + continue + var item := entry as Dictionary + items.append({ + "itemKey": String(item.get("itemKey", "")).strip_edges(), + "quantity": int(item.get("quantity", 0)), + "slot": item.get("slot", null) + }) + + return items + + +func _load_visible_location_inventories() -> void: + for coord_variant in _known_locations.keys(): + var coord: Vector2i = coord_variant + var location_data: Dictionary = _known_locations[coord] + var location_id := String(location_data.get("id", "")).strip_edges() + if location_id.is_empty(): + continue + var floor_items := await _fetch_location_inventory(location_id) + location_data["floorItems"] = floor_items + _known_locations[coord] = location_data + + +func _refresh_location_inventory(location_id: String) -> void: + if location_id.is_empty(): + return + var floor_items := await _fetch_location_inventory(location_id) + for coord_variant in _known_locations.keys(): + var coord: Vector2i = coord_variant + var location_data: Dictionary = _known_locations[coord] + if String(location_data.get("id", "")).strip_edges() != location_id: + continue + location_data["floorItems"] = floor_items + _known_locations[coord] = location_data + _update_tile(coord, location_data) + return + + +func _fetch_location_inventory(location_id: String) -> Array: + var request := HTTPRequest.new() + add_child(request) + + var headers := PackedStringArray() + if not AuthState.access_token.is_empty(): + headers.append("Authorization: Bearer %s" % AuthState.access_token) + + var err := request.request("%s/by-owner/location/%s" % [INVENTORY_API_URL, location_id], headers, HTTPClient.METHOD_GET) + if err != OK: + request.queue_free() + push_warning("Failed to request floor inventory for location %s: %s" % [location_id, err]) + return [] + + var result: Array = await request.request_completed + request.queue_free() + + var result_code: int = result[0] + var response_code: int = result[1] + var response_body: String = result[3].get_string_from_utf8() + if result_code != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300: + push_warning("Failed to load floor inventory for location %s (%s/%s): %s" % [location_id, result_code, response_code, response_body]) + return [] + + var parsed: Variant = JSON.parse_string(response_body) + if typeof(parsed) != TYPE_DICTIONARY: + return [] + + var payload := parsed as Dictionary + return _parse_floor_inventory_items(payload.get("items", [])) func _get_biome_material(tile: MeshInstance3D, biome_key: String) -> Material: diff --git a/microservices/InventoryApi/Controllers/InventoryController.cs b/microservices/InventoryApi/Controllers/InventoryController.cs index 7dd51c9..1ce0b09 100644 --- a/microservices/InventoryApi/Controllers/InventoryController.cs +++ b/microservices/InventoryApi/Controllers/InventoryController.cs @@ -124,12 +124,15 @@ public class InventoryController : ControllerBase if (definition is null) return BadRequest("Unknown itemKey"); - var items = await _inventory.GrantAsync(access, req, definition); + var grant = await _inventory.GrantAsync(access, req, definition); return Ok(new InventoryOwnerResponse { OwnerType = access.OwnerType, OwnerId = access.OwnerId, - Items = items.Select(InventoryItemResponse.FromModel).ToList() + RequestedQuantity = grant.RequestedQuantity, + GrantedQuantity = grant.GrantedQuantity, + OverflowQuantity = grant.OverflowQuantity, + Items = grant.Items.Select(InventoryItemResponse.FromModel).ToList() }); } diff --git a/microservices/InventoryApi/Models/InventoryOwnerResponse.cs b/microservices/InventoryApi/Models/InventoryOwnerResponse.cs index e3af042..5aa2df7 100644 --- a/microservices/InventoryApi/Models/InventoryOwnerResponse.cs +++ b/microservices/InventoryApi/Models/InventoryOwnerResponse.cs @@ -6,5 +6,11 @@ public class InventoryOwnerResponse public string OwnerId { get; set; } = string.Empty; + public int RequestedQuantity { get; set; } + + public int GrantedQuantity { get; set; } + + public int OverflowQuantity { get; set; } + public List Items { get; set; } = []; } diff --git a/microservices/InventoryApi/Services/InventoryStore.cs b/microservices/InventoryApi/Services/InventoryStore.cs index 8f2cea6..dacab91 100644 --- a/microservices/InventoryApi/Services/InventoryStore.cs +++ b/microservices/InventoryApi/Services/InventoryStore.cs @@ -8,6 +8,7 @@ public class InventoryStore { private const string CharacterOwnerType = "character"; private const string LocationOwnerType = "location"; + private const int CharacterInventorySlotCount = 6; private const string OwnerIndexName = "owner_type_1_owner_id_1"; private const string SlotIndexName = "owner_type_1_owner_id_1_slot_1"; private const string EquippedSlotIndexName = "owner_type_1_owner_id_1_equipped_slot_1"; @@ -20,6 +21,8 @@ public class InventoryStore private readonly IMongoClient _client; private readonly string _dbName; + public sealed record GrantResult(List Items, int RequestedQuantity, int GrantedQuantity, int OverflowQuantity); + public InventoryStore(IConfiguration cfg) { var cs = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017"; @@ -131,53 +134,70 @@ public class InventoryStore return existing; } - public async Task> GrantAsync(OwnerAccessResult owner, GrantInventoryItemRequest req, ItemDefinition definition) + public async Task GrantAsync(OwnerAccessResult owner, GrantInventoryItemRequest req, ItemDefinition definition) { var normalizedKey = NormalizeItemKey(req.ItemKey); + var remaining = req.Quantity; if (definition.Stackable) { - var remaining = req.Quantity; var targetSlot = req.PreferredSlot; + var existingStacks = await _items.Find(i => + i.OwnerType == owner.OwnerType && + i.OwnerId == owner.OwnerId && + i.ItemKey == normalizedKey && + i.EquippedSlot == null && + i.Slot != null) + .SortBy(i => i.Slot) + .ToListAsync(); + + foreach (var existing in existingStacks) + { + if (remaining <= 0) + break; + + var availableSpace = definition.MaxStackSize - existing.Quantity; + if (availableSpace <= 0) + continue; + + var added = Math.Min(remaining, availableSpace); + existing.Quantity += added; + existing.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(existing); + remaining -= added; + } + while (remaining > 0) { var slot = targetSlot ?? await FindFirstOpenSlotAsync(owner.OwnerType, owner.OwnerId); - var existing = await FindStackAsync(owner.OwnerType, owner.OwnerId, normalizedKey, slot); - if (existing is not null) + if (slot is null) + break; + + var stackQuantity = Math.Min(remaining, definition.MaxStackSize); + await InsertItemAsync(new InventoryItem { - var availableSpace = definition.MaxStackSize - existing.Quantity; - if (availableSpace > 0) - { - var added = Math.Min(remaining, availableSpace); - existing.Quantity += added; - existing.UpdatedUtc = DateTime.UtcNow; - await ReplaceItemAsync(existing); - remaining -= added; - } - } - else - { - var stackQuantity = Math.Min(remaining, definition.MaxStackSize); - await InsertItemAsync(new InventoryItem - { - ItemKey = normalizedKey, - Quantity = stackQuantity, - OwnerType = owner.OwnerType, - OwnerId = owner.OwnerId, - OwnerUserId = owner.OwnerUserId, - Slot = slot - }); - remaining -= stackQuantity; - } + ItemKey = normalizedKey, + Quantity = stackQuantity, + OwnerType = owner.OwnerType, + OwnerId = owner.OwnerId, + OwnerUserId = owner.OwnerUserId, + Slot = slot.Value + }); + remaining -= stackQuantity; targetSlot = null; } - return await GetByOwnerAsync(owner.OwnerType, owner.OwnerId); + + var stackItems = await GetByOwnerAsync(owner.OwnerType, owner.OwnerId); + return new GrantResult(stackItems, req.Quantity, req.Quantity - remaining, remaining); } var nextPreferredSlot = req.PreferredSlot; - for (var index = 0; index < req.Quantity; index += 1) + while (remaining > 0) { var slot = nextPreferredSlot ?? await FindFirstOpenSlotAsync(owner.OwnerType, owner.OwnerId); + if (slot is null) + break; + await InsertItemAsync(new InventoryItem { ItemKey = normalizedKey, @@ -185,11 +205,13 @@ public class InventoryStore OwnerType = owner.OwnerType, OwnerId = owner.OwnerId, OwnerUserId = owner.OwnerUserId, - Slot = slot + Slot = slot.Value }); + remaining -= 1; nextPreferredSlot = null; } - return await GetByOwnerAsync(owner.OwnerType, owner.OwnerId); + var nonStackItems = await GetByOwnerAsync(owner.OwnerType, owner.OwnerId); + return new GrantResult(nonStackItems, req.Quantity, req.Quantity - remaining, remaining); } public async Task MoveAsync(OwnerAccessResult owner, MoveInventoryItemRequest req) @@ -298,7 +320,12 @@ public class InventoryStore } var toSlot = req.ToSlot ?? await FindFirstOpenSlotAsync(toOwner.OwnerType, toOwner.OwnerId, session); - var target = await FindItemBySlotAsync(toOwner.OwnerType, toOwner.OwnerId, toSlot, session); + if (toSlot is null) + { + await session.AbortTransactionAsync(); + return new InventoryMutationResult { Status = InventoryMutationStatus.Conflict }; + } + var target = await FindItemBySlotAsync(toOwner.OwnerType, toOwner.OwnerId, toSlot.Value, session); if (target is not null && !CanMerge(item, target, definition)) { await session.AbortTransactionAsync(); @@ -451,12 +478,14 @@ public class InventoryStore return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; var slot = preferredSlot ?? await FindFirstOpenSlotAsync(item.OwnerType, item.OwnerId); - var existing = await FindItemBySlotAsync(item.OwnerType, item.OwnerId, slot); + if (slot is null) + return new InventoryMutationResult { Status = InventoryMutationStatus.Conflict }; + var existing = await FindItemBySlotAsync(item.OwnerType, item.OwnerId, slot.Value); if (existing is not null && existing.Id != item.Id) return new InventoryMutationResult { Status = InventoryMutationStatus.Conflict }; item.EquippedSlot = null; - item.Slot = slot; + item.Slot = slot.Value; item.UpdatedUtc = DateTime.UtcNow; await ReplaceItemAsync(item); @@ -480,16 +509,19 @@ public class InventoryStore }; } - private async Task FindFirstOpenSlotAsync(string ownerType, string ownerId, IClientSessionHandle? session = null) + private async Task FindFirstOpenSlotAsync(string ownerType, string ownerId, IClientSessionHandle? session = null) { var items = session is null ? await GetByOwnerAsync(ownerType, ownerId) : await _items.Find(session, i => i.OwnerType == ownerType && i.OwnerId == ownerId).ToListAsync(); var usedSlots = items.Where(i => i.Slot.HasValue).Select(i => i.Slot!.Value).ToHashSet(); + var maxSlotCount = GetMaxSlotCount(ownerType); var slot = 0; while (usedSlots.Contains(slot)) slot += 1; + if (maxSlotCount.HasValue && slot >= maxSlotCount.Value) + return null; return slot; } @@ -540,6 +572,11 @@ public class InventoryStore target.EquippedSlot is null && definition.Stackable; + private static int? GetMaxSlotCount(string ownerType) => + string.Equals(ownerType, CharacterOwnerType, StringComparison.OrdinalIgnoreCase) + ? CharacterInventorySlotCount + : null; + private void EnsureIndexes() { _items.Indexes.CreateOne(new CreateIndexModel( diff --git a/microservices/LocationsApi/Controllers/LocationsController.cs b/microservices/LocationsApi/Controllers/LocationsController.cs index 3cf947b..4986d7b 100644 --- a/microservices/LocationsApi/Controllers/LocationsController.cs +++ b/microservices/LocationsApi/Controllers/LocationsController.cs @@ -180,6 +180,64 @@ public class LocationsController : ControllerBase return StatusCode((int)response.StatusCode, responseBody); } + var characterGrantedQuantity = interact.QuantityGranted; + var floorGrantedQuantity = 0; + var overflowQuantity = 0; + + if (!string.IsNullOrWhiteSpace(responseBody)) + { + using var grantJson = JsonDocument.Parse(responseBody); + if (grantJson.RootElement.TryGetProperty("grantedQuantity", out var grantedElement) && grantedElement.ValueKind == JsonValueKind.Number) + characterGrantedQuantity = grantedElement.GetInt32(); + if (grantJson.RootElement.TryGetProperty("overflowQuantity", out var overflowElement) && overflowElement.ValueKind == JsonValueKind.Number) + overflowQuantity = overflowElement.GetInt32(); + } + + if (overflowQuantity > 0) + { + var floorGrantBody = JsonSerializer.Serialize(new + { + itemKey = interact.ItemKey, + quantity = overflowQuantity + }); + + using var floorRequest = new HttpRequestMessage( + HttpMethod.Post, + $"{inventoryBaseUrl}/api/inventory/by-owner/location/{id}/grant"); + floorRequest.Content = new StringContent(floorGrantBody, Encoding.UTF8, "application/json"); + if (!string.IsNullOrWhiteSpace(token)) + floorRequest.Headers.Authorization = AuthenticationHeaderValue.Parse(token); + + using var floorResponse = await client.SendAsync(floorRequest); + var floorResponseBody = await floorResponse.Content.ReadAsStringAsync(); + if (!floorResponse.IsSuccessStatusCode) + { + _logger.LogError( + "Character inventory overflow could not be redirected to location inventory. Location {LocationId}, character {CharacterId}, item {ItemKey}, quantity {Quantity}, response {StatusCode}: {Body}", + id, + req.CharacterId, + interact.ItemKey, + overflowQuantity, + (int)floorResponse.StatusCode, + floorResponseBody + ); + return StatusCode((int)floorResponse.StatusCode, floorResponseBody); + } + + if (!string.IsNullOrWhiteSpace(floorResponseBody)) + { + using var floorJson = JsonDocument.Parse(floorResponseBody); + if (floorJson.RootElement.TryGetProperty("grantedQuantity", out var floorGrantedElement) && floorGrantedElement.ValueKind == JsonValueKind.Number) + floorGrantedQuantity = floorGrantedElement.GetInt32(); + else + floorGrantedQuantity = overflowQuantity; + } + else + { + floorGrantedQuantity = overflowQuantity; + } + } + return Ok(new InteractLocationObjectResponse { LocationId = id, @@ -188,6 +246,8 @@ public class LocationsController : ControllerBase ObjectType = interact.ObjectType, ItemKey = interact.ItemKey, QuantityGranted = interact.QuantityGranted, + CharacterGrantedQuantity = characterGrantedQuantity, + FloorGrantedQuantity = floorGrantedQuantity, RemainingQuantity = interact.RemainingQuantity, Consumed = interact.Consumed, InventoryResponseJson = responseBody diff --git a/microservices/LocationsApi/Models/InteractLocationObjectResponse.cs b/microservices/LocationsApi/Models/InteractLocationObjectResponse.cs index 6fd38f3..be23fda 100644 --- a/microservices/LocationsApi/Models/InteractLocationObjectResponse.cs +++ b/microservices/LocationsApi/Models/InteractLocationObjectResponse.cs @@ -14,6 +14,10 @@ public class InteractLocationObjectResponse public int QuantityGranted { get; set; } + public int CharacterGrantedQuantity { get; set; } + + public int FloorGrantedQuantity { get; set; } + public int RemainingQuantity { get; set; } public bool Consumed { get; set; }