mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 00:32:40 +00:00
Add overlevel penalty and per-location cooldown to prevent low-tier farming
- Overlevel penalty reduces loot and XP when effective level exceeds location minimum (gap >3: -15%/level, floor 5%) - 3-hour per-location cooldown after successful runs, persisted via activity log - Applies to all activity types (dungeons, mining, foraging, fishing) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -632,6 +632,19 @@ func (p *AdventurePlugin) resolveActivity(ctx MessageContext, char *AdventureCha
|
||||
return p.SendDM(ctx.Sender, fmt.Sprintf("You are not eligible for %s. Your level or equipment tier is too low.", loc.Name))
|
||||
}
|
||||
|
||||
// Per-location cooldown: 3 hours after a successful run
|
||||
if remaining := advLocationCooldown(char.UserID, loc.Name); remaining > 0 {
|
||||
mins := int(remaining.Minutes())
|
||||
if mins < 1 {
|
||||
return p.SendDM(ctx.Sender, fmt.Sprintf("🕐 %s is still being cleared out. Try again in less than a minute, or pick a different location.", loc.Name))
|
||||
}
|
||||
h, m := mins/60, mins%60
|
||||
if h > 0 {
|
||||
return p.SendDM(ctx.Sender, fmt.Sprintf("🕐 %s is still being cleared out. Try again in %dh %dm, or pick a different location.", loc.Name, h, m))
|
||||
}
|
||||
return p.SendDM(ctx.Sender, fmt.Sprintf("🕐 %s is still being cleared out. Try again in %d minutes, or pick a different location.", loc.Name, m))
|
||||
}
|
||||
|
||||
// Resolve the action
|
||||
result := resolveAdvAction(char, equip, loc, bonuses, inPenaltyZone)
|
||||
|
||||
|
||||
@@ -3,10 +3,15 @@ package plugin
|
||||
import (
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"time"
|
||||
|
||||
"gogobee/internal/db"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
const advLocationCooldownDuration = 3 * time.Hour
|
||||
|
||||
// ── Activity & Outcome Types ─────────────────────────────────────────────────
|
||||
|
||||
type AdvActivityType string
|
||||
@@ -339,6 +344,33 @@ func computeAdvBonuses(treasures []AdvTreasureBonus, buffs []AdvBuff, streak int
|
||||
return b
|
||||
}
|
||||
|
||||
// ── Location Cooldown ───────────────────────────────────────────────────────
|
||||
|
||||
// advLocationCooldown returns how long until a player can run the same location
|
||||
// again. Returns 0 if no cooldown is active. Only successful runs trigger cooldown.
|
||||
func advLocationCooldown(userID id.UserID, location string) time.Duration {
|
||||
d := db.Get()
|
||||
var loggedAt string
|
||||
err := d.QueryRow(
|
||||
`SELECT logged_at FROM adventure_activity_log
|
||||
WHERE user_id = ? AND location = ? AND outcome IN ('success', 'exceptional')
|
||||
ORDER BY logged_at DESC LIMIT 1`,
|
||||
string(userID), location,
|
||||
).Scan(&loggedAt)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
t, err := time.Parse("2006-01-02 15:04:05", loggedAt)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
remaining := advLocationCooldownDuration - time.Since(t)
|
||||
if remaining <= 0 {
|
||||
return 0
|
||||
}
|
||||
return remaining
|
||||
}
|
||||
|
||||
// ── Eligibility ──────────────────────────────────────────────────────────────
|
||||
|
||||
// advIsEligible checks if a character can enter a location.
|
||||
@@ -578,6 +610,20 @@ func advCheckBrokenEquipment(equip map[EquipmentSlot]*AdvEquipment) []EquipmentS
|
||||
return broken
|
||||
}
|
||||
|
||||
// ── Overlevel Penalty ───────────────────────────────────────────────────────
|
||||
|
||||
// advOverlevelMultiplier returns a multiplier (0.05–1.0) that reduces XP and
|
||||
// loot when a character's effective level far exceeds the location's minimum.
|
||||
// Gap 0-3: no penalty. Gap 4+: −15% per level over 3, floor 5%.
|
||||
func advOverlevelMultiplier(effectiveLevel, minLevel int) float64 {
|
||||
gap := effectiveLevel - minLevel
|
||||
if gap <= 3 {
|
||||
return 1.0
|
||||
}
|
||||
mult := 1.0 - 0.15*float64(gap-3)
|
||||
return math.Max(0.05, mult)
|
||||
}
|
||||
|
||||
// ── Outcome Resolution ───────────────────────────────────────────────────────
|
||||
|
||||
type AdvActionResult struct {
|
||||
@@ -606,6 +652,10 @@ func resolveAdvAction(char *AdventureCharacter, equip map[EquipmentSlot]*AdvEqui
|
||||
|
||||
probs := calculateAdvProbabilities(char, equip, loc, bonuses, inPenaltyZone)
|
||||
|
||||
// Overlevel penalty — reduces loot and XP for farming low-tier content
|
||||
skillLevel := advEffectiveSkill(char, loc.Activity, bonuses)
|
||||
overlevelMult := advOverlevelMultiplier(skillLevel, loc.MinLevel)
|
||||
|
||||
// Roll outcome
|
||||
roll := rand.Float64() * 100
|
||||
|
||||
@@ -629,6 +679,12 @@ func resolveAdvAction(char *AdventureCharacter, equip map[EquipmentSlot]*AdvEqui
|
||||
// Generate loot for success/exceptional
|
||||
if result.Outcome == AdvOutcomeSuccess || result.Outcome == AdvOutcomeExceptional {
|
||||
result.LootItems = generateAdvLoot(loc, result.Outcome == AdvOutcomeExceptional, bonuses.LootQuality)
|
||||
// Apply overlevel penalty to loot values
|
||||
if overlevelMult < 1.0 {
|
||||
for i := range result.LootItems {
|
||||
result.LootItems[i].Value = max(1, int64(float64(result.LootItems[i].Value)*overlevelMult))
|
||||
}
|
||||
}
|
||||
for _, item := range result.LootItems {
|
||||
result.TotalLootValue += item.Value
|
||||
}
|
||||
@@ -656,6 +712,10 @@ func resolveAdvAction(char *AdventureCharacter, equip map[EquipmentSlot]*AdvEqui
|
||||
if advEquippedArenaSets(equip)["ironclad"] {
|
||||
xp = int(float64(xp) * 1.05)
|
||||
}
|
||||
// Apply overlevel penalty to XP
|
||||
if overlevelMult < 1.0 {
|
||||
xp = max(1, int(float64(xp)*overlevelMult))
|
||||
}
|
||||
result.XPGained = xp
|
||||
|
||||
// Equipment degradation on bad outcomes
|
||||
|
||||
Reference in New Issue
Block a user