currently refactoring a lot

This commit is contained in:
kento2 2026-07-04 01:03:46 +02:00
parent 701a5ff620
commit 7fe7409a15
49 changed files with 856 additions and 767 deletions

2
.idea/misc.xml generated
View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21 (3)" project-jdk-type="JavaSDK" />
</project> </project>

120
.idea/workspace.xml generated
View file

@ -5,12 +5,59 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="56c685a1-f609-4ffd-a951-849beb2a7f8d" name="Changes" comment="."> <list default="true" id="56c685a1-f609-4ffd-a951-849beb2a7f8d" name="Changes" comment=".">
<change afterPath="$PROJECT_DIR$/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/PlayerManager.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/playermanager/PlayerManagerImpl.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/convention/ScrowMessageStyle.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/messaging/MessageBroker.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/messaging/RequestHandler.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/messaging/ResultRepository.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/InstanceManager.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/InstanceManagerImpl.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/utils/EnvUtils.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../build-logic/convention/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/../build-logic/convention/build.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../build-logic/convention/src/main/kotlin/de/kentoj/scrow/BaseDependenciesConvention.kt" beforeDir="false" afterPath="$PROJECT_DIR$/../build-logic/convention/src/main/kotlin/de/kentoj/scrow/BaseDependenciesConvention.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../build-logic/convention/src/main/kotlin/de/kentoj/scrow/ScrowRepositoryConvention.kt" beforeDir="false" afterPath="$PROJECT_DIR$/../build-logic/convention/src/main/kotlin/de/kentoj/scrow/ScrowRepositoryConvention.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../build-logic/settings.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/../build-logic/settings.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" /> <change beforePath="$PROJECT_DIR$/core-bukkit-api/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-api/build.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/region/RegionManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/region/RegionManager.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/ScrowAPI.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-api/src/main/java/de/kentoj/scrow/bukkit/ScrowAPI.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/region/RegionManagerImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/region/RegionManagerImpl.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/core-bukkit-impl/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/build.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/region/RegionPlayerTracker.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/region/RegionPlayerTracker.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/CoreImplPlugin.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/CoreImplPlugin.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/region/RegionTask.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/region/RegionTask.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/ScrowAPISurface.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/ScrowAPISurface.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownCommand.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownCommand.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDeployServerLiteral.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDeployServerLiteral.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDestroyServerLiteral.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownDestroyServerLiteral.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownListServersLiteral.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/crown/CrownListServersLiteral.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/CoinsCommand.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/economy/CoinsCommand.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendAddLiteral.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendAddLiteral.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendCommand.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendCommand.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendListLiteral.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendListLiteral.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRemoveLiteral.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRemoveLiteral.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRequestsLiteral.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/command/FriendRequestsLiteral.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/friendship/InMemoryFriendshipService.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/friendship/InMemoryFriendshipService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/friends/handler/FriendAddHandler.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/LobbyCommand.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/LobbyCommand.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/PlayCommand.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/server/PlayCommand.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-lib/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/core-lib/build.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/messaging/NatsRepository.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/messaging/SubscriptionContext.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeErrorResult.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeResult.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/messaging/respose/SafeSuccessResult.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerInstance.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerInstance.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerManager.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/core-lib/src/main/java/de/kentoj/scrowlib/servermanager/ServerManagerImpl.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/core-velocity-api/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/core-velocity-api/build.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-velocity-api/src/main/java/de/kentoj/scrow/velocity/ScrowAPI.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-velocity-api/src/main/java/de/kentoj/scrow/velocity/ScrowAPI.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-velocity-impl/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/core-velocity-impl/build.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/CoreVelocityPlugin.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/CoreVelocityPlugin.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/ScrowAPISurface.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/ScrowAPISurface.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/command/OnlineCommand.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/command/OnlineCommand.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/MsgCommand.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/MsgCommand.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/ReplyCommand.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/privmsg/ReplyCommand.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/SendPlayerWatchdog.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/SendPlayerWatchdog.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/ServerRegisterWatchdog.java" beforeDir="false" afterPath="$PROJECT_DIR$/core-velocity-impl/src/main/java/de/kentoj/scrow/corevelocity/server/ServerRegisterWatchdog.java" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -97,15 +144,24 @@
<option name="RECENT_TEMPLATES"> <option name="RECENT_TEMPLATES">
<list> <list>
<option value="Kotlin Class" /> <option value="Kotlin Class" />
<option value="Class" />
<option value="Interface" /> <option value="Interface" />
<option value="Class" />
</list> </list>
</option> </option>
</component> </component>
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component> </component>
<component name="GradleScriptDefinitionsStorage" workingDir="$PROJECT_DIR$" gradleHome="$USER_HOME$/.gradle/wrapper/dists/gradle-8.11-bin/c4te04g51qsyw1bxcb929u7br/gradle-8.11" javaHome="/usr/lib/jvm/openjdk21" gradleVersion="8.11" /> <component name="GradleScriptDefinitionsStorage" workingDir="$PROJECT_DIR$" gradleHome="$USER_HOME$/.gradle/wrapper/dists/gradle-8.11-bin/c4te04g51qsyw1bxcb929u7br/gradle-8.11" javaHome="/usr/lib/jvm/openjdk21" gradleVersion="8.11">
<option name="jvmArguments">
<list>
<option value="-Didea.gradle.download.sources.force=true" />
</list>
</option>
</component>
<component name="HighlightingSettingsPerFile">
<setting file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.projectreactor/reactor-core/3.8.5/ed6c3f423d84bf282e50a4f7432a8d65af321efb/reactor-core-3.8.5-sources.jar!/reactor/core/publisher/Mono.java" root0="SKIP_INSPECTION" />
</component>
<component name="ProjectColorInfo">{ <component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;, &quot;customColor&quot;: &quot;&quot;,
&quot;associatedIndex&quot;: 0 &quot;associatedIndex&quot;: 0
@ -124,22 +180,29 @@
"Gradle.core [build].executor": "Run", "Gradle.core [build].executor": "Run",
"Gradle.core [clean].executor": "Run", "Gradle.core [clean].executor": "Run",
"Gradle.core [publishAllPublicationsToScrowRepository].executor": "Run", "Gradle.core [publishAllPublicationsToScrowRepository].executor": "Run",
"Gradle.core [publishCoreLibPublicationToScrowRepository].executor": "Run",
"Gradle.core:core-bukkit-api [build].executor": "Run", "Gradle.core:core-bukkit-api [build].executor": "Run",
"Gradle.core:core-bukkit-api [clean].executor": "Run", "Gradle.core:core-bukkit-api [clean].executor": "Run",
"Gradle.core:core-lib [publishAllPublicationsToScrowRepository].executor": "Run",
"Gradle.core:core-lib [publishCoreLibPublicationToMavenLocal].executor": "Run",
"Gradle.core:core-lib [publishCoreLibPublicationToScrowRepository].executor": "Run",
"ModuleVcsDetector.initialDetectionPerformed": "true", "ModuleVcsDetector.initialDetectionPerformed": "true",
"RunOnceActivity.ShowReadmeOnStart": "true", "RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true", "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"RunOnceActivity.git.unshallow": "true", "RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "master", "git-widget-placeholder": "master",
"kotlin-language-version-configured": "true", "kotlin-language-version-configured": "true",
"last_opened_file_path": "/home/a/src/scrow/core/settings.gradle.kts", "last_opened_file_path": "/home/a/src/scrow",
"project.structure.last.edited": "Modules", "project.structure.last.edited": "Project",
"project.structure.proportion": "0.15", "project.structure.proportion": "0.15",
"project.structure.side.proportion": "0.6268199", "project.structure.side.proportion": "0.6268199",
"settings.editor.selected.configurable": "editor.preferences.smartKeys" "settings.editor.selected.configurable": "preferences.pluginManager"
} }
}]]></component> }]]></component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CreateClassDialog.RecentsKey">
<recent name="de.kentoj.scrowlib.messaging" />
</key>
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/build-logic/gradle" /> <recent name="$PROJECT_DIR$/build-logic/gradle" />
</key> </key>
@ -183,7 +246,7 @@
<option name="executionName" /> <option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" /> <option name="scriptParameters" />
<option name="taskDescriptions"> <option name="taskDescriptions">
<list /> <list />
</option> </option>
@ -200,10 +263,10 @@
<RunAsTest>false</RunAsTest> <RunAsTest>false</RunAsTest>
<method v="2" /> <method v="2" />
</configuration> </configuration>
<configuration name="core [publishAllPublicationsToScrowRepository]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true"> <configuration name="core:core-lib [publishAllPublicationsToScrowRepository]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings> <ExternalSystemSettings>
<option name="executionName" /> <option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$/core-lib" />
<option name="externalSystemIdString" value="GRADLE" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" /> <option name="scriptParameters" />
<option name="taskDescriptions"> <option name="taskDescriptions">
@ -222,10 +285,10 @@
<RunAsTest>false</RunAsTest> <RunAsTest>false</RunAsTest>
<method v="2" /> <method v="2" />
</configuration> </configuration>
<configuration name="core:core-bukkit-api [build]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true"> <configuration name="core:core-lib [publishCoreLibPublicationToMavenLocal]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings> <ExternalSystemSettings>
<option name="executionName" /> <option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/core-bukkit-api" /> <option name="externalProjectPath" value="$PROJECT_DIR$/core-lib" />
<option name="externalSystemIdString" value="GRADLE" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" /> <option name="scriptParameters" />
<option name="taskDescriptions"> <option name="taskDescriptions">
@ -233,7 +296,7 @@
</option> </option>
<option name="taskNames"> <option name="taskNames">
<list> <list>
<option value="build" /> <option value="publishCoreLibPublicationToMavenLocal" />
</list> </list>
</option> </option>
<option name="vmOptions" /> <option name="vmOptions" />
@ -244,10 +307,10 @@
<RunAsTest>false</RunAsTest> <RunAsTest>false</RunAsTest>
<method v="2" /> <method v="2" />
</configuration> </configuration>
<configuration name="core:core-bukkit-api [clean]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true"> <configuration name="core:core-lib [publishCoreLibPublicationToScrowRepository]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings> <ExternalSystemSettings>
<option name="executionName" /> <option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/core-bukkit-api" /> <option name="externalProjectPath" value="$PROJECT_DIR$/core-lib" />
<option name="externalSystemIdString" value="GRADLE" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" /> <option name="scriptParameters" />
<option name="taskDescriptions"> <option name="taskDescriptions">
@ -255,7 +318,7 @@
</option> </option>
<option name="taskNames"> <option name="taskNames">
<list> <list>
<option value="clean" /> <option value="publishCoreLibPublicationToScrowRepository" />
</list> </list>
</option> </option>
<option name="vmOptions" /> <option name="vmOptions" />
@ -280,10 +343,10 @@
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="Gradle.core [build]" /> <item itemvalue="Gradle.core [build]" />
<item itemvalue="Gradle.core [publishAllPublicationsToScrowRepository]" />
<item itemvalue="Gradle.core [clean]" /> <item itemvalue="Gradle.core [clean]" />
<item itemvalue="Gradle.core:core-bukkit-api [build]" /> <item itemvalue="Gradle.core:core-lib [publishCoreLibPublicationToScrowRepository]" />
<item itemvalue="Gradle.core:core-bukkit-api [clean]" /> <item itemvalue="Gradle.core:core-lib [publishCoreLibPublicationToMavenLocal]" />
<item itemvalue="Gradle.core:core-lib [publishAllPublicationsToScrowRepository]" />
</list> </list>
</recent_temporary> </recent_temporary>
</component> </component>
@ -311,11 +374,20 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1782673270039</updated> <updated>1782673270039</updated>
</task> </task>
<option name="localTasksCounter" value="3" /> <task id="LOCAL-00003" summary="fix: unable to leave regions when unloaded">
<option name="closed" value="true" />
<created>1782675674934</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1782675674934</updated>
</task>
<option name="localTasksCounter" value="4" />
<servers /> <servers />
</component> </component>
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<MESSAGE value="." /> <MESSAGE value="." />
<option name="LAST_COMMIT_MESSAGE" value="." /> <MESSAGE value="fix: unable to leave regions when unloaded" />
<option name="LAST_COMMIT_MESSAGE" value="fix: unable to leave regions when unloaded" />
</component> </component>
</project> </project>

View file

@ -4,29 +4,29 @@ plugins {
id("de.kentoj.scrow.scrow-repository") id("de.kentoj.scrow.scrow-repository")
id("de.kentoj.scrow.base-dependencies") id("de.kentoj.scrow.base-dependencies")
id("de.kentoj.scrow.java-library") id("de.kentoj.scrow.java-library")
id("de.kentoj.scrow.reactor")
} }
group = "de.kentoj.scrow" group = "de.kentoj.scrow"
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/") {
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") { name = "papermc"
name = "spigotmc-repo"
} }
} }
dependencies { dependencies {
api(project(":core-lib")) api(project(":core-lib"))
api("org.spigotmc:spigot-api:1.21.11-R0.1-SNAPSHOT") api("io.papermc.paper:paper-api:1.21.11-rc3-R0.1-SNAPSHOT")
// https://mvnrepository.com/artifact/io.nats/jnats // https://mvnrepository.com/artifact/io.nats/jnats
api("io.nats:jnats:2.25.2") api("io.nats:jnats:2.25.2")
// http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core // http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core
api("de.kentoj.scrow:kencommandapi-core:0.9") api("de.kentoj.scrow:kencommandapi-core:0.21")
// http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-bukkit // http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-bukkit
api("de.kentoj.scrow:kencommandapi-bukkit:0.9") api("de.kentoj.scrow:kencommandapi-bukkit:0.21")
} }
publishing { publishing {

View file

@ -0,0 +1,10 @@
package de.kentoj.scrow.bukkit;
import com.leakyabstractions.result.api.Result;
import java.util.UUID;
public interface PlayerManager {
Result<Void, String> sendPlayer(UUID uuid, String instanceHandle);
}

View file

@ -1,11 +1,11 @@
package de.kentoj.scrow.bukkit; package de.kentoj.scrow.bukkit;
import de.kentoj.kencommandapi.BukkitKenCommandApi; import de.kentoj.kencommandapi.CommandAPI;
import de.kentoj.scrow.bukkit.friends.FriendRequestService; import de.kentoj.scrow.bukkit.friends.FriendRequestService;
import de.kentoj.scrow.bukkit.friends.FriendshipService; import de.kentoj.scrow.bukkit.friends.FriendshipService;
import de.kentoj.scrow.bukkit.region.RegionManager; import de.kentoj.scrow.bukkit.region.RegionManager;
import de.kentoj.scrowlib.connection.DatabaseConnectionFactory; import de.kentoj.scrowlib.connection.DatabaseConnectionFactory;
import de.kentoj.scrowlib.servermanager.ServerManager; import de.kentoj.scrowlib.servermanager.InstanceManager;
import io.nats.client.Connection; import io.nats.client.Connection;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
@ -30,11 +30,13 @@ public class ScrowAPI {
@Getter @Getter
private static Connection nats; private static Connection nats;
@Getter @Getter
private static ServerManager serverManager; private static InstanceManager instanceManager;
@Getter @Getter
private static RegionManager regionManager; private static RegionManager regionManager;
@Getter @Getter
private static final BukkitKenCommandApi commandApi = new BukkitKenCommandApi(); private static CommandAPI commandApi;
@Getter
private static PlayerManager playerManager;
@ApiStatus.Internal @ApiStatus.Internal
public static void setEconomyService(EconomyService economyService) { public static void setEconomyService(EconomyService economyService) {
@ -62,8 +64,8 @@ public class ScrowAPI {
} }
@ApiStatus.Internal @ApiStatus.Internal
public static void setServerManager(ServerManager serverManager) { public static void setInstanceManager(InstanceManager instanceManager) {
ScrowAPI.serverManager = serverManager; ScrowAPI.instanceManager = instanceManager;
} }
@ApiStatus.Internal @ApiStatus.Internal
@ -75,5 +77,15 @@ public class ScrowAPI {
public static void setRegionManager(RegionManager regionManager) { public static void setRegionManager(RegionManager regionManager) {
ScrowAPI.regionManager = regionManager; ScrowAPI.regionManager = regionManager;
} }
@ApiStatus.Internal
public static void setCommandApi(CommandAPI commandApi) {
ScrowAPI.commandApi = commandApi;
}
@ApiStatus.Internal
public static void setPlayerManager(PlayerManager playerManager) {
ScrowAPI.playerManager = playerManager;
}
} }

View file

@ -5,6 +5,7 @@ plugins {
id("de.kentoj.scrow.scrow-repository") id("de.kentoj.scrow.scrow-repository")
id("de.kentoj.scrow.base-dependencies") id("de.kentoj.scrow.base-dependencies")
id("de.kentoj.scrow.database") id("de.kentoj.scrow.database")
id("de.kentoj.scrow.reactor")
} }
group = "de.kentoj.scrow" group = "de.kentoj.scrow"
@ -12,14 +13,17 @@ group = "de.kentoj.scrow"
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") { maven("https://repo.papermc.io/repository/maven-public/") {
name = "spigotmc-repo" name = "papermc"
} }
} }
dependencies { dependencies {
implementation(project(":core-bukkit-api")) implementation(project(":core-bukkit-api"))
// http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core
implementation("de.kentoj.scrow:kencommandapi-core:0.21")
// TODO remove, we use postgres now // TODO remove, we use postgres now
// https://www.mongodb.com/docs/languages/java/reactive-streams-driver/current/get-started/download-and-install/ // https://www.mongodb.com/docs/languages/java/reactive-streams-driver/current/get-started/download-and-install/
implementation(platform("org.mongodb:mongodb-driver-bom:5.6.1")) implementation(platform("org.mongodb:mongodb-driver-bom:5.6.1"))

View file

@ -10,6 +10,13 @@ import de.kentoj.scrow.bukkit.server.PlayCommand;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class CoreImplPlugin extends JavaPlugin { public class CoreImplPlugin extends JavaPlugin {
public static boolean ENABLE_DATABASE = false; public static boolean ENABLE_DATABASE = false;
@ -32,8 +39,13 @@ public class CoreImplPlugin extends JavaPlugin {
} }
private void registerServer() { private void registerServer() {
ScrowAPI.getServerManager().setState(System.getenv("SERVER_HANDLE"), "UP") var opts = new OpenOption[]{StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE, StandardOpenOption.WRITE};
.subscribe(__ -> Bukkit.getLogger().info("registered self")); try (var file = FileChannel.open(Path.of("/proc/self/fd/3"), opts)) {
//noinspection ResultOfMethodCallIgnored
file.write(ByteBuffer.wrap(new byte[]{'\n'}));
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
@Override @Override

View file

@ -1,6 +1,6 @@
package de.kentoj.scrow.bukkit; package de.kentoj.scrow.bukkit;
import de.kentoj.kencommandapi.BukkitKenCommandApi; import de.kentoj.kencommandapi.CommandAPI;
import de.kentoj.scrow.bukkit.economy.InMemoryEconomyService; import de.kentoj.scrow.bukkit.economy.InMemoryEconomyService;
import de.kentoj.scrow.bukkit.economy.PostgresEconomyService; import de.kentoj.scrow.bukkit.economy.PostgresEconomyService;
import de.kentoj.scrow.bukkit.friends.friendship.InMemoryFriendshipService; import de.kentoj.scrow.bukkit.friends.friendship.InMemoryFriendshipService;
@ -9,7 +9,8 @@ import de.kentoj.scrow.bukkit.friends.request.InMemoryFriendRequestService;
import de.kentoj.scrow.bukkit.friends.request.PostgresFriendRequestService; import de.kentoj.scrow.bukkit.friends.request.PostgresFriendRequestService;
import de.kentoj.scrow.bukkit.region.RegionManagerImpl; import de.kentoj.scrow.bukkit.region.RegionManagerImpl;
import de.kentoj.scrowlib.connection.DatabaseConnectionFactoryImpl; import de.kentoj.scrowlib.connection.DatabaseConnectionFactoryImpl;
import de.kentoj.scrowlib.servermanager.ServerManagerImpl; import de.kentoj.scrowlib.servermanager.InstanceManagerImpl;
import de.kentoj.scrowlib.utils.EnvUtils;
import io.nats.client.Nats; import io.nats.client.Nats;
import io.nats.client.Options; import io.nats.client.Options;
import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactories;
@ -31,7 +32,7 @@ public class ScrowAPISurface {
})); }));
try { try {
String natsHost = System.getenv("HOST_NATS"); String natsHost = EnvUtils.envOrThrow("HOST_NATS");
var options = Options.builder() var options = Options.builder()
.server(natsHost) .server(natsHost)
.pedantic() .pedantic()
@ -42,12 +43,13 @@ public class ScrowAPISurface {
} }
if (enableDatabase) { if (enableDatabase) {
ConnectionFactory factory = ConnectionFactories.get(System.getenv("HOST_POSTGRES")); ConnectionFactory factory = ConnectionFactories.get(EnvUtils.envOrThrow("HOST_POSTGRES"));
ScrowAPI.setDbConFactory(new DatabaseConnectionFactoryImpl(factory)); ScrowAPI.setDbConFactory(new DatabaseConnectionFactoryImpl(factory));
} }
ScrowAPI.setRegionManager(new RegionManagerImpl()); ScrowAPI.setRegionManager(new RegionManagerImpl());
ScrowAPI.setServerManager(new ServerManagerImpl(ScrowAPI.getNats())); ScrowAPI.setCommandApi(new CommandAPI(plugin));
ScrowAPI.setInstanceManager(new InstanceManagerImpl(ScrowAPI.getNats()));
ScrowAPI.setEconomyService(ScrowAPI.getDbConFactory() != null ? ScrowAPI.setEconomyService(ScrowAPI.getDbConFactory() != null ?
new PostgresEconomyService(ScrowAPI.getDbConFactory()) : new InMemoryEconomyService()); new PostgresEconomyService(ScrowAPI.getDbConFactory()) : new InMemoryEconomyService());
ScrowAPI.setFriendRequestService(ScrowAPI.getDbConFactory() != null ? ScrowAPI.setFriendRequestService(ScrowAPI.getDbConFactory() != null ?

View file

@ -1,20 +1,23 @@
package de.kentoj.scrow.bukkit.crown; package de.kentoj.scrow.bukkit.crown;
import de.kentoj.kencommandapi.BukkitCommandContext; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import de.kentoj.scrow.bukkit.ScrowAPI;
import lombok.Getter; import lombok.Getter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
public class CrownCommand { public class CrownCommand {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> rootNode;
public CrownCommand() { public CrownCommand() {
this.rootNode = ScrowAPI.getCommandApi().createNode("crown", "server-manager"); this.rootNode = CommandNode.node("crown", "server-manager");
this.rootNode.addLiteral(new CrownListServersLiteral().getRootNode()); var style = new ScrowMessageStyle(Component.text("crown")
this.rootNode.addLiteral(new CrownDeployServerLiteral().getRootNode()); .color(NamedTextColor.GOLD));
this.rootNode.addLiteral(new CrownDestroyServerLiteral().getRootNode()); this.rootNode.addLiteral(new CrownListServersLiteral(style).getNode());
this.rootNode.addLiteral(new CrownDeployServerLiteral(style).getRootNode());
this.rootNode.addLiteral(new CrownDestroyServerLiteral(style).getRootNode());
} }
} }

View file

@ -1,35 +1,40 @@
package de.kentoj.scrow.bukkit.crown; package de.kentoj.scrow.bukkit.crown;
import de.kentoj.kencommandapi.BukkitCommandContext; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.argument.CommandArgument;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.processing.CommandExecutor; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.types.StringArgumentType; import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import com.leakyabstractions.result.api.Result;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrow.bukkit.ScrowAPI;
import lombok.Getter; import lombok.Getter;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
public class CrownDeployServerLiteral implements CommandExecutor<CommandSender, BukkitCommandContext> { import java.util.concurrent.CompletableFuture;
public class CrownDeployServerLiteral implements CommandExecutor<CommandSender> {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, String> templateArg; private final CommandArgument<CommandSender, String> templateArg;
public CrownDeployServerLiteral() { public CrownDeployServerLiteral() {
this.rootNode = ScrowAPI.getCommandApi().createNode("deploy-server"); this.rootNode = CommandNode.node("deploy-server");
this.rootNode.setExecutor(this); this.rootNode.setExecutor(this);
{ {
this.templateArg = ScrowAPI.getCommandApi().createArgument("template", new StringArgumentType<>()); this.templateArg = CommandArgument.arg("template", new StringArgumentType<>());
this.rootNode.addArgument(templateArg); this.rootNode.addArgument(templateArg);
} }
} }
@Override @Override
public void execute(BukkitCommandContext ctx) { public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSender> ctx) {
var template = ctx.getArg(templateArg); var template = ctx.getArg(templateArg);
ScrowAPI.getServerManager().deployServer(template) return ScrowAPI.getInstanceManager().deployInstance(template)
.subscribe(server -> .map(server -> {
ctx.getSender().sendMessage("Deployed instance of " + template + " with handle " + server.getHandle()) ctx.getSender().sendMessage("Deployed instance of " + template + " with handle " + server.getHandle());
); // FIXME handle unknown server
return Result.success();
}).toFuture();
} }
} }

View file

@ -1,31 +1,34 @@
package de.kentoj.scrow.bukkit.crown; package de.kentoj.scrow.bukkit.crown;
import de.kentoj.kencommandapi.BukkitCommandContext; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.argument.CommandArgument;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.processing.CommandExecutor; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.types.StringArgumentType; import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.api.Results;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrowlib.servermanager.ServerInstance; import de.kentoj.scrowlib.servermanager.ServerInstance;
import lombok.Getter; import lombok.Getter;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class CrownDestroyServerLiteral implements CommandExecutor<CommandSender, BukkitCommandContext> { public class CrownDestroyServerLiteral implements CommandExecutor<CommandSender> {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, String> handleArg; private final CommandArgument<CommandSender, String> handleArg;
public CrownDestroyServerLiteral() { public CrownDestroyServerLiteral() {
this.rootNode = ScrowAPI.getCommandApi().createNode("destroy-server"); this.rootNode = CommandNode.node("destroy-server");
this.rootNode.setExecutor(this); this.rootNode.setExecutor(this);
{ {
this.handleArg = ScrowAPI.getCommandApi().createArgument("template", new StringArgumentType<>()); this.handleArg = CommandArgument.arg("template", new StringArgumentType<>());
this.handleArg.setSuggestionProvider(ctx -> { this.handleArg.setSuggestionProvider(ctx -> {
//noinspection CodeBlock2Expr //noinspection CodeBlock2Expr
return ScrowAPI.getServerManager().getRunningServers() return ScrowAPI.getInstanceManager().getInstances()
.map(ServerInstance::getHandle) .map(ServerInstance::getHandle)
.collect(Collectors.toSet()) .collect(Collectors.toSet())
.toFuture(); .toFuture();
@ -35,11 +38,12 @@ public class CrownDestroyServerLiteral implements CommandExecutor<CommandSender,
} }
@Override @Override
public void execute(BukkitCommandContext ctx) { public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSender> ctx) {
var handle = ctx.getArg(handleArg); var handle = ctx.getArg(handleArg);
ScrowAPI.getServerManager().destroyServer(handle) return ScrowAPI.getInstanceManager().destroyInstance(handle)
.subscribe(__ -> .map(__ -> {
ctx.getSender().sendMessage("Destroyed server with handle " + handle) ctx.getSender().sendMessage("Destroyed server with handle " + handle);
); return Result.success();
}).toFuture();
} }
} }

View file

@ -1,35 +1,41 @@
package de.kentoj.scrow.bukkit.crown; package de.kentoj.scrow.bukkit.crown;
import de.kentoj.kencommandapi.BukkitCommandContext; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import de.kentoj.kencommandapi.api.processing.CommandExecutor; import com.leakyabstractions.result.api.Result;
import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.Getter; import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
public class CrownListServersLiteral implements CommandExecutor<CommandSender, BukkitCommandContext> { import java.util.concurrent.CompletableFuture;
public class CrownListServersLiteral implements CommandExecutor<CommandSender> {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> node;
private final ScrowMessageStyle style;
public CrownListServersLiteral() { public CrownListServersLiteral(ScrowMessageStyle style) {
this.rootNode = ScrowAPI.getCommandApi().createNode("list-servers"); this.style = style;
this.rootNode.setExecutor(this); this.node = CommandNode.node("list-servers");
this.node.setExecutor(this);
} }
@Override @Override
public void execute(BukkitCommandContext ctx) { public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSender> ctx) {
ScrowAPI.getServerManager().getRunningServers() return ScrowAPI.getInstanceManager().getInstances()
.reduce(new StringBuilder("\n"), (acc, cur) -> acc.append("> ") .collectList()
.append(cur.getHandle()) .map(list -> style.list(Component.text("Running instances:"),
.append(" on port ") list.stream()
.append(cur.getPort()) .map(inst ->
.append(": ") Component.text(inst.getHandle() + " on port " + inst.getPort() + ": " + inst.getState())
.append(cur.getState()) )
.append('\n') .toList())
) )
.subscribe(msg -> .doOnNext(ctx.getSender()::sendMessage)
ctx.getPlayerSender().sendMessage(msg.toString()) .thenReturn(Result.success()).toFuture();
);
} }
} }

View file

@ -1,9 +1,13 @@
package de.kentoj.scrow.bukkit.economy; package de.kentoj.scrow.bukkit.economy;
import de.kentoj.kencommandapi.BukkitCommandContext; import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.argument.CommandArgument;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.argument.types.IntegerArgumentType;
import de.kentoj.kencommandapi.api.types.IntegerArgumentType; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.api.Results;
import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType; import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrow.bukkit.ScrowAPI;
import lombok.Getter; import lombok.Getter;
@ -12,67 +16,63 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level; import java.util.logging.Level;
public class CoinsCommand { public class CoinsCommand {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, OfflinePlayer> playerArg; private final CommandArgument<CommandSender, OfflinePlayer> playerArg;
private final CommandArgument<CommandSender, BukkitCommandContext, Integer> amountArg; private final CommandArgument<CommandSender, Integer> amountArg;
public CoinsCommand() { public CoinsCommand() {
this.rootNode = ScrowAPI.getCommandApi().createNode("coins", "eco"); this.rootNode = CommandNode.node("coins", "eco");
this.playerArg = ScrowAPI.getCommandApi().createArgument("player", OfflinePlayerArgumentType.getInstance()); this.playerArg = CommandArgument.arg("player", OfflinePlayerArgumentType.getInstance());
this.amountArg = ScrowAPI.getCommandApi().createArgument("amount", new IntegerArgumentType<>()); this.amountArg = CommandArgument.arg("amount", new IntegerArgumentType<>());
{ {
var setLiteral = ScrowAPI.getCommandApi().createNode("set"); CommandNode<CommandSender> setLiteral = CommandNode.node("set");
rootNode.addLiteral(setLiteral); rootNode.addLiteral(setLiteral);
setLiteral.addArgument(playerArg); setLiteral.addArgument(playerArg);
setLiteral.addArgument(amountArg); setLiteral.addArgument(amountArg);
setLiteral.setExecutor(this::setCoins); setLiteral.setExecutor(this::setCoins);
} }
{ {
var getLiteral = ScrowAPI.getCommandApi().createNode("get"); CommandNode<CommandSender> getLiteral = CommandNode.node("get");
rootNode.addLiteral(getLiteral); rootNode.addLiteral(getLiteral);
getLiteral.addArgument(playerArg); getLiteral.addArgument(playerArg);
getLiteral.setExecutor(this::getCoins); getLiteral.setExecutor(this::getCoins);
} }
} }
private void getCoins(BukkitCommandContext ctx) { private CompletableFuture<Result<Void,String>> getCoins(CommandContext<CommandSender> ctx) {
var player = ctx.getArg(playerArg); var player = ctx.getArg(playerArg);
return ScrowAPI.getEconomyService()
ScrowAPI.getEconomyService()
.getCoins(player.getUniqueId()) .getCoins(player.getUniqueId())
.subscribeOn(Schedulers.boundedElastic()) .subscribeOn(Schedulers.boundedElastic())
.publishOn(ScrowAPI.getMinecraftScheduler()) .publishOn(ScrowAPI.getMinecraftScheduler())
.subscribe(amount -> { .map(amount -> {
if (player == ctx.getSender()) { if (player == ctx.getSender()) {
ctx.getSender().sendMessage("You have " + amount + "$"); ctx.getSender().sendMessage("You have " + amount + "$");
} else { } else {
ctx.getSender().sendMessage(player.getName() + " has " + amount + "$"); ctx.getSender().sendMessage(player.getName() + " has " + amount + "$");
} }
}, err -> { return Result.success();
ctx.getSender().sendMessage("Unable to get coins: " + err.getMessage()); }).toFuture();
Bukkit.getLogger().log(Level.SEVERE, "Unable to get coins of " + player.getUniqueId(), err);
});
} }
private void setCoins(BukkitCommandContext ctx) { private CompletableFuture<Result<Void,String>> setCoins(CommandContext<CommandSender> ctx) {
var player = ctx.getArg(playerArg); var player = ctx.getArg(playerArg);
var amount = ctx.getArg(amountArg); var amount = ctx.getArg(amountArg);
ScrowAPI.getEconomyService() return ScrowAPI.getEconomyService()
.setCoins(player.getUniqueId(), amount) .setCoins(player.getUniqueId(), amount)
.subscribeOn(Schedulers.boundedElastic()) .subscribeOn(Schedulers.boundedElastic())
.publishOn(ScrowAPI.getMinecraftScheduler()) .publishOn(ScrowAPI.getMinecraftScheduler())
.subscribe(__ -> { .map(__ -> {
ctx.getSender().sendMessage(player.getName() + " now has " + amount + "$"); ctx.getSender().sendMessage(player.getName() + " now has " + amount + "$");
}, err -> { return Results.success(null);
ctx.getSender().sendMessage("Unable to set coins: " + err.getMessage()); }).toFuture();
Bukkit.getLogger().log(Level.SEVERE, "Unable to set coins of " + player.getUniqueId() + " to " + amount, err);
});
} }
} }

View file

@ -1,33 +1,107 @@
package de.kentoj.scrow.bukkit.friends.command; package de.kentoj.scrow.bukkit.friends.command;
import de.kentoj.kencommandapi.BukkitCommandContext; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.argument.CommandArgument;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.processing.CommandExecutor; import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.api.Results;
import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType; import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.friends.handler.FriendAddHandler; import de.kentoj.scrow.bukkit.friends.friendship.FriendshipImpl;
import de.kentoj.scrow.bukkit.friends.request.FriendRequestImpl;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Mono;
public class FriendAddLiteral implements CommandExecutor<CommandSender, BukkitCommandContext> { import java.time.Instant;
import java.util.concurrent.CompletableFuture;
@RequiredArgsConstructor
public class FriendAddLiteral implements CommandExecutor<CommandSender> {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, OfflinePlayer> playerArg; private final CommandArgument<CommandSender, OfflinePlayer> playerArg;
public FriendAddLiteral() { public FriendAddLiteral() {
var cmds = ScrowAPI.getCommandApi(); playerArg = CommandArgument.arg("player", OfflinePlayerArgumentType.getInstance());
playerArg = cmds.createArgument("player", OfflinePlayerArgumentType.getInstance()); rootNode = CommandNode.node("add");
rootNode = cmds.createNode("add");
rootNode.addArgument(playerArg); rootNode.addArgument(playerArg);
rootNode.setExecutor(this); rootNode.setExecutor(this);
} }
@Override @Override
public void execute(BukkitCommandContext ctx) { public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSender> ctx) {
var handler = new FriendAddHandler(ctx.getPlayerSender(), ctx.getArg(playerArg)); var self = (Player) ctx.getSender();
handler.handle(); var other = ctx.getArg(playerArg);
return findAction(self, other)
.flatMap(action -> executeAction(self, other, action))
.toFuture();
}
private Mono<@NotNull Result> executeAction(Player self, OfflinePlayer other, AddAction action) {
return switch (action) {
case ERR_CAN_NOT_ADD_SELF -> Mono.just(Result.error("You can not add yourself"));
case ERR_ALREADY_FRIENDS -> Mono.just(Result.error("You are already friends with " + other.getName()));
case ERR_ALREADY_REQUESTED ->
Mono.just(Result.error("You already requested " + other.getName() + " to be friends with you"));
case SEND_REQUEST -> sendRequest(self, other);
case ACCEPT_REQUEST -> acceptRequest(self, other);
};
}
private Mono<@NotNull Result> sendRequest(Player self, OfflinePlayer other) {
return ScrowAPI.getFriendRequestService().saveFriendRequest(new FriendRequestImpl(self.getUniqueId(), other.getUniqueId(), Instant.now()))
.map(success -> {
if (!success) return Result.error("You already sent " + other.getName() + " a friend request");
self.sendMessage("Successfully sent " + other.getName() + " a friend request.");
var otherPlayer = other.getPlayer();
if (otherPlayer != null)
otherPlayer.sendMessage(" wants to be friends with you. Use '/f add " + self.getName() + "' to accept.");
return Result.success();
});
}
private Mono<@NotNull Result> acceptRequest(Player self, OfflinePlayer other) {
return Mono.when(
ScrowAPI.getFriendRequestService().deleteFriendRequest(self.getUniqueId(), other.getUniqueId()),
ScrowAPI.getFriendRequestService().deleteFriendRequest(other.getUniqueId(), self.getUniqueId()),
ScrowAPI.getFriendshipService().saveFriendship(new FriendshipImpl(self.getUniqueId(), other.getUniqueId(), Instant.now()))
)
.doOnNext(__ -> {
self.sendMessage("You are now friends with " + other.getName());
if (other.getPlayer() != null)
other.getPlayer().sendMessage("You are now friends with " + self.getName());
})
.then(Mono.just(Result.success()));
}
private Mono<@NotNull AddAction> findAction(Player self, OfflinePlayer other) {
if (self.getUniqueId() == other.getUniqueId()) return Mono.just(AddAction.ERR_CAN_NOT_ADD_SELF);
return ScrowAPI.getFriendshipService().getFriendship(self.getUniqueId(), other.getUniqueId())
.map(f -> AddAction.ERR_ALREADY_FRIENDS)
.switchIfEmpty(
ScrowAPI.getFriendRequestService().getFriendRequest(self.getUniqueId(), other.getUniqueId())
.map(r -> AddAction.ERR_ALREADY_REQUESTED)
.switchIfEmpty(
ScrowAPI.getFriendRequestService().getFriendRequest(other.getUniqueId(), self.getUniqueId())
.map(r -> AddAction.ACCEPT_REQUEST)
.switchIfEmpty(Mono.just(AddAction.SEND_REQUEST))
)
);
}
private enum AddAction {
ERR_ALREADY_FRIENDS,
ERR_ALREADY_REQUESTED,
ERR_CAN_NOT_ADD_SELF,
SEND_REQUEST,
ACCEPT_REQUEST
} }
} }

View file

@ -1,21 +1,25 @@
package de.kentoj.scrow.bukkit.friends.command; package de.kentoj.scrow.bukkit.friends.command;
import de.kentoj.kencommandapi.BukkitCommandContext; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.node.RootCommandNode;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrowlib.convention.ScrowMessageStyle;
import lombok.Getter; import lombok.Getter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
public class FriendCommand { public class FriendCommand {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final RootCommandNode<CommandSender> rootNode;
public FriendCommand() { public FriendCommand() {
this.rootNode = ScrowAPI.getCommandApi().createNode("friends", "f", "friend"); var style = new ScrowMessageStyle(Component.text("Friends")
rootNode.addLiteral(new FriendListLiteral().getRootNode()); .color(NamedTextColor.GREEN));
rootNode.addLiteral(new FriendAddLiteral().getRootNode()); this.rootNode = CommandNode.rootNode(style, "f", "friend");
rootNode.addLiteral(new FriendRemoveLiteral().getRootNode()); rootNode.addLiteral(new FriendListLiteral(style).getRootNode());
rootNode.addLiteral(new FriendRequestsLiteral().getRootNode()); rootNode.addLiteral(new FriendAddLiteral(style).getRootNode());
rootNode.addLiteral(new FriendRemoveLiteral(style).getRootNode());
rootNode.addLiteral(new FriendRequestsLiteral(style).getRootNode());
} }
} }

View file

@ -1,36 +1,51 @@
package de.kentoj.scrow.bukkit.friends.command; package de.kentoj.scrow.bukkit.friends.command;
import de.kentoj.kencommandapi.BukkitCommandContext; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.api.Results;
import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrow.bukkit.ScrowAPI;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class FriendListLiteral { import java.util.concurrent.CompletableFuture;
public class FriendListLiteral implements CommandExecutor<CommandSender> {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> rootNode;
public FriendListLiteral() { public FriendListLiteral() {
this.rootNode = ScrowAPI.getCommandApi().createNode("list"); this.rootNode = CommandNode.node("list");
rootNode.setExecutor(this::friendList); rootNode.setExecutor(this);
} }
private void friendList(BukkitCommandContext ctx) { @Override
var ownUid = ctx.getPlayerSender().getUniqueId(); public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSender> ctx) {
ctx.getPlayerSender().sendMessage("friends:"); var sender = (Player) ctx.getSender();
ScrowAPI.getFriendshipService().getFriendships(ownUid) var ownUid = sender.getUniqueId();
return ScrowAPI.getFriendshipService().getFriendships(ownUid)
.map(friendship -> Bukkit.getOfflinePlayer(friendship.getUuids().getOther(ownUid))) .map(friendship -> Bukkit.getOfflinePlayer(friendship.getUuids().getOther(ownUid)))
.sort(this::compareByOnlineStatus) .sort(this::compareByOnlineStatus)
.map(friendPlayer -> { .collectList()
var onlineStatus = friendPlayer.isOnline() ? "§aonline" : "offline"; .map(list -> {
return "- " + friendPlayer.getName() + ": " + onlineStatus; if (list.isEmpty()) return Result.error("You have no friends. Use /friend add <player>");
})
.doOnNext(line -> ctx.getSender().sendMessage(line)) var msg = "§3§lFriends§r§7 ☆ You have §3" + list.size() + "§7 friend";
.doOnComplete(() -> ctx.getSender().sendMessage("EOF")) if (list.size() > 1) msg += "s";
.subscribe(); msg += ":§r\n";
for (var friend : list) {
var status = friend.isOnline() ? "(§aonline§7)" : "(offline)";
msg += "§c≫ §7" + friend.getName() + " " + status + "\n";
}
sender.sendMessage(msg);
return Result.success();
}).toFuture();
} }
private int compareByOnlineStatus(OfflinePlayer p1, OfflinePlayer p2) { private int compareByOnlineStatus(OfflinePlayer p1, OfflinePlayer p2) {

View file

@ -1,34 +1,42 @@
package de.kentoj.scrow.bukkit.friends.command; package de.kentoj.scrow.bukkit.friends.command;
import de.kentoj.kencommandapi.BukkitCommandContext; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.argument.CommandArgument;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.api.Results;
import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType; import de.kentoj.kencommandapi.type.OfflinePlayerArgumentType;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrow.bukkit.ScrowAPI;
import lombok.Getter; import lombok.Getter;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandException;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class FriendRemoveLiteral { import java.util.concurrent.CompletableFuture;
public class FriendRemoveLiteral implements CommandExecutor<CommandSender> {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, OfflinePlayer> playerArg; private final CommandArgument<CommandSender, OfflinePlayer> playerArg;
public FriendRemoveLiteral() { public FriendRemoveLiteral() {
this.playerArg = ScrowAPI.getCommandApi().createArgument("player", OfflinePlayerArgumentType.getInstance()); this.playerArg = CommandArgument.arg("player", OfflinePlayerArgumentType.getInstance());
this.rootNode = ScrowAPI.getCommandApi().createNode("remove"); this.rootNode = CommandNode.node("remove");
this.rootNode.addArgument(playerArg); this.rootNode.addArgument(playerArg);
this.rootNode.setExecutor(this::removeFriend); this.rootNode.setExecutor(this);
} }
private void removeFriend(BukkitCommandContext ctx) { @Override
public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSender> ctx) {
var friend = ctx.getArg(playerArg); var friend = ctx.getArg(playerArg);
var player = (Player) ctx.getSender();
ScrowAPI.getFriendshipService().deleteFriendship(friend.getUniqueId(), ctx.getPlayerSender().getUniqueId()) return ScrowAPI.getFriendshipService().deleteFriendship(friend.getUniqueId(), player.getUniqueId())
.subscribe(wasDeleted -> { .map(wasAdded -> wasAdded
if (wasDeleted) throw new CommandException("You were not friends with " + friend.getName() + " in the first place"); ? Result.success() : Result.error("You are not friends with " + friend.getName())
ctx.getSender().sendMessage("You are no longer friends with " + friend.getName()); ).doOnNext(success -> {
}); if (success.isSuccess())
ctx.getSender().sendMessage("You are no longer friends with " + friend.getName());
}).toFuture();
} }
} }

View file

@ -1,32 +1,45 @@
package de.kentoj.scrow.bukkit.friends.command; package de.kentoj.scrow.bukkit.friends.command;
import de.kentoj.kencommandapi.BukkitCommandContext; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.api.Results;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.friends.FriendRequest;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class FriendRequestsLiteral { import java.util.concurrent.CompletableFuture;
public class FriendRequestsLiteral implements CommandExecutor<CommandSender> {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> rootNode;
public FriendRequestsLiteral() { public FriendRequestsLiteral() {
this.rootNode = ScrowAPI.getCommandApi().createNode("requests", "list-requests"); this.rootNode = CommandNode.node("requests", "list-requests");
rootNode.setExecutor(this::friendRequestsList); rootNode.setExecutor(this);
} }
private void friendRequestsList(BukkitCommandContext ctx) { @Override
var ownUid = ctx.getPlayerSender().getUniqueId(); public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSender> ctx) {
ctx.getPlayerSender().sendMessage("incoming friends requests:"); var sender = (Player) ctx.getSender();
ScrowAPI.getFriendRequestService().getFriendRequestsTo(ownUid) return ScrowAPI.getFriendRequestService().getFriendRequestsTo(sender.getUniqueId())
.map(friendRequest -> { .collectList()
var fromUid = friendRequest.getFrom(); .map(list -> {
var fromName = Bukkit.getOfflinePlayer(fromUid).getName(); if (list.isEmpty()) return Result.error("You have no friend requests");
return "- " + fromName;
}).doOnNext(line -> ctx.getPlayerSender().sendMessage(line)) var msg = "§3§lFriends§r§7 ☆ You have §3" + list.size() + "§7 open friend request";
.doOnComplete(() -> ctx.getPlayerSender().sendMessage("EOF")) if (list.size() > 1) msg += "s";
.subscribe(); msg += ":§r\n";
for (FriendRequest friendRequest : list) {
msg += "§c≫ §7" + friendRequest.getFrom() + "\n";
}
sender.sendMessage(msg);
return Result.success();
}).toFuture();
} }
} }

View file

@ -17,7 +17,7 @@ public class InMemoryFriendshipService implements FriendshipService {
@Override @Override
public Mono<@NotNull Friendship> getFriendship(UUID uuid1, UUID uuid2) { public Mono<@NotNull Friendship> getFriendship(UUID uuid1, UUID uuid2) {
return Mono.just(friendships.get(new OrderedUUIDPair(uuid1, uuid2))); return Mono.justOrEmpty(friendships.get(new OrderedUUIDPair(uuid1, uuid2)));
} }
@Override @Override

View file

@ -1,108 +0,0 @@
package de.kentoj.scrow.bukkit.friends.handler;
import de.kentoj.kencommandapi.api.processing.CommandException;
import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.friends.friendship.FriendshipImpl;
import de.kentoj.scrow.bukkit.friends.request.FriendRequestImpl;
import lombok.RequiredArgsConstructor;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Mono;
import java.time.Instant;
@RequiredArgsConstructor
public class FriendAddHandler {
private final Player self;
private final OfflinePlayer other;
public void handle() {
findAction()
.flatMap(this::executeAction)
.doOnError(CommandException.class, e -> {
ScrowAPI.getCommandApi().sendErrorMessage(self, e);
})
.onErrorComplete(CommandException.class)
.subscribe();
}
private Mono<?> executeAction(AddAction action) {
return switch (action) {
case ERR_CAN_NOT_ADD_SELF -> throw new CommandException("You can not add yourself");
case ERR_ALREADY_FRIENDS -> throw new CommandException("You are already friends with " + other.getName());
case ERR_ALREADY_REQUESTED ->
throw new CommandException("You already requested " + other.getName() + " to be friends with you");
case SEND_REQUEST -> sendRequest();
case ACCEPT_REQUEST -> acceptRequest();
};
}
private Mono<?> sendRequest() {
return ScrowAPI.getFriendRequestService().saveFriendRequest(new FriendRequestImpl(self.getUniqueId(), other.getUniqueId(), Instant.now()))
.flatMap(success -> {
if (success) {
sendMessage(self.getPlayer(), "Successfully sent " + other.getName() + " a friend request.");
return Mono.just(true);
} else {
sendMessage(self.getPlayer(), "You already sent " + other.getName() + " a friend request");
return Mono.just(Mono.empty());
}
})
.doOnNext(success -> {
var otherPlayer = other.getPlayer();
if (otherPlayer != null)
sendMessage(otherPlayer, self.getName() + " wants to be friends with you. Use '/f add " + self.getName() + "' to accept.").subscribe();
})
.doOnError(this::handleError);
}
private Mono<?> acceptRequest() {
return Mono.when(
ScrowAPI.getFriendRequestService().deleteFriendRequest(self.getUniqueId(), other.getUniqueId()),
ScrowAPI.getFriendRequestService().deleteFriendRequest(other.getUniqueId(), self.getUniqueId()),
ScrowAPI.getFriendshipService().saveFriendship(new FriendshipImpl(self.getUniqueId(), other.getUniqueId(), Instant.now()))
)
.then(sendMessage(self, "You are now friends with " + other.getName()))
.then(Mono.fromRunnable(() -> {
var otherPlayer = other.getPlayer();
if (otherPlayer != null)
sendMessage(otherPlayer, "You are now friends with " + self.getName()).subscribe();
}))
.doOnError(this::handleError);
}
private Mono<@NotNull AddAction> findAction() {
if (self.getUniqueId() == other.getUniqueId()) return Mono.just(AddAction.ERR_CAN_NOT_ADD_SELF);
return ScrowAPI.getFriendshipService().getFriendship(self.getUniqueId(), other.getUniqueId())
.map(f -> AddAction.ERR_ALREADY_FRIENDS)
.switchIfEmpty(
ScrowAPI.getFriendRequestService().getFriendRequest(self.getUniqueId(), other.getUniqueId())
.map(r -> AddAction.ERR_ALREADY_REQUESTED)
.switchIfEmpty(
ScrowAPI.getFriendRequestService().getFriendRequest(other.getUniqueId(), self.getUniqueId())
.map(r -> AddAction.ACCEPT_REQUEST)
.switchIfEmpty(Mono.just(AddAction.SEND_REQUEST))
)
);
}
private Mono<?> sendMessage(Player player, String msg) {
return Mono.fromRunnable(() -> player.sendMessage(msg))
.subscribeOn(ScrowAPI.getMinecraftScheduler());
}
private void handleError(Throwable e) {
sendMessage(self.getPlayer(), "FAILED SENDING REQUEST. REPORT THIS BUG. " + e.getMessage()).subscribe();
}
private enum AddAction {
ERR_ALREADY_FRIENDS,
ERR_ALREADY_REQUESTED,
ERR_CAN_NOT_ADD_SELF,
SEND_REQUEST,
ACCEPT_REQUEST
}
}

View file

@ -0,0 +1,23 @@
package de.kentoj.scrow.bukkit.playermanager;
import com.leakyabstractions.result.api.Result;
import de.kentoj.scrow.bukkit.PlayerManager;
import de.kentoj.scrowlib.messaging.MessageBroker;
import lombok.RequiredArgsConstructor;
import org.bson.Document;
import java.util.UUID;
@RequiredArgsConstructor
public class PlayerManagerImpl implements PlayerManager {
private final MessageBroker messageBroker;
@Override
public Result<Void, String> sendPlayer(UUID playerId, String instanceHandle) {
return messageBroker.request("players.send", new Document()
.append("playerId", playerId)
.append("handle", instanceHandle))
.mapSuccess(__ -> null);
}
}

View file

@ -1,26 +1,29 @@
package de.kentoj.scrow.bukkit.server; package de.kentoj.scrow.bukkit.server;
import de.kentoj.kencommandapi.BukkitCommandContext; import com.leakyabstractions.result.api.Result;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.processing.CommandExecutor; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
public class LobbyCommand implements CommandExecutor<CommandSender, BukkitCommandContext> { import java.util.concurrent.CompletableFuture;
public class LobbyCommand implements CommandExecutor<CommandSender> {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> rootNode;
public LobbyCommand() { public LobbyCommand() {
var cmds = ScrowAPI.getCommandApi(); this.rootNode = CommandNode.node("lobby", "l", "hub");
this.rootNode = cmds.createNode("lobby", "l", "hub");
this.rootNode.setExecutor(this); this.rootNode.setExecutor(this);
} }
@Override @Override
public void execute(BukkitCommandContext ctx) { public CompletableFuture<Result<Void, String>> execute(CommandContext<CommandSender> ctx) {
Bukkit.dispatchCommand(ctx.getSender(), "play lobby"); Bukkit.dispatchCommand(ctx.getSender(), "play lobby");
return CompletableFuture.completedFuture(Results.success(null));
} }
} }

View file

@ -1,43 +1,44 @@
package de.kentoj.scrow.bukkit.server; package de.kentoj.scrow.bukkit.server;
import de.kentoj.kencommandapi.BukkitCommandContext; import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.argument.CommandArgument;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.processing.CommandExecutor; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.types.StringArgumentType; import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.scrow.bukkit.ScrowAPI; import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrowlib.servermanager.ServerInstance;
import lombok.Getter; import lombok.Getter;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import reactor.core.publisher.Mono; import org.bukkit.entity.Player;
public class PlayCommand implements CommandExecutor<CommandSender, BukkitCommandContext> { import java.util.concurrent.CompletableFuture;
public class PlayCommand implements CommandExecutor<CommandSender> {
@Getter @Getter
private final CommandNode<CommandSender, BukkitCommandContext> rootNode; private final CommandNode<CommandSender> rootNode;
private final CommandArgument<CommandSender, BukkitCommandContext, String> gamemodeArg; private final CommandArgument<CommandSender, String> gamemodeArg;
public PlayCommand() { public PlayCommand() {
this.rootNode = ScrowAPI.getCommandApi().createNode("play", "queue"); this.rootNode = CommandNode.node("play", "queue");
this.gamemodeArg = ScrowAPI.getCommandApi().createArgument("gamemode", new StringArgumentType<>()); this.gamemodeArg = CommandArgument.arg("gamemode", new StringArgumentType<>());
this.rootNode.addArgument(this.gamemodeArg); this.rootNode.addArgument(this.gamemodeArg);
this.rootNode.setExecutor(this); this.rootNode.setExecutor(this);
} }
@Override @Override
public void execute(BukkitCommandContext ctx) { public CompletableFuture<Result<Void, String>> execute(CommandContext<CommandSender> ctx) {
var gamemode = ctx.getArg(gamemodeArg); var gamemode = ctx.getArg(gamemodeArg);
ScrowAPI.getServerManager().getRunningServers() var player = (Player) ctx.getSender();
.filter(inst -> inst.getTemplateName().equals(gamemode)) return CompletableFuture.supplyAsync(() -> ScrowAPI.getInstanceManager().getInstances()
.filter(inst -> inst.getState().equals("UP")) .flatMapSuccess(instances -> {
.next() var instance = instances.stream()
.switchIfEmpty(Mono.error(new IllegalStateException("No instance available"))) .filter(inst -> inst.getTemplate().equals(gamemode))
.map(ServerInstance::getHandle) .findFirst()
.flatMap(handle -> .orElse(null);
ScrowAPI.getServerManager().sendPlayer(ctx.getPlayerSender().getUniqueId(), handle) if (instance == null) return Results.failure("No instance available");
) return ScrowAPI.getPlayerManager().sendPlayer(player.getUniqueId(), instance.getHandle());
.subscribe(null, ex -> { }));
ctx.getPlayerSender().sendMessage("failed sending you to an instance of " + gamemode + ": " + ex.getMessage());
});
} }
} }

View file

@ -21,6 +21,9 @@ dependencies {
// https://mvnrepository.com/artifact/org.mongodb/bson // https://mvnrepository.com/artifact/org.mongodb/bson
implementation("org.mongodb:bson:5.8.0") implementation("org.mongodb:bson:5.8.0")
api("org.mongodb:bson:5.8.0") api("org.mongodb:bson:5.8.0")
// http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core
api("de.kentoj.scrow:kencommandapi-core:0.21")
} }
publishing { publishing {

View file

@ -0,0 +1,57 @@
package de.kentoj.scrowlib.convention;
import de.kentoj.kencommandapi.api.platform.MessageStyle;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import java.util.List;
public class ScrowMessageStyle implements MessageStyle {
@Getter
private final Component prefix;
@Getter
private final String prefixText;
@Getter
private final TextColor prefixColor;
public ScrowMessageStyle(String prefixText, TextColor prefixColor) {
this.prefixText = prefixText;
this.prefixColor = prefixColor;
this.prefix = Component.text(prefixText)
.color(prefixColor)
.decorate(TextDecoration.BOLD);
}
@Override
public Component ok(Component msg) {
return prefix.append(Component.text("").color(NamedTextColor.GRAY))
.append(msg.colorIfAbsent(NamedTextColor.GRAY));
}
public Component list(Component header, List<? extends Component> list) {
var res = ok(header);
for (Component component : list) {
res = res.appendNewline()
.append(Component.text(" - ").color(NamedTextColor.GRAY))
.append(component.colorIfAbsent(NamedTextColor.GRAY));
}
return res;
}
@Override
public Component err(Component msg) {
return ok(msg.color(NamedTextColor.RED));
}
@Override
public Component exception(Component msg) {
return ok(Component.text("ERROR: ")
.color(NamedTextColor.DARK_RED)
.append(msg)
.decorate(TextDecoration.BOLD));
}
}

View file

@ -0,0 +1,46 @@
package de.kentoj.scrowlib.messaging;
import com.leakyabstractions.result.api.Result;
import io.nats.client.Connection;
import io.nats.client.Dispatcher;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public class MessageBroker {
private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
private final Connection nats;
protected final Dispatcher dispatcher;
public MessageBroker(Connection nats) {
this.nats = nats;
this.dispatcher = nats.createDispatcher();
}
public void publish(String subject, Result<Document, String> result) {
nats.publish(subject, ResultRepository.toBytes(result));
}
public void handle(String subject, RequestHandler handler) {
dispatcher.subscribe(subject, msg -> {
if (msg.getReplyTo() == null) {
log.warn("request in {} has replyTo unset; ignoring", subject);
return;
}
handler.handle(DocumentRepository.fromMessage(msg))
.thenAccept(result ->
executor.submit(() -> publish(msg.getReplyTo(), result))
);
});
}
public Result<Document, String> request(String subject, Document document) {
var response = nats.request(subject, DocumentRepository.toBytes(document)).join();
return ResultRepository.fromDocument(DocumentRepository.fromMessage(response));
}
}

View file

@ -1,57 +0,0 @@
package de.kentoj.scrowlib.messaging;
import de.kentoj.scrowlib.messaging.respose.SafeResult;
import io.nats.client.Connection;
import io.nats.client.Dispatcher;
import io.nats.client.Message;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.concurrent.atomic.AtomicBoolean;
public class NatsRepository {
private final Connection nats;
private final Dispatcher dispatcher;
public NatsRepository(Connection nats) {
this.nats = nats;
this.dispatcher = nats.createDispatcher();
}
public Mono<@NotNull Void> publishSafeResult(String subject, SafeResult safeResult) {
return this.publish(subject, safeResult.toDocument());
}
public Mono<@NotNull Void> publish(String subject, Document document) {
return publishRaw(subject, DocumentRepository.toBytes(document));
}
public Mono<@NotNull Void> publishRaw(String subject, byte[] data) {
return Mono.fromRunnable(() -> nats.publish(subject, data));
}
public Flux<@NotNull SubscriptionContext> subscribe(String subject) {
return Flux.create(sink -> {
dispatcher.subscribe(subject, msg -> {
try {
sink.next(new SubscriptionContext(msg));
} catch (Throwable e) {
sink.error(e);
}
});
});
}
public Mono<@NotNull Message> requestRaw(String subject, Document document) {
return Mono.fromFuture(nats.request(subject, DocumentRepository.toBytes(document)));
}
public Mono<@NotNull Message> requestRaw(String subject, byte[] data) {
return Mono.fromFuture(nats.request(subject, data));
}
}

View file

@ -0,0 +1,16 @@
package de.kentoj.scrowlib.messaging;
import com.leakyabstractions.result.api.Result;
import org.bson.Document;
import java.util.concurrent.CompletionStage;
@FunctionalInterface
public interface RequestHandler {
/**
* Processes a request and replies
*
* @return result to send back to the requesting client.
*/
CompletionStage<Result<Document, String>> handle(Document request);
}

View file

@ -0,0 +1,26 @@
package de.kentoj.scrowlib.messaging;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.bson.Document;
@NoArgsConstructor(access = AccessLevel.NONE)
public class ResultRepository {
public static byte[] toBytes(Result<Document, String> result) {
var doc = result.hasSuccess()
? new Document("data", result.getSuccess().orElseThrow())
: new Document("error", result.getFailure().orElseThrow());
return DocumentRepository.toBytes(doc);
}
public static Result<Document, String> fromDocument(Document doc) {
var error = doc.getString("error");
return error != null
? Results.failure(error)
: Results.success(doc.get("data", Document.class));
}
}

View file

@ -1,17 +0,0 @@
package de.kentoj.scrowlib.messaging;
import io.nats.client.Message;
import lombok.AllArgsConstructor;
import lombok.Value;
import org.bson.Document;
@Value
@AllArgsConstructor
public class SubscriptionContext {
Message msg;
Document document;
public SubscriptionContext(Message msg) {
this(msg, DocumentRepository.fromMessage(msg));
}
}

View file

@ -1,34 +0,0 @@
package de.kentoj.scrowlib.messaging.respose;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.bson.Document;
@Getter
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public final class SafeErrorResult extends SafeResult {
private final String type;
private final String message;
public SafeErrorResult(Throwable throwable) {
this.type = throwable.getClass().getCanonicalName();
this.message = throwable.getMessage();
}
@Override
public Document unwrap() {
throw this.toException();
}
public RuntimeException toException() {
return new RuntimeException(message);
}
@Override
public Document toDocument() {
return new Document("error", new Document()
.append("type", this.type)
.append("message", this.getMessage()));
}
}

View file

@ -1,71 +0,0 @@
package de.kentoj.scrowlib.messaging.respose;
import de.kentoj.scrowlib.messaging.DocumentRepository;
import io.nats.client.Message;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Mono;
public abstract sealed class SafeResult permits SafeErrorResult, SafeSuccessResult {
/**
* @return the SafeResult as a Document for transit
*/
public abstract Document toDocument();
/**
* @return the data stored in the SafeResult
* @throws RuntimeException if the SafeResult represents an error
*/
public abstract Document unwrap();
public boolean isError() {
return this instanceof SafeErrorResult;
}
public boolean isSuccess() {
return this instanceof SafeSuccessResult;
}
public SafeErrorResult asError() {
return (SafeErrorResult) this;
}
public SafeSuccessResult asSuccess() {
return (SafeSuccessResult) this;
}
public static Document unwrap(Message message) {
return fromMessage(message).unwrap();
}
public static SafeResult wrap(Document document) {
return new SafeSuccessResult(document);
}
public static SafeResult wrap(Throwable throwable) {
return new SafeErrorResult(throwable);
}
public static SafeResult fromMessage(Message message) {
return fromDocument(DocumentRepository.fromMessage(message));
}
public static SafeResult fromDocument(Document document) {
if (document.containsKey("error")) {
var doc = document.get("error", Document.class);
return new SafeErrorResult(
doc.getString("type"),
doc.getString("message")
);
} else if (document.containsKey("data")) {
return new SafeSuccessResult(document.get("data", Document.class));
}
throw new IllegalArgumentException("document does not conform to SafeDocument format");
}
public static Mono<@NotNull SafeResult> mono(Mono<@NotNull Document> mono) {
return mono.map(SafeResult::wrap)
.onErrorResume(err -> Mono.just(SafeResult.wrap(err)));
}
}

View file

@ -1,22 +0,0 @@
package de.kentoj.scrowlib.messaging.respose;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.bson.Document;
@Getter
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public final class SafeSuccessResult extends SafeResult {
private final Document data;
@Override
public Document unwrap() {
return this.getData();
}
@Override
public Document toDocument() {
return new Document("data", this.getData());
}
}

View file

@ -0,0 +1,23 @@
package de.kentoj.scrowlib.servermanager;
import com.leakyabstractions.result.api.Result;
import org.jspecify.annotations.Nullable;
import java.util.List;
public interface InstanceManager {
/**
* @return Mono with handle of deployed server
*/
Result<ServerInstance, String> deployInstance(String template);
Result<Void, String> destroyInstance(String handle);
/**
* @return Flux with handles of running servers
*/
Result<List<ServerInstance>, String> getInstances();
Result<@Nullable ServerInstance, String> getInstance(String handle);
}

View file

@ -0,0 +1,45 @@
package de.kentoj.scrowlib.servermanager;
import com.leakyabstractions.result.api.Result;
import de.kentoj.scrowlib.messaging.MessageBroker;
import io.nats.client.Connection;
import org.bson.Document;
import org.jspecify.annotations.Nullable;
import java.util.List;
public class InstanceManagerImpl implements InstanceManager {
private final MessageBroker messageBroker;
public InstanceManagerImpl(Connection natsCon) {
messageBroker = new MessageBroker(natsCon);
}
@Override
public Result<ServerInstance, String> deployInstance(String template) {
var result = messageBroker.request("instance.deploy", new Document("template", template));
return result.mapSuccess(ServerInstance::fromDocument);
}
@Override
public Result<Void, String> destroyInstance(String handle) {
var result = messageBroker.request("instance.destroy", new Document("handle", handle));
return result.mapSuccess(__ -> null);
}
@Override
public Result<List<ServerInstance>, String> getInstances() {
var result = messageBroker.request("instance.list", new Document());
return result.mapSuccess(doc -> doc.getList("instances", Document.class)
.stream()
.map(ServerInstance::fromDocument)
.toList());
}
@Override
public Result<@Nullable ServerInstance, String> getInstance(String handle) {
var result = messageBroker.request("instance.get", new Document("handle", handle));
return result.mapSuccess(ServerInstance::fromDocument);
}
}

View file

@ -2,25 +2,27 @@ package de.kentoj.scrowlib.servermanager;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import lombok.Value; import lombok.Value;
import org.bson.Document;
@Value @Value
public class ServerInstance { public class ServerInstance {
String templateName;
String handle; String handle;
String template;
int port; int port;
String state;
public ServerInstance(String templateName, String handle, int port, String state) { public ServerInstance(String handle, String template, int port) {
Preconditions.checkNotNull(templateName); Preconditions.checkNotNull(template);
Preconditions.checkNotNull(handle); Preconditions.checkNotNull(handle);
Preconditions.checkNotNull(state); this.template = template;
this.templateName = templateName;
this.handle = handle; this.handle = handle;
this.port = port; this.port = port;
this.state = state;
} }
public ServerInstance(String templateName, String handle, int port) { static ServerInstance fromDocument(Document document) {
this(templateName, handle, port, "STARTING"); return new ServerInstance(
document.getString("handle"),
document.getString("template"),
document.getInteger("port")
);
} }
} }

View file

@ -1,31 +0,0 @@
package de.kentoj.scrowlib.servermanager;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.UUID;
public interface ServerManager {
/**
* @return Mono with handle of deployed server
*/
Mono<@NotNull ServerInstance> deployServer(String template);
/**
* Attempts a clean shutdown, if that doesn't succeed, kills the server.
*/
Mono<@NotNull Void> destroyServer(String handle);
/**
* @return Flux with handles of running servers
*/
Flux<@NotNull ServerInstance> getRunningServers();
Mono<@NotNull ServerInstance> getInfo(String handle);
Mono<@NotNull Void> setState(String handle, String state);
Mono<@NotNull Void> sendPlayer(UUID playerId, String serverHandle);
}

View file

@ -1,88 +0,0 @@
package de.kentoj.scrowlib.servermanager;
import com.google.common.base.Preconditions;
import de.kentoj.scrowlib.messaging.NatsRepository;
import de.kentoj.scrowlib.messaging.respose.SafeResult;
import io.nats.client.Connection;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.UUID;
public class ServerManagerImpl implements ServerManager {
private final NatsRepository natsRepo;
public ServerManagerImpl(Connection nats) {
this.natsRepo = new NatsRepository(nats);
}
@Override
public Mono<@NotNull ServerInstance> deployServer(String template) {
return natsRepo.requestRaw("crown.deploy-server", new Document("template", template))
.map(SafeResult::unwrap)
.map(doc -> new ServerInstance(
template,
doc.getString("handle"),
doc.getInteger("port")
));
}
@Override
public Flux<@NotNull ServerInstance> getRunningServers() {
return natsRepo.requestRaw("crown.list-servers", new Document())
.map(SafeResult::unwrap)
.map(doc ->
doc.getList("instances", Document.class)
)
.flatMapIterable(l -> l)
.map(doc -> new ServerInstance(
doc.getString("templateName"),
doc.getString("handle"),
doc.getInteger("port"),
doc.getString("state")
));
}
@Override
public Mono<@NotNull Void> destroyServer(String handle) {
return natsRepo.requestRaw("crown.destroy-server", new Document("handle", handle))
.map(SafeResult::unwrap)
.then();
}
@Override
public Mono<@NotNull ServerInstance> getInfo(String handle) {
return natsRepo.requestRaw("crown.get-info", new Document("handle", handle))
.map(SafeResult::unwrap)
.map(doc -> new ServerInstance(
doc.getString("templateName"),
handle,
doc.getInteger("port"),
doc.getString("state")
));
}
@Override
public Mono<@NotNull Void> setState(String handle, String state) {
Preconditions.checkArgument(!state.contains(" "), "state may not contain spaces");
var payload = new Document()
.append("handle", handle)
.append("state", state);
return natsRepo.requestRaw("crown.set-state", payload)
.map(SafeResult::unwrap)
.then();
}
@Override
public Mono<@NotNull Void> sendPlayer(UUID playerId, String serverHandle) {
var payload = new Document()
.append("playerId", playerId.toString())
.append("handle", serverHandle);
return natsRepo.requestRaw("proxy.send-player", payload)
.map(SafeResult::unwrap)
.then();
}
}

View file

@ -0,0 +1,13 @@
package de.kentoj.scrowlib.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.NONE)
public class EnvUtils {
public static String envOrThrow(String name) {
var env = System.getenv(name);
if (env == null) throw new IllegalStateException("environment variable " + name + " not set");
return env;
}
}

View file

@ -4,6 +4,7 @@ plugins {
id("de.kentoj.scrow.scrow-repository") id("de.kentoj.scrow.scrow-repository")
id("de.kentoj.scrow.base-dependencies") id("de.kentoj.scrow.base-dependencies")
id("de.kentoj.scrow.java-library") id("de.kentoj.scrow.java-library")
id("de.kentoj.scrow.reactor")
} }
group = "de.kentoj.scrow" group = "de.kentoj.scrow"
@ -23,9 +24,9 @@ dependencies {
api("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT") api("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT")
// http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core // http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-core
api("de.kentoj.scrow:kencommandapi-core:0.8") api("de.kentoj.scrow:kencommandapi-core:0.21")
// http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-velocity // http://forge.kentoj.de/scrow/-/packages/maven/de.kentoj.scrow:kencommandapi-velocity
api("de.kentoj.scrow:kencommandapi-velocity:0.8") api("de.kentoj.scrow:kencommandapi-velocity:0.21")
} }
publishing { publishing {

View file

@ -1,8 +1,8 @@
package de.kentoj.scrow.velocity; package de.kentoj.scrow.velocity;
import de.kentoj.kencommandapi.VelocityKenCommandApi; import de.kentoj.kencommandapi.CommandAPI;
import de.kentoj.scrowlib.connection.DatabaseConnectionFactory; import de.kentoj.scrowlib.connection.DatabaseConnectionFactory;
import de.kentoj.scrowlib.servermanager.ServerManager; import de.kentoj.scrowlib.servermanager.InstanceManager;
import io.nats.client.Connection; import io.nats.client.Connection;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
@ -16,14 +16,14 @@ public class ScrowAPI {
@Getter @Getter
private static DatabaseConnectionFactory dbConFactory; private static DatabaseConnectionFactory dbConFactory;
@Getter @Getter
private static VelocityKenCommandApi commandApi; private static CommandAPI commandApi;
@Getter @Getter
private static Connection nats; private static Connection nats;
@Getter @Getter
private static ServerManager serverManager; private static InstanceManager instanceManager;
@ApiStatus.Internal @ApiStatus.Internal
public static void setCommandApi(VelocityKenCommandApi commandApi) { public static void setCommandApi(CommandAPI commandApi) {
ScrowAPI.commandApi = commandApi; ScrowAPI.commandApi = commandApi;
} }
@ -33,8 +33,8 @@ public class ScrowAPI {
} }
@ApiStatus.Internal @ApiStatus.Internal
public static void setServerManager(ServerManager serverManager) { public static void setInstanceManager(InstanceManager instanceManager) {
ScrowAPI.serverManager = serverManager; ScrowAPI.instanceManager = instanceManager;
} }
@ApiStatus.Internal @ApiStatus.Internal

View file

@ -4,6 +4,7 @@ plugins {
id("de.kentoj.scrow.scrow-repository") id("de.kentoj.scrow.scrow-repository")
id("de.kentoj.scrow.base-dependencies") id("de.kentoj.scrow.base-dependencies")
id("de.kentoj.scrow.database") id("de.kentoj.scrow.database")
id("de.kentoj.scrow.reactor")
} }
group = "de.kentoj.scrow" group = "de.kentoj.scrow"
@ -23,8 +24,6 @@ dependencies {
// https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine // https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine
implementation("com.github.ben-manes.caffeine:caffeine:3.2.4") implementation("com.github.ben-manes.caffeine:caffeine:3.2.4")
implementation("de.kentoj.scrow:kencommandapi-velocity")
} }
val generateBuildInfo by tasks.registering { val generateBuildInfo by tasks.registering {

View file

@ -33,7 +33,7 @@ public class CoreVelocityPlugin {
cmds.register(new OnlineCommand(server).getRootNode()); cmds.register(new OnlineCommand(server).getRootNode());
} }
var registerWatchdog = new ServerRegisterWatchdog(ScrowAPI.getNats(), ScrowAPI.getServerManager(), server); var registerWatchdog = new ServerRegisterWatchdog(ScrowAPI.getNats(), ScrowAPI.getInstanceManager(), server);
var sendPlayerWatchdog = new SendPlayerWatchdog(ScrowAPI.getNats(), server); var sendPlayerWatchdog = new SendPlayerWatchdog(ScrowAPI.getNats(), server);
registerWatchdog.listen(); registerWatchdog.listen();
sendPlayerWatchdog.listen(); sendPlayerWatchdog.listen();

View file

@ -1,10 +1,10 @@
package de.kentoj.scrow.corevelocity; package de.kentoj.scrow.corevelocity;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.kencommandapi.VelocityKenCommandApi; import de.kentoj.kencommandapi.CommandAPI;
import de.kentoj.scrow.velocity.ScrowAPI; import de.kentoj.scrow.velocity.ScrowAPI;
import de.kentoj.scrowlib.connection.DatabaseConnectionFactoryImpl; import de.kentoj.scrowlib.connection.DatabaseConnectionFactoryImpl;
import de.kentoj.scrowlib.servermanager.ServerManagerImpl; import de.kentoj.scrowlib.utils.EnvUtils;
import io.nats.client.Nats; import io.nats.client.Nats;
import io.nats.client.Options; import io.nats.client.Options;
import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactories;
@ -19,7 +19,7 @@ public class ScrowAPISurface {
public static void initScrowAPI(ProxyServer server, boolean enableDatabase) { public static void initScrowAPI(ProxyServer server, boolean enableDatabase) {
try { try {
String natsHost = System.getenv("HOST_NATS"); String natsHost = EnvUtils.envOrThrow("HOST_NATS");
var options = Options.builder() var options = Options.builder()
.server(natsHost) .server(natsHost)
.pedantic() .pedantic()
@ -30,13 +30,13 @@ public class ScrowAPISurface {
} }
if (enableDatabase) { if (enableDatabase) {
ConnectionFactory factory = ConnectionFactories.get(System.getenv("HOST_POSTGRES")); ConnectionFactory factory = ConnectionFactories.get(EnvUtils.envOrThrow("HOST_POSTGRES"));
ScrowAPI.setDbConFactory(new DatabaseConnectionFactoryImpl(factory)); ScrowAPI.setDbConFactory(new DatabaseConnectionFactoryImpl(factory));
} }
ScrowAPI.setServerManager(new ServerManagerImpl(ScrowAPI.getNats())); ScrowAPI.setInstanceManager(new ServerManagerImpl(ScrowAPI.getNats()));
ScrowAPI.setCommandApi(new VelocityKenCommandApi(server)); ScrowAPI.setCommandApi(new CommandAPI(server));
} }
public static void destroyScrowAPI() { public static void destroyScrowAPI() {

View file

@ -2,34 +2,38 @@ package de.kentoj.scrow.corevelocity.command;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.kencommandapi.VelocityCommandContext; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.processing.CommandExecutor; import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import de.kentoj.scrow.velocity.ScrowAPI; import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.api.Results;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import java.util.concurrent.CompletableFuture;
@RequiredArgsConstructor @RequiredArgsConstructor
public class OnlineCommand implements CommandExecutor<CommandSource, VelocityCommandContext> { public class OnlineCommand implements CommandExecutor<CommandSource> {
@Getter @Getter
private final CommandNode<CommandSource, VelocityCommandContext> rootNode; private final CommandNode<CommandSource> rootNode;
private final ProxyServer server; private final ProxyServer server;
public OnlineCommand(ProxyServer server) { public OnlineCommand(ProxyServer server) {
this.server = server; this.server = server;
rootNode = ScrowAPI.getCommandApi().createNode("online"); rootNode = CommandNode.node("online");
rootNode.setExecutor(this); rootNode.setExecutor(this);
} }
@Override @Override
public void execute(VelocityCommandContext ctx) { public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSource> ctx) {
int cnt = server.getPlayerCount(); int cnt = server.getPlayerCount();
var msg = cnt != 1 var msg = cnt != 1
? "There are currently " + cnt + " players online" ? "There are currently " + cnt + " players online"
: "There is currently 1 player online"; : "There is currently 1 player online";
ctx.getPlayerSender().sendMessage(Component.text(msg)); ctx.getSender().sendMessage(Component.text(msg));
return Result.success().toFuture();
} }
} }

View file

@ -3,46 +3,50 @@ package de.kentoj.scrow.corevelocity.privmsg;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.kencommandapi.VelocityCommandContext; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.argument.CommandArgument;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.processing.CommandException; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.processing.CommandExecutor; import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import de.kentoj.kencommandapi.api.types.StringArgumentType; import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.api.Results;
import de.kentoj.kencommandapi.type.PlayerArgumentType; import de.kentoj.kencommandapi.type.PlayerArgumentType;
import de.kentoj.scrow.velocity.ScrowAPI; import de.kentoj.scrow.velocity.ScrowAPI;
import lombok.Getter; import lombok.Getter;
public class MsgCommand implements CommandExecutor<CommandSource, VelocityCommandContext> { import java.util.concurrent.CompletableFuture;
public class MsgCommand implements CommandExecutor<CommandSource> {
@Getter @Getter
private final CommandNode<CommandSource, VelocityCommandContext> rootNode; private final CommandNode<CommandSource> rootNode;
private final CommandArgument<CommandSource, VelocityCommandContext, Player> targetArg; private final CommandArgument<CommandSource, Player> targetArg;
private final CommandArgument<CommandSource, VelocityCommandContext, String> msgArg; private final CommandArgument<CommandSource, String> msgArg;
private final LastTargetCache cache; private final LastTargetCache cache;
public MsgCommand(ProxyServer server, LastTargetCache cache) { public MsgCommand(ProxyServer server, LastTargetCache cache) {
this.cache = cache; this.cache = cache;
var cmds = ScrowAPI.getCommandApi(); targetArg = CommandArgument.arg("target", new PlayerArgumentType(server));
targetArg = cmds.createArgument("target", new PlayerArgumentType(server)); msgArg = CommandArgument.arg("msg", new StringArgumentType<>(true));
msgArg = cmds.createArgument("msg", new StringArgumentType<>(true)); rootNode = CommandNode.node("msg", "w", "privmsg");
rootNode = cmds.createNode("msg", "w", "privmsg");
rootNode.setExecutor(this); rootNode.setExecutor(this);
rootNode.addArgument(targetArg); rootNode.addArgument(targetArg);
rootNode.addArgument(msgArg); rootNode.addArgument(msgArg);
} }
@Override @Override
public void execute(VelocityCommandContext ctx) { public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSource> ctx) {
var target = ctx.getArg(targetArg); var target = ctx.getArg(targetArg);
var msg = ctx.getArg(msgArg); var msg = ctx.getArg(msgArg);
var sender = (Player)ctx.getSender();
var isMessagingSelf = target.getUniqueId().equals(ctx.getPlayerSender().getUniqueId()); var isMessagingSelf = target.getUniqueId().equals(sender.getUniqueId());
if (isMessagingSelf) throw new CommandException("You can't message yourself."); if (isMessagingSelf) return Result.error("You can't message yourself").toFuture();
cache.setLastTarget(ctx.getPlayerSender().getUniqueId(), target.getUniqueId()); cache.setLastTarget(sender.getUniqueId(), target.getUniqueId());
PrivmsgHelper.handle(ctx.getPlayerSender(), target, msg); PrivmsgHelper.handle(sender, target, msg);
return Result.success().toFuture();
} }
} }

View file

@ -1,21 +1,24 @@
package de.kentoj.scrow.corevelocity.privmsg; package de.kentoj.scrow.corevelocity.privmsg;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.kencommandapi.VelocityCommandContext; import de.kentoj.kencommandapi.api.node.CommandNode;
import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.argument.CommandArgument;
import de.kentoj.kencommandapi.api.nodes.CommandNode; import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
import de.kentoj.kencommandapi.api.processing.CommandException; import de.kentoj.kencommandapi.api.invocation.CommandContext;
import de.kentoj.kencommandapi.api.processing.CommandExecutor; import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
import de.kentoj.kencommandapi.api.types.StringArgumentType; import com.leakyabstractions.result.api.Result;
import de.kentoj.scrow.velocity.ScrowAPI; import com.leakyabstractions.result.api.Results;
import lombok.Getter; import lombok.Getter;
public class ReplyCommand implements CommandExecutor<CommandSource, VelocityCommandContext> { import java.util.concurrent.CompletableFuture;
public class ReplyCommand implements CommandExecutor<CommandSource> {
@Getter @Getter
private final CommandNode<CommandSource, VelocityCommandContext> rootNode; private final CommandNode<CommandSource> rootNode;
private final CommandArgument<CommandSource, VelocityCommandContext, String> msgArg; private final CommandArgument<CommandSource, String> msgArg;
private final LastTargetCache cache; private final LastTargetCache cache;
private final ProxyServer server; private final ProxyServer server;
@ -24,21 +27,22 @@ public class ReplyCommand implements CommandExecutor<CommandSource, VelocityComm
this.cache = cache; this.cache = cache;
this.server = server; this.server = server;
var cmds = ScrowAPI.getCommandApi(); msgArg = CommandArgument.arg("msg", new StringArgumentType<>(true));
msgArg = cmds.createArgument("msg", new StringArgumentType<>(true)); rootNode = CommandNode.node("reply", "r");
rootNode = cmds.createNode("reply", "r");
rootNode.setExecutor(this); rootNode.setExecutor(this);
rootNode.addArgument(msgArg); rootNode.addArgument(msgArg);
} }
@Override @Override
public void execute(VelocityCommandContext ctx) { public CompletableFuture<Result<Void,String>> execute(CommandContext<CommandSource> ctx) {
var targetId = cache.getLastTarget(ctx.getPlayerSender().getUniqueId()); var sender = (Player)ctx.getSender();
var targetId = cache.getLastTarget(sender.getUniqueId());
var target = server.getPlayer(targetId).orElse(null); var target = server.getPlayer(targetId).orElse(null);
if (target == null) throw new CommandException("The player you last messaged is no longer online"); if (target == null) return Result.error("The player you last messaged is no longer online").toFuture();
var msg = ctx.getArg(msgArg); var msg = ctx.getArg(msgArg);
cache.setLastTarget(ctx.getPlayerSender().getUniqueId(), target.getUniqueId()); cache.setLastTarget(sender.getUniqueId(), target.getUniqueId());
PrivmsgHelper.handle(ctx.getPlayerSender(), target, msg); PrivmsgHelper.handle(sender, target, msg);
return Result.success().toFuture();
} }
} }

View file

@ -1,43 +1,47 @@
package de.kentoj.scrow.corevelocity.server; package de.kentoj.scrow.corevelocity.server;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import de.kentoj.scrowlib.messaging.NatsRepository; import de.kentoj.scrowlib.messaging.MessageBroker;
import de.kentoj.scrowlib.messaging.respose.SafeResult;
import io.nats.client.Connection; import io.nats.client.Connection;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document; import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Mono;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
@Slf4j
public class SendPlayerWatchdog { public class SendPlayerWatchdog {
private final NatsRepository natsRepo; private final MessageBroker natsRepo;
private final ProxyServer server; private final ProxyServer server;
public SendPlayerWatchdog(Connection nats, ProxyServer server) { public SendPlayerWatchdog(Connection nats, ProxyServer server) {
this.natsRepo = new NatsRepository(nats); this.natsRepo = new MessageBroker(nats);
this.server = server; this.server = server;
} }
public void listen() { public void listen() {
natsRepo.subscribe("proxy.send-player") natsRepo.handle("players.send", doc -> sendPlayer(
.flatMap(ctx -> doc.get("playerId", UUID.class),
SafeResult.mono(handleSendPlayer(ctx.getDocument())) doc.getString("handle")
) ));
.subscribe();
} }
private Mono<@NotNull Document> handleSendPlayer(Document doc) { private CompletionStage<Result<Document, String>> sendPlayer(UUID playerId, String handle) {
return Mono.fromCallable(() -> { return CompletableFuture.supplyAsync(() -> {
var player = server.getPlayer(UUID.fromString(doc.getString("playerId"))).orElseThrow(); var player = server.getPlayer(playerId).orElseThrow();
var targetServer = server.getServer(doc.getString("handle")).orElseThrow(); var targetServer = server.getServer(handle).orElseThrow();
return player.createConnectionRequest(targetServer).connect(); try {
}) var res = player.createConnectionRequest(targetServer).connect().get();
.flatMap(Mono::fromFuture) return res.isSuccessful() ? Results.success(new Document()) : Results.failure(res.getStatus().name());
.flatMap(res -> res.isSuccessful() } catch (InterruptedException | ExecutionException e) {
? Mono.just(new Document()) log.error("failed to send player to {}", handle, e);
: Mono.error(new RuntimeException(res.getStatus().name().toLowerCase().replace('_', ' '))) return Results.failure(e.getMessage());
); }
});
} }
} }

View file

@ -2,10 +2,9 @@ package de.kentoj.scrow.corevelocity.server;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
import de.kentoj.scrowlib.messaging.NatsRepository; import de.kentoj.scrowlib.messaging.MessageBroker;
import de.kentoj.scrowlib.messaging.SubscriptionContext;
import de.kentoj.scrowlib.messaging.respose.SafeResult; import de.kentoj.scrowlib.messaging.respose.SafeResult;
import de.kentoj.scrowlib.servermanager.ServerManager; import de.kentoj.scrowlib.servermanager.InstanceManager;
import io.nats.client.Connection; import io.nats.client.Connection;
import lombok.extern.java.Log; import lombok.extern.java.Log;
import org.bson.Document; import org.bson.Document;
@ -17,13 +16,13 @@ import java.net.InetSocketAddress;
@Log @Log
public class ServerRegisterWatchdog { public class ServerRegisterWatchdog {
private final NatsRepository natsRepo; private final MessageBroker natsRepo;
private final ServerManager serverManager; private final InstanceManager instanceManager;
private final ProxyServer server; private final ProxyServer server;
public ServerRegisterWatchdog(Connection nats, ServerManager serverManager, ProxyServer server) { public ServerRegisterWatchdog(Connection nats, InstanceManager instanceManager, ProxyServer server) {
this.natsRepo = new NatsRepository(nats); this.natsRepo = new MessageBroker(nats);
this.serverManager = serverManager; this.instanceManager = instanceManager;
this.server = server; this.server = server;
} }
@ -42,11 +41,11 @@ public class ServerRegisterWatchdog {
private @NotNull Mono<@NotNull Void> handleStateChange(SubscriptionContext ctx, String state, String handle) { private @NotNull Mono<@NotNull Void> handleStateChange(SubscriptionContext ctx, String state, String handle) {
return switch (state) { return switch (state) {
case "UP" -> serverManager.getInfo(handle) case "UP" -> instanceManager.getInfo(handle)
.doOnNext(instance -> .doOnNext(instance ->
server.registerServer(new ServerInfo(handle, new InetSocketAddress(instance.getPort())))) server.registerServer(new ServerInfo(handle, new InetSocketAddress(instance.getPort()))))
.then(); .then();
case "DOWN" -> serverManager.getInfo(handle) case "DOWN" -> instanceManager.getInfo(handle)
.doOnNext(instance -> .doOnNext(instance ->
server.unregisterServer(new ServerInfo(handle, new InetSocketAddress(instance.getPort())))) server.unregisterServer(new ServerInfo(handle, new InetSocketAddress(instance.getPort()))))
.then(); .then();