package dnfbridge import ( "errors" "fmt" "longheng.io/server/internal/modules/dnf/dnfenum" ) // The caller holds only its own dungeon lock. Party members publish snapshots // under owner.mu; continuations run in each peer's existing session mailbox. func (s *Service) prepareRuntimePartyDungeonCardRewardsLocked(session *gameSession, runtime *runtimeDungeonState) (bool, error) { owner := runtime.partyDungeonOwner if owner == nil { return true, nil } plan := runtime.settlementResultPlan if plan == nil || plan.clearRewardSnapshot == nil { return false, errors.New("party dungeon result has no owned reward snapshot") } owner.mu.Lock() slot := byte(0xff) expected := uint8(0) for _, member := range owner.members { expected |= uint8(1) << member.slot if member.userID == session.selectedCharacterID && member.generation == session.characterGeneration { slot = member.slot } } if owner.retired || slot >= dungeonCardSlotsPerSide || !runtime.partyMemberIndexed || runtime.partyMemberIndex != slot || owner.cardRewardDepartedSlots&(uint8(1)<= dungeonCardSideCount || memberIndex >= dungeonCardSlotsPerSide || len(body) == 0 { return nil } owner := runtime.partyDungeonOwner member, current := owner.member(source.selectedCharacterID, source.characterGeneration) if !current || member.slot != memberIndex { return fmt.Errorf("party dungeon card source %d is not stable slot %d", source.selectedCharacterID, memberIndex) } for _, peer := range owner.roster() { if peer.userID == source.selectedCharacterID && peer.generation == source.characterGeneration { continue } identity, online := s.onlineGameSessionCharacterSnapshot(peer.userID) if !online || identity.session == nil || identity.generation != peer.generation { return fmt.Errorf("party dungeon card member %d session generation changed", peer.userID) } peerSession := identity.session peerBody := append([]byte(nil), body...) if err := s.postGameSessionCharacterEvent( peerSession, "party-dungeon-card-selection", identity.character, identity.generation, func() error { peerSession.dungeon.mu.Lock() defer peerSession.dungeon.mu.Unlock() peerRuntime := peerSession.dungeon.runtime if peerRuntime == nil || peerRuntime.partyDungeonOwner != owner { return nil } if peerRuntime.partyCardSelectionSeen[side][memberIndex] { return nil } if !peerRuntime.settlementCardLayoutSent { peerRuntime.partyCardSelectionPending = append( peerRuntime.partyCardSelectionPending, peerBody, ) peerRuntime.partyCardSelectionSeen[side][memberIndex] = true return nil } if err := s.sendGameUpperRawClass( peerSession, uint16(dnfenum.CmdPacketSelectCard), peerBody, currentDungeonCardResponseClass, ); err != nil { return err } peerRuntime.partyCardSelectionSeen[side][memberIndex] = true return nil }, ); err != nil { return fmt.Errorf("publish party dungeon card to member %d: %w", peer.userID, err) } } return nil } func (s *Service) flushRuntimePartyDungeonCardSelectionsLocked( session *gameSession, runtime *runtimeDungeonState, ) error { if s == nil || session == nil || runtime == nil || session.dungeon.runtime != runtime || !runtime.settlementCardLayoutSent || len(runtime.partyCardSelectionPending) == 0 { return nil } for len(runtime.partyCardSelectionPending) != 0 { body := runtime.partyCardSelectionPending[0] if err := s.sendGameUpperRawClass( session, uint16(dnfenum.CmdPacketSelectCard), append([]byte(nil), body...), currentDungeonCardResponseClass, ); err != nil { return err } runtime.partyCardSelectionPending = runtime.partyCardSelectionPending[1:] } return nil }