From 655cdad796d535373ea48caf5ca0e5ccec350460 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 18 Mar 2026 22:35:21 +0100 Subject: [PATCH] =?UTF-8?q?=C3=84nderungen=20am=20Msessage=20System,=20Dat?= =?UTF-8?q?enschutz-Einstellungen=20hinzugef=C3=BCgt,=20BDSM=20und=20CardL?= =?UTF-8?q?ock=20Game=20weiterverfeinert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 4 +- .metadata/.lock_info | 4 +- .metadata/.log | 970 +++++ .../project-preferences/xxxthegame | 4 +- .../.projects/xxxthegame/.markers | Bin 1201 -> 2826 bytes .../.safetable/org.eclipse.core.resources | Bin 1310 -> 910 bytes .../org.eclipse.e4.workbench/workbench.xmi | 3841 ++++++++--------- .../org.eclipse.jdt.core/1865797976.index | Bin 8225149 -> 8225149 bytes .../org.eclipse.jdt.core/externalFilesCache | Bin 33772 -> 33891 bytes .../externalLibsTimeStamps | Bin 17370 -> 17370 bytes .../org.eclipse.jdt.core/nonChainingJarsCache | Bin 16475 -> 16538 bytes .../org.eclipse.jdt.core/savedIndexNames.txt | 189 +- .../QualifiedTypeNameHistory.xml | 1 + .../org.eclipse.jdt.ui/dialog_settings.xml | 13 + .../.plugins/org.eclipse.m2e.logback/0.log | 1 + .../dialog_settings.xml | 2 +- .metadata/version.ini | 2 +- .../java/de/oaa/xxx/aufgaben/Finisher.java | 2 +- .../xxx/aufgaben/entity/AufgabeEntity.java | 6 +- .../aufgaben/entity/AufgabenGruppeEntity.java | 2 +- .../xxx/aufgaben/entity/FinisherEntity.java | 8 +- .../oaa/xxx/aufgaben/entity/SperreEntity.java | 2 +- .../oaa/xxx/aufgaben/entity/StrafeEntity.java | 6 +- .../{session => games/bdsm}/AktiveSperre.java | 2 +- .../bdsm}/AufgabeAnzeige.java | 46 +- .../{session => games/bdsm}/AufgabeArt.java | 2 +- .../Session.java => games/bdsm/BdsmGame.java} | 5 +- .../bdsm/BdsmGameDurchfuehren.java} | 516 +-- .../xxx/{session => games/bdsm}/Callback.java | 2 +- .../bdsm}/GeschlechtEnum.java | 2 +- .../{session => games/bdsm}/Mitspieler.java | 4 +- .../{session => games/bdsm}/RolleEnum.java | 2 +- .../xxx/{session => games/bdsm}/Werkzeug.java | 2 +- .../bdsm}/aufgaben/Aufgabe.java | 6 +- .../bdsm}/aufgaben/AufgabenList.java | 2 +- .../bdsm}/aufgaben/Finisher.java | 6 +- .../bdsm}/aufgaben/Sperre.java | 6 +- .../bdsm}/aufgaben/Strafe.java | 6 +- .../controller/BdsmEinladungController.java | 195 + .../bdsm/controller/BdsmGameController.java} | 155 +- .../bdsm}/controller/SperreController.java | 32 +- .../bdsm}/entity/AktiveSperreEntity.java | 10 +- .../games/bdsm/entity/BdsmDefaultsEntity.java | 30 + .../bdsm/entity/BdsmEinladungEntity.java | 50 + .../bdsm/entity/BdsmGameEntity.java} | 12 +- .../bdsm}/entity/MitspielerEntity.java | 160 +- .../repository/AktiveSperreRepository.java | 4 +- .../repository/BdsmDefaultsRepository.java | 11 + .../repository/BdsmEinladungRepository.java | 17 + .../bdsm/repository/BdsmGameRepository.java | 12 + .../repository/MitspielerRepository.java | 4 +- .../bdsm}/sperre/SperreCallback.java | 4 +- .../bdsm}/sperre/SperreVerarbeiten.java | 26 +- .../sperre/SperrenVerlaengernCallback.java | 4 +- .../chastity/LockeeInvitationController.java | 33 +- .../chastity/cardlock/CardLockController.java | 125 +- .../chastity/cardlock/CardLockService.java | 86 +- .../chastity/cardlock/TaskCardController.java | 25 +- .../chastity/cardlock/TaskVoteScheduler.java | 26 +- .../history/LockHistoryController.java | 57 - .../chastity/history/LockHistoryDTO.java | 11 - .../chastity/history/LockHistoryEntity.java | 53 - .../history/LockHistoryRepository.java | 12 - .../games/history/GameHistoryController.java | 82 + .../oaa/xxx/games/history/GameHistoryDTO.java | 17 + .../xxx/games/history/GameHistoryEntity.java | 49 + .../history/GameHistoryParticipantEntity.java | 30 + .../GameHistoryParticipantRepository.java | 8 + .../games/history/GameHistoryRepository.java | 17 + .../de/oaa/xxx/games/history/GameRole.java | 7 + .../de/oaa/xxx/games/history/GameType.java | 8 + .../de/oaa/xxx/mail/MailTemplateService.java | 50 + .../registration/ActivationController.java | 8 +- .../de/oaa/xxx/registration/Registration.java | 2 + .../registration/RegistrationController.java | 8 + .../xxx/registration/RegistrationEntity.java | 5 + .../session/repository/SessionRepository.java | 12 - .../de/oaa/xxx/social/SocialController.java | 69 +- .../oaa/xxx/social/SystemMessageService.java | 103 + .../de/oaa/xxx/social/dto/UserProfile.java | 15 +- .../oaa/xxx/social/entity/MessageCause.java | 8 + .../oaa/xxx/social/entity/MessageEntity.java | 4 + .../entity/NotificationPreferenceEntity.java | 40 + .../NotificationPreferenceRepository.java | 16 + .../java/de/oaa/xxx/user/Registration.java | 21 - .../java/de/oaa/xxx/user/Sichtbarkeit.java | 7 + .../src/main/java/de/oaa/xxx/user/User.java | 8 +- .../java/de/oaa/xxx/user/UserController.java | 181 +- .../main/java/de/oaa/xxx/user/UserEntity.java | 41 +- .../src/main/resources/static/aufgaben.html | 21 +- .../main/resources/static/bdsm-einladung.html | 128 + .../static/{sessionbdsm.html => bdsm.html} | 12 +- ...sessionbdsmingame.html => bdsmingame.html} | 215 +- .../main/resources/static/bdsmplayers.html | 743 ++++ .../{sessionbdsmtasks.html => bdsmtasks.html} | 19 +- .../{sessionbdsmtoys.html => bdsmtoys.html} | 19 +- .../src/main/resources/static/bdsmwarten.html | 92 + .../src/main/resources/static/benutzer.html | 193 +- .../main/resources/static/einladungen.html | 76 +- .../main/resources/static/einstellungen.html | 968 +++++ .../src/main/resources/static/js/sidebar.js | 6 +- .../resources/static/js/social-sidebar.js | 6 +- .../src/main/resources/static/profile.html | 245 +- .../main/resources/static/registration.html | 22 +- .../resources/static/sessionbdsmplayers.html | 439 -- .../src/main/resources/static/userhome.html | 2 +- 106 files changed, 7156 insertions(+), 3686 deletions(-) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/AktiveSperre.java (92%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/AufgabeAnzeige.java (81%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/AufgabeArt.java (63%) rename xxxthegame/src/main/java/de/oaa/xxx/{session/Session.java => games/bdsm/BdsmGame.java} (88%) rename xxxthegame/src/main/java/de/oaa/xxx/{session/SessionDurchfuehren.java => games/bdsm/BdsmGameDurchfuehren.java} (91%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/Callback.java (88%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/GeschlechtEnum.java (65%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/Mitspieler.java (87%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/RolleEnum.java (74%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/Werkzeug.java (93%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/aufgaben/Aufgabe.java (88%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/aufgaben/AufgabenList.java (89%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/aufgaben/Finisher.java (80%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/aufgaben/Sperre.java (84%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/aufgaben/Strafe.java (87%) create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmEinladungController.java rename xxxthegame/src/main/java/de/oaa/xxx/{session/controller/SessionController.java => games/bdsm/controller/BdsmGameController.java} (56%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/controller/SperreController.java (81%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/entity/AktiveSperreEntity.java (89%) create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmDefaultsEntity.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmEinladungEntity.java rename xxxthegame/src/main/java/de/oaa/xxx/{session/entity/SessionEntity.java => games/bdsm/entity/BdsmGameEntity.java} (81%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/entity/MitspielerEntity.java (86%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/repository/AktiveSperreRepository.java (86%) create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmDefaultsRepository.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmEinladungRepository.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmGameRepository.java rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/repository/MitspielerRepository.java (62%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/sperre/SperreCallback.java (87%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/sperre/SperreVerarbeiten.java (77%) rename xxxthegame/src/main/java/de/oaa/xxx/{session => games/bdsm}/sperre/SperrenVerlaengernCallback.java (85%) delete mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryController.java delete mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryDTO.java delete mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryEntity.java delete mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryRepository.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryController.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryDTO.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryEntity.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryParticipantEntity.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryParticipantRepository.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryRepository.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/history/GameRole.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/games/history/GameType.java delete mode 100644 xxxthegame/src/main/java/de/oaa/xxx/session/repository/SessionRepository.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/social/SystemMessageService.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/social/entity/MessageCause.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/social/entity/NotificationPreferenceEntity.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/social/repository/NotificationPreferenceRepository.java delete mode 100644 xxxthegame/src/main/java/de/oaa/xxx/user/Registration.java create mode 100644 xxxthegame/src/main/java/de/oaa/xxx/user/Sichtbarkeit.java create mode 100644 xxxthegame/src/main/resources/static/bdsm-einladung.html rename xxxthegame/src/main/resources/static/{sessionbdsm.html => bdsm.html} (95%) rename xxxthegame/src/main/resources/static/{sessionbdsmingame.html => bdsmingame.html} (68%) create mode 100644 xxxthegame/src/main/resources/static/bdsmplayers.html rename xxxthegame/src/main/resources/static/{sessionbdsmtasks.html => bdsmtasks.html} (93%) rename xxxthegame/src/main/resources/static/{sessionbdsmtoys.html => bdsmtoys.html} (92%) create mode 100644 xxxthegame/src/main/resources/static/bdsmwarten.html create mode 100644 xxxthegame/src/main/resources/static/einstellungen.html delete mode 100644 xxxthegame/src/main/resources/static/sessionbdsmplayers.html diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 09f8071..ec2dde6 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -23,7 +23,9 @@ "Bash(./gradlew compileJava -q 2>&1 | tail -30)", "Bash(ls -lah /home/mario/Workspaces/xxx-thegame/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/*.java)", "Bash(for f:*)", - "Bash(ls:*)" + "Bash(ls:*)", + "Bash(./gradlew compileJava)", + "Bash(./gradlew build:*)" ] } } diff --git a/.metadata/.lock_info b/.metadata/.lock_info index c1de82b..f5a0191 100644 --- a/.metadata/.lock_info +++ b/.metadata/.lock_info @@ -1,5 +1,5 @@ -#Tue Mar 17 19:55:50 CET 2026 +#Wed Mar 18 15:28:58 CET 2026 display=\:0 host=Mario-Linux -process-id=148721 +process-id=26624 user=mario diff --git a/.metadata/.log b/.metadata/.log index 68ec10f..36798dc 100644 --- a/.metadata/.log +++ b/.metadata/.log @@ -1097,3 +1097,973 @@ Caused by: java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git at java.base/java.io.FileInputStream.(FileInputStream.java:106) at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:204) ... 68 more + +!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-17 23:01:40.053 +!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-17 23:01:40.298 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) +!SESSION 2026-03-18 15:28:52.788 ----------------------------------------------- +eclipse.buildId=4.39.0.20260305-0817 +java.version=21.0.10 +java.vendor=Eclipse Adoptium +BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE +Framework arguments: -product org.eclipse.epp.package.java.product +Command-line arguments: -os linux -ws gtk -arch x86_64 -product org.eclipse.epp.package.java.product + +!ENTRY ch.qos.logback.classic 1 0 2026-03-18 15:28:53.799 +!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized. + +!ENTRY ch.qos.logback.classic 1 0 2026-03-18 15:28:58.414 +!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml + +!ENTRY org.eclipse.ui 2 0 2026-03-18 15:28:58.572 +!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points. +!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-18 15:28:58.572 +!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory' + +!ENTRY org.eclipse.ui 2 0 2026-03-18 15:28:58.702 +!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points. +!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-18 15:28:58.702 +!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory' + +!ENTRY org.eclipse.ui.workbench 4 0 2026-03-18 16:50:37.017 +!MESSAGE Dynamic menu contribution 'DynamicContributionItems(id=org.eclipse.terminal.connector.local.LocalLauncherDynamicContributionItems, visible=true)' threw an unexpected exception +!STACK 0 +org.eclipse.swt.SWTException: i/o error (java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden)) + at org.eclipse.swt.SWT.error(SWT.java:4950) + at org.eclipse.swt.SWT.error(SWT.java:4865) + at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:207) + at org.eclipse.swt.graphics.ImageLoader.load(ImageLoader.java:198) + at org.eclipse.terminal.view.ui.internal.local.showin.ExternalExecutablesUtils.loadImage(ExternalExecutablesUtils.java:38) + at org.eclipse.terminal.view.ui.internal.local.showin.DynamicContributionItems.getContributionItems(DynamicContributionItems.java:76) + at org.eclipse.ui.actions.CompoundContributionItem.getContributionItemsToFill(CompoundContributionItem.java:83) + at org.eclipse.ui.actions.CompoundContributionItem.fill(CompoundContributionItem.java:57) + at org.eclipse.ui.internal.menus.DynamicMenuContributionItem.fill(DynamicMenuContributionItem.java:194) + at org.eclipse.jface.action.MenuManager.doItemFill(MenuManager.java:727) + at org.eclipse.jface.action.MenuManager.update(MenuManager.java:804) + at org.eclipse.jface.action.MenuManager.update(MenuManager.java:671) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.scheduleManagerUpdate(MenuManagerRenderer.java:1149) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.subscribeUIElementTopicVisible(MenuManagerRenderer.java:211) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:58) + at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:183) + at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:136) + at org.eclipse.swt.widgets.Display.syncExec(Display.java:5950) + at org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize.syncExec(DisplayUISynchronize.java:34) + at org.eclipse.e4.ui.internal.di.UIEventObjectSupplier$UIEventHandler.handleEvent(UIEventObjectSupplier.java:65) + at org.eclipse.equinox.internal.event.EventHandlerWrapper.handleEvent(EventHandlerWrapper.java:206) + at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:201) + at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:1) + at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230) + at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151) + at org.eclipse.equinox.internal.event.EventAdminImpl.dispatchEvent(EventAdminImpl.java:132) + at org.eclipse.equinox.internal.event.EventAdminImpl.sendEvent(EventAdminImpl.java:73) + at org.eclipse.equinox.internal.event.EventComponent.sendEvent(EventComponent.java:48) + at org.eclipse.e4.ui.services.internal.events.EventBroker.send(EventBroker.java:55) + at org.eclipse.e4.ui.internal.workbench.UIEventPublisher.notifyChanged(UIEventPublisher.java:61) + at org.eclipse.emf.common.notify.impl.BasicNotifierImpl.eNotify(BasicNotifierImpl.java:424) + at org.eclipse.e4.ui.model.application.ui.impl.UIElementImpl.setVisible(UIElementImpl.java:365) + at org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.updateVisibility(ContributionRecord.java:110) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:169) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:179) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.showMenu(MenuManagerShowProcessor.java:243) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.menuAboutToHide(MenuManagerShowProcessor.java:111) + at org.eclipse.jface.internal.MenuManagerEventHelper.showEventPostHelper(MenuManagerEventHelper.java:89) + at org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:467) + at org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:493) + at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:297) + at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:91) + at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5845) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1656) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1682) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1661) + at org.eclipse.swt.widgets.Menu._setVisible(Menu.java:290) + at org.eclipse.swt.widgets.Display.runPopups(Display.java:5102) + at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4488) + at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1160) + at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339) + at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1051) + at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153) + at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:684) + at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339) + at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583) + at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173) + at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:185) + at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:219) + at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:149) + at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:115) + at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:467) + at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:298) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:615) + at org.eclipse.equinox.launcher.Main.basicRun(Main.java:563) + at org.eclipse.equinox.launcher.Main.run(Main.java:1415) + at org.eclipse.equinox.launcher.Main.main(Main.java:1387) +Caused by: java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at java.base/java.io.FileInputStream.(FileInputStream.java:106) + at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:204) + ... 68 more + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 17:25:44.906 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.ui.workbench 4 0 2026-03-18 17:33:38.648 +!MESSAGE Dynamic menu contribution 'DynamicContributionItems(id=org.eclipse.terminal.connector.local.LocalLauncherDynamicContributionItems, visible=true)' threw an unexpected exception +!STACK 0 +org.eclipse.swt.SWTException: i/o error (java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden)) + at org.eclipse.swt.SWT.error(SWT.java:4950) + at org.eclipse.swt.SWT.error(SWT.java:4865) + at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:207) + at org.eclipse.swt.graphics.ImageLoader.load(ImageLoader.java:198) + at org.eclipse.terminal.view.ui.internal.local.showin.ExternalExecutablesUtils.loadImage(ExternalExecutablesUtils.java:38) + at org.eclipse.terminal.view.ui.internal.local.showin.DynamicContributionItems.getContributionItems(DynamicContributionItems.java:76) + at org.eclipse.ui.actions.CompoundContributionItem.getContributionItemsToFill(CompoundContributionItem.java:83) + at org.eclipse.ui.actions.CompoundContributionItem.fill(CompoundContributionItem.java:57) + at org.eclipse.ui.internal.menus.DynamicMenuContributionItem.fill(DynamicMenuContributionItem.java:194) + at org.eclipse.jface.action.MenuManager.doItemFill(MenuManager.java:727) + at org.eclipse.jface.action.MenuManager.update(MenuManager.java:804) + at org.eclipse.jface.action.MenuManager.update(MenuManager.java:671) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.scheduleManagerUpdate(MenuManagerRenderer.java:1149) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.subscribeUIElementTopicVisible(MenuManagerRenderer.java:211) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:58) + at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:183) + at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:136) + at org.eclipse.swt.widgets.Display.syncExec(Display.java:5950) + at org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize.syncExec(DisplayUISynchronize.java:34) + at org.eclipse.e4.ui.internal.di.UIEventObjectSupplier$UIEventHandler.handleEvent(UIEventObjectSupplier.java:65) + at org.eclipse.equinox.internal.event.EventHandlerWrapper.handleEvent(EventHandlerWrapper.java:206) + at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:201) + at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:1) + at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230) + at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151) + at org.eclipse.equinox.internal.event.EventAdminImpl.dispatchEvent(EventAdminImpl.java:132) + at org.eclipse.equinox.internal.event.EventAdminImpl.sendEvent(EventAdminImpl.java:73) + at org.eclipse.equinox.internal.event.EventComponent.sendEvent(EventComponent.java:48) + at org.eclipse.e4.ui.services.internal.events.EventBroker.send(EventBroker.java:55) + at org.eclipse.e4.ui.internal.workbench.UIEventPublisher.notifyChanged(UIEventPublisher.java:61) + at org.eclipse.emf.common.notify.impl.BasicNotifierImpl.eNotify(BasicNotifierImpl.java:424) + at org.eclipse.e4.ui.model.application.ui.impl.UIElementImpl.setVisible(UIElementImpl.java:365) + at org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.updateVisibility(ContributionRecord.java:110) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:169) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:179) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.showMenu(MenuManagerShowProcessor.java:243) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.menuAboutToHide(MenuManagerShowProcessor.java:111) + at org.eclipse.jface.internal.MenuManagerEventHelper.showEventPostHelper(MenuManagerEventHelper.java:89) + at org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:467) + at org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:493) + at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:297) + at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:91) + at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5845) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1656) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1682) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1661) + at org.eclipse.swt.widgets.Menu._setVisible(Menu.java:290) + at org.eclipse.swt.widgets.Display.runPopups(Display.java:5102) + at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4488) + at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1160) + at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339) + at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1051) + at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153) + at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:684) + at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339) + at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583) + at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173) + at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:185) + at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:219) + at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:149) + at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:115) + at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:467) + at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:298) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:615) + at org.eclipse.equinox.launcher.Main.basicRun(Main.java:563) + at org.eclipse.equinox.launcher.Main.run(Main.java:1415) + at org.eclipse.equinox.launcher.Main.main(Main.java:1387) +Caused by: java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at java.base/java.io.FileInputStream.(FileInputStream.java:106) + at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:204) + ... 68 more + +!ENTRY org.eclipse.ui.workbench 4 0 2026-03-18 17:35:19.625 +!MESSAGE Dynamic menu contribution 'DynamicContributionItems(id=org.eclipse.terminal.connector.local.LocalLauncherDynamicContributionItems, visible=true)' threw an unexpected exception +!STACK 0 +org.eclipse.swt.SWTException: i/o error (java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden)) + at org.eclipse.swt.SWT.error(SWT.java:4950) + at org.eclipse.swt.SWT.error(SWT.java:4865) + at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:207) + at org.eclipse.swt.graphics.ImageLoader.load(ImageLoader.java:198) + at org.eclipse.terminal.view.ui.internal.local.showin.ExternalExecutablesUtils.loadImage(ExternalExecutablesUtils.java:38) + at org.eclipse.terminal.view.ui.internal.local.showin.DynamicContributionItems.getContributionItems(DynamicContributionItems.java:76) + at org.eclipse.ui.actions.CompoundContributionItem.getContributionItemsToFill(CompoundContributionItem.java:83) + at org.eclipse.ui.actions.CompoundContributionItem.fill(CompoundContributionItem.java:57) + at org.eclipse.ui.internal.menus.DynamicMenuContributionItem.fill(DynamicMenuContributionItem.java:194) + at org.eclipse.jface.action.MenuManager.doItemFill(MenuManager.java:727) + at org.eclipse.jface.action.MenuManager.update(MenuManager.java:804) + at org.eclipse.jface.action.MenuManager.update(MenuManager.java:671) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.scheduleManagerUpdate(MenuManagerRenderer.java:1149) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.subscribeUIElementTopicVisible(MenuManagerRenderer.java:211) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:58) + at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:183) + at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:136) + at org.eclipse.swt.widgets.Display.syncExec(Display.java:5950) + at org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize.syncExec(DisplayUISynchronize.java:34) + at org.eclipse.e4.ui.internal.di.UIEventObjectSupplier$UIEventHandler.handleEvent(UIEventObjectSupplier.java:65) + at org.eclipse.equinox.internal.event.EventHandlerWrapper.handleEvent(EventHandlerWrapper.java:206) + at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:201) + at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:1) + at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230) + at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151) + at org.eclipse.equinox.internal.event.EventAdminImpl.dispatchEvent(EventAdminImpl.java:132) + at org.eclipse.equinox.internal.event.EventAdminImpl.sendEvent(EventAdminImpl.java:73) + at org.eclipse.equinox.internal.event.EventComponent.sendEvent(EventComponent.java:48) + at org.eclipse.e4.ui.services.internal.events.EventBroker.send(EventBroker.java:55) + at org.eclipse.e4.ui.internal.workbench.UIEventPublisher.notifyChanged(UIEventPublisher.java:61) + at org.eclipse.emf.common.notify.impl.BasicNotifierImpl.eNotify(BasicNotifierImpl.java:424) + at org.eclipse.e4.ui.model.application.ui.impl.UIElementImpl.setVisible(UIElementImpl.java:365) + at org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.updateVisibility(ContributionRecord.java:110) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:169) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:179) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.showMenu(MenuManagerShowProcessor.java:243) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.menuAboutToHide(MenuManagerShowProcessor.java:111) + at org.eclipse.jface.internal.MenuManagerEventHelper.showEventPostHelper(MenuManagerEventHelper.java:89) + at org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:467) + at org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:493) + at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:297) + at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:91) + at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5845) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1656) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1682) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1661) + at org.eclipse.swt.widgets.Menu._setVisible(Menu.java:290) + at org.eclipse.swt.widgets.Display.runPopups(Display.java:5102) + at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4488) + at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1160) + at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339) + at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1051) + at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153) + at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:684) + at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339) + at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583) + at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173) + at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:185) + at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:219) + at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:149) + at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:115) + at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:467) + at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:298) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:615) + at org.eclipse.equinox.launcher.Main.basicRun(Main.java:563) + at org.eclipse.equinox.launcher.Main.run(Main.java:1415) + at org.eclipse.equinox.launcher.Main.main(Main.java:1387) +Caused by: java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at java.base/java.io.FileInputStream.(FileInputStream.java:106) + at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:204) + ... 68 more + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 18:02:17.714 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jface 2 0 2026-03-18 18:11:03.312 +!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation. +!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-18 18:11:03.312 +!MESSAGE A conflict occurred for CTRL+SHIFT+T: +Binding(CTRL+SHIFT+T, + ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type, + Open a type in a Java editor, + Category(org.eclipse.ui.category.navigate,Navigate,null,true), + WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"), + ,,true),null), + org.eclipse.ui.defaultAcceleratorConfiguration, + org.eclipse.ui.contexts.window,,,system) +Binding(CTRL+SHIFT+T, + ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace, + , + Category(org.eclipse.lsp4e.category,Language Servers,null,true), + WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"), + ,,true),null), + org.eclipse.ui.defaultAcceleratorConfiguration, + org.eclipse.ui.contexts.window,,,system) + +!ENTRY org.eclipse.lsp4e 2 0 2026-03-18 18:12:46.286 +!MESSAGE Javadoc unavailable. Failed to obtain it. +!STACK 0 +java.lang.InterruptedException + at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:386) + at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2096) + at org.eclipse.lsp4e.jdt.LSJavaHoverProvider.getHoverInfo2(LSJavaHoverProvider.java:66) + at org.eclipse.jdt.internal.ui.text.java.hover.BestMatchHover.getHoverInfo2(BestMatchHover.java:165) + at org.eclipse.jdt.internal.ui.text.java.hover.BestMatchHover.getHoverInfo2(BestMatchHover.java:131) + at org.eclipse.jdt.internal.ui.text.java.hover.JavaEditorTextHoverProxy.getHoverInfo2(JavaEditorTextHoverProxy.java:89) + at org.eclipse.jface.text.TextViewerHoverManager$1.run(TextViewerHoverManager.java:155) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 18:12:57.074 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 18:13:45.585 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 18:25:16.508 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 18:46:49.386 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 18:48:21.541 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 18:48:31.516 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 18:49:49.718 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 18:57:46.512 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 19:16:53.710 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 19:30:00.005 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.ui.workbench 4 0 2026-03-18 19:30:35.497 +!MESSAGE Dynamic menu contribution 'DynamicContributionItems(id=org.eclipse.terminal.connector.local.LocalLauncherDynamicContributionItems, visible=true)' threw an unexpected exception +!STACK 0 +org.eclipse.swt.SWTException: i/o error (java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden)) + at org.eclipse.swt.SWT.error(SWT.java:4950) + at org.eclipse.swt.SWT.error(SWT.java:4865) + at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:207) + at org.eclipse.swt.graphics.ImageLoader.load(ImageLoader.java:198) + at org.eclipse.terminal.view.ui.internal.local.showin.ExternalExecutablesUtils.loadImage(ExternalExecutablesUtils.java:38) + at org.eclipse.terminal.view.ui.internal.local.showin.DynamicContributionItems.getContributionItems(DynamicContributionItems.java:76) + at org.eclipse.ui.actions.CompoundContributionItem.getContributionItemsToFill(CompoundContributionItem.java:83) + at org.eclipse.ui.actions.CompoundContributionItem.fill(CompoundContributionItem.java:57) + at org.eclipse.ui.internal.menus.DynamicMenuContributionItem.fill(DynamicMenuContributionItem.java:194) + at org.eclipse.jface.action.MenuManager.doItemFill(MenuManager.java:727) + at org.eclipse.jface.action.MenuManager.update(MenuManager.java:804) + at org.eclipse.jface.action.MenuManager.update(MenuManager.java:671) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.scheduleManagerUpdate(MenuManagerRenderer.java:1149) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.subscribeUIElementTopicVisible(MenuManagerRenderer.java:211) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:58) + at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:183) + at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:136) + at org.eclipse.swt.widgets.Display.syncExec(Display.java:5950) + at org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize.syncExec(DisplayUISynchronize.java:34) + at org.eclipse.e4.ui.internal.di.UIEventObjectSupplier$UIEventHandler.handleEvent(UIEventObjectSupplier.java:65) + at org.eclipse.equinox.internal.event.EventHandlerWrapper.handleEvent(EventHandlerWrapper.java:206) + at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:201) + at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:1) + at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230) + at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151) + at org.eclipse.equinox.internal.event.EventAdminImpl.dispatchEvent(EventAdminImpl.java:132) + at org.eclipse.equinox.internal.event.EventAdminImpl.sendEvent(EventAdminImpl.java:73) + at org.eclipse.equinox.internal.event.EventComponent.sendEvent(EventComponent.java:48) + at org.eclipse.e4.ui.services.internal.events.EventBroker.send(EventBroker.java:55) + at org.eclipse.e4.ui.internal.workbench.UIEventPublisher.notifyChanged(UIEventPublisher.java:61) + at org.eclipse.emf.common.notify.impl.BasicNotifierImpl.eNotify(BasicNotifierImpl.java:424) + at org.eclipse.e4.ui.model.application.ui.impl.UIElementImpl.setVisible(UIElementImpl.java:365) + at org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.updateVisibility(ContributionRecord.java:110) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:169) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:179) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.showMenu(MenuManagerShowProcessor.java:243) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.menuAboutToHide(MenuManagerShowProcessor.java:111) + at org.eclipse.jface.internal.MenuManagerEventHelper.showEventPostHelper(MenuManagerEventHelper.java:89) + at org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:467) + at org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:493) + at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:297) + at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:91) + at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5845) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1656) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1682) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1661) + at org.eclipse.swt.widgets.Menu._setVisible(Menu.java:290) + at org.eclipse.swt.widgets.Display.runPopups(Display.java:5102) + at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4488) + at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1160) + at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339) + at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1051) + at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153) + at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:684) + at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339) + at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583) + at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173) + at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:185) + at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:219) + at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:149) + at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:115) + at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:467) + at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:298) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:615) + at org.eclipse.equinox.launcher.Main.basicRun(Main.java:563) + at org.eclipse.equinox.launcher.Main.run(Main.java:1415) + at org.eclipse.equinox.launcher.Main.main(Main.java:1387) +Caused by: java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at java.base/java.io.FileInputStream.(FileInputStream.java:106) + at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:204) + ... 68 more + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 19:32:18.384 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 19:32:45.429 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.ui.workbench 4 0 2026-03-18 19:33:19.167 +!MESSAGE Dynamic menu contribution 'DynamicContributionItems(id=org.eclipse.terminal.connector.local.LocalLauncherDynamicContributionItems, visible=true)' threw an unexpected exception +!STACK 0 +org.eclipse.swt.SWTException: i/o error (java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden)) + at org.eclipse.swt.SWT.error(SWT.java:4950) + at org.eclipse.swt.SWT.error(SWT.java:4865) + at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:207) + at org.eclipse.swt.graphics.ImageLoader.load(ImageLoader.java:198) + at org.eclipse.terminal.view.ui.internal.local.showin.ExternalExecutablesUtils.loadImage(ExternalExecutablesUtils.java:38) + at org.eclipse.terminal.view.ui.internal.local.showin.DynamicContributionItems.getContributionItems(DynamicContributionItems.java:76) + at org.eclipse.ui.actions.CompoundContributionItem.getContributionItemsToFill(CompoundContributionItem.java:83) + at org.eclipse.ui.actions.CompoundContributionItem.fill(CompoundContributionItem.java:57) + at org.eclipse.ui.internal.menus.DynamicMenuContributionItem.fill(DynamicMenuContributionItem.java:194) + at org.eclipse.jface.action.MenuManager.doItemFill(MenuManager.java:727) + at org.eclipse.jface.action.MenuManager.update(MenuManager.java:804) + at org.eclipse.jface.action.MenuManager.update(MenuManager.java:671) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.scheduleManagerUpdate(MenuManagerRenderer.java:1149) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.subscribeUIElementTopicVisible(MenuManagerRenderer.java:211) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:58) + at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:183) + at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:136) + at org.eclipse.swt.widgets.Display.syncExec(Display.java:5950) + at org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize.syncExec(DisplayUISynchronize.java:34) + at org.eclipse.e4.ui.internal.di.UIEventObjectSupplier$UIEventHandler.handleEvent(UIEventObjectSupplier.java:65) + at org.eclipse.equinox.internal.event.EventHandlerWrapper.handleEvent(EventHandlerWrapper.java:206) + at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:201) + at org.eclipse.equinox.internal.event.EventHandlerTracker.dispatchEvent(EventHandlerTracker.java:1) + at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230) + at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:151) + at org.eclipse.equinox.internal.event.EventAdminImpl.dispatchEvent(EventAdminImpl.java:132) + at org.eclipse.equinox.internal.event.EventAdminImpl.sendEvent(EventAdminImpl.java:73) + at org.eclipse.equinox.internal.event.EventComponent.sendEvent(EventComponent.java:48) + at org.eclipse.e4.ui.services.internal.events.EventBroker.send(EventBroker.java:55) + at org.eclipse.e4.ui.internal.workbench.UIEventPublisher.notifyChanged(UIEventPublisher.java:61) + at org.eclipse.emf.common.notify.impl.BasicNotifierImpl.eNotify(BasicNotifierImpl.java:424) + at org.eclipse.e4.ui.model.application.ui.impl.UIElementImpl.setVisible(UIElementImpl.java:365) + at org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.updateVisibility(ContributionRecord.java:110) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:169) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRendererFilter.updateElementVisibility(MenuManagerRendererFilter.java:179) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.showMenu(MenuManagerShowProcessor.java:243) + at org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerShowProcessor.menuAboutToHide(MenuManagerShowProcessor.java:111) + at org.eclipse.jface.internal.MenuManagerEventHelper.showEventPostHelper(MenuManagerEventHelper.java:89) + at org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:467) + at org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:493) + at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:297) + at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:91) + at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5845) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1656) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1682) + at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1661) + at org.eclipse.swt.widgets.Menu._setVisible(Menu.java:290) + at org.eclipse.swt.widgets.Display.runPopups(Display.java:5102) + at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4488) + at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1160) + at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339) + at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1051) + at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153) + at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:684) + at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339) + at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583) + at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173) + at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:185) + at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:219) + at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:149) + at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:115) + at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:467) + at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:298) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:615) + at org.eclipse.equinox.launcher.Main.basicRun(Main.java:563) + at org.eclipse.equinox.launcher.Main.run(Main.java:1415) + at org.eclipse.equinox.launcher.Main.main(Main.java:1387) +Caused by: java.io.FileNotFoundException: C:\Program Files\Git\mingw64\share\git\git-for-windows.ico (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at java.base/java.io.FileInputStream.(FileInputStream.java:106) + at org.eclipse.swt.graphics.ImageLoader.loadByZoom(ImageLoader.java:204) + ... 68 more + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 19:34:29.072 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 19:39:46.763 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 19:46:32.144 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 19:50:39.778 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 19:57:10.404 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 20:08:07.916 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 20:08:34.316 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 20:50:39.209 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 21:02:50.177 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 21:02:51.971 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 22:02:40.121 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 22:21:25.529 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 22:34:27.436 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) + +!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-18 22:34:35.489 +!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS + +!ENTRY org.eclipse.jdt.core 4 4 2026-03-18 22:34:35.761 +!MESSAGE Failed to save JDT index: Index for /xxxthegame +!STACK 0 +java.io.FileNotFoundException: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core/9341915.index (Datei oder Verzeichnis nicht gefunden) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.eclipse.jdt.internal.core.index.FileIndexLocation.getInputStream(FileIndexLocation.java:83) + at org.eclipse.jdt.internal.core.index.DiskIndex.readAllDocumentNames(DiskIndex.java:633) + at org.eclipse.jdt.internal.core.index.DiskIndex.mergeWith(DiskIndex.java:536) + at org.eclipse.jdt.internal.core.index.Index.save(Index.java:229) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndex(IndexManager.java:1135) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.saveIndexes(IndexManager.java:1178) + at org.eclipse.jdt.internal.core.search.indexing.IndexManager.notifyIdle(IndexManager.java:822) + at org.eclipse.jdt.internal.core.search.processing.JobManager.indexerLoop(JobManager.java:508) + at java.base/java.lang.Thread.run(Thread.java:1583) diff --git a/.metadata/.plugins/org.eclipse.buildship.core/project-preferences/xxxthegame b/.metadata/.plugins/org.eclipse.buildship.core/project-preferences/xxxthegame index cfd7476..6135611 100644 --- a/.metadata/.plugins/org.eclipse.buildship.core/project-preferences/xxxthegame +++ b/.metadata/.plugins/org.eclipse.buildship.core/project-preferences/xxxthegame @@ -1,8 +1,8 @@ # -#Tue Mar 17 19:55:42 CET 2026 +#Wed Mar 18 22:34:35 CET 2026 buildDir=build buildScriptPath=build.gradle.kts -classpath=\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n +classpath=\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n\t\n\t\t\n\t\t\n\t\n\n\n derivedResources=.gradle\:build gradleVersion=8.9 hasAutoBuildTasks=false diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/xxxthegame/.markers b/.metadata/.plugins/org.eclipse.core.resources/.projects/xxxthegame/.markers index b1de59c52cac5e8534a15d46cbaa9de2e9b2c86c..cdf2748954ff931039917a10b04a83ebd3f75683 100644 GIT binary patch literal 2826 zcmeHJOK;Oa5FR@ceSm-iAR!^J#DOBgn)U*!$RRWl6qOc~@Q?~b8}HOLv3ITAb(?~? zapWHWR}TCPF8l{T;>3|N++f!sscYJb@Q^qVDYAFhJ3I5uH{UD(RN$81>-F?1hBQH6 z$-qx&#Qg_!jrt6IL8)E$X|@v54st&bTuTwhDE->^xyv$3Q=A(v5W5)wT=Tb&rVB*K zkYEr;sY3FAX~M9Rh53gI5ywjsX*U%S*Vm}*qJ&A92v(_VYAUrcbLaw0ZEqnE5{b%G zikw{+aTILWQ(f@4|2&Jx)!MCEyRp!?)>v${uhbUn3yt=Cy#lYQTXDFX193M;qUwi| zvKZSc5Ghvkm8w6S%@neBXQQ}!Mx)YI{~|+TASA{#89+!jA$+tegv&dv!k7i8!I_2T z85}Rh+6Oj=1v%}+$Q%bIV*~PHmUK{Bcv3i3+_IUEd|Skorg1zcGH$U42gVXqiiTK$ zhRv4O8K!G?Cl|eCd*rM)Jw0>JJL`q0YjHf))H34XX*gOuzTk1hDsZ^CHgpY6ZaZzT zH2K#XDJ~lNR^a^Zc$1ynHBr8BpS>P4;hLY@a`I=1g-4$LXJMhti!40yW^@)l{ACu- zjm*NC0SlMb`P9vu^Ywq0gYVzL?VnO)tJli!FqITF`H~qoA|6wg@$g4U?(-Vl+b$d{ z7gSzKsT4*t5}1H-g=;7yoeQdCh9Z*z&YKFYT&=gZ>a+8jH=;T1)jMua;?X zHbjon#L2t_^{QZ=(pd+_P13|B2}*rrk43`y_~{GW_!T4qDp_n`@aN2n#iNOGEegy9 zp?`lEz;dwvQc~mn>wjO3pa1PUL0-gUzzs4$D~Wwbp2f^5aD83QA=kH n+xfX%NHcEpHf-mk+$LZv#s6gUpD8r|9e^dJxN-u2djI(wE$p+o delta 340 zcmZ9HF-yZh6vr<~+cYgMB3-(iT~g3SE5$(sk=jk9;wTQ6%OgEW@5J=r0@osVN=5L&gU5gP{eSPF@f^$$LLRE`)e}#|Kb;)t?Utg^HO6mZ#rA{Xtt=}3Hx>#a1v*b*f#?@BJx5LsjY4$HeD|^o57P?xihDwy|p&_ m_4Lqzsn^2w-tHCd;1u*3;zWtb z3QXJ^GaMN?EzB&<%uUUVOefbe8xXI5vjS5HqZC{-*MW%NpOyD8h)4x~?y25gehdJB C+9)6Z delta 198 zcmeBUpT{*pUM-|FRlzs0NWsut!O+sm)YQtvNWs}PM8U|w$c#&0ucRn7)z;8(;zWtb z3QXJ^GaMN?&CQL?O^i&9OeSw)G$3C8W(B4YJerLRtPBjT4Dnhn!6 - - + + activeSchemeId:org.eclipse.ui.defaultAcceleratorConfiguration - + - + topLevel shellMaximized - - - + + + persp.actionSet:org.eclipse.mylyn.tasks.ui.navigation persp.actionSet:org.eclipse.ui.cheatsheets.actionSet @@ -77,133 +77,127 @@ persp.newWizSC:org.eclipse.jdt.junit.wizards.NewTestCaseCreationWizard persp.actionSet:org.eclipse.jdt.junit.JUnitActionSet persp.viewSC:org.eclipse.ant.ui.views.AntView + persp.actionSet:org.eclipse.debug.ui.debugActionSet persp.editorOnboardingImageUri:platform:/plugin/org.eclipse.jdt.ui/$nl$/icons/full/onboarding_jperspective.svg persp.editorOnboardingText:Open a file or drop files here to open them. persp.editorOnboardingCommand:Find Actions$$$Ctrl+3 persp.editorOnboardingCommand:Show Key Assist$$$Shift+Ctrl+L persp.editorOnboardingCommand:New$$$Ctrl+N persp.editorOnboardingCommand:Open Type$$$Shift+Ctrl+T - persp.actionSet:org.eclipse.debug.ui.debugActionSet - - - - + + + + org.eclipse.e4.primaryNavigationStack active - noFocus - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:General - + View categoryTag:Java - - + + View categoryTag:Other - - + + View categoryTag:Git - - - - + + + + org.eclipse.e4.secondaryNavigationStack - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Mylyn - + View categoryTag:Java - + View categoryTag:Ant - + org.eclipse.e4.secondaryDataStack Oomph Gradle Debug - + View categoryTag:General - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Terminal - + View categoryTag:Gradle - + View categoryTag:Gradle - - View - categoryTag:Oomph - NoRestore - - + View categoryTag:Debug busy @@ -212,7 +206,7 @@ - + persp.actionSet:org.eclipse.mylyn.tasks.ui.navigation persp.actionSet:org.eclipse.ui.cheatsheets.actionSet @@ -261,100 +255,100 @@ persp.editorOnboardingCommand:Step Over$$$F6 persp.editorOnboardingCommand:Step Return$$$F7 persp.editorOnboardingCommand:Resume$$$F8 - - + + org.eclipse.e4.primaryNavigationStack - + View categoryTag:Debug - + View categoryTag:General - + View categoryTag:Java active - + View categoryTag:Java - + View categoryTag:Java - - - - + + + + org.eclipse.e4.secondaryNavigationStack - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Ant - - + + View categoryTag:General - + View categoryTag:General - + View categoryTag:Debug - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Terminal - + View categoryTag:Debug - + View categoryTag:General @@ -363,2640 +357,2603 @@ - - + + View categoryTag:Help - + View categoryTag:General - + View categoryTag:Help - + View categoryTag:Help - + View categoryTag:General - + View categoryTag:Help - - + + EditorStack org.eclipse.e4.primaryDataStack - - - Editor - removeOnHide - org.eclipse.jdt.ui.ClassFileEditor - - - - Editor - removeOnHide - org.eclipse.jdt.ui.CompilationUnitEditor - - - - Editor - removeOnHide - org.eclipse.jdt.ui.CompilationUnitEditor - - - - Editor - removeOnHide - org.eclipse.jdt.ui.CompilationUnitEditor - - - Editor - removeOnHide - org.eclipse.compare.CompareEditor - - + View categoryTag:Java active - + activeOnClose + ViewMenu menuContribution:menu - + - + View categoryTag:Java - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Mylyn - + View categoryTag:Terminal - + View categoryTag:Java - + View categoryTag:Git - + View categoryTag:Java - + View categoryTag:Other - + ViewMenu menuContribution:menu - + - + View categoryTag:Ant - + View categoryTag:Gradle - + ViewMenu menuContribution:menu - + - + View categoryTag:Gradle - + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - busy - + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - + View categoryTag:General - + View categoryTag:General - + - + View categoryTag:Debug - + ViewMenu menuContribution:menu - + - - - - - View - categoryTag:Oomph - NoRestore - - ViewMenu - menuContribution:menu - - - - - + + toolbarSeparator - + - + Draggable - + - + toolbarSeparator - + - + Draggable - - + + - + toolbarSeparator - + - + Draggable - + Draggable - + Draggable - + Draggable - + toolbarSeparator - + - + Draggable - + - - Draggable - - - Draggable - - - Draggable - - + toolbarSeparator - + - + toolbarSeparator - + - + Draggable - + stretch SHOW_RESTORE_MENU - + Draggable HIDEABLE SHOW_RESTORE_MENU - - + + stretch - + Draggable - + + Draggable + + + + + TrimStack + Draggable + + + + + TrimStack + Draggable + + + TrimStack Draggable - - - - - - - - - - - - - - - + + + + + + + + + + + + + platform:gtk - - - - + + + + platform:gtk - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - + + + + + - - + + - - - - - - - - - + + + + + + + + + - - + + - - - + + + - - - - - + + + + + - - + + - - - + + + - - - + + + - - - - - - - - + + + + + + + + platform:gtk - - - - - + + + + + - - + + - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - + + + + - - + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - - - + + + + + + + + - - + + - - - - - - + + + + + + - - - - - - + + + + + + - - + + - - - - - - - - + + + + + + + + - - - + + + - - - - + + + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Editor removeOnHide - + View categoryTag:Ant - + View categoryTag:Gradle - + View categoryTag:Gradle - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Debug - + View categoryTag:Java - + View categoryTag:Git - + View categoryTag:Git - + View categoryTag:Git - + View categoryTag:Git NoRestore - + View categoryTag:Git - + View categoryTag:Help - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:Debug - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:Java Browsing - + View categoryTag:Java Browsing - + View categoryTag:Java Browsing - + View categoryTag:Java Browsing - + View categoryTag:Java - + View categoryTag:General - + View categoryTag:Java - + View categoryTag:Java - + View categoryTag:Language Servers - + View categoryTag:Language Servers - + View categoryTag:Language Servers - + View categoryTag:Maven - + View categoryTag:Maven - + View categoryTag:Maven - + View categoryTag:Mylyn - + View categoryTag:Mylyn - + View categoryTag:Mylyn - + View categoryTag:Mylyn - + View categoryTag:Mylyn - + View categoryTag:Mylyn - + View categoryTag:Oomph - + View categoryTag:Oomph NoRestore - + View categoryTag:Plug-in Development - + View categoryTag:General - + View categoryTag:Version Control (Team) - + View categoryTag:Version Control (Team) - + View categoryTag:Terminal - + View categoryTag:Help - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Help - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:General - + View categoryTag:Docker - + View categoryTag:Docker - + View categoryTag:Docker - + View categoryTag:Docker - + View categoryTag:Spring - + View categoryTag:Spring - + View categoryTag:Spring - - + + glue move_after:PerspectiveSpacer SHOW_RESTORE_MENU - + move_after:Spacer Glue HIDEABLE SHOW_RESTORE_MENU - + glue move_after:SearchField SHOW_RESTORE_MENU - - - - - + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.metadata/.plugins/org.eclipse.jdt.core/1865797976.index b/.metadata/.plugins/org.eclipse.jdt.core/1865797976.index index cee262783d9207ae3849fbf76c2062f30d962756..0da28b0ebe571f4800a48e027f3ada34359e4dd0 100644 GIT binary patch delta 2775 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 diff --git a/.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache b/.metadata/.plugins/org.eclipse.jdt.core/externalFilesCache index 2e4cff8d4839e944bc5e3db3096e51797afe7aab..8f63c03189199949a6aedc54b20fcc1fafed6470 100644 GIT binary patch literal 33891 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 H`Jwp#*1xjA literal 33772 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 diff --git a/.metadata/.plugins/org.eclipse.jdt.core/externalLibsTimeStamps b/.metadata/.plugins/org.eclipse.jdt.core/externalLibsTimeStamps index d9306b80fcecb59b8e1c93f6997d18b10a0e5cd9..792fd75093fcbb7cce7bd3852908572bfd75c8dd 100644 GIT binary patch delta 997 zcmY*YTS${(81`LDH;EY z&`K~!%G8qTN<_L463RQlix7ecN-w$$d-prny8OTI{hs$Z{cF9n)=N@9Kfkl+4}FL) zsiU|UCE!A27(xnV*cNP1XqO-nW~mnzlYoN!YFdI)YeG$GI11G_={aUp0&0T%v8{>4 z6RN|ZlySwU=Eo?Lb?N4$xa&Jfnss-iQ*EP z@d56pJ5WX&u^Hx~1)j2DUDpO@k^$!EuDR8q^F*B(2o+G8TLP=XfM!#K2(%nyr$_vt z6hHDys9R({x4(>@@Yyk>ht|PGZ<(DNNQE4k1B;ms{FXKlyku50tn7?{_c^sZZGgfo z!(zeJ|1_GFw{aj)iiMO$ez_$#Aw}Q7!WdU86}{3K)dO`7yjBe(QPqb{`CaBOpfJAE z1A3iCn4SUOoIjb>W zXccoyH8{nyOGQmI=D}~JL64GXOj&HqWtwN6(vWz+qHM%qG8>~w#PNM+Xr8y)P!kh^ zUpg7mV#;XPlYA`7kDj7&C^h-`B#VMRqk@Cju{18BRzztwMPfRwWmoy(cIp%DUr33B zGO2_nd|DKX&w+L>-a-Wy~kHnSgizhoH8wxU@6 F@-OoJT}S`` delta 1071 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 diff --git a/.metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt b/.metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt index fe69f8c..1384d64 100644 --- a/.metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt +++ b/.metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt @@ -1,105 +1,106 @@ INDEX VERSION 1.134+/home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.jdt.core -2628068441.index -2403041570.index -1704193220.index -3051047092.index -2466743981.index -2852275968.index -3371017197.index -2376429633.index -2236377038.index -833027591.index -4080887926.index -3424266581.index -2488355463.index -3514351073.index -1090991043.index -13999064.index -352173590.index -2799433906.index -3718169413.index -2157310342.index -1730373086.index -341080888.index -3839581777.index -3572667491.index -2826242951.index -2874180664.index -134995224.index -2455962971.index -1063231598.index -3769604005.index 794464160.index -1067882983.index -3547251881.index -1022297761.index +2655170954.index +176453541.index +677104696.index +341080888.index +774576701.index 4134502745.index -1780956574.index -369020172.index +41199409.index +134995224.index 2217896880.index +4025319337.index +900586112.index +2929476459.index +2065500052.index +3051047092.index +815902026.index +3718169413.index +721517855.index +369020172.index +3899935016.index +2157310342.index +2488355463.index +3572667491.index +2799433906.index +675283020.index +2032345814.index +3839581777.index +2466743981.index +13999064.index +673436610.index +3972616808.index +1914043487.index +3154281632.index +1117161889.index +983587063.index +766461225.index +286641703.index +3371017197.index +4080887926.index +2941512597.index +1730373086.index +3882180612.index +4020783879.index +2900482015.index +3059431983.index +833027591.index +13156219.index +4088356365.index +37241354.index +1295630681.index +2701419231.index +3939420913.index +1067882983.index +1318022262.index +773718761.index +2311226047.index +3539841425.index +1865797976.index +2455962971.index +836138551.index +2389383899.index +2226615777.index +3515611559.index +3728851734.index +2826242951.index +2899155238.index +3763224039.index +2138052223.index +2236377038.index +3547251881.index 371677185.index 2127778675.index -2389383899.index -2701419231.index +2519831052.index +1063231598.index +2874180664.index +2939623059.index +2576972120.index +2376429633.index +2628068441.index +1090991043.index +1138623861.index +1223891870.index +3769604005.index +3158780236.index +2237645717.index +2852275968.index +2403041570.index +1704193220.index +2004806901.index +3952767374.index 3416862923.index 3912907421.index -1256436118.index -815902026.index -900586112.index -766461225.index -1117161889.index -675283020.index -4088356365.index -836138551.index -2226615777.index -3539841425.index -2939623059.index -3728851734.index -3972616808.index -2494834982.index -1938594271.index -4025319337.index 781064456.index -2032345814.index -2655170954.index -983587063.index -3939420913.index -2247053514.index -1138623861.index -3882180612.index -2237645717.index -721517855.index -176453541.index -4020783879.index -3899935016.index -2576972120.index -1223891870.index -3158780236.index -677104696.index +352173590.index 766439048.index -41199409.index -2900482015.index -3952767374.index -773718761.index -2519831052.index -286641703.index -3515611559.index -1865797976.index -3059431983.index -2929476459.index -774576701.index -13156219.index -2311226047.index -2138052223.index -3763224039.index -3154281632.index -1318022262.index -2065500052.index -37241354.index -2899155238.index -673436610.index -1914043487.index -1295630681.index -2941512597.index +3424266581.index +2247053514.index +1765772496.index +3514351073.index 3892622621.index -2004806901.index +2494834982.index +1780956574.index +1022297761.index +1938594271.index +1256436118.index diff --git a/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml b/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml index 37c217c..aa899f1 100644 --- a/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml +++ b/.metadata/.plugins/org.eclipse.jdt.ui/QualifiedTypeNameHistory.xml @@ -18,4 +18,5 @@ + diff --git a/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml b/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml index 271e066..f904cf6 100644 --- a/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml +++ b/.metadata/.plugins/org.eclipse.jdt.ui/dialog_settings.xml @@ -24,6 +24,10 @@
+ + + +
@@ -68,4 +72,13 @@
+
+ + + + +
+
+ +
diff --git a/.metadata/.plugins/org.eclipse.m2e.logback/0.log b/.metadata/.plugins/org.eclipse.m2e.logback/0.log index e7ed630..819a1c0 100644 --- a/.metadata/.plugins/org.eclipse.m2e.logback/0.log +++ b/.metadata/.plugins/org.eclipse.m2e.logback/0.log @@ -21,3 +21,4 @@ 2026-03-17 19:39:55,931 [Worker-1: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is out-of-date. Trying to update. 2026-03-17 19:49:51,508 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read. 2026-03-17 19:55:53,050 [Worker-1: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read. +2026-03-18 15:29:01,371 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read. diff --git a/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml b/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml index e074d67..a736a11 100644 --- a/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml +++ b/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml @@ -12,7 +12,7 @@ - +
diff --git a/.metadata/version.ini b/.metadata/version.ini index 57cce48..5c3b5a0 100644 --- a/.metadata/version.ini +++ b/.metadata/version.ini @@ -1,3 +1,3 @@ -#Tue Mar 17 19:55:50 CET 2026 +#Wed Mar 18 15:28:58 CET 2026 org.eclipse.core.runtime=2 org.eclipse.platform=4.39.0.v20260226-0420 diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/Finisher.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/Finisher.java index 363b15d..4df7e27 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/Finisher.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/Finisher.java @@ -1,6 +1,6 @@ package de.oaa.xxx.aufgaben; -import de.oaa.xxx.session.GeschlechtEnum; +import de.oaa.xxx.games.bdsm.GeschlechtEnum; import lombok.Getter; import lombok.Setter; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabeEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabeEntity.java index 090ebd8..0b1f491 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabeEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabeEntity.java @@ -67,9 +67,9 @@ public class AufgabeEntity { public Aufgabe toAufgabe() { Aufgabe aufgabe = new Aufgabe(); aufgabe.setAufgabeId(aufgabeId); - aufgabe.setBenoetigtAktiv(benoetigtAktiv); - aufgabe.setBenoetigteToys(benoetigteToys.stream().map(ToyEntity::toToy).toList()); - aufgabe.setBenoetigtPassiv(benoetigtPassiv); + aufgabe.setBenoetigtAktiv(benoetigtAktiv != null ? new ArrayList<>(benoetigtAktiv) : new ArrayList<>()); + aufgabe.setBenoetigteToys(benoetigteToys != null ? benoetigteToys.stream().map(ToyEntity::toToy).toList() : new ArrayList<>()); + aufgabe.setBenoetigtPassiv(benoetigtPassiv != null ? new ArrayList<>(benoetigtPassiv) : new ArrayList<>()); aufgabe.setGruppeId(aufgabenGruppe.getGruppenId()); aufgabe.setKurzText(kurzText); aufgabe.setLevel(level); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabenGruppeEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabenGruppeEntity.java index 69da27b..1f1fb00 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabenGruppeEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/AufgabenGruppeEntity.java @@ -18,7 +18,7 @@ import java.util.UUID; @Getter @Setter @Entity -@Table(name = "aufgabenGruppe") +@Table(name = "aufgaben_gruppe") public class AufgabenGruppeEntity { @Id diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/FinisherEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/FinisherEntity.java index 3d54c0a..b4b7c55 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/FinisherEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/FinisherEntity.java @@ -2,7 +2,7 @@ package de.oaa.xxx.aufgaben.entity; import de.oaa.xxx.aufgaben.Finisher; import de.oaa.xxx.aufgaben.Werkzeug; -import de.oaa.xxx.session.GeschlechtEnum; +import de.oaa.xxx.games.bdsm.GeschlechtEnum; import jakarta.persistence.CascadeType; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; @@ -67,9 +67,9 @@ public class FinisherEntity { finisher.setKurzText(kurzText); finisher.setText(text); finisher.setGeschlecht(geschlecht); - finisher.setBenoetigtAktiv(benoetigtAktiv); - finisher.setBenoetigtPassiv(benoetigtPassiv); - finisher.setBenoetigteToys(benoetigteToys.stream().map(ToyEntity::toToy).toList()); + finisher.setBenoetigtAktiv(benoetigtAktiv != null ? new ArrayList<>(benoetigtAktiv) : new ArrayList<>()); + finisher.setBenoetigtPassiv(benoetigtPassiv != null ? new ArrayList<>(benoetigtPassiv) : new ArrayList<>()); + finisher.setBenoetigteToys(benoetigteToys != null ? benoetigteToys.stream().map(ToyEntity::toToy).toList() : new ArrayList<>()); finisher.setGruppeId(aufgabenGruppe.getGruppenId()); return finisher; } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/SperreEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/SperreEntity.java index e8150dd..10fc49f 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/SperreEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/SperreEntity.java @@ -67,7 +67,7 @@ public class SperreEntity { sperre.setMinutenBis(minutenBis); sperre.setMinutenVon(minutenVon); sperre.setReleaseText(releaseText); - sperre.setSperreFuer(sperreFuer); + sperre.setSperreFuer(sperreFuer != null ? new ArrayList<>(sperreFuer) : new ArrayList<>()); sperre.setText(text); sperre.setBenoetigteToys(benoetigteToys != null ? benoetigteToys.stream().map(ToyEntity::toToy).toList() : new ArrayList<>()); return sperre; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/StrafeEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/StrafeEntity.java index 6bbedbf..b97d64b 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/StrafeEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/aufgaben/entity/StrafeEntity.java @@ -67,9 +67,9 @@ public class StrafeEntity { public Strafe toStrafe() { Strafe strafe = new Strafe(); strafe.setStrafeId(strafeId); - strafe.setBenoetigtAktiv(benoetigtAktiv); - strafe.setBenoetigteToys(benoetigteToys.stream().map(ToyEntity::toToy).toList()); - strafe.setBenoetigtPassiv(benoetigtPassiv); + strafe.setBenoetigtAktiv(benoetigtAktiv != null ? new ArrayList<>(benoetigtAktiv) : new ArrayList<>()); + strafe.setBenoetigteToys(benoetigteToys != null ? benoetigteToys.stream().map(ToyEntity::toToy).toList() : new ArrayList<>()); + strafe.setBenoetigtPassiv(benoetigtPassiv != null ? new ArrayList<>(benoetigtPassiv) : new ArrayList<>()); strafe.setGruppeId(aufgabenGruppe.getGruppenId()); strafe.setKurzText(kurzText); strafe.setLevel(level); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/AktiveSperre.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/AktiveSperre.java similarity index 92% rename from xxxthegame/src/main/java/de/oaa/xxx/session/AktiveSperre.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/AktiveSperre.java index 6a203b0..a19c7e8 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/AktiveSperre.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/AktiveSperre.java @@ -1,4 +1,4 @@ -package de.oaa.xxx.session; +package de.oaa.xxx.games.bdsm; import lombok.Getter; import lombok.Setter; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/AufgabeAnzeige.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/AufgabeAnzeige.java similarity index 81% rename from xxxthegame/src/main/java/de/oaa/xxx/session/AufgabeAnzeige.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/AufgabeAnzeige.java index 29dd0c2..5972b6d 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/AufgabeAnzeige.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/AufgabeAnzeige.java @@ -1,21 +1,25 @@ -package de.oaa.xxx.session; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class AufgabeAnzeige { - - private String nameAktiverMitspieler; - private String aufgabeText; - private Integer timer; - private Callback callback; - private Integer level; - - @Override - public String toString() { - return "AufgabeAnzeige[mitspieler=" + nameAktiverMitspieler + ", level=" + level + ", timer=" + timer - + ", callback=" + (callback != null ? callback.getClass().getSimpleName() : null) + "]"; - } -} +package de.oaa.xxx.games.bdsm; + +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +public class AufgabeAnzeige { + + private String nameAktiverMitspieler; + private String aufgabeText; + private Integer timer; + private Callback callback; + private Integer level; + private UUID mitspielerId; + private boolean eigenesGeraet; + + @Override + public String toString() { + return "AufgabeAnzeige[mitspieler=" + nameAktiverMitspieler + ", level=" + level + ", timer=" + timer + + ", callback=" + (callback != null ? callback.getClass().getSimpleName() : null) + "]"; + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/AufgabeArt.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/AufgabeArt.java similarity index 63% rename from xxxthegame/src/main/java/de/oaa/xxx/session/AufgabeArt.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/AufgabeArt.java index 1af07df..2444f20 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/AufgabeArt.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/AufgabeArt.java @@ -1,4 +1,4 @@ -package de.oaa.xxx.session; +package de.oaa.xxx.games.bdsm; public enum AufgabeArt { AUFGABE, diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/Session.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/BdsmGame.java similarity index 88% rename from xxxthegame/src/main/java/de/oaa/xxx/session/Session.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/BdsmGame.java index 13e36bf..7ac64a4 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/Session.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/BdsmGame.java @@ -1,4 +1,4 @@ -package de.oaa.xxx.session; +package de.oaa.xxx.games.bdsm; import lombok.Getter; import lombok.Setter; @@ -8,10 +8,11 @@ import java.util.UUID; @Getter @Setter -public class Session { +public class BdsmGame { private UUID sessionId; private UUID userId; + private UUID setupId; private Integer wahrscheinlichkeitSperre; private Integer wahrscheinlichkeitStrafe; private Integer aufgabenProLevel; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/SessionDurchfuehren.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/BdsmGameDurchfuehren.java similarity index 91% rename from xxxthegame/src/main/java/de/oaa/xxx/session/SessionDurchfuehren.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/BdsmGameDurchfuehren.java index 5704f94..e3c7941 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/SessionDurchfuehren.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/BdsmGameDurchfuehren.java @@ -1,251 +1,265 @@ -package de.oaa.xxx.session; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import de.oaa.xxx.session.aufgaben.Aufgabe; -import de.oaa.xxx.session.aufgaben.AufgabenList; -import de.oaa.xxx.session.aufgaben.Sperre; -import de.oaa.xxx.session.aufgaben.Strafe; -import de.oaa.xxx.session.entity.SessionEntity; -import de.oaa.xxx.session.sperre.SperreCallback; -import de.oaa.xxx.session.sperre.SperrenVerlaengernCallback; - -public class SessionDurchfuehren { - - private final AufgabenList aufgabenList; - private final List mitspieler = new ArrayList<>(); - private final List aktiveSperren = new ArrayList<>(); - - private final Integer wahrscheinlichkeitSperre; - private final Integer wahrscheinlichkeitStrafe; - - private int aufgabenProLevel; - private int level; - private int aufgabenAufAktuellemLevel; - - public SessionDurchfuehren(SessionEntity entity) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); - aufgabenList = objectMapper.readValue(entity.getAufgaben(), AufgabenList.class); - entity.getMitspieler().forEach(mitspielerEntity -> mitspieler.add(mitspielerEntity.toMitspieler())); - entity.getAktiveSperren().forEach(sperreEntity -> aktiveSperren.add(sperreEntity.toSperre(mitspieler))); - - wahrscheinlichkeitSperre = entity.getWahrscheinlichkeitSperre(); - wahrscheinlichkeitStrafe = entity.getWahrscheinlichkeitStrafe(); - - this.aufgabenProLevel = entity.getAufgabenProLevel() != null ? entity.getAufgabenProLevel() : 5; - this.level = entity.getLevel() != null ? entity.getLevel() : 1; - this.aufgabenAufAktuellemLevel = entity.getAufgabenAufAktuellemLevel() != null ? entity.getAufgabenAufAktuellemLevel() : 0; - } - - public AufgabeAnzeige getNext() { - checkLevel(); - if (level == 6) { - return null; - } - AufgabeAnzeige anzeige = null; - int nextInt = new Random().nextInt(1, 100); - if (nextInt == 1) { - anzeige = findUltimativeStrafe(); - } else if (nextInt == 2) { - anzeige = findSperreVerlaengern(); - } else if (nextInt > wahrscheinlichkeitSperre + wahrscheinlichkeitStrafe + 2) { - anzeige = findeAufgabe(); - } else if (nextInt > wahrscheinlichkeitSperre + 2) { - anzeige = findeStrafe(); - } else { - anzeige = findeSperre(); - } - if (anzeige == null) { - Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_AKTIV); - Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, aktiv); - String text = "Ups, da ist etwas schief gelaufen. Keine potenzielle Aufgabe gefunden. Entweder seid ihr inzwischen so gut weggesperrt, dass wirklich keine Aufgaben mehr zur Verfügung stehen, oder uns ist ein Fehler unterlaufen. {AKTIV} und {PASSIV} überbrücken die Zeit mit ein wenig Petting."; - anzeige = new AufgabeAnzeige(); - anzeige.setNameAktiverMitspieler(aktiv != null ? aktiv.getName() : ""); - anzeige.setAufgabeText(getAnzeigeText(text, aktiv != null ? aktiv.getName() : "?", passiv != null ? passiv.getName() : "?")); - anzeige.setTimer(120); - } - return anzeige; - } - - public void backToLvl5() { - this.level = 5; - this.aufgabenAufAktuellemLevel = 0; - } - - public List getFinisher() { - var list = new ArrayList(); - List.of(GeschlechtEnum.WEIBLICH, GeschlechtEnum.DIVERS, GeschlechtEnum.MAENNLICH).forEach(geschlecht -> { - mitspieler.stream().filter(m -> geschlecht == m.getGeschlecht()).toList().forEach(cumming -> { - var partner = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, cumming); - var finishers = aufgabenList.getFinisher().stream() - .filter(finisher -> geschlecht == finisher.getGeschlecht()) - .toList(); - if (!finishers.isEmpty()) { - var aufgabe = finishers.get(new Random().nextInt(list.size())); - AufgabeAnzeige anzeige = new AufgabeAnzeige(); - anzeige.setNameAktiverMitspieler(cumming.getName()); - anzeige.setAufgabeText(getAnzeigeText(aufgabe.getText(), - cumming.getName(), partner != null ? partner.getName() : "")); - list.add(anzeige); - } - }); - }); - return list; - } - - private void checkLevel() { - if (++aufgabenAufAktuellemLevel >= 1 + aufgabenProLevel) { - aufgabenAufAktuellemLevel = 0; - level++; - } - } - - private AufgabeAnzeige findUltimativeStrafe() { - Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV); - if (aktiv != null) { - Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv); - if (passiv != null) { - String text = "{AKTIV}, verschnüre {PASSIV} fachmännisch inkl. KG, Plugs, Knebel, Augenbinde und was dir sonst einfällt. Nutze die Ruhe für was auch immer du möchtest."; - AufgabeAnzeige anzeige = new AufgabeAnzeige(); - anzeige.setNameAktiverMitspieler(aktiv.getName()); - anzeige.setAufgabeText(getAnzeigeText(text, aktiv.getName(), passiv.getName())); - anzeige.setTimer(new Random().nextInt(1800, 7200)); - return anzeige; - } - } - return findeStrafe(); - } - - private AufgabeAnzeige findSperreVerlaengern() { - if (!aktiveSperren.isEmpty()) { - AktiveSperre sperre = aktiveSperren.get(new Random().nextInt(aktiveSperren.size())); - Mitspieler passiv = sperre.getMitspieler(); - Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV, passiv); - if (aktiv != null) { - String text = "{AKTIV}, du entscheidest. Sollen alle bestehenden Zeitstrafen von {PASSIV} verlängert werden...?"; - AufgabeAnzeige anzeige = new AufgabeAnzeige(); - anzeige.setAufgabeText(getAnzeigeText(text, aktiv.getName(), passiv.getName())); - anzeige.setNameAktiverMitspieler(aktiv.getName()); - SperrenVerlaengernCallback callback = new SperrenVerlaengernCallback(); - callback.setFaktor(new Random().nextInt(2, 4)); - callback.setSpielerId(passiv.getId()); - anzeige.setCallback(callback); - return anzeige; - } - } - return findeSperre(); - } - - private AufgabeAnzeige findeAufgabe() { - Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_AKTIV); - if (aktiv != null) { - Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, aktiv); - if (passiv != null) { - List list = aufgabenList.getAufgaben().stream() - .filter(aufgabe -> aufgabe.isAufgabePassend(level, aktiv, passiv)) - .collect(Collectors.toList()); - if (!list.isEmpty()) { - Aufgabe aufgabe = list.get(new Random().nextInt(list.size())); - AufgabeAnzeige anzeige = new AufgabeAnzeige(); - anzeige.setNameAktiverMitspieler(aktiv.getName()); - anzeige.setAufgabeText(getAnzeigeText(aufgabe.getText(), aktiv.getName(), passiv.getName())); - if (aufgabe.getSekundenVon() != null) { - if (aufgabe.getSekundenBis() != null) { - anzeige.setTimer(new Random().nextInt(aufgabe.getSekundenVon(), aufgabe.getSekundenBis())); - } else { - anzeige.setTimer(aufgabe.getSekundenVon()); - } - } - return anzeige; - } - } - } - return findeStrafe(); - } - - private AufgabeAnzeige findeStrafe() { - Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV); - if (aktiv != null) { - Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv); - if (passiv != null) { - List list = aufgabenList.getStrafen().stream() - .filter(strafe -> strafe.isAufgabePassend(level, aktiv, passiv)) - .collect(Collectors.toList()); - if (!list.isEmpty()) { - Strafe strafe = list.get(new Random().nextInt(list.size())); - AufgabeAnzeige anzeige = new AufgabeAnzeige(); - anzeige.setNameAktiverMitspieler(aktiv.getName()); - anzeige.setAufgabeText(getAnzeigeText(strafe.getText(), aktiv.getName(), passiv.getName())); - if (strafe.getSekundenVon() != null) { - if (strafe.getSekundenBis() != null) { - anzeige.setTimer(new Random().nextInt(strafe.getSekundenVon(), strafe.getSekundenBis())); - } else { - anzeige.setTimer(strafe.getSekundenVon()); - } - } - return anzeige; - } - } - } - return findeSperre(); - } - - private AufgabeAnzeige findeSperre() { - Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV); - if (aktiv != null) { - Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv); - if (passiv != null) { - List list = aufgabenList.getSperren().stream() - .filter(sperre -> sperre.isAufgabePassend(passiv)) - .collect(Collectors.toList()); - if (!list.isEmpty()) { - Sperre sperre = list.get(new Random().nextInt(list.size())); - AufgabeAnzeige anzeige = new AufgabeAnzeige(); - anzeige.setNameAktiverMitspieler(aktiv.getName()); - anzeige.setAufgabeText(getAnzeigeText(sperre.getText(), aktiv.getName(), passiv.getName())); - SperreCallback callback = new SperreCallback(); - callback.setSperreId(sperre.getSperreId()); - callback.setSpielerId(passiv.getId()); - callback.setReleaseText(getAnzeigeText(sperre.getReleaseText(), aktiv.getName(), passiv.getName())); - anzeige.setCallback(callback); - return anzeige; - } - } - } - return null; - } - - private String getAnzeigeText(String textMitPlatzhaltern, String nameAktiv, String namePassiv) { - return textMitPlatzhaltern.replace("{AKTIV}", nameAktiv).replace("{PASSIV}", namePassiv); - } - - private Mitspieler findeMitspielerMitRolle(RolleEnum rolle) { - List list = mitspieler.stream() - .filter(m -> m.getRollen().contains(rolle)) - .toList(); - return list.isEmpty() ? null : list.get(new Random().nextInt(list.size())); - } - - private Mitspieler findeMitspielerMitRolle(RolleEnum rolle, Mitspieler gegenspieler) { - if (gegenspieler == null) return findeMitspielerMitRolle(rolle); - List list = mitspieler.stream() - .filter(m -> m != gegenspieler) - .filter(m -> m.isPassenderSpielpartner(gegenspieler)) - .filter(m -> m.getRollen().contains(rolle)) - .toList(); - return list.isEmpty() ? null : list.get(new Random().nextInt(list.size())); - } - - public int getAufgabenAufAktuellemLevel() { - return aufgabenAufAktuellemLevel; - } - - public int getLevel() { - return level; - } -} +package de.oaa.xxx.games.bdsm; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.oaa.xxx.games.bdsm.aufgaben.Aufgabe; +import de.oaa.xxx.games.bdsm.aufgaben.AufgabenList; +import de.oaa.xxx.games.bdsm.aufgaben.Sperre; +import de.oaa.xxx.games.bdsm.aufgaben.Strafe; +import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity; +import de.oaa.xxx.games.bdsm.sperre.SperreCallback; +import de.oaa.xxx.games.bdsm.sperre.SperrenVerlaengernCallback; + +public class BdsmGameDurchfuehren { + + private final AufgabenList aufgabenList; + private final List mitspieler = new ArrayList<>(); + private final List aktiveSperren = new ArrayList<>(); + + private final Integer wahrscheinlichkeitSperre; + private final Integer wahrscheinlichkeitStrafe; + + private int aufgabenProLevel; + private int level; + private int aufgabenAufAktuellemLevel; + + public BdsmGameDurchfuehren(BdsmGameEntity entity) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + aufgabenList = objectMapper.readValue(entity.getAufgaben(), AufgabenList.class); + entity.getMitspieler().forEach(mitspielerEntity -> mitspieler.add(mitspielerEntity.toMitspieler())); + entity.getAktiveSperren().forEach(sperreEntity -> aktiveSperren.add(sperreEntity.toSperre(mitspieler))); + + wahrscheinlichkeitSperre = entity.getWahrscheinlichkeitSperre(); + wahrscheinlichkeitStrafe = entity.getWahrscheinlichkeitStrafe(); + + this.aufgabenProLevel = entity.getAufgabenProLevel() != null ? entity.getAufgabenProLevel() : 5; + this.level = entity.getLevel() != null ? entity.getLevel() : 1; + this.aufgabenAufAktuellemLevel = entity.getAufgabenAufAktuellemLevel() != null ? entity.getAufgabenAufAktuellemLevel() : 0; + } + + public AufgabeAnzeige getNext() { + checkLevel(); + if (level == 6) { + return null; + } + AufgabeAnzeige anzeige = null; + int nextInt = new Random().nextInt(1, 100); + if (nextInt == 1) { + anzeige = findUltimativeStrafe(); + } else if (nextInt == 2) { + anzeige = findSperreVerlaengern(); + } else if (nextInt > wahrscheinlichkeitSperre + wahrscheinlichkeitStrafe + 2) { + anzeige = findeAufgabe(); + } else if (nextInt > wahrscheinlichkeitSperre + 2) { + anzeige = findeStrafe(); + } else { + anzeige = findeSperre(); + } + if (anzeige == null) { + Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_AKTIV); + Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, aktiv); + String text = "Ups, da ist etwas schief gelaufen. Keine potenzielle Aufgabe gefunden. Entweder seid ihr inzwischen so gut weggesperrt, dass wirklich keine Aufgaben mehr zur Verfügung stehen, oder uns ist ein Fehler unterlaufen. {AKTIV} und {PASSIV} überbrücken die Zeit mit ein wenig Petting."; + anzeige = new AufgabeAnzeige(); + anzeige.setNameAktiverMitspieler(aktiv != null ? aktiv.getName() : ""); + setMitspielerInfo(anzeige, aktiv); + anzeige.setAufgabeText(getAnzeigeText(text, aktiv != null ? aktiv.getName() : "?", passiv != null ? passiv.getName() : "?")); + anzeige.setTimer(120); + } + return anzeige; + } + + public void backToLvl5() { + this.level = 5; + this.aufgabenAufAktuellemLevel = 0; + } + + public List getFinisher() { + var list = new ArrayList(); + List.of(GeschlechtEnum.WEIBLICH, GeschlechtEnum.DIVERS, GeschlechtEnum.MAENNLICH).forEach(geschlecht -> { + mitspieler.stream().filter(m -> geschlecht == m.getGeschlecht()).toList().forEach(cumming -> { + var partner = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, cumming); + var finishers = aufgabenList.getFinisher().stream() + .filter(finisher -> geschlecht == finisher.getGeschlecht()) + .toList(); + if (!finishers.isEmpty()) { + var aufgabe = finishers.get(new Random().nextInt(list.size())); + AufgabeAnzeige anzeige = new AufgabeAnzeige(); + anzeige.setNameAktiverMitspieler(cumming.getName()); + setMitspielerInfo(anzeige, cumming); + anzeige.setAufgabeText(getAnzeigeText(aufgabe.getText(), + cumming.getName(), partner != null ? partner.getName() : "")); + list.add(anzeige); + } + }); + }); + return list; + } + + private void checkLevel() { + if (++aufgabenAufAktuellemLevel >= 1 + aufgabenProLevel) { + aufgabenAufAktuellemLevel = 0; + level++; + } + } + + private void setMitspielerInfo(AufgabeAnzeige anzeige, Mitspieler aktiv) { + if (aktiv != null) { + anzeige.setMitspielerId(aktiv.getId()); + anzeige.setEigenesGeraet(aktiv.isEigenesGeraet()); + } + } + + private AufgabeAnzeige findUltimativeStrafe() { + Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV); + if (aktiv != null) { + Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv); + if (passiv != null) { + String text = "{AKTIV}, verschnüre {PASSIV} fachmännisch inkl. KG, Plugs, Knebel, Augenbinde und was dir sonst einfällt. Nutze die Ruhe für was auch immer du möchtest."; + AufgabeAnzeige anzeige = new AufgabeAnzeige(); + anzeige.setNameAktiverMitspieler(aktiv.getName()); + setMitspielerInfo(anzeige, aktiv); + anzeige.setAufgabeText(getAnzeigeText(text, aktiv.getName(), passiv.getName())); + anzeige.setTimer(new Random().nextInt(1800, 7200)); + return anzeige; + } + } + return findeStrafe(); + } + + private AufgabeAnzeige findSperreVerlaengern() { + if (!aktiveSperren.isEmpty()) { + AktiveSperre sperre = aktiveSperren.get(new Random().nextInt(aktiveSperren.size())); + Mitspieler passiv = sperre.getMitspieler(); + Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV, passiv); + if (aktiv != null) { + String text = "{AKTIV}, du entscheidest. Sollen alle bestehenden Zeitstrafen von {PASSIV} verlängert werden...?"; + AufgabeAnzeige anzeige = new AufgabeAnzeige(); + anzeige.setAufgabeText(getAnzeigeText(text, aktiv.getName(), passiv.getName())); + anzeige.setNameAktiverMitspieler(aktiv.getName()); + setMitspielerInfo(anzeige, aktiv); + SperrenVerlaengernCallback callback = new SperrenVerlaengernCallback(); + callback.setFaktor(new Random().nextInt(2, 4)); + callback.setSpielerId(passiv.getId()); + anzeige.setCallback(callback); + return anzeige; + } + } + return findeSperre(); + } + + private AufgabeAnzeige findeAufgabe() { + Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_AKTIV); + if (aktiv != null) { + Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.AUFGABE_PASSIV, aktiv); + if (passiv != null) { + List list = aufgabenList.getAufgaben().stream() + .filter(aufgabe -> aufgabe.isAufgabePassend(level, aktiv, passiv)) + .collect(Collectors.toList()); + if (!list.isEmpty()) { + Aufgabe aufgabe = list.get(new Random().nextInt(list.size())); + AufgabeAnzeige anzeige = new AufgabeAnzeige(); + anzeige.setNameAktiverMitspieler(aktiv.getName()); + setMitspielerInfo(anzeige, aktiv); + anzeige.setAufgabeText(getAnzeigeText(aufgabe.getText(), aktiv.getName(), passiv.getName())); + if (aufgabe.getSekundenVon() != null) { + if (aufgabe.getSekundenBis() != null) { + anzeige.setTimer(new Random().nextInt(aufgabe.getSekundenVon(), aufgabe.getSekundenBis())); + } else { + anzeige.setTimer(aufgabe.getSekundenVon()); + } + } + return anzeige; + } + } + } + return findeStrafe(); + } + + private AufgabeAnzeige findeStrafe() { + Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV); + if (aktiv != null) { + Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv); + if (passiv != null) { + List list = aufgabenList.getStrafen().stream() + .filter(strafe -> strafe.isAufgabePassend(level, aktiv, passiv)) + .collect(Collectors.toList()); + if (!list.isEmpty()) { + Strafe strafe = list.get(new Random().nextInt(list.size())); + AufgabeAnzeige anzeige = new AufgabeAnzeige(); + anzeige.setNameAktiverMitspieler(aktiv.getName()); + setMitspielerInfo(anzeige, aktiv); + anzeige.setAufgabeText(getAnzeigeText(strafe.getText(), aktiv.getName(), passiv.getName())); + if (strafe.getSekundenVon() != null) { + if (strafe.getSekundenBis() != null) { + anzeige.setTimer(new Random().nextInt(strafe.getSekundenVon(), strafe.getSekundenBis())); + } else { + anzeige.setTimer(strafe.getSekundenVon()); + } + } + return anzeige; + } + } + } + return findeSperre(); + } + + private AufgabeAnzeige findeSperre() { + Mitspieler aktiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_AKTIV); + if (aktiv != null) { + Mitspieler passiv = findeMitspielerMitRolle(RolleEnum.BESTRAFUNG_PASSIV, aktiv); + if (passiv != null) { + List list = aufgabenList.getSperren().stream() + .filter(sperre -> sperre.isAufgabePassend(passiv)) + .collect(Collectors.toList()); + if (!list.isEmpty()) { + Sperre sperre = list.get(new Random().nextInt(list.size())); + AufgabeAnzeige anzeige = new AufgabeAnzeige(); + anzeige.setNameAktiverMitspieler(aktiv.getName()); + setMitspielerInfo(anzeige, aktiv); + anzeige.setAufgabeText(getAnzeigeText(sperre.getText(), aktiv.getName(), passiv.getName())); + SperreCallback callback = new SperreCallback(); + callback.setSperreId(sperre.getSperreId()); + callback.setSpielerId(passiv.getId()); + callback.setReleaseText(getAnzeigeText(sperre.getReleaseText(), aktiv.getName(), passiv.getName())); + anzeige.setCallback(callback); + return anzeige; + } + } + } + return null; + } + + private String getAnzeigeText(String textMitPlatzhaltern, String nameAktiv, String namePassiv) { + return textMitPlatzhaltern.replace("{AKTIV}", nameAktiv).replace("{PASSIV}", namePassiv); + } + + private Mitspieler findeMitspielerMitRolle(RolleEnum rolle) { + List list = mitspieler.stream() + .filter(m -> m.getRollen().contains(rolle)) + .toList(); + return list.isEmpty() ? null : list.get(new Random().nextInt(list.size())); + } + + private Mitspieler findeMitspielerMitRolle(RolleEnum rolle, Mitspieler gegenspieler) { + if (gegenspieler == null) return findeMitspielerMitRolle(rolle); + List list = mitspieler.stream() + .filter(m -> m != gegenspieler) + .filter(m -> m.isPassenderSpielpartner(gegenspieler)) + .filter(m -> m.getRollen().contains(rolle)) + .toList(); + return list.isEmpty() ? null : list.get(new Random().nextInt(list.size())); + } + + public int getAufgabenAufAktuellemLevel() { + return aufgabenAufAktuellemLevel; + } + + public int getLevel() { + return level; + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/Callback.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/Callback.java similarity index 88% rename from xxxthegame/src/main/java/de/oaa/xxx/session/Callback.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/Callback.java index a116d45..0f83c59 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/Callback.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/Callback.java @@ -1,4 +1,4 @@ -package de.oaa.xxx.session; +package de.oaa.xxx.games.bdsm; import java.util.UUID; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/GeschlechtEnum.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/GeschlechtEnum.java similarity index 65% rename from xxxthegame/src/main/java/de/oaa/xxx/session/GeschlechtEnum.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/GeschlechtEnum.java index ff5e173..7851012 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/GeschlechtEnum.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/GeschlechtEnum.java @@ -1,4 +1,4 @@ -package de.oaa.xxx.session; +package de.oaa.xxx.games.bdsm; public enum GeschlechtEnum { WEIBLICH, diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/Mitspieler.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/Mitspieler.java similarity index 87% rename from xxxthegame/src/main/java/de/oaa/xxx/session/Mitspieler.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/Mitspieler.java index fb23fa1..40ae751 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/Mitspieler.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/Mitspieler.java @@ -1,4 +1,4 @@ -package de.oaa.xxx.session; +package de.oaa.xxx.games.bdsm; import lombok.Getter; import lombok.Setter; @@ -11,6 +11,8 @@ import java.util.UUID; public class Mitspieler { private UUID id; + private UUID userId; + private boolean eigenesGeraet; private String name; private GeschlechtEnum geschlecht; private List spieltMit; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/RolleEnum.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/RolleEnum.java similarity index 74% rename from xxxthegame/src/main/java/de/oaa/xxx/session/RolleEnum.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/RolleEnum.java index d53e8d7..cb19db6 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/RolleEnum.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/RolleEnum.java @@ -1,4 +1,4 @@ -package de.oaa.xxx.session; +package de.oaa.xxx.games.bdsm; public enum RolleEnum { BESTRAFUNG_AKTIV, diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/Werkzeug.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/Werkzeug.java similarity index 93% rename from xxxthegame/src/main/java/de/oaa/xxx/session/Werkzeug.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/Werkzeug.java index 0394fdd..0314551 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/Werkzeug.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/Werkzeug.java @@ -1,4 +1,4 @@ -package de.oaa.xxx.session; +package de.oaa.xxx.games.bdsm; public enum Werkzeug { diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Aufgabe.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Aufgabe.java similarity index 88% rename from xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Aufgabe.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Aufgabe.java index 4b2384e..307852f 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Aufgabe.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Aufgabe.java @@ -1,7 +1,7 @@ -package de.oaa.xxx.session.aufgaben; +package de.oaa.xxx.games.bdsm.aufgaben; -import de.oaa.xxx.session.Mitspieler; -import de.oaa.xxx.session.Werkzeug; +import de.oaa.xxx.games.bdsm.Mitspieler; +import de.oaa.xxx.games.bdsm.Werkzeug; import lombok.Getter; import lombok.Setter; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/AufgabenList.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/AufgabenList.java similarity index 89% rename from xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/AufgabenList.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/AufgabenList.java index c929ee4..6701e48 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/AufgabenList.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/AufgabenList.java @@ -1,4 +1,4 @@ -package de.oaa.xxx.session.aufgaben; +package de.oaa.xxx.games.bdsm.aufgaben; import lombok.Getter; import lombok.Setter; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Finisher.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Finisher.java similarity index 80% rename from xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Finisher.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Finisher.java index dd71013..11914b6 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Finisher.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Finisher.java @@ -1,7 +1,7 @@ -package de.oaa.xxx.session.aufgaben; +package de.oaa.xxx.games.bdsm.aufgaben; -import de.oaa.xxx.session.GeschlechtEnum; -import de.oaa.xxx.session.Werkzeug; +import de.oaa.xxx.games.bdsm.GeschlechtEnum; +import de.oaa.xxx.games.bdsm.Werkzeug; import lombok.Getter; import lombok.Setter; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Sperre.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Sperre.java similarity index 84% rename from xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Sperre.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Sperre.java index c2be31c..28fa8e4 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Sperre.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Sperre.java @@ -1,7 +1,7 @@ -package de.oaa.xxx.session.aufgaben; +package de.oaa.xxx.games.bdsm.aufgaben; -import de.oaa.xxx.session.Mitspieler; -import de.oaa.xxx.session.Werkzeug; +import de.oaa.xxx.games.bdsm.Mitspieler; +import de.oaa.xxx.games.bdsm.Werkzeug; import lombok.Getter; import lombok.Setter; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Strafe.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Strafe.java similarity index 87% rename from xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Strafe.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Strafe.java index 3fd1fbe..b1cb3ad 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/aufgaben/Strafe.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/aufgaben/Strafe.java @@ -1,7 +1,7 @@ -package de.oaa.xxx.session.aufgaben; +package de.oaa.xxx.games.bdsm.aufgaben; -import de.oaa.xxx.session.Mitspieler; -import de.oaa.xxx.session.Werkzeug; +import de.oaa.xxx.games.bdsm.Mitspieler; +import de.oaa.xxx.games.bdsm.Werkzeug; import lombok.Getter; import lombok.Setter; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmEinladungController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmEinladungController.java new file mode 100644 index 0000000..7a9c4e0 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmEinladungController.java @@ -0,0 +1,195 @@ +package de.oaa.xxx.games.bdsm.controller; + +import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity; +import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity.Status; +import de.oaa.xxx.games.bdsm.repository.BdsmEinladungRepository; +import de.oaa.xxx.social.SystemMessageService; +import de.oaa.xxx.social.entity.MessageCause; +import de.oaa.xxx.social.repository.FriendshipRepository; +import de.oaa.xxx.user.UserRepository; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; +import java.time.LocalDateTime; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@RestController +@RequestMapping("/bdsm/einladung") +@Transactional +public class BdsmEinladungController { + + private final BdsmEinladungRepository einladungRepository; + private final UserRepository userRepository; + private final FriendshipRepository friendshipRepository; + private final SystemMessageService systemMessageService; + + public BdsmEinladungController(BdsmEinladungRepository einladungRepository, + UserRepository userRepository, + FriendshipRepository friendshipRepository, + SystemMessageService systemMessageService) { + this.einladungRepository = einladungRepository; + this.userRepository = userRepository; + this.friendshipRepository = friendshipRepository; + this.systemMessageService = systemMessageService; + } + + record EinladungRequest(UUID setupId, int slotIndex, UUID inviteeId) {} + record AntwortRequest(boolean accepted, String mode) {} // mode: OWN_DEVICE | HOST_DEVICE + + private UUID currentUserId(Principal principal) { + return userRepository.findByEmail(principal.getName()) + .map(u -> u.getUserId()).orElse(null); + } + + @PostMapping + public ResponseEntity> sendEinladung(@RequestBody EinladungRequest req, Principal principal) { + UUID inviterId = currentUserId(principal); + if (inviterId == null) return ResponseEntity.status(401).build(); + + // Freundschaft prüfen + var friendship = friendshipRepository.findExisting(inviterId, req.inviteeId()); + if (friendship.isEmpty() || friendship.get().getStatus() != de.oaa.xxx.social.entity.FriendshipEntity.Status.ACCEPTED) { + return ResponseEntity.status(403).build(); + } + + // Alte Einladung für diesen Slot canceln + einladungRepository.findBySetupId(req.setupId()).stream() + .filter(e -> e.getSlotIndex() == req.slotIndex() && e.getStatus() == Status.PENDING) + .forEach(e -> e.setStatus(Status.CANCELLED)); + + BdsmEinladungEntity entity = new BdsmEinladungEntity(); + entity.setEinladungId(UUID.randomUUID()); + entity.setSetupId(req.setupId()); + entity.setInviterId(inviterId); + entity.setInviteeId(req.inviteeId()); + entity.setSlotIndex(req.slotIndex()); + entity.setStatus(Status.PENDING); + entity.setCreatedAt(LocalDateTime.now()); + einladungRepository.save(entity); + + String inviterName = userRepository.findById(inviterId).map(u -> u.getName()).orElse("Jemand"); + systemMessageService.send( + inviterId, req.inviteeId(), + inviterName + " hat dich zum BDSM Game eingeladen.", + "/einladungen.html", + MessageCause.INVITATION + ); + + Map result = new LinkedHashMap<>(); + result.put("einladungId", entity.getEinladungId()); + return ResponseEntity.ok(result); + } + + @DeleteMapping("/{id}") + public ResponseEntity cancelEinladung(@PathVariable UUID id, Principal principal) { + UUID userId = currentUserId(principal); + if (userId == null) return ResponseEntity.status(401).build(); + BdsmEinladungEntity e = einladungRepository.findById(id).orElse(null); + if (e == null) return ResponseEntity.notFound().build(); + if (!e.getInviterId().equals(userId)) return ResponseEntity.status(403).build(); + e.setStatus(Status.CANCELLED); + return ResponseEntity.accepted().build(); + } + + @GetMapping + public ResponseEntity>> getBySetupId(@RequestParam UUID setupId, Principal principal) { + UUID userId = currentUserId(principal); + if (userId == null) return ResponseEntity.status(401).build(); + List> list = einladungRepository.findBySetupId(setupId).stream() + .map(this::toMap).toList(); + return ResponseEntity.ok(list); + } + + @GetMapping("/pending") + public ResponseEntity>> getPending(Principal principal) { + UUID userId = currentUserId(principal); + if (userId == null) return ResponseEntity.status(401).build(); + List> list = einladungRepository.findByInviteeIdAndStatus(userId, Status.PENDING) + .stream().map(e -> { + Map m = toMap(e); + userRepository.findById(e.getInviterId()).ifPresent(u -> { + m.put("inviterName", u.getName()); + m.put("inviterAvatar", u.getProfilePicture() != null ? u.getProfilePicture() : ""); + }); + return m; + }).toList(); + return ResponseEntity.ok(list); + } + + @GetMapping("/sent") + public ResponseEntity>> getSent(Principal principal) { + UUID userId = currentUserId(principal); + if (userId == null) return ResponseEntity.status(401).build(); + List> list = einladungRepository.findByInviterIdAndStatus(userId, Status.PENDING) + .stream().map(e -> { + Map m = toMap(e); + userRepository.findById(e.getInviteeId()).ifPresent(u -> { + m.put("inviteeName", u.getName()); + m.put("inviteeAvatar", u.getProfilePicture() != null ? u.getProfilePicture() : ""); + }); + return m; + }).toList(); + return ResponseEntity.ok(list); + } + + @GetMapping("/{id}") + public ResponseEntity> getById(@PathVariable UUID id, Principal principal) { + UUID userId = currentUserId(principal); + if (userId == null) return ResponseEntity.status(401).build(); + BdsmEinladungEntity e = einladungRepository.findById(id).orElse(null); + if (e == null) return ResponseEntity.notFound().build(); + if (!e.getInviteeId().equals(userId) && !e.getInviterId().equals(userId)) { + return ResponseEntity.status(403).build(); + } + Map m = toMap(e); + userRepository.findById(e.getInviterId()).ifPresent(u -> { + m.put("inviterName", u.getName()); + m.put("inviterAvatar", u.getProfilePicture() != null ? u.getProfilePicture() : ""); + }); + return ResponseEntity.ok(m); + } + + @PutMapping("/{id}/antwort") + public ResponseEntity antwort(@PathVariable UUID id, @RequestBody AntwortRequest req, Principal principal) { + UUID userId = currentUserId(principal); + if (userId == null) return ResponseEntity.status(401).build(); + BdsmEinladungEntity e = einladungRepository.findById(id).orElse(null); + if (e == null) return ResponseEntity.notFound().build(); + if (!e.getInviteeId().equals(userId)) return ResponseEntity.status(403).build(); + if (e.getStatus() != Status.PENDING) return ResponseEntity.badRequest().build(); + if (!req.accepted()) { + e.setStatus(Status.DECLINED); + } else if ("OWN_DEVICE".equals(req.mode())) { + e.setStatus(Status.ACCEPTED_OWN); + } else { + e.setStatus(Status.ACCEPTED_HOST); + } + return ResponseEntity.accepted().build(); + } + + private Map toMap(BdsmEinladungEntity e) { + Map m = new LinkedHashMap<>(); + m.put("einladungId", e.getEinladungId()); + m.put("setupId", e.getSetupId()); + m.put("slotIndex", e.getSlotIndex()); + m.put("inviteeId", e.getInviteeId()); + m.put("inviterId", e.getInviterId()); + m.put("status", e.getStatus().name()); + m.put("sessionId", e.getSessionId()); + return m; + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/controller/SessionController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmGameController.java similarity index 56% rename from xxxthegame/src/main/java/de/oaa/xxx/session/controller/SessionController.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmGameController.java index 6a78f21..ea747e4 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/controller/SessionController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/BdsmGameController.java @@ -1,16 +1,22 @@ -package de.oaa.xxx.session.controller; +package de.oaa.xxx.games.bdsm.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import de.oaa.xxx.session.AufgabeAnzeige; -import de.oaa.xxx.session.Mitspieler; -import de.oaa.xxx.session.Session; -import de.oaa.xxx.session.SessionDurchfuehren; -import de.oaa.xxx.session.aufgaben.AufgabenList; -import de.oaa.xxx.session.entity.MitspielerEntity; -import de.oaa.xxx.session.entity.SessionEntity; -import de.oaa.xxx.session.repository.AktiveSperreRepository; -import de.oaa.xxx.session.repository.MitspielerRepository; -import de.oaa.xxx.session.repository.SessionRepository; +import de.oaa.xxx.games.bdsm.AufgabeAnzeige; +import de.oaa.xxx.games.bdsm.Mitspieler; +import de.oaa.xxx.games.bdsm.BdsmGame; +import de.oaa.xxx.games.bdsm.BdsmGameDurchfuehren; +import de.oaa.xxx.games.bdsm.aufgaben.AufgabenList; +import de.oaa.xxx.games.bdsm.entity.MitspielerEntity; +import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity; +import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity; +import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository; +import de.oaa.xxx.games.bdsm.repository.BdsmEinladungRepository; +import de.oaa.xxx.games.bdsm.repository.MitspielerRepository; +import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository; +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.user.UserRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,59 +27,69 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import java.security.Principal; +import java.time.Duration; import java.time.LocalDateTime; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.UUID; @RestController -@RequestMapping("/session") +@RequestMapping("/bdsm") @Transactional -public class SessionController { +public class BdsmGameController { - private static final Logger LOGGER = LoggerFactory.getLogger(SessionController.class); + private static final Logger LOGGER = LoggerFactory.getLogger(BdsmGameController.class); - private final SessionRepository sessionRepository; + private final BdsmGameRepository sessionRepository; private final MitspielerRepository mitspielerRepository; private final AktiveSperreRepository aktiveSperreRepository; private final UserRepository userRepository; + private final GameHistoryRepository gameHistoryRepository; + private final BdsmEinladungRepository einladungRepository; private final ObjectMapper objectMapper; - public SessionController(SessionRepository sessionRepository, MitspielerRepository mitspielerRepository, + public BdsmGameController(BdsmGameRepository sessionRepository, MitspielerRepository mitspielerRepository, AktiveSperreRepository aktiveSperreRepository, UserRepository userRepository, + GameHistoryRepository gameHistoryRepository, BdsmEinladungRepository einladungRepository, ObjectMapper objectMapper) { this.sessionRepository = sessionRepository; this.mitspielerRepository = mitspielerRepository; this.aktiveSperreRepository = aktiveSperreRepository; this.userRepository = userRepository; + this.gameHistoryRepository = gameHistoryRepository; + this.einladungRepository = einladungRepository; this.objectMapper = objectMapper; } @GetMapping("/{sessionId}") - public ResponseEntity getBySessionId(@PathVariable UUID sessionId) { + public ResponseEntity getBySessionId(@PathVariable UUID sessionId) { return sessionRepository.findById(sessionId) .map(entity -> ResponseEntity.ok(toSession(entity))) .orElse(ResponseEntity.noContent().build()); } @GetMapping - public ResponseEntity getByUserId(@RequestParam UUID userId) { + public ResponseEntity getByUserId(@RequestParam UUID userId) { return sessionRepository.findByUserId(userId) .map(entity -> ResponseEntity.ok(toSession(entity))) .orElse(ResponseEntity.noContent().build()); } @PostMapping - public ResponseEntity create(@RequestBody Session session) { + public ResponseEntity create(@RequestBody BdsmGame session) { String email = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); UUID userId = userRepository.findByEmail(email).map(u -> u.getUserId()).orElse(null); if (userId == null) return ResponseEntity.status(401).build(); - SessionEntity entity = new SessionEntity(); + BdsmGameEntity entity = new BdsmGameEntity(); entity.setSessionId(UUID.randomUUID()); entity.setUserId(userId); entity.setAufgabenAufAktuellemLevel(0); @@ -85,8 +101,16 @@ public class SessionController { entity.setWahrscheinlichkeitStrafe(session.getWahrscheinlichkeitStrafe() != null ? session.getWahrscheinlichkeitStrafe() : 10); entity.setZeitfaktorZeitstrafen(session.getZeitfaktorZeitstrafen() != null ? session.getZeitfaktorZeitstrafen() : 1.0); entity.setLevel(1); + entity.setSetupId(session.getSetupId()); sessionRepository.save(entity); - LOGGER.debug("Session gestartet [sessionId={}, userId={}, aufgabenProLevel={}, wahrscheinlichkeitStrafe={}%, wahrscheinlichkeitSperre={}%, zeitfaktorZeitstrafen={}]", + // Akzeptierte Einladungen mit der neuen Session verknüpfen + if (session.getSetupId() != null) { + einladungRepository.findBySetupId(session.getSetupId()).stream() + .filter(e -> e.getStatus() == BdsmEinladungEntity.Status.ACCEPTED_OWN + || e.getStatus() == BdsmEinladungEntity.Status.ACCEPTED_HOST) + .forEach(e -> e.setSessionId(entity.getSessionId())); + } + LOGGER.debug("BdsmGame gestartet [sessionId={}, userId={}, aufgabenProLevel={}, wahrscheinlichkeitStrafe={}%, wahrscheinlichkeitSperre={}%, zeitfaktorZeitstrafen={}]", entity.getSessionId(), entity.getUserId(), entity.getAufgabenProLevel(), entity.getWahrscheinlichkeitStrafe(), entity.getWahrscheinlichkeitSperre(), entity.getZeitfaktorZeitstrafen()); @@ -96,9 +120,20 @@ public class SessionController { } @DeleteMapping - public ResponseEntity deleteSession(@RequestBody Session session) { + public ResponseEntity deleteSession(@RequestBody BdsmGame session) { return sessionRepository.findById(session.getSessionId()) .map(entity -> { + LocalDateTime endTime = LocalDateTime.now(); + long durationMinutes = Duration.between(entity.getStartZeit(), endTime).toMinutes(); + + GameHistoryEntity entry = new GameHistoryEntity(); + entry.setGameType(GameType.BDSM); + entry.setStartTime(entity.getStartZeit()); + entry.setEndTime(endTime); + entry.setDurationMinutes(durationMinutes); + entry.addParticipant(entity.getUserId(), GameRole.PLAYER); + gameHistoryRepository.save(entry); + aktiveSperreRepository.deleteAll(entity.getAktiveSperren()); mitspielerRepository.deleteAll(entity.getMitspieler()); sessionRepository.delete(entity); @@ -114,7 +149,7 @@ public class SessionController { return ResponseEntity.badRequest().build(); } String aufgaben = objectMapper.writeValueAsString(list); - SessionEntity session = sessionRepository.findById(sessionId).orElse(null); + BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null); if (session == null) { return ResponseEntity.badRequest().build(); } @@ -130,12 +165,12 @@ public class SessionController { @GetMapping("/{sessionId}/aufgaben/next") public ResponseEntity getNextAufgabe(@PathVariable UUID sessionId) { try { - SessionEntity session = sessionRepository.findById(sessionId).orElse(null); + BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null); if (session == null) { return ResponseEntity.badRequest().build(); } session.setLetzteAktivitaet(LocalDateTime.now()); - SessionDurchfuehren durchfuehren = new SessionDurchfuehren(session); + BdsmGameDurchfuehren durchfuehren = new BdsmGameDurchfuehren(session); AufgabeAnzeige next = durchfuehren.getNext(); session.setLevel(durchfuehren.getLevel()); session.setAufgabenAufAktuellemLevel(durchfuehren.getAufgabenAufAktuellemLevel()); @@ -165,7 +200,7 @@ public class SessionController { || mitspieler.getVerfuegbareWerkzeuge() == null || mitspieler.getVerfuegbareWerkzeuge().isEmpty()) { return ResponseEntity.badRequest().build(); } - SessionEntity session = sessionRepository.findById(sessionId).orElse(null); + BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null); if (session == null) { return ResponseEntity.badRequest().build(); } @@ -176,6 +211,8 @@ public class SessionController { entity.setRollen(mitspieler.getRollen()); entity.setSpieltMit(mitspieler.getSpieltMit()); entity.setWerkzeuge(mitspieler.getVerfuegbareWerkzeuge()); + entity.setUserId(mitspieler.getUserId()); + entity.setEigenesGeraet(mitspieler.isEigenesGeraet()); entity.setSession(session); mitspielerRepository.save(entity); return ResponseEntity.accepted().build(); @@ -184,9 +221,9 @@ public class SessionController { @GetMapping("/{sessionId}/finisher") public ResponseEntity> getFinisher(@PathVariable UUID sessionId) { try { - SessionEntity session = sessionRepository.findById(sessionId).orElse(null); + BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null); if (session == null) return ResponseEntity.badRequest().build(); - SessionDurchfuehren durchfuehren = new SessionDurchfuehren(session); + BdsmGameDurchfuehren durchfuehren = new BdsmGameDurchfuehren(session); return ResponseEntity.ok(durchfuehren.getFinisher()); } catch (Exception exception) { LOGGER.error(exception.getMessage(), exception); @@ -197,9 +234,9 @@ public class SessionController { @PostMapping("/{sessionId}/backToLevel5") public ResponseEntity backToLevel5(@PathVariable UUID sessionId) { try { - SessionEntity session = sessionRepository.findById(sessionId).orElse(null); + BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null); if (session == null) return ResponseEntity.badRequest().build(); - SessionDurchfuehren durchfuehren = new SessionDurchfuehren(session); + BdsmGameDurchfuehren durchfuehren = new BdsmGameDurchfuehren(session); durchfuehren.backToLvl5(); session.setLevel(durchfuehren.getLevel()); session.setAufgabenAufAktuellemLevel(durchfuehren.getAufgabenAufAktuellemLevel()); @@ -211,8 +248,62 @@ public class SessionController { } } - private Session toSession(SessionEntity entity) { - Session session = new Session(); + @GetMapping("/{sessionId}/mitspieler/me") + public ResponseEntity> getMeinMitspieler(@PathVariable UUID sessionId, Principal principal) { + UUID userId = userRepository.findByEmail(principal.getName()).map(u -> u.getUserId()).orElse(null); + if (userId == null) return ResponseEntity.status(401).build(); + BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null); + if (session == null) return ResponseEntity.notFound().build(); + return session.getMitspieler().stream() + .filter(m -> userId.equals(m.getUserId())) + .findFirst() + .map(m -> { + Map result = new LinkedHashMap<>(); + result.put("mitspielerId", m.getMitspielerId()); + result.put("name", m.getName()); + result.put("eigenesGeraet", m.isEigenesGeraet()); + return ResponseEntity.ok(result); + }) + .orElse(ResponseEntity.noContent().build()); + } + + record ActiveTaskRequest(String taskJson, LocalDateTime timerStartedAt) {} + record ActiveTaskResponse(String taskJson, Long elapsedSeconds) {} + + @PutMapping("/{sessionId}/active-task") + public ResponseEntity setActiveTask(@PathVariable UUID sessionId, @RequestBody ActiveTaskRequest req) { + BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null); + if (session == null) return ResponseEntity.notFound().build(); + session.setActiveTaskJson(req.taskJson()); + session.setTaskStartedAt(req.timerStartedAt()); + sessionRepository.save(session); + return ResponseEntity.accepted().build(); + } + + @DeleteMapping("/{sessionId}/active-task") + public ResponseEntity clearActiveTask(@PathVariable UUID sessionId) { + BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null); + if (session == null) return ResponseEntity.notFound().build(); + session.setActiveTaskJson(null); + session.setTaskStartedAt(null); + sessionRepository.save(session); + return ResponseEntity.accepted().build(); + } + + @GetMapping("/{sessionId}/active-task") + public ResponseEntity getActiveTask(@PathVariable UUID sessionId) { + BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null); + if (session == null) return ResponseEntity.notFound().build(); + if (session.getActiveTaskJson() == null) return ResponseEntity.noContent().build(); + Long elapsed = null; + if (session.getTaskStartedAt() != null) { + elapsed = Duration.between(session.getTaskStartedAt(), LocalDateTime.now()).getSeconds(); + } + return ResponseEntity.ok(new ActiveTaskResponse(session.getActiveTaskJson(), elapsed)); + } + + private BdsmGame toSession(BdsmGameEntity entity) { + BdsmGame session = new BdsmGame(); session.setSessionId(entity.getSessionId()); session.setUserId(entity.getUserId()); session.setAufgabenProLevel(entity.getAufgabenProLevel()); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/controller/SperreController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/SperreController.java similarity index 81% rename from xxxthegame/src/main/java/de/oaa/xxx/session/controller/SperreController.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/SperreController.java index 36372e8..e31022e 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/controller/SperreController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/controller/SperreController.java @@ -1,15 +1,15 @@ -package de.oaa.xxx.session.controller; +package de.oaa.xxx.games.bdsm.controller; -import de.oaa.xxx.session.AktiveSperre; -import de.oaa.xxx.session.Mitspieler; -import de.oaa.xxx.session.entity.AktiveSperreEntity; -import de.oaa.xxx.session.entity.SessionEntity; -import de.oaa.xxx.session.repository.AktiveSperreRepository; -import de.oaa.xxx.session.repository.MitspielerRepository; -import de.oaa.xxx.session.repository.SessionRepository; -import de.oaa.xxx.session.sperre.SperreCallback; -import de.oaa.xxx.session.sperre.SperreVerarbeiten; -import de.oaa.xxx.session.sperre.SperrenVerlaengernCallback; +import de.oaa.xxx.games.bdsm.AktiveSperre; +import de.oaa.xxx.games.bdsm.Mitspieler; +import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity; +import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity; +import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository; +import de.oaa.xxx.games.bdsm.repository.MitspielerRepository; +import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository; +import de.oaa.xxx.games.bdsm.sperre.SperreCallback; +import de.oaa.xxx.games.bdsm.sperre.SperreVerarbeiten; +import de.oaa.xxx.games.bdsm.sperre.SperrenVerlaengernCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -26,18 +26,18 @@ import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -@RestController("sessionSperreController") -@RequestMapping("/session/sperre") +@RestController("bdsmSperreController") +@RequestMapping("/bdsm/sperre") @Transactional public class SperreController { private static final Logger LOGGER = LoggerFactory.getLogger(SperreController.class); - private final SessionRepository sessionRepository; + private final BdsmGameRepository sessionRepository; private final MitspielerRepository mitspielerRepository; private final AktiveSperreRepository aktiveSperreRepository; - public SperreController(SessionRepository sessionRepository, MitspielerRepository mitspielerRepository, + public SperreController(BdsmGameRepository sessionRepository, MitspielerRepository mitspielerRepository, AktiveSperreRepository aktiveSperreRepository) { this.sessionRepository = sessionRepository; this.mitspielerRepository = mitspielerRepository; @@ -78,7 +78,7 @@ public class SperreController { @GetMapping("/aktive") public ResponseEntity> getAktiveSperren(@RequestParam UUID sessionId) { try { - SessionEntity session = sessionRepository.findById(sessionId).orElse(null); + BdsmGameEntity session = sessionRepository.findById(sessionId).orElse(null); if (session == null) return ResponseEntity.noContent().build(); List mitspielerList = session.getMitspieler().stream() .map(m -> m.toMitspieler()) diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/entity/AktiveSperreEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/AktiveSperreEntity.java similarity index 89% rename from xxxthegame/src/main/java/de/oaa/xxx/session/entity/AktiveSperreEntity.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/AktiveSperreEntity.java index 83a806b..5365ba5 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/entity/AktiveSperreEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/AktiveSperreEntity.java @@ -1,8 +1,8 @@ -package de.oaa.xxx.session.entity; +package de.oaa.xxx.games.bdsm.entity; -import de.oaa.xxx.session.AktiveSperre; -import de.oaa.xxx.session.Mitspieler; -import de.oaa.xxx.session.Werkzeug; +import de.oaa.xxx.games.bdsm.AktiveSperre; +import de.oaa.xxx.games.bdsm.Mitspieler; +import de.oaa.xxx.games.bdsm.Werkzeug; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; @@ -50,7 +50,7 @@ public class AktiveSperreEntity { private String releaseText; @ManyToOne @JoinColumn(name = "sessionId", nullable = false) - private SessionEntity session; + private BdsmGameEntity session; public AktiveSperre toSperre(List mitspielerList) { AktiveSperre sperre = new AktiveSperre(); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmDefaultsEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmDefaultsEntity.java new file mode 100644 index 0000000..45444c0 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmDefaultsEntity.java @@ -0,0 +1,30 @@ +package de.oaa.xxx.games.bdsm.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +@Entity +@Table(name = "bdsm_defaults") +public class BdsmDefaultsEntity { + + @Id + @Column(name = "user_id") + private UUID userId; + + @Column(length = 100) + private String spieltMit; + + @Column(length = 200) + private String rollen; + + @Column(length = 200) + private String werkzeuge; +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmEinladungEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmEinladungEntity.java new file mode 100644 index 0000000..9fe0ea8 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmEinladungEntity.java @@ -0,0 +1,50 @@ +package de.oaa.xxx.games.bdsm.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Getter +@Setter +@Entity +@Table(name = "bdsm_einladung") +public class BdsmEinladungEntity { + + public enum Status { + PENDING, ACCEPTED_OWN, ACCEPTED_HOST, DECLINED, CANCELLED + } + + @Id + @Column + private UUID einladungId; + + @Column(nullable = false) + private UUID setupId; + + @Column + private UUID sessionId; + + @Column(nullable = false) + private UUID inviterId; + + @Column(nullable = false) + private UUID inviteeId; + + @Column(nullable = false) + private int slotIndex; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private Status status; + + @Column(nullable = false) + private LocalDateTime createdAt; +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/entity/SessionEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmGameEntity.java similarity index 81% rename from xxxthegame/src/main/java/de/oaa/xxx/session/entity/SessionEntity.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmGameEntity.java index 0015ae5..0a987bc 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/entity/SessionEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/BdsmGameEntity.java @@ -1,4 +1,4 @@ -package de.oaa.xxx.session.entity; +package de.oaa.xxx.games.bdsm.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -18,7 +18,7 @@ import java.util.UUID; @Setter @Entity @Table(name = "session") -public class SessionEntity { +public class BdsmGameEntity { @Id @Column @@ -47,10 +47,16 @@ public class SessionEntity { private String aufgaben; @Column private Double zeitfaktorZeitstrafen; + @Column(columnDefinition = "TEXT") + private String activeTaskJson; + @Column + private LocalDateTime taskStartedAt; + @Column + private UUID setupId; @Override public String toString() { - return "SessionEntity[sessionId=" + sessionId + ", userId=" + userId + return "BdsmGameEntity[sessionId=" + sessionId + ", userId=" + userId + ", level=" + level + ", aufgaben=" + aufgabenAufAktuellemLevel + "/" + aufgabenProLevel + ", pStrafe=" + wahrscheinlichkeitStrafe + "%, pSperre=" + wahrscheinlichkeitSperre + "%" + ", zeitfaktor=" + zeitfaktorZeitstrafen + ", start=" + startZeit + "]"; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/entity/MitspielerEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/MitspielerEntity.java similarity index 86% rename from xxxthegame/src/main/java/de/oaa/xxx/session/entity/MitspielerEntity.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/MitspielerEntity.java index 040fced..49755e7 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/entity/MitspielerEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/entity/MitspielerEntity.java @@ -1,77 +1,83 @@ -package de.oaa.xxx.session.entity; - -import de.oaa.xxx.session.GeschlechtEnum; -import de.oaa.xxx.session.Mitspieler; -import de.oaa.xxx.session.RolleEnum; -import de.oaa.xxx.session.Werkzeug; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import lombok.Getter; -import lombok.Setter; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -@Getter -@Setter -@Entity -@Table(name = "mitspieler") -public class MitspielerEntity { - - @Id - @Column - private UUID mitspielerId; - @Column - private String name; - @Enumerated(EnumType.STRING) - @Column - private GeschlechtEnum geschlecht; - @Enumerated(EnumType.STRING) - @ElementCollection(targetClass = Werkzeug.class, fetch = FetchType.EAGER) - @CollectionTable(name = "mitspieler_werkzeuge", joinColumns = @JoinColumn(name = "mitspielerId")) - @Column(name = "werkzeug") - private List werkzeuge = new ArrayList<>(); - @Enumerated(EnumType.STRING) - @ElementCollection(targetClass = GeschlechtEnum.class, fetch = FetchType.EAGER) - @CollectionTable(name = "mitspieler_spieltMit", joinColumns = @JoinColumn(name = "mitspielerId")) - @Column(name = "geschlecht") - private List spieltMit = new ArrayList<>(); - @Enumerated(EnumType.STRING) - @ElementCollection(targetClass = RolleEnum.class, fetch = FetchType.EAGER) - @CollectionTable(name = "mitspieler_rollen", joinColumns = @JoinColumn(name = "mitspielerId")) - @Column(name = "rolle") - private List rollen = new ArrayList<>(); - @ManyToOne - @JoinColumn(name = "sessionId", nullable = false) - private SessionEntity session; - @OneToMany(mappedBy = "mitspieler", fetch = FetchType.EAGER) - private List aktiveSperren = new ArrayList<>(); - - @Override - public String toString() { - return "MitspielerEntity[mitspielerId=" + mitspielerId + ", name=" + name - + ", geschlecht=" + geschlecht + ", rollen=" + rollen + ", werkzeuge=" + werkzeuge + "]"; - } - - public Mitspieler toMitspieler() { - Mitspieler mitspieler = new Mitspieler(); - mitspieler.setGeschlecht(geschlecht); - mitspieler.setId(mitspielerId); - mitspieler.setName(name); - mitspieler.setRollen(rollen); - mitspieler.setSpieltMit(spieltMit); - mitspieler.setVerfuegbareWerkzeuge(new ArrayList<>(werkzeuge)); - return mitspieler; - } -} +package de.oaa.xxx.games.bdsm.entity; + +import de.oaa.xxx.games.bdsm.GeschlechtEnum; +import de.oaa.xxx.games.bdsm.Mitspieler; +import de.oaa.xxx.games.bdsm.RolleEnum; +import de.oaa.xxx.games.bdsm.Werkzeug; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Getter +@Setter +@Entity +@Table(name = "mitspieler") +public class MitspielerEntity { + + @Id + @Column + private UUID mitspielerId; + @Column + private UUID userId; + @Column + private boolean eigenesGeraet; + @Column + private String name; + @Enumerated(EnumType.STRING) + @Column + private GeschlechtEnum geschlecht; + @Enumerated(EnumType.STRING) + @ElementCollection(targetClass = Werkzeug.class, fetch = FetchType.EAGER) + @CollectionTable(name = "mitspieler_werkzeuge", joinColumns = @JoinColumn(name = "mitspielerId")) + @Column(name = "werkzeug") + private List werkzeuge = new ArrayList<>(); + @Enumerated(EnumType.STRING) + @ElementCollection(targetClass = GeschlechtEnum.class, fetch = FetchType.EAGER) + @CollectionTable(name = "mitspieler_spieltMit", joinColumns = @JoinColumn(name = "mitspielerId")) + @Column(name = "geschlecht") + private List spieltMit = new ArrayList<>(); + @Enumerated(EnumType.STRING) + @ElementCollection(targetClass = RolleEnum.class, fetch = FetchType.EAGER) + @CollectionTable(name = "mitspieler_rollen", joinColumns = @JoinColumn(name = "mitspielerId")) + @Column(name = "rolle") + private List rollen = new ArrayList<>(); + @ManyToOne + @JoinColumn(name = "sessionId", nullable = false) + private BdsmGameEntity session; + @OneToMany(mappedBy = "mitspieler", fetch = FetchType.EAGER) + private List aktiveSperren = new ArrayList<>(); + + @Override + public String toString() { + return "MitspielerEntity[mitspielerId=" + mitspielerId + ", name=" + name + + ", geschlecht=" + geschlecht + ", rollen=" + rollen + ", werkzeuge=" + werkzeuge + "]"; + } + + public Mitspieler toMitspieler() { + Mitspieler mitspieler = new Mitspieler(); + mitspieler.setGeschlecht(geschlecht); + mitspieler.setId(mitspielerId); + mitspieler.setUserId(userId); + mitspieler.setEigenesGeraet(eigenesGeraet); + mitspieler.setName(name); + mitspieler.setRollen(rollen); + mitspieler.setSpieltMit(spieltMit); + mitspieler.setVerfuegbareWerkzeuge(new ArrayList<>(werkzeuge)); + return mitspieler; + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/repository/AktiveSperreRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/AktiveSperreRepository.java similarity index 86% rename from xxxthegame/src/main/java/de/oaa/xxx/session/repository/AktiveSperreRepository.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/AktiveSperreRepository.java index 549bcd6..acf8fa4 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/repository/AktiveSperreRepository.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/AktiveSperreRepository.java @@ -1,6 +1,6 @@ -package de.oaa.xxx.session.repository; +package de.oaa.xxx.games.bdsm.repository; -import de.oaa.xxx.session.entity.AktiveSperreEntity; +import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmDefaultsRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmDefaultsRepository.java new file mode 100644 index 0000000..c144a2d --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmDefaultsRepository.java @@ -0,0 +1,11 @@ +package de.oaa.xxx.games.bdsm.repository; + +import de.oaa.xxx.games.bdsm.entity.BdsmDefaultsEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface BdsmDefaultsRepository extends JpaRepository { + Optional findByUserId(UUID userId); +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmEinladungRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmEinladungRepository.java new file mode 100644 index 0000000..4ac66bc --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmEinladungRepository.java @@ -0,0 +1,17 @@ +package de.oaa.xxx.games.bdsm.repository; + +import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity; +import de.oaa.xxx.games.bdsm.entity.BdsmEinladungEntity.Status; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.UUID; + +public interface BdsmEinladungRepository extends JpaRepository { + + List findBySetupId(UUID setupId); + + List findByInviteeIdAndStatus(UUID inviteeId, Status status); + + List findByInviterIdAndStatus(UUID inviterId, Status status); +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmGameRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmGameRepository.java new file mode 100644 index 0000000..d3aedeb --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/BdsmGameRepository.java @@ -0,0 +1,12 @@ +package de.oaa.xxx.games.bdsm.repository; + +import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface BdsmGameRepository extends JpaRepository { + + Optional findByUserId(UUID userId); +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/repository/MitspielerRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/MitspielerRepository.java similarity index 62% rename from xxxthegame/src/main/java/de/oaa/xxx/session/repository/MitspielerRepository.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/MitspielerRepository.java index 07dece5..f920772 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/repository/MitspielerRepository.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/repository/MitspielerRepository.java @@ -1,6 +1,6 @@ -package de.oaa.xxx.session.repository; +package de.oaa.xxx.games.bdsm.repository; -import de.oaa.xxx.session.entity.MitspielerEntity; +import de.oaa.xxx.games.bdsm.entity.MitspielerEntity; import org.springframework.data.jpa.repository.JpaRepository; import java.util.UUID; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/sperre/SperreCallback.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/sperre/SperreCallback.java similarity index 87% rename from xxxthegame/src/main/java/de/oaa/xxx/session/sperre/SperreCallback.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/sperre/SperreCallback.java index 2633076..83f5dc8 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/sperre/SperreCallback.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/sperre/SperreCallback.java @@ -1,6 +1,6 @@ -package de.oaa.xxx.session.sperre; +package de.oaa.xxx.games.bdsm.sperre; -import de.oaa.xxx.session.Callback; +import de.oaa.xxx.games.bdsm.Callback; import java.util.UUID; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/sperre/SperreVerarbeiten.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/sperre/SperreVerarbeiten.java similarity index 77% rename from xxxthegame/src/main/java/de/oaa/xxx/session/sperre/SperreVerarbeiten.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/sperre/SperreVerarbeiten.java index 7796687..051d434 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/sperre/SperreVerarbeiten.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/sperre/SperreVerarbeiten.java @@ -1,14 +1,14 @@ -package de.oaa.xxx.session.sperre; +package de.oaa.xxx.games.bdsm.sperre; import com.fasterxml.jackson.databind.ObjectMapper; -import de.oaa.xxx.session.aufgaben.AufgabenList; -import de.oaa.xxx.session.aufgaben.Sperre; -import de.oaa.xxx.session.entity.AktiveSperreEntity; -import de.oaa.xxx.session.entity.MitspielerEntity; -import de.oaa.xxx.session.entity.SessionEntity; -import de.oaa.xxx.session.repository.AktiveSperreRepository; -import de.oaa.xxx.session.repository.MitspielerRepository; -import de.oaa.xxx.session.repository.SessionRepository; +import de.oaa.xxx.games.bdsm.aufgaben.AufgabenList; +import de.oaa.xxx.games.bdsm.aufgaben.Sperre; +import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity; +import de.oaa.xxx.games.bdsm.entity.MitspielerEntity; +import de.oaa.xxx.games.bdsm.entity.BdsmGameEntity; +import de.oaa.xxx.games.bdsm.repository.AktiveSperreRepository; +import de.oaa.xxx.games.bdsm.repository.MitspielerRepository; +import de.oaa.xxx.games.bdsm.repository.BdsmGameRepository; import java.time.LocalDateTime; import java.util.Optional; @@ -19,9 +19,9 @@ public class SperreVerarbeiten { private final ObjectMapper objectMapper = new ObjectMapper(); - public void sperreAnwenden(SperreCallback callback, SessionRepository sessionRepository, + public void sperreAnwenden(SperreCallback callback, BdsmGameRepository sessionRepository, MitspielerRepository mitspielerRepository, AktiveSperreRepository sperreRepository) throws Exception { - SessionEntity session = sessionRepository.findById(callback.getSessionId()).orElse(null); + BdsmGameEntity session = sessionRepository.findById(callback.getSessionId()).orElse(null); MitspielerEntity mitspieler = mitspielerRepository.findById(callback.getSpielerId()).orElse(null); if (session != null) { AufgabenList aufgaben = objectMapper.readValue(session.getAufgaben(), AufgabenList.class); @@ -56,7 +56,7 @@ public class SperreVerarbeiten { sperreRepository.save(verlaengern); } - private void fill(SperreCallback callback, SessionEntity session, MitspielerEntity mitspieler, + private void fill(SperreCallback callback, BdsmGameEntity session, MitspielerEntity mitspieler, Sperre sperre, AktiveSperreEntity aktiv) { aktiv.setAktiveSperreId(UUID.randomUUID()); LocalDateTime now = LocalDateTime.now(); @@ -70,7 +70,7 @@ public class SperreVerarbeiten { aktiv.setReleaseText(callback.getReleaseText()); } - private Integer berechneDauer(SessionEntity session, Sperre sperre) { + private Integer berechneDauer(BdsmGameEntity session, Sperre sperre) { Integer minuten = 30; if (sperre.getMinutenVon() != null) { if (sperre.getMinutenBis() != null) { diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/sperre/SperrenVerlaengernCallback.java b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/sperre/SperrenVerlaengernCallback.java similarity index 85% rename from xxxthegame/src/main/java/de/oaa/xxx/session/sperre/SperrenVerlaengernCallback.java rename to xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/sperre/SperrenVerlaengernCallback.java index 58bf7fa..3d26541 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/sperre/SperrenVerlaengernCallback.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/bdsm/sperre/SperrenVerlaengernCallback.java @@ -1,6 +1,6 @@ -package de.oaa.xxx.session.sperre; +package de.oaa.xxx.games.bdsm.sperre; -import de.oaa.xxx.session.Callback; +import de.oaa.xxx.games.bdsm.Callback; import java.util.UUID; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/LockeeInvitationController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/LockeeInvitationController.java index ee87f07..a59a830 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/LockeeInvitationController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/LockeeInvitationController.java @@ -22,9 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import de.oaa.xxx.games.chastity.cardlock.CardlockRepository; -import de.oaa.xxx.social.SseService; -import de.oaa.xxx.social.entity.MessageEntity; -import de.oaa.xxx.social.repository.MessageRepository; +import de.oaa.xxx.social.SystemMessageService; import de.oaa.xxx.user.UserRepository; @RestController @@ -34,8 +32,7 @@ public class LockeeInvitationController { private final LockeeInvitationRepository lockeeInvitationRepository; private final CardlockRepository cardlockRepository; private final UserRepository userRepository; - private final MessageRepository messageRepository; - private final SseService sseService; + private final SystemMessageService systemMessageService; @Value("${app.base-url:http://localhost:8080}") private String baseUrl; @@ -45,27 +42,15 @@ public class LockeeInvitationController { public LockeeInvitationController(LockeeInvitationRepository lockeeInvitationRepository, CardlockRepository cardlockRepository, UserRepository userRepository, - MessageRepository messageRepository, - SseService sseService) { + SystemMessageService systemMessageService) { this.lockeeInvitationRepository = lockeeInvitationRepository; this.cardlockRepository = cardlockRepository; this.userRepository = userRepository; - this.messageRepository = messageRepository; - this.sseService = sseService; + this.systemMessageService = systemMessageService; } - private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl) { - MessageEntity msg = new MessageEntity(); - msg.setMessageId(UUID.randomUUID()); - msg.setSenderId(senderId); - msg.setReceiverId(receiverId); - msg.setText(text); - msg.setSentAt(LocalDateTime.now()); - msg.setSystemMessage(true); - if (targetUrl != null) msg.setTargetUrl(targetUrl); - messageRepository.save(msg); - long unread = messageRepository.countByReceiverIdAndSystemMessageAndReadAtIsNull(receiverId, true); - sseService.push(receiverId, "NOTIFICATION", java.util.Map.of("unreadCount", unread, "text", text)); + private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl, de.oaa.xxx.social.entity.MessageCause cause) { + systemMessageService.send(senderId, receiverId, text, targetUrl, cause); } private String generateUnlockCode(int lines) { @@ -154,7 +139,7 @@ public class LockeeInvitationController { String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock"; sendMessage(myId, inv.getLockeeUserId(), me.getName() + " hat die Lockee-Einladung für das Lock „" + lockName + "\" zurückgezogen.", - null); + null, de.oaa.xxx.social.entity.MessageCause.INVITATION); } return ResponseEntity.noContent().build(); @@ -248,7 +233,7 @@ public class LockeeInvitationController { String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock"; sendMessage(myId, inv.getKeyholderUserId(), me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" angenommen.", - "/keyholder.html"); + "/keyholder.html", de.oaa.xxx.social.entity.MessageCause.INVITATION); return ResponseEntity.ok(Map.of( "lockId", lock.getLockId().toString(), @@ -278,7 +263,7 @@ public class LockeeInvitationController { String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock"; sendMessage(myId, inv.getKeyholderUserId(), me.getName() + " hat die Einladung als Lockee für das Lock „" + lockName + "\" abgelehnt.", - null); + null, de.oaa.xxx.social.entity.MessageCause.INVITATION); } return ResponseEntity.noContent().build(); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java index 05f8d11..72a49f1 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockController.java @@ -41,7 +41,7 @@ import de.oaa.xxx.games.chastity.KeyholderInvitationEntity; import de.oaa.xxx.games.chastity.KeyholderInvitationRepository; import de.oaa.xxx.games.chastity.LockeeInvitationEntity; import de.oaa.xxx.games.chastity.LockeeInvitationRepository; -import de.oaa.xxx.games.chastity.history.LockHistoryRepository; +import de.oaa.xxx.games.history.GameHistoryRepository; import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity; import de.oaa.xxx.games.chastity.tasks.AssignedTaskRepository; import de.oaa.xxx.games.chastity.tasks.Task; @@ -49,9 +49,7 @@ import de.oaa.xxx.games.chastity.verification.VerificationEntity; import de.oaa.xxx.games.chastity.verification.VerificationRepository; import de.oaa.xxx.games.chastity.verification.VerificationVoteEntity; import de.oaa.xxx.games.chastity.verification.VerificationVoteRepository; -import de.oaa.xxx.social.SseService; -import de.oaa.xxx.social.entity.MessageEntity; -import de.oaa.xxx.social.repository.MessageRepository; +import de.oaa.xxx.social.SystemMessageService; import de.oaa.xxx.user.UserRepository; @RestController @@ -65,15 +63,14 @@ public class CardLockController { private final VerificationRepository verificationRepository; private final VerificationVoteRepository verificationVoteRepository; private final HygieneViolationRepository hygieneViolationRepository; - private final MessageRepository messageRepository; private final LockeeInvitationRepository lockeeInvitationRepository; private final AssignedTaskRepository assignedTaskRepository; private final KeyholderTaskChoiceRepository keyholderTaskChoiceRepository; private final CommunityTaskVoteRepository communityTaskVoteRepository; private final UnlockCodeHistoryRepository unlockCodeHistoryRepository; private final UnlockCodeHistoryService unlockCodeHistoryService; - private final LockHistoryRepository lockHistoryRepository; - private final SseService sseService; + private final GameHistoryRepository gameHistoryRepository; + private final SystemMessageService systemMessageService; @Value("${app.base-url:http://localhost:8080}") private String baseUrl; @@ -85,15 +82,14 @@ public class CardLockController { VerificationRepository verificationRepository, VerificationVoteRepository verificationVoteRepository, HygieneViolationRepository hygieneViolationRepository, - MessageRepository messageRepository, LockeeInvitationRepository lockeeInvitationRepository, AssignedTaskRepository assignedTaskRepository, KeyholderTaskChoiceRepository keyholderTaskChoiceRepository, CommunityTaskVoteRepository communityTaskVoteRepository, UnlockCodeHistoryRepository unlockCodeHistoryRepository, UnlockCodeHistoryService unlockCodeHistoryService, - LockHistoryRepository lockHistoryRepository, - SseService sseService) { + GameHistoryRepository gameHistoryRepository, + SystemMessageService systemMessageService) { this.cardlockRepository = cardlockRepository; this.cardLockRepository = cardLockRepository; this.userRepository = userRepository; @@ -101,15 +97,14 @@ public class CardLockController { this.verificationRepository = verificationRepository; this.verificationVoteRepository = verificationVoteRepository; this.hygieneViolationRepository = hygieneViolationRepository; - this.messageRepository = messageRepository; this.lockeeInvitationRepository = lockeeInvitationRepository; this.assignedTaskRepository = assignedTaskRepository; this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository; this.communityTaskVoteRepository = communityTaskVoteRepository; this.unlockCodeHistoryRepository = unlockCodeHistoryRepository; this.unlockCodeHistoryService = unlockCodeHistoryService; - this.lockHistoryRepository = lockHistoryRepository; - this.sseService = sseService; + this.gameHistoryRepository = gameHistoryRepository; + this.systemMessageService = systemMessageService; } record CreateCardLockRequest( @@ -194,7 +189,7 @@ public class CardLockController { String lockName = req.name() != null && !req.name().isBlank() ? req.name() : "Unbenanntes Lock"; sendMessage(myId, lockee.getUserId(), me.getName() + " hat dich als Lockee für das Lock „" + lockName + "\" eingeladen.", - "/einladungen.html"); + "/einladungen.html", de.oaa.xxx.social.entity.MessageCause.INVITATION); return ResponseEntity.ok(Map.of( "lockId", lock.getLockId().toString(), @@ -257,7 +252,7 @@ public class CardLockController { String lockName = req.name() != null && !req.name().isBlank() ? req.name() : "Unbenanntes Lock"; sendMessage(me.getUserId(), kh.getUserId(), me.getName() + " hat dich als Keyholder*In für das Lock „" + lockName + "\" eingeladen.", - "/einladungen.html"); + "/einladungen.html", de.oaa.xxx.social.entity.MessageCause.INVITATION); keyholderPending = true; } @@ -282,7 +277,7 @@ public class CardLockController { var l = lockOpt.get(); if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build(); - CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, lockHistoryRepository, userRepository); + CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, gameHistoryRepository, userRepository); CardDTO dto = service.getNextCard(); if (dto == null) return ResponseEntity.status(409).body(Map.of("error", "Keine Karte verfügbar")); @@ -300,7 +295,7 @@ public class CardLockController { userRepository.findById(l.getKeyholder()).ifPresent(kh -> sendMessage(l.getLockee(), kh.getUserId(), "Deine Lockee hat eine Aufgaben-Karte gezogen – wähle eine Aufgabe aus.", - "/keyholder.html")); + "/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE)); taskPending = "KEYHOLDER"; } else if ("COMMUNITY".equals(l.getTaskCardMode())) { @@ -320,9 +315,14 @@ public class CardLockController { result.put("unlockCode", dto.unlockCode() != null ? dto.unlockCode() : ""); if (taskPending != null) result.put("taskPending", taskPending); - // Grüne Karte → Entsperrcode-Historie speichern + // Grüne Karte → Entsperrcode-Historie speichern + Keyholder benachrichtigen if (dto.unlockCode() != null && !dto.unlockCode().isBlank()) { unlockCodeHistoryService.save(myId, l.getLockId(), l.getName(), dto.unlockCode(), "GREEN_CARD"); + if (l.getKeyholder() != null) { + sendMessage(myId, l.getKeyholder(), + meOpt.get().getName() + " hat die grüne Karte gezogen! Der Entsperrcode wurde angezeigt.", + "/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE); + } } return ResponseEntity.ok(result); @@ -417,7 +417,7 @@ public class CardLockController { var l = lockOpt.get(); if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build(); - CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, lockHistoryRepository, userRepository); + CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, gameHistoryRepository, userRepository); service.clearTask(); return ResponseEntity.noContent().build(); } @@ -434,8 +434,16 @@ public class CardLockController { var l = lockOpt.get(); if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build(); - CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, lockHistoryRepository, userRepository); + CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, gameHistoryRepository, userRepository); service.putBackGreen(); + + // Grüne Karte zurückgelegt → Keyholder benachrichtigen + if (l.getKeyholder() != null) { + sendMessage(myId, l.getKeyholder(), + meOpt.get().getName() + " hat die grüne Karte zurückgelegt und bleibt im Lock.", + "/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE); + } + return ResponseEntity.noContent().build(); } @@ -557,7 +565,7 @@ public class CardLockController { lockDirty = true; sendMessage(l.getKeyholder(), l.getLockee(), "Die dir gestellte Aufgabe ist abgelaufen, ohne dass du reagiert hast. Die Strafe wurde automatisch angewendet.", - "/activelock.html?lockId=" + l.getLockId()); + "/activelock.html?lockId=" + l.getLockId(), de.oaa.xxx.social.entity.MessageCause.GAME_STATE); } if (lockDirty) cardlockRepository.save(l); @@ -695,7 +703,7 @@ public class CardLockController { var lockee = meOpt.get(); sendMessage(myId, lock.getKeyholder(), "📸 " + lockee.getName() + " hat eine Verifikation eingereicht.", - "/keyholder.html"); + "/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE); } return ResponseEntity.noContent().build(); @@ -793,14 +801,9 @@ public class CardLockController { if (lockOpt.isPresent()) { var lock = lockOpt.get(); String lockName = lock.getName() != null && !lock.getName().isBlank() ? lock.getName() : "Unbenanntes Lock"; - MessageEntity msg = new MessageEntity(); - msg.setMessageId(UUID.randomUUID()); - msg.setSenderId(myId); - msg.setReceiverId(lock.getLockee()); - msg.setText(me.getName() + " hat die Einladung als Keyholder*In für das Lock „" + lockName + "\" abgelehnt."); - msg.setSentAt(LocalDateTime.now()); - msg.setSystemMessage(true); - messageRepository.save(msg); + sendMessage(myId, lock.getLockee(), + me.getName() + " hat die Einladung als Keyholder*In für das Lock „" + lockName + "\" abgelehnt.", + null, de.oaa.xxx.social.entity.MessageCause.INVITATION); } return ResponseEntity.noContent().build(); @@ -855,14 +858,9 @@ public class CardLockController { String lockName = lockOpt.get().getName() != null && !lockOpt.get().getName().isBlank() ? lockOpt.get().getName() : "Unbenanntes Lock"; - MessageEntity msg = new MessageEntity(); - msg.setMessageId(UUID.randomUUID()); - msg.setSenderId(myId); - msg.setReceiverId(inv.getKeyholderUserId()); - msg.setText(me.getName() + " hat die Keyholder-Einladung für das Lock „" + lockName + "\" zurückgezogen."); - msg.setSentAt(LocalDateTime.now()); - msg.setSystemMessage(true); - messageRepository.save(msg); + sendMessage(myId, inv.getKeyholderUserId(), + me.getName() + " hat die Keyholder-Einladung für das Lock „" + lockName + "\" zurückgezogen.", + null, de.oaa.xxx.social.entity.MessageCause.INVITATION); return ResponseEntity.noContent().build(); } @@ -1069,7 +1067,7 @@ public class CardLockController { if (!l.getLockee().equals(myId)) return ResponseEntity.status(403).build(); // Entsperrung protokollieren (History + XP) – gültig nur wenn Keyholder vorhanden und kein Auto-Notfall - CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, lockHistoryRepository, userRepository); + CardLockService service = new CardLockService(l, verificationRepository, verificationVoteRepository, cardLockRepository, gameHistoryRepository, userRepository); service.unlock(l.getUnlockCode()); var verifications = verificationRepository.findByLockId(lockId); @@ -1124,14 +1122,8 @@ public class CardLockController { ? me.getName() + " hat " + toAdd.size() + " Karte(n) zu deinem Lock hinzugefügt: " + detail + "." : me.getName() + " hat Karten zu deinem Lock hinzugefügt."; - MessageEntity msg = new MessageEntity(); - msg.setMessageId(UUID.randomUUID()); - msg.setSenderId(myId); - msg.setReceiverId(l.getLockee()); - msg.setText(msgText); - msg.setSentAt(LocalDateTime.now()); - msg.setSystemMessage(true); - messageRepository.save(msg); + sendMessage(myId, l.getLockee(), msgText, "/activelock.html?lockId=" + lockId, + de.oaa.xxx.social.entity.MessageCause.GAME_STATE); return ResponseEntity.noContent().build(); } @@ -1189,14 +1181,8 @@ public class CardLockController { ? me.getName() + " hat " + removed.size() + " Karte(n) aus deinem Lock entfernt: " + detail + "." : me.getName() + " hat Karten aus deinem Lock entfernt."; - MessageEntity msg = new MessageEntity(); - msg.setMessageId(UUID.randomUUID()); - msg.setSenderId(myId); - msg.setReceiverId(l.getLockee()); - msg.setText(msgText); - msg.setSentAt(LocalDateTime.now()); - msg.setSystemMessage(true); - messageRepository.save(msg); + sendMessage(myId, l.getLockee(), msgText, "/activelock.html?lockId=" + lockId, + de.oaa.xxx.social.entity.MessageCause.GAME_STATE); return ResponseEntity.noContent().build(); } @@ -1221,19 +1207,8 @@ public class CardLockController { } } - private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl) { - if (senderId == null || receiverId == null) return; - MessageEntity msg = new MessageEntity(); - msg.setMessageId(UUID.randomUUID()); - msg.setSenderId(senderId); - msg.setReceiverId(receiverId); - msg.setText(text); - msg.setSentAt(LocalDateTime.now()); - msg.setSystemMessage(true); - if (targetUrl != null) msg.setTargetUrl(targetUrl); - messageRepository.save(msg); - long unread = messageRepository.countByReceiverIdAndSystemMessageAndReadAtIsNull(receiverId, true); - sseService.push(receiverId, "NOTIFICATION", java.util.Map.of("unreadCount", unread, "text", text)); + private void sendMessage(UUID senderId, UUID receiverId, String text, String targetUrl, de.oaa.xxx.social.entity.MessageCause cause) { + systemMessageService.send(senderId, receiverId, text, targetUrl, cause); } @GetMapping("/cardlock/unlock-history") @@ -1305,7 +1280,7 @@ public class CardLockController { sendMessage(me.getUserId(), l.getLockee(), me.getName() + " hat dir eine Aufgabe gestellt. Du hast " + req.acceptDeadlineMinutes() + " Minuten, um sie anzunehmen.", - "/activelock.html?lockId=" + lockId); + "/activelock.html?lockId=" + lockId, de.oaa.xxx.social.entity.MessageCause.GAME_STATE); return ResponseEntity.noContent().build(); } @@ -1365,7 +1340,7 @@ public class CardLockController { assignedTaskRepository.save(task); cardlockRepository.save(l); - sendMessage(myId, l.getKeyholder(), meOpt.get().getName() + " hat die gestellte Aufgabe angenommen.", "/keyholder.html"); + sendMessage(myId, l.getKeyholder(), meOpt.get().getName() + " hat die gestellte Aufgabe angenommen.", "/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE); return ResponseEntity.noContent().build(); } @@ -1397,7 +1372,7 @@ public class CardLockController { assignedTaskRepository.save(task); cardlockRepository.save(l); - sendMessage(myId, l.getKeyholder(), meOpt.get().getName() + " hat die gestellte Aufgabe abgelehnt. Die Strafe wurde angewendet.", "/keyholder.html"); + sendMessage(myId, l.getKeyholder(), meOpt.get().getName() + " hat die gestellte Aufgabe abgelehnt. Die Strafe wurde angewendet.", "/keyholder.html", de.oaa.xxx.social.entity.MessageCause.GAME_STATE); return ResponseEntity.noContent().build(); } @@ -1465,7 +1440,7 @@ public class CardLockController { until.toLocalDate().format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy")) + " " + until.toLocalTime().format(java.time.format.DateTimeFormatter.ofPattern("HH:mm")) + " Uhr eingefroren.", - "/activelock.html?lockId=" + lockId); + "/activelock.html?lockId=" + lockId, de.oaa.xxx.social.entity.MessageCause.GAME_STATE); return ResponseEntity.noContent().build(); } @@ -1490,7 +1465,7 @@ public class CardLockController { cardlockRepository.save(l); sendMessage(myId, l.getLockee(), me.getName() + " hat dein Lock wieder entfroren.", - "/activelock.html?lockId=" + lockId); + "/activelock.html?lockId=" + lockId, de.oaa.xxx.social.entity.MessageCause.GAME_STATE); return ResponseEntity.noContent().build(); } @@ -1512,7 +1487,7 @@ public class CardLockController { sendMessage(myId, l.getLockee(), "Dein Keyholder hat das Lock freigeschaltet. Du erhältst beim nächsten Laden deinen Entsperrcode.", - "/activelock.html?lockId=" + lockId); + "/activelock.html?lockId=" + lockId, de.oaa.xxx.social.entity.MessageCause.GAME_STATE); return ResponseEntity.noContent().build(); } @@ -1542,7 +1517,7 @@ public class CardLockController { // Keyholderin benachrichtigen sendMessage(myId, l.getKeyholder(), "⚠️ NOTFALL: " + me.getName() + " bittet dringend um Freigabe des Locks. Bitte reagiere innerhalb einer Stunde, sonst öffnet sich das Lock automatisch.", - "/keyholder.html"); + "/keyholder.html", de.oaa.xxx.social.entity.MessageCause.EMERGENCY); } cardlockRepository.save(l); return ResponseEntity.noContent().build(); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockService.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockService.java index 39b49f7..9ab95fb 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockService.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/CardLockService.java @@ -11,10 +11,9 @@ import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import de.oaa.xxx.games.chastity.LockType; import de.oaa.xxx.games.chastity.ProcessLock; -import de.oaa.xxx.games.chastity.history.LockHistoryEntity; -import de.oaa.xxx.games.chastity.history.LockHistoryRepository; +import de.oaa.xxx.games.history.GameHistoryEntity; +import de.oaa.xxx.games.history.GameHistoryRepository; import de.oaa.xxx.games.chastity.tasks.Task; import de.oaa.xxx.games.chastity.verification.VerificationEntity; import de.oaa.xxx.games.chastity.verification.VerificationRepository; @@ -29,15 +28,15 @@ public class CardLockService extends ProcessLock { private VerificationRepository verificationRepository; private VerificationVoteRepository verificationVoteRepository; private CardLockRepository cardLockRepository; - private LockHistoryRepository lockHistoryRepository; + private GameHistoryRepository gameHistoryRepository; private UserRepository userRepository; - public CardLockService(CardLockEntity lock, VerificationRepository verificationRepository, VerificationVoteRepository verificationVoteRepository, CardLockRepository cardLockRepository, LockHistoryRepository lockHistoryRepository, UserRepository userRepository) { + public CardLockService(CardLockEntity lock, VerificationRepository verificationRepository, VerificationVoteRepository verificationVoteRepository, CardLockRepository cardLockRepository, GameHistoryRepository gameHistoryRepository, UserRepository userRepository) { this.lock = lock; this.verificationRepository = verificationRepository; this.verificationVoteRepository = verificationVoteRepository; this.cardLockRepository = cardLockRepository; - this.lockHistoryRepository = lockHistoryRepository; + this.gameHistoryRepository = gameHistoryRepository; this.userRepository = userRepository; } @@ -101,28 +100,30 @@ public class CardLockService extends ProcessLock { public void unlock(String unlockCode) { this.lock.setUnlockTime(LocalDateTime.now()); - // Self-Lock oder automatische Entsperrung ohne Keyholder-Zustimmung → ungültig - boolean valid = lock.getKeyholder() != null && !lock.isEmergencyAutoUnlocked(); - if (!this.lock.isTestLock()) { - if (Duration.between(lock.getStartTime(), lock.getUnlockTime()).toHours() > 24) { - Set verifications = verificationRepository.findByLockId(this.lock.getLockId()).stream() - .filter(verification -> isValid(verification)) - .map(verification -> verification.getVerificationTime().toLocalDate()) - .collect(Collectors.toSet()); - - LocalDate current = this.lock.getStartTime().toLocalDate(); - LocalDate last = this.lock.getUnlockTime().toLocalDate().minusDays(1); - - while (!current.isAfter(last)) { - if (!verifications.contains(current)) { - valid = false; - break; - } - current = current.plusDays(1); + boolean valid = true; + if (lock.isEmergencyAutoUnlocked()) { + valid = false; + LOGGER.debug("Lock invalid - Emergency Auto-Unlock (1h timer)"); + } + if (lock.isTestLock()) { + valid = false; + } else if (Duration.between(lock.getStartTime(), lock.getUnlockTime()).toHours() > 24) { + Set verifications = verificationRepository.findByLockId(this.lock.getLockId()).stream() + .filter(verification -> isValid(verification)) + .map(verification -> verification.getVerificationTime().toLocalDate()) + .collect(Collectors.toSet()); + + LocalDate current = this.lock.getStartTime().toLocalDate(); + LocalDate last = this.lock.getUnlockTime().toLocalDate().minusDays(1); + + while (!current.isAfter(last)) { + if (!verifications.contains(current)) { + valid = false; + LOGGER.debug("Lock invalid - no daily verification on %s", current.toString()); + break; } + current = current.plusDays(1); } - - } lock.setUnlockTime(LocalDateTime.now()); @@ -132,31 +133,18 @@ public class CardLockService extends ProcessLock { if (valid) { long durationMinutes = Duration.between(lock.getStartTime(), lock.getUnlockTime()).toMinutes(); - // Eintrag für den Lockee - LockHistoryEntity lockeeEntry = new LockHistoryEntity(); - lockeeEntry.setUserId(lock.getLockee()); - lockeeEntry.setLockedBy(lock.getKeyholder()); - lockeeEntry.setLockName(lock.getName()); - lockeeEntry.setStartTime(lock.getStartTime()); - lockeeEntry.setEndTime(lock.getUnlockTime()); - lockeeEntry.setType(LockType.CARD); - lockeeEntry.setDurationMinutes(durationMinutes); - lockeeEntry.setRole("LOCKEE"); - lockHistoryRepository.save(lockeeEntry); - - // Eintrag für die Keyholderin + // Gemeinsamer History-Eintrag mit Teilnehmerliste + GameHistoryEntity entry = new GameHistoryEntity(); + entry.setGameType(de.oaa.xxx.games.history.GameType.CARDLOCK); + entry.setGameName(lock.getName()); + entry.setStartTime(lock.getStartTime()); + entry.setEndTime(lock.getUnlockTime()); + entry.setDurationMinutes(durationMinutes); + entry.addParticipant(lock.getLockee(), de.oaa.xxx.games.history.GameRole.LOCKEE); if (lock.getKeyholder() != null) { - LockHistoryEntity khEntry = new LockHistoryEntity(); - khEntry.setUserId(lock.getKeyholder()); - khEntry.setLockedBy(lock.getLockee()); - khEntry.setLockName(lock.getName()); - khEntry.setStartTime(lock.getStartTime()); - khEntry.setEndTime(lock.getUnlockTime()); - khEntry.setType(LockType.CARD); - khEntry.setDurationMinutes(durationMinutes); - khEntry.setRole("KEYHOLDER"); - lockHistoryRepository.save(khEntry); + entry.addParticipant(lock.getKeyholder(), de.oaa.xxx.games.history.GameRole.KEYHOLDER); } + gameHistoryRepository.save(entry); int minutes = (int) durationMinutes; userRepository.findById(lock.getLockee()).ifPresent(u -> { diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/TaskCardController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/TaskCardController.java index aada089..0bf9ecb 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/TaskCardController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/TaskCardController.java @@ -3,10 +3,8 @@ package de.oaa.xxx.games.chastity.cardlock; import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity; import de.oaa.xxx.games.chastity.tasks.AssignedTaskRepository; import de.oaa.xxx.games.chastity.tasks.Task; -import de.oaa.xxx.social.SseService; +import de.oaa.xxx.social.SystemMessageService; import de.oaa.xxx.user.UserRepository; -import de.oaa.xxx.social.entity.MessageEntity; -import de.oaa.xxx.social.repository.MessageRepository; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; @@ -25,8 +23,7 @@ public class TaskCardController { private final CommunityTaskVoteRepository communityTaskVoteRepository; private final CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository; private final AssignedTaskRepository assignedTaskRepository; - private final MessageRepository messageRepository; - private final SseService sseService; + private final SystemMessageService systemMessageService; public TaskCardController(CardlockRepository cardlockRepository, UserRepository userRepository, @@ -34,16 +31,14 @@ public class TaskCardController { CommunityTaskVoteRepository communityTaskVoteRepository, CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository, AssignedTaskRepository assignedTaskRepository, - MessageRepository messageRepository, - SseService sseService) { + SystemMessageService systemMessageService) { this.cardlockRepository = cardlockRepository; this.userRepository = userRepository; this.keyholderTaskChoiceRepository = keyholderTaskChoiceRepository; this.communityTaskVoteRepository = communityTaskVoteRepository; this.communityTaskVoteEntryRepository = communityTaskVoteEntryRepository; this.assignedTaskRepository = assignedTaskRepository; - this.messageRepository = messageRepository; - this.sseService = sseService; + this.systemMessageService = systemMessageService; } // ── Keyholder: ausstehende Aufgaben-Karten-Entscheidungen ───────────────── @@ -240,16 +235,6 @@ public class TaskCardController { } private void sendMessage(UUID fromId, UUID toId, String text, String targetUrl) { - if (toId == null) return; - MessageEntity msg = new MessageEntity(); - msg.setMessageId(java.util.UUID.randomUUID()); - msg.setSenderId(fromId); - msg.setReceiverId(toId); - msg.setText(text); - msg.setSystemMessage(true); - msg.setTargetUrl(targetUrl); - msg.setSentAt(java.time.LocalDateTime.now()); - messageRepository.save(msg); - sseService.push(toId, "notification", Map.of("text", text)); + systemMessageService.send(fromId, toId, text, targetUrl, de.oaa.xxx.social.entity.MessageCause.GAME_STATE); } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/TaskVoteScheduler.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/TaskVoteScheduler.java index d9faf5b..63cead2 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/TaskVoteScheduler.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/cardlock/TaskVoteScheduler.java @@ -4,7 +4,6 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.UUID; @@ -17,9 +16,7 @@ import org.springframework.transaction.annotation.Transactional; import de.oaa.xxx.games.chastity.tasks.AssignedTaskEntity; import de.oaa.xxx.games.chastity.tasks.AssignedTaskRepository; import de.oaa.xxx.games.chastity.tasks.Task; -import de.oaa.xxx.social.SseService; -import de.oaa.xxx.social.entity.MessageEntity; -import de.oaa.xxx.social.repository.MessageRepository; +import de.oaa.xxx.social.SystemMessageService; @Component public class TaskVoteScheduler { @@ -30,21 +27,18 @@ public class TaskVoteScheduler { private final CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository; private final CardlockRepository cardlockRepository; private final AssignedTaskRepository assignedTaskRepository; - private final MessageRepository messageRepository; - private final SseService sseService; + private final SystemMessageService systemMessageService; public TaskVoteScheduler(CommunityTaskVoteRepository communityTaskVoteRepository, CommunityTaskVoteEntryRepository communityTaskVoteEntryRepository, CardlockRepository cardlockRepository, AssignedTaskRepository assignedTaskRepository, - MessageRepository messageRepository, - SseService sseService) { + SystemMessageService systemMessageService) { this.communityTaskVoteRepository = communityTaskVoteRepository; this.communityTaskVoteEntryRepository = communityTaskVoteEntryRepository; this.cardlockRepository = cardlockRepository; this.assignedTaskRepository = assignedTaskRepository; - this.messageRepository = messageRepository; - this.sseService = sseService; + this.systemMessageService = systemMessageService; } @Scheduled(fixedDelay = 60_000) @@ -117,16 +111,6 @@ public class TaskVoteScheduler { } private void sendMessage(UUID toId, String text, String targetUrl) { - if (toId == null) return; - MessageEntity msg = new MessageEntity(); - msg.setMessageId(UUID.randomUUID()); - msg.setSenderId(toId); // System-Nachricht, kein echter Sender - msg.setReceiverId(toId); - msg.setText(text); - msg.setSystemMessage(true); - msg.setTargetUrl(targetUrl); - msg.setSentAt(LocalDateTime.now()); - messageRepository.save(msg); - sseService.push(toId, "notification", Map.of("text", text)); + systemMessageService.send(toId, toId, text, targetUrl, de.oaa.xxx.social.entity.MessageCause.GAME_STATE); } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryController.java deleted file mode 100644 index 0d3e6e4..0000000 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryController.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.oaa.xxx.games.chastity.history; - -import de.oaa.xxx.user.UserRepository; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.security.Principal; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -@RestController -@RequestMapping("/lockhistory") -public class LockHistoryController { - - private final UserRepository userRepository; - private final LockHistoryRepository lockHistoryRepository; - - public LockHistoryController(UserRepository userRepository, LockHistoryRepository lockHistoryRepository) { - this.userRepository = userRepository; - this.lockHistoryRepository = lockHistoryRepository; - } - - @GetMapping - public ResponseEntity>> get(@RequestParam UUID userId, Principal principal) { - var meOpt = userRepository.findByEmail(principal.getName()); - if (meOpt.isEmpty()) return ResponseEntity.status(401).build(); - - var result = lockHistoryRepository.findByUserIdOrderByEndTimeDesc(userId).stream() - .map(e -> { - Map item = new LinkedHashMap<>(); - item.put("role", e.getRole()); - item.put("lockName", e.getLockName() != null ? e.getLockName() : ""); - item.put("startTime", e.getStartTime().toString()); - item.put("unlockTime", e.getEndTime().toString()); - item.put("durationMinutes", e.getDurationMinutes()); - if (e.getLockedBy() != null) { - userRepository.findById(e.getLockedBy()).ifPresent(u -> { - if ("LOCKEE".equals(e.getRole())) { - item.put("keyholderName", u.getName()); - } else { - item.put("lockeeName", u.getName()); - } - if (u.getProfilePicture() != null) { - item.put("partnerPic", u.getProfilePicture()); - } - }); - } - return item; - }).toList(); - return ResponseEntity.ok(result); - } -} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryDTO.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryDTO.java deleted file mode 100644 index b2fb49b..0000000 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryDTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package de.oaa.xxx.games.chastity.history; - -import java.time.LocalDateTime; -import java.util.UUID; - -import de.oaa.xxx.games.chastity.LockType; - -public record LockHistoryDTO (UUID historyId, UUID userId, LocalDateTime startTime, LocalDateTime endTime, LockType type, UUID lockedBy, String lockName, long durationMinutes, String role) { - -} - diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryEntity.java deleted file mode 100644 index a90b6b1..0000000 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryEntity.java +++ /dev/null @@ -1,53 +0,0 @@ -package de.oaa.xxx.games.chastity.history; - -import java.time.LocalDateTime; -import java.util.UUID; - -import de.oaa.xxx.games.chastity.LockType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@Entity -@Table(name = "lock_history") -public class LockHistoryEntity { - - @Id - @GeneratedValue(strategy = GenerationType.UUID) - @Column - private UUID historyId; - @Column(nullable = false) - private UUID userId; - @Column(nullable = false) - private LocalDateTime startTime; - @Column(nullable = false) - private LocalDateTime endTime; - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private LockType type; - @Column - private UUID lockedBy; - - @Column - private String lockName; - - @Column(nullable = false, columnDefinition = "BIGINT DEFAULT 0") - private long durationMinutes; - - // LOCKEE oder KEYHOLDER - @Column(nullable = false, length = 20) - private String role; - - public LockHistoryDTO toLockHistory() { - return new LockHistoryDTO(historyId, userId, startTime, endTime, type, lockedBy, lockName, durationMinutes, role); - } -} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryRepository.java deleted file mode 100644 index 4b35ab8..0000000 --- a/xxxthegame/src/main/java/de/oaa/xxx/games/chastity/history/LockHistoryRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.oaa.xxx.games.chastity.history; - -import java.util.List; -import java.util.UUID; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface LockHistoryRepository extends JpaRepository { - - List findByUserIdOrderByEndTimeDesc(UUID userId); - -} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryController.java b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryController.java new file mode 100644 index 0000000..8f39527 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryController.java @@ -0,0 +1,82 @@ +package de.oaa.xxx.games.history; + +import de.oaa.xxx.user.UserRepository; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@RestController +@RequestMapping("/gamehistory") +public class GameHistoryController { + + private final UserRepository userRepository; + private final GameHistoryRepository gameHistoryRepository; + + public GameHistoryController(UserRepository userRepository, GameHistoryRepository gameHistoryRepository) { + this.userRepository = userRepository; + this.gameHistoryRepository = gameHistoryRepository; + } + + @GetMapping + @Transactional(readOnly = true) + public ResponseEntity>> get(@RequestParam UUID userId, Principal principal) { + var meOpt = userRepository.findByEmail(principal.getName()); + if (meOpt.isEmpty()) return ResponseEntity.status(401).build(); + + var result = gameHistoryRepository.findByParticipantUserId(userId).stream() + .map(e -> { + Map item = new LinkedHashMap<>(); + item.put("historyId", e.getHistoryId()); + item.put("gameType", e.getGameType()); + item.put("gameName", e.getGameName() != null ? e.getGameName() : ""); + item.put("lockName", e.getGameName() != null ? e.getGameName() : ""); + item.put("startTime", e.getStartTime().toString()); + item.put("unlockTime", e.getEndTime().toString()); + item.put("durationMinutes", e.getDurationMinutes()); + + List> participants = e.getParticipants().stream() + .map(p -> { + Map pm = new LinkedHashMap<>(); + pm.put("userId", p.getUserId()); + pm.put("role", p.getRole()); + userRepository.findById(p.getUserId()).ifPresent(u -> { + pm.put("name", u.getName()); + pm.put("picture", u.getProfilePicture()); + }); + return pm; + }) + .toList(); + item.put("participants", participants); + + // Abwärtskompatible Felder für benutzer.html (wird später angepasst) + e.getParticipants().stream() + .filter(p -> p.getUserId().equals(userId)) + .findFirst() + .ifPresent(own -> item.put("role", own.getRole().name())); + + e.getParticipants().stream() + .filter(p -> !p.getUserId().equals(userId)) + .findFirst() + .ifPresent(partner -> userRepository.findById(partner.getUserId()).ifPresent(u -> { + if (GameRole.LOCKEE == partner.getRole()) { + item.put("lockeeName", u.getName()); + } else if (GameRole.KEYHOLDER == partner.getRole()) { + item.put("keyholderName", u.getName()); + } + item.put("partnerPic", u.getProfilePicture()); + })); + + return item; + }).toList(); + return ResponseEntity.ok(result); + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryDTO.java b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryDTO.java new file mode 100644 index 0000000..2849c6b --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryDTO.java @@ -0,0 +1,17 @@ +package de.oaa.xxx.games.history; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +public record GameHistoryDTO( + UUID historyId, + GameType gameType, + LocalDateTime startTime, + LocalDateTime endTime, + String gameName, + long durationMinutes, + List participants +) { + public record ParticipantDTO(UUID userId, GameRole role) {} +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryEntity.java new file mode 100644 index 0000000..f8f57f5 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryEntity.java @@ -0,0 +1,49 @@ +package de.oaa.xxx.games.history; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Getter +@Setter +@Entity +@Table(name = "game_history") +public class GameHistoryEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column + private UUID historyId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private GameType gameType; + + @Column(nullable = false) + private LocalDateTime startTime; + + @Column(nullable = false) + private LocalDateTime endTime; + + @Column + private String gameName; + + @Column(nullable = false, columnDefinition = "BIGINT DEFAULT 0") + private long durationMinutes; + + @OneToMany(mappedBy = "history", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private List participants = new ArrayList<>(); + + public void addParticipant(UUID userId, GameRole role) { + GameHistoryParticipantEntity p = new GameHistoryParticipantEntity(); + p.setUserId(userId); + p.setRole(role); + p.setHistory(this); + participants.add(p); + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryParticipantEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryParticipantEntity.java new file mode 100644 index 0000000..a5ad051 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryParticipantEntity.java @@ -0,0 +1,30 @@ +package de.oaa.xxx.games.history; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +@Entity +@Table(name = "game_history_participant") +public class GameHistoryParticipantEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column + private UUID participantId; + + @Column(nullable = false) + private UUID userId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private GameRole role; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "history_id", nullable = false) + private GameHistoryEntity history; +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryParticipantRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryParticipantRepository.java new file mode 100644 index 0000000..b64881f --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryParticipantRepository.java @@ -0,0 +1,8 @@ +package de.oaa.xxx.games.history; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface GameHistoryParticipantRepository extends JpaRepository { +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryRepository.java new file mode 100644 index 0000000..d4a81fc --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameHistoryRepository.java @@ -0,0 +1,17 @@ +package de.oaa.xxx.games.history; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.UUID; + +public interface GameHistoryRepository extends JpaRepository { + + @Query("SELECT DISTINCT h FROM GameHistoryEntity h JOIN h.participants p WHERE p.userId = :userId ORDER BY h.endTime DESC") + List findByParticipantUserId(@Param("userId") UUID userId); + + @Query("SELECT DISTINCT h FROM GameHistoryEntity h JOIN h.participants p WHERE p.userId = :userId AND h.gameType = :gameType ORDER BY h.endTime DESC") + List findByParticipantUserIdAndGameType(@Param("userId") UUID userId, @Param("gameType") GameType gameType); +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameRole.java b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameRole.java new file mode 100644 index 0000000..f7e5562 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameRole.java @@ -0,0 +1,7 @@ +package de.oaa.xxx.games.history; + +public enum GameRole { + LOCKEE, + KEYHOLDER, + PLAYER +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameType.java b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameType.java new file mode 100644 index 0000000..04d1a86 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/games/history/GameType.java @@ -0,0 +1,8 @@ +package de.oaa.xxx.games.history; + +public enum GameType { + CARDLOCK, + TIMELOCK, + BDSM, + VANILLA +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/mail/MailTemplateService.java b/xxxthegame/src/main/java/de/oaa/xxx/mail/MailTemplateService.java index e4b8692..ec681d2 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/mail/MailTemplateService.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/mail/MailTemplateService.java @@ -165,6 +165,56 @@ public class MailTemplateService { ); } + public String buildNotificationMail(String name, String text, String targetUrl, String baseUrl) { + String actionButton = targetUrl != null + ? """ + + """.formatted(baseUrl, targetUrl, colorPrimary) + : "
"; + + String settingsUrl = baseUrl + "/einstellungen.html#sec-benachrichtigungen"; + + return """ + + + +
+ +

XXX The Game

+ +

Hallo %s,

+

%s

+ + %s + +
+ +

+ Du erhältst diese E-Mail, weil du E-Mail-Benachrichtigungen für diese Kategorie aktiviert hast. + Du kannst deine Einstellungen jederzeit unter + Einstellungen → Benachrichtigungen anpassen. +

+
+ + + """.formatted( + colorBg, colorText, + colorCard, colorSecondary, + colorPrimary, + colorText, name, + colorText, text, + actionButton, + colorSecondary, + colorMuted, settingsUrl, colorPrimary + ); + } + public String buildActivationMail(String name, String activationLink, String activatePageUrl, String uuid) { return """ diff --git a/xxxthegame/src/main/java/de/oaa/xxx/registration/ActivationController.java b/xxxthegame/src/main/java/de/oaa/xxx/registration/ActivationController.java index 16382f2..fc3d067 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/registration/ActivationController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/registration/ActivationController.java @@ -9,7 +9,6 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import de.oaa.xxx.user.Registration; import de.oaa.xxx.user.UserController; @@ -29,12 +28,7 @@ public class ActivationController { public ResponseEntity activate(@PathVariable String uuid) { RegistrationEntity registration = registrationRepository.findById(UUID.fromString(uuid)).orElse(null); if (registration != null && !Boolean.TRUE.equals(registration.getActivated())) { - Registration reg = new Registration(); - reg.setEmail(registration.getEmail()); - reg.setName(registration.getName()); - reg.setPasswordHash(registration.getPassword()); - - ResponseEntity response = userController.userAnlegen(reg); + ResponseEntity response = userController.userAnlegen(registration.toRegistration()); if (response.getStatusCode().is2xxSuccessful()) { registration.setActivated(Boolean.TRUE); registrationRepository.save(registration); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/registration/Registration.java b/xxxthegame/src/main/java/de/oaa/xxx/registration/Registration.java index 3bc5227..182b551 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/registration/Registration.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/registration/Registration.java @@ -3,6 +3,7 @@ package de.oaa.xxx.registration; import lombok.Getter; import lombok.Setter; +import java.time.LocalDate; import java.util.UUID; @Getter @@ -13,6 +14,7 @@ public class Registration { private String name; private String email; private String passwordHash; + private LocalDate geburtsdatum; @Override public String toString() { diff --git a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationController.java b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationController.java index c2c0f0b..000efe3 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationController.java @@ -14,6 +14,9 @@ import de.oaa.xxx.mail.MailService; import de.oaa.xxx.mail.MailTemplateService; import de.oaa.xxx.user.UserRepository; +import java.time.LocalDate; +import java.time.Period; + @RestController @RequestMapping("/registration") public class RegistrationController { @@ -39,6 +42,11 @@ public class RegistrationController { @PostMapping public ResponseEntity create(@RequestBody Registration registration) { LOGGER.info("POST {}: {}", getClass().getName(), registration); + if (registration.getGeburtsdatum() == null + || Period.between(registration.getGeburtsdatum(), LocalDate.now()).getYears() < 18) { + LOGGER.warn("Registrierung abgelehnt – Mindestalter nicht erreicht"); + return ResponseEntity.status(422).build(); + } if (registrationRepository.findByEmail(registration.getEmail()).isPresent() || userRepository.findByEmail(registration.getEmail()).isPresent()) { LOGGER.warn("User mit E-Mail {} bereits vorhanden", registration.getEmail()); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationEntity.java index b15496c..a943ba2 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/registration/RegistrationEntity.java @@ -7,6 +7,7 @@ import jakarta.persistence.Table; import lombok.Getter; import lombok.Setter; +import java.time.LocalDate; import java.util.UUID; @Getter @@ -26,6 +27,8 @@ public class RegistrationEntity { private String password; @Column private Boolean activated; + @Column + private LocalDate geburtsdatum; @Override public String toString() { @@ -38,6 +41,7 @@ public class RegistrationEntity { registration.setEmail(email); registration.setName(name); registration.setPasswordHash(password); + registration.setGeburtsdatum(geburtsdatum); return registration; } @@ -48,6 +52,7 @@ public class RegistrationEntity { entity.setActivated(Boolean.FALSE); entity.setName(registration.getName()); entity.setPassword(registration.getPasswordHash()); + entity.setGeburtsdatum(registration.getGeburtsdatum()); return entity; } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/session/repository/SessionRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/session/repository/SessionRepository.java deleted file mode 100644 index fb8fdd6..0000000 --- a/xxxthegame/src/main/java/de/oaa/xxx/session/repository/SessionRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.oaa.xxx.session.repository; - -import de.oaa.xxx.session.entity.SessionEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; -import java.util.UUID; - -public interface SessionRepository extends JpaRepository { - - Optional findByUserId(UUID userId); -} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/SocialController.java b/xxxthegame/src/main/java/de/oaa/xxx/social/SocialController.java index 9c71992..8c65bb0 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/social/SocialController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/social/SocialController.java @@ -6,6 +6,7 @@ import de.oaa.xxx.social.dto.MessageDto; import de.oaa.xxx.social.dto.UserProfile; import de.oaa.xxx.social.entity.FriendshipEntity; import de.oaa.xxx.social.entity.FriendshipEntity.Status; +import de.oaa.xxx.social.entity.MessageCause; import de.oaa.xxx.social.entity.MessageEntity; import de.oaa.xxx.social.repository.FriendshipRepository; import de.oaa.xxx.social.repository.MessageRepository; @@ -31,15 +32,18 @@ public class SocialController { private final FriendshipRepository friendshipRepository; private final MessageRepository messageRepository; private final SseService sseService; + private final SystemMessageService systemMessageService; public SocialController(UserRepository userRepository, FriendshipRepository friendshipRepository, MessageRepository messageRepository, - SseService sseService) { + SseService sseService, + SystemMessageService systemMessageService) { this.userRepository = userRepository; this.friendshipRepository = friendshipRepository; this.messageRepository = messageRepository; this.sseService = sseService; + this.systemMessageService = systemMessageService; } record FriendRequestBody(UUID receiverId) {} @@ -94,6 +98,13 @@ public class SocialController { f.setCreatedAt(LocalDateTime.now()); friendshipRepository.save(f); LOGGER.info("User {} hat Freundschaftsanfrage an User {} gesendet", myId, body.receiverId()); + + String senderName = meOpt.get().getName(); + systemMessageService.send(myId, body.receiverId(), + senderName + " hat dir eine Freundschaftsanfrage gesendet.", + "/benutzer.html?userId=" + myId, + MessageCause.FRIENDREQUEST); + return ResponseEntity.status(201).build(); } @@ -299,22 +310,52 @@ public class SocialController { // ── Helpers ── private UserProfile toUserProfileWithStatus(UserEntity user, UUID myId) { + boolean isOwn = user.getUserId().equals(myId); String status = "NONE"; - var existing = friendshipRepository.findExisting(myId, user.getUserId()); - if (existing.isPresent()) { - FriendshipEntity f = existing.get(); - if (f.getStatus() == Status.ACCEPTED) { - status = "FRIEND"; - } else if (f.getSenderId().equals(myId)) { - status = "PENDING_SENT"; - } else { - status = "PENDING_RECEIVED"; + if (!isOwn) { + var existing = friendshipRepository.findExisting(myId, user.getUserId()); + if (existing.isPresent()) { + FriendshipEntity f = existing.get(); + if (f.getStatus() == Status.ACCEPTED) { + status = "FRIEND"; + } else if (f.getSenderId().equals(myId)) { + status = "PENDING_SENT"; + } else { + status = "PENDING_RECEIVED"; + } } } - return new UserProfile(user.getUserId(), user.getName(), user.getProfilePicture(), user.getProfilePictureHq(), - status, user.getAlter(), user.getGroesse(), user.getGewicht(), - user.getGeschlecht(), user.getNeigung(), user.getBeziehungsstatus(), user.getBeschreibung(), - user.getLockeeXp(), user.getKeyholderXp()); + boolean isFriend = isOwn || "FRIEND".equals(status); + + // Grunddaten nur zurückgeben wenn berechtigt + de.oaa.xxx.user.Sichtbarkeit svGd = user.getSichtbarkeitGrunddaten(); + boolean showGrunddaten = isOwn || svGd == de.oaa.xxx.user.Sichtbarkeit.ALLE + || (svGd == de.oaa.xxx.user.Sichtbarkeit.NUR_FREUNDE && isFriend); + + // XP nur zurückgeben wenn berechtigt + de.oaa.xxx.user.Sichtbarkeit svXp = user.getSichtbarkeitXp(); + boolean showXp = isOwn || svXp == de.oaa.xxx.user.Sichtbarkeit.ALLE + || (svXp == de.oaa.xxx.user.Sichtbarkeit.NUR_FREUNDE && isFriend); + + return new UserProfile( + user.getUserId(), user.getName(), user.getProfilePicture(), user.getProfilePictureHq(), + status, + showGrunddaten ? user.getAlter() : null, + showGrunddaten ? user.getGroesse() : null, + showGrunddaten ? user.getGewicht() : null, + showGrunddaten ? user.getGeschlecht() : null, + showGrunddaten ? user.getNeigung() : null, + showGrunddaten ? user.getBeziehungsstatus() : null, + showGrunddaten ? user.getBeschreibung() : null, + showXp ? user.getLockeeXp() : 0, + showXp ? user.getKeyholderXp() : 0, + user.getSichtbarkeitGrunddaten(), + user.getSichtbarkeitGalerie(), + user.getSichtbarkeitFreunde(), + user.getSichtbarkeitFeed(), + user.getSichtbarkeitPinnwand(), + user.getSichtbarkeitXp(), + user.getSichtbarkeitLockhistorie()); } private MessageDto toMessageDto(MessageEntity m) { diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/SystemMessageService.java b/xxxthegame/src/main/java/de/oaa/xxx/social/SystemMessageService.java new file mode 100644 index 0000000..6113b0b --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/social/SystemMessageService.java @@ -0,0 +1,103 @@ +package de.oaa.xxx.social; + +import de.oaa.xxx.mail.Email; +import de.oaa.xxx.mail.MailService; +import de.oaa.xxx.mail.MailTemplateService; +import org.springframework.beans.factory.annotation.Value; +import de.oaa.xxx.social.entity.MessageCause; +import de.oaa.xxx.social.entity.MessageEntity; +import de.oaa.xxx.social.entity.NotificationPreferenceEntity; +import de.oaa.xxx.social.repository.MessageRepository; +import de.oaa.xxx.social.repository.NotificationPreferenceRepository; +import de.oaa.xxx.user.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.UUID; + +@Service +public class SystemMessageService { + + private static final Logger LOGGER = LoggerFactory.getLogger(SystemMessageService.class); + + private final MessageRepository messageRepository; + private final NotificationPreferenceRepository preferenceRepository; + private final UserRepository userRepository; + private final SseService sseService; + private final MailService mailService; + private final MailTemplateService mailTemplateService; + + @Value("${app.base-url:http://localhost:8080}") + private String baseUrl; + + public SystemMessageService(MessageRepository messageRepository, + NotificationPreferenceRepository preferenceRepository, + UserRepository userRepository, + SseService sseService, + MailService mailService, + MailTemplateService mailTemplateService) { + this.messageRepository = messageRepository; + this.preferenceRepository = preferenceRepository; + this.userRepository = userRepository; + this.sseService = sseService; + this.mailService = mailService; + this.mailTemplateService = mailTemplateService; + } + + /** + * Sendet eine Systemnachricht unter Berücksichtigung der Benachrichtigungseinstellungen des Empfängers. + */ + public void send(UUID senderId, UUID receiverId, String text, String targetUrl, MessageCause cause) { + if (senderId == null || receiverId == null) return; + + NotificationPreferenceEntity pref = preferenceRepository + .findByUserIdAndCause(receiverId, cause) + .orElseGet(() -> NotificationPreferenceEntity.defaultFor(receiverId, cause)); + + // FRIENDREQUEST ist immer in-app, unabhängig von der Einstellung + boolean sendInApp = cause == MessageCause.FRIENDREQUEST || pref.isInApp(); + + if (sendInApp) { + MessageEntity msg = new MessageEntity(); + msg.setMessageId(UUID.randomUUID()); + msg.setSenderId(senderId); + msg.setReceiverId(receiverId); + msg.setText(text); + msg.setSentAt(LocalDateTime.now()); + msg.setSystemMessage(true); + msg.setMessageCause(cause); + if (targetUrl != null) msg.setTargetUrl(targetUrl); + messageRepository.save(msg); + + long unread = messageRepository.countByReceiverIdAndSystemMessageAndReadAtIsNull(receiverId, true); + sseService.push(receiverId, "NOTIFICATION", Map.of("unreadCount", unread, "text", text)); + } + + if (pref.isEmail()) { + userRepository.findById(receiverId).ifPresent(user -> { + try { + Email email = new Email(); + email.setEmailAdresse(user.getEmail()); + email.setTitel(causeTitel(cause)); + email.setText(mailTemplateService.buildNotificationMail(user.getName(), text, targetUrl, baseUrl)); + mailService.send(email); + } catch (Exception e) { + LOGGER.error("E-Mail-Benachrichtigung fehlgeschlagen für userId={}: {}", receiverId, e.getMessage()); + } + }); + } + } + + private String causeTitel(MessageCause cause) { + return switch (cause) { + case INVITATION -> "XXX The Game – Neue Einladung"; + case GAME_STATE -> "XXX The Game – Spielstatus-Änderung"; + case EMERGENCY -> "XXX The Game – ⚠️ Notfall"; + case FRIENDREQUEST -> "XXX The Game – Neue Freundschaftsanfrage"; + }; + } + +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/dto/UserProfile.java b/xxxthegame/src/main/java/de/oaa/xxx/social/dto/UserProfile.java index 4edc020..9f22920 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/social/dto/UserProfile.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/social/dto/UserProfile.java @@ -3,6 +3,7 @@ package de.oaa.xxx.social.dto; import de.oaa.xxx.user.Beziehungsstatus; import de.oaa.xxx.user.Geschlecht; import de.oaa.xxx.user.Neigung; +import de.oaa.xxx.user.Sichtbarkeit; import java.util.UUID; @@ -20,10 +21,20 @@ public record UserProfile( Beziehungsstatus beziehungsstatus, String beschreibung, int lockeeXp, - int keyholderXp + int keyholderXp, + // Datenschutz-Einstellungen + Sichtbarkeit sichtbarkeitGrunddaten, + Sichtbarkeit sichtbarkeitGalerie, + Sichtbarkeit sichtbarkeitFreunde, + Sichtbarkeit sichtbarkeitFeed, + Sichtbarkeit sichtbarkeitPinnwand, + Sichtbarkeit sichtbarkeitXp, + Sichtbarkeit sichtbarkeitLockhistorie ) { /** Compact constructor for contexts where profile details are not needed (friend list etc.) */ public UserProfile(UUID userId, String name, String profilePicture, String profilePictureHq, String friendStatus) { - this(userId, name, profilePicture, profilePictureHq, friendStatus, null, null, null, null, null, null, null, 0, 0); + this(userId, name, profilePicture, profilePictureHq, friendStatus, + null, null, null, null, null, null, null, 0, 0, + null, null, null, null, null, null, null); } } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/entity/MessageCause.java b/xxxthegame/src/main/java/de/oaa/xxx/social/entity/MessageCause.java new file mode 100644 index 0000000..9bbbac6 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/social/entity/MessageCause.java @@ -0,0 +1,8 @@ +package de.oaa.xxx.social.entity; + +public enum MessageCause { + INVITATION, + GAME_STATE, + EMERGENCY, + FRIENDREQUEST +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/entity/MessageEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/social/entity/MessageEntity.java index cd5805d..0027ed1 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/social/entity/MessageEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/social/entity/MessageEntity.java @@ -35,6 +35,10 @@ public class MessageEntity { @Column(nullable = false) private boolean systemMessage = false; + @Enumerated(EnumType.STRING) + @Column(length = 20) + private MessageCause messageCause; + @Column(length = 500) private String targetUrl; } diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/entity/NotificationPreferenceEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/social/entity/NotificationPreferenceEntity.java new file mode 100644 index 0000000..2fdc08e --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/social/entity/NotificationPreferenceEntity.java @@ -0,0 +1,40 @@ +package de.oaa.xxx.social.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +@Entity +@Table(name = "notification_preference", uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "cause"})) +public class NotificationPreferenceEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(name = "user_id", nullable = false) + private UUID userId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private MessageCause cause; + + @Column(nullable = false) + private boolean inApp = true; + + @Column(nullable = false) + private boolean email = false; + + /** Erzeugt eine nicht persistierte Standardpräferenz für unbekannte/neue Causes. */ + public static NotificationPreferenceEntity defaultFor(UUID userId, MessageCause cause) { + NotificationPreferenceEntity p = new NotificationPreferenceEntity(); + p.setUserId(userId); + p.setCause(cause); + // inApp=true und email=false sind bereits die Java-Felddefaults + return p; + } +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/social/repository/NotificationPreferenceRepository.java b/xxxthegame/src/main/java/de/oaa/xxx/social/repository/NotificationPreferenceRepository.java new file mode 100644 index 0000000..2d2d1f9 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/social/repository/NotificationPreferenceRepository.java @@ -0,0 +1,16 @@ +package de.oaa.xxx.social.repository; + +import de.oaa.xxx.social.entity.MessageCause; +import de.oaa.xxx.social.entity.NotificationPreferenceEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface NotificationPreferenceRepository extends JpaRepository { + + List findByUserId(UUID userId); + + Optional findByUserIdAndCause(UUID userId, MessageCause cause); +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/user/Registration.java b/xxxthegame/src/main/java/de/oaa/xxx/user/Registration.java deleted file mode 100644 index dc4d5c1..0000000 --- a/xxxthegame/src/main/java/de/oaa/xxx/user/Registration.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.oaa.xxx.user; - -import lombok.Getter; -import lombok.Setter; - -import java.util.UUID; - -@Getter -@Setter -public class Registration { - - private UUID id; - private String name; - private String email; - private String passwordHash; - - @Override - public String toString() { - return "Registration [id=" + id + ", name=" + name + ", email=" + email + "]"; - } -} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/user/Sichtbarkeit.java b/xxxthegame/src/main/java/de/oaa/xxx/user/Sichtbarkeit.java new file mode 100644 index 0000000..7bfb559 --- /dev/null +++ b/xxxthegame/src/main/java/de/oaa/xxx/user/Sichtbarkeit.java @@ -0,0 +1,7 @@ +package de.oaa.xxx.user; + +public enum Sichtbarkeit { + ALLE, + NUR_FREUNDE, + NUR_ICH +} diff --git a/xxxthegame/src/main/java/de/oaa/xxx/user/User.java b/xxxthegame/src/main/java/de/oaa/xxx/user/User.java index e3c883a..5fced89 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/user/User.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/user/User.java @@ -3,6 +3,8 @@ package de.oaa.xxx.user; import lombok.Getter; import lombok.Setter; +import java.time.LocalDate; +import java.time.Period; import java.util.UUID; @Getter @@ -14,7 +16,7 @@ public class User { private String email; private String password; private String profilePicture; - private Integer alter; + private LocalDate geburtsdatum; private Integer groesse; private Integer gewicht; private Geschlecht geschlecht; @@ -22,6 +24,10 @@ public class User { private Beziehungsstatus beziehungsstatus; private String beschreibung; + public Integer getAlter() { + return geburtsdatum != null ? Period.between(geburtsdatum, LocalDate.now()).getYears() : null; + } + @Override public String toString() { return "User[userId=" + userId + ", name=" + name + ", email=" + email + "]"; diff --git a/xxxthegame/src/main/java/de/oaa/xxx/user/UserController.java b/xxxthegame/src/main/java/de/oaa/xxx/user/UserController.java index 355cd9f..8f51478 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/user/UserController.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/user/UserController.java @@ -1,8 +1,13 @@ package de.oaa.xxx.user; import java.security.Principal; +import java.time.LocalDate; +import java.time.Period; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,6 +15,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -17,6 +23,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import de.oaa.xxx.aufgaben.repository.AufgabeRepository; +import de.oaa.xxx.games.bdsm.entity.BdsmDefaultsEntity; +import de.oaa.xxx.games.bdsm.repository.BdsmDefaultsRepository; import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository; import de.oaa.xxx.aufgaben.repository.FavoritRepository; import de.oaa.xxx.aufgaben.repository.GruppenAboRepository; @@ -24,19 +32,23 @@ import de.oaa.xxx.aufgaben.repository.SperreRepository; import de.oaa.xxx.aufgaben.repository.StrafeRepository; import de.oaa.xxx.aufgaben.repository.ToyRepository; import de.oaa.xxx.emailchange.EmailChangeRepository; +import de.oaa.xxx.games.bdsm.entity.AktiveSperreEntity; +import de.oaa.xxx.games.bdsm.entity.MitspielerEntity; +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.passwordreset.PasswordResetRepository; +import de.oaa.xxx.registration.Registration; import de.oaa.xxx.registration.RegistrationRepository; -import de.oaa.xxx.session.entity.AktiveSperreEntity; -import de.oaa.xxx.session.entity.MitspielerEntity; -import de.oaa.xxx.session.repository.AktiveSperreRepository; -import de.oaa.xxx.session.repository.MitspielerRepository; -import de.oaa.xxx.session.repository.SessionRepository; -import de.oaa.xxx.social.repository.ProfileImageLikeRepository; -import de.oaa.xxx.social.repository.ProfileImageRepository; +import de.oaa.xxx.social.entity.MessageCause; +import de.oaa.xxx.social.entity.NotificationPreferenceEntity; +import de.oaa.xxx.social.repository.KommentarLikeRepository; +import de.oaa.xxx.social.repository.KommentarRepository; +import de.oaa.xxx.social.repository.NotificationPreferenceRepository; import de.oaa.xxx.social.repository.PinnwandEintragRepository; import de.oaa.xxx.social.repository.PinnwandLikeRepository; -import de.oaa.xxx.social.repository.KommentarRepository; -import de.oaa.xxx.social.repository.KommentarLikeRepository; +import de.oaa.xxx.social.repository.ProfileImageLikeRepository; +import de.oaa.xxx.social.repository.ProfileImageRepository; import jakarta.transaction.Transactional; @RestController @@ -54,7 +66,7 @@ public class UserController { private final ToyRepository toyRepository; private final FavoritRepository favoritRepository; private final GruppenAboRepository gruppenAboRepository; - private final SessionRepository sessionRepository; + private final BdsmGameRepository sessionRepository; private final AktiveSperreRepository aktiveSperreRepository; private final MitspielerRepository mitspielerRepository; private final EmailChangeRepository emailChangeRepository; @@ -65,6 +77,8 @@ public class UserController { private final PinnwandLikeRepository pinnwandLikeRepository; private final KommentarRepository kommentarRepository; private final KommentarLikeRepository kommentarLikeRepository; + private final NotificationPreferenceRepository notificationPreferenceRepository; + private final BdsmDefaultsRepository bdsmDefaultsRepository; public UserController(UserRepository userRepository, RegistrationRepository registrationRepository, @@ -75,7 +89,7 @@ public class UserController { ToyRepository toyRepository, FavoritRepository favoritRepository, GruppenAboRepository gruppenAboRepository, - SessionRepository sessionRepository, + BdsmGameRepository sessionRepository, AktiveSperreRepository aktiveSperreRepository, MitspielerRepository mitspielerRepository, EmailChangeRepository emailChangeRepository, @@ -85,7 +99,9 @@ public class UserController { PinnwandEintragRepository pinnwandEintragRepository, PinnwandLikeRepository pinnwandLikeRepository, KommentarRepository kommentarRepository, - KommentarLikeRepository kommentarLikeRepository) { + KommentarLikeRepository kommentarLikeRepository, + NotificationPreferenceRepository notificationPreferenceRepository, + BdsmDefaultsRepository bdsmDefaultsRepository) { this.userRepository = userRepository; this.registrationRepository = registrationRepository; this.aufgabenGruppeRepository = aufgabenGruppeRepository; @@ -106,12 +122,23 @@ public class UserController { this.pinnwandLikeRepository = pinnwandLikeRepository; this.kommentarRepository = kommentarRepository; this.kommentarLikeRepository = kommentarLikeRepository; + this.notificationPreferenceRepository = notificationPreferenceRepository; + this.bdsmDefaultsRepository = bdsmDefaultsRepository; } record ProfilePictureRequest(String picture, String pictureHq) {} record NameChangeRequest(String name) {} - record ProfileRequest(Integer alter, Integer groesse, Integer gewicht, + record GeburtsdatumChangeRequest(LocalDate geburtsdatum) {} + record ProfileRequest(Integer groesse, Integer gewicht, Geschlecht geschlecht, Neigung neigung, Beziehungsstatus beziehungsstatus, String beschreibung) {} + record PrivacyRequest( + Sichtbarkeit sichtbarkeitGrunddaten, + Sichtbarkeit sichtbarkeitGalerie, + Sichtbarkeit sichtbarkeitFreunde, + Sichtbarkeit sichtbarkeitFeed, + Sichtbarkeit sichtbarkeitPinnwand, + Sichtbarkeit sichtbarkeitXp, + Sichtbarkeit sichtbarkeitLockhistorie) {} @PutMapping("/me/picture") public ResponseEntity updateProfilePicture(@RequestBody ProfilePictureRequest request, Principal principal) { @@ -132,7 +159,6 @@ public class UserController { if (request.beschreibung() != null && request.beschreibung().length() > 600) { return ResponseEntity.badRequest().build(); } - user.setAlter(request.alter()); user.setGroesse(request.groesse()); user.setGewicht(request.gewicht()); user.setGeschlecht(request.geschlecht()); @@ -144,6 +170,126 @@ public class UserController { return ResponseEntity.ok().build(); } + @PutMapping("/me/privacy") + public ResponseEntity updatePrivacy(@RequestBody PrivacyRequest request, Principal principal) { + var userOpt = userRepository.findByEmail(principal.getName()); + if (userOpt.isEmpty()) return ResponseEntity.status(401).build(); + var user = userOpt.get(); + if (request.sichtbarkeitGrunddaten() != null) user.setSichtbarkeitGrunddaten(request.sichtbarkeitGrunddaten()); + if (request.sichtbarkeitGalerie() != null) user.setSichtbarkeitGalerie(request.sichtbarkeitGalerie()); + if (request.sichtbarkeitFreunde() != null) user.setSichtbarkeitFreunde(request.sichtbarkeitFreunde()); + if (request.sichtbarkeitFeed() != null) user.setSichtbarkeitFeed(request.sichtbarkeitFeed()); + if (request.sichtbarkeitPinnwand() != null) user.setSichtbarkeitPinnwand(request.sichtbarkeitPinnwand()); + if (request.sichtbarkeitXp() != null) user.setSichtbarkeitXp(request.sichtbarkeitXp()); + if (request.sichtbarkeitLockhistorie()!= null) user.setSichtbarkeitLockhistorie(request.sichtbarkeitLockhistorie()); + userRepository.save(user); + LOGGER.info("User {} hat Datenschutz-Einstellungen aktualisiert", user.getUserId()); + return ResponseEntity.ok().build(); + } + + record NotificationPreferenceRequest(boolean inApp, boolean email) {} + + @GetMapping("/me/notifications") + public ResponseEntity> getNotifications(Principal principal) { + var userOpt = userRepository.findByEmail(principal.getName()); + if (userOpt.isEmpty()) return ResponseEntity.status(401).build(); + UUID userId = userOpt.get().getUserId(); + + Map byKey = notificationPreferenceRepository.findByUserId(userId) + .stream().collect(Collectors.toMap(p -> p.getCause().name(), p -> p)); + + Map result = new LinkedHashMap<>(); + for (MessageCause cause : MessageCause.values()) { + NotificationPreferenceEntity pref = byKey.getOrDefault( + cause.name(), NotificationPreferenceEntity.defaultFor(userId, cause)); + Map entry = new LinkedHashMap<>(); + entry.put("inApp", pref.isInApp()); + entry.put("email", pref.isEmail()); + result.put(cause.name(), entry); + } + return ResponseEntity.ok(result); + } + + @PutMapping("/me/notifications") + public ResponseEntity updateNotifications(@RequestBody Map request, Principal principal) { + var userOpt = userRepository.findByEmail(principal.getName()); + if (userOpt.isEmpty()) return ResponseEntity.status(401).build(); + UUID userId = userOpt.get().getUserId(); + + for (var entry : request.entrySet()) { + MessageCause cause; + try { + cause = MessageCause.valueOf(entry.getKey()); + } catch (IllegalArgumentException e) { + continue; + } + NotificationPreferenceEntity pref = notificationPreferenceRepository + .findByUserIdAndCause(userId, cause) + .orElseGet(() -> { + NotificationPreferenceEntity n = new NotificationPreferenceEntity(); + n.setUserId(userId); + n.setCause(cause); + return n; + }); + pref.setInApp(entry.getValue().inApp()); + pref.setEmail(entry.getValue().email()); + notificationPreferenceRepository.save(pref); + } + return ResponseEntity.ok().build(); + } + + record BdsmDefaultsRequest(List spieltMit, List rollen, List werkzeuge) {} + + @GetMapping("/me/bdsm-defaults") + public ResponseEntity> getBdsmDefaults(Principal principal) { + var userOpt = userRepository.findByEmail(principal.getName()); + if (userOpt.isEmpty()) return ResponseEntity.status(401).build(); + UUID userId = userOpt.get().getUserId(); + + BdsmDefaultsEntity d = bdsmDefaultsRepository.findByUserId(userId) + .orElse(new BdsmDefaultsEntity()); + Map result = new java.util.LinkedHashMap<>(); + result.put("spieltMit", splitOrEmpty(d.getSpieltMit())); + result.put("rollen", splitOrEmpty(d.getRollen())); + result.put("werkzeuge", splitOrEmpty(d.getWerkzeuge())); + return ResponseEntity.ok(result); + } + + @PutMapping("/me/bdsm-defaults") + public ResponseEntity updateBdsmDefaults(@RequestBody BdsmDefaultsRequest request, Principal principal) { + var userOpt = userRepository.findByEmail(principal.getName()); + if (userOpt.isEmpty()) return ResponseEntity.status(401).build(); + UUID userId = userOpt.get().getUserId(); + + BdsmDefaultsEntity d = bdsmDefaultsRepository.findByUserId(userId) + .orElseGet(() -> { BdsmDefaultsEntity n = new BdsmDefaultsEntity(); n.setUserId(userId); return n; }); + d.setSpieltMit(request.spieltMit() == null ? "" : String.join(",", request.spieltMit())); + d.setRollen(request.rollen() == null ? "" : String.join(",", request.rollen())); + d.setWerkzeuge(request.werkzeuge() == null ? "" : String.join(",", request.werkzeuge())); + bdsmDefaultsRepository.save(d); + return ResponseEntity.ok().build(); + } + + private static List splitOrEmpty(String s) { + if (s == null || s.isBlank()) return List.of(); + return List.of(s.split(",")); + } + + @PutMapping("/me/geburtsdatum") + public ResponseEntity updateGeburtsdatum(@RequestBody GeburtsdatumChangeRequest request, Principal principal) { + if (request.geburtsdatum() == null + || Period.between(request.geburtsdatum(), LocalDate.now()).getYears() < 18) { + return ResponseEntity.status(422).build(); + } + var userOpt = userRepository.findByEmail(principal.getName()); + if (userOpt.isEmpty()) return ResponseEntity.status(401).build(); + var user = userOpt.get(); + user.setGeburtsdatum(request.geburtsdatum()); + userRepository.save(user); + LOGGER.info("User {} hat Geburtsdatum aktualisiert", user.getUserId()); + return ResponseEntity.ok().build(); + } + @PutMapping("/me/name") public ResponseEntity updateName(@RequestBody NameChangeRequest request, Principal principal) { String newName = request.name(); @@ -264,7 +410,14 @@ public class UserController { entity.setEmail(registration.getEmail()); entity.setName(registration.getName()); entity.setPassword(registration.getPasswordHash()); + entity.setGeburtsdatum(registration.getGeburtsdatum()); userRepository.save(entity); + + for (MessageCause cause : MessageCause.values()) { + notificationPreferenceRepository.save( + NotificationPreferenceEntity.defaultFor(entity.getUserId(), cause)); + } + return ResponseEntity.status(201).build(); } catch (Exception exception) { LOGGER.error(exception.getMessage(), exception); diff --git a/xxxthegame/src/main/java/de/oaa/xxx/user/UserEntity.java b/xxxthegame/src/main/java/de/oaa/xxx/user/UserEntity.java index c05a140..13e1c04 100644 --- a/xxxthegame/src/main/java/de/oaa/xxx/user/UserEntity.java +++ b/xxxthegame/src/main/java/de/oaa/xxx/user/UserEntity.java @@ -4,6 +4,8 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; +import java.time.LocalDate; +import java.time.Period; import java.util.UUID; @Getter @@ -28,8 +30,8 @@ public class UserEntity { @Column(columnDefinition = "MEDIUMTEXT") private String profilePictureHq; - @Column(name = "benutzer_alter") - private Integer alter; + @Column + private LocalDate geburtsdatum; @Column private Integer groesse; @@ -58,6 +60,39 @@ public class UserEntity { @Column(nullable = false, columnDefinition = "INT DEFAULT 0") private int keyholderXp; + // ── Datenschutz / Sichtbarkeit ── + @Enumerated(EnumType.STRING) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'") + private Sichtbarkeit sichtbarkeitGrunddaten = Sichtbarkeit.ALLE; + + @Enumerated(EnumType.STRING) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'") + private Sichtbarkeit sichtbarkeitGalerie = Sichtbarkeit.ALLE; + + @Enumerated(EnumType.STRING) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'") + private Sichtbarkeit sichtbarkeitFreunde = Sichtbarkeit.ALLE; + + @Enumerated(EnumType.STRING) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'") + private Sichtbarkeit sichtbarkeitFeed = Sichtbarkeit.ALLE; + + @Enumerated(EnumType.STRING) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'") + private Sichtbarkeit sichtbarkeitPinnwand = Sichtbarkeit.ALLE; + + @Enumerated(EnumType.STRING) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'") + private Sichtbarkeit sichtbarkeitXp = Sichtbarkeit.ALLE; + + @Enumerated(EnumType.STRING) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'ALLE'") + private Sichtbarkeit sichtbarkeitLockhistorie = Sichtbarkeit.ALLE; + + public Integer getAlter() { + return geburtsdatum != null ? Period.between(geburtsdatum, LocalDate.now()).getYears() : null; + } + @Override public String toString() { return "UserEntity[userId=" + userId + ", name=" + name + ", email=" + email + "]"; @@ -69,7 +104,7 @@ public class UserEntity { user.setName(name); user.setUserId(userId); user.setProfilePicture(profilePicture); - user.setAlter(alter); + user.setGeburtsdatum(geburtsdatum); user.setGroesse(groesse); user.setGewicht(gewicht); user.setGeschlecht(geschlecht); diff --git a/xxxthegame/src/main/resources/static/aufgaben.html b/xxxthegame/src/main/resources/static/aufgaben.html index f332810..9e3c898 100644 --- a/xxxthegame/src/main/resources/static/aufgaben.html +++ b/xxxthegame/src/main/resources/static/aufgaben.html @@ -1,7 +1,7 @@ - + Aufgaben – XXX The Game @@ -608,44 +608,47 @@ resetSelection(); document.getElementById('userLoading').style.display = 'block'; fetch(`/gruppe/list/user?page=${userPage}&size=${PAGE_SIZE}`) - .then(r => r.json()) + .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }) .then(data => { + console.log('[aufgaben] user gruppen:', data); userTotalPages = data.totalPages || 1; - renderGruppen('userList', data.content, 'user'); + try { renderGruppen('userList', data.content, 'user'); } catch(e) { console.error('[aufgaben] renderGruppen user Fehler:', e); throw e; } updatePaging('userPaging', 'userPrev', 'userNext', 'userPageInfo', userPage, userTotalPages); document.getElementById('userLoading').style.display = 'none'; reapplyPendingExpand(); }) - .catch(() => { document.getElementById('userLoading').textContent = 'Fehler beim Laden.'; }); + .catch(err => { console.error('[aufgaben] Fehler user gruppen:', err); document.getElementById('userLoading').textContent = 'Fehler beim Laden: ' + err.message; }); } function loadSystemGruppen() { resetSelection(); document.getElementById('systemLoading').style.display = 'block'; fetch(`/gruppe/list/system?page=${systemPage}&size=${PAGE_SIZE}`) - .then(r => r.json()) + .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }) .then(data => { + console.log('[aufgaben] system gruppen:', data); systemTotalPages = data.totalPages || 1; - renderGruppen('systemList', data.content, 'system'); + try { renderGruppen('systemList', data.content, 'system'); } catch(e) { console.error('[aufgaben] renderGruppen system Fehler:', e); throw e; } updatePaging('systemPaging', 'systemPrev', 'systemNext', 'systemPageInfo', systemPage, systemTotalPages); document.getElementById('systemLoading').style.display = 'none'; reapplyPendingExpand(); }) - .catch(() => { document.getElementById('systemLoading').textContent = 'Fehler beim Laden.'; }); + .catch(err => { console.error('[aufgaben] Fehler system gruppen:', err); document.getElementById('systemLoading').textContent = 'Fehler beim Laden: ' + err.message; }); } function loadAboGruppen() { document.getElementById('aboLoading').style.display = 'block'; fetch(`/abo/list?page=${aboPage}&size=${PAGE_SIZE}`) - .then(r => r.json()) + .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }) .then(data => { + console.log('[aufgaben] abo gruppen:', data); aboTotalPages = data.totalPages || 1; renderGruppen('aboList', data.content, 'abo'); updatePaging('aboPaging', 'aboPrev', 'aboNext', 'aboPageInfo', aboPage, aboTotalPages); document.getElementById('aboLoading').style.display = 'none'; reapplyPendingExpand(); }) - .catch(() => { document.getElementById('aboLoading').textContent = 'Fehler beim Laden.'; }); + .catch(err => { console.error('[aufgaben] Fehler abo gruppen:', err); document.getElementById('aboLoading').textContent = 'Fehler beim Laden: ' + err.message; }); } function reapplyPendingExpand() { diff --git a/xxxthegame/src/main/resources/static/bdsm-einladung.html b/xxxthegame/src/main/resources/static/bdsm-einladung.html new file mode 100644 index 0000000..6a13f43 --- /dev/null +++ b/xxxthegame/src/main/resources/static/bdsm-einladung.html @@ -0,0 +1,128 @@ + + + + + + + BDSM Game – Einladung – XXX The Game + + + + + +
+
+
Einladung wird geladen…
+ +
+
+ + + + diff --git a/xxxthegame/src/main/resources/static/sessionbdsm.html b/xxxthegame/src/main/resources/static/bdsm.html similarity index 95% rename from xxxthegame/src/main/resources/static/sessionbdsm.html rename to xxxthegame/src/main/resources/static/bdsm.html index 9ea92cc..2dea196 100644 --- a/xxxthegame/src/main/resources/static/sessionbdsm.html +++ b/xxxthegame/src/main/resources/static/bdsm.html @@ -1,14 +1,14 @@ - + BDSM Game – Neue Session – XXX The Game + + + + + +
+
+ +

BDSM Game

+

Schritt 2 von 4 – Mitspieler

+ +
+

Mitspieler

+
+ +
+ +
+
+ + +
+ +
+
+ + + + + diff --git a/xxxthegame/src/main/resources/static/sessionbdsmtasks.html b/xxxthegame/src/main/resources/static/bdsmtasks.html similarity index 93% rename from xxxthegame/src/main/resources/static/sessionbdsmtasks.html rename to xxxthegame/src/main/resources/static/bdsmtasks.html index b9af930..7075d55 100644 --- a/xxxthegame/src/main/resources/static/sessionbdsmtasks.html +++ b/xxxthegame/src/main/resources/static/bdsmtasks.html @@ -1,14 +1,14 @@ - + BDSM Game – Aufgaben-Gruppen – XXX The Game + + +
+
+
+
Warte auf Spielstart…
+
Der Host startet das Spiel in Kürze. Diese Seite aktualisiert sich automatisch.
+ + +
+
+ + + + diff --git a/xxxthegame/src/main/resources/static/benutzer.html b/xxxthegame/src/main/resources/static/benutzer.html index 88f2374..cff69e7 100644 --- a/xxxthegame/src/main/resources/static/benutzer.html +++ b/xxxthegame/src/main/resources/static/benutzer.html @@ -406,11 +406,11 @@ - +
- +
@@ -429,10 +429,10 @@
- -
-
- + +
+
+
@@ -467,6 +467,7 @@ // ── State ── const params = new URLSearchParams(window.location.search); let targetUserId = params.get('userId'); + const previewMode = params.get('preview'); // 'FREUND' | 'UNBEKANNT' | null let myUserId = null; let isOwnProfile = false; let profileData = null; @@ -524,25 +525,52 @@ } myUserId = me ? me.userId : null; - isOwnProfile = me && me.userId === profile.userId; + isOwnProfile = !previewMode && me && me.userId === profile.userId; profileData = profile; allImages = images; + // ── Preview-Modus: friendStatus simulieren ── + if (previewMode) { + profile.friendStatus = (previewMode === 'FREUND') ? 'FRIEND' : 'NONE'; + showPreviewBanner(previewMode); + } + + const isFriend = profile.friendStatus === 'FRIEND'; + document.title = profile.name + ' – XXX The Game'; renderHeader(profile); - renderGallery(); - loadFriends(); - if (profile.beschreibung) { + + // ── Galerie ── + if (canSee(profile.sichtbarkeitGalerie, isFriend, isOwnProfile)) { + renderGallery(); + } + + // ── Freunde ── + if (canSee(profile.sichtbarkeitFreunde, isFriend, isOwnProfile)) { + loadFriends(); + } + + if (profile.beschreibung && canSee(profile.sichtbarkeitGrunddaten, isFriend, isOwnProfile)) { document.getElementById('beschreibungLabel').style.display = ''; const el = document.getElementById('profilBeschreibung'); el.style.display = ''; el.textContent = profile.beschreibung; } - await loadPinnwand(); + + // ── Tabs: Feed, Pinnwand, Spielhistorie ── + applyTabPrivacy(profile, isFriend); + + if (canSee(profile.sichtbarkeitPinnwand, isFriend, isOwnProfile)) { + await loadPinnwand(); + } + document.getElementById('profileView').style.display = ''; - // Feed-Tab ist vorausgewählt → sofort laden - loadProfilPosts(); - profilPostsObserver.observe(document.getElementById('profilPostsSentinel')); + + // Feed-Tab ist vorausgewählt → sofort laden (nur wenn sichtbar) + if (canSee(profile.sichtbarkeitFeed, isFriend, isOwnProfile)) { + loadProfilPosts(); + profilPostsObserver.observe(document.getElementById('profilPostsSentinel')); + } } catch { document.getElementById('loadingHint').textContent = 'Fehler beim Laden.'; document.getElementById('loadingHint').style.display = ''; @@ -568,14 +596,16 @@ d.innerHTML = `${label}${esc(value)}`; tags.appendChild(d); }; - if (profile.alter) addTag('Alter', profile.alter + ' J.'); - if (profile.groesse) addTag('Größe', profile.groesse + ' cm'); - if (profile.gewicht) addTag('Gewicht', profile.gewicht + ' kg'); - if (profile.geschlecht) addTag('Geschlecht', GESCHLECHT_LABEL[profile.geschlecht] || profile.geschlecht); - if (profile.neigung) addTag('Neigung', NEIGUNG_LABEL[profile.neigung] || profile.neigung); - if (profile.beziehungsstatus) addTag('Beziehung', BEZIEHUNG_LABEL[profile.beziehungsstatus] || profile.beziehungsstatus); - if (profile.lockeeXp > 0) addTag('🔒 Lockee XP', profile.lockeeXp + ' XP'); - if (profile.keyholderXp > 0) addTag('🔑 Keyholder XP', profile.keyholderXp + ' XP'); + const grunddatenVisible = canSee(profile.sichtbarkeitGrunddaten, profile.friendStatus === 'FRIEND', isOwnProfile); + if (grunddatenVisible && profile.alter) addTag('Alter', profile.alter + ' J.'); + if (grunddatenVisible && profile.groesse) addTag('Größe', profile.groesse + ' cm'); + if (grunddatenVisible && profile.gewicht) addTag('Gewicht', profile.gewicht + ' kg'); + if (grunddatenVisible && profile.geschlecht) addTag('Geschlecht', GESCHLECHT_LABEL[profile.geschlecht] || profile.geschlecht); + if (grunddatenVisible && profile.neigung) addTag('Neigung', NEIGUNG_LABEL[profile.neigung] || profile.neigung); + if (grunddatenVisible && profile.beziehungsstatus) addTag('Beziehung', BEZIEHUNG_LABEL[profile.beziehungsstatus] || profile.beziehungsstatus); + const xpVisible = canSee(profile.sichtbarkeitXp, profile.friendStatus === 'FRIEND', isOwnProfile); + if (xpVisible && profile.lockeeXp > 0) addTag('🔒 Lockee XP', profile.lockeeXp + ' XP'); + if (xpVisible && profile.keyholderXp > 0) addTag('🔑 Keyholder XP', profile.keyholderXp + ' XP'); // Action buttons const actions = document.getElementById('profileActions'); @@ -596,6 +626,50 @@ } } + // ── Privacy helpers ── + function canSee(sichtbarkeit, isFriend, isOwn) { + if (isOwn || !sichtbarkeit) return true; + if (sichtbarkeit === 'ALLE') return true; + if (sichtbarkeit === 'NUR_FREUNDE') return isFriend; + // NUR_ICH + return false; + } + + function applyTabPrivacy(profile, isFriend) { + const showFeed = canSee(profile.sichtbarkeitFeed, isFriend, isOwnProfile); + const showPinnwand = canSee(profile.sichtbarkeitPinnwand, isFriend, isOwnProfile); + const showHistory = canSee(profile.sichtbarkeitLockhistorie, isFriend, isOwnProfile); + + const btnFeed = document.getElementById('tabBtnPosts'); + const btnPinnwand = document.getElementById('tabBtnPinnwand'); + const btnHistory = document.getElementById('tabBtnGameHistory'); + + if (!showFeed) { btnFeed.style.display = 'none'; document.getElementById('tab-posts').classList.remove('active'); } + if (!showPinnwand) { btnPinnwand.style.display = 'none'; } + if (!showHistory) { btnHistory.style.display = 'none'; } + + // Ersten sichtbaren Tab aktivieren + if (!showFeed) { + btnFeed.classList.remove('active'); + document.getElementById('tab-posts').classList.remove('active'); + if (showPinnwand) { + btnPinnwand.classList.add('active'); + document.getElementById('tab-pinnwand').classList.add('active'); + } else if (showHistory) { + btnHistory.classList.add('active'); + document.getElementById('tab-gamehistory').classList.add('active'); + } + } + } + + function showPreviewBanner(mode) { + const banner = document.createElement('div'); + banner.style.cssText = 'background:var(--color-secondary);border:1px solid var(--color-primary);border-radius:8px;padding:0.65rem 1rem;margin-bottom:1rem;font-size:0.88rem;display:flex;align-items:center;justify-content:space-between;gap:0.75rem;'; + const label = mode === 'FREUND' ? '👥 Vorschau aus Freundessicht' : '👤 Vorschau aus Sicht einer fremden Person'; + banner.innerHTML = `${label}← Einstellungen`; + document.getElementById('profileView').prepend(banner); + } + // ── Tab switching ── function switchProfilTab(name, btn) { document.querySelectorAll('.profil-tab-btn').forEach(b => b.classList.remove('active')); @@ -777,47 +851,60 @@ `; } - // ── Lock-Historie ── - let lockHistoryLoaded = false; - async function loadLockHistory() { - if (lockHistoryLoaded) return; - lockHistoryLoaded = true; - const list = document.getElementById('lockHistoryList'); - const empty = document.getElementById('lockHistoryEmpty'); + // ── Spielhistorie ── + const GAME_TYPE_ICON = { + CARDLOCK: '🔒', + TIMELOCK: '🔒', + BDSM: '⛓️', + VANILLA: '❤️' + }; + const ROLE_BADGE = { KEYHOLDER: '🔑', LOCKEE: '🔒', PLAYER: '' }; + + let gameHistoryLoaded = false; + async function loadGameHistory() { + if (gameHistoryLoaded) return; + gameHistoryLoaded = true; + const list = document.getElementById('gameHistoryList'); + const empty = document.getElementById('gameHistoryEmpty'); list.innerHTML = '

Lädt…

'; try { - const res = await fetch('/lockhistory?userId=' + targetUserId); + const res = await fetch('/gamehistory?userId=' + targetUserId); if (!res.ok) { list.innerHTML = ''; return; } const entries = await res.json(); list.innerHTML = ''; if (entries.length === 0) { empty.style.display = ''; return; } + list.innerHTML = entries.map(e => { - const icon = e.role === 'KEYHOLDER' ? '🔑' : '🔒'; - const partner = e.role === 'KEYHOLDER' - ? (e.lockeeName ? `Lockee: ${esc(e.lockeeName)}` : '') - : (e.keyholderName ? `Keyholder: ${esc(e.keyholderName)}` : 'Self-Lock'); - const days = Math.floor(e.durationMinutes / 1440); - const hours = Math.floor((e.durationMinutes % 1440) / 60); - const mins = e.durationMinutes % 60; - const dur = days > 0 + const gameIconRaw = GAME_TYPE_ICON[e.gameType] || '🎮'; + const gameIcon = (e.gameType === 'CARDLOCK' || e.gameType === 'TIMELOCK') + ? gameIconRaw + : `${gameIconRaw}`; + + const days = Math.floor(e.durationMinutes / 1440); + const hours = Math.floor((e.durationMinutes % 1440) / 60); + const mins = e.durationMinutes % 60; + const dur = days > 0 ? `${days}d ${hours}h ${mins}min` : hours > 0 ? `${hours}h ${mins}min` : `${mins}min`; - const avatar = e.partnerPic - ? `` - : `
👤
`; - return `
-
- ${avatar} - ${icon} -
+ + const participants = (e.participants || []).map(p => { + const badge = ROLE_BADGE[p.role] || ''; + const img = p.picture + ? `` + : `
👤
`; + return ` + ${img} + ${badge ? `${badge}` : ''} + `; + }).join(''); + + return `
+
${gameIcon}
-
${esc(e.lockName) || 'Unbenanntes Lock'}
-
- ${partner} - ⏱ ${dur} - ${fmtDate(e.unlockTime)} -
+
${esc(e.gameName) || 'Unbenannt'}
+
⏱ ${dur}  ·  ${new Date(e.endTime).toLocaleDateString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric'})}
+
${participants}
`; }).join(''); } catch(e) { list.innerHTML = ''; } @@ -954,7 +1041,7 @@ : '◉'; const bildRaw = bilderCarousel(p.bilder); const bildHtml = bildRaw - ? `
${bildRaw}
💬
` + ? `
${bildRaw}
` : ''; const privacyLabel = p.isPublic ? '' : '🔒 privat'; diff --git a/xxxthegame/src/main/resources/static/einladungen.html b/xxxthegame/src/main/resources/static/einladungen.html index bd7e665..c5d3054 100644 --- a/xxxthegame/src/main/resources/static/einladungen.html +++ b/xxxthegame/src/main/resources/static/einladungen.html @@ -167,6 +167,33 @@ } .blind-hint-icon { font-size: 1.4rem; flex-shrink: 0; } + /* Bestätigungs-Modal */ + .confirm-modal-bg { + display: none; position: fixed; inset: 0; z-index: 600; + align-items: center; justify-content: center; + } + .confirm-modal-bg.open { display: flex; } + .confirm-modal-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.6); } + .confirm-modal-box { + position: relative; background: var(--color-card); + border: 1px solid var(--color-secondary); border-radius: 12px; + padding: 1.75rem 1.5rem 1.5rem; max-width: 380px; width: 92%; z-index: 1; + display: flex; flex-direction: column; gap: 1rem; + } + .confirm-modal-title { + font-weight: 700; font-size: 1rem; padding-right: 1.5rem; + } + .confirm-modal-text { + font-size: 0.9rem; color: var(--color-muted); line-height: 1.5; + } + .confirm-modal-actions { + display: flex; gap: 0.6rem; justify-content: flex-end; margin-top: 0.25rem; + } + .confirm-modal-actions button { width: auto; padding: 0.6rem 1.3rem; font-size: 0.9rem; } + .confirm-modal-cancel { background: var(--color-secondary) !important; color: var(--color-text) !important; } + .confirm-modal-ok { background: #c0392b !important; } + .confirm-modal-ok:hover { background: #a93226 !important; } + /* Entsperrcode-Modal */ .unlock-modal-bg { display: none; position: fixed; inset: 0; z-index: 500; @@ -214,6 +241,20 @@
+ +
+
+
+ +
+
+
+ + +
+
+
+
@@ -475,9 +516,29 @@ renderSentPage(); } + // ── Bestätigungs-Modal ── + let _confirmResolve = null; + + function showConfirm(title, text) { + document.getElementById('confirmTitle').textContent = title; + document.getElementById('confirmText').textContent = text; + document.getElementById('confirmModal').classList.add('open'); + return new Promise(resolve => { + _confirmResolve = resolve; + document.getElementById('confirmOkBtn').onclick = () => { confirmClose(true); }; + }); + } + + function confirmCancel() { confirmClose(false); } + + function confirmClose(result) { + document.getElementById('confirmModal').classList.remove('open'); + if (_confirmResolve) { _confirmResolve(result); _confirmResolve = null; } + } + // ── Aktionen: Empfangen ── async function declineLockeeInvitation(token, btn) { - if (!confirm('Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return; + if (!await showConfirm('Einladung ablehnen', 'Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return; btn.disabled = true; try { const res = await fetch('/lockee/invitation/' + encodeURIComponent(token), { method: 'DELETE' }); @@ -487,7 +548,7 @@ } async function declineKhInvitation(token, btn) { - if (!confirm('Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return; + if (!await showConfirm('Einladung ablehnen', 'Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return; btn.disabled = true; try { const res = await fetch('/keyholder/invitations/mine/' + encodeURIComponent(token), { method: 'DELETE' }); @@ -498,10 +559,11 @@ // ── Aktionen: Gesendet ── async function cancelSentInvitation(token, type, btn) { - const msg = type === 'lockee' - ? 'Einladung zurückziehen? Das Lock wird gelöscht und der Lockee wird benachrichtigt.' - : 'Keyholder-Einladung zurückziehen? Der Keyholder wird benachrichtigt.'; - if (!confirm(msg)) return; + const title = 'Einladung zurückziehen'; + const text = type === 'lockee' + ? 'Das Lock wird gelöscht und der Lockee wird benachrichtigt.' + : 'Der Keyholder wird benachrichtigt.'; + if (!await showConfirm(title, text)) return; btn.disabled = true; const url = type === 'lockee' ? '/lockee/invitations/sent/' + encodeURIComponent(token) @@ -634,7 +696,7 @@ async function declineLockeeInviteDialog() { if (!activeDialogToken) return; - if (!confirm('Bist du sicher, dass du diese Einladung ablehnen möchtest? Das Lock wird gelöscht.')) return; + if (!await showConfirm('Einladung ablehnen', 'Bist du sicher, dass du diese Einladung ablehnen möchtest? Das Lock wird gelöscht.')) return; const declineBtn = document.querySelector('.btn-decline'); declineBtn.disabled = true; try { diff --git a/xxxthegame/src/main/resources/static/einstellungen.html b/xxxthegame/src/main/resources/static/einstellungen.html new file mode 100644 index 0000000..f2943f1 --- /dev/null +++ b/xxxthegame/src/main/resources/static/einstellungen.html @@ -0,0 +1,968 @@ + + + + + + + Einstellungen – XXX The Game + + + + + +
+
+

⚙️ Einstellungen

+ + +
+
+ 👤 Grunddaten + +
+
+ +
+
+
Nickname
+
+
+ +
+ +
+
+
E-Mail
+
+
+ +
+ +
+
+
Geburtsdatum
+
+
+ +
+ +
+
+
Konto löschen
+
Alle Daten werden unwiderruflich gelöscht
+
+ +
+ +
+
+ + +
+
+ 🕹️ Spiel Einstellungen + +
+
+ + +
+
BDSM Game
+ +
+
Spiele mit Geschlecht
+
+ + + +
+
+ +
+
Meine Rollen
+
+ + + + +
+
+ +
+
Was ich einsetze
+
+ + + + + +
+
+
+ +
+
+ + +
+
+ 🔔 Benachrichtigungen + +
+
+
+ + +
+
+
In-App
+
E-Mail
+
+ + +
+
+
Einladungen
+
Einladungen zu Locks und Spielen, Annahmen und Ablehnungen
+
+
+ +
+
+ +
+
+ + +
+
+
Spielstatus
+
Karten, Aufgaben, Verifikationen, Einfrierungen und andere Spielereignisse
+
+
+ +
+
+ +
+
+ + +
+
+
Notfall
+
Notfall-Entsperrungen und dringende Meldungen
+
+
+ +
+
+ +
+
+ + +
+
+
Freundschaftsanfragen
+
Neue Freundschaftsanfragen von anderen Nutzern
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+ 🛡️ Datenschutz + +
+
+ + +
+
+
Grunddaten
+
Alter, Größe, Gewicht, Geschlecht, Neigung, Beziehungsstatus, Beschreibung
+
+ +
+ + +
+
+
Galerie
+
Fotos auf dem Profil
+
+ +
+ + +
+
+
Freundesliste
+
Wer kann sehen, wer deine Freunde sind
+
+ +
+ + +
+
+
Feed / Posts
+
Posts auf dem Profil-Tab
+
+ +
+ + +
+
+
Pinnwand
+
Einträge auf der Pinnwand
+
+ +
+ + +
+
+
XP-Punkte
+
Lockee-XP und Keyholder-XP
+
+ +
+ + +
+
+
Lock-Historie
+
Abgeschlossene Locks und Keyholder-Aktivitäten
+
+ +
+ +
+ + +
+ Profil-Vorschau – wie sieht mein Profil für andere aus? + + +
+ +
+
+
+
+ + + + + + + + + + + + + +
✓ Gespeichert
+ + + + + + diff --git a/xxxthegame/src/main/resources/static/js/sidebar.js b/xxxthegame/src/main/resources/static/js/sidebar.js index 1b15c1b..bf99bbd 100644 --- a/xxxthegame/src/main/resources/static/js/sidebar.js +++ b/xxxthegame/src/main/resources/static/js/sidebar.js @@ -13,8 +13,8 @@ label: 'BDSM Game', icon: '◆', items: [ - { href: '/sessionbdsm.html', icon: '▷', label: 'Neue Session', id: 'navBdsmNeu' }, - { href: '/sessionbdsmingame.html', icon: '▶', label: 'Im Spiel', id: 'navBdsmImSpiel' }, + { href: '/bdsm.html', icon: '▷', label: 'Neue Session', id: 'navBdsmNeu' }, + { href: '/bdsmingame.html', icon: '▶', label: 'Im Spiel', id: 'navBdsmImSpiel' }, { href: '/aufgaben.html', icon: '✓', label: 'Aufgaben' }, { href: '/toys.html', icon: '◈', label: 'Toys' }, { href: '/entdecken.html', icon: '⊙', label: 'Entdecken' }, @@ -105,7 +105,7 @@ // BDSM Session-Status try { - const sessionRes = await fetch(`/session?userId=${user.userId}`); + const sessionRes = await fetch(`/bdsm?userId=${user.userId}`); const hasSession = sessionRes.status === 200; if (navNeu) navNeu.style.display = hasSession ? 'none' : ''; if (navImSpiel) navImSpiel.style.display = hasSession ? '' : 'none'; diff --git a/xxxthegame/src/main/resources/static/js/social-sidebar.js b/xxxthegame/src/main/resources/static/js/social-sidebar.js index 841b9b0..e054108 100644 --- a/xxxthegame/src/main/resources/static/js/social-sidebar.js +++ b/xxxthegame/src/main/resources/static/js/social-sidebar.js @@ -39,6 +39,7 @@

  • ${desktopItems}

  • +
  • ⚙️ Einstellungen
  • Abmelden
  • `; @@ -66,11 +67,12 @@ `; const sep = ''; + const mobileSettings = ``; const logoutLi = sidebarUl.querySelector('a[href="/login/logout"]')?.closest('li'); if (logoutLi) { - logoutLi.insertAdjacentHTML('beforebegin', sep + mobileLinks + mobileProfile); + logoutLi.insertAdjacentHTML('beforebegin', sep + mobileLinks + mobileProfile + mobileSettings); } else { - sidebarUl.insertAdjacentHTML('beforeend', sep + mobileLinks + mobileProfile); + sidebarUl.insertAdjacentHTML('beforeend', sep + mobileLinks + mobileProfile + mobileSettings); } } diff --git a/xxxthegame/src/main/resources/static/profile.html b/xxxthegame/src/main/resources/static/profile.html index 7b085a1..83b9965 100644 --- a/xxxthegame/src/main/resources/static/profile.html +++ b/xxxthegame/src/main/resources/static/profile.html @@ -1,7 +1,7 @@ - + Profil – XXX The Game @@ -269,35 +269,19 @@
    -
    +
    -
    - -
    -

    - -
    -
    - -
    - -
    -

    - -
    -
    -
    - +
    @@ -346,10 +330,6 @@
    -
    - -
    -
    @@ -364,50 +344,6 @@
    - - - - - - - - - @@ -424,14 +360,18 @@ }) .then(user => { if (!user) return; - document.getElementById('userName').textContent = user.name; - document.getElementById('userEmail').textContent = user.email; if (user.profilePicture) { currentPicture = user.profilePicture; renderPicture(currentPicture); } // Fill optional profile fields - if (user.alter) document.getElementById('profileAlter').value = user.alter; + if (user.geburtsdatum) { + const birth = new Date(user.geburtsdatum); + const today = new Date(); + const age = today.getFullYear() - birth.getFullYear() + - (today < new Date(today.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0); + document.getElementById('profileAlter').value = age + ' Jahre'; + } if (user.groesse) document.getElementById('profileGroesse').value = user.groesse; if (user.gewicht) document.getElementById('profileGewicht').value = user.gewicht; if (user.geschlecht) document.getElementById('profileGeschlecht').value = user.geschlecht; @@ -506,7 +446,6 @@ method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - alter: toNullOrInt('profileAlter'), groesse: toNullOrInt('profileGroesse'), gewicht: toNullOrInt('profileGewicht'), geschlecht: toNullOrStr('profileGeschlecht'), @@ -530,105 +469,6 @@ } } - // ── Nickname dialog ── - function openNameDialog() { - document.getElementById('newName').value = ''; - hideModalMessage('nameMessage'); - document.getElementById('nameModal').classList.add('visible'); - document.getElementById('newName').focus(); - } - - function closeNameDialog() { - document.getElementById('nameModal').classList.remove('visible'); - } - - async function saveName() { - const newName = document.getElementById('newName').value.trim(); - if (!newName) { - showModalMessage('nameMessage', 'Bitte einen Namen eingeben.', 'error'); - return; - } - const btn = document.getElementById('nameConfirmBtn'); - btn.disabled = true; - btn.textContent = 'Wird gespeichert…'; - hideModalMessage('nameMessage'); - - try { - const response = await fetch('/user/me/name', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: newName }) - }); - if (response.ok) { - document.getElementById('userName').textContent = newName; - closeNameDialog(); - showMessage('Nickname geändert.', 'success'); - } else if (response.status === 409) { - showModalMessage('nameMessage', 'Dieser Nickname ist bereits vergeben.', 'error'); - } else { - showModalMessage('nameMessage', `Fehler: HTTP ${response.status}`, 'error'); - } - } catch (err) { - showModalMessage('nameMessage', 'Server nicht erreichbar.', 'error'); - console.error(err); - } finally { - btn.disabled = false; - btn.textContent = 'Speichern'; - } - } - - // ── E-Mail dialog ── - function openEmailDialog() { - document.getElementById('newEmail').value = ''; - hideModalMessage('emailMessage'); - document.getElementById('emailModal').classList.add('visible'); - document.getElementById('newEmail').focus(); - } - - function closeEmailDialog() { - document.getElementById('emailModal').classList.remove('visible'); - } - - async function requestEmailChange() { - const newEmail = document.getElementById('newEmail').value.trim(); - if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) { - showModalMessage('emailMessage', 'Bitte eine gültige E-Mail-Adresse eingeben.', 'error'); - return; - } - const btn = document.getElementById('emailConfirmBtn'); - btn.disabled = true; - btn.textContent = 'Wird gesendet…'; - hideModalMessage('emailMessage'); - - try { - const response = await fetch('/email-change', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ newEmail }) - }); - if (response.status === 202) { - showModalMessage('emailMessage', - 'Bestätigungsmail wurde an die neue Adresse gesendet. Bitte bestätige die Änderung über den Link in der E-Mail.', - 'success'); - btn.disabled = true; - btn.textContent = 'Gesendet'; - } else if (response.status === 409) { - showModalMessage('emailMessage', 'Diese E-Mail-Adresse ist bereits vergeben.', 'error'); - btn.disabled = false; - btn.textContent = 'Bestätigungsmail senden'; - } else { - showModalMessage('emailMessage', `Fehler: HTTP ${response.status}`, 'error'); - btn.disabled = false; - btn.textContent = 'Bestätigungsmail senden'; - } - } catch (err) { - showModalMessage('emailMessage', 'Server nicht erreichbar.', 'error'); - btn.disabled = false; - btn.textContent = 'Bestätigungsmail senden'; - console.error(err); - } - } - // ── Gallery ── let myUserId = null; @@ -755,71 +595,6 @@ function hideMessage() { document.getElementById('message').style.display = 'none'; } - - function showModalMessage(id, text, type) { - const el = document.getElementById(id); - el.textContent = text; - el.className = `message ${type}`; - el.style.display = 'block'; - } - - function hideModalMessage(id) { - document.getElementById(id).style.display = 'none'; - } - - // ── Konto löschen ── - function openDeleteDialog() { - hideModalMessage('deleteMessage'); - document.getElementById('deleteConfirmBtn').disabled = false; - document.getElementById('deleteConfirmBtn').textContent = 'Konto löschen'; - document.getElementById('deleteModal').classList.add('visible'); - } - - function closeDeleteDialog() { - document.getElementById('deleteModal').classList.remove('visible'); - } - - async function deleteAccount() { - const btn = document.getElementById('deleteConfirmBtn'); - btn.disabled = true; - btn.textContent = 'Wird gelöscht…'; - hideModalMessage('deleteMessage'); - - try { - const response = await fetch('/user/me', { method: 'DELETE' }); - if (response.ok) { - window.location.href = '/login.html?accountDeleted=1'; - } else { - showModalMessage('deleteMessage', `Fehler: HTTP ${response.status}`, 'error'); - btn.disabled = false; - btn.textContent = 'Konto löschen'; - } - } catch (err) { - showModalMessage('deleteMessage', 'Server nicht erreichbar.', 'error'); - btn.disabled = false; - btn.textContent = 'Konto löschen'; - console.error(err); - } - } - - // Close modals on backdrop click - document.getElementById('deleteModal').addEventListener('click', e => { - if (e.target === document.getElementById('deleteModal')) closeDeleteDialog(); - }); - document.getElementById('nameModal').addEventListener('click', e => { - if (e.target === document.getElementById('nameModal')) closeNameDialog(); - }); - document.getElementById('emailModal').addEventListener('click', e => { - if (e.target === document.getElementById('emailModal')) closeEmailDialog(); - }); - - // Enter key in modal inputs - document.getElementById('newName').addEventListener('keydown', e => { - if (e.key === 'Enter') saveName(); - }); - document.getElementById('newEmail').addEventListener('keydown', e => { - if (e.key === 'Enter') requestEmailChange(); - }); diff --git a/xxxthegame/src/main/resources/static/registration.html b/xxxthegame/src/main/resources/static/registration.html index af529b1..de4c9df 100644 --- a/xxxthegame/src/main/resources/static/registration.html +++ b/xxxthegame/src/main/resources/static/registration.html @@ -1,7 +1,7 @@ - + xXx Games – Neues Konto erstellen @@ -19,6 +19,9 @@ + + + @@ -50,11 +53,12 @@ async function register() { const name = document.getElementById('name').value.trim(); const email = document.getElementById('email').value.trim(); + const geburtsdatum = document.getElementById('geburtsdatum').value; const password = document.getElementById('password').value; const passwordConfirm = document.getElementById('passwordConfirm').value; const btn = document.getElementById('registerBtn'); - if (!name || !email || !password || !passwordConfirm) { + if (!name || !email || !geburtsdatum || !password || !passwordConfirm) { showMessage('Bitte alle Felder ausfüllen.', 'error'); return; } @@ -62,6 +66,14 @@ showMessage('Bitte eine gültige E-Mail-Adresse eingeben.', 'error'); return; } + const today = new Date(); + const birth = new Date(geburtsdatum); + const age = today.getFullYear() - birth.getFullYear() + - (today < new Date(today.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0); + if (age < 18) { + showMessage('Du musst mindestens 18 Jahre alt sein, um dich zu registrieren.', 'error'); + return; + } if (password !== passwordConfirm) { showMessage('Die Passwörter stimmen nicht überein.', 'error'); return; @@ -76,11 +88,15 @@ const response = await fetch('/registration', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, email, passwordHash }) + body: JSON.stringify({ name, email, passwordHash, geburtsdatum }) }); if (response.status === 202) { window.location.href = `/activate.html?email=${encodeURIComponent(email)}`; + } else if (response.status === 422) { + showMessage('Du musst mindestens 18 Jahre alt sein, um dich zu registrieren.', 'error'); + btn.disabled = false; + btn.textContent = 'Registrieren'; } else if (response.status === 400) { showMessage('Diese E-Mail-Adresse ist bereits registriert.', 'error'); btn.disabled = false; diff --git a/xxxthegame/src/main/resources/static/sessionbdsmplayers.html b/xxxthegame/src/main/resources/static/sessionbdsmplayers.html deleted file mode 100644 index ba3f2e6..0000000 --- a/xxxthegame/src/main/resources/static/sessionbdsmplayers.html +++ /dev/null @@ -1,439 +0,0 @@ - - - - - - - BDSM Game – Mitspieler – XXX The Game - - - - - -
    -
    - -

    BDSM Game

    -

    Schritt 2 von 4 – Mitspieler

    - -
    -

    Mitspieler

    -
    - -
    - -
    -
    - - -
    - -
    -
    - - - - - diff --git a/xxxthegame/src/main/resources/static/userhome.html b/xxxthegame/src/main/resources/static/userhome.html index 58c7f52..9c399f5 100644 --- a/xxxthegame/src/main/resources/static/userhome.html +++ b/xxxthegame/src/main/resources/static/userhome.html @@ -54,7 +54,7 @@ Tauche ein in strukturierte Sessions mit Aufgaben, Toys und klaren Rollen. Definiere Grenzen, vergib Aufgaben und erlebe intensive Momente mit deinem Partner.

    - +