mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 00:52:40 +00:00
Adv 2.0 Phase L1: Babysit pivot + legacy resolver retire
Babysit pivots from harvest service to pet-care + safe-rest perk: - runBabysitDaily/runAutoBabysitDay/runBabysitAction deleted; replaced by runBabysitDailyTrickle (3 pet XP/day while subscribed). - BabysitSafeRest predicate promotes Standard camps to Fortified-tier rest (HP+1d6, threat -5, resources refresh) and reduces wandering monster check campMod from 0 to -4. Hooks live in dnd_expedition_camp.go and dnd_expedition_night.go. - !adventure babysit auto/focus subcommands removed; status/purchase DMs reflect the new perks. Scheduler drops the auto-babysit fallback block (no more silent debits + harvest-day for missed logins). Babysit subscribers still skip the morning DM; trickle runs in the background. TwinBee daily simulator self-contained: simulateTwinBeeOutcome + simulateTwinBeeLoot replace the resolveAdvAction call against a fake CombatLevel=35 character. Outcome distribution and reward shape preserved; gold-share to active players unchanged. Legacy activity resolver retired: resolveAdvAction, advEligibleLocations, AdvEligibleLocation, resolveAdvEmptyOutcome deleted from adventure_activities.go (zero production callers post-babysit/twinbee). AdvActionResult kept — combat_bridge still produces it for arena/dungeon flows; dies in L2/L3. Migration plan doc gogobee_legacy_migration.md added: five-phase L1-L5 plan covering arena (Adv 2.0 boss flow rebuild), co-op deletion, perk-gate sweep, and final teardown of AdventureCharacter + CombatLevel + combat_engine. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -779,173 +779,6 @@ func advEquipmentMasteryBonus(equip map[EquipmentSlot]*AdvEquipment) float64 {
|
||||
return total
|
||||
}
|
||||
|
||||
func resolveAdvAction(char *AdventureCharacter, equip map[EquipmentSlot]*AdvEquipment, loc *AdvLocation, bonuses *AdvBonusSummary, inPenaltyZone bool) *AdvActionResult {
|
||||
result := &AdvActionResult{
|
||||
Location: loc,
|
||||
XPSkill: advXPSkill(loc.Activity),
|
||||
}
|
||||
|
||||
// Apply equipment-mastery loot bonus on a local copy so we don't mutate
|
||||
// the caller's bonuses struct (it's reused across multiple actions).
|
||||
localBonuses := *bonuses
|
||||
localBonuses.LootQuality += advEquipmentMasteryBonus(equip)
|
||||
bonuses = &localBonuses
|
||||
|
||||
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)
|
||||
|
||||
// Roll outcome
|
||||
roll := rand.Float64() * 100
|
||||
|
||||
switch {
|
||||
case roll < probs.DeathPct:
|
||||
result.Outcome = AdvOutcomeDeath
|
||||
case roll < probs.DeathPct+probs.EmptyPct:
|
||||
// Activity-specific empty outcomes
|
||||
result.Outcome = resolveAdvEmptyOutcome(loc, roll)
|
||||
case roll < probs.DeathPct+probs.EmptyPct+probs.SuccessPct:
|
||||
result.Outcome = AdvOutcomeSuccess
|
||||
default:
|
||||
result.Outcome = AdvOutcomeExceptional
|
||||
}
|
||||
|
||||
// Near-death check: survived within 2% of death threshold
|
||||
if result.Outcome != AdvOutcomeDeath && roll < probs.DeathPct+2 && roll >= probs.DeathPct {
|
||||
result.NearDeath = true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// XP calculation
|
||||
xp := advXPForOutcome(loc.Activity, loc.Tier, result.Outcome)
|
||||
if result.Outcome == AdvOutcomeSuccess || result.Outcome == AdvOutcomeExceptional {
|
||||
xp = advXPTable[loc.Activity][loc.Tier].Success
|
||||
if result.Outcome == AdvOutcomeExceptional {
|
||||
xp = advXPTable[loc.Activity][loc.Tier].Exceptional
|
||||
}
|
||||
}
|
||||
|
||||
xpResult := applyXPBonuses(XPBonusParams{
|
||||
BaseXP: xp,
|
||||
NearDeath: result.NearDeath,
|
||||
BonusMult: bonuses.XPMultiplier,
|
||||
Ironclad: advEquippedArenaSets(equip)["ironclad"],
|
||||
OverlevelMult: overlevelMult,
|
||||
})
|
||||
result.XPGained = xpResult.Total
|
||||
result.XPBreakdown = xpResult.Breakdown
|
||||
|
||||
// Equipment degradation on bad outcomes
|
||||
if result.Outcome == AdvOutcomeDeath || result.Outcome == AdvOutcomeEmpty ||
|
||||
result.Outcome == AdvOutcomeCaveIn || result.Outcome == AdvOutcomeBear ||
|
||||
result.Outcome == AdvOutcomeRiver {
|
||||
result.EquipDamage = applyAdvEquipDegradation(equip, result.Outcome)
|
||||
result.EquipBroken = advCheckBrokenEquipment(equip)
|
||||
}
|
||||
|
||||
// Increment actions_used for equipment mastery only on slots relevant
|
||||
// to the activity — a foraging trip shouldn't master a sword, and a
|
||||
// dungeon run shouldn't master a pickaxe. Detect threshold crossings
|
||||
// so the caller can DM a celebration.
|
||||
for slot, eq := range equip {
|
||||
if eq == nil {
|
||||
continue
|
||||
}
|
||||
if !advSlotRelevantToActivity(slot, loc.Activity) {
|
||||
continue
|
||||
}
|
||||
before := eq.ActionsUsed
|
||||
eq.ActionsUsed++
|
||||
for _, t := range advMasteryThresholds {
|
||||
if before < t && eq.ActionsUsed >= t {
|
||||
result.MasteryCrossings = append(result.MasteryCrossings, AdvMasteryCrossing{
|
||||
Slot: slot,
|
||||
ItemName: eq.Name,
|
||||
Threshold: t,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// resolveAdvEmptyOutcome returns an activity-specific "empty" outcome.
|
||||
func resolveAdvEmptyOutcome(loc *AdvLocation, _ float64) AdvOutcomeType {
|
||||
switch loc.Activity {
|
||||
case AdvActivityMining:
|
||||
// 40% chance of cave-in on "empty" result
|
||||
if rand.Float64() < 0.4 {
|
||||
return AdvOutcomeCaveIn
|
||||
}
|
||||
return AdvOutcomeEmpty
|
||||
|
||||
case AdvActivityForaging:
|
||||
// Split empty into specific outcomes
|
||||
r := rand.Float64()
|
||||
switch {
|
||||
case r < 0.35:
|
||||
return AdvOutcomeHornets
|
||||
case r < 0.55:
|
||||
return AdvOutcomeBear
|
||||
case r < 0.70:
|
||||
return AdvOutcomeRiver
|
||||
default:
|
||||
return AdvOutcomeEmpty
|
||||
}
|
||||
|
||||
case AdvActivityFishing:
|
||||
// Fishing empty is just empty — no sub-outcomes
|
||||
return AdvOutcomeEmpty
|
||||
|
||||
default:
|
||||
return AdvOutcomeEmpty
|
||||
}
|
||||
}
|
||||
|
||||
// ── Eligible Locations for DM Menu ───────────────────────────────────────────
|
||||
|
||||
type AdvEligibleLocation struct {
|
||||
Location *AdvLocation
|
||||
InPenaltyZone bool
|
||||
DeathPct float64
|
||||
ExceptionalPct float64
|
||||
}
|
||||
|
||||
func advEligibleLocations(char *AdventureCharacter, equip map[EquipmentSlot]*AdvEquipment, activity AdvActivityType, bonuses *AdvBonusSummary) []AdvEligibleLocation {
|
||||
var eligible []AdvEligibleLocation
|
||||
for _, loc := range allAdvLocations(activity) {
|
||||
loc := loc
|
||||
ok, penalty := advIsEligible(char, equip, &loc, bonuses)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
probs := calculateAdvProbabilities(char, equip, &loc, bonuses, penalty)
|
||||
eligible = append(eligible, AdvEligibleLocation{
|
||||
Location: &loc,
|
||||
InPenaltyZone: penalty,
|
||||
DeathPct: probs.DeathPct,
|
||||
ExceptionalPct: probs.ExceptionalPct,
|
||||
})
|
||||
}
|
||||
return eligible
|
||||
}
|
||||
|
||||
// ── Party Bonus Check ────────────────────────────────────────────────────────
|
||||
|
||||
// advCheckPartyBonus checks if other players visited the same location today.
|
||||
|
||||
Reference in New Issue
Block a user