mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 08:52:42 +00:00
Add chat level perks: XP bonus, shop flavor, rare drop nudge, death pardon (Adventure 2.5 steps 3-6)
- XP bonus: +5% per 10 chat levels (cap +25% at 50+), applies to all adventure and arena XP - Shop flavor: shopkeeper greeting changes at chat levels 10/30/50, replacing random flavor for recognized players - Rare drop nudge: +0.5% per 10 chat levels (cap +2.5%) applied additively to treasure and masterwork drop rates - Death pardon: chat level 20+ gets 33% chance to survive death once per 7 days (converts to bad-failure, quiet DM). Does not apply in arena. Fires before Sovereign Death's Reprieve check. - New adventure_chat_perks.go with perk calculation helpers - LastPardonUsed field + migration on adventure_characters Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,7 @@ func runMigrations(d *sql.DB) error {
|
|||||||
`ALTER TABLE adventure_characters ADD COLUMN last_death_date TEXT NOT NULL DEFAULT ''`,
|
`ALTER TABLE adventure_characters ADD COLUMN last_death_date TEXT NOT NULL DEFAULT ''`,
|
||||||
`ALTER TABLE adventure_characters ADD COLUMN combat_actions_used INTEGER NOT NULL DEFAULT 0`,
|
`ALTER TABLE adventure_characters ADD COLUMN combat_actions_used INTEGER NOT NULL DEFAULT 0`,
|
||||||
`ALTER TABLE adventure_characters ADD COLUMN harvest_actions_used INTEGER NOT NULL DEFAULT 0`,
|
`ALTER TABLE adventure_characters ADD COLUMN harvest_actions_used INTEGER NOT NULL DEFAULT 0`,
|
||||||
|
`ALTER TABLE adventure_characters ADD COLUMN last_pardon_used DATETIME`,
|
||||||
}
|
}
|
||||||
for _, stmt := range columnMigrations {
|
for _, stmt := range columnMigrations {
|
||||||
if _, err := d.Exec(stmt); err != nil {
|
if _, err := d.Exec(stmt); err != nil {
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ func (p *AdventurePlugin) handleShopCmd(ctx MessageContext, args string) error {
|
|||||||
p.shopSessionStart(ctx.Sender)
|
p.shopSessionStart(ctx.Sender)
|
||||||
|
|
||||||
if category == "" {
|
if category == "" {
|
||||||
text := luigiShopGreeting(ctx.Sender, equip, balance, showAll)
|
text := luigiShopGreeting(ctx.Sender, equip, balance, showAll, p.chatLevel(ctx.Sender))
|
||||||
p.pending.Store(string(ctx.Sender), &advPendingInteraction{
|
p.pending.Store(string(ctx.Sender), &advPendingInteraction{
|
||||||
Type: "shop_category",
|
Type: "shop_category",
|
||||||
Data: &advPendingShopCategory{ShowAll: showAll},
|
Data: &advPendingShopCategory{ShowAll: showAll},
|
||||||
@@ -768,6 +768,11 @@ func (p *AdventurePlugin) resolveActivity(ctx MessageContext, char *AdventureCha
|
|||||||
// Select flavor text
|
// Select flavor text
|
||||||
result.FlavorText, result.FlavorKey = p.selectFlavorText(char, result)
|
result.FlavorText, result.FlavorKey = p.selectFlavorText(char, result)
|
||||||
|
|
||||||
|
// Chat level XP bonus
|
||||||
|
if bonus := chatLevelXPBonus(p.chatLevel(char.UserID)); bonus > 0 {
|
||||||
|
result.XPGained = int(float64(result.XPGained) * (1.0 + bonus))
|
||||||
|
}
|
||||||
|
|
||||||
// Apply XP
|
// Apply XP
|
||||||
switch result.XPSkill {
|
switch result.XPSkill {
|
||||||
case "combat":
|
case "combat":
|
||||||
@@ -788,28 +793,41 @@ func (p *AdventurePlugin) resolveActivity(ctx MessageContext, char *AdventureCha
|
|||||||
|
|
||||||
// Handle death
|
// Handle death
|
||||||
deathReprieved := false
|
deathReprieved := false
|
||||||
|
pardonFired := false
|
||||||
if result.Outcome == AdvOutcomeDeath {
|
if result.Outcome == AdvOutcomeDeath {
|
||||||
// Sovereign set: Death's Reprieve — survive lethal outcome
|
// Chat level death pardon (does NOT apply in arena — arena uses separate code path)
|
||||||
if advEquippedArenaSets(equip)["sovereign"] && char.DeathReprieveAvailable() {
|
chatLvl := p.chatLevel(char.UserID)
|
||||||
|
if chatLvl >= 20 && char.PardonAvailable() && rand.Float64() < 0.33 {
|
||||||
|
pardonFired = true
|
||||||
deathReprieved = true
|
deathReprieved = true
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
char.DeathReprieveLast = &now
|
char.LastPardonUsed = &now
|
||||||
|
result.Outcome = AdvOutcomeEmpty
|
||||||
char.GrudgeLocation = loc.Name
|
char.GrudgeLocation = loc.Name
|
||||||
// Gear absorbs the blow — all equipment set to 1 condition
|
}
|
||||||
for _, slot := range allSlots {
|
if !pardonFired {
|
||||||
if eq, ok := equip[slot]; ok {
|
// Sovereign set: Death's Reprieve — survive lethal outcome
|
||||||
eq.Condition = 1
|
if advEquippedArenaSets(equip)["sovereign"] && char.DeathReprieveAvailable() {
|
||||||
|
deathReprieved = true
|
||||||
|
now := time.Now().UTC()
|
||||||
|
char.DeathReprieveLast = &now
|
||||||
|
char.GrudgeLocation = loc.Name
|
||||||
|
// Gear absorbs the blow — all equipment set to 1 condition
|
||||||
|
for _, slot := range allSlots {
|
||||||
|
if eq, ok := equip[slot]; ok {
|
||||||
|
eq.Condition = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Post room announcement
|
||||||
|
nextWindow := now.Add(168 * time.Hour)
|
||||||
|
gr := gamesRoom()
|
||||||
|
if gr != "" {
|
||||||
|
p.SendMessage(gr, renderArenaDeathReprieve(char.DisplayName, loc.Name, nextWindow))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
char.Kill()
|
||||||
|
char.GrudgeLocation = loc.Name
|
||||||
}
|
}
|
||||||
// Post room announcement
|
|
||||||
nextWindow := now.Add(168 * time.Hour)
|
|
||||||
gr := gamesRoom()
|
|
||||||
if gr != "" {
|
|
||||||
p.SendMessage(gr, renderArenaDeathReprieve(char.DisplayName, loc.Name, nextWindow))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
char.Kill()
|
|
||||||
char.GrudgeLocation = loc.Name
|
|
||||||
}
|
}
|
||||||
} else if hasGrudge && (result.Outcome == AdvOutcomeSuccess || result.Outcome == AdvOutcomeExceptional) {
|
} else if hasGrudge && (result.Outcome == AdvOutcomeSuccess || result.Outcome == AdvOutcomeExceptional) {
|
||||||
// Clear grudge on successful return
|
// Clear grudge on successful return
|
||||||
@@ -881,6 +899,11 @@ func (p *AdventurePlugin) resolveActivity(ctx MessageContext, char *AdventureCha
|
|||||||
slog.Error("adventure: failed to send resolution DM", "user", ctx.Sender, "err", err)
|
slog.Error("adventure: failed to send resolution DM", "user", ctx.Sender, "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quiet DM for death pardon
|
||||||
|
if pardonFired {
|
||||||
|
p.SendDM(ctx.Sender, "The healers got there in time. Barely. Don't make this a habit.")
|
||||||
|
}
|
||||||
|
|
||||||
// Send hospital ad on death (delayed, arrives after resolution DM)
|
// Send hospital ad on death (delayed, arrives after resolution DM)
|
||||||
if result.Outcome == AdvOutcomeDeath && !deathReprieved {
|
if result.Outcome == AdvOutcomeDeath && !deathReprieved {
|
||||||
p.sendHospitalAd(ctx.Sender, char)
|
p.sendHospitalAd(ctx.Sender, char)
|
||||||
@@ -960,7 +983,7 @@ func (p *AdventurePlugin) resolveRest(ctx MessageContext, char *AdventureCharact
|
|||||||
// ── Treasure Drop Check ─────────────────────────────────────────────────────
|
// ── Treasure Drop Check ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
func (p *AdventurePlugin) checkTreasureDrop(userID id.UserID, char *AdventureCharacter, loc *AdvLocation) {
|
func (p *AdventurePlugin) checkTreasureDrop(userID id.UserID, char *AdventureCharacter, loc *AdvLocation) {
|
||||||
drop := rollAdvTreasureDrop(loc.Tier, userID)
|
drop := rollAdvTreasureDrop(loc.Tier, userID, p.chatLevel(userID))
|
||||||
if drop == nil {
|
if drop == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -361,12 +361,15 @@ func (p *AdventurePlugin) resolveArenaSurvival(ctx MessageContext, run *ArenaRun
|
|||||||
run.RoundsSurvived++
|
run.RoundsSurvived++
|
||||||
run.LastMonster = monster.Name
|
run.LastMonster = monster.Name
|
||||||
|
|
||||||
// Award battle XP (Ironclad set: Battle-Hardened — +5% XP)
|
// Award battle XP (Ironclad set: Battle-Hardened — +5% XP, chat level bonus)
|
||||||
battleXP := tier.BattleXP
|
battleXP := tier.BattleXP
|
||||||
equip, _ := loadAdvEquipment(ctx.Sender)
|
equip, _ := loadAdvEquipment(ctx.Sender)
|
||||||
if advEquippedArenaSets(equip)["ironclad"] {
|
if advEquippedArenaSets(equip)["ironclad"] {
|
||||||
battleXP = int(float64(battleXP) * 1.05)
|
battleXP = int(float64(battleXP) * 1.05)
|
||||||
}
|
}
|
||||||
|
if bonus := chatLevelXPBonus(p.chatLevel(ctx.Sender)); bonus > 0 {
|
||||||
|
battleXP = int(float64(battleXP) * (1.0 + bonus))
|
||||||
|
}
|
||||||
char.CombatXP += battleXP
|
char.CombatXP += battleXP
|
||||||
leveled, newLevel := checkAdvLevelUp(char, "combat")
|
leveled, newLevel := checkAdvLevelUp(char, "combat")
|
||||||
if leveled {
|
if leveled {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ type AdventureCharacter struct {
|
|||||||
HospitalVisits int
|
HospitalVisits int
|
||||||
RobbieVisitCount int
|
RobbieVisitCount int
|
||||||
LastDeathDate string
|
LastDeathDate string
|
||||||
|
LastPardonUsed *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdvEquipment struct {
|
type AdvEquipment struct {
|
||||||
@@ -205,6 +206,15 @@ func (c *AdventureCharacter) DeathReprieveAvailable() bool {
|
|||||||
return time.Since(*c.DeathReprieveLast) >= 168*time.Hour
|
return time.Since(*c.DeathReprieveLast) >= 168*time.Hour
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PardonAvailable returns true if the chat level death pardon cooldown
|
||||||
|
// has expired (or was never triggered). 7-day rolling cooldown.
|
||||||
|
func (c *AdventureCharacter) PardonAvailable() bool {
|
||||||
|
if c.LastPardonUsed == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return time.Since(*c.LastPardonUsed) >= 168*time.Hour
|
||||||
|
}
|
||||||
|
|
||||||
// Kill marks the character as dead with a 6-hour respawn timer.
|
// Kill marks the character as dead with a 6-hour respawn timer.
|
||||||
func (c *AdventureCharacter) Kill() {
|
func (c *AdventureCharacter) Kill() {
|
||||||
c.Alive = false
|
c.Alive = false
|
||||||
@@ -346,7 +356,7 @@ func loadAdvCharacter(userID id.UserID) (*AdventureCharacter, error) {
|
|||||||
d := db.Get()
|
d := db.Get()
|
||||||
c := &AdventureCharacter{}
|
c := &AdventureCharacter{}
|
||||||
var alive, actionTaken, holidayTaken, rivalUnlocked, babysitAct int
|
var alive, actionTaken, holidayTaken, rivalUnlocked, babysitAct int
|
||||||
var deadUntil, reprieveLast, babysitExp sql.NullTime
|
var deadUntil, reprieveLast, babysitExp, pardonUsed sql.NullTime
|
||||||
|
|
||||||
err := d.QueryRow(`
|
err := d.QueryRow(`
|
||||||
SELECT user_id, display_name,
|
SELECT user_id, display_name,
|
||||||
@@ -360,7 +370,8 @@ func loadAdvCharacter(userID id.UserID) (*AdventureCharacter, error) {
|
|||||||
rival_pool, rival_unlocked_notified,
|
rival_pool, rival_unlocked_notified,
|
||||||
babysit_active, babysit_expires_at, babysit_skill_focus,
|
babysit_active, babysit_expires_at, babysit_skill_focus,
|
||||||
hospital_visits, robbie_visit_count, last_death_date,
|
hospital_visits, robbie_visit_count, last_death_date,
|
||||||
combat_actions_used, harvest_actions_used
|
combat_actions_used, harvest_actions_used,
|
||||||
|
last_pardon_used
|
||||||
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,
|
||||||
@@ -374,6 +385,7 @@ func loadAdvCharacter(userID id.UserID) (*AdventureCharacter, error) {
|
|||||||
&babysitAct, &babysitExp, &c.BabysitSkillFocus,
|
&babysitAct, &babysitExp, &c.BabysitSkillFocus,
|
||||||
&c.HospitalVisits, &c.RobbieVisitCount, &c.LastDeathDate,
|
&c.HospitalVisits, &c.RobbieVisitCount, &c.LastDeathDate,
|
||||||
&c.CombatActionsUsed, &c.HarvestActionsUsed,
|
&c.CombatActionsUsed, &c.HarvestActionsUsed,
|
||||||
|
&pardonUsed,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -392,6 +404,9 @@ func loadAdvCharacter(userID id.UserID) (*AdventureCharacter, error) {
|
|||||||
if babysitExp.Valid {
|
if babysitExp.Valid {
|
||||||
c.BabysitExpiresAt = &babysitExp.Time
|
c.BabysitExpiresAt = &babysitExp.Time
|
||||||
}
|
}
|
||||||
|
if pardonUsed.Valid {
|
||||||
|
c.LastPardonUsed = &pardonUsed.Time
|
||||||
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +474,8 @@ func saveAdvCharacter(char *AdventureCharacter) error {
|
|||||||
rival_pool = ?, rival_unlocked_notified = ?,
|
rival_pool = ?, rival_unlocked_notified = ?,
|
||||||
babysit_active = ?, babysit_expires_at = ?, babysit_skill_focus = ?,
|
babysit_active = ?, babysit_expires_at = ?, babysit_skill_focus = ?,
|
||||||
hospital_visits = ?, robbie_visit_count = ?, last_death_date = ?,
|
hospital_visits = ?, robbie_visit_count = ?, last_death_date = ?,
|
||||||
combat_actions_used = ?, harvest_actions_used = ?
|
combat_actions_used = ?, harvest_actions_used = ?,
|
||||||
|
last_pardon_used = ?
|
||||||
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,
|
||||||
@@ -471,6 +487,7 @@ func saveAdvCharacter(char *AdventureCharacter) error {
|
|||||||
babysitAct, char.BabysitExpiresAt, char.BabysitSkillFocus,
|
babysitAct, char.BabysitExpiresAt, char.BabysitSkillFocus,
|
||||||
char.HospitalVisits, char.RobbieVisitCount, char.LastDeathDate,
|
char.HospitalVisits, char.RobbieVisitCount, char.LastDeathDate,
|
||||||
char.CombatActionsUsed, char.HarvestActionsUsed,
|
char.CombatActionsUsed, char.HarvestActionsUsed,
|
||||||
|
char.LastPardonUsed,
|
||||||
string(char.UserID),
|
string(char.UserID),
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
|
|||||||
38
internal/plugin/adventure_chat_perks.go
Normal file
38
internal/plugin/adventure_chat_perks.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
// ── Chat Level Perks ────────────────────────────────────────────────────────
|
||||||
|
// Passive bonuses based on community chat level. All perks are automatic.
|
||||||
|
|
||||||
|
// chatLevelXPBonus returns the fractional XP bonus for the given chat level.
|
||||||
|
// +5% per 10 levels, capped at +25% at level 50+.
|
||||||
|
func chatLevelXPBonus(chatLevel int) float64 {
|
||||||
|
tier := chatLevel / 10
|
||||||
|
if tier > 5 {
|
||||||
|
tier = 5
|
||||||
|
}
|
||||||
|
return float64(tier) * 0.05
|
||||||
|
}
|
||||||
|
|
||||||
|
// chatLevelRareBonus returns the additive rare drop rate bonus.
|
||||||
|
// +0.5% per 10 levels, capped at +2.5% at level 50+.
|
||||||
|
func chatLevelRareBonus(chatLevel int) float64 {
|
||||||
|
tier := chatLevel / 10
|
||||||
|
if tier > 5 {
|
||||||
|
tier = 5
|
||||||
|
}
|
||||||
|
return float64(tier) * 0.005
|
||||||
|
}
|
||||||
|
|
||||||
|
// shopGreeting returns the shopkeeper's greeting based on chat level.
|
||||||
|
func shopGreeting(chatLevel int) string {
|
||||||
|
switch {
|
||||||
|
case chatLevel >= 50:
|
||||||
|
return "I saw you coming from down the street. Your usual is already on the counter. The shop is open."
|
||||||
|
case chatLevel >= 30:
|
||||||
|
return "Back again. I've started keeping your usual in the back. The shop is open."
|
||||||
|
case chatLevel >= 10:
|
||||||
|
return "Ah. You again. The shop is open."
|
||||||
|
default:
|
||||||
|
return "The shop is open. What do you need?"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -169,8 +169,9 @@ func (p *AdventurePlugin) checkMasterworkDrop(userID id.UserID, char *AdventureC
|
|||||||
return // no masterwork available for this activity+tier (e.g. dungeon)
|
return // no masterwork available for this activity+tier (e.g. dungeon)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Roll for drop
|
// Roll for drop (chat level rare bonus applied additively)
|
||||||
if rand.Float64() >= def.DropRate {
|
dropRate := def.DropRate + chatLevelRareBonus(p.chatLevel(userID))
|
||||||
|
if rand.Float64() >= dropRate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ func (p *AdventurePlugin) shopScheduleBrowseNudge(userID id.UserID) {
|
|||||||
|
|
||||||
// ── Display: Luigi Greeting + Category Menu ─────────────────────────────────
|
// ── Display: Luigi Greeting + Category Menu ─────────────────────────────────
|
||||||
|
|
||||||
func luigiShopGreeting(userID id.UserID, equip map[EquipmentSlot]*AdvEquipment, balance float64, showAll bool) string {
|
func luigiShopGreeting(userID id.UserID, equip map[EquipmentSlot]*AdvEquipment, balance float64, showAll bool, chatLevel int) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
|
||||||
// Check if fully maxed out.
|
// Check if fully maxed out.
|
||||||
@@ -141,7 +141,12 @@ func luigiShopGreeting(userID id.UserID, equip map[EquipmentSlot]*AdvEquipment,
|
|||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
greet, _ := advPickFlavor(luigiGreetings, userID, "luigi_greet")
|
var greet string
|
||||||
|
if chatLevel >= 10 {
|
||||||
|
greet = shopGreeting(chatLevel)
|
||||||
|
} else {
|
||||||
|
greet, _ = advPickFlavor(luigiGreetings, userID, "luigi_greet")
|
||||||
|
}
|
||||||
sb.WriteString(fmt.Sprintf("🛒 **Luigi's**\n💰 Balance: €%.0f\n\n", balance))
|
sb.WriteString(fmt.Sprintf("🛒 **Luigi's**\n💰 Balance: €%.0f\n\n", balance))
|
||||||
sb.WriteString(fmt.Sprintf("*%s*\n\n", greet))
|
sb.WriteString(fmt.Sprintf("*%s*\n\n", greet))
|
||||||
|
|
||||||
@@ -359,7 +364,7 @@ func (p *AdventurePlugin) resolveShopItemChoice(ctx MessageContext, interaction
|
|||||||
}
|
}
|
||||||
balance := p.euro.GetBalance(ctx.Sender)
|
balance := p.euro.GetBalance(ctx.Sender)
|
||||||
|
|
||||||
text := luigiShopGreeting(ctx.Sender, equip, balance, data.ShowAll)
|
text := luigiShopGreeting(ctx.Sender, equip, balance, data.ShowAll, p.chatLevel(ctx.Sender))
|
||||||
p.pending.Store(string(ctx.Sender), &advPendingInteraction{
|
p.pending.Store(string(ctx.Sender), &advPendingInteraction{
|
||||||
Type: "shop_category",
|
Type: "shop_category",
|
||||||
Data: &advPendingShopCategory{ShowAll: data.ShowAll},
|
Data: &advPendingShopCategory{ShowAll: data.ShowAll},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func TestLuigiShopGreeting_Basic(t *testing.T) {
|
|||||||
equip := map[EquipmentSlot]*AdvEquipment{
|
equip := map[EquipmentSlot]*AdvEquipment{
|
||||||
SlotWeapon: {Tier: 2, Condition: 100},
|
SlotWeapon: {Tier: 2, Condition: 100},
|
||||||
}
|
}
|
||||||
text := luigiShopGreeting("@user:test", equip, 5000, false)
|
text := luigiShopGreeting("@user:test", equip, 5000, false, 0)
|
||||||
|
|
||||||
if !strings.Contains(text, "Luigi's") {
|
if !strings.Contains(text, "Luigi's") {
|
||||||
t.Error("greeting should contain Luigi's")
|
t.Error("greeting should contain Luigi's")
|
||||||
@@ -35,7 +35,7 @@ func TestLuigiShopGreeting_MaxedOut(t *testing.T) {
|
|||||||
SlotBoots: {Tier: 5, Condition: 100},
|
SlotBoots: {Tier: 5, Condition: 100},
|
||||||
SlotTool: {Tier: 5, Condition: 100},
|
SlotTool: {Tier: 5, Condition: 100},
|
||||||
}
|
}
|
||||||
text := luigiShopGreeting("@user:test", equip, 99999, false)
|
text := luigiShopGreeting("@user:test", equip, 99999, false, 0)
|
||||||
|
|
||||||
// Should NOT show category grid.
|
// Should NOT show category grid.
|
||||||
if strings.Contains(text, "Reply with a category") {
|
if strings.Contains(text, "Reply with a category") {
|
||||||
@@ -49,7 +49,7 @@ func TestLuigiShopGreeting_MaxedOut(t *testing.T) {
|
|||||||
|
|
||||||
func TestLuigiShopGreeting_ShowAll(t *testing.T) {
|
func TestLuigiShopGreeting_ShowAll(t *testing.T) {
|
||||||
equip := map[EquipmentSlot]*AdvEquipment{}
|
equip := map[EquipmentSlot]*AdvEquipment{}
|
||||||
text := luigiShopGreeting("@user:test", equip, 1000, true)
|
text := luigiShopGreeting("@user:test", equip, 1000, true, 0)
|
||||||
|
|
||||||
// Should contain a show-all comment.
|
// Should contain a show-all comment.
|
||||||
if !strings.Contains(text, "judgment") && !strings.Contains(text, "thoroughness") {
|
if !strings.Contains(text, "judgment") && !strings.Contains(text, "thoroughness") {
|
||||||
|
|||||||
@@ -241,11 +241,12 @@ var advAllTreasures = map[int][]AdvTreasureDef{
|
|||||||
|
|
||||||
// ── Treasure Drop Logic ──────────────────────────────────────────────────────
|
// ── Treasure Drop Logic ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
func rollAdvTreasureDrop(tier int, userID id.UserID) *AdvTreasureDrop {
|
func rollAdvTreasureDrop(tier int, userID id.UserID, chatLevel int) *AdvTreasureDrop {
|
||||||
rate, ok := advTreasureDropRates[tier]
|
rate, ok := advTreasureDropRates[tier]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
rate += chatLevelRareBonus(chatLevel)
|
||||||
|
|
||||||
if rand.Float64() >= rate {
|
if rand.Float64() >= rate {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user