rest fixes

master
Denis Ranneft 1 month ago
parent 8f6feaa6b2
commit 9f2a7d6cd7

@ -1459,7 +1459,8 @@ func (hm *HeroMovement) beginRoadsideRest(now time.Time) {
RoadFreezeWaypoint: hm.WaypointIndex,
RoadFreezeFraction: hm.WaypointFraction,
}
hm.RestUntil = returnUntil
// RestUntil tracks only the rest (wild) phase; travel out/return is separate.
hm.RestUntil = wildUntil
}
func (hm *HeroMovement) beginAdventureInlineRest(now time.Time) {
@ -1575,7 +1576,7 @@ func ProcessSingleHeroMovementTick(
} else if hm.Excursion.Phase == model.ExcursionWild {
cfg := tuning.Get()
hpFrac := float64(hm.Hero.HP) / float64(hm.Hero.MaxHP)
if hpFrac >= cfg.RoadsideRestExitHp {
if now.After(hm.RestUntil) || hpFrac >= cfg.RoadsideRestExitHp {
hm.Excursion.Phase = model.ExcursionReturn
speed := hm.Speed
if speed < 0.1 {

@ -130,8 +130,15 @@ func TestRoadsideRest_ExitsByTimer(t *testing.T) {
pastTimer := hm.RestUntil.Add(time.Second)
ProcessSingleHeroMovementTick(hero.ID, hm, graph, pastTimer, nil, nil, nil, nil, nil)
if hm.Excursion.Phase != model.ExcursionReturn {
t.Fatalf("expected Return phase after rest timer, got %s", hm.Excursion.Phase)
}
pastReturn := hm.Excursion.ReturnUntil.Add(time.Second)
ProcessSingleHeroMovementTick(hero.ID, hm, graph, pastReturn, nil, nil, nil, nil, nil)
if hm.State != model.StateWalking {
t.Fatalf("expected StateWalking after timer, got %s (rest kind: %s)", hm.State, hm.ActiveRestKind)
t.Fatalf("expected StateWalking after return, got %s (rest kind: %s)", hm.State, hm.ActiveRestKind)
}
}
@ -538,3 +545,58 @@ func TestAdminStopExcursion_RejectsNone(t *testing.T) {
t.Fatal("AdminStopExcursion should reject when no excursion")
}
}
// --- FSM: road freeze + no rest in combat ---
// TestExcursion_FreezesRoadWaypointDuringSession asserts AdvanceTick does not advance the spine
// while an excursion is active (waypoint index/fraction stay at freeze snapshot).
func TestExcursion_FreezesRoadWaypointDuringSession(t *testing.T) {
graph := testGraph()
hero := testHeroOnRoad(1, 500, 1000)
now := time.Now()
hm := NewHeroMovement(hero, graph, now)
hm.State = model.StateWalking
hm.Hero.State = model.StateWalking
hm.beginExcursion(now)
freezeIdx := hm.Excursion.RoadFreezeWaypoint
freezeFr := hm.Excursion.RoadFreezeFraction
// Mid wild phase: several movement ticks should not move along the road polyline.
wildMid := hm.Excursion.OutUntil.Add(hm.Excursion.WildUntil.Sub(hm.Excursion.OutUntil) / 2)
for i := 0; i < 5; i++ {
tick := wildMid.Add(time.Duration(i) * time.Second)
ProcessSingleHeroMovementTick(hero.ID, hm, graph, tick, nil, nil, nil, nil, nil)
if hm.Excursion.Phase == model.ExcursionNone {
t.Fatalf("excursion ended unexpectedly at tick %v", tick)
}
if hm.WaypointIndex != freezeIdx || hm.WaypointFraction != freezeFr {
t.Fatalf("road spine should stay frozen: want idx=%d fr=%v, got idx=%d fr=%v",
freezeIdx, freezeFr, hm.WaypointIndex, hm.WaypointFraction)
}
}
}
// TestLowHP_DoesNotStartRestWhileFighting ensures ProcessSingleHeroMovementTick does not
// transition to roadside or inline rest when the hero is in combat state.
func TestLowHP_DoesNotStartRestWhileFighting(t *testing.T) {
graph := testGraph()
cfg := tuning.Get()
maxHP := 1000
lowHP := int(float64(maxHP)*cfg.LowHpThreshold) - 1
hero := testHeroOnRoad(1, lowHP, maxHP)
now := time.Now()
hm := NewHeroMovement(hero, graph, now)
hm.State = model.StateFighting
hm.Hero.State = model.StateFighting
ProcessSingleHeroMovementTick(hero.ID, hm, graph, now.Add(time.Second), nil, nil, nil, nil, nil)
if hm.State != model.StateFighting {
t.Fatalf("expected StateFighting unchanged, got %s", hm.State)
}
if hm.ActiveRestKind != model.RestKindNone {
t.Fatalf("expected no rest kind, got %s", hm.ActiveRestKind)
}
}

Loading…
Cancel
Save