aboutsummaryrefslogtreecommitdiff
path: root/internal/game/production_index.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/production_index.go')
-rw-r--r--internal/game/production_index.go92
1 files changed, 51 insertions, 41 deletions
diff --git a/internal/game/production_index.go b/internal/game/production_index.go
index 2646a43..8c0207c 100644
--- a/internal/game/production_index.go
+++ b/internal/game/production_index.go
@@ -178,63 +178,73 @@ func craftMatchesEntry(c *item.CraftDef, itemID string) bool {
return false
}
-func craftHasAllItems(c *item.CraftDef, hasItem func(string) bool) bool {
- if c == nil {
- return false
- }
- for _, e := range c.Ingredients {
- found := false
- for _, id := range e.Items {
- if hasItem(id) {
- found = true
- break
- }
- }
- if !found {
- return false
+// craftHasAllItems reports whether every ingredient entry can be satisfied:
+// one listed alternative covering the entry's full quantity (not merely one
+// of each item being present — a recipe needing 15 feathers is not offered
+// on 1). Resolves through craftIngredientSource so availability checks can
+// never disagree with consumption.
+func craftHasAllItems(c *item.CraftDef, countItem func(string) int) bool {
+ return craftHasAllItemsQty(c, countItem)
+}
+
+// craftIngredientSource resolves one ingredient entry: the FIRST listed
+// alternative the player holds in the entry's full (normalized) quantity,
+// plus that quantity. Availability checks and consumption both resolve
+// through this helper so they always pick the same alternative — an entry
+// listing several alternatives (e.g. bucket or jug of water) consumes the
+// one the check verified, never a partially-stocked other one.
+// Returns "" when no alternative covers the quantity.
+func craftIngredientSource(e *item.IngredientEntry, countItem func(string) int) (string, int) {
+ qty := e.Quantity
+ if qty <= 0 {
+ qty = 1
+ }
+ for _, id := range e.Items {
+ if countItem(id) >= qty {
+ return id, qty
}
}
- return true
+ return "", qty
}
func craftHasAllItemsQty(c *item.CraftDef, countItem func(string) int) bool {
if c == nil {
return false
}
- for _, e := range c.Ingredients {
- found := false
- for _, id := range e.Items {
- qty := e.Quantity
- if qty <= 0 {
- qty = 1
- }
- if countItem(id) >= qty {
- found = true
- break
- }
- }
- if !found {
+ for i := range c.Ingredients {
+ if id, _ := craftIngredientSource(&c.Ingredients[i], countItem); id == "" {
return false
}
}
return true
}
-func consumeIngredients(c *item.CraftDef, hasItem func(string) bool, removeItem func(string, int) bool) {
+// consumeIngredients removes one batch of every ingredient entry. Each
+// entry consumes the same alternative the availability checks verify (the
+// first one covering the FULL quantity), and consumption is two-pass:
+// nothing is removed unless every entry resolves, so a craft can never
+// partially consume an alternative and silently keep the rest. Callers that
+// already ran a craftHasAllItems* check cannot see a false return; it
+// guards callers whose own pre-check is weaker. Callers that check the
+// result must abort the craft without producing output.
+func consumeIngredients(c *item.CraftDef, countItem func(string) int, removeItem func(string, int) bool) bool {
if c == nil {
- return
+ return true
}
- for _, e := range c.Ingredients {
- found := false
- for _, id := range e.Items {
- if hasItem(id) {
- removeItem(id, e.Quantity)
- found = true
- break
- }
- }
- if !found {
- return
+ type consumption struct {
+ id string
+ qty int
+ }
+ plan := make([]consumption, 0, len(c.Ingredients))
+ for i := range c.Ingredients {
+ id, qty := craftIngredientSource(&c.Ingredients[i], countItem)
+ if id == "" {
+ return false
}
+ plan = append(plan, consumption{id: id, qty: qty})
+ }
+ for _, s := range plan {
+ removeItem(s.id, s.qty)
}
+ return true
}