package dnfbridge import ( "context" "strconv" dnfitemcooldown "longheng.io/server/internal/modules/dnf/itemcooldown" dnfrepo "longheng.io/server/internal/modules/dnf/repository" ) // resetHealingPotionCooldownsOnDungeonEntry clears the durable cooldown windows // of HP/MP recovery potions when a new dungeon run is committed, so every entry // starts with potions ready instead of inheriting the previous run's window. // // Only recovery consumables are touched: tickets, stat-change potions and other // [cooltime maintenance] items keep their cross-session windows. func (s *Service) resetHealingPotionCooldownsOnDungeonEntry(session *gameSession, source string) { if s == nil || session == nil || session.selectedCharacterID == 0 { return } repositories, ok := s.repositoryGroup() if !ok || repositories.CharacterAssets == nil { return } catalog, catalogErr := s.currentPVFItemCatalog() if catalogErr != nil { s.logGameEvent(session, "game-dungeon-potion-cooldown-reset-skipped", "char_id", session.selectedCharacterID, "source", source, "reason", "catalog_unavailable", "error", catalogErr) return } characterID := strconv.FormatUint(uint64(session.selectedCharacterID), 10) ctx, cancel := context.WithTimeout(context.Background(), createWriteTimeout) defer cancel() cleared := 0 var remaining []dnfitemcooldown.Entry err := repositories.CharacterAssets.WithinCharacterAssets(ctx, characterID, func( characters dnfrepo.CharacterRepository, _ dnfrepo.InventoryRepository, _ dnfrepo.EquipmentRepository, ) error { character, found, loadErr := characters.Load(ctx, characterID) if loadErr != nil { return loadErr } if !found || character.Stats == nil { return nil } character = dnfrepo.CloneCharacter(character) for _, entry := range dnfitemcooldown.Entries(character.Stats) { definition, resolveErr := catalog.ResolveItem(uint32(entry.ItemID)) if resolveErr != nil { continue } if !definition.HasHPRecovery && !definition.HasMPRecovery { continue } dnfitemcooldown.Clear(character.Stats, entry.ItemID) cleared++ } if cleared == 0 { return nil } remaining = dnfitemcooldown.ActiveEntries(character.Stats, s.gameplayNow().UTC().Unix()) return dnfrepo.SaveCharacterFields(ctx, characters, character, dnfrepo.CharacterFieldStats) }) if err != nil { s.logGameEvent(session, "game-dungeon-potion-cooldown-reset-failed", "char_id", session.selectedCharacterID, "source", source, "error", err) return } if cleared > 0 { // 客户端按同一份 PVF 自己起本地冷却,只清服务端记录不够: // 这里把冷却列表(class-0/op172)重新下发一次(空列表就是 count=0), // 让客户端把本地倒计时清掉。 body := buildCurrentItemCooltimeBody(remaining, s.gameplayNow().UTC().Unix()) if len(body) == 0 { body = []byte{0} } sendErr := s.sendGameUpperRawClass(session, currentLoadCooltimeItemInfoMsgID, body, 0) s.logGameEvent(session, "game-dungeon-potion-cooldown-reset", "char_id", session.selectedCharacterID, "source", source, "cleared", cleared, "remaining", len(remaining), "projection_sent", sendErr == nil, "error", sendErr) } }