diff options
| author | historia <[not public]> | 2026-09-18 05:31:09 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 05:31:09 -0400 |
| commit | 153acb7f644d755c45533b7c5311e3ffa47e8973 (patch) | |
| tree | 7645e6f7b65bc3830b078e3b9336d2e6b199d584 /internal/game/act_farm.go | |
| parent | 87d08f0151e643951769e323995f1c5ed89ee200 (diff) | |
| download | thehouseoficarus-153acb7f644d755c45533b7c5311e3ffa47e8973.tar.gz | |
fix: harden engine across casino, economy, concurrency, security, and content
Diffstat (limited to 'internal/game/act_farm.go')
| -rw-r--r-- | internal/game/act_farm.go | 77 |
1 files changed, 51 insertions, 26 deletions
diff --git a/internal/game/act_farm.go b/internal/game/act_farm.go index 1e576c3..48fbec7 100644 --- a/internal/game/act_farm.go +++ b/internal/game/act_farm.go @@ -242,43 +242,68 @@ func (g *Game) advanceHarvest(sess *net.Session, p *player.Player) { bonus := p.Level(player.Farming) / 20 yield += bonus - freeSlots := p.FreeSlots() - if yield > freeSlots { - productDef, _ := g.ItemStore.Load(product) - if productDef == nil || !productDef.Stackable { + productDef, _ := g.ItemStore.Load(product) + stackable := productDef != nil && productDef.Stackable + + // The delivery target is chosen before the crop is consumed: a harvest + // that cannot be stored must leave the patch ready instead of being + // silently destroyed. A stackable product merges into an existing stack + // and therefore needs no free slot. + mergeSlot := -1 + freeSlot := -1 + noRoom := false + if stackable { + quality, maxQuality := stackProps(productDef) + for i := 0; i < 28; i++ { + s := p.InvSlot(i) + // Only a stack with the properties a fresh harvest stack gets + // may absorb it; merging into a differently qualified stack + // would silently destroy one of the two qualities. + if s != nil && s.ItemID == product && s.Quality == quality && s.MaxQuality == maxQuality { + mergeSlot = i + break + } + } + if mergeSlot < 0 { + freeSlot = p.FirstFreeSlot() + } + noRoom = mergeSlot < 0 && freeSlot < 0 + } else { + // One slot per unit: the clamp and the placement loop must count + // slots the same way (a nil 0..27 slot). + freeSlots := 0 + for i := 0; i < 28; i++ { + if p.InvSlot(i) == nil { + freeSlots++ + } + } + if yield > freeSlots { yield = freeSlots } + noRoom = yield <= 0 } - if yield <= 0 { + if noRoom { sess.WriteLine("Your inventory is too full!") p.Action = nil return } - productDef, _ := g.ItemStore.Load(product) - if productDef != nil && productDef.Stackable { - added := 0 - for i := 0; i < 28 && added < yield; i++ { - slot := p.InvSlot(i) - if slot != nil && slot.ItemID == product { - slot.Quantity += yield - added = yield - break - } - } - if added == 0 { - freeSlot := p.FirstFreeSlot() - if freeSlot >= 0 { - p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: product, Quantity: yield}) - } + placed := yield + if stackable { + if mergeSlot >= 0 { + p.InvSlot(mergeSlot).Quantity += yield + } else { + p.SetInvSlot(freeSlot, g.newInventorySlot(product, yield)) } } else { - for i := 0; i < yield; i++ { - freeSlot := p.FirstFreeSlot() - if freeSlot < 0 { + placed = 0 + for placed < yield { + fs := p.FirstFreeSlot() + if fs < 0 { break } - p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: product, Quantity: 1}) + p.SetInvSlot(fs, g.newInventorySlot(product, 1)) + placed++ } } @@ -302,7 +327,7 @@ func (g *Game) advanceHarvest(sess *net.Session, p *player.Player) { productName = g.itemColorize(sess, productDef, productDef.Name) } patchName := patchDisplayName(prefix) - msg := fmt.Sprintf("You harvest %d %s from the %s.", yield, productName, patchName) + msg := fmt.Sprintf("You harvest %d %s from the %s.", placed, productName, patchName) if int(xp) > 0 && p.OptionBool("xp_drops") { msg += g.formatXpDropSingle(sess, p, player.Farming, int(xp)) } |
