Recommiting glbs for LFS
Deploy Promiscuity Auth API / deploy (push) Successful in 1m59s
Deploy Promiscuity Character API / deploy (push) Successful in 1m16s
Deploy Promiscuity Inventory API / deploy (push) Has been cancelled
Deploy Promiscuity Locations API / deploy (push) Has been cancelled
Deploy Promiscuity Mail API / deploy (push) Has been cancelled
Deploy Promiscuity World API / deploy (push) Has been cancelled
Deploy Promiscuity Crafting API / deploy (push) Has been cancelled
k8s smoke test / test (push) Has been cancelled
Deploy Promiscuity Auth API / deploy (push) Successful in 1m59s
Deploy Promiscuity Character API / deploy (push) Successful in 1m16s
Deploy Promiscuity Inventory API / deploy (push) Has been cancelled
Deploy Promiscuity Locations API / deploy (push) Has been cancelled
Deploy Promiscuity Mail API / deploy (push) Has been cancelled
Deploy Promiscuity World API / deploy (push) Has been cancelled
Deploy Promiscuity Crafting API / deploy (push) Has been cancelled
k8s smoke test / test (push) Has been cancelled
This commit is contained in:
@@ -99,7 +99,7 @@ public class CraftingStore
|
||||
{
|
||||
var recipes = await _recipes.Find(r => r.Enabled).SortBy(r => r.Category).ThenBy(r => r.Name).ToListAsync();
|
||||
var items = await GetCharacterItemsAsync(character.CharacterId);
|
||||
var itemTotals = items.GroupBy(i => i.ItemKey).ToDictionary(g => g.Key, g => g.Sum(x => x.Quantity), StringComparer.Ordinal);
|
||||
var itemTotals = GetCraftableItemTotals(items);
|
||||
var location = await GetLocationAtCoordAsync(character.CoordX, character.CoordY);
|
||||
|
||||
return recipes.Select(recipe =>
|
||||
@@ -126,7 +126,7 @@ public class CraftingStore
|
||||
return new CraftAttemptResult { Status = CraftStatus.MissingStation };
|
||||
|
||||
var items = await GetCharacterItemsAsync(character.CharacterId);
|
||||
var totals = items.GroupBy(i => i.ItemKey).ToDictionary(g => g.Key, g => g.Sum(x => x.Quantity), StringComparer.Ordinal);
|
||||
var totals = GetCraftableItemTotals(items);
|
||||
var missing = GetMissingRequirements(recipe, totals, location, craftCount);
|
||||
if (missing.Count > 0)
|
||||
return new CraftAttemptResult { Status = CraftStatus.MissingInputs, MissingRequirements = missing };
|
||||
@@ -139,6 +139,10 @@ public class CraftingStore
|
||||
return new CraftAttemptResult { Status = CraftStatus.InvalidRecipe, MissingRequirements = [$"Missing item definition for output '{output.ItemKey}'"] };
|
||||
}
|
||||
|
||||
var capacityError = GetOutputCapacityError(recipe, craftCount, items, definitionMap);
|
||||
if (capacityError is not null)
|
||||
return new CraftAttemptResult { Status = CraftStatus.InventoryFull, MissingRequirements = [capacityError] };
|
||||
|
||||
foreach (var input in recipe.Inputs)
|
||||
await ConsumeItemKeyAsync(character, input.ItemKey, input.Quantity * craftCount);
|
||||
|
||||
@@ -157,6 +161,12 @@ public class CraftingStore
|
||||
private async Task<List<InventoryItemDocument>> GetCharacterItemsAsync(string characterId) =>
|
||||
await _items.Find(i => i.OwnerType == CharacterOwnerType && i.OwnerId == characterId).SortBy(i => i.Slot).ThenBy(i => i.ItemKey).ToListAsync();
|
||||
|
||||
private static Dictionary<string, int> GetCraftableItemTotals(IEnumerable<InventoryItemDocument> items) =>
|
||||
items
|
||||
.Where(i => i.EquippedSlot is null)
|
||||
.GroupBy(i => i.ItemKey)
|
||||
.ToDictionary(g => g.Key, g => g.Sum(x => x.Quantity), StringComparer.Ordinal);
|
||||
|
||||
private async Task<LocationDocument?> GetLocationAtCoordAsync(int x, int y) =>
|
||||
await _locations.Find(l => l.Coord.X == x && l.Coord.Y == y).FirstOrDefaultAsync();
|
||||
|
||||
@@ -216,6 +226,74 @@ public class CraftingStore
|
||||
return string.Equals(recipe.StationType, stationType, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static string? GetOutputCapacityError(CraftingRecipe recipe, int craftCount, List<InventoryItemDocument> currentItems, IReadOnlyDictionary<string, ItemDefinitionDocument> definitions)
|
||||
{
|
||||
var simulatedItems = currentItems
|
||||
.Select(item => new SimulatedInventoryItem
|
||||
{
|
||||
ItemKey = item.ItemKey,
|
||||
Quantity = item.Quantity,
|
||||
Slot = item.Slot,
|
||||
EquippedSlot = item.EquippedSlot,
|
||||
CreatedUtc = item.CreatedUtc
|
||||
})
|
||||
.ToList();
|
||||
|
||||
foreach (var input in recipe.Inputs)
|
||||
SimulateConsume(simulatedItems, input.ItemKey, input.Quantity * craftCount);
|
||||
|
||||
var usedSlots = simulatedItems
|
||||
.Where(i => i.Slot.HasValue)
|
||||
.Select(i => i.Slot!.Value)
|
||||
.ToHashSet();
|
||||
var openSlots = Enumerable.Range(0, InventorySlotCount).Count(slot => !usedSlots.Contains(slot));
|
||||
|
||||
foreach (var outputGroup in recipe.Outputs.GroupBy(output => output.ItemKey, StringComparer.Ordinal))
|
||||
{
|
||||
var itemKey = outputGroup.Key;
|
||||
var requiredQuantity = outputGroup.Sum(output => output.Quantity * craftCount);
|
||||
var definition = definitions[itemKey];
|
||||
|
||||
if (definition.Stackable)
|
||||
{
|
||||
var existingSpace = simulatedItems
|
||||
.Where(i => i.EquippedSlot is null && i.Slot.HasValue && i.ItemKey == itemKey)
|
||||
.Sum(i => Math.Max(0, definition.MaxStackSize - i.Quantity));
|
||||
var remaining = Math.Max(0, requiredQuantity - existingSpace);
|
||||
var slotsNeeded = (int)Math.Ceiling(remaining / (double)Math.Max(1, definition.MaxStackSize));
|
||||
if (slotsNeeded > openSlots)
|
||||
return $"Not enough inventory space for output '{itemKey}'";
|
||||
openSlots -= slotsNeeded;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (requiredQuantity > openSlots)
|
||||
return $"Not enough inventory space for output '{itemKey}'";
|
||||
openSlots -= requiredQuantity;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void SimulateConsume(List<SimulatedInventoryItem> items, string itemKey, int quantity)
|
||||
{
|
||||
var remaining = quantity;
|
||||
foreach (var item in items
|
||||
.Where(i => i.EquippedSlot is null && i.ItemKey == itemKey)
|
||||
.OrderBy(i => i.Slot)
|
||||
.ThenBy(i => i.CreatedUtc))
|
||||
{
|
||||
if (remaining <= 0)
|
||||
break;
|
||||
|
||||
var consumed = Math.Min(remaining, item.Quantity);
|
||||
item.Quantity -= consumed;
|
||||
remaining -= consumed;
|
||||
}
|
||||
|
||||
items.RemoveAll(i => i.Quantity <= 0);
|
||||
}
|
||||
|
||||
private async Task ConsumeItemKeyAsync(CharacterAccessResult character, string itemKey, int quantity)
|
||||
{
|
||||
var remaining = quantity;
|
||||
@@ -302,7 +380,7 @@ public class CraftingStore
|
||||
{
|
||||
var items = await GetCharacterItemsAsync(characterId);
|
||||
var used = items.Where(i => i.Slot.HasValue).Select(i => i.Slot!.Value).ToHashSet();
|
||||
for (var slot = 0; slot < 6; slot++)
|
||||
for (var slot = 0; slot < InventorySlotCount; slot++)
|
||||
{
|
||||
if (!used.Contains(slot))
|
||||
return slot;
|
||||
@@ -344,7 +422,23 @@ public class CraftingStore
|
||||
RecipeNotFound,
|
||||
MissingInputs,
|
||||
MissingStation,
|
||||
InvalidRecipe
|
||||
InvalidRecipe,
|
||||
InventoryFull
|
||||
}
|
||||
|
||||
private const int InventorySlotCount = 6;
|
||||
|
||||
private class SimulatedInventoryItem
|
||||
{
|
||||
public string ItemKey { get; set; } = string.Empty;
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public int? Slot { get; set; }
|
||||
|
||||
public string? EquippedSlot { get; set; }
|
||||
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
}
|
||||
|
||||
[BsonIgnoreExtraElements]
|
||||
|
||||
Reference in New Issue
Block a user