mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 00:32:40 +00:00
Phase-H review fixes: camp, fork streak, pet proc, BG transit, legacy streak
- Camp: campLocationCheck rejects only on live combat; no-encounter and post-kill rooms are campable (kills the misleading "clear it first"). - Fork: markActedToday moved after the pending-fork early-return so spamming !zone advance at a fork no longer keeps the daily streak alive. - Pet whiff/deflect: single proc spent on the first multiattack swing only (was applied to every swing); also dedups the per-swing event spam. - Autopilot: background region crossing now runs an HP/SU preflight and pauses if low, instead of burning a transit day while the player is idle. - Legacy streak: acted-branch restamps LastActionDate=today so a purely-legacy actor's streak no longer resets to 1 every night.
This commit is contained in:
@@ -486,6 +486,11 @@ func (p *AdventurePlugin) midnightReset() error {
|
||||
if char.CurrentStreak > char.BestStreak {
|
||||
char.BestStreak = char.CurrentStreak
|
||||
}
|
||||
// Restamp to today (mirrors the busy branch above). A purely-legacy
|
||||
// actor whose action path bumps CombatActionsUsed/HarvestActionsUsed
|
||||
// but never stamps LastActionDate lands here with a stale date; without
|
||||
// this its streak would reset to 1 every night even with continuous play.
|
||||
char.LastActionDate = today
|
||||
_ = saveAdvCharacter(&char)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,11 +348,12 @@ func (te *turnEngine) stepEnemyTurn() {
|
||||
}
|
||||
|
||||
if !abilityDealtDamage {
|
||||
// Pet defensive procs are a per-round concept (mirrors the auto-resolve
|
||||
// engine): roll once for the whole enemy turn, then apply to every swing
|
||||
// in a multiattack profile. Whiff makes the enemy's attack this round a
|
||||
// guaranteed miss; deflect halves the incoming damage. The shared
|
||||
// resolveEnemyAttack primitive consumes both flags.
|
||||
// Pet defensive procs are a single proc per enemy turn: roll once, then
|
||||
// spend it on the first swing only. Whiff makes that one swing a
|
||||
// guaranteed miss; deflect halves its damage. Against a multiattack the
|
||||
// remaining swings resolve normally — a single proc shouldn't nullify a
|
||||
// boss's whole multiattack round. (This deliberately diverges from the
|
||||
// auto-resolve engine's apply-to-all model.)
|
||||
petWhiff := te.player.Mods.PetWhiffProc > 0 && te.st.randFloat() < te.player.Mods.PetWhiffProc
|
||||
petDeflect := te.player.Mods.PetDeflectProc > 0 && te.st.randFloat() < te.player.Mods.PetDeflectProc
|
||||
if petDeflect {
|
||||
@@ -365,11 +366,14 @@ func (te *turnEngine) stepEnemyTurn() {
|
||||
// resolveEnemyAttack returns true when the fight is decided — either the
|
||||
// player went down without a death save, or a reflect consumable killed
|
||||
// the enemy. Disambiguate by inspecting HP.
|
||||
for _, atk := range enemyAttackProfile(te.sess.EnemyID, te.enemy.Stats) {
|
||||
for i, atk := range enemyAttackProfile(te.sess.EnemyID, te.enemy.Stats) {
|
||||
swing := *te.enemy
|
||||
swing.Stats.Attack = atk.Damage
|
||||
swing.Stats.AttackBonus = atk.AttackBonus
|
||||
decided := resolveEnemyAttack(te.st, te.player, &swing, &turnCombatPhase, te.result, petWhiff, petDeflect, false)
|
||||
// Spend the proc on the first swing only; later swings see false.
|
||||
swingWhiff := petWhiff && i == 0
|
||||
swingDeflect := petDeflect && i == 0
|
||||
decided := resolveEnemyAttack(te.st, te.player, &swing, &turnCombatPhase, te.result, swingWhiff, swingDeflect, false)
|
||||
if te.st.playerHP <= 0 {
|
||||
te.finish(CombatStatusLost)
|
||||
return
|
||||
|
||||
@@ -361,19 +361,20 @@ func campLocationCheck(exp *Expedition) (cleared bool, problem string) {
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
// Not yet advanced-past, but if there's no live combat encounter in
|
||||
// this room, it's still safe to rest in. Forward-only navigation means
|
||||
// players naturally pause right after a kill before advancing — the
|
||||
// "cleared" flag would otherwise force-downgrade their camp to rough.
|
||||
// Not yet advanced-past, but the only thing that bars a rest is a live
|
||||
// fight. Forward-only navigation means players pause right after a kill
|
||||
// before advancing, and peaceful/exploration/loot rooms never spawn an
|
||||
// encounter at all — both are safe to rest in. The "cleared" flag would
|
||||
// otherwise reject standard camp here with a misleading "clear it first".
|
||||
encID := encounterIDForRoom(run.CurrentRoom)
|
||||
sess, err := getCombatSessionForEncounter(run.RunID, encID)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
if sess != nil && sess.Status != CombatStatusActive {
|
||||
return true, ""
|
||||
if sess != nil && sess.Status == CombatStatusActive {
|
||||
return false, "You can't camp mid-fight — finish the encounter first."
|
||||
}
|
||||
return false, ""
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// campCurrentRoomIndex returns 0 (entry) when no room context exists.
|
||||
|
||||
@@ -655,6 +655,18 @@ func (p *AdventurePlugin) runAutopilotWalk(ctx MessageContext, maxRooms int, com
|
||||
IsMultiRegionZone(fresh.ZoneID) {
|
||||
if cur, ok := CurrentRegion(fresh); ok {
|
||||
if next, ok := nextRegion(fresh.ZoneID, cur.ID); ok {
|
||||
// A region crossing burns a transit day + supplies and
|
||||
// draws unprotected wandering damage. On the background
|
||||
// walk, don't cross while the player is weak — preflight
|
||||
// HP/SU and hand the crossing back to a foreground
|
||||
// `!region travel` / `!expedition run` if either is low.
|
||||
if compact {
|
||||
if msg, stop := autopilotPreflight(ctx.Sender, fresh); stop {
|
||||
finalMsg = res.final + "\n\n" + msg
|
||||
reason = stopPreflight
|
||||
break
|
||||
}
|
||||
}
|
||||
stream = append(stream, res.final)
|
||||
transit, terr := p.advanceToNextRegion(ctx.Sender, fresh, cur, next)
|
||||
if terr != nil {
|
||||
|
||||
@@ -458,23 +458,25 @@ func (p *AdventurePlugin) advanceOnceWithOpts(ctx MessageContext, compact bool)
|
||||
reason: stopBlocked,
|
||||
}, nil
|
||||
}
|
||||
// compact==true is the background auto-walk path; only credit
|
||||
// player-initiated advances toward the daily streak.
|
||||
if !compact {
|
||||
markActedToday(ctx.Sender)
|
||||
}
|
||||
_ = applyMoodDecayIfStale(run)
|
||||
zone := zoneOrFallback(run.ZoneID)
|
||||
// A pending fork means advanceTransitionGraph already cleared the
|
||||
// current room and stopped — re-running resolveRoom would re-fire
|
||||
// combat and re-drop loot on the same room. Re-emit the fork prompt
|
||||
// and let the caller surface it; the player commits via !zone go <n>.
|
||||
// This returns *before* crediting the daily streak: spamming `!zone
|
||||
// advance` at a fork resolves no room, so it must not keep the streak alive.
|
||||
if pf, derr := decodePendingFork(run.NodeChoices); derr == nil && pf != nil {
|
||||
return advanceResult{
|
||||
final: renderForkPrompt(zone, *pf),
|
||||
reason: stopFork,
|
||||
}, nil
|
||||
}
|
||||
// compact==true is the background auto-walk path; only credit
|
||||
// player-initiated advances toward the daily streak.
|
||||
if !compact {
|
||||
markActedToday(ctx.Sender)
|
||||
}
|
||||
prev := run.CurrentRoomType()
|
||||
prevIdx := run.CurrentRoom
|
||||
|
||||
|
||||
Reference in New Issue
Block a user