diff --git a/backend/internal/game/revive.go b/backend/internal/game/revive.go new file mode 100644 index 0000000..e38eb0a --- /dev/null +++ b/backend/internal/game/revive.go @@ -0,0 +1,59 @@ +package game + +import ( + "errors" + + "github.com/denisovdennis/autohero/internal/model" + "github.com/denisovdennis/autohero/internal/tuning" +) + +var ( + // ErrReviveQuotaExceeded is returned when a non-subscriber has used their free revives. + ErrReviveQuotaExceeded = errors.New("revive quota exceeded") + // ErrReviveNotDead is returned when revive is requested for a living hero. + ErrReviveNotDead = errors.New("hero is not dead") +) + +// IsEffectivelyDead is true when the hero must be revived to keep playing. +func IsEffectivelyDead(hero *model.Hero) bool { + if hero == nil { + return false + } + return hero.State == model.StateDead || hero.HP <= 0 +} + +// CheckPlayerReviveQuota enforces the same limits as POST /hero/revive (non-subscribers only). +func CheckPlayerReviveQuota(hero *model.Hero) error { + if hero == nil { + return ErrReviveNotDead + } + if !hero.SubscriptionActive && hero.ReviveCount >= 2 { + return ErrReviveQuotaExceeded + } + return nil +} + +// ApplyHeroReviveMechanical sets partial HP, walking state, clears debuffs, and consumes resurrection buff. +// Used for timeout auto-revive, offline catch-up, and as the base step for button/admin revive. +func ApplyHeroReviveMechanical(hero *model.Hero) { + if hero == nil { + return + } + hero.HP = int(float64(hero.MaxHP) * tuning.Get().ReviveHpPercent) + if hero.HP < 1 { + hero.HP = 1 + } + hero.State = model.StateWalking + hero.Buffs = model.RemoveBuffType(hero.Buffs, model.BuffResurrection) + hero.Debuffs = nil +} + +// ApplyPlayerReviveProgressCounters matches POST /api/v1/hero/revive side effects (quota uses ReviveCount). +func ApplyPlayerReviveProgressCounters(hero *model.Hero) { + if hero == nil { + return + } + hero.TotalDeaths++ + hero.KillsSinceDeath = 0 + hero.ReviveCount++ +}