|
|
|
|
@ -868,11 +868,14 @@ func WanderingMerchantCost(level int) int64 {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// rollRoadEncounter returns whether to trigger an encounter; if so, monster true means combat.
|
|
|
|
|
func (hm *HeroMovement) rollRoadEncounter(now time.Time) (monster bool, enemy model.Enemy, hit bool) {
|
|
|
|
|
func (hm *HeroMovement) rollRoadEncounter(now time.Time, graph *RoadGraph) (monster bool, enemy model.Enemy, hit bool) {
|
|
|
|
|
cfg := tuning.Get()
|
|
|
|
|
if hm.Road == nil || len(hm.Road.Waypoints) < 2 {
|
|
|
|
|
return false, model.Enemy{}, false
|
|
|
|
|
}
|
|
|
|
|
if graph != nil && graph.HeroInTownAt(hm.worldPositionAt(now)) {
|
|
|
|
|
return false, model.Enemy{}, false
|
|
|
|
|
}
|
|
|
|
|
if now.Sub(hm.LastEncounterAt) < time.Duration(cfg.EncounterCooldownBaseMs)*time.Millisecond {
|
|
|
|
|
return false, model.Enemy{}, false
|
|
|
|
|
}
|
|
|
|
|
@ -1030,13 +1033,22 @@ func (hm *HeroMovement) Die() {
|
|
|
|
|
hm.State = model.StateDead
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// worldPositionAt returns hero world (x,y) matching SyncToHero / hero_move (spine + display offset).
|
|
|
|
|
func (hm *HeroMovement) worldPositionAt(now time.Time) (x, y float64) {
|
|
|
|
|
if hm == nil || hm.Hero == nil {
|
|
|
|
|
return 0, 0
|
|
|
|
|
}
|
|
|
|
|
ox, oy := hm.displayOffset(now)
|
|
|
|
|
return hm.CurrentX + ox, hm.CurrentY + oy
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SyncToHero writes movement state back to the hero model for persistence.
|
|
|
|
|
// Position uses the same world coordinates as hero_move / position_sync (road spine + display offset).
|
|
|
|
|
func (hm *HeroMovement) SyncToHero() {
|
|
|
|
|
now := time.Now()
|
|
|
|
|
ox, oy := hm.displayOffset(now)
|
|
|
|
|
hm.Hero.PositionX = hm.CurrentX + ox
|
|
|
|
|
hm.Hero.PositionY = hm.CurrentY + oy
|
|
|
|
|
x, y := hm.worldPositionAt(now)
|
|
|
|
|
hm.Hero.PositionX = x
|
|
|
|
|
hm.Hero.PositionY = y
|
|
|
|
|
hm.Hero.State = hm.State
|
|
|
|
|
if hm.CurrentTownID != 0 {
|
|
|
|
|
id := hm.CurrentTownID
|
|
|
|
|
@ -1683,8 +1695,11 @@ func (hm *HeroMovement) applyRestHealTick(dt float64) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (hm *HeroMovement) rollAdventureEncounter(now time.Time) (monster bool, enemy model.Enemy, hit bool) {
|
|
|
|
|
func (hm *HeroMovement) rollAdventureEncounter(now time.Time, graph *RoadGraph) (monster bool, enemy model.Enemy, hit bool) {
|
|
|
|
|
cfg := tuning.Get()
|
|
|
|
|
if graph != nil && graph.HeroInTownAt(hm.worldPositionAt(now)) {
|
|
|
|
|
return false, model.Enemy{}, false
|
|
|
|
|
}
|
|
|
|
|
cooldown := time.Duration(cfg.AdventureEncounterCooldownMs) * time.Millisecond
|
|
|
|
|
if now.Sub(hm.LastEncounterAt) < cooldown {
|
|
|
|
|
return false, model.Enemy{}, false
|
|
|
|
|
@ -2195,7 +2210,7 @@ func ProcessSingleHeroMovementTick(
|
|
|
|
|
canEncounter := hm.Excursion.Phase == model.ExcursionWild ||
|
|
|
|
|
(hm.Excursion.Phase == model.ExcursionReturn && cfg.AdventureReturnEncounterEnabled)
|
|
|
|
|
if canEncounter && (onEncounter != nil || onMerchantEncounter != nil) {
|
|
|
|
|
monster, enemy, hit := hm.rollAdventureEncounter(now)
|
|
|
|
|
monster, enemy, hit := hm.rollAdventureEncounter(now, graph)
|
|
|
|
|
if hit {
|
|
|
|
|
if monster && onEncounter != nil {
|
|
|
|
|
hm.LastEncounterAt = now
|
|
|
|
|
@ -2272,7 +2287,7 @@ func ProcessSingleHeroMovementTick(
|
|
|
|
|
|
|
|
|
|
canRollEncounter := hm.Road != nil && len(hm.Road.Waypoints) >= 2
|
|
|
|
|
if canRollEncounter && (onEncounter != nil || sender != nil || onMerchantEncounter != nil) {
|
|
|
|
|
monster, enemy, hit := hm.rollRoadEncounter(now)
|
|
|
|
|
monster, enemy, hit := hm.rollRoadEncounter(now, graph)
|
|
|
|
|
if hit {
|
|
|
|
|
if monster {
|
|
|
|
|
if onEncounter != nil {
|
|
|
|
|
|