|
|
|
|
@ -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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|