package de.oaa.xxx.games.bdsm; import java.time.Duration; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity; import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository; import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository; import de.oaa.xxx.games.bdsm.repository.MitspielerRepository; import de.oaa.xxx.games.chastity.cardlock.CardLockEntity; import de.oaa.xxx.games.chastity.cardlock.CardlockRepository; import de.oaa.xxx.games.history.GameHistoryEntity; import de.oaa.xxx.games.history.GameHistoryRepository; import de.oaa.xxx.games.history.GameRole; import de.oaa.xxx.games.history.GameType; import de.oaa.xxx.social.SystemMessageService; import de.oaa.xxx.social.entity.MessageCause; import de.oaa.xxx.user.UserRepository; /** * Service für komplexe BDSM-Game-Operationen. * Kapselt Spielabschluss-Logik (XP-Vergabe, History) und den BDSM→Chastity-Übergang. */ @Service public class BdsmGameService { private static final Logger LOGGER = LoggerFactory.getLogger(BdsmGameService.class); private final BdsmGameRepository sessionRepository; private final MitspielerRepository mitspielerRepository; private final AktiveSperreRepository aktiveSperreRepository; private final UserRepository userRepository; private final GameHistoryRepository gameHistoryRepository; private final CardlockRepository cardlockRepository; private final SystemMessageService systemMessageService; public BdsmGameService(BdsmGameRepository sessionRepository, MitspielerRepository mitspielerRepository, AktiveSperreRepository aktiveSperreRepository, UserRepository userRepository, GameHistoryRepository gameHistoryRepository, CardlockRepository cardlockRepository, SystemMessageService systemMessageService) { this.sessionRepository = sessionRepository; this.mitspielerRepository = mitspielerRepository; this.aktiveSperreRepository = aktiveSperreRepository; this.userRepository = userRepository; this.gameHistoryRepository = gameHistoryRepository; this.cardlockRepository = cardlockRepository; this.systemMessageService = systemMessageService; } /** * Beendet eine BDSM-Session ordentlich: History speichern, XP vergeben, * Gäste auf eigenem Gerät benachrichtigen, Daten aufräumen. */ @Transactional public void spielAbschliessen(BdsmGameEntity entity) { LocalDateTime endTime = LocalDateTime.now(); long durationMinutes = Duration.between(entity.getStartZeit(), endTime).toMinutes(); GameHistoryEntity entry = new GameHistoryEntity(); entry.setGameName("BDSM Game"); entry.setGameType(GameType.BDSM); entry.setStartTime(entity.getStartZeit()); entry.setEndTime(endTime); entry.setDurationMinutes(durationMinutes); entry.addParticipant(entity.getUserId(), GameRole.PLAYER); entity.getMitspieler().stream() .filter(m -> m.getUserId() != null) .forEach(m -> entry.addParticipant(m.getUserId(), GameRole.PLAYER)); gameHistoryRepository.save(entry); int xp = (int) durationMinutes; userRepository.findById(entity.getUserId()).ifPresent(u -> { u.setBdsmXp(u.getBdsmXp() + xp); userRepository.save(u); }); entity.getMitspieler().stream() .filter(m -> m.getUserId() != null) .forEach(m -> userRepository.findById(m.getUserId()).ifPresent(u -> { u.setBdsmXp(u.getBdsmXp() + xp); userRepository.save(u); })); // Gäste auf eigenem Gerät benachrichtigen String endNachricht = "Das BDSM-Spiel wurde erfolgreich beendet. Danke fürs Mitspielen! 🎉"; entity.getMitspieler().stream() .filter(m -> m.isEigenesGeraet() && m.getUserId() != null) .forEach(m -> systemMessageService.send(entity.getUserId(), m.getUserId(), endNachricht, "/userhome.html", MessageCause.GAME_STATE)); bereinige(entity); } /** * Überführt eine BDSM-Session in ein neues Chastity-Lock (BDSM→Chastity-Transition). * History + XP werden wie beim normalen Spielabschluss vergeben. * * @return Das neu angelegte CardLockEntity * @throws IllegalArgumentException wenn Session oder Template nicht gefunden * @throws IllegalStateException wenn Lockee bereits ein aktives Lock hat */ @Transactional public CardLockEntity zuChastity(UUID sessionId, UUID templateLockId, UUID lockeeUserId, UUID keyholderUserId) { BdsmGameEntity entity = sessionRepository.findById(sessionId) .orElseThrow(() -> new IllegalArgumentException("Session nicht gefunden: " + sessionId)); CardLockEntity template = cardlockRepository.findById(templateLockId) .orElseThrow(() -> new IllegalArgumentException("Template-Lock nicht gefunden: " + templateLockId)); if (lockeeUserId != null && cardlockRepository.existsByLockeeAndStartTimeIsNotNullAndUnlockTimeIsNull(lockeeUserId)) { throw new IllegalStateException("Lockee hat bereits ein aktives Chastity-Lock"); } LocalDateTime now = LocalDateTime.now(); CardLockEntity newLock = new CardLockEntity(); newLock.setName(template.getName()); newLock.setLockee(lockeeUserId); newLock.setKeyholder(keyholderUserId); newLock.setInitialCards(template.getInitialCards()); newLock.setPickEverySeconds(template.getPickEverySeconds()); newLock.setAccumulatePicks(template.isAccumulatePicks()); newLock.setShowRemainingCards(template.isShowRemainingCards()); newLock.setLatestOpeningtime(template.getLatestOpeningtime()); newLock.setHygineOpeningDurationSeconds(template.getHygineOpeningDurationSeconds()); newLock.setHygineOpeningEverySeconds(template.getHygineOpeningEverySeconds()); newLock.setTasks(template.getTasks()); newLock.setRequiresVerification(template.isRequiresVerification()); newLock.setTestLock(false); newLock.setTaskMode(template.getTaskMode()); int codeLines = template.getUnlockCodeLength() != null ? template.getUnlockCodeLength() : 5; newLock.setUnlockCodeLength(codeLines); StringBuilder codeBuilder = new StringBuilder(); java.util.Random rng = new java.util.Random(); for (int i = 0; i < codeLines; i++) codeBuilder.append(rng.nextInt(10)); newLock.setUnlockCode(codeBuilder.toString()); newLock.setStartTime(now); newLock.setAvailableCards(template.getInitialCards() != null ? new ArrayList<>(template.getInitialCards()) : new ArrayList<>()); newLock.setOpenPicks(0); if (template.getPickEverySeconds() != null) { newLock.setNextCardIn(now.plusSeconds(template.getPickEverySeconds())); } if (template.getHygineOpeningEverySeconds() != null) { newLock.setLastHygineOpening(now); } cardlockRepository.save(newLock); // Lockee benachrichtigen if (lockeeUserId != null) { userRepository.findById(keyholderUserId).ifPresent(keyholder -> systemMessageService.send(keyholderUserId, lockeeUserId, keyholder.getName() + " hat nach dem BDSM Game ein Chastity Lock auf dich gesetzt.", "/games/chastity/activelock.html", MessageCause.GAME_STATE)); } // Spielabschluss-Logik (History + XP + Cleanup) LocalDateTime endTime = LocalDateTime.now(); long durationMinutes = Duration.between(entity.getStartZeit(), endTime).toMinutes(); GameHistoryEntity entry = new GameHistoryEntity(); entry.setGameName("BDSM Game"); entry.setGameType(GameType.BDSM); entry.setStartTime(entity.getStartZeit()); entry.setEndTime(endTime); entry.setDurationMinutes(durationMinutes); entry.addParticipant(entity.getUserId(), GameRole.PLAYER); entity.getMitspieler().stream() .filter(m -> m.getUserId() != null) .forEach(m -> entry.addParticipant(m.getUserId(), GameRole.PLAYER)); gameHistoryRepository.save(entry); int xp = (int) durationMinutes; userRepository.findById(entity.getUserId()).ifPresent(u -> { u.setBdsmXp(u.getBdsmXp() + xp); userRepository.save(u); }); entity.getMitspieler().stream() .filter(m -> m.getUserId() != null) .forEach(m -> userRepository.findById(m.getUserId()).ifPresent(u -> { u.setBdsmXp(u.getBdsmXp() + xp); userRepository.save(u); })); bereinige(entity); LOGGER.info("BDSM-Session {} in Chastity-Lock {} überführt (Lockee: {}, Keyholder: {})", sessionId, newLock.getLockId(), lockeeUserId, keyholderUserId); return newLock; } /** Löscht alle Session-Daten (Sperren, Mitspieler, Session selbst). */ private void bereinige(BdsmGameEntity entity) { aktiveSperreRepository.deleteAll(entity.getAktiveSperren()); mitspielerRepository.deleteAll(entity.getMitspieler()); sessionRepository.delete(entity); } }