N5/D2: NPC arcs — Misty stages, Robbie's gift, Thom's final letter

Three flavor arcs on existing per-player counters (no schema, golden
byte-identical):

- Misty: 3 deepening dialogue beats at MistyEncounterCount 5/15/30,
  prepended to the encounter opening the one time the counter lands on a
  threshold. Fiction only — no copy ties a donation to the hidden arena
  effects.
- Robbie: every 10th visit he leaves a consumable "for the trouble,"
  drawn from the dungeon pool at a level-matched tier.
- Thom: paying off the Tier-4 mortgage (no next tier) sends a final
  letter instead of the stock payoff line, plus an inert pet-treat
  keepsake if the player keeps a pet.

Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
This commit is contained in:
prosolis
2026-07-10 16:12:53 -07:00
parent c37c95a3e3
commit 3103b519fb
7 changed files with 208 additions and 6 deletions

View File

@@ -146,6 +146,18 @@ var thomFreezePet = "Ten missed payments. I've stopped trying for now. The house
var thomPaidOffNoPet = "Paid off. Noted. The next tier is available when you're ready. I'll be here."
var thomPaidOffPet = "Paid off. Good. Come in when you're ready for the next tier. I've been thinking about what %s would need in a better space. I have thoughts."
// thomPetTreatItem is the keepsake Thom leaves for the pet on the final
// mortgage payoff. Inert (Type "card" like the medical-debt card): not a
// consumable, not gear, and Robbie's sweep skips it.
func thomPetTreatItem() AdvItem {
return AdvItem{
Name: "Krooke's Finest Pet Treat",
Type: "card",
Tier: 0,
Value: 0,
}
}
// thomGreeting returns an appropriate greeting based on pet state.
func thomGreeting(char *AdventureCharacter) string {
if char.HasPet() && char.PetLevel >= 10 {
@@ -553,11 +565,22 @@ func (p *AdventurePlugin) processMortgagePayments() {
freshChar.HouseLoanBalance = 0
freshChar.HouseMissedPayments = 0
_ = saveAdvCharacter(freshChar)
// The last house: no next tier means Thom is done with this
// player. A letter, and a treat in the bag if they keep a pet.
finalPayoff := houseNextTier(freshChar.HouseTier) == nil
if finalPayoff && freshChar.HasPet() {
_ = addAdvInventoryItem(freshChar.UserID, thomPetTreatItem())
}
userMu.Unlock()
// Paid off!
if freshChar.HasPet() {
switch {
case finalPayoff && freshChar.HasPet():
letter := strings.ReplaceAll(thomFinalLetterPet, "{pet}", freshChar.PetName)
_ = p.SendDM(freshChar.UserID, "🏠 "+letter)
case finalPayoff:
_ = p.SendDM(freshChar.UserID, "🏠 "+thomFinalLetterNoPet)
case freshChar.HasPet():
_ = p.SendDM(freshChar.UserID, fmt.Sprintf("🏠 %s", fmt.Sprintf(thomPaidOffPet, freshChar.PetName)))
} else {
default:
_ = p.SendDM(freshChar.UserID, fmt.Sprintf("🏠 %s", thomPaidOffNoPet))
}
continue