mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 00:52:40 +00:00
Merge pull request #9 from prosolis/holdem-tips-validation
Crafting feedback loop + consumable protection + housekeeping
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,3 +3,5 @@ data/
|
|||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
gogobee
|
gogobee
|
||||||
|
gensolver
|
||||||
|
holdem-train
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -528,6 +528,20 @@ Four activity types across 5 tiers of locations. Higher tiers require higher cha
|
|||||||
- **Mid-day events** — random events can trigger between actions, delivering bonus loot, buffs, or narrative encounters.
|
- **Mid-day events** — random events can trigger between actions, delivering bonus loot, buffs, or narrative encounters.
|
||||||
- **Chat level perks** — active chat participation boosts your adventurer. +5% XP per 10 chat levels (capped at +25% at level 50+), plus +0.5% rare drop chance per 10 levels.
|
- **Chat level perks** — active chat participation boosts your adventurer. +5% XP per 10 chat levels (capped at +25% at level 50+), plus +0.5% rare drop chance per 10 levels.
|
||||||
|
|
||||||
|
#### Crafting
|
||||||
|
|
||||||
|
Auto-crafting kicks in at Foraging Lv.10. Before each combat action, the system scans your inventory for matching ingredients and assembles the highest-tier recipe you qualify for. 12 recipes spanning T1–T5, gated at Foraging 10/15/20/25/30. Per-recipe success rate is 50% at the unlock level, +3% per 5 levels above (capped 95%). Failed crafts destroy the ingredients — you tried.
|
||||||
|
|
||||||
|
Per attempt:
|
||||||
|
- **Successful crafts** grant Foraging XP (T1: +12, T2: +25, T3: +40, T4: +60, T5: +90) and bump a lifetime `CraftsSucceeded` counter shown on `!adventure status`.
|
||||||
|
- **Failed crafts** grant a token Foraging XP (1/4 of success).
|
||||||
|
|
||||||
|
Max auto-crafts per combat scales with level: 1 at Foraging 10, 2 at 20, 3 at 30+.
|
||||||
|
|
||||||
|
`!adventure recipes` lists every recipe unlocked at your current Foraging level with ingredients and per-recipe success rate, plus a teaser for the next unlock threshold.
|
||||||
|
|
||||||
|
**Consumable protection:** crafted/dropped consumables (Type `consumable`) are excluded from `!adventure sell all` and Robbie the Friendly Bandit's pickup filter. Selling consumables requires explicit `!adventure sell <name>` — no accidental mass-sells, no surprise donations to the community pot.
|
||||||
|
|
||||||
#### Blacksmith & Repair
|
#### Blacksmith & Repair
|
||||||
|
|
||||||
Equipment accumulates damage on bad outcomes and breaks at 0 condition. The blacksmith repairs gear for a per-point fee that scales with tier (base rates T0→T5: €1, €2, €5, €12, €30, €80; masterwork and arena gear use the next tier up). The cost has a mild convexity (`baseRate × damage × (1 + damage/200)`), so repairing earlier is slightly cheaper per point than letting gear sit at low condition — but not punitively so. `!adventure blacksmith` previews quotes; `!adventure repair all` or `!adventure repair <slot>` commits. Visiting the blacksmith counts as your daily action.
|
Equipment accumulates damage on bad outcomes and breaks at 0 condition. The blacksmith repairs gear for a per-point fee that scales with tier (base rates T0→T5: €1, €2, €5, €12, €30, €80; masterwork and arena gear use the next tier up). The cost has a mild convexity (`baseRate × damage × (1 + damage/200)`), so repairing earlier is slightly cheaper per point than letting gear sit at low condition — but not punitively so. `!adventure blacksmith` previews quotes; `!adventure repair all` or `!adventure repair <slot>` commits. Visiting the blacksmith counts as your daily action.
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ func runMigrations(d *sql.DB) error {
|
|||||||
`ALTER TABLE coop_dungeon_gifts ADD COLUMN expires_at DATETIME`,
|
`ALTER TABLE coop_dungeon_gifts ADD COLUMN expires_at DATETIME`,
|
||||||
`ALTER TABLE coop_dungeon_gifts ADD COLUMN applied_at DATETIME`,
|
`ALTER TABLE coop_dungeon_gifts ADD COLUMN applied_at DATETIME`,
|
||||||
`ALTER TABLE coop_dungeon_gifts ADD COLUMN stack_lead_id INTEGER`,
|
`ALTER TABLE coop_dungeon_gifts ADD COLUMN stack_lead_id INTEGER`,
|
||||||
|
`ALTER TABLE adventure_characters ADD COLUMN crafts_succeeded INTEGER NOT NULL DEFAULT 0`,
|
||||||
}
|
}
|
||||||
for _, stmt := range columnMigrations {
|
for _, stmt := range columnMigrations {
|
||||||
if _, err := d.Exec(stmt); err != nil {
|
if _, err := d.Exec(stmt); err != nil {
|
||||||
|
|||||||
@@ -267,6 +267,8 @@ func (p *AdventurePlugin) dispatchCommand(ctx MessageContext) error {
|
|||||||
return p.handleRepairSlotCmd(ctx, strings.TrimSpace(args[7:]))
|
return p.handleRepairSlotCmd(ctx, strings.TrimSpace(args[7:]))
|
||||||
case lower == "boost":
|
case lower == "boost":
|
||||||
return p.handleBoostCmd(ctx)
|
return p.handleBoostCmd(ctx)
|
||||||
|
case lower == "recipes":
|
||||||
|
return p.handleRecipesCmd(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.SendDM(ctx.Sender, "Unknown command. Type `!adventure help` to see available commands.")
|
return p.SendDM(ctx.Sender, "Unknown command. Type `!adventure help` to see available commands.")
|
||||||
@@ -290,6 +292,7 @@ const advHelpText = `**Adventure Commands**
|
|||||||
` + "`!adventure blacksmith`" + ` — Visit the blacksmith (view repair costs)
|
` + "`!adventure blacksmith`" + ` — Visit the blacksmith (view repair costs)
|
||||||
` + "`!adventure repair all`" + ` — Repair all damaged equipment
|
` + "`!adventure repair all`" + ` — Repair all damaged equipment
|
||||||
` + "`!adventure repair <slot>`" + ` — Repair a specific slot
|
` + "`!adventure repair <slot>`" + ` — Repair a specific slot
|
||||||
|
` + "`!adventure recipes`" + ` — List crafting recipes known at your current Foraging level
|
||||||
` + "`!coop`" + ` — Co-op dungeons (multi-day party runs). See ` + "`!coop help`" + `.
|
` + "`!coop`" + ` — Co-op dungeons (multi-day party runs). See ` + "`!coop help`" + `.
|
||||||
` + "`!hospital`" + ` — Visit St. Guildmore's Memorial Hospital (same-day revival when dead)
|
` + "`!hospital`" + ` — Visit St. Guildmore's Memorial Hospital (same-day revival when dead)
|
||||||
` + "`!thom`" + ` — Visit Thom Krooke (housing and loans)
|
` + "`!thom`" + ` — Visit Thom Krooke (housing and loans)
|
||||||
@@ -1343,6 +1346,14 @@ func advApplyBoost(result *AdvActionResult) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *AdventurePlugin) handleRecipesCmd(ctx MessageContext) error {
|
||||||
|
char, _, err := p.ensureCharacter(ctx.Sender)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendDM(ctx.Sender, "Failed to load your character.")
|
||||||
|
}
|
||||||
|
return p.SendDM(ctx.Sender, renderRecipesKnown(char.ForagingSkill))
|
||||||
|
}
|
||||||
|
|
||||||
func (p *AdventurePlugin) handleBoostCmd(ctx MessageContext) error {
|
func (p *AdventurePlugin) handleBoostCmd(ctx MessageContext) error {
|
||||||
if !p.IsAdmin(ctx.Sender) {
|
if !p.IsAdmin(ctx.Sender) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ type AdventureCharacter struct {
|
|||||||
PetMorningDefense bool
|
PetMorningDefense bool
|
||||||
AutoBabysit bool
|
AutoBabysit bool
|
||||||
StreakDecayed bool
|
StreakDecayed bool
|
||||||
|
CraftsSucceeded int
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdvEquipment struct {
|
type AdvEquipment struct {
|
||||||
@@ -445,7 +446,7 @@ func loadAdvCharacter(userID id.UserID) (*AdventureCharacter, error) {
|
|||||||
pet_chased_away, pet_reactivated, pet_arrived,
|
pet_chased_away, pet_reactivated, pet_arrived,
|
||||||
misty_encounter_count, misty_donated_count,
|
misty_encounter_count, misty_donated_count,
|
||||||
thom_animal_line_fired, pet_supply_shop_unlocked, pet_level10_date,
|
thom_animal_line_fired, pet_supply_shop_unlocked, pet_level10_date,
|
||||||
pet_morning_defense, auto_babysit, streak_decayed
|
pet_morning_defense, auto_babysit, streak_decayed, crafts_succeeded
|
||||||
FROM adventure_characters WHERE user_id = ?`, string(userID)).Scan(
|
FROM adventure_characters WHERE user_id = ?`, string(userID)).Scan(
|
||||||
&c.UserID, &c.DisplayName,
|
&c.UserID, &c.DisplayName,
|
||||||
&c.CombatLevel, &c.MiningSkill, &c.ForagingSkill, &c.FishingSkill,
|
&c.CombatLevel, &c.MiningSkill, &c.ForagingSkill, &c.FishingSkill,
|
||||||
@@ -470,7 +471,7 @@ func loadAdvCharacter(userID id.UserID) (*AdventureCharacter, error) {
|
|||||||
&petChasedAway, &petReactivated, &petArrived,
|
&petChasedAway, &petReactivated, &petArrived,
|
||||||
&c.MistyEncounterCount, &c.MistyDonatedCount,
|
&c.MistyEncounterCount, &c.MistyDonatedCount,
|
||||||
&thomAnimalLine, &petSupplyUnlocked, &c.PetLevel10Date,
|
&thomAnimalLine, &petSupplyUnlocked, &c.PetLevel10Date,
|
||||||
&petMorningDef, &autoBabysit, &streakDecayed,
|
&petMorningDef, &autoBabysit, &streakDecayed, &c.CraftsSucceeded,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -638,7 +639,8 @@ func saveAdvCharacter(char *AdventureCharacter) error {
|
|||||||
thom_animal_line_fired = ?, pet_supply_shop_unlocked = ?, pet_level10_date = ?,
|
thom_animal_line_fired = ?, pet_supply_shop_unlocked = ?, pet_level10_date = ?,
|
||||||
pet_morning_defense = ?,
|
pet_morning_defense = ?,
|
||||||
auto_babysit = ?,
|
auto_babysit = ?,
|
||||||
streak_decayed = ?
|
streak_decayed = ?,
|
||||||
|
crafts_succeeded = ?
|
||||||
WHERE user_id = ?`,
|
WHERE user_id = ?`,
|
||||||
char.DisplayName, char.CombatLevel, char.MiningSkill, char.ForagingSkill, char.FishingSkill,
|
char.DisplayName, char.CombatLevel, char.MiningSkill, char.ForagingSkill, char.FishingSkill,
|
||||||
char.CombatXP, char.MiningXP, char.ForagingXP, char.FishingXP,
|
char.CombatXP, char.MiningXP, char.ForagingXP, char.FishingXP,
|
||||||
@@ -664,6 +666,7 @@ func saveAdvCharacter(char *AdventureCharacter) error {
|
|||||||
petMorningDef,
|
petMorningDef,
|
||||||
autoBabysit,
|
autoBabysit,
|
||||||
streakDecayed,
|
streakDecayed,
|
||||||
|
char.CraftsSucceeded,
|
||||||
string(char.UserID),
|
string(char.UserID),
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
@@ -794,7 +797,7 @@ func loadAllAdvCharacters() ([]AdventureCharacter, error) {
|
|||||||
pet_chased_away, pet_reactivated, pet_arrived,
|
pet_chased_away, pet_reactivated, pet_arrived,
|
||||||
misty_encounter_count, misty_donated_count,
|
misty_encounter_count, misty_donated_count,
|
||||||
thom_animal_line_fired, pet_supply_shop_unlocked, pet_level10_date,
|
thom_animal_line_fired, pet_supply_shop_unlocked, pet_level10_date,
|
||||||
pet_morning_defense, auto_babysit, streak_decayed
|
pet_morning_defense, auto_babysit, streak_decayed, crafts_succeeded
|
||||||
FROM adventure_characters`)
|
FROM adventure_characters`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -834,7 +837,7 @@ func loadAllAdvCharacters() ([]AdventureCharacter, error) {
|
|||||||
&petChasedAway, &petReactivated, &petArrived,
|
&petChasedAway, &petReactivated, &petArrived,
|
||||||
&c.MistyEncounterCount, &c.MistyDonatedCount,
|
&c.MistyEncounterCount, &c.MistyDonatedCount,
|
||||||
&thomAnimalLine, &petSupplyUnlocked, &c.PetLevel10Date,
|
&thomAnimalLine, &petSupplyUnlocked, &c.PetLevel10Date,
|
||||||
&petMorningDef, &autoBabysit, &streakDecayed,
|
&petMorningDef, &autoBabysit, &streakDecayed, &c.CraftsSucceeded,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
)
|
)
|
||||||
@@ -339,9 +341,74 @@ type CraftResult struct {
|
|||||||
Success bool
|
Success bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renderRecipesKnown returns a player-facing list of recipes available at
|
||||||
|
// their current foraging level, plus a teaser line for the next unlock
|
||||||
|
// threshold. Hides exact ingredient lists for recipes the player hasn't
|
||||||
|
// unlocked — only the count of locked recipes is shown.
|
||||||
|
func renderRecipesKnown(foragingLevel int) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(fmt.Sprintf("🧪 **Crafting Recipes** — Foraging Lv.%d\n\n", foragingLevel))
|
||||||
|
|
||||||
|
if foragingLevel < 10 {
|
||||||
|
sb.WriteString("_Auto-crafting unlocks at Foraging Lv.10. Keep gathering._")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group recipes by tier, list those known.
|
||||||
|
known := map[int][]CraftingRecipe{}
|
||||||
|
totalKnown := 0
|
||||||
|
totalLocked := 0
|
||||||
|
nextUnlock := 0
|
||||||
|
for _, r := range craftingRecipes {
|
||||||
|
if r.MinForaging <= foragingLevel {
|
||||||
|
known[r.Tier] = append(known[r.Tier], r)
|
||||||
|
totalKnown++
|
||||||
|
} else {
|
||||||
|
totalLocked++
|
||||||
|
if nextUnlock == 0 || r.MinForaging < nextUnlock {
|
||||||
|
nextUnlock = r.MinForaging
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for tier := 1; tier <= 5; tier++ {
|
||||||
|
recipes := known[tier]
|
||||||
|
if len(recipes) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("**Tier %d** (Foraging %d+)\n", tier, recipes[0].MinForaging))
|
||||||
|
for _, r := range recipes {
|
||||||
|
rate := craftingSuccessRate(foragingLevel, r.MinForaging) * 100
|
||||||
|
sb.WriteString(fmt.Sprintf(" • %s — %s (%.0f%% success)\n",
|
||||||
|
r.Result, strings.Join(r.Ingredients, " + "), rate))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(fmt.Sprintf("\nKnown: %d · ", totalKnown))
|
||||||
|
if totalLocked == 0 {
|
||||||
|
sb.WriteString("All recipes unlocked. ⭐")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("Locked: %d (next unlock at Foraging Lv.%d)", totalLocked, nextUnlock))
|
||||||
|
}
|
||||||
|
|
||||||
|
maxCrafts := 1 + (foragingLevel-10)/10
|
||||||
|
sb.WriteString(fmt.Sprintf("\nMax auto-crafts per combat: %d.", maxCrafts))
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// craftXPRewards: per-tier foraging XP granted on successful (and tiny on
|
||||||
|
// failed) auto-crafts. Successful T1 ≈ 30% of a Foraging Success haul, scaling
|
||||||
|
// up so T5 grants are meaningful. Failures get a token consolation grant —
|
||||||
|
// you tried.
|
||||||
|
var craftXPSuccess = map[int]int{1: 12, 2: 25, 3: 40, 4: 60, 5: 90}
|
||||||
|
var craftXPFailure = map[int]int{1: 3, 2: 5, 3: 8, 4: 12, 5: 18}
|
||||||
|
|
||||||
// autoCraftConsumables scans the player's inventory for craftable recipes and
|
// autoCraftConsumables scans the player's inventory for craftable recipes and
|
||||||
// attempts to craft the highest-tier recipe available. Consumes ingredients on
|
// attempts to craft the highest-tier recipe available. Consumes ingredients on
|
||||||
// attempt; on failure one ingredient is lost. Returns crafted items and results.
|
// attempt; on failure one ingredient is lost. Returns crafted items and results.
|
||||||
|
//
|
||||||
|
// Side effects: grants foraging XP per attempt (success > failure) and bumps
|
||||||
|
// the player's CraftsSucceeded counter on each success.
|
||||||
func autoCraftConsumables(userID id.UserID, items []AdvItem, foragingLevel int) ([]CraftResult, []AdvItem) {
|
func autoCraftConsumables(userID id.UserID, items []AdvItem, foragingLevel int) ([]CraftResult, []AdvItem) {
|
||||||
if foragingLevel < 10 {
|
if foragingLevel < 10 {
|
||||||
return nil, items
|
return nil, items
|
||||||
@@ -393,6 +460,13 @@ func autoCraftConsumables(userID id.UserID, items []AdvItem, foragingLevel int)
|
|||||||
}
|
}
|
||||||
results = append(results, CraftResult{Recipe: bestRecipe, Success: true})
|
results = append(results, CraftResult{Recipe: bestRecipe, Success: true})
|
||||||
crafted++
|
crafted++
|
||||||
|
// Foraging XP + lifetime counter bump.
|
||||||
|
if char, err := loadAdvCharacter(userID); err == nil {
|
||||||
|
char.ForagingXP += craftXPSuccess[bestRecipe.Tier]
|
||||||
|
char.CraftsSucceeded++
|
||||||
|
checkAdvLevelUp(char, "foraging")
|
||||||
|
_ = saveAdvCharacter(char)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Failure: all ingredients destroyed
|
// Failure: all ingredients destroyed
|
||||||
for _, id := range ingredientIDs {
|
for _, id := range ingredientIDs {
|
||||||
@@ -400,6 +474,12 @@ func autoCraftConsumables(userID id.UserID, items []AdvItem, foragingLevel int)
|
|||||||
}
|
}
|
||||||
remaining = removeItemsByIDs(remaining, ingredientIDs)
|
remaining = removeItemsByIDs(remaining, ingredientIDs)
|
||||||
results = append(results, CraftResult{Recipe: bestRecipe, Success: false})
|
results = append(results, CraftResult{Recipe: bestRecipe, Success: false})
|
||||||
|
// Token foraging XP for the attempt.
|
||||||
|
if char, err := loadAdvCharacter(userID); err == nil {
|
||||||
|
char.ForagingXP += craftXPFailure[bestRecipe.Tier]
|
||||||
|
checkAdvLevelUp(char, "foraging")
|
||||||
|
_ = saveAdvCharacter(char)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,12 @@ func renderAdvCharacterSheet(char *AdventureCharacter, equip map[EquipmentSlot]*
|
|||||||
sb.WriteString(fmt.Sprintf(" (best: %d)\n", char.BestStreak))
|
sb.WriteString(fmt.Sprintf(" (best: %d)\n", char.BestStreak))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Crafting (only show once they've actually crafted something)
|
||||||
|
if char.CraftsSucceeded > 0 {
|
||||||
|
sb.WriteString(fmt.Sprintf("🧪 Crafts: %d successful (Foraging Lv.%d — `!adventure recipes`)\n",
|
||||||
|
char.CraftsSucceeded, char.ForagingSkill))
|
||||||
|
}
|
||||||
|
|
||||||
// Equipment
|
// Equipment
|
||||||
sb.WriteString("\n🛡️ Equipment:\n")
|
sb.WriteString("\n🛡️ Equipment:\n")
|
||||||
eqScore := advEquipmentScore(equip)
|
eqScore := advEquipmentScore(equip)
|
||||||
|
|||||||
@@ -184,8 +184,10 @@ func (p *AdventurePlugin) robbieVisitPlayer(userID id.UserID, displayName string
|
|||||||
func robbieQualifyingItems(inv []AdvItem, equip map[EquipmentSlot]*AdvEquipment) []AdvItem {
|
func robbieQualifyingItems(inv []AdvItem, equip map[EquipmentSlot]*AdvEquipment) []AdvItem {
|
||||||
var result []AdvItem
|
var result []AdvItem
|
||||||
for _, item := range inv {
|
for _, item := range inv {
|
||||||
// Never touch Arena gear or cards
|
// Never touch Arena gear, cards, or consumables. Consumables are a
|
||||||
if item.Type == "ArenaGear" || item.Type == "card" {
|
// player-curated stockpile (crafted or dropped); selling them is an
|
||||||
|
// explicit decision the player must make themselves.
|
||||||
|
if item.Type == "ArenaGear" || item.Type == "card" || item.Type == "consumable" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -778,11 +778,16 @@ func (p *AdventurePlugin) advSellAll(userID id.UserID) string {
|
|||||||
var total float64
|
var total float64
|
||||||
var sold int
|
var sold int
|
||||||
var keptSpecial int
|
var keptSpecial int
|
||||||
|
var keptConsumable int
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if item.Type == "MasterworkGear" || item.Type == "ArenaGear" || item.Type == "card" {
|
if item.Type == "MasterworkGear" || item.Type == "ArenaGear" || item.Type == "card" {
|
||||||
keptSpecial++
|
keptSpecial++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if item.Type == "consumable" {
|
||||||
|
keptConsumable++
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err := removeAdvInventoryItem(item.ID); err != nil {
|
if err := removeAdvInventoryItem(item.ID); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -791,8 +796,13 @@ func (p *AdventurePlugin) advSellAll(userID id.UserID) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sold == 0 {
|
if sold == 0 {
|
||||||
if keptSpecial > 0 {
|
switch {
|
||||||
|
case keptSpecial > 0 && keptConsumable > 0:
|
||||||
|
return "Your inventory only contains special gear and consumables. The merchant won't touch any of it. Use `!adventure equip` for gear or `!adventure sell <name>` for individual consumables."
|
||||||
|
case keptSpecial > 0:
|
||||||
return "Your inventory only contains Masterwork and Arena gear. The merchant doesn't deal in that. Use `!adventure equip` instead."
|
return "Your inventory only contains Masterwork and Arena gear. The merchant doesn't deal in that. Use `!adventure equip` instead."
|
||||||
|
case keptConsumable > 0:
|
||||||
|
return "Your inventory only contains consumables. `sell all` won't touch them — use `!adventure sell <name>` to sell a specific consumable."
|
||||||
}
|
}
|
||||||
return "Your inventory is empty. There is nothing to sell. This is a metaphor for something but now is not the time."
|
return "Your inventory is empty. There is nothing to sell. This is a metaphor for something but now is not the time."
|
||||||
}
|
}
|
||||||
@@ -811,6 +821,9 @@ func (p *AdventurePlugin) advSellAll(userID id.UserID) string {
|
|||||||
if keptSpecial > 0 {
|
if keptSpecial > 0 {
|
||||||
msg += fmt.Sprintf("\n\n(%d special gear items kept — the merchant knows better than to touch those.)", keptSpecial)
|
msg += fmt.Sprintf("\n\n(%d special gear items kept — the merchant knows better than to touch those.)", keptSpecial)
|
||||||
}
|
}
|
||||||
|
if keptConsumable > 0 {
|
||||||
|
msg += fmt.Sprintf("\n(%d consumable(s) kept — `sell all` doesn't touch them. Sell explicitly with `!adventure sell <name>`.)", keptConsumable)
|
||||||
|
}
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user