From c4f42a7c4c99ae51611106b590c690ce77064a81 Mon Sep 17 00:00:00 2001 From: Remy Duijsens Date: Thu, 6 Aug 2026 09:17:21 +0200 Subject: [PATCH 001/110] Redesign api --- README.md | 15 +- docs/API.md | 109 ++++++++ docs/ARCHITECTURE.md | 157 ++++++++---- docs/DEVELOPMENT.md | 3 +- docs/MIGRATING-3.0.md | 46 ---- docs/MIGRATING-3.3.md | 58 +++++ docs/README.md | 3 +- docs/RELEASE.md | 3 +- docs/TESTING.md | 8 + docs/features/README.md | 2 +- docs/features/antivpn.md | 12 +- docs/{ => features}/capacity-placeholders.md | 0 docs/features/capacity.md | 19 ++ docs/features/friends.md | 23 +- docs/features/maintenance.md | 12 +- docs/features/messager.md | 4 +- docs/features/motd.md | 25 +- docs/features/playercount.md | 44 ++-- docs/features/playerinfo.md | 35 ++- docs/features/playerlanguage.md | 33 +-- docs/features/playerlist.md | 8 +- docs/features/queue.md | 16 +- docs/features/restart.md | 6 +- docs/features/sanctions.md | 7 +- docs/features/twofactor.md | 3 +- docs/features/vanish.md | 29 +-- docs/features/versioncheck.md | 12 +- pom.xml | 4 +- proxyfeatures-api/pom.xml | 84 ------ .../proxyfeatures/api/ProxyFeaturesApi.java | 22 ++ .../api/ProxyFeaturesApiVersion.java | 28 ++ .../api/ProxyFeaturesContext.java | 22 -- .../capability/admission/AdmissionApi.java | 11 + .../admission/AdmissionDecision.java | 41 +++ .../admission/AdmissionDenialReason.java | 16 ++ .../capability/admission/AdmissionIntent.java | 21 ++ .../capability/admission/AdmissionLease.java | 30 +++ .../admission/AdmissionRequest.java | 26 ++ .../admission/AdmissionScopeSnapshot.java | 39 +++ .../admission/AdmissionSnapshot.java | 26 ++ .../capability/admission/AdmissionState.java | 13 + .../moderation/SanctionSnapshot.java | 27 ++ .../capability/moderation/SanctionType.java | 10 + .../capability/moderation/SanctionsApi.java | 10 + .../capability/network/PlayerCountApi.java | 6 + .../network/PlayerCountSnapshot.java | 24 ++ .../api/capability/network/PlayerCounts.java | 18 ++ .../capability/operations/MaintenanceApi.java | 12 + .../operations/MaintenanceScope.java | 25 ++ .../operations/MaintenanceSnapshot.java | 15 ++ .../api/capability/operations/RestartApi.java | 12 + .../capability/operations/TwoFactorApi.java | 12 + .../api/capability/operations/VersionApi.java | 12 + .../capability/player/NetworkLocationApi.java | 9 + .../capability/player/PlayerLanguageApi.java | 15 ++ .../api/capability/presence/PresenceApi.java | 10 + .../presence/PresenceChangedEvent.java | 14 + .../capability/presence/PresenceSnapshot.java | 36 +++ .../api/capability/queue/QueueApi.java | 20 ++ .../api/capability/queue/QueueJoinCause.java | 8 + .../capability/queue/QueueJoinRequest.java | 15 ++ .../api/capability/queue/QueueJoinResult.java | 26 ++ .../api/capability/queue/QueueJoinStatus.java | 12 + .../capability/queue/QueueLeaveStatus.java | 8 + .../capability/queue/QueueServerSnapshot.java | 28 ++ .../api/capability/queue/QueueSnapshot.java | 23 ++ .../queue/QueuedPlayerSnapshot.java | 26 ++ .../api/capability/social/FriendshipApi.java | 14 + .../capability/social/FriendshipStatus.java | 7 + .../api/capacity/RestartAdmissionAPI.java | 8 - .../api/extension/ExtensionRegistration.java | 8 + .../api/extension/MotdContext.java | 11 + .../api/extension/MotdContribution.java | 20 ++ .../api/extension/MotdContributor.java | 9 + .../api/extension/MotdExtensions.java | 6 + .../api/feature/FeatureCatalog.java | 12 + .../api/feature/FeatureClassification.java | 9 + .../api/feature/FeatureDescriptor.java | 32 +++ .../proxyfeatures/api/feature/FeatureId.java | 46 ++++ .../api/feature/FeatureSnapshot.java | 20 ++ .../api/feature/FeatureState.java | 10 + .../api/friends/FriendshipApi.java | 23 -- .../proxyfeatures/api/io/cache/CacheType.java | 9 - .../api/io/cache/impl/SqliteCacheFile.java | 51 ---- .../proxyfeatures/api/io/packet/Packet.java | 10 - .../api/io/packet/PacketManager.java | 59 ----- .../api/io/resource/ResourceHandler.java | 108 -------- .../proxyfeatures/api/model/ServerId.java | 44 ++++ .../api/queue/QueueAdmissionAPI.java | 24 -- .../api/queue/QueueObservabilityAPI.java | 6 - .../api/queue/QueueServerSnapshot.java | 27 -- .../api/queue/QueueSnapshot.java | 26 -- .../api/service/CapabilityRef.java | 28 ++ .../api/service/CapabilityRegistry.java | 13 + .../CapabilityUnavailableException.java | 21 ++ .../api/util/http/DiscordUtils.java | 72 ------ .../api/AdmissionAndQueueContractsTest.java | 168 ++++++++++++ .../api/DomainContractsTest.java | 198 ++++++++++++++ .../api/IdentityAndServiceContractsTest.java | 88 +++++++ .../network/PlayerCountSnapshotTest.java | 33 +++ .../api/capacity/CapacityApiTest.java | 242 ------------------ .../io/cache/impl/SqliteCacheFileTest.java | 97 ------- .../api/io/packet/PacketManagerTest.java | 57 ----- .../api/io/resource/ResourceHandlerTest.java | 122 --------- .../api/util/http/DiscordUtilsTest.java | 63 ----- .../resources/nested/resource-default.yml | 1 - .../src/test/resources/resource-default.yml | 1 - proxyfeatures-contracts/pom.xml | 12 +- .../messaging/RestartLifecycleMessage.java | 2 +- .../RestartLifecycleMessageTest.java | 2 +- .../ProxyFeaturesAcceptanceConsumer.java | 9 +- proxyfeatures-platform-velocity/pom.xml | 32 +-- .../proxyfeatures/ProxyFeatures.java | 79 +++++- .../features/FeatureContext.java | 2 +- .../features/FeatureFactory.java | 2 +- .../features/VelocityBaseFeature.java | 8 +- .../features/announcer/Announcer.java | 9 +- .../announcer/command/AnnouncerCommand.java | 2 +- .../announcer/internal/AnnouncerHandler.java | 6 +- .../announcer/internal/AnnouncerRegistry.java | 6 +- .../features/announcer/meta/Meta.java | 2 +- .../features/antibot/AntiBot.java | 9 +- .../antibot/command/AntiBotCommand.java | 2 +- .../antibot/internal/AddressWhitelist.java | 2 + .../antibot/internal/AntiBotService.java | 2 + .../antibot/listener/AntiBotLoginPolicy.java | 2 +- .../features/antibot/meta/Meta.java | 2 +- .../features/antivpn/AntiVPN.java | 19 +- .../features/antivpn/api/CountryAPI.java | 14 - .../antivpn/command/AntiVPNCommand.java | 2 +- .../antivpn/internal/CountryService.java | 6 +- .../antivpn/internal/PersistentIpCache.java | 4 +- .../antivpn/listener/AntiVPNLoginPolicy.java | 2 +- .../features/antivpn/meta/Meta.java | 2 +- .../features/broadcast/Broadcast.java | 4 +- .../command/BroadcastProxyCommand.java | 6 +- .../features/broadcast/meta/Meta.java | 2 +- .../features/capacity/Capacity.java | 14 +- .../capacity/command/CapacityCommand.java | 8 +- .../capacity/config/CapacityConfig.java | 4 +- .../capacity/config/CapacityConfigStore.java | 6 +- .../internal/AdmissionCapability.java | 153 +++++++++++ .../internal/CapacityControlPlane.java | 2 +- .../CapacityIntegrationSynchronizer.java | 39 ++- .../capacity/internal/CapacityService.java | 58 ++++- .../internal/CapacitySnapshotPublisher.java | 4 +- .../capacity/listener/CapacityListener.java | 77 ++---- .../features/capacity/meta/Meta.java | 2 +- .../features/clientinfo/ClientInfo.java | 9 +- .../command/ClientInfoBrigadierCommand.java | 2 +- .../clientinfo/internal/ClientInfoConfig.java | 4 +- .../features/clientinfo/meta/Meta.java | 2 +- .../features/commandhider/CommandHider.java | 4 +- .../command/CommandHiderCommand.java | 2 +- .../features/commandhider/meta/Meta.java | 2 +- .../features/commandlogger/CommandLogger.java | 4 +- .../features/commandlogger/meta/Meta.java | 2 +- .../service/CommandLogService.java | 14 +- .../features/commandrelay/CommandRelay.java | 4 +- .../internal/EventBusHandler.java | 8 +- .../internal/ProcessedCommandLedger.java | 4 +- .../features/commandrelay/meta/Meta.java | 2 +- .../connectioninfo/ConnectionInfo.java | 4 +- .../command/ConnectionInfoCommand.java | 2 +- .../connectioninfo/command/PingCommand.java | 2 +- .../features/connectioninfo/meta/Meta.java | 2 +- .../features/friends/Friends.java | 6 +- .../friends/api/FriendshipApiImpl.java | 2 +- .../friends/command/AsyncFriendCommand.java | 2 +- .../friends/command/FriendCommand.java | 12 +- .../friends/entity/FriendsService.java | 15 +- .../listener/FriendActivityListener.java | 24 +- .../features/friends/meta/Meta.java | 2 +- .../proxyfeatures/features/hlink/HLink.java | 4 +- .../features/hlink/command/HLinkCommand.java | 2 +- .../features/hlink/command/LinkCommand.java | 2 +- .../hlink/command/RegisterCommand.java | 2 +- .../features/hlink/internal/HLinkHandler.java | 4 +- .../features/hlink/meta/Meta.java | 2 +- .../proxyfeatures/features/hub/Hub.java | 4 +- .../features/hub/command/HubCommand.java | 2 +- .../proxyfeatures/features/hub/meta/Meta.java | 2 +- .../features/maintenance/Maintenance.java | 24 +- .../command/MaintenanceCommand.java | 2 +- .../internal/MaintenanceHandler.java | 33 ++- .../MaintenanceConnectionListener.java | 17 +- .../features/maintenance/meta/Meta.java | 2 +- .../features/messager/Messenger.java | 11 +- .../messager/command/MessagingCommand.java | 4 +- .../messager/command/ReplyCommand.java | 2 +- .../messager/entity/MessageModeConverter.java | 2 +- .../entity/PlayerMessageSettingsEntity.java | 2 +- .../internal/MessagePrivacyPolicy.java | 2 +- .../messager/internal/MessagingHandler.java | 6 +- .../internal/MessagingSettingsService.java | 15 +- .../internal/MessengerTargetVisibility.java | 13 +- .../features/messager/meta/Meta.java | 2 +- .../features/messager/model}/MessageMode.java | 2 +- .../proxyfeatures/features/motd/Motd.java | 4 +- .../features/motd/internal/MotdHandler.java | 51 ++-- .../internal/MotdLine2OverrideRegistry.java | 75 ------ .../features/motd/listener/PingListener.java | 7 +- .../features/motd/meta/Meta.java | 2 +- .../features/playercount/PlayerCount.java | 19 +- .../internal/PlayerCountPublisher.java | 13 +- ...rCountAPI.java => PlayerCountService.java} | 51 ++-- .../internal/PlayerCountSnapshot.java | 61 ----- .../features/playercount/meta/Meta.java | 2 +- .../features/playerinfo/PlayerInfo.java | 15 +- .../playerinfo/command/PlayerInfoCommand.java | 30 ++- .../features/playerinfo/meta/Meta.java | 2 +- .../playerinfo/service/PlayerInfoService.java | 37 ++- .../playerlanguage/PlayerLanguage.java | 8 +- .../playerlanguage/api/LanguageAPI.java | 13 - .../command/LanguageCommand.java | 4 +- .../command/LanguageCommandPolicy.java | 2 +- .../features/playerlanguage/meta/Meta.java | 2 +- .../service/LanguageService.java | 47 +++- .../features/playerlist/PlayerList.java | 4 +- .../playerlist/command/GlobalListCommand.java | 4 +- .../playerlist/command/ListCommand.java | 4 +- .../internal/PlayerListHandler.java | 8 +- .../features/playerlist/meta/Meta.java | 2 +- .../features/proxyinfo/ProxyInfo.java | 4 +- .../proxyinfo/command/ProxyInfoCommand.java | 2 +- .../features/proxyinfo/meta/Meta.java | 2 +- .../proxyfeatures/features/queue/Queue.java | 18 +- .../features/queue/QueueManager.java | 175 ++++++++++--- .../features/queue/command/QueueCommand.java | 4 +- .../features/queue/meta/Meta.java | 2 +- .../features/queue/model/ServerQueue.java | 4 + .../features/resourcepack/ResourcePack.java | 4 +- .../command/ResourcePackCommand.java | 2 +- .../internal/ResourcePackHandler.java | 6 +- .../features/resourcepack/meta/Meta.java | 2 +- .../features/restart/Restart.java | 29 ++- .../restart/command/AutoreconnectCommand.java | 2 +- .../restart/command/ProxyRestartCommand.java | 2 +- .../internal/BackendReconnectManager.java | 2 +- .../internal/RestartCapacityCoordinator.java | 38 ++- .../messaging/RestartLifecycleBus.java | 2 + .../features/restart/meta/Meta.java | 2 +- .../features/sanctions/Sanctions.java | 17 +- .../command/AsyncSanctionsCommand.java | 2 +- .../sanctions/command/BanCommand.java | 2 +- .../sanctions/command/BanIpCommand.java | 2 +- .../sanctions/command/KickCommand.java | 4 +- .../sanctions/command/MuteCommand.java | 2 +- .../command/SanctionListCommand.java | 8 +- .../sanctions/command/UnbanCommand.java | 4 +- .../sanctions/command/UnbanIpCommand.java | 4 +- .../sanctions/command/UnmuteCommand.java | 4 +- .../sanctions/command/WarnCommand.java | 4 +- .../sanctions/entity/SanctionEntity.java | 0 .../sanctions/entity/SanctionType.java | 0 .../sanctions/listener/ConnectListener.java | 2 +- .../features/sanctions/meta/Meta.java | 2 +- .../sanctions/service/DiscordService.java | 25 +- .../service/SanctionSuggestionLookup.java | 3 +- .../service/SanctionsCapability.java | 48 ++++ .../sanctions/service/SanctionsService.java | 32 ++- .../sanctions/service/ServiceLookup.java | 13 +- .../features/serverlinks/ServerLinks.java | 4 +- .../features/serverlinks/meta/Meta.java | 2 +- .../features/slashserver/SlashServer.java | 6 +- .../command/SlashServerAdminCommand.java | 2 +- .../command/SlashServerCommand.java | 2 +- .../features/slashserver/meta/Meta.java | 2 +- .../features/staffchat/StaffChat.java | 4 +- .../staffchat/internal/ChatChannel.java | 2 +- .../staffchat/listener/ConnectListener.java | 2 +- .../features/staffchat/meta/Meta.java | 2 +- .../features/textcommands/TextCommands.java | 6 +- .../textcommands/command/TextCommand.java | 4 +- .../features/textcommands/meta/Meta.java | 2 +- .../features/twofactor/TwoFactor.java | 15 +- .../twofactor/command/TwoFactorCommand.java | 2 +- .../features/twofactor/meta/Meta.java | 2 +- .../service/TwoFactorCapability.java | 29 +++ .../proxyfeatures/features/vanish/Vanish.java | 21 +- .../vanish/event/VanishStateChangeEvent.java | 16 -- .../vanish/internal/PresenceService.java | 45 ++++ .../features/vanish/internal/VanishAPI.java | 51 ---- .../vanish/internal/VanishRegistry.java | 13 +- .../vanish/listener/TabCompleteListener.java | 2 +- .../features/vanish/meta/Meta.java | 2 +- .../features/versioncheck/VersionCheck.java | 11 +- .../versioncheck/internal/VersionHandler.java | 37 ++- .../features/versioncheck/meta/Meta.java | 2 +- .../features/votifier/Votifier.java | 4 +- .../command/ExtendedVotifierCommand.java | 2 +- .../votifier/command/VotifierCommand.java | 2 +- .../votifier/internal/VoteStatsService.java | 31 +-- .../messaging/VoteDeliverySettings.java | 2 +- .../features/votifier/meta/Meta.java | 2 +- .../framework/admission}/AdmissionIntent.java | 2 +- .../framework/admission}/CapacityAPI.java | 10 +- .../admission}/CapacityDecision.java | 2 +- .../admission}/CapacityDenialReason.java | 2 +- .../framework/admission}/CapacityLease.java | 2 +- .../framework/admission}/CapacityRequest.java | 2 +- .../admission}/CapacityScopeSnapshot.java | 2 +- .../admission}/CapacitySnapshot.java | 2 +- .../framework/admission}/CapacityState.java | 2 +- .../admission/QueueAdmissionPort.java | 20 ++ .../admission/RestartCoordinationPort.java | 8 + .../framework}/command/FeatureCommand.java | 2 +- .../command/ProxyFeaturesCommand.java | 2 +- .../command/brigadier/BrigadierCommand.java | 2 +- .../config/FeatureConfigHandler.java | 8 +- .../framework/config/FeatureStoragePaths.java | 2 +- .../framework/config/MainConfigHandler.java | 8 +- .../extension/DefaultMotdExtensions.java | 70 +++++ .../framework}/feature/Feature.java | 6 +- .../feature/FeatureScopeFactory.java | 2 +- .../framework}/feature/meta/BaseMeta.java | 2 +- .../feature/stateful/SnapshotState.java | 2 +- .../feature/stateful/StatefulFeature.java | 2 +- .../lifecycle/FeatureApiManager.java | 119 ++++----- .../lifecycle/FeatureCacheManager.java | 2 +- .../lifecycle/FeatureCommandManager.java | 4 +- .../lifecycle/FeatureLifecycleFactory.java | 8 +- .../framework/loader/BuiltInFeatures.java | 76 ++++++ .../framework/loader/FeatureDescriptor.java | 2 +- .../framework/loader/FeatureLoadManager.java | 127 +++++---- .../localization/LocalizationHandler.java | 23 +- .../framework/log/ConnectionLogHelper.java | 2 +- .../network}/IpAddressUtil.java | 42 +-- .../persistence/PlayerReference.java | 0 .../service/CapabilityRegistration.java | 8 + .../service/DefaultCapabilityRegistry.java | 102 ++++++++ .../service/DefaultFeatureCatalog.java | 80 ++++++ .../service/DefaultProxyFeaturesApi.java | 40 +++ .../framework/service/FeatureServices.java | 9 +- .../service/InternalServiceRegistry.java | 52 ++++ .../framework/service/InternalServices.java | 24 ++ .../ArchitectureBoundaryTest.java | 123 +++++++++ .../features/FeatureFactoryTest.java | 6 +- .../features/VelocityBaseFeatureTest.java | 6 +- .../antivpn/internal/AntiVPNServiceTest.java | 2 +- .../antivpn/internal/CountryServiceTest.java | 6 +- .../antivpn/internal/IpWhitelistTest.java | 2 +- .../internal/NotificationServiceTest.java | 2 +- .../internal/PersistentIpCacheTest.java | 6 +- .../provider/ProviderRegistryTest.java | 2 +- .../ip2location/IP2LocationProviderTest.java | 2 +- .../proxycheck/ProxyCheckProviderTest.java | 2 +- .../command/BroadcastProxyCommandTest.java | 2 +- .../capacity/command/CapacityCommandTest.java | 2 +- .../config/CapacityConfigurationTest.java | 4 +- .../internal/CapacityServiceTest.java | 12 +- .../CapacitySnapshotPublisherTest.java | 6 +- .../ClientInfoAdvisorNotifyPolicyTest.java | 2 +- .../ClientInfoAdvisorOutputConfigTest.java | 4 +- .../internal/ClientInfoConfigTest.java | 6 +- .../service/CommandLogServiceTest.java | 13 +- .../internal/EventBusHandlerTest.java | 4 +- .../internal/ProcessedCommandLedgerTest.java | 4 +- .../friends/entity/FriendsServiceTest.java | 9 +- .../MaintenanceConnectionListenerTest.java | 25 +- .../command/MessagingModeCommandTest.java | 2 +- .../command/MessagingVanishCommandTest.java | 19 +- .../entity/MessageModeConverterTest.java | 2 +- .../PlayerMessageSettingsEntityTest.java | 2 +- .../internal/MessagePrivacyPolicyTest.java | 2 +- ...essagingFriendServiceAvailabilityTest.java | 6 +- .../internal/MessagingHandlerTest.java | 7 +- .../MessagingHandlerVanishVisibilityTest.java | 17 +- .../MessagingPlayerStateCleanupTest.java | 4 +- .../MessagingSettingsServiceTest.java | 23 +- .../MessengerTargetVisibilityTest.java | 25 +- .../messager/model}/MessageModeTest.java | 2 +- .../motd/internal/MotdHandlerTest.java | 82 +++--- .../MotdLine2OverrideRegistryTest.java | 57 ----- .../internal/PlayerCountPublisherTest.java | 19 +- ...ITest.java => PlayerCountServiceTest.java} | 39 +-- .../internal/PlayerCountSnapshotTest.java | 38 --- .../service/PlayerInfoServiceTest.java | 45 +--- .../command/LanguageCommandPolicyTest.java | 2 +- .../service/LanguageServiceTest.java | 2 +- .../internal/PlayerListHandlerTest.java | 28 +- .../features/queue/QueueManagerTest.java | 42 +-- .../queue/command/QueueCommandTest.java | 4 +- .../BackendReconnectCancellationTest.java | 2 +- ...BackendReconnectDelayedNoticeRaceTest.java | 2 +- .../internal/BackendReconnectManagerTest.java | 2 +- .../PlayerAutoreconnectCancellationTest.java | 2 +- .../RestartCapacityCoordinatorTest.java | 6 +- .../messaging/RestartLifecycleBusTest.java | 2 + .../command/AsyncSanctionsCommandTest.java | 2 +- .../sanctions/entity/SanctionEntityTest.java | 0 .../listener/ConnectListenerTest.java | 2 +- .../sanctions/service/DiscordServiceTest.java | 69 ++--- .../service/SanctionsServiceParsingTest.java | 13 +- .../service/SanctionsServiceSharedIpTest.java | 9 +- .../sanctions/service/ServiceLookupTest.java | 5 +- .../vanish/internal/PresenceServiceTest.java | 43 ++++ .../vanish/internal/VanishAPITest.java | 39 --- .../internal/VanishRegistryEventTest.java | 14 +- .../internal/VersionHandlerTest.java | 10 +- .../internal/VoteStatsServiceTest.java | 4 +- .../votifier/internal/VotifierConfigTest.java | 2 +- .../command/ProxyFeaturesCommandTest.java | 2 +- .../brigadier/BrigadierCommandTest.java | 2 +- .../config/FeatureConfigHandlerTest.java | 4 +- .../config/MainConfigHandlerTest.java | 4 +- .../extension/DefaultMotdExtensionsTest.java | 51 ++++ .../feature/FeatureScopeFactoryTest.java | 2 +- .../lifecycle/FeatureApiManagerTest.java | 144 ++++------- .../lifecycle/FeatureCacheManagerTest.java | 2 +- .../lifecycle/FeatureCommandManagerTest.java | 4 +- .../framework/loader/BuiltInFeaturesTest.java | 60 +++++ .../loader/FeatureDescriptorTest.java | 2 +- .../loader/FeatureLoadManagerTest.java | 14 +- .../localization/LocalizationHandlerTest.java | 36 ++- .../DefaultCapabilityRegistryTest.java | 76 ++++++ .../service/DefaultFeatureCatalogTest.java | 88 +++++++ .../test/MutableCapabilityRegistry.java | 42 +++ .../test/TestFeatureServiceDirectory.java | 59 ----- proxyfeatures-toolkit/pom.xml | 98 +++++++ .../proxyfeatures/toolkit/ToolkitContext.java | 16 ++ .../toolkit}/io/cache/CacheDirectory.java | 11 +- .../toolkit}/io/cache/CacheStore.java | 2 +- .../toolkit/io/cache/CacheType.java | 8 + .../toolkit}/io/cache/CacheValue.java | 2 +- .../toolkit}/io/cache/FileCacheStore.java | 2 +- .../toolkit}/io/cache/impl/JsonCacheFile.java | 32 ++- .../toolkit}/io/config/ConfigMap.java | 2 +- .../toolkit}/io/config/ConfigNode.java | 2 +- .../toolkit}/io/config/ConfigService.java | 6 +- .../toolkit}/io/config/ConfigTypes.java | 2 +- .../toolkit}/io/config/ConfigView.java | 2 +- .../toolkit}/io/config/YamlFile.java | 2 +- .../toolkit}/io/localization/Language.java | 2 +- .../toolkit}/io/localization/MessageMap.java | 2 +- .../toolkit}/util/http/SimpleHttpClient.java | 28 +- .../toolkit}/util/parse/JsonUtils.java | 2 +- .../toolkit}/util/text/TextPatterns.java | 2 +- .../util/text/format/ComponentFormatter.java | 6 +- .../util/text/format/TextFormatter.java | 8 +- .../text/format/color/LegacyColorUtils.java | 2 +- .../format/constants/FormatConstants.java | 2 +- .../text/format/inspect/FormatInspector.java | 6 +- .../text/placeholder/MessagePlaceholders.java | 2 +- .../toolkit}/util/tools/Paginator.java | 2 +- .../toolkit}/util/type/CastUtils.java | 2 +- .../toolkit}/io/cache/CacheDirectoryTest.java | 7 +- .../toolkit}/io/cache/CacheValueTest.java | 2 +- .../io/cache/impl/JsonCacheFileTest.java | 4 +- .../toolkit}/io/config/ConfigMapTest.java | 2 +- .../toolkit}/io/config/ConfigNodeTest.java | 2 +- .../toolkit}/io/config/ConfigServiceTest.java | 6 +- .../toolkit}/io/config/ConfigTypesTest.java | 2 +- .../toolkit}/io/config/ConfigViewTest.java | 2 +- .../toolkit}/io/config/YamlFileTest.java | 2 +- .../io/localization/LanguageTest.java | 2 +- .../util/http/SimpleHttpClientTest.java | 2 +- .../toolkit}/util/parse/JsonUtilsTest.java | 2 +- .../toolkit}/util/text/TextPatternsTest.java | 2 +- .../text/format/ComponentFormatterTest.java | 4 +- .../util/text/format/TextFormatterTest.java | 2 +- .../format/color/LegacyColorUtilsTest.java | 2 +- .../format/constants/FormatConstantsTest.java | 2 +- .../format/inspect/FormatInspectorTest.java | 4 +- .../placeholder/MessagePlaceholdersTest.java | 2 +- .../toolkit}/util/tools/PaginatorTest.java | 2 +- .../toolkit}/util/type/CastUtilsTest.java | 2 +- 467 files changed, 5030 insertions(+), 3211 deletions(-) create mode 100644 docs/API.md delete mode 100644 docs/MIGRATING-3.0.md create mode 100644 docs/MIGRATING-3.3.md rename docs/{ => features}/capacity-placeholders.md (100%) create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesApiVersion.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesContext.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionDecision.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionDenialReason.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionIntent.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionLease.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionRequest.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionScopeSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionState.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionType.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionsApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCounts.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceScope.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/RestartApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/TwoFactorApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/VersionApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/player/NetworkLocationApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/player/PlayerLanguageApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceChangedEvent.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinCause.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinRequest.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinResult.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinStatus.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueLeaveStatus.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueServerSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueuedPlayerSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/social/FriendshipApi.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/social/FriendshipStatus.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/RestartAdmissionAPI.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/ExtensionRegistration.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContext.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContribution.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContributor.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdExtensions.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureCatalog.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureClassification.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureDescriptor.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureId.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureState.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/friends/FriendshipApi.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheType.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/SqliteCacheFile.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/packet/Packet.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/packet/PacketManager.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/resource/ResourceHandler.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/model/ServerId.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueAdmissionAPI.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueObservabilityAPI.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueServerSnapshot.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueSnapshot.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityRef.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityRegistry.java create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityUnavailableException.java delete mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/http/DiscordUtils.java create mode 100644 proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/AdmissionAndQueueContractsTest.java create mode 100644 proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/DomainContractsTest.java create mode 100644 proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/IdentityAndServiceContractsTest.java create mode 100644 proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountSnapshotTest.java delete mode 100644 proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityApiTest.java delete mode 100644 proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/SqliteCacheFileTest.java delete mode 100644 proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/packet/PacketManagerTest.java delete mode 100644 proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/resource/ResourceHandlerTest.java delete mode 100644 proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/http/DiscordUtilsTest.java delete mode 100644 proxyfeatures-api/src/test/resources/nested/resource-default.yml delete mode 100644 proxyfeatures-api/src/test/resources/resource-default.yml rename {proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart => proxyfeatures-contracts/src/main/java/nl/hauntedmc/proxyfeatures/contracts}/messaging/RestartLifecycleMessage.java (98%) rename {proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart => proxyfeatures-contracts/src/test/java/nl/hauntedmc/proxyfeatures/contracts}/messaging/RestartLifecycleMessageTest.java (98%) delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/api/CountryAPI.java create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/AdmissionCapability.java rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/messaging => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/model}/MessageMode.java (94%) delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdLine2OverrideRegistry.java rename proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/{PlayerCountAPI.java => PlayerCountService.java} (65%) delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountSnapshot.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/api/LanguageAPI.java rename {proxyfeatures-contracts => proxyfeatures-platform-velocity}/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionEntity.java (100%) rename {proxyfeatures-contracts => proxyfeatures-platform-velocity}/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionType.java (100%) create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsCapability.java create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/service/TwoFactorCapability.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/event/VanishStateChangeEvent.java create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/PresenceService.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishAPI.java rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission}/AdmissionIntent.java (88%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission}/CapacityAPI.java (74%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission}/CapacityDecision.java (93%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission}/CapacityDenialReason.java (71%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission}/CapacityLease.java (88%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission}/CapacityRequest.java (95%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission}/CapacityScopeSnapshot.java (92%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission}/CapacitySnapshot.java (89%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission}/CapacityState.java (78%) create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/QueueAdmissionPort.java create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/RestartCoordinationPort.java rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework}/command/FeatureCommand.java (88%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework}/command/brigadier/BrigadierCommand.java (93%) create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/extension/DefaultMotdExtensions.java rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework}/feature/Feature.java (82%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework}/feature/meta/BaseMeta.java (86%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework}/feature/stateful/SnapshotState.java (67%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework}/feature/stateful/StatefulFeature.java (90%) create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeatures.java rename proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/{features/antibot/internal => framework/network}/IpAddressUtil.java (70%) rename {proxyfeatures-contracts => proxyfeatures-platform-velocity}/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReference.java (100%) create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/CapabilityRegistration.java create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultCapabilityRegistry.java create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalog.java create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultProxyFeaturesApi.java create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServiceRegistry.java create mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServices.java create mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/architecture/ArchitectureBoundaryTest.java rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/messaging => proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/model}/MessageModeTest.java (91%) delete mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdLine2OverrideRegistryTest.java rename proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/{PlayerCountAPITest.java => PlayerCountServiceTest.java} (66%) delete mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountSnapshotTest.java rename {proxyfeatures-contracts => proxyfeatures-platform-velocity}/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionEntityTest.java (100%) create mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/PresenceServiceTest.java delete mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishAPITest.java rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework}/command/brigadier/BrigadierCommandTest.java (93%) create mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/extension/DefaultMotdExtensionsTest.java create mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeaturesTest.java create mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultCapabilityRegistryTest.java create mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalogTest.java create mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/test/MutableCapabilityRegistry.java delete mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/test/TestFeatureServiceDirectory.java create mode 100644 proxyfeatures-toolkit/pom.xml create mode 100644 proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/ToolkitContext.java rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/cache/CacheDirectory.java (85%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/cache/CacheStore.java (91%) create mode 100644 proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheType.java rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/cache/CacheValue.java (97%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/cache/FileCacheStore.java (91%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/cache/impl/JsonCacheFile.java (82%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/ConfigMap.java (96%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/ConfigNode.java (98%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/ConfigService.java (96%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/ConfigTypes.java (99%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/ConfigView.java (99%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/YamlFile.java (98%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/localization/Language.java (92%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/io/localization/MessageMap.java (89%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/http/SimpleHttpClient.java (81%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/parse/JsonUtils.java (86%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/TextPatterns.java (97%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/format/ComponentFormatter.java (99%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/format/TextFormatter.java (98%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/format/color/LegacyColorUtils.java (98%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/format/constants/FormatConstants.java (82%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/format/inspect/FormatInspector.java (96%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/placeholder/MessagePlaceholders.java (98%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/tools/Paginator.java (93%) rename {proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit}/util/type/CastUtils.java (94%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/io/cache/CacheDirectoryTest.java (91%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/io/cache/CacheValueTest.java (95%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/io/cache/impl/JsonCacheFileTest.java (97%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/ConfigMapTest.java (95%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/ConfigNodeTest.java (97%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/ConfigServiceTest.java (94%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/ConfigTypesTest.java (98%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/ConfigViewTest.java (99%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/io/config/YamlFileTest.java (97%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/io/localization/LanguageTest.java (83%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/http/SimpleHttpClientTest.java (99%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/parse/JsonUtilsTest.java (84%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/TextPatternsTest.java (96%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/format/ComponentFormatterTest.java (98%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/format/TextFormatterTest.java (98%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/format/color/LegacyColorUtilsTest.java (93%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/format/constants/FormatConstantsTest.java (88%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/format/inspect/FormatInspectorTest.java (96%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/text/placeholder/MessagePlaceholdersTest.java (97%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/tools/PaginatorTest.java (95%) rename {proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api => proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit}/util/type/CastUtilsTest.java (94%) diff --git a/README.md b/README.md index 361af19f..f9c321a5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Java 25](https://img.shields.io/badge/Java-25-007396?logo=openjdk&logoColor=white)](https://adoptium.net/) [![License](https://img.shields.io/github/license/HauntedMC/ProxyFeatures)](LICENSE) -A modular feature framework and reusable API for your entire Velocity network. +A modular Velocity feature runtime with a reload-safe, dependency-free integration API. ## Quick Start @@ -58,8 +58,9 @@ public runtime integration and clean shutdown. Set `PLATFORM_ACCEPTANCE_KEEP_WOR ## Published Modules -- `proxyfeatures-api`: reusable commands, configuration, cache, localization, packet, and text contracts. -- `proxyfeatures-contracts`: small cross-platform persistence and wire-message contracts shared with ServerFeatures. +- `proxyfeatures-api`: dependency-free root API, capability contracts, immutable DTOs, feature catalog, and extensions. +- `proxyfeatures-toolkit`: shared runtime implementation support for config, cache, localization, HTTP, and text. +- `proxyfeatures-contracts`: seven cross-platform wire-message contracts shared with ServerFeatures. - `proxyfeatures`: the installable Velocity plugin; its jar keeps the historical `ProxyFeatures.jar` name. The testkit is reactor-internal and is not part of the supported production API. Maven consumers should depend on the @@ -69,7 +70,7 @@ smallest public artifact they need, with `provided` scope when the Velocity plug nl.hauntedmc.proxyfeatures proxyfeatures-api - 3.0.0 + 3.3.0 provided ``` @@ -77,7 +78,8 @@ smallest public artifact they need, with `provided` scope when the Velocity plug ## Repository Layout - `proxyfeatures-api`: public, reusable integration surface. -- `proxyfeatures-contracts`: shared sanction, player persistence, and cross-platform messaging model. +- `proxyfeatures-toolkit`: reusable implementation support used by the Velocity runtime. +- `proxyfeatures-contracts`: cross-platform wire messages only. - `proxyfeatures-testkit`: common test infrastructure. - `proxyfeatures-platform-velocity`: feature framework, feature implementations, and distributable jar. - `proxyfeatures-platform-acceptance`: API-only consumer and real Velocity boot gate. @@ -87,10 +89,11 @@ smallest public artifact they need, with `provided` scope when the Velocity plug - [Configuration Guide](docs/CONFIGURATION.md) - [Documentation Index](docs/README.md) - [Architecture](docs/ARCHITECTURE.md) +- [Public API](docs/API.md) - [Development Notes](docs/DEVELOPMENT.md) - [Testing and Quality](docs/TESTING.md) - [Release Process](docs/RELEASE.md) -- [Migrating to 3.0](docs/MIGRATING-3.0.md) +- [Migrating to 3.3](docs/MIGRATING-3.3.md) - [Contributing](CONTRIBUTING.md) ## Community diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 00000000..52295d8c --- /dev/null +++ b/docs/API.md @@ -0,0 +1,109 @@ +# ProxyFeatures 3.3 Public API + +The 3.3 API is a small dependency-free artifact. It exposes runtime discovery, feature lifecycle observations, +reload-safe capabilities, immutable domain snapshots, and lifecycle-safe extension registration. It does not expose +Velocity adapters, persistence entities, configuration implementation, feature classes, or internal coordination. + +## Dependency + +```xml + + nl.hauntedmc.proxyfeatures + proxyfeatures-api + 3.3.0 + provided + +``` + +Add an optional or required Velocity plugin dependency on `proxyfeatures` according to your integration policy. + +## Discover the root API + +The Velocity plugin instance implements `ProxyFeaturesApi`: + +```java +ProxyFeaturesApi proxyFeatures = proxy.getPluginManager() + .getPlugin("proxyfeatures") + .flatMap(container -> container.getInstance()) + .filter(ProxyFeaturesApi.class::isInstance) + .map(ProxyFeaturesApi.class::cast) + .orElseThrow(() -> new IllegalStateException("ProxyFeatures API is unavailable")); +``` + +`proxyFeatures.version()` reports both API and runtime versions. `features()` lists every known built-in feature and +its current `DISABLED`, `STARTING`, `ACTIVE`, `STOPPING`, or `FAILED` state. + +## Resolve capabilities safely + +Keep the reference, not the provider: + +```java +CapabilityRef presence = + proxyFeatures.capabilities().reference(PresenceApi.class); + +boolean hidden = presence.get() + .map(api -> api.isHidden(playerId)) + .orElse(false); +``` + +`CapabilityRef` is stable for the lifetime of the plugin. Its `get()` and `require()` methods resolve the currently +active provider. A feature reload can remove one implementation and install another, so callers must resolve the +reference again for each operation. `require()` throws `CapabilityUnavailableException` when no provider is active. + +## Capability inventory + +| Contract | Owner | Purpose | +|---|---|---| +| `AdmissionApi` | Capacity | Atomic backend admission decisions and expiring leases | +| `QueueApi` | Queue | Asynchronous queue join/leave, lookup, enablement, and snapshots | +| `PresenceApi` | Vanish | Authoritative online visibility state and immutable snapshots | +| `FriendshipApi` | Friends | Asynchronous friendship decisions independent of ORM entities | +| `NetworkLocationApi` | AntiVPN | Session-scoped country-code lookup without triggering remote calls | +| `PlayerLanguageApi` | PlayerLanguage | Cached preference/resolved locale and asynchronous mutation | +| `PlayerCountApi` | PlayerCount | Vanish-aware network/backend count snapshots | +| `MaintenanceApi` | Maintenance | Global/backend state, bypass decisions, and snapshots | +| `RestartApi` | Restart | Drain state and expected return checks | +| `TwoFactorApi` | TwoFactor | Authentication lock and authentication-server checks | +| `VersionApi` | VersionCheck | Minimum supported protocol and version name | +| `SanctionsApi` | Sanctions | Asynchronous persistence-independent sanction history snapshots | +| `MotdExtensions` | Core | Ordered, lifecycle-safe MOTD contribution registration | + +Features not listed as capability providers remain operator-facing or implementation-only. Their concrete classes are +not supported integration contracts. + +## Admission and queue + +`AdmissionRequest` is intentionally untrusted: callers provide player identity, previous/target server, and intent; +the runtime derives permissions, maintenance, restart, two-factor, capacity, and reservation policy. An allowed +`AdmissionDecision` always contains exactly one `AdmissionLease`. Commit it after the exact connection succeeds or +release/close it when the attempt ends. Lease operations are idempotent. + +Queue operations return `CompletionStage` and typed status values. `QueueJoinRequest` cannot supply priority, +permission, capacity, or bypass values; those remain runtime-owned. All snapshots and contained collections are +immutable point-in-time views. + +## MOTD extensions + +External plugins can register an ordered contributor through the core capability: + +```java +CapabilityRef extensions = + proxyFeatures.capabilities().reference(MotdExtensions.class); + +ExtensionRegistration registration = extensions.require().register( + "my-plugin", + 100, + context -> Optional.of(MotdContribution.secondLine("Scheduled event tonight")) +); +``` + +Close the returned registration during plugin shutdown or before replacing it. Registration close is idempotent. +Blank contributions are ignored and contributor failures are isolated by the runtime registry. + +## Threading and data rules + +- Treat DTOs as immutable snapshots; obtain a new snapshot when freshness matters. +- Do not block Velocity event threads on `CompletionStage` results. +- Do not rely on concrete provider classes or cast API contracts to runtime implementations. +- Treat absence as normal for optional features and during reload transitions. +- API events and DTOs use Java types only; platform-specific events remain in the Velocity runtime. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 58ba7ad6..5f9592db 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,59 +1,130 @@ -# Architecture Overview +# ProxyFeatures 3.3 Architecture -ProxyFeatures has two kinds of modularity: Maven modules enforce compile-time ownership, while runtime feature -classes provide independently configurable network behavior. +ProxyFeatures 3.3 separates public integration contracts, reusable implementation support, wire contracts, and the +Velocity runtime. Feature implementations collaborate through typed capabilities; they do not import one another. -## Build Modules +## Module boundaries ```text -proxyfeatures-testkit ──(test only)──▶ api / contracts / velocity -proxyfeatures-api ───────────────────▶ velocity plugin -proxyfeatures-contracts ─────────────▶ velocity plugin and ServerFeatures -velocity plugin ─────────────────────▶ packaged ProxyFeatures.jar +proxyfeatures-api ───────────────────────────────▶ external plugins + │ + └───────────────────────────────────────▶ Velocity runtime +proxyfeatures-toolkit ──────────────────────────▶ Velocity runtime +proxyfeatures-contracts ─▶ Velocity runtime / ServerFeatures +proxyfeatures-testkit ────(test scope only)─────▶ project tests +Velocity runtime + the three production modules ▶ ProxyFeatures.jar ``` -- `proxyfeatures-api` contains public reusable framework contracts. It knows about Velocity APIs but not the - ProxyFeatures plugin bootstrap or feature implementations. -- `proxyfeatures-contracts` owns the deliberately small persistence and cross-platform messaging schemas shared with - ServerFeatures. ServerFeatures therefore does not depend on the full proxy plugin, and both runtimes serialize the - same wire types. -- `proxyfeatures-testkit` contains test infrastructure and is never a production dependency. -- `proxyfeatures-platform-velocity` owns bootstrapping, lifecycle implementation, and concrete network features. -- `proxyfeatures-platform-acceptance` is activated by a Maven profile. Its API-only consumer proves the API is - available from the packaged plugin, and its final module boots a pinned Velocity runtime. +- `proxyfeatures-api` is dependency-free Java. It owns the root API, feature catalog, capability contracts, immutable + DTOs, identifiers, lifecycle-safe references, and public extension points. +- `proxyfeatures-toolkit` owns reusable config, cache, localization, formatting, parsing, and HTTP implementation + support. It is an implementation library, not a plugin integration API. +- `proxyfeatures-contracts` owns only seven cross-process wire messages. Persistence entities and runtime services do + not belong here. +- `proxyfeatures-platform-velocity` owns the plugin bootstrap, Velocity adapters, persistence, lifecycle framework, + internal collaboration ports, and all concrete features. +- `proxyfeatures-platform-acceptance` is an optional real-runtime gate whose consumer compiles against only public + APIs and obtains `ProxyFeaturesApi` from the Velocity plugin instance. -Dependencies point toward stable contracts. Public modules must not import plugin bootstrap, framework-internal, or -feature-implementation packages. +Dependencies point inward toward stable contracts. The API never imports Velocity, DataRegistry, DataProvider, +toolkit, contracts, runtime framework, or feature implementation packages. -## Design Goals +## Runtime ownership -- Keep features isolated so one module can be changed without destabilizing others. -- Centralize common lifecycle concerns such as command/listener/task registration. -- Let operators adopt features gradually, not all at once. +The `ProxyFeatures` plugin instance implements `ProxyFeaturesApi` and owns three long-lived registries: -## Runtime Model +1. `DefaultCapabilityRegistry` publishes public API interfaces. +2. `DefaultFeatureCatalog` projects the explicit built-in manifest and lifecycle state. +3. `InternalServiceRegistry` connects implementation-only ports such as queue/admission coordination. -At startup, the plugin loads shared configuration, discovers available features, validates dependencies, and starts only the features that are enabled. +A feature registers services through its `FeatureApiManager`. Every registration is tagged with the owning feature +and gets an idempotent removal handle. Cleanup removes all services before the feature object is discarded. A +`CapabilityRef` remains stable across disable, enable, and reload, but resolves the current provider for each +operation. Consumers must not retain a resolved implementation across reloads. -During runtime, each feature owns its own behavior while using shared framework services for common tasks (config access, lifecycle management, logging, and integration points). +Feature lifecycle is externally observable as: -On reload/shutdown, features are asked to clean up resources so stale listeners, tasks, and cached state do not leak into the next run. - -The plugin entry point implements `ProxyFeaturesContext`, which gives reusable configuration/resource services the -minimal host capabilities they need without coupling the API module back to the concrete plugin. - -## Configuration and Data +```text +DISABLED → STARTING → ACTIVE → STOPPING → DISABLED + └──────────────────────→ FAILED +``` -- `config.yml` stores shared/global settings only. -- Each feature owns `features//config.yml`. +Startup includes context creation, defaults, configuration/localization reload, feature initialization, and optional +state restoration. Failure in any of those stages moves the catalog to `FAILED` and triggers cleanup. + +## Feature discovery and isolation + +`BuiltInFeatures` explicitly lists all 33 shipped features. Runtime classpath scanning is not used. The manifest is +the source of truth for feature identity, classification, and published capability names; each feature still owns its +metadata and configuration defaults. + +Production code inside one feature package may not import another feature package. Cross-feature behavior uses: + +- a public capability when external plugins can reasonably use the contract; +- a runtime-only collaboration port when the interaction is an implementation detail; +- a shared framework or toolkit utility when the behavior is genuinely generic; +- a wire contract when the boundary crosses processes. + +Automated architecture tests enforce these rules, the dependency-free API, the wire-only contracts module, the +explicit 33-feature manifest, and the absence of the removed discovery/service systems. + +## Feature inventory + +“Consumes” lists public capabilities. Internal ports are deliberately unavailable to external plugins. + +| Feature | Role | Provides | Consumes / internal collaboration | +|---|---|---|---| +| Announcer | Internal | — | shared lifecycle/toolkit | +| AntiBot | Internal | — | shared `IpAddressUtil` | +| AntiVPN | Provider | `NetworkLocationApi` | shared `IpAddressUtil` | +| Broadcast | Internal | — | — | +| Capacity | Provider | `AdmissionApi` | `MaintenanceApi`, `RestartApi`, `TwoFactorApi`; internal `QueueAdmissionPort` | +| ClientInfo | Internal | — | — | +| CommandHider | Internal | — | — | +| CommandLogger | Internal | — | — | +| CommandRelay | Internal | — | wire `CommandRelayMessage` | +| ConnectionInfo | Internal | — | — | +| Friends | Provider | `FriendshipApi` | `PresenceApi` | +| HLink | Internal | — | — | +| Hub | Internal | — | — | +| Maintenance | Provider | `MaintenanceApi` | core `MotdExtensions` | +| Messenger | Consumer | — | `FriendshipApi`, `PresenceApi` | +| Motd | Consumer | — | `PresenceApi`, `VersionApi`, core `MotdExtensions` | +| PlayerCount | Provider | `PlayerCountApi` | `PresenceApi`; wire `PlayerCountSnapshotMessage` | +| PlayerInfo | Consumer | — | `PlayerLanguageApi`, `SanctionsApi` | +| PlayerLanguage | Provider | `PlayerLanguageApi` | `NetworkLocationApi` | +| PlayerList | Consumer | — | `PresenceApi` | +| ProxyInfo | Internal | — | — | +| Queue | Provider | `QueueApi` | internal admission port | +| ResourcePack | Internal | — | — | +| Restart | Provider | `RestartApi` | internal capacity coordination; wire `RestartLifecycleMessage` | +| Sanctions | Provider | `SanctionsApi` | — | +| ServerLinks | Internal | — | — | +| SlashServer | Internal | — | — | +| StaffChat | Internal | — | wire `StaffChatMessage` | +| TextCommands | Internal | — | — | +| TwoFactor | Provider | `TwoFactorApi` | — | +| Vanish | Provider | `PresenceApi` | wire `VanishStateMessage` | +| VersionCheck | Provider | `VersionApi` | — | +| Votifier | Internal | — | wire `VoteMessage` | + +## Shared implementation services + +Generic implementation code lives outside feature packages: + +- one cached `PlayerReferenceResolver` per plugin runtime serves persistence-aware features; +- `SimpleHttpClient` is the shared HTTP transport, including Discord webhook JSON posts; +- `IpAddressUtil` is shared by AntiBot and AntiVPN; +- `DefaultMotdExtensions` owns ordered, lifecycle-safe MOTD contributions; +- toolkit config/cache writes use centralized path handling, validation, and atomic file replacement. + +## Configuration and data + +- `config.yml` stores shared feature enablement and global settings. +- Each feature owns `features//config.yml` and localized message files. - Framework messages live in `lang/messages*.yml`. -- Each feature owns `features//messages*.yml`. -- Some features may use additional local files for structured data. -- Shared database entities and Redis wire messages live in `proxyfeatures-contracts`; publishing and subscription - behavior remains platform-owned. - -## Why This Matters - -For operators, this architecture means safer rollout and easier troubleshooting. +- Persistence entities stay with their owning runtime feature or framework adapter. +- Redis messages stay in `proxyfeatures-contracts`; publishers/subscribers remain runtime-owned. -For contributors, it means clearer boundaries: implement behavior inside a feature, keep shared behavior in the framework, and avoid tight coupling between unrelated modules. +See [Public API](API.md) for plugin integration and [Migrating to 3.3](MIGRATING-3.3.md) for the intentionally +breaking transition. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index fa14263b..295a16a4 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -39,7 +39,8 @@ Target one module and its reactor dependencies during a tight feedback loop: - Make external calls fail-safe and time-bounded. - Clean up tasks/listeners/resources during disable and reload paths. - Favor simple, explicit code over clever abstractions. -- Keep public code in `proxyfeatures-api`, cross-project persistence types in `proxyfeatures-contracts`, and +- Keep public contracts in `proxyfeatures-api`, reusable runtime support in `proxyfeatures-toolkit`, wire messages in + `proxyfeatures-contracts`, persistence types with their runtime owner, and Velocity implementation details in `proxyfeatures-platform-velocity`. - Do not make public modules depend on the plugin module. Introduce a narrow public interface when reusable code needs a host capability. diff --git a/docs/MIGRATING-3.0.md b/docs/MIGRATING-3.0.md deleted file mode 100644 index 0dc13e90..00000000 --- a/docs/MIGRATING-3.0.md +++ /dev/null @@ -1,46 +0,0 @@ -# Migrating to ProxyFeatures 3.0 - -Version 3.0 separates reusable contracts from the Velocity runtime. The installed plugin and its runtime identity are -unchanged, but source consumers should select an explicit public module. - -## Server operators - -- Replace the existing plugin with `ProxyFeatures.jar`; its filename, plugin id, and configuration locations are - unchanged. -- Use Java 25 and the Velocity version listed in the project README. -- Back up configuration and data, then exercise the acceptance checks relevant to your network before rollout. - -## Maven consumers - -The historical runtime coordinate remains available: - -```text -nl.hauntedmc.proxyfeatures:proxyfeatures:3.0.0 -``` - -New code should depend with `provided` scope on the smallest supported surface: - -- `nl.hauntedmc.proxyfeatures:proxyfeatures-api:3.0.0` for the reusable Velocity-facing API. -- `nl.hauntedmc.proxyfeatures:proxyfeatures-contracts:3.0.0` for player/sanction persistence and shared wire-message - contracts. - -Do not depend on `proxyfeatures-platform-velocity` as an artifact id; that is the repository module directory, while -the published runtime artifact deliberately retains the `proxyfeatures` artifact id. - -## Source compatibility - -- Reusable configuration and resource services now accept `ProxyFeaturesContext`, not the concrete plugin bootstrap. - Implement that narrow interface in tests or embedding hosts. -- `SimpleHttpClient` now uses its own `FormParameter` record and the JDK HTTP client. Replace Apache - `NameValuePair` arguments with `new SimpleHttpClient.FormParameter(name, value)` and handle - `InterruptedException` without clearing the thread's interruption signal. -- Persistence types shared with ServerFeatures now come from `proxyfeatures-contracts`. -- Command relay, staff chat, vanish state, and vote wire types now live under - `nl.hauntedmc.proxyfeatures.contracts.messaging`. Their message type is fixed by each class; callers no longer pass - a free-form type to `StaffChatMessage` or `VanishStateMessage`. -- Repository source paths moved from the root `src/` tree into `proxyfeatures-api`, `proxyfeatures-contracts`, and - `proxyfeatures-platform-velocity`. -- Build output moved from `target/ProxyFeatures.jar` to - `proxyfeatures-platform-velocity/target/ProxyFeatures.jar`. - -Recompile integrations against 3.0; do not assume binaries compiled against 2.x remain compatible. diff --git a/docs/MIGRATING-3.3.md b/docs/MIGRATING-3.3.md new file mode 100644 index 00000000..7475a255 --- /dev/null +++ b/docs/MIGRATING-3.3.md @@ -0,0 +1,58 @@ +# Migrating to ProxyFeatures 3.3 + +ProxyFeatures 3.3 is an intentional clean break. It does not ship compatibility adapters, deprecated aliases, old +package facades, or fallback registration through DataRegistry. Consumers must migrate to the new system before +upgrading the runtime. + +## Required consumer changes + +1. Update `proxyfeatures-api` to `3.3.0` with `provided` scope. +2. Discover the `proxyfeatures` Velocity plugin instance and cast it to `ProxyFeaturesApi`. +3. Resolve integrations through `api.capabilities().reference(Contract.class)`. +4. Keep `CapabilityRef`, not a resolved implementation, across reloads. +5. Replace runtime implementation DTOs/entities with public immutable snapshots. +6. Close extension registrations during your own plugin lifecycle. + +## Removed-to-new mapping + +| Removed system | 3.3 replacement | +|---|---| +| `ProxyFeaturesContext` | `ProxyFeaturesApi` | +| DataRegistry `FeatureServiceDirectory` publication | ProxyFeatures-owned `CapabilityRegistry` | +| root `api.capacity.CapacityAPI` | `capability.admission.AdmissionApi` | +| root `api.queue.QueueAdmissionAPI` | public `QueueApi`; queue/admission hand-off is runtime-only | +| root `api.friends.FriendshipAPI` | `capability.social.FriendshipApi` | +| feature `VanishAPI` | `capability.presence.PresenceApi` | +| feature `CountryAPI` | `capability.player.NetworkLocationApi` | +| feature `LanguageAPI` | `capability.player.PlayerLanguageApi` | +| internal player-count DTO/API | `capability.network.PlayerCountApi` and `PlayerCountSnapshot` | +| direct Sanctions entity/ORM access | `capability.moderation.SanctionsApi` and `SanctionSnapshot` | +| direct Maintenance/Restart/TwoFactor feature access | their `capability.operations` contracts | +| static MOTD override registry | core `MotdExtensions` capability | +| API config/cache/localization/util packages | `proxyfeatures-toolkit` for runtime implementation use | +| classpath feature scanning | explicit built-in manifest | + +## Behavioral changes + +- Capacity is the sole admission-policy owner. Public requests cannot forge permission, reserved-slot, maintenance, + restart, or two-factor bypass state. +- Queue is a public asynchronous capability. Its private admission hand-off is not an external API. +- Capability registrations are feature-owned and disappear automatically on cleanup. +- The feature catalog reports startup, active, stopping, disabled, and failed states for all 33 built-ins. +- Cross-feature calls resolve capabilities at operation time, so disable/reload does not leave stale references. +- MOTD contributions are ordered registrations with explicit ownership handles. +- `proxyfeatures-contracts` contains only wire messages; persistence types moved back to the runtime. +- Generic config/cache/localization/formatting/HTTP code moved to `proxyfeatures-toolkit` and is no longer presented as + the public plugin API. + +## No compatibility mode + +There is no switch that restores old packages or DataRegistry feature-service lookup. A mixed old/new plugin set will +fail to link or report unavailable capabilities by design. Upgrade and test all consumers together, then run: + +```bash +./mvnw -B -ntp clean verify +./mvnw -B -ntp -Pplatform-acceptance verify +``` + +See [Public API](API.md) for integration examples and [Architecture](ARCHITECTURE.md) for ownership rules. diff --git a/docs/README.md b/docs/README.md index affd5390..b5840468 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,8 @@ If you run the plugin: - [Feature reference](features/README.md): commands, permissions, configuration, integrations, runtime behavior, and troubleshooting for every Velocity feature. - [Configuration](CONFIGURATION.md): day-to-day setup and safe change workflow. -- [Migrating to 3.0](MIGRATING-3.0.md): operator and dependency changes from the 2.x line. +- [Public API](API.md): capability discovery, reload-safe usage, contracts, and extension registration. +- [Migrating to 3.3](MIGRATING-3.3.md): required changes for the intentionally breaking 3.3 API. - [Architecture](ARCHITECTURE.md): how the plugin is structured and how features are managed. If you contribute code: diff --git a/docs/RELEASE.md b/docs/RELEASE.md index d3390b3d..b1f831aa 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -52,7 +52,8 @@ The Maven Wrapper distribution and third-party GitHub Actions are checksum/SHA p - Repository: `https://maven.pkg.github.com/HauntedMC/ProxyFeatures` - Group: `nl.hauntedmc.proxyfeatures` -- Public artifacts: `proxyfeatures-api`, `proxyfeatures-contracts` +- Public integration artifact: `proxyfeatures-api`; shared runtime artifacts: `proxyfeatures-toolkit` and + `proxyfeatures-contracts` - Runtime artifact: `proxyfeatures` ServerFeatures releases that consume a new contracts version must be published after the corresponding ProxyFeatures diff --git a/docs/TESTING.md b/docs/TESTING.md index fa1333fc..a6504da6 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -46,6 +46,14 @@ When you change behavior, add or update tests close to that behavior: Focus on user-visible behavior and regression-prone logic. Avoid writing tests that only duplicate framework boilerplate. +The Velocity module also contains architecture tests. They enforce that feature packages do not import each other, +the public API remains dependency-free, shared contracts remain wire-only, the built-in catalog remains explicit and +complete, and removed pre-3.3 service/discovery systems cannot return unnoticed. Treat those tests as executable module +boundaries, not assertions to weaken when a change crosses a boundary. + +Public API contract tests cover identifier normalization, immutable snapshots, request invariants, stable capability +references, admission leases, queue results, extension values, and the domain contracts exposed to external plugins. + ## Test Quality Bar Use these rules when adding or reviewing tests: diff --git a/docs/features/README.md b/docs/features/README.md index 58dade4f..b2415bfe 100644 --- a/docs/features/README.md +++ b/docs/features/README.md @@ -25,7 +25,7 @@ This directory documents all **33** feature modules currently present in ProxyFe ## Social and moderation -- [Vanish](vanish.md) — Mirrors versioned backend vanish updates into a proxy-local online-only `VanishAPI` view. +- [Vanish](vanish.md) — Mirrors versioned backend vanish updates into a proxy-local online-only `PresenceApi` view. - [Friends](friends.md) — Stores canonical directional relationship rows for requests, accepted friendships and blocks, with local caches and activity notices. - [Messenger](messager.md) — Provides online private messages, replies, toggle/privacy modes, blocks, spy copies and delivered-message history. - [StaffChat](staffchat.md) — Subscribes to Redis staff-chat payloads, routes them to local permission viewers and announces staff activity. diff --git a/docs/features/antivpn.md b/docs/features/antivpn.md index 1376f2a5..47c0f7f2 100644 --- a/docs/features/antivpn.md +++ b/docs/features/antivpn.md @@ -2,7 +2,7 @@ > Velocity · Feature ID `antivpn` · disabled by default · asynchronous IP intelligence admission policy -AntiVPN resolves every login's numeric remote IP, bypasses configured exact/CIDR/private ranges, consults a bounded memory/disk cache, then runs an ordered asynchronous provider chain until it has enough country/VPN data. Region policy is evaluated before VPN policy. The result either allows login and stages the country in `CountryAPI`, or denies the `LoginEvent`, notifies staff and asynchronously writes an audit row. +AntiVPN resolves every login's numeric remote IP, bypasses configured exact/CIDR/private ranges, consults a bounded memory/disk cache, then runs an ordered asynchronous provider chain until it has enough country/VPN data. Region policy is evaluated before VPN policy. The result either allows login and stages the country in `NetworkLocationApi`, or denies the `LoginEvent`, notifies staff and asynchronously writes an audit row. There is no permission bypass for ordinary players; trusted sources must be handled by the IP whitelist. @@ -141,9 +141,9 @@ A timeout, exception or null result increments error metrics. `on_api_error=DENY This is distinct from a provider successfully returning unknown country/VPN, which uses the two unknown policies. -## Country API +## Network location API -`CountryAPI` is registered through the feature API manager and backed by `CountryService`. +`NetworkLocationApi` is registered through the feature API manager and backed by `CountryService`. It exposes the recently evaluated country for connected UUIDs. AntiVPN clears the entry on `DisconnectEvent`; TTL protects against missing disconnect cleanup. @@ -239,7 +239,7 @@ Whitelisting an IP while a lookup is in flight affects future `evaluate` calls; - Fresh provider results can be labeled MEM by policy evaluation. - Only whitelist can be live-reloaded; check/policy/provider/cache fields are captured. - Login waits on async provider completion through `.join()` on an async event worker. -- Country API is staged state, not lookup-on-demand. +- Network location API is staged state, not lookup-on-demand. - No cross-proxy cache/decision sharing exists. - Fail-open provider errors are allowed but audited when possible. - Missing remote IP always denies, independently of on-api-error policy. @@ -257,7 +257,7 @@ Whitelisting an IP while a lookup is in flight affects future `evaluate` calls; 9. Test missing IP, online/offline command resolution and DataRegistry outage. 10. Stop ORM and confirm policy remains while audit disables/fails. 11. Inspect staff notification throttling and allowed-with-error audit rows. -12. Disconnect during lookup and verify CountryAPI lifecycle. +12. Disconnect during lookup and verify NetworkLocationApi lifecycle. 13. Run multiple proxy instances and confirm cache/metrics/whitelist runtime are local. 14. Review IP retention/privacy in JSON cache and MySQL audit. @@ -270,7 +270,7 @@ Whitelisting an IP while a lookup is in flight affects future `evaluate` calls; - Whitelist: `internal/IpWhitelist.java` - Provider merge: `internal/provider/ProviderChain.java` - Provider construction/implementations: `ProviderRegistry.java`, `ProxyCheckProvider.java`, `IP2LocationProvider.java` -- Country API/state: `api/CountryAPI.java`, `internal/CountryService.java` +- Network location API/state: `proxyfeatures-api/.../capability/player/NetworkLocationApi.java`, `internal/CountryService.java` - Command: `command/AntiVPNCommand.java` - Metrics/notifications: `MetricsCollector.java`, `NotificationService.java` - Audit: `audit/AntiVpnAuditLogService.java`, `PlayerAntiVpnLogEntity.java` diff --git a/docs/capacity-placeholders.md b/docs/features/capacity-placeholders.md similarity index 100% rename from docs/capacity-placeholders.md rename to docs/features/capacity-placeholders.md diff --git a/docs/features/capacity.md b/docs/features/capacity.md index b3bf40d9..11631944 100644 --- a/docs/features/capacity.md +++ b/docs/features/capacity.md @@ -201,3 +201,22 @@ Capacity publishes its current in-memory snapshot for ServerFeatures placeholder observational only. Redis failure does not alter admission decisions. A reload reconfigures the publisher when its channel, publisher ID or interval changes and keeps the existing publisher when those values are unchanged. + +## Public `AdmissionApi` + +Capacity registers `AdmissionApi` from `proxyfeatures-api`. `tryAcquire(AdmissionRequest)` performs one atomic +evaluation and returns either a typed denial or an `AdmissionLease` for the exact player/target/intent. Public callers +cannot submit reserved status, permissions, bypass flags, maintenance state, or restart state; Capacity derives every +trusted input itself. Callers commit after a successful connection or release/close an abandoned lease. + +`snapshot()` returns immutable proxy, gameplay, group, and server scope observations. Snapshots and Redis messages are +diagnostic only and must never be used to authorize a connection. + +The richer `CapacityAPI` used by Queue/Restart is a runtime-only collaboration port. It is not an external API. + +## Source reference + +- `features/capacity/Capacity.java` +- `features/capacity/internal/AdmissionCapability.java` +- `features/capacity/internal/CapacityService.java` +- `proxyfeatures-api/.../capability/admission/AdmissionApi.java` diff --git a/docs/features/friends.md b/docs/features/friends.md index d318947b..c18f5e51 100644 --- a/docs/features/friends.md +++ b/docs/features/friends.md @@ -4,7 +4,7 @@ Friends manages network-wide friend requests, accepted relationships, blocking, per-player availability, list/request views, bulk actions and friend activity notifications. Relationships are stored by canonical DataRegistry player IDs in one normalized pair row, while command/API results use UUID/name snapshots and online Velocity state. -It also registers `FriendshipAPI`, which Messenger uses for `FRIENDS` privacy checks. +It also registers `FriendshipApi`, which Messenger uses for `FRIENDS` privacy checks. ## Configuration @@ -265,25 +265,25 @@ Failure cases include: It is a server-join helper, not teleport-to-player coordinates. It uses Velocity server connection APIs and does not bypass backend admission/maintenance/full/queue policies. -## `FriendshipAPI` +## `FriendshipApi` Registered API interface: ```java -boolean areFriends(UUID first, UUID second); -CompletionStage areFriendsAsync(UUID first, UUID second); +CompletionStage areFriends(UUID firstPlayer, UUID secondPlayer) +CompletionStage relationship(UUID firstPlayer, UUID secondPlayer) ``` Implementation behavior: -- null/self pairs return false; -- synchronous method delegates to `FriendsService.areFriends` and can load cache/ORM depending on service state; -- async method runs the same check through feature async scheduling; -- errors log and return false. +- null inputs fail fast and self pairs complete with `false`; +- lookup runs through the feature task manager and never exposes ORM entities; +- disabling the feature completes pending/new lookups exceptionally; +- `relationship` derives `FRIENDS` or `NOT_FRIENDS` from the asynchronous decision. Messenger's `FRIENDS` privacy mode uses this service. Failure/unavailability therefore fails closed to “not friends.” Consumers must also handle the Friends feature being disabled/unregistered. -The API checks ACCEPTED relation only; PENDING/BLOCKED are false. +The API checks reciprocal ACCEPTED relation only; PENDING/BLOCKED are false. ## Activity notifications @@ -380,7 +380,7 @@ In-flight command futures can complete around disable; service/task-manager reje - Disable removes pending requests but keeps accepted friends/blocks. - Cache is local to one proxy and can be stale across proxy nodes until TTL. - Request expiry is milliseconds and cleanup runs every 5 minutes. -- API errors return false. +- API failures complete exceptionally; consumers choose fail-open or fail-closed policy. - Root base permission is required for all subcommands. - `/friend server` connects to a backend; it does not bypass queue/maintenance. - Activity state/generations are process-local and vanish-aware. @@ -411,6 +411,7 @@ In-flight command futures can complete around disable; service/task-manager reje - ORM entities: `entity/FriendRelationEntity.java`, `FriendSettingsEntity.java`, `FriendStatus.java` - Local snapshots: `support/FriendsCache.java`, `FriendSnapshot.java` - Command tree/feedback/visibility: `command/FriendCommand.java` -- Registered API: `api/FriendshipAPI.java`, `FriendshipApiImpl.java` +- Public API: `proxyfeatures-api/.../capability/social/FriendshipApi.java` +- Runtime adapter: `api/FriendshipApiImpl.java` - Activity events/generation: `listener/FriendActivityListener.java`, `FriendActivityPolicy.java` - Activity delivery: `FriendsActivityService` and friends messaging support diff --git a/docs/features/maintenance.md b/docs/features/maintenance.md index 2bb31668..63176504 100644 --- a/docs/features/maintenance.md +++ b/docs/features/maintenance.md @@ -198,9 +198,16 @@ Restored gamemode maintenance similarly schedules immediate evacuation for every This matters during feature reloads: reloading while a countdown is active turns persisted active state into immediate enforcement after initialization. +## Public `MaintenanceApi` + +The feature publishes read-only `MaintenanceApi`. It reports global/backend scope activity, evaluates a player's +bypass permission for a scope, and returns an immutable `MaintenanceSnapshot`. Admission and external integrations use +this contract instead of accessing `MaintenanceHandler` or feature registry state. + ## MOTD integration -Maintenance registers line-2 override key `maintenance` with priority `100` in `MotdLine2OverrideRegistry`. +Maintenance resolves core `MotdExtensions` and registers owner `maintenance` with priority `100`. Its lifecycle-owned +`ExtensionRegistration` is closed during disable. The override returns configured `motd_line2` only when: @@ -267,4 +274,5 @@ Velocity player iteration, messaging and connection operations are invoked throu - `features/maintenance/internal/MaintenanceHandler.java` - `features/maintenance/command/MaintenanceCommand.java` - `features/maintenance/listener/MaintenanceConnectionListener.java` -- `features/motd/internal/MotdLine2OverrideRegistry.java` +- `framework/extension/DefaultMotdExtensions.java` +- `proxyfeatures-api/.../extension/MotdExtensions.java` diff --git a/docs/features/messager.md b/docs/features/messager.md index dd7a7f0b..dcd32e0f 100644 --- a/docs/features/messager.md +++ b/docs/features/messager.md @@ -205,7 +205,7 @@ The reply partner is volatile and set symmetrically after each delivered message ## Target visibility and vanish -Ordinary viewers cannot resolve/list a target reported vanished by `VanishAPI`. Commands and replies return the same `message.offline` output as a genuinely offline player. +Ordinary viewers cannot resolve/list a target reported vanished by `PresenceApi`. Commands and replies return the same `message.offline` output as a genuinely offline player. Visibility bypass: @@ -213,7 +213,7 @@ Visibility bypass: proxyfeatures.feature.vanish.bypass ``` -When VanishAPI is absent, direct lookups/listing fail open and all online players are visible. If the Vanish service throws, single-target visibility fails closed while suggestion listing returns empty and logs the error. +When PresenceApi is absent, direct lookups/listing fail open and all online players are visible. If the Vanish service throws, single-target visibility fails closed while suggestion listing returns empty and logs the error. Console suggestions list all online players; commands themselves remain player-only for direct/reply/settings operations. diff --git a/docs/features/motd.md b/docs/features/motd.md index f9af562b..e467aecf 100644 --- a/docs/features/motd.md +++ b/docs/features/motd.md @@ -71,26 +71,26 @@ A runtime override is checked first. Without a nonblank override, line 2 resolve This makes the ping description tolerant of incomplete lists rather than failing the event. -## Runtime line-2 override registry +## Public MOTD extension registry -`MotdLine2OverrideRegistry` is a process-global concurrent registry used by other features. +Core publishes the lifecycle-safe `MotdExtensions` capability for internal features and external plugins. Registration contract: ```java -register(String key, int priority, Supplier supplier) -unregister(String key) +ExtensionRegistration register(String owner, int priority, MotdContributor contributor) ``` Resolution: 1. entries are sorted by descending priority; -2. ties are sorted by ascending registry key; -3. suppliers are called in that order; -4. a supplier exception, null or blank result is skipped; +2. ties are sorted by normalized owner key; +3. contributors receive `MotdContext` and return optional `MotdContribution` line overrides; +4. an exception, null, empty or blank contribution is skipped; 5. the first usable value wins. -Maintenance currently registers key `maintenance` at priority `100`. Overrides are raw line-2 templates and receive the normal MOTD placeholders after selection. +The returned registration is idempotently closed by its owner. Maintenance registers owner `maintenance` at priority +`100`. Overrides receive the normal MOTD placeholders after selection. ## Player-count presentation @@ -100,7 +100,7 @@ The handler starts from the incoming `ServerPing.Players` object: raw online = incoming online, or 0 when absent raw max = incoming max, or raw online when absent sample = incoming sample, or empty -vanished = VanishAPI.getVanishedCount(), or 0 when unavailable +vanished = PresenceApi.snapshot().hiddenCount(), or 0 when unavailable visible = max(0, raw online - vanished) adjusted online = (int) (visible * max(0, multiplier)) adjusted max = max(adjusted online, raw max) @@ -108,7 +108,7 @@ adjusted max = max(adjusted online, raw max) The multiplication uses Java's narrowing cast to `int`, so fractional results are truncated toward zero. The original sample list is preserved even though it may contain players excluded from the displayed count; MOTD itself does not filter sample identities. -When no Vanish API is registered, vanished count is treated as zero. +When no `PresenceApi` is registered, hidden count is treated as zero. ## Version presentation @@ -133,7 +133,7 @@ Both lines and runtime overrides support exact uppercase replacements: | `{MAX}` | Adjusted max count. | | `{ONLINE_REAL}` | Incoming raw online count. | | `{MAX_REAL}` | Incoming raw max count. | -| `{VANISHED}` | Current Vanish API count. | +| `{VANISHED}` | Current `PresenceApi` hidden count. | | `{SERVER_NAME}` | Global setting `server_name`, default `proxy`, escaped for MiniMessage. | | `{VERSION}` | Displayed version name, escaped for MiniMessage. | | `{PROTOCOL}` | Displayed protocol integer. | @@ -205,5 +205,6 @@ No PlaceholderAPI expansion is registered; this is a Velocity server-list featur - `features/motd/Motd.java` - `features/motd/internal/MotdHandler.java` - `features/motd/internal/MotdConfig.java` -- `features/motd/internal/MotdLine2OverrideRegistry.java` +- `framework/extension/DefaultMotdExtensions.java` +- `proxyfeatures-api/.../extension/MotdExtensions.java` - `features/motd/listener/PingListener.java` diff --git a/docs/features/playercount.md b/docs/features/playercount.md index fc109f71..5e3b538a 100644 --- a/docs/features/playercount.md +++ b/docs/features/playercount.md @@ -32,8 +32,8 @@ Configuration is read during feature initialization. Changes require feature rel Initialization order: -1. construct `PlayerCountAPI` over the current `ProxyServer` and optional `VanishAPI` supplier; -2. register `PlayerCountAPI` as a lifecycle-owned service; +1. construct the internal `PlayerCountService` adapter over the current `ProxyServer` and optional `PresenceApi` reference; +2. register it under the public `PlayerCountApi` contract as a lifecycle-owned service; 3. attempt to register non-durable Redis `MessagingDataAccess` named `redis`; 4. when available, normalize settings, create publisher and schedule repeating publication; 5. when unavailable, warn and return. @@ -44,7 +44,7 @@ No database or DataRegistry access is required. ## Count semantics -Every `Counts` value contains: +Every `PlayerCounts` value contains: ```text online = real connected count, including vanished players @@ -99,44 +99,45 @@ Operators should treat normalized backend names as stable contract keys across V ## Vanish integration -The API resolves optional `VanishAPI` at capture time rather than caching one instance. +The implementation resolves optional `PresenceApi` at capture time through the reload-safe capability registry. When absent—or when the supplier returns null/empty—vanished count is zero and real online counts remain available. -When present, `getVanishedPlayers()` is called and converted into an immutable UUID set. Null/empty output becomes an empty set. +When present, `PresenceApi.snapshot().hiddenPlayers()` supplies the immutable hidden UUID set. -The publisher does not ask `isVanished` for each player and does not apply viewer-specific bypass logic. It publishes one canonical network view. +The publisher does not ask `isHidden` for each player and does not apply viewer-specific bypass logic. It publishes one canonical network view. -An exception from service lookup or `getVanishedPlayers()` propagates out of capture; that publication cycle is skipped and warning-throttled. +An exception from service lookup or snapshot capture propagates out of capture; that publication cycle is skipped and +warning-throttled. Because ProxyFeatures Vanish is itself a proxy-local online-state mirror, stale/missing Vanish updates can make published vanished counts temporarily inaccurate. -## Local `PlayerCountAPI` +## Public `PlayerCountApi` -Registered service class: +Registered public contract: ```java -nl.hauntedmc.proxyfeatures.features.playercount.internal.PlayerCountAPI +nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCountApi ``` Methods: ```java -PlayerCountSnapshot capture() -PlayerCountSnapshot.Counts getNetworkCounts() -PlayerCountSnapshot.Counts getServerCounts(String serverName) +PlayerCountSnapshot snapshot() ``` -`getNetworkCounts` and `getServerCounts` each perform a new complete capture. They are not cheap cached getters. +Every `snapshot()` call performs a new complete capture. It is not a cached getter. `PlayerCountSnapshot` is immutable: ```text -network: Counts -servers: immutable normalized map +network: PlayerCounts(online, hidden), with derived visible() +servers: immutable map +observedAt: Instant ``` -The API is local/internal concrete service, not part of `proxyfeatures-api` and not a cross-plugin compatibility promise unless consumers intentionally depend on this implementation package. +The API and DTOs are part of `proxyfeatures-api`. The concrete `PlayerCountService` implementation remains internal and +must not be cast to or retained by consumers. ## Wire contract @@ -319,7 +320,7 @@ at every publication/API call. At the default two-second interval this is lightw - wall-clock timestamps can jump; - Vanish accuracy depends on its local mirror; - capture is structurally immutable but not transactional; -- local API lives in an internal package; +- public API resolves through a reload-safe capability reference; - no proxy-side placeholders/commands. ## Operational verification @@ -343,8 +344,9 @@ at every publication/API call. At the default two-second interval this is lightw ## Source reference - `features/playercount/PlayerCount.java` -- `features/playercount/internal/PlayerCountAPI.java` -- `features/playercount/internal/PlayerCountSnapshot.java` +- `features/playercount/internal/PlayerCountService.java` +- `proxyfeatures-api/.../capability/network/PlayerCountApi.java` +- `proxyfeatures-api/.../capability/network/PlayerCountSnapshot.java` - `features/playercount/internal/PlayerCountPublisher.java` - `proxyfeatures-contracts/.../PlayerCountSnapshotMessage.java` -- `features/vanish/internal/VanishAPI.java` +- `proxyfeatures-api/.../capability/presence/PresenceApi.java` diff --git a/docs/features/playerinfo.md b/docs/features/playerinfo.md index 85779c4f..f7849dba 100644 --- a/docs/features/playerinfo.md +++ b/docs/features/playerinfo.md @@ -2,7 +2,9 @@ > Velocity · Feature ID `playerinfo` · disabled by default · canonical profile, activity, shared-IP and sanction inspection -PlayerInfo exposes one staff command that combines a full DataRegistry player profile with live Velocity presence, optional language preference, possible alternate-account names inferred from last IP, and active sanctions read directly from the sanctions table. +PlayerInfo exposes one staff command that combines a full DataRegistry player profile with live Velocity presence, +optional language preference, possible alternate-account names inferred from last IP, and sanctions read through the +public `SanctionsApi` capability. It does not aggregate Friends, ClientInfo or ConnectionInfo despite the broader wording in the original first-pass page. @@ -70,7 +72,7 @@ Missing connection timestamps render an intrinsic em dash (`—`), not a localiz ## Language integration -PlayerInfo optionally locates `LanguageAPI`. +PlayerInfo optionally locates `PlayerLanguageApi`. - Missing API: `playerinfo.language_unknown`. - Explicit language: enum name. @@ -112,16 +114,9 @@ Because this is a second asynchronous phase, main profile output can appear firs ## Active sanctions -PlayerInfo creates its own ORM context for `SanctionEntity` and queries: - -```text -targetPlayerId = canonical player ID -active = true -expiresAt IS NULL OR expiresAt > now -order by createdAt desc -``` - -It does not require the Sanctions feature to be loaded, provided the entity/table and database access exist. +PlayerInfo resolves `SanctionsApi` for each request and calls `find(playerUuid, true)`. When Sanctions is disabled or +the capability lookup/query fails, the section degrades to an empty result without importing its entity or ORM code. +This keeps persistence ownership inside Sanctions and makes feature reloads safe. Each item displays raw enum type, reason, expiry/permanent label and creation timestamp. Actor, sanction ID and target IP are not shown. @@ -147,11 +142,13 @@ No PlaceholderAPI expansion or public PlayerInfo service is registered. ## Threading and lifecycle -DataRegistry profile/alt operations return completion stages. Rendering callbacks are scheduled through the feature task manager. The active-sanctions ORM query is executed during the first scheduled render phase. +DataRegistry profile/alt operations and `SanctionsApi` return completion stages. Rendering callbacks are scheduled +through the feature task manager. The command captures the original `CommandSource`; it does not re-resolve a player source before the delayed alt callback. Console remains safe, while a disconnected player source may receive no visible result according to Velocity behavior. -Initialization requires DataRegistry plus ORM access and registers only the command. Disable has no custom cleanup. +Initialization requires DataRegistry and registers only the command. Sanctions persistence remains owned by the +optional Sanctions feature. ## Operational limitations and privacy @@ -163,9 +160,9 @@ Initialization requires DataRegistry plus ORM access and registers only the comm - timezone invalidity silently falls back; - profile failure is mislabeled as not found; - alt failure is mislabeled as no alts; -- active sanctions are a point-in-time query independent of Sanctions feature state; +- sanctions are point-in-time public snapshots and unavailable when Sanctions is disabled; - no pagination or clickable drill-down; -- nickname/language sources are DataRegistry/LanguageAPI only; +- nickname/language sources are DataRegistry/PlayerLanguageApi only; - output tail can interleave across requests. ## Operational verification @@ -180,7 +177,7 @@ Initialization requires DataRegistry plus ORM access and registers only the comm 8. Force alt lookup failure and confirm it appears empty. 9. Test shared household/VPN IPs and document false-positive expectations. 10. Use a large name history and concurrent command calls to inspect message volume/interleaving. -11. Run with Sanctions feature disabled but database table available. +11. Run with Sanctions disabled and confirm graceful empty punishment output. ## Source reference @@ -188,5 +185,5 @@ Initialization requires DataRegistry plus ORM access and registers only the comm - `features/playerinfo/command/PlayerInfoCommand.java` - `features/playerinfo/service/PlayerInfoService.java` - DataRegistry `PlayerData`/`PlayerProfile` -- `features/playerlanguage/api/LanguageAPI.java` -- `features/sanctions/entity/SanctionEntity.java` +- `proxyfeatures-api/.../capability/player/PlayerLanguageApi.java` +- `proxyfeatures-api/.../capability/moderation/SanctionsApi.java` diff --git a/docs/features/playerlanguage.md b/docs/features/playerlanguage.md index b3ea5be7..f57dc898 100644 --- a/docs/features/playerlanguage.md +++ b/docs/features/playerlanguage.md @@ -9,7 +9,7 @@ preference: AUTO | NL | EN effective: NL | EN ``` -`AUTO` resolves Dutch for configured country codes and otherwise a configured fallback. The feature registers `LanguageAPI` so localization and PlayerInfo can read the current cached result. +`AUTO` resolves Dutch for configured country codes and otherwise a configured fallback. The feature registers `PlayerLanguageApi` so localization and PlayerInfo can read the current cached result. ## Hard dependencies @@ -18,7 +18,7 @@ Initialization requires: - DataRegistry API; - DataRegistry feature flag `LANGUAGE` enabled. -Missing either fails feature initialization. Country detection is optional through AntiVPN's `CountryAPI`. +Missing either fails feature initialization. Country detection is optional through AntiVPN's `NetworkLocationApi`. ## Configuration @@ -105,7 +105,7 @@ Disconnect removes the cached state. ## AUTO resolution -Country code comes from optional `CountryAPI.getCountry(UUID)`. +Country code comes from optional `NetworkLocationApi.countryCode(UUID)`. ### Warm/default evaluation @@ -114,7 +114,7 @@ country in dutch set -> NL otherwise -> fallback ``` -Missing CountryAPI/record yields `UNKNOWN` and therefore fallback. +Missing NetworkLocationApi/record yields `UNKNOWN` and therefore fallback. ### Setting preference to AUTO @@ -156,15 +156,16 @@ Stored effective: `stateCache` is a local concurrent UUID map. -`LanguageAPI` methods: +Public `PlayerLanguageApi` methods use JDK `Locale` values and asynchronous mutation: ```java -Language get(UUID) // effective -Language getPreference(UUID) -void set(UUID, Language) // starts async write and discards result +Optional resolvedLanguage(UUID playerId) +Optional preference(UUID playerId) // empty represents AUTO +CompletionStage setPreference(UUID playerId, Optional language) ``` -`LanguageService.setAsync` is available internally for command confirmation. +`Optional.empty()` selects AUTO. `LanguageService` retains its internal enum and `setAsync` helper for command +confirmation; neither is part of the public API. ### Cache-miss behavior @@ -181,7 +182,7 @@ This is an important distinction between fast hot-path API semantics and durable ## Localization integration -The shared `LocalizationHandler` uses `LanguageAPI` when available to choose audience message files. Effective NL/EN is used; AUTO is never a localizable file code. +The shared `LocalizationHandler` uses `PlayerLanguageApi` when available to choose audience message files. Effective NL/EN is used; AUTO is never a localizable file code. When the API is absent, localization falls back according to framework behavior. PlayerLanguage itself registers no PlaceholderAPI expansion. @@ -210,12 +211,12 @@ All display values are raw enum codes rather than translated language names. - Warm load failure caches a computed default without persisting it and sends no error. - Staff target lookup/update failure is shown as `language.not_found`, conflating unavailable storage with missing identity. - Scheduling a completion failure logs a warning; the source may receive no final response. -- `LanguageAPI.set` is fire-and-forget and provides no caller-visible failure. -- CountryAPI absence/failure represented as empty falls back deterministically. +- `PlayerLanguageApi.setPreference` completes exceptionally when persistence rejects the update. +- NetworkLocationApi absence/failure represented as empty falls back deterministically. ## Lifecycle and limitations -Initialization registers command, listener and `LanguageAPI`. Disable has no custom cache clear; listener teardown/feature disposal handles lifecycle, while online map remains in the service object until discarded. +Initialization registers command, listener and `PlayerLanguageApi`. Disable has no custom cache clear; listener teardown/feature disposal handles lifecycle, while online map remains in the service object until discarded. Limitations: @@ -235,7 +236,7 @@ Limitations: 1. Enable without DataRegistry LANGUAGE support and confirm fail-fast initialization. 2. Test first login creation for AUTO and explicit defaults. -3. Test every Dutch country, known non-Dutch and missing CountryAPI case. +3. Test every Dutch country, known non-Dutch and missing NetworkLocationApi case. 4. Compare AUTO write with unknown country versus next-login warm recomputation. 5. Validate `/language`, aliases, self/others permissions and extra arguments. 6. Query an offline player with persisted nondefault language and confirm current cache-miss limitation. @@ -253,6 +254,6 @@ Limitations: - `features/playerlanguage/listener/LanguageListener.java` - `features/playerlanguage/command/LanguageCommand.java` - `features/playerlanguage/command/LanguageCommandPolicy.java` -- `features/playerlanguage/api/LanguageAPI.java` -- `proxyfeatures-api/.../Language.java` +- `proxyfeatures-api/.../capability/player/PlayerLanguageApi.java` +- `proxyfeatures-api/.../capability/player/PlayerLanguageApi.java` - DataRegistry language APIs diff --git a/docs/features/playerlist.md b/docs/features/playerlist.md index 72d78ba2..697d60d2 100644 --- a/docs/features/playerlist.md +++ b/docs/features/playerlist.md @@ -2,7 +2,7 @@ > Velocity · Feature ID `playerlist` · disabled by default · per-backend and global visible-player lists -PlayerList provides `/list` and `/glist` for player-facing network population views. It groups visible players by backend, separates staff by permission, optionally hides configured server rows, and excludes UUIDs reported vanished by `VanishAPI`. +PlayerList provides `/list` and `/glist` for player-facing network population views. It groups visible players by backend, separates staff by permission, optionally hides configured server rows, and excludes UUIDs reported vanished by `PresenceApi`. ## Configuration @@ -54,10 +54,10 @@ Shows every registered server not blacklisted. Extra arguments are ignored becau ## Vanish visibility -The handler resolves optional `VanishAPI` once per formatting operation and creates a predicate: +The handler resolves optional `PresenceApi` once per formatting operation and creates a predicate: ```text -visible = VanishAPI absent OR !api.isVanished(uuid) +visible = PresenceApi absent OR !api.isHidden(uuid) ``` When the API is missing, all players are visible. API exceptions are not caught and can fail command rendering. @@ -211,4 +211,4 @@ Disable has no custom cleanup. - `features/playerlist/internal/PlayerListHandler.java` - `features/playerlist/command/ListCommand.java` - `features/playerlist/command/GlobalListCommand.java` -- `features/vanish/internal/VanishAPI.java` +- `proxyfeatures-api/.../capability/presence/PresenceApi.java` diff --git a/docs/features/queue.md b/docs/features/queue.md index fbbc769c..71878d22 100644 --- a/docs/features/queue.md +++ b/docs/features/queue.md @@ -31,7 +31,9 @@ Queue declares a hard feature dependency on: Capacity ``` -Queue cannot initialize without the `CapacityAPI` service. There is no ping-capacity fallback. +Queue cannot initialize without Capacity's runtime-only admission port. External plugins use `QueueApi` and +`AdmissionApi`; the private hand-off between Queue and Capacity is deliberately not public. There is no ping-capacity +fallback. ## Configuration @@ -190,6 +192,13 @@ A reconnect before expiry reuses the same entry. Expiry removes both the entry a | `proxyfeatures.feature.queue.command.info` | Staff queue inspection. | | `proxyfeatures.feature.queue.priority.1..3` | Ordering priority. | +## Public `QueueApi` + +The lifecycle-owned `QueueApi` exposes asynchronous `join`/`leave`, player lookup, server enablement, and immutable +queue snapshots. Join requests contain only player, target, and cause; the runtime resolves online state, permission +priority, admission, and bypass policy. Typed results distinguish joined, moved, already queued, offline, disabled, +rejected, and unavailable outcomes. Consumers retain a `CapabilityRef` across reloads, not `QueueManager`. + ## Messages and placeholders | Variable | Meaning | @@ -231,5 +240,6 @@ In a future multi-proxy topology, queue ordering must be coordinated together wi - `features/queue/model/QueueEntry.java` - `features/queue/command/QueueCommand.java` - `features/queue/listener/ConnectionListener.java` -- `proxyfeatures-api/.../api/queue/QueueAdmissionAPI.java` -- `proxyfeatures-api/.../api/capacity/CapacityAPI.java` +- `proxyfeatures-api/.../capability/queue/QueueApi.java` +- `proxyfeatures-api/.../capability/admission/AdmissionApi.java` +- `framework/admission/QueueAdmissionPort.java` diff --git a/docs/features/restart.md b/docs/features/restart.md index 6b8a894e..0bf71365 100644 --- a/docs/features/restart.md +++ b/docs/features/restart.md @@ -320,6 +320,10 @@ Autoreconnect variables: ## Lifecycle and failure semantics +The feature publishes read-only `RestartApi`. Capacity uses `isDraining(ServerId)` and +`isExpectedReturn(UUID, ServerId)` without importing Restart implementation classes. Reservation mutation and +snapshot synchronization remain behind runtime-only collaboration ports. + Disable order: 1. close durable subscription, waiting up to five seconds; @@ -363,7 +367,7 @@ Important limitations: - `features/restart/internal/RestartHandler.java` - `features/restart/command/ProxyRestartCommand.java` - `features/restart/command/RestartScheduleParser.java` -- `features/restart/messaging/RestartLifecycleMessage.java` +- `proxyfeatures-contracts/.../messaging/RestartLifecycleMessage.java` - `features/restart/messaging/RestartLifecycleBus.java` - `features/restart/internal/BackendReconnectManager.java` - `features/restart/listener/BackendReconnectListener.java` diff --git a/docs/features/sanctions.md b/docs/features/sanctions.md index 2608b06c..da52f118 100644 --- a/docs/features/sanctions.md +++ b/docs/features/sanctions.md @@ -230,11 +230,14 @@ Sanction screens/announcements use service-generated values including: List output additionally uses `{player}`, `{mode}`, `{count}`, `{page}`, `{pages}`, `{size}`. -No PlaceholderAPI expansion or public sanctions API is registered. +No PlaceholderAPI expansion is registered. The feature publishes read-only `SanctionsApi`, whose asynchronous +`find(UUID, activeOnly)` returns immutable, persistence-independent `SanctionSnapshot` values. ## Lifecycle and limitations -Initialization requires DataRegistry and ORM. It registers nine commands, login listener and repeating expiry sweep. `disable()` is empty; task/listener/ORM cleanup relies on feature lifecycle teardown and the in-memory mute map is not explicitly cleared. +Initialization requires DataRegistry and ORM. It registers nine commands, the login listener, repeating expiry sweep, +and `SanctionsApi`. Capability/task/listener/ORM cleanup is lifecycle-owned; the in-memory mute map is discarded with +the feature instance. Important limitations: diff --git a/docs/features/twofactor.md b/docs/features/twofactor.md index 6d32f8d1..66f6a486 100644 --- a/docs/features/twofactor.md +++ b/docs/features/twofactor.md @@ -329,7 +329,8 @@ The cooldown map is pruned opportunistically on service operations, not by a sch | `{seconds}` | Remaining cooldown. | | `{target}` | Reset target identifier. | -No PlaceholderAPI expansion or public TwoFactor API is registered. +No PlaceholderAPI expansion is registered. The read-only `TwoFactorApi` publishes player lock and authentication +server checks for admission/security integrations without exposing accounts, secrets, or persistence. ## Lifecycle and operational limitations diff --git a/docs/features/vanish.md b/docs/features/vanish.md index 05605fa2..6c7e80b6 100644 --- a/docs/features/vanish.md +++ b/docs/features/vanish.md @@ -4,7 +4,7 @@ ProxyFeatures Vanish does not provide a `/vanish` command and does not persist authoritative vanish state. ServerFeatures publishes state transitions; this feature consumes them into a proxy-local registry containing only players who are currently online on this proxy and vanished according to the newest accepted revision. -Other ProxyFeatures modules consume the registered `VanishAPI` instead of inferring visibility from connection presence. +Other ProxyFeatures modules consume the registered `PresenceApi` instead of inferring visibility from connection presence. ## Configuration @@ -29,7 +29,7 @@ The feature warns when the default group is used because every proxy that must r Initialization order: 1. create `VanishRegistry`; -2. create/register `VanishAPI` as a feature service; +2. create/register `PresenceApi` as a feature service; 3. attempt to register Redis messaging provider `redis`; 4. when available, start durable consumption; 5. register disconnect cleanup listener. @@ -153,25 +153,23 @@ Registry methods return snapshots derived from current Velocity players: Names stored in the map are convenience metadata; API list/count operations use UUID/player objects. -## Public `VanishAPI` +## Public `PresenceApi` -Registered service class: +Registered public contract: ```java -nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI +nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi ``` Methods: ```java -int getAdjustedPlayerCount() -List getAdjustedOnlinePlayers() -List getVanishedPlayers() -int getVanishedCount() -boolean isVanished(UUID uuid) +boolean isHidden(UUID playerId) +PresenceSnapshot snapshot() ``` -Returned lists are snapshots, not live collections. `isVanished` is true only for a UUID currently present in the local online map. +`PresenceSnapshot` contains immutable online/hidden UUID sets, derived visible players, counts, and an observation +timestamp. `isHidden` is true only for a UUID currently present in the local online map. Known consumers include MOTD, Friends, Messenger, PlayerList and PlayerCount. @@ -227,7 +225,7 @@ Important limitations: ## Operational verification -1. Publish a valid vanished update for an online player; verify API count/list/isVanished and one transition event. +1. Publish a valid vanished update for an online player; verify `isHidden`, snapshot counts and one transition event. 2. Publish the same state with a newer revision; verify registry revision advances without another transition event. 3. Publish the same or lower revision; verify it is ignored. 4. Publish an unvanish with a higher revision; verify removal and one visible transition event. @@ -239,14 +237,15 @@ Important limitations: 10. Disable Redis and verify the API remains available but does not update. 11. Test null/invalid UUID/key/revision payloads and verify acknowledgement/discard behavior. 12. Vanish and unvanish a publicly online friend; verify exactly one offline and one online friend notification. -13. Confirm every visibility-aware consumer uses `VanishAPI` or the applied transition event and handles degraded state intentionally. +13. Confirm every visibility-aware consumer uses `PresenceApi` or the applied transition event and handles degraded state intentionally. ## Source reference - `features/vanish/Vanish.java` -- `features/vanish/event/VanishStateChangeEvent.java` +- `proxyfeatures-api/.../capability/presence/PresenceChangedEvent.java` - `features/vanish/internal/VanishRegistry.java` -- `features/vanish/internal/VanishAPI.java` +- `features/vanish/internal/PresenceService.java` +- `proxyfeatures-api/.../capability/presence/PresenceApi.java` - `features/vanish/internal/messaging/EventBusHandler.java` - `features/vanish/listener/ConnectListener.java` - `proxyfeatures-contracts/.../VanishStateMessage.java` diff --git a/docs/features/versioncheck.md b/docs/features/versioncheck.md index a2ab0bb3..3bca1a6f 100644 --- a/docs/features/versioncheck.md +++ b/docs/features/versioncheck.md @@ -120,7 +120,7 @@ Allowed clients are only version-audited, not written as denied connection logs. ## MOTD integration -When MOTD is loaded, it accesses `VersionHandler` directly. +When MOTD is loaded, it resolves the public `VersionApi` capability. For an incoming server-list ping protocol below the minimum, MOTD replaces the displayed ping version with: @@ -135,15 +135,15 @@ Because MOTD checks whether the `VersionCheck` feature is loaded, disabling Vers ## Public/internal methods -`VersionHandler` exposes: +The registered `VersionApi` exposes: ```java -boolean isUnsupportedVersion(int protocolVersion) -int getMinimumProtcolVersion() -String getFriendlyProtocolName() +int minimumProtocolVersion() +String minimumVersionName() +boolean isSupported(int protocolVersion) ``` -Note the source method name contains the typo `Protcol`. This is an internal concrete-class integration, not a registered public service API. +Consumers resolve this contract through `ProxyFeaturesApi.capabilities()` and do not depend on `VersionHandler`. ## Commands, permissions and PAPI diff --git a/pom.xml b/pom.xml index 27e0865c..d6f9b86a 100644 --- a/pom.xml +++ b/pom.xml @@ -58,12 +58,13 @@ proxyfeatures-testkit proxyfeatures-api + proxyfeatures-toolkit proxyfeatures-contracts proxyfeatures-platform-velocity - 3.2.0 + 3.3.0 UTF-8 UTF-8 2026-07-26T00:00:00Z @@ -83,7 +84,6 @@ 1.0.0-SNAPSHOT - 4.8.184 3.2.4 4.2.0 5.5 diff --git a/proxyfeatures-api/pom.xml b/proxyfeatures-api/pom.xml index 55d3f56e..a5cfb657 100644 --- a/proxyfeatures-api/pom.xml +++ b/proxyfeatures-api/pom.xml @@ -12,96 +12,12 @@ 0.95 - - net.kyori - adventure-api - provided - - - net.kyori - adventure-key - provided - - - net.kyori - adventure-text-minimessage - provided - - - net.kyori - adventure-text-serializer-gson - provided - - - net.kyori - adventure-text-serializer-legacy - provided - - - net.kyori - adventure-text-serializer-plain - provided - - - net.kyori - adventure-text-logger-slf4j - provided - - - com.velocitypowered - velocity-api - ${velocity.version} - provided - - - com.velocitypowered - velocity-brigadier - ${velocity.brigadier.version} - provided - - - org.slf4j - slf4j-api - provided - - - org.spongepowered - configurate-yaml - ${configurate.version} - - - org.spongepowered - configurate-core - ${configurate.version} - - - com.google.code.gson - gson - - - org.jetbrains - annotations - ${jetbrains.annotations.version} - provided - org.junit.jupiter junit-jupiter ${junit.version} test - - ${project.groupId} - proxyfeatures-testkit - ${project.version} - test - - - org.mockito - mockito-core - ${mockito.version} - test - diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesApi.java new file mode 100644 index 00000000..eaf38963 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesApi.java @@ -0,0 +1,22 @@ +package nl.hauntedmc.proxyfeatures.api; + +import nl.hauntedmc.proxyfeatures.api.feature.FeatureCatalog; +import nl.hauntedmc.proxyfeatures.api.service.CapabilityRegistry; + +/** + * Stable entry point published by the running ProxyFeatures platform. + * + *

The entry point remains valid for the lifetime of the plugin. Feature reloads change the + * availability reported by {@link #capabilities()} and {@link #features()}, not this object.

+ */ +public interface ProxyFeaturesApi { + + /** Returns the version of the API and runtime implementation. */ + ProxyFeaturesApiVersion version(); + + /** Returns the live, read-only capability catalog. */ + CapabilityRegistry capabilities(); + + /** Returns the live, read-only feature catalog. */ + FeatureCatalog features(); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesApiVersion.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesApiVersion.java new file mode 100644 index 00000000..6570a2be --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesApiVersion.java @@ -0,0 +1,28 @@ +package nl.hauntedmc.proxyfeatures.api; + +import java.util.Objects; + +/** Semantic API and implementation versions exposed by a running platform. */ +public record ProxyFeaturesApiVersion(String apiVersion, String implementationVersion) { + + public static final String CURRENT = "3.3.0"; + + public ProxyFeaturesApiVersion { + apiVersion = requireVersion(apiVersion, "apiVersion"); + implementationVersion = requireVersion(implementationVersion, "implementationVersion"); + } + + /** Creates a version pair for the current API. */ + public static ProxyFeaturesApiVersion current(String implementationVersion) { + return new ProxyFeaturesApiVersion(CURRENT, implementationVersion); + } + + private static String requireVersion(String value, String field) { + Objects.requireNonNull(value, field); + String normalized = value.trim(); + if (normalized.isEmpty()) { + throw new IllegalArgumentException(field + " must not be blank"); + } + return normalized; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesContext.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesContext.java deleted file mode 100644 index 25fad920..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/ProxyFeaturesContext.java +++ /dev/null @@ -1,22 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api; - -import org.slf4j.Logger; - -import java.nio.file.Path; - -/** - * Minimal host contract required by reusable ProxyFeatures API utilities. - * - *

Keeping this contract in the API module prevents general configuration and resource helpers - * from depending on the Velocity plugin entry point.

- */ -public interface ProxyFeaturesContext { - - Path getDataDirectory(); - - Logger getLogger(); - - default ClassLoader getResourceClassLoader() { - return getClass().getClassLoader(); - } -} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionApi.java new file mode 100644 index 00000000..e513a5c2 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionApi.java @@ -0,0 +1,11 @@ +package nl.hauntedmc.proxyfeatures.api.capability.admission; + +/** Public admission-control capability. Implementations derive all trusted policy inputs. */ +public interface AdmissionApi { + + /** Evaluates and reserves one admission attempt atomically. */ + AdmissionDecision tryAcquire(AdmissionRequest request); + + /** Returns an immutable operational snapshot. */ + AdmissionSnapshot snapshot(); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionDecision.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionDecision.java new file mode 100644 index 00000000..82ef27e6 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionDecision.java @@ -0,0 +1,41 @@ +package nl.hauntedmc.proxyfeatures.api.capability.admission; + +import java.util.Objects; +import java.util.Optional; + +/** Typed result of an atomic admission attempt. */ +public record AdmissionDecision( + AdmissionDenialReason denialReason, + Optional blockingPolicy, + Optional lease +) { + public AdmissionDecision { + denialReason = Objects.requireNonNull(denialReason, "denialReason"); + blockingPolicy = blockingPolicy == null + ? Optional.empty() + : blockingPolicy.map(String::trim).filter(value -> !value.isEmpty()); + lease = lease == null ? Optional.empty() : lease; + if ((denialReason == AdmissionDenialReason.NONE) != lease.isPresent()) { + throw new IllegalArgumentException("Only allowed decisions contain a lease"); + } + } + + public static AdmissionDecision allow(AdmissionLease lease) { + return new AdmissionDecision( + AdmissionDenialReason.NONE, + Optional.empty(), + Optional.of(Objects.requireNonNull(lease, "lease")) + ); + } + + public static AdmissionDecision deny(AdmissionDenialReason reason, String blockingPolicy) { + if (reason == AdmissionDenialReason.NONE) { + throw new IllegalArgumentException("A denial requires a denial reason"); + } + return new AdmissionDecision(reason, Optional.ofNullable(blockingPolicy), Optional.empty()); + } + + public boolean allowed() { + return denialReason == AdmissionDenialReason.NONE; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionDenialReason.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionDenialReason.java new file mode 100644 index 00000000..977bc7a5 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionDenialReason.java @@ -0,0 +1,16 @@ +package nl.hauntedmc.proxyfeatures.api.capability.admission; + +/** Stable reason for an expected admission rejection. */ +public enum AdmissionDenialReason { + NONE, + CAPACITY, + SERVER_STATE, + MAINTENANCE, + RESTART, + TWO_FACTOR, + PROXY_HARD_LIMIT, + PLAYER_OFFLINE, + UNKNOWN_TARGET, + INVALID_REQUEST, + INTERNAL_ERROR +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionIntent.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionIntent.java new file mode 100644 index 00000000..49598a5d --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionIntent.java @@ -0,0 +1,21 @@ +package nl.hauntedmc.proxyfeatures.api.capability.admission; + +/** Describes the purpose of an admission request without granting any bypass privileges. */ +public enum AdmissionIntent { + NORMAL(true), + QUEUE_ADVANCE(false), + RESTART_RETURN(false), + MAINTENANCE_EVACUATION(false), + SECURITY_ROUTE(false), + PLUGIN(false); + + private final boolean queueable; + + AdmissionIntent(boolean queueable) { + this.queueable = queueable; + } + + public boolean isQueueable() { + return queueable; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionLease.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionLease.java new file mode 100644 index 00000000..716e42bb --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionLease.java @@ -0,0 +1,30 @@ +package nl.hauntedmc.proxyfeatures.api.capability.admission; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.time.Instant; +import java.util.UUID; + +/** Short-lived, idempotent reservation for one exact backend connection. */ +public interface AdmissionLease extends AutoCloseable { + UUID id(); + + UUID playerId(); + + ServerId targetServer(); + + AdmissionIntent intent(); + + Instant expiresAt(); + + boolean isActive(); + + void commit(); + + void release(); + + @Override + default void close() { + release(); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionRequest.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionRequest.java new file mode 100644 index 00000000..162d5d22 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionRequest.java @@ -0,0 +1,26 @@ +package nl.hauntedmc.proxyfeatures.api.capability.admission; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +/** Untrusted request to evaluate one player connection to a backend. */ +public record AdmissionRequest( + UUID playerId, + Optional previousServer, + ServerId targetServer, + AdmissionIntent intent +) { + public AdmissionRequest { + Objects.requireNonNull(playerId, "playerId"); + previousServer = previousServer == null ? Optional.empty() : previousServer; + Objects.requireNonNull(targetServer, "targetServer"); + intent = intent == null ? AdmissionIntent.PLUGIN : intent; + } + + public static AdmissionRequest normal(UUID playerId, ServerId targetServer) { + return new AdmissionRequest(playerId, Optional.empty(), targetServer, AdmissionIntent.NORMAL); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionScopeSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionScopeSnapshot.java new file mode 100644 index 00000000..44a8969c --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionScopeSnapshot.java @@ -0,0 +1,39 @@ +package nl.hauntedmc.proxyfeatures.api.capability.admission; + +import java.util.Objects; + +/** Immutable operational view of one configured admission scope. */ +public record AdmissionScopeSnapshot( + String name, + int capacity, + int reservedSlots, + int occupied, + int pending, + int restorationReserved, + AdmissionState state +) { + public AdmissionScopeSnapshot { + name = Objects.requireNonNull(name, "name").trim(); + if (name.isEmpty()) { + throw new IllegalArgumentException("name must not be blank"); + } + capacity = Math.max(0, capacity); + reservedSlots = Math.max(0, reservedSlots); + occupied = Math.max(0, occupied); + pending = Math.max(0, pending); + restorationReserved = Math.max(0, restorationReserved); + state = Objects.requireNonNull(state, "state"); + } + + public int effectiveUsed() { + return occupied + pending + restorationReserved; + } + + public int normalAvailable() { + return Math.max(0, capacity - reservedSlots - effectiveUsed()); + } + + public int absoluteAvailable() { + return Math.max(0, capacity - effectiveUsed()); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionSnapshot.java new file mode 100644 index 00000000..116fac31 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionSnapshot.java @@ -0,0 +1,26 @@ +package nl.hauntedmc.proxyfeatures.api.capability.admission; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.time.Instant; +import java.util.Map; +import java.util.Objects; + +/** Point-in-time operational admission snapshot. */ +public record AdmissionSnapshot( + AdmissionScopeSnapshot proxy, + AdmissionScopeSnapshot gameplay, + Map groups, + Map servers, + int activeLeases, + Instant observedAt +) { + public AdmissionSnapshot { + Objects.requireNonNull(proxy, "proxy"); + Objects.requireNonNull(gameplay, "gameplay"); + groups = groups == null ? Map.of() : Map.copyOf(groups); + servers = servers == null ? Map.of() : Map.copyOf(servers); + activeLeases = Math.max(0, activeLeases); + observedAt = Objects.requireNonNull(observedAt, "observedAt"); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionState.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionState.java new file mode 100644 index 00000000..f7a71692 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/admission/AdmissionState.java @@ -0,0 +1,13 @@ +package nl.hauntedmc.proxyfeatures.api.capability.admission; + +/** Runtime admission state of a backend. */ +public enum AdmissionState { + OPEN, + DRAINING, + CLOSED, + OFFLINE; + + public boolean acceptsNormalAdmissions() { + return this == OPEN; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionSnapshot.java new file mode 100644 index 00000000..6ba5e044 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionSnapshot.java @@ -0,0 +1,27 @@ +package nl.hauntedmc.proxyfeatures.api.capability.moderation; + +import java.time.Instant; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +/** Persistence-independent public view of one sanction. */ +public record SanctionSnapshot( + long id, + UUID playerId, + SanctionType type, + String reason, + String actor, + Instant createdAt, + Optional expiresAt, + boolean active +) { + public SanctionSnapshot { + Objects.requireNonNull(playerId, "playerId"); + type = Objects.requireNonNull(type, "type"); + reason = Objects.requireNonNull(reason, "reason"); + actor = Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(createdAt, "createdAt"); + expiresAt = expiresAt == null ? Optional.empty() : expiresAt; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionType.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionType.java new file mode 100644 index 00000000..301ac149 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionType.java @@ -0,0 +1,10 @@ +package nl.hauntedmc.proxyfeatures.api.capability.moderation; + +/** Public sanction category. */ +public enum SanctionType { + BAN, + IP_BAN, + MUTE, + WARNING, + KICK +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionsApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionsApi.java new file mode 100644 index 00000000..835b8e36 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionsApi.java @@ -0,0 +1,10 @@ +package nl.hauntedmc.proxyfeatures.api.capability.moderation; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletionStage; + +/** Read-only moderation history capability. */ +public interface SanctionsApi { + CompletionStage> find(UUID playerId, boolean activeOnly); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountApi.java new file mode 100644 index 00000000..d3e80b10 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountApi.java @@ -0,0 +1,6 @@ +package nl.hauntedmc.proxyfeatures.api.capability.network; + +/** Public, reload-scoped player count capability. */ +public interface PlayerCountApi { + PlayerCountSnapshot snapshot(); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountSnapshot.java new file mode 100644 index 00000000..73e7f452 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountSnapshot.java @@ -0,0 +1,24 @@ +package nl.hauntedmc.proxyfeatures.api.capability.network; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.time.Instant; +import java.util.Map; +import java.util.Objects; + +/** Immutable network and per-backend player-count view. */ +public record PlayerCountSnapshot( + PlayerCounts network, + Map servers, + Instant observedAt +) { + public PlayerCountSnapshot { + network = Objects.requireNonNull(network, "network"); + servers = servers == null ? Map.of() : Map.copyOf(servers); + observedAt = Objects.requireNonNull(observedAt, "observedAt"); + } + + public PlayerCounts server(ServerId server) { + return servers.getOrDefault(Objects.requireNonNull(server, "server"), PlayerCounts.empty()); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCounts.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCounts.java new file mode 100644 index 00000000..cb48c1ac --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCounts.java @@ -0,0 +1,18 @@ +package nl.hauntedmc.proxyfeatures.api.capability.network; + +/** Vanish-aware counts for one network scope. */ +public record PlayerCounts(int online, int hidden) { + public PlayerCounts { + if (online < 0 || hidden < 0 || hidden > online) { + throw new IllegalArgumentException("invalid player counts"); + } + } + + public static PlayerCounts empty() { + return new PlayerCounts(0, 0); + } + + public int visible() { + return online - hidden; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceApi.java new file mode 100644 index 00000000..4a281752 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceApi.java @@ -0,0 +1,12 @@ +package nl.hauntedmc.proxyfeatures.api.capability.operations; + +import java.util.UUID; + +/** Read-only maintenance capability used by admission and external integrations. */ +public interface MaintenanceApi { + boolean isActive(MaintenanceScope scope); + + boolean mayBypass(UUID playerId, MaintenanceScope scope); + + MaintenanceSnapshot snapshot(); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceScope.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceScope.java new file mode 100644 index 00000000..70f5f07e --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceScope.java @@ -0,0 +1,25 @@ +package nl.hauntedmc.proxyfeatures.api.capability.operations; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.util.Objects; +import java.util.Optional; + +/** Global or backend-specific maintenance scope. */ +public record MaintenanceScope(Optional server) { + public MaintenanceScope { + server = server == null ? Optional.empty() : server; + } + + public static MaintenanceScope global() { + return new MaintenanceScope(Optional.empty()); + } + + public static MaintenanceScope server(ServerId server) { + return new MaintenanceScope(Optional.of(Objects.requireNonNull(server, "server"))); + } + + public boolean isGlobal() { + return server.isEmpty(); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceSnapshot.java new file mode 100644 index 00000000..b2c4ee5c --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/MaintenanceSnapshot.java @@ -0,0 +1,15 @@ +package nl.hauntedmc.proxyfeatures.api.capability.operations; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.time.Instant; +import java.util.Objects; +import java.util.Set; + +/** Point-in-time maintenance state. */ +public record MaintenanceSnapshot(boolean global, Set servers, Instant observedAt) { + public MaintenanceSnapshot { + servers = servers == null ? Set.of() : Set.copyOf(servers); + observedAt = Objects.requireNonNull(observedAt, "observedAt"); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/RestartApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/RestartApi.java new file mode 100644 index 00000000..471b53ae --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/RestartApi.java @@ -0,0 +1,12 @@ +package nl.hauntedmc.proxyfeatures.api.capability.operations; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.util.UUID; + +/** Read-only restart lifecycle capability. */ +public interface RestartApi { + boolean isExpectedReturn(UUID playerId, ServerId server); + + boolean isDraining(ServerId server); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/TwoFactorApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/TwoFactorApi.java new file mode 100644 index 00000000..e0d25f3f --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/TwoFactorApi.java @@ -0,0 +1,12 @@ +package nl.hauntedmc.proxyfeatures.api.capability.operations; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.util.UUID; + +/** Read-only two-factor authentication session capability. */ +public interface TwoFactorApi { + boolean isLocked(UUID playerId); + + boolean isAuthenticationServer(ServerId server); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/VersionApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/VersionApi.java new file mode 100644 index 00000000..57bcccbf --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/operations/VersionApi.java @@ -0,0 +1,12 @@ +package nl.hauntedmc.proxyfeatures.api.capability.operations; + +/** Supported client-protocol information. */ +public interface VersionApi { + int minimumProtocolVersion(); + + String minimumVersionName(); + + default boolean isSupported(int protocolVersion) { + return protocolVersion >= minimumProtocolVersion(); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/player/NetworkLocationApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/player/NetworkLocationApi.java new file mode 100644 index 00000000..aa88f5c4 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/player/NetworkLocationApi.java @@ -0,0 +1,9 @@ +package nl.hauntedmc.proxyfeatures.api.capability.player; + +import java.util.Optional; +import java.util.UUID; + +/** Resolved network-location information known for a player session. */ +public interface NetworkLocationApi { + Optional countryCode(UUID playerId); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/player/PlayerLanguageApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/player/PlayerLanguageApi.java new file mode 100644 index 00000000..6a432931 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/player/PlayerLanguageApi.java @@ -0,0 +1,15 @@ +package nl.hauntedmc.proxyfeatures.api.capability.player; + +import java.util.Locale; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletionStage; + +/** Player language preference and resolved-language capability. */ +public interface PlayerLanguageApi { + Optional resolvedLanguage(UUID playerId); + + Optional preference(UUID playerId); + + CompletionStage setPreference(UUID playerId, Optional language); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceApi.java new file mode 100644 index 00000000..6d8a21cd --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceApi.java @@ -0,0 +1,10 @@ +package nl.hauntedmc.proxyfeatures.api.capability.presence; + +import java.util.UUID; + +/** Authoritative player-presence and visibility capability. */ +public interface PresenceApi { + boolean isHidden(UUID playerId); + + PresenceSnapshot snapshot(); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceChangedEvent.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceChangedEvent.java new file mode 100644 index 00000000..47f2e770 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceChangedEvent.java @@ -0,0 +1,14 @@ +package nl.hauntedmc.proxyfeatures.api.capability.presence; + +import java.time.Instant; +import java.util.Objects; +import java.util.UUID; + +/** Fired after a real online player's visibility state changes. */ +public record PresenceChangedEvent(UUID playerId, String playerName, boolean hidden, Instant changedAt) { + public PresenceChangedEvent { + Objects.requireNonNull(playerId, "playerId"); + playerName = playerName == null ? "" : playerName; + Objects.requireNonNull(changedAt, "changedAt"); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceSnapshot.java new file mode 100644 index 00000000..587ed913 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/presence/PresenceSnapshot.java @@ -0,0 +1,36 @@ +package nl.hauntedmc.proxyfeatures.api.capability.presence; + +import java.time.Instant; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** Point-in-time view of online and hidden players. */ +public record PresenceSnapshot(Set onlinePlayers, Set hiddenPlayers, Instant observedAt) { + public PresenceSnapshot { + onlinePlayers = onlinePlayers == null ? Set.of() : Set.copyOf(onlinePlayers); + hiddenPlayers = hiddenPlayers == null ? Set.of() : Set.copyOf(hiddenPlayers); + if (!onlinePlayers.containsAll(hiddenPlayers)) { + throw new IllegalArgumentException("hiddenPlayers must be a subset of onlinePlayers"); + } + observedAt = Objects.requireNonNull(observedAt, "observedAt"); + } + + public int onlineCount() { + return onlinePlayers.size(); + } + + public int hiddenCount() { + return hiddenPlayers.size(); + } + + public int visibleCount() { + return onlineCount() - hiddenCount(); + } + + public Set visiblePlayers() { + java.util.HashSet visible = new java.util.HashSet<>(onlinePlayers); + visible.removeAll(hiddenPlayers); + return Set.copyOf(visible); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueApi.java new file mode 100644 index 00000000..6b686366 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueApi.java @@ -0,0 +1,20 @@ +package nl.hauntedmc.proxyfeatures.api.capability.queue; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletionStage; + +/** Public, reload-scoped Queue capability. */ +public interface QueueApi { + CompletionStage join(QueueJoinRequest request); + + CompletionStage leave(UUID playerId); + + Optional find(UUID playerId); + + QueueSnapshot snapshot(); + + boolean isEnabled(ServerId server); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinCause.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinCause.java new file mode 100644 index 00000000..b0ff453d --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinCause.java @@ -0,0 +1,8 @@ +package nl.hauntedmc.proxyfeatures.api.capability.queue; + +/** Public reason for asking the Queue capability to enqueue a player. */ +public enum QueueJoinCause { + CAPACITY, + MANUAL, + PLUGIN +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinRequest.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinRequest.java new file mode 100644 index 00000000..2f9fc520 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinRequest.java @@ -0,0 +1,15 @@ +package nl.hauntedmc.proxyfeatures.api.capability.queue; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.util.Objects; +import java.util.UUID; + +/** Untrusted request to enter a configured server queue. */ +public record QueueJoinRequest(UUID playerId, ServerId targetServer, QueueJoinCause cause) { + public QueueJoinRequest { + Objects.requireNonNull(playerId, "playerId"); + Objects.requireNonNull(targetServer, "targetServer"); + cause = cause == null ? QueueJoinCause.PLUGIN : cause; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinResult.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinResult.java new file mode 100644 index 00000000..2acd7c9b --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinResult.java @@ -0,0 +1,26 @@ +package nl.hauntedmc.proxyfeatures.api.capability.queue; + +import java.util.Objects; +import java.util.Optional; + +/** Typed result of a queue join request. */ +public record QueueJoinResult(QueueJoinStatus status, Optional entry) { + public QueueJoinResult { + status = Objects.requireNonNull(status, "status"); + entry = entry == null ? Optional.empty() : entry; + boolean joined = status == QueueJoinStatus.JOINED + || status == QueueJoinStatus.MOVED + || status == QueueJoinStatus.ALREADY_QUEUED; + if (joined != entry.isPresent()) { + throw new IllegalArgumentException("Successful queue results require an entry"); + } + } + + public static QueueJoinResult success(QueueJoinStatus status, QueuedPlayerSnapshot entry) { + return new QueueJoinResult(status, Optional.of(Objects.requireNonNull(entry, "entry"))); + } + + public static QueueJoinResult failure(QueueJoinStatus status) { + return new QueueJoinResult(status, Optional.empty()); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinStatus.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinStatus.java new file mode 100644 index 00000000..78557116 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueJoinStatus.java @@ -0,0 +1,12 @@ +package nl.hauntedmc.proxyfeatures.api.capability.queue; + +/** Outcome of a queue join request. */ +public enum QueueJoinStatus { + JOINED, + MOVED, + ALREADY_QUEUED, + PLAYER_OFFLINE, + QUEUE_DISABLED, + REJECTED, + UNAVAILABLE +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueLeaveStatus.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueLeaveStatus.java new file mode 100644 index 00000000..885d50e4 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueLeaveStatus.java @@ -0,0 +1,8 @@ +package nl.hauntedmc.proxyfeatures.api.capability.queue; + +/** Outcome of a queue leave request. */ +public enum QueueLeaveStatus { + LEFT, + NOT_QUEUED, + UNAVAILABLE +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueServerSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueServerSnapshot.java new file mode 100644 index 00000000..fc8aa85d --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueServerSnapshot.java @@ -0,0 +1,28 @@ +package nl.hauntedmc.proxyfeatures.api.capability.queue; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.time.Instant; +import java.util.Objects; +import java.util.Optional; + +/** Immutable Queue snapshot for one backend. */ +public record QueueServerSnapshot( + ServerId server, + int waiting, + int connected, + int grace, + int inFlight, + int blocked, + Optional oldestEnqueuedAt +) { + public QueueServerSnapshot { + Objects.requireNonNull(server, "server"); + waiting = Math.max(0, waiting); + connected = Math.max(0, connected); + grace = Math.max(0, grace); + inFlight = Math.max(0, inFlight); + blocked = Math.max(0, blocked); + oldestEnqueuedAt = oldestEnqueuedAt == null ? Optional.empty() : oldestEnqueuedAt; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueSnapshot.java new file mode 100644 index 00000000..48edb812 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueueSnapshot.java @@ -0,0 +1,23 @@ +package nl.hauntedmc.proxyfeatures.api.capability.queue; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.time.Instant; +import java.util.Map; +import java.util.Objects; + +/** Immutable process-local Queue observability snapshot. */ +public record QueueSnapshot(Map servers, Instant observedAt) { + public QueueSnapshot { + servers = servers == null ? Map.of() : Map.copyOf(servers); + observedAt = Objects.requireNonNull(observedAt, "observedAt"); + } + + public int totalWaiting() { + return servers.values().stream().mapToInt(QueueServerSnapshot::waiting).sum(); + } + + public int totalInFlight() { + return servers.values().stream().mapToInt(QueueServerSnapshot::inFlight).sum(); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueuedPlayerSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueuedPlayerSnapshot.java new file mode 100644 index 00000000..4a0bf304 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/queue/QueuedPlayerSnapshot.java @@ -0,0 +1,26 @@ +package nl.hauntedmc.proxyfeatures.api.capability.queue; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.time.Instant; +import java.util.Objects; +import java.util.UUID; + +/** Immutable view of one queued player. Position is one-based. */ +public record QueuedPlayerSnapshot( + UUID playerId, + ServerId targetServer, + int position, + int priority, + Instant enqueuedAt, + boolean inFlight +) { + public QueuedPlayerSnapshot { + Objects.requireNonNull(playerId, "playerId"); + Objects.requireNonNull(targetServer, "targetServer"); + if (position < 1) { + throw new IllegalArgumentException("position must be positive"); + } + Objects.requireNonNull(enqueuedAt, "enqueuedAt"); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/social/FriendshipApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/social/FriendshipApi.java new file mode 100644 index 00000000..664f5b4a --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/social/FriendshipApi.java @@ -0,0 +1,14 @@ +package nl.hauntedmc.proxyfeatures.api.capability.social; + +import java.util.UUID; +import java.util.concurrent.CompletionStage; + +/** Asynchronous, persistence-independent friendship capability. */ +public interface FriendshipApi { + CompletionStage areFriends(UUID firstPlayer, UUID secondPlayer); + + default CompletionStage relationship(UUID firstPlayer, UUID secondPlayer) { + return areFriends(firstPlayer, secondPlayer) + .thenApply(friends -> friends ? FriendshipStatus.FRIENDS : FriendshipStatus.NOT_FRIENDS); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/social/FriendshipStatus.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/social/FriendshipStatus.java new file mode 100644 index 00000000..d567c7b2 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/social/FriendshipStatus.java @@ -0,0 +1,7 @@ +package nl.hauntedmc.proxyfeatures.api.capability.social; + +/** Current relationship between two players. */ +public enum FriendshipStatus { + FRIENDS, + NOT_FRIENDS +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/RestartAdmissionAPI.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/RestartAdmissionAPI.java deleted file mode 100644 index 0c6ce8a6..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/RestartAdmissionAPI.java +++ /dev/null @@ -1,8 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; - -import java.util.UUID; - -/** Optional Restart feature bridge used to classify an expected backend return. */ -public interface RestartAdmissionAPI { - boolean isRestartReturn(UUID playerId, String serverName); -} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/ExtensionRegistration.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/ExtensionRegistration.java new file mode 100644 index 00000000..3aa3231c --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/ExtensionRegistration.java @@ -0,0 +1,8 @@ +package nl.hauntedmc.proxyfeatures.api.extension; + +/** Idempotent ownership handle returned for every public extension registration. */ +@FunctionalInterface +public interface ExtensionRegistration extends AutoCloseable { + @Override + void close(); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContext.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContext.java new file mode 100644 index 00000000..bb904c01 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContext.java @@ -0,0 +1,11 @@ +package nl.hauntedmc.proxyfeatures.api.extension; + +import java.net.InetAddress; +import java.util.Objects; + +/** Stable information made available to MOTD contributors. */ +public record MotdContext(InetAddress remoteAddress, int protocolVersion) { + public MotdContext { + Objects.requireNonNull(remoteAddress, "remoteAddress"); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContribution.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContribution.java new file mode 100644 index 00000000..ccd12a7d --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContribution.java @@ -0,0 +1,20 @@ +package nl.hauntedmc.proxyfeatures.api.extension; + +import java.util.Objects; +import java.util.Optional; + +/** Optional text overrides supplied by one MOTD contributor. */ +public record MotdContribution(Optional firstLine, Optional secondLine) { + public MotdContribution { + firstLine = clean(firstLine); + secondLine = clean(secondLine); + } + + public static MotdContribution secondLine(String value) { + return new MotdContribution(Optional.empty(), Optional.of(Objects.requireNonNull(value, "value"))); + } + + private static Optional clean(Optional value) { + return value == null ? Optional.empty() : value.map(String::trim).filter(text -> !text.isEmpty()); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContributor.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContributor.java new file mode 100644 index 00000000..05a5529e --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdContributor.java @@ -0,0 +1,9 @@ +package nl.hauntedmc.proxyfeatures.api.extension; + +import java.util.Optional; + +/** Computes optional MOTD overrides for one status request. */ +@FunctionalInterface +public interface MotdContributor { + Optional contribute(MotdContext context); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdExtensions.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdExtensions.java new file mode 100644 index 00000000..1c9a55ca --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/extension/MotdExtensions.java @@ -0,0 +1,6 @@ +package nl.hauntedmc.proxyfeatures.api.extension; + +/** Lifecycle-safe registry for public MOTD contributors. */ +public interface MotdExtensions { + ExtensionRegistration register(String owner, int priority, MotdContributor contributor); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureCatalog.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureCatalog.java new file mode 100644 index 00000000..006f2556 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureCatalog.java @@ -0,0 +1,12 @@ +package nl.hauntedmc.proxyfeatures.api.feature; + +import java.util.List; +import java.util.Optional; + +/** Read-only catalog of all known built-in features. */ +public interface FeatureCatalog { + + Optional find(FeatureId id); + + List snapshot(); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureClassification.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureClassification.java new file mode 100644 index 00000000..302f0292 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureClassification.java @@ -0,0 +1,9 @@ +package nl.hauntedmc.proxyfeatures.api.feature; + +/** Defines how a built-in feature participates in the supported public architecture. */ +public enum FeatureClassification { + CAPABILITY_PROVIDER, + EXTENSION_PROVIDER, + CAPABILITY_CONSUMER, + INTERNAL +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureDescriptor.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureDescriptor.java new file mode 100644 index 00000000..76ba1da6 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureDescriptor.java @@ -0,0 +1,32 @@ +package nl.hauntedmc.proxyfeatures.api.feature; + +import java.util.Objects; +import java.util.Set; + +/** Immutable public metadata for a built-in feature. */ +public record FeatureDescriptor( + FeatureId id, + String displayName, + String version, + FeatureClassification classification, + Set requiredFeatures, + Set providedCapabilities +) { + public FeatureDescriptor { + Objects.requireNonNull(id, "id"); + displayName = requireText(displayName, "displayName"); + version = requireText(version, "version"); + classification = Objects.requireNonNull(classification, "classification"); + requiredFeatures = requiredFeatures == null ? Set.of() : Set.copyOf(requiredFeatures); + providedCapabilities = providedCapabilities == null ? Set.of() : Set.copyOf(providedCapabilities); + } + + private static String requireText(String value, String field) { + Objects.requireNonNull(value, field); + String normalized = value.trim(); + if (normalized.isEmpty()) { + throw new IllegalArgumentException(field + " must not be blank"); + } + return normalized; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureId.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureId.java new file mode 100644 index 00000000..387384a1 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureId.java @@ -0,0 +1,46 @@ +package nl.hauntedmc.proxyfeatures.api.feature; + +import java.util.Locale; +import java.util.Objects; + +/** Stable, normalized identity of a ProxyFeatures feature. */ +public record FeatureId(String value) implements Comparable { + + public FeatureId { + Objects.requireNonNull(value, "value"); + value = value.trim().toLowerCase(Locale.ROOT); + if (!isValid(value)) { + throw new IllegalArgumentException("Invalid feature id: " + value); + } + } + + public static FeatureId of(String value) { + return new FeatureId(value); + } + + @Override + public int compareTo(FeatureId other) { + return value.compareTo(Objects.requireNonNull(other, "other").value); + } + + @Override + public String toString() { + return value; + } + + private static boolean isValid(String value) { + if (value.isEmpty() || !Character.isLetterOrDigit(value.charAt(0))) { + return false; + } + for (int index = 1; index < value.length(); index++) { + char character = value.charAt(index); + if (!Character.isLetterOrDigit(character) + && character != '-' + && character != '_' + && character != '.') { + return false; + } + } + return true; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureSnapshot.java new file mode 100644 index 00000000..198485f5 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureSnapshot.java @@ -0,0 +1,20 @@ +package nl.hauntedmc.proxyfeatures.api.feature; + +import java.time.Instant; +import java.util.Objects; +import java.util.Optional; + +/** Point-in-time public view of one feature and its lifecycle state. */ +public record FeatureSnapshot( + FeatureDescriptor descriptor, + FeatureState state, + Optional failure, + Instant observedAt +) { + public FeatureSnapshot { + Objects.requireNonNull(descriptor, "descriptor"); + state = Objects.requireNonNull(state, "state"); + failure = failure == null ? Optional.empty() : failure.filter(value -> !value.isBlank()); + observedAt = Objects.requireNonNull(observedAt, "observedAt"); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureState.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureState.java new file mode 100644 index 00000000..6d34d507 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/FeatureState.java @@ -0,0 +1,10 @@ +package nl.hauntedmc.proxyfeatures.api.feature; + +/** Observable lifecycle state of a built-in feature. */ +public enum FeatureState { + DISABLED, + STARTING, + ACTIVE, + STOPPING, + FAILED +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/friends/FriendshipApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/friends/FriendshipApi.java deleted file mode 100644 index bba342ce..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/friends/FriendshipApi.java +++ /dev/null @@ -1,23 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.friends; - -import java.util.UUID; -import java.util.concurrent.CompletionStage; - -/** - * Read-only friendship relationship API published by the Friends feature. - * - *

The asynchronous contract keeps callers independent from the underlying - * persistence or network implementation. Implementations must complete - * exceptionally when relationship data cannot be determined reliably.

- */ -public interface FriendshipApi { - - /** - * Determines whether both players have an accepted friendship relation. - * - * @param firstPlayer first player UUID - * @param secondPlayer second player UUID - * @return a stage containing {@code true} only for an accepted friendship - */ - CompletionStage areFriends(UUID firstPlayer, UUID secondPlayer); -} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheType.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheType.java deleted file mode 100644 index b9865ba7..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheType.java +++ /dev/null @@ -1,9 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache; - -/** - * Supported cache back-ends. - */ -public enum CacheType { - JSON, - SQLITE -} \ No newline at end of file diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/SqliteCacheFile.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/SqliteCacheFile.java deleted file mode 100644 index a27533fe..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/SqliteCacheFile.java +++ /dev/null @@ -1,51 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache.impl; - -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheStore; - -import java.io.File; -import java.io.IOException; -import java.sql.Connection; -import java.sql.DriverManager; - -/** - * Stub SQLite-backed cache file. Records TTL cleanup at application layer. - */ -public class SqliteCacheFile implements CacheStore { - private final File file; - - public SqliteCacheFile(File file) { - this.file = file; - try { - file.getParentFile().mkdirs(); - file.createNewFile(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public File getUnderlyingFile() { - return file; - } - - public Connection getConnection() { - try { - return DriverManager.getConnection("jdbc:sqlite:" + file.getAbsolutePath()); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void cleanupExpired() { /* no-op */ } - - @Override - public void delete() { - if (!file.delete()) file.deleteOnExit(); - } - - @Override - public boolean isEmpty() { - return false; - } -} \ No newline at end of file diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/packet/Packet.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/packet/Packet.java deleted file mode 100644 index 14e86331..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/packet/Packet.java +++ /dev/null @@ -1,10 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.io.packet; - -import com.velocitypowered.api.proxy.Player; - -/** - * Interface for all packet types, enabling abstraction from PacketEvents or other libraries. - */ -public interface Packet { - void sendTo(Player player); -} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/packet/PacketManager.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/packet/PacketManager.java deleted file mode 100644 index 8fa6c696..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/packet/PacketManager.java +++ /dev/null @@ -1,59 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.io.packet; - -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ProxyServer; - -import java.util.List; - -/** - * Manages sending packets to players, allowing unicast, multicast, and broadcast. - */ -public class PacketManager { - - /** - * Sends packets to a single player. - * - * @param player The recipient player. - * @param packets The packets to send. - */ - public static void sendUnicast(Player player, Packet... packets) { - for (Packet packet : packets) { - packet.sendTo(player); - } - } - - /** - * Sends packets to multiple players in range of a specific player. - * - * @param players The center player. - * @param packets The packets to send. - */ - public static void sendMulticast(List players, Packet... packets) { - for (Player target : players) { - for (Packet packet : packets) { - packet.sendTo(target); - } - } - } - - /** - * Sends packets to all online players. - * - * @param packets The packets to send. - */ - public static void sendBroadcast(ProxyServer proxy, Packet... packets) { - sendBroadcast(proxy.getAllPlayers(), packets); - } - - /** - * Sends packets to a provided player collection. - * Useful for tests and isolated call paths that already have a target set. - */ - public static void sendBroadcast(Iterable players, Packet... packets) { - for (Player player : players) { - for (Packet packet : packets) { - packet.sendTo(player); - } - } - } -} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/resource/ResourceHandler.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/resource/ResourceHandler.java deleted file mode 100644 index d552b6e4..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/resource/ResourceHandler.java +++ /dev/null @@ -1,108 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.io.resource; - -import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesContext; -import org.spongepowered.configurate.CommentedConfigurationNode; -import org.spongepowered.configurate.loader.ConfigurationLoader; -import org.spongepowered.configurate.yaml.NodeStyle; -import org.spongepowered.configurate.yaml.YamlConfigurationLoader; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; - -public class ResourceHandler { - private final ProxyFeaturesContext plugin; - private final String resourcePath; - private final Path file; - private final ConfigurationLoader loader; - private CommentedConfigurationNode config; - - public ResourceHandler(ProxyFeaturesContext plugin, String fileName) { - this.plugin = plugin; - this.resourcePath = fileName; - Path dataDir = plugin.getDataDirectory().toAbsolutePath().normalize(); - Path resolved = dataDir.resolve(fileName).normalize(); - if (!resolved.startsWith(dataDir)) { - throw new IllegalArgumentException("Resource path escapes data directory: " + fileName); - } - this.file = resolved; - this.loader = YamlConfigurationLoader.builder() - .path(file) - .nodeStyle(NodeStyle.BLOCK) - .build(); - ensureFileExists(); - load(); - } - - /** - * Ensures that the file exists in the data directory. - * If it does not exist, attempts to copy the default from the plugin resources. - */ - private void ensureFileExists() { - try { - Files.createDirectories(file.getParent()); - if (!Files.exists(file)) { - try (InputStream in = openResourceStream()) { - if (in != null) { - Files.copy(in, file); - return; - } else { - plugin.getLogger().warn("Default '{}' not found in resources. Creating empty file '{}'.", resourcePath, file); - } - } - Files.createFile(file); - } - } catch (IOException e) { - plugin.getLogger().error("Error ensuring file exists: {}", file, e); - } - } - - /** - * Loads the configuration from disk. - */ - private void load() { - try { - this.config = loader.load(); - } catch (IOException e) { - plugin.getLogger().error("Error loading file: {}", file, e); - this.config = CommentedConfigurationNode.root(); - } - } - - /** - * Reloads the configuration from disk. - */ - public void reload() { - load(); - } - - /** - * Returns the current configuration node. - */ - public CommentedConfigurationNode getConfig() { - return config; - } - - /** - * Saves any changes to the file. - */ - public void save() { - try { - loader.save(config); - } catch (IOException e) { - plugin.getLogger().error("Error saving file: {}", file, e); - } - } - - private InputStream openResourceStream() { - ClassLoader classLoader = plugin.getResourceClassLoader(); - InputStream in = classLoader.getResourceAsStream(resourcePath); - if (in != null) { - return in; - } - - String fallback = file.getFileName().toString(); - return classLoader.getResourceAsStream(fallback); - } -} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/model/ServerId.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/model/ServerId.java new file mode 100644 index 00000000..d491345c --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/model/ServerId.java @@ -0,0 +1,44 @@ +package nl.hauntedmc.proxyfeatures.api.model; + +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; + +/** Validated, case-normalized backend server identity. */ +public record ServerId(String value) implements Comparable { + + public ServerId { + Objects.requireNonNull(value, "value"); + value = value.trim().toLowerCase(Locale.ROOT); + if (value.isEmpty()) { + throw new IllegalArgumentException("server id must not be blank"); + } + for (int index = 0; index < value.length(); index++) { + char character = value.charAt(index); + if (!Character.isLetterOrDigit(character) + && character != '-' + && character != '_' + && character != '.') { + throw new IllegalArgumentException("Invalid server id: " + value); + } + } + } + + public static ServerId of(String value) { + return new ServerId(value); + } + + public static Optional optional(String value) { + return value == null || value.isBlank() ? Optional.empty() : Optional.of(new ServerId(value)); + } + + @Override + public int compareTo(ServerId other) { + return value.compareTo(Objects.requireNonNull(other, "other").value); + } + + @Override + public String toString() { + return value; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueAdmissionAPI.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueAdmissionAPI.java deleted file mode 100644 index b579fdbf..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueAdmissionAPI.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.queue; - -import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDenialReason; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityRequest; - -import java.util.UUID; - -/** Optional bridge used by Capacity when a normal target is full. */ -public interface QueueAdmissionAPI { - boolean isQueueEnabled(String serverName); - - boolean enqueue(Player player, String serverName, CapacityDenialReason reason, CapacityRequest admissionContext); - - void wake(String serverName); - - /** - * Consumes a short-lived fence for a Queue connection request cancelled after it entered - * Velocity's connection pipeline. Implementations must make the match one-shot and target-specific. - */ - default boolean consumeCancelledAdvance(UUID playerId, String targetServer) { - return false; - } -} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueObservabilityAPI.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueObservabilityAPI.java deleted file mode 100644 index 15e45ea2..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueObservabilityAPI.java +++ /dev/null @@ -1,6 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.queue; - -/** Read-only Queue telemetry used by Capacity persistence and staff tooling. */ -public interface QueueObservabilityAPI { - QueueSnapshot snapshot(); -} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueServerSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueServerSnapshot.java deleted file mode 100644 index fa586e38..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueServerSnapshot.java +++ /dev/null @@ -1,27 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.queue; - -import java.time.Instant; - -/** Immutable operational Queue snapshot for one target backend. */ -public record QueueServerSnapshot( - String server, - int waiting, - int connected, - int grace, - int inFlight, - int blocked, - Instant oldestEnqueuedAt -) { - public QueueServerSnapshot { - server = normalize(server); - waiting = Math.max(0, waiting); - connected = Math.max(0, connected); - grace = Math.max(0, grace); - inFlight = Math.max(0, inFlight); - blocked = Math.max(0, blocked); - } - - private static String normalize(String value) { - return value == null ? "" : value.trim().toLowerCase(java.util.Locale.ROOT); - } -} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueSnapshot.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueSnapshot.java deleted file mode 100644 index d842a86f..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/queue/QueueSnapshot.java +++ /dev/null @@ -1,26 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.queue; - -import java.time.Instant; -import java.util.LinkedHashMap; -import java.util.Map; - -/** Immutable process-local Queue observability snapshot. */ -public record QueueSnapshot( - Map servers, - Instant observedAt -) { - public QueueSnapshot { - servers = servers == null - ? Map.of() - : java.util.Collections.unmodifiableMap(new LinkedHashMap<>(servers)); - observedAt = observedAt == null ? Instant.now() : observedAt; - } - - public int totalWaiting() { - return servers.values().stream().mapToInt(QueueServerSnapshot::waiting).sum(); - } - - public int totalInFlight() { - return servers.values().stream().mapToInt(QueueServerSnapshot::inFlight).sum(); - } -} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityRef.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityRef.java new file mode 100644 index 00000000..4f193a0e --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityRef.java @@ -0,0 +1,28 @@ +package nl.hauntedmc.proxyfeatures.api.service; + +import java.util.Optional; + +/** + * Reload-safe reference to an optional capability. + * + *

Callers should resolve {@link #get()} for each operation instead of retaining an implementation + * returned before a feature reload.

+ */ +public interface CapabilityRef { + + /** The public contract represented by this reference. */ + Class type(); + + /** Returns the currently active implementation, if its provider is enabled. */ + Optional get(); + + /** Returns whether the capability currently has an active provider. */ + default boolean isAvailable() { + return get().isPresent(); + } + + /** Resolves the current implementation or fails with a descriptive exception. */ + default T require() { + return get().orElseThrow(() -> new CapabilityUnavailableException(type())); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityRegistry.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityRegistry.java new file mode 100644 index 00000000..4b543e1b --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityRegistry.java @@ -0,0 +1,13 @@ +package nl.hauntedmc.proxyfeatures.api.service; + +import java.util.Set; + +/** Read-only catalog of feature capabilities provided by the current runtime. */ +public interface CapabilityRegistry { + + /** Returns a stable reference for the requested public contract. */ + CapabilityRef reference(Class type); + + /** Returns the contracts that currently have an active provider. */ + Set> availableTypes(); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityUnavailableException.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityUnavailableException.java new file mode 100644 index 00000000..27c338f5 --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/service/CapabilityUnavailableException.java @@ -0,0 +1,21 @@ +package nl.hauntedmc.proxyfeatures.api.service; + +import java.util.Objects; + +/** Thrown when a required optional capability has no active provider. */ +public final class CapabilityUnavailableException extends IllegalStateException { + + private static final long serialVersionUID = 1L; + + private final Class capabilityType; + + public CapabilityUnavailableException(Class capabilityType) { + super("ProxyFeatures capability is unavailable: " + + Objects.requireNonNull(capabilityType, "capabilityType").getName()); + this.capabilityType = capabilityType; + } + + public Class capabilityType() { + return capabilityType; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/http/DiscordUtils.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/http/DiscordUtils.java deleted file mode 100644 index 057b259c..00000000 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/http/DiscordUtils.java +++ /dev/null @@ -1,72 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.util.http; - -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URL; -import java.nio.charset.StandardCharsets; - -public class DiscordUtils { - - - /** - * Sends the provided JSON payload to the specified Discord webhook URL. - * - * @param webhookUrl The webhook URL. - * @param payload The JSON payload. - */ - public static boolean sendPayload(String webhookUrl, String payload) { - HttpURLConnection connection = null; - try { - if (webhookUrl == null || webhookUrl.isBlank()) { - return false; - } - - URI uri = URI.create(webhookUrl.trim()); - String scheme = uri.getScheme(); - if (scheme == null || !scheme.equalsIgnoreCase("https")) { - return false; - } - - URL url = uri.toURL(); - connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("POST"); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - connection.setDoOutput(true); - - try (OutputStream os = connection.getOutputStream()) { - String data = payload == null ? "" : payload; - os.write(data.getBytes(StandardCharsets.UTF_8)); - os.flush(); - } - - int responseCode = connection.getResponseCode(); - boolean success = responseCode == HttpURLConnection.HTTP_OK - || responseCode == HttpURLConnection.HTTP_NO_CONTENT; - - InputStream stream = success ? connection.getInputStream() : connection.getErrorStream(); - if (stream != null) { - try (BufferedReader ignored = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { - while (ignored.readLine() != null) { - // drain stream so keep-alive connections remain reusable - } - } - } - - return success; - } catch (Exception ex) { - return false; - } finally { - if (connection != null) { - connection.disconnect(); - } - } - } - - -} diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/AdmissionAndQueueContractsTest.java b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/AdmissionAndQueueContractsTest.java new file mode 100644 index 00000000..d788d9b1 --- /dev/null +++ b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/AdmissionAndQueueContractsTest.java @@ -0,0 +1,168 @@ +package nl.hauntedmc.proxyfeatures.api; + +import nl.hauntedmc.proxyfeatures.api.capability.admission.*; +import nl.hauntedmc.proxyfeatures.api.capability.queue.*; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class AdmissionAndQueueContractsTest { + + @Test + void admissionRequestsNormalizeOptionalAndIntentInputs() { + UUID playerId = UUID.randomUUID(); + ServerId server = ServerId.of("survival"); + + AdmissionRequest request = new AdmissionRequest(playerId, null, server, null); + assertEquals(Optional.empty(), request.previousServer()); + assertEquals(AdmissionIntent.PLUGIN, request.intent()); + assertEquals(AdmissionIntent.NORMAL, AdmissionRequest.normal(playerId, server).intent()); + assertThrows(NullPointerException.class, + () -> new AdmissionRequest(null, Optional.empty(), server, AdmissionIntent.NORMAL)); + assertThrows(NullPointerException.class, + () -> new AdmissionRequest(playerId, Optional.empty(), null, AdmissionIntent.NORMAL)); + } + + @Test + void admissionDecisionsEnforceTheLeaseInvariant() { + TestLease lease = new TestLease(); + AdmissionDecision allowed = AdmissionDecision.allow(lease); + + assertTrue(allowed.allowed()); + assertSame(lease, allowed.lease().orElseThrow()); + + AdmissionDecision denied = AdmissionDecision.deny(AdmissionDenialReason.CAPACITY, " server:survival "); + assertFalse(denied.allowed()); + assertEquals(Optional.of("server:survival"), denied.blockingPolicy()); + assertEquals(Optional.empty(), AdmissionDecision.deny(AdmissionDenialReason.MAINTENANCE, " ").blockingPolicy()); + assertThrows(IllegalArgumentException.class, + () -> AdmissionDecision.deny(AdmissionDenialReason.NONE, "none")); + assertThrows(NullPointerException.class, () -> AdmissionDecision.allow(null)); + assertThrows(IllegalArgumentException.class, + () -> new AdmissionDecision(AdmissionDenialReason.NONE, null, Optional.empty())); + assertThrows(IllegalArgumentException.class, + () -> new AdmissionDecision(AdmissionDenialReason.CAPACITY, null, Optional.of(lease))); + } + + @Test + void admissionSnapshotsAreImmutableAndExposeAvailabilityMath() { + AdmissionScopeSnapshot scope = new AdmissionScopeSnapshot( + " survival ", 20, 3, 10, 2, 1, AdmissionState.OPEN + ); + assertEquals("survival", scope.name()); + assertEquals(13, scope.effectiveUsed()); + assertEquals(4, scope.normalAvailable()); + assertEquals(7, scope.absoluteAvailable()); + + AdmissionScopeSnapshot normalized = new AdmissionScopeSnapshot( + "proxy", -1, -1, -1, -1, -1, AdmissionState.DRAINING + ); + assertEquals(0, normalized.capacity()); + assertEquals(0, normalized.effectiveUsed()); + assertThrows(IllegalArgumentException.class, + () -> new AdmissionScopeSnapshot(" ", 1, 0, 0, 0, 0, AdmissionState.OPEN)); + assertThrows(NullPointerException.class, + () -> new AdmissionScopeSnapshot("x", 1, 0, 0, 0, 0, null)); + + Map groups = new LinkedHashMap<>(); + groups.put("game", scope); + AdmissionSnapshot snapshot = new AdmissionSnapshot( + scope, scope, groups, null, -2, Instant.EPOCH + ); + groups.clear(); + assertEquals(scope, snapshot.groups().get("game")); + assertTrue(snapshot.servers().isEmpty()); + assertEquals(0, snapshot.activeLeases()); + assertThrows(NullPointerException.class, + () -> new AdmissionSnapshot(null, scope, null, null, 0, Instant.EPOCH)); + assertThrows(NullPointerException.class, + () -> new AdmissionSnapshot(scope, scope, null, null, 0, null)); + } + + @Test + void queueJoinResultsEnforceTypedSuccessAndFailureOutcomes() { + QueuedPlayerSnapshot entry = queuedPlayer(); + + for (QueueJoinStatus status : new QueueJoinStatus[] { + QueueJoinStatus.JOINED, + QueueJoinStatus.MOVED, + QueueJoinStatus.ALREADY_QUEUED + }) { + assertEquals(entry, QueueJoinResult.success(status, entry).entry().orElseThrow()); + } + assertTrue(QueueJoinResult.failure(QueueJoinStatus.REJECTED).entry().isEmpty()); + assertThrows(IllegalArgumentException.class, + () -> QueueJoinResult.failure(QueueJoinStatus.JOINED)); + assertThrows(IllegalArgumentException.class, + () -> QueueJoinResult.success(QueueJoinStatus.REJECTED, entry)); + assertThrows(NullPointerException.class, + () -> new QueueJoinResult(null, Optional.empty())); + assertThrows(NullPointerException.class, + () -> QueueJoinResult.success(QueueJoinStatus.JOINED, null)); + } + + @Test + void queueRequestsAndSnapshotsAreValidatedAndImmutable() { + UUID playerId = UUID.randomUUID(); + ServerId server = ServerId.of("survival"); + QueueJoinRequest request = new QueueJoinRequest(playerId, server, null); + assertEquals(QueueJoinCause.PLUGIN, request.cause()); + assertThrows(NullPointerException.class, + () -> new QueueJoinRequest(null, server, QueueJoinCause.MANUAL)); + assertThrows(NullPointerException.class, + () -> new QueueJoinRequest(playerId, null, QueueJoinCause.MANUAL)); + + QueuedPlayerSnapshot queued = queuedPlayer(); + assertEquals(1, queued.position()); + assertThrows(IllegalArgumentException.class, + () -> new QueuedPlayerSnapshot(playerId, server, 0, 0, Instant.EPOCH, false)); + assertThrows(NullPointerException.class, + () -> new QueuedPlayerSnapshot(playerId, server, 1, 0, null, false)); + + QueueServerSnapshot serverSnapshot = new QueueServerSnapshot( + server, -1, -1, 2, 3, -1, null + ); + assertEquals(0, serverSnapshot.waiting()); + assertEquals(0, serverSnapshot.connected()); + assertEquals(2, serverSnapshot.grace()); + assertEquals(3, serverSnapshot.inFlight()); + assertTrue(serverSnapshot.oldestEnqueuedAt().isEmpty()); + + Map values = new LinkedHashMap<>(); + values.put(server, new QueueServerSnapshot(server, 4, 0, 0, 2, 0, Optional.of(Instant.EPOCH))); + QueueSnapshot snapshot = new QueueSnapshot(values, Instant.EPOCH); + values.clear(); + assertEquals(4, snapshot.totalWaiting()); + assertEquals(2, snapshot.totalInFlight()); + assertThrows(UnsupportedOperationException.class, () -> snapshot.servers().clear()); + assertTrue(new QueueSnapshot(null, Instant.EPOCH).servers().isEmpty()); + assertThrows(NullPointerException.class, () -> new QueueSnapshot(Map.of(), null)); + } + + private static QueuedPlayerSnapshot queuedPlayer() { + return new QueuedPlayerSnapshot( + UUID.randomUUID(), ServerId.of("survival"), 1, 10, Instant.EPOCH, false + ); + } + + private static final class TestLease implements AdmissionLease { + private final UUID id = UUID.randomUUID(); + private boolean active = true; + + @Override public UUID id() { return id; } + @Override public UUID playerId() { return id; } + @Override public ServerId targetServer() { return ServerId.of("survival"); } + @Override public AdmissionIntent intent() { return AdmissionIntent.NORMAL; } + @Override public Instant expiresAt() { return Instant.MAX; } + @Override public boolean isActive() { return active; } + @Override public void commit() { active = false; } + @Override public void release() { active = false; } + } +} diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/DomainContractsTest.java b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/DomainContractsTest.java new file mode 100644 index 00000000..ea474753 --- /dev/null +++ b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/DomainContractsTest.java @@ -0,0 +1,198 @@ +package nl.hauntedmc.proxyfeatures.api; + +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionType; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCountSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCounts; +import nl.hauntedmc.proxyfeatures.api.capability.operations.*; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceChangedEvent; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.social.FriendshipApi; +import nl.hauntedmc.proxyfeatures.api.capability.social.FriendshipStatus; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContext; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContribution; +import nl.hauntedmc.proxyfeatures.api.feature.*; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; +import org.junit.jupiter.api.Test; + +import java.net.InetAddress; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static org.junit.jupiter.api.Assertions.*; + +class DomainContractsTest { + + @Test + void playerCountsAndSnapshotsRemainConsistentAndImmutable() { + PlayerCounts counts = new PlayerCounts(8, 3); + assertEquals(5, counts.visible()); + assertEquals(new PlayerCounts(0, 0), PlayerCounts.empty()); + assertThrows(IllegalArgumentException.class, () -> new PlayerCounts(-1, 0)); + assertThrows(IllegalArgumentException.class, () -> new PlayerCounts(1, -1)); + assertThrows(IllegalArgumentException.class, () -> new PlayerCounts(1, 2)); + + ServerId lobby = ServerId.of("lobby"); + Map servers = new LinkedHashMap<>(); + servers.put(lobby, counts); + PlayerCountSnapshot snapshot = new PlayerCountSnapshot(counts, servers, Instant.EPOCH); + servers.clear(); + assertEquals(counts, snapshot.server(lobby)); + assertEquals(PlayerCounts.empty(), snapshot.server(ServerId.of("missing"))); + assertThrows(NullPointerException.class, () -> snapshot.server(null)); + assertTrue(new PlayerCountSnapshot(counts, null, Instant.EPOCH).servers().isEmpty()); + assertThrows(NullPointerException.class, + () -> new PlayerCountSnapshot(null, Map.of(), Instant.EPOCH)); + assertThrows(NullPointerException.class, + () -> new PlayerCountSnapshot(counts, Map.of(), null)); + } + + @Test + void presenceSnapshotsValidateVisibilityAndDeriveVisiblePlayers() { + UUID visible = UUID.randomUUID(); + UUID hidden = UUID.randomUUID(); + Set online = new LinkedHashSet<>(List.of(visible, hidden)); + PresenceSnapshot snapshot = new PresenceSnapshot(online, Set.of(hidden), Instant.EPOCH); + online.clear(); + + assertEquals(2, snapshot.onlineCount()); + assertEquals(1, snapshot.hiddenCount()); + assertEquals(1, snapshot.visibleCount()); + assertEquals(Set.of(visible), snapshot.visiblePlayers()); + assertTrue(new PresenceSnapshot(null, null, Instant.EPOCH).onlinePlayers().isEmpty()); + assertThrows(IllegalArgumentException.class, + () -> new PresenceSnapshot(Set.of(visible), Set.of(hidden), Instant.EPOCH)); + assertThrows(NullPointerException.class, + () -> new PresenceSnapshot(Set.of(), Set.of(), null)); + + PresenceChangedEvent event = new PresenceChangedEvent(hidden, null, true, Instant.EPOCH); + assertEquals("", event.playerName()); + assertThrows(NullPointerException.class, + () -> new PresenceChangedEvent(null, "player", true, Instant.EPOCH)); + assertThrows(NullPointerException.class, + () -> new PresenceChangedEvent(hidden, "player", true, null)); + } + + @Test + void maintenanceValueObjectsRepresentGlobalAndServerScopes() { + MaintenanceScope global = MaintenanceScope.global(); + MaintenanceScope server = MaintenanceScope.server(ServerId.of("survival")); + assertTrue(global.isGlobal()); + assertFalse(server.isGlobal()); + assertTrue(new MaintenanceScope(null).isGlobal()); + assertThrows(NullPointerException.class, () -> MaintenanceScope.server(null)); + + Set servers = new LinkedHashSet<>(Set.of(ServerId.of("survival"))); + MaintenanceSnapshot snapshot = new MaintenanceSnapshot(true, servers, Instant.EPOCH); + servers.clear(); + assertEquals(1, snapshot.servers().size()); + assertTrue(new MaintenanceSnapshot(false, null, Instant.EPOCH).servers().isEmpty()); + assertThrows(NullPointerException.class, + () -> new MaintenanceSnapshot(false, Set.of(), null)); + } + + @Test + void versionAndFriendshipDefaultMethodsProvideUsefulDerivedDecisions() { + VersionApi versions = new VersionApi() { + @Override public int minimumProtocolVersion() { return 765; } + @Override public String minimumVersionName() { return "1.20.4"; } + }; + assertTrue(versions.isSupported(765)); + assertFalse(versions.isSupported(764)); + + FriendshipApi friends = (first, second) -> CompletableFuture.completedFuture(first.equals(second)); + UUID player = UUID.randomUUID(); + assertEquals(FriendshipStatus.FRIENDS, friends.relationship(player, player).toCompletableFuture().join()); + assertEquals(FriendshipStatus.NOT_FRIENDS, + friends.relationship(player, UUID.randomUUID()).toCompletableFuture().join()); + } + + @Test + void sanctionsArePersistenceIndependentImmutableViews() { + UUID player = UUID.randomUUID(); + SanctionSnapshot snapshot = new SanctionSnapshot( + 10L, + player, + SanctionType.BAN, + "reason", + "actor", + Instant.EPOCH, + null, + true + ); + assertTrue(snapshot.expiresAt().isEmpty()); + assertEquals(player, snapshot.playerId()); + assertThrows(NullPointerException.class, + () -> new SanctionSnapshot(1, null, SanctionType.BAN, "r", "a", Instant.EPOCH, null, true)); + assertThrows(NullPointerException.class, + () -> new SanctionSnapshot(1, player, null, "r", "a", Instant.EPOCH, null, true)); + assertThrows(NullPointerException.class, + () -> new SanctionSnapshot(1, player, SanctionType.BAN, null, "a", Instant.EPOCH, null, true)); + assertThrows(NullPointerException.class, + () -> new SanctionSnapshot(1, player, SanctionType.BAN, "r", null, Instant.EPOCH, null, true)); + assertThrows(NullPointerException.class, + () -> new SanctionSnapshot(1, player, SanctionType.BAN, "r", "a", null, null, true)); + } + + @Test + void motdExtensionValuesNormalizeOptionalOverrides() throws Exception { + MotdContribution contribution = new MotdContribution( + Optional.of(" line one "), + Optional.of(" ") + ); + assertEquals(Optional.of("line one"), contribution.firstLine()); + assertTrue(contribution.secondLine().isEmpty()); + assertEquals(Optional.of("status"), MotdContribution.secondLine("status").secondLine()); + assertTrue(new MotdContribution(null, null).firstLine().isEmpty()); + assertThrows(NullPointerException.class, () -> MotdContribution.secondLine(null)); + + MotdContext context = new MotdContext(InetAddress.getLoopbackAddress(), 765); + assertEquals(765, context.protocolVersion()); + assertThrows(NullPointerException.class, () -> new MotdContext(null, 765)); + } + + @Test + void featureDescriptorsAndSnapshotsAreNormalizedImmutableContracts() { + FeatureId queue = FeatureId.of("queue"); + Set dependencies = new LinkedHashSet<>(Set.of(FeatureId.of("capacity"))); + Set capabilities = new LinkedHashSet<>(Set.of("QueueApi")); + FeatureDescriptor descriptor = new FeatureDescriptor( + queue, + " Queue ", + " 1.0.0 ", + FeatureClassification.CAPABILITY_PROVIDER, + dependencies, + capabilities + ); + dependencies.clear(); + capabilities.clear(); + assertEquals("Queue", descriptor.displayName()); + assertEquals("1.0.0", descriptor.version()); + assertEquals(Set.of(FeatureId.of("capacity")), descriptor.requiredFeatures()); + assertEquals(Set.of("QueueApi"), descriptor.providedCapabilities()); + assertThrows(NullPointerException.class, + () -> new FeatureDescriptor(null, "x", "1", FeatureClassification.INTERNAL, null, null)); + assertThrows(IllegalArgumentException.class, + () -> new FeatureDescriptor(queue, " ", "1", FeatureClassification.INTERNAL, null, null)); + assertThrows(IllegalArgumentException.class, + () -> new FeatureDescriptor(queue, "x", " ", FeatureClassification.INTERNAL, null, null)); + assertThrows(NullPointerException.class, + () -> new FeatureDescriptor(queue, "x", "1", null, null, null)); + + FeatureSnapshot active = new FeatureSnapshot(descriptor, FeatureState.ACTIVE, null, Instant.EPOCH); + assertTrue(active.failure().isEmpty()); + FeatureSnapshot failed = new FeatureSnapshot( + descriptor, FeatureState.FAILED, Optional.of("boom"), Instant.EPOCH + ); + assertEquals(Optional.of("boom"), failed.failure()); + assertTrue(new FeatureSnapshot(descriptor, FeatureState.FAILED, Optional.of(" "), Instant.EPOCH) + .failure().isEmpty()); + assertThrows(NullPointerException.class, + () -> new FeatureSnapshot(null, FeatureState.ACTIVE, null, Instant.EPOCH)); + assertThrows(NullPointerException.class, + () -> new FeatureSnapshot(descriptor, null, null, Instant.EPOCH)); + assertThrows(NullPointerException.class, + () -> new FeatureSnapshot(descriptor, FeatureState.ACTIVE, null, null)); + } +} diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/IdentityAndServiceContractsTest.java b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/IdentityAndServiceContractsTest.java new file mode 100644 index 00000000..7458053e --- /dev/null +++ b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/IdentityAndServiceContractsTest.java @@ -0,0 +1,88 @@ +package nl.hauntedmc.proxyfeatures.api; + +import nl.hauntedmc.proxyfeatures.api.feature.FeatureId; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; +import nl.hauntedmc.proxyfeatures.api.service.CapabilityRef; +import nl.hauntedmc.proxyfeatures.api.service.CapabilityUnavailableException; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +class IdentityAndServiceContractsTest { + + @Test + void versionsAreNormalizedAndCurrentFactoryKeepsTheApiVersion() { + ProxyFeaturesApiVersion version = new ProxyFeaturesApiVersion(" 3.3.0 ", " build-12 "); + + assertEquals("3.3.0", version.apiVersion()); + assertEquals("build-12", version.implementationVersion()); + assertEquals(ProxyFeaturesApiVersion.CURRENT, + ProxyFeaturesApiVersion.current("runtime").apiVersion()); + assertThrows(NullPointerException.class, () -> new ProxyFeaturesApiVersion(null, "runtime")); + assertThrows(IllegalArgumentException.class, () -> new ProxyFeaturesApiVersion(" ", "runtime")); + assertThrows(IllegalArgumentException.class, () -> new ProxyFeaturesApiVersion("3.3.0", " ")); + } + + @Test + void featureIdsAreStableNormalizedComparableValues() { + FeatureId id = FeatureId.of(" Queue.Main_1 "); + + assertEquals("queue.main_1", id.value()); + assertEquals("queue.main_1", id.toString()); + assertTrue(id.compareTo(FeatureId.of("vanish")) < 0); + assertThrows(NullPointerException.class, () -> id.compareTo(null)); + assertThrows(NullPointerException.class, () -> FeatureId.of(null)); + assertThrows(IllegalArgumentException.class, () -> FeatureId.of("")); + assertThrows(IllegalArgumentException.class, () -> FeatureId.of("-queue")); + assertThrows(IllegalArgumentException.class, () -> FeatureId.of("queue/name")); + } + + @Test + void serverIdsNormalizeAndSupportOptionalParsing() { + ServerId id = ServerId.of(" Lobby.One_1 "); + + assertEquals("lobby.one_1", id.value()); + assertEquals("lobby.one_1", id.toString()); + assertEquals(Optional.of(id), ServerId.optional("LOBBY.ONE_1")); + assertTrue(ServerId.optional(null).isEmpty()); + assertTrue(ServerId.optional(" ").isEmpty()); + assertTrue(id.compareTo(ServerId.of("survival")) < 0); + assertThrows(NullPointerException.class, () -> id.compareTo(null)); + assertThrows(NullPointerException.class, () -> ServerId.of(null)); + assertThrows(IllegalArgumentException.class, () -> ServerId.of(" ")); + assertThrows(IllegalArgumentException.class, () -> ServerId.of("bad/server")); + } + + @Test + void capabilityReferencesRemainStableAcrossAvailabilityChanges() { + AtomicReference current = new AtomicReference<>(); + CapabilityRef reference = new CapabilityRef<>() { + @Override + public Class type() { + return Runnable.class; + } + + @Override + public Optional get() { + return Optional.ofNullable(current.get()); + } + }; + + assertFalse(reference.isAvailable()); + CapabilityUnavailableException unavailable = assertThrows( + CapabilityUnavailableException.class, + reference::require + ); + assertEquals(Runnable.class, unavailable.capabilityType()); + assertTrue(unavailable.getMessage().contains(Runnable.class.getName())); + + Runnable provider = () -> { }; + current.set(provider); + assertTrue(reference.isAvailable()); + assertSame(provider, reference.require()); + assertThrows(NullPointerException.class, () -> new CapabilityUnavailableException(null)); + } +} diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountSnapshotTest.java b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountSnapshotTest.java new file mode 100644 index 00000000..8c8feb9a --- /dev/null +++ b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/capability/network/PlayerCountSnapshotTest.java @@ -0,0 +1,33 @@ +package nl.hauntedmc.proxyfeatures.api.capability.network; + +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class PlayerCountSnapshotTest { + + @Test + void rejectsMissingNetworkAndServerCounts() { + assertThrows( + NullPointerException.class, + () -> new PlayerCountSnapshot(null, Map.of(), Instant.now()) + ); + + Map servers = new java.util.HashMap<>(); + servers.put(ServerId.of("survival"), null); + assertThrows( + NullPointerException.class, + () -> new PlayerCountSnapshot(PlayerCounts.empty(), servers, Instant.now()) + ); + } + + @Test + void rejectsInvalidCounts() { + assertThrows(IllegalArgumentException.class, () -> new PlayerCounts(1, 2)); + } +} diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityApiTest.java b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityApiTest.java deleted file mode 100644 index 04d2c909..00000000 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityApiTest.java +++ /dev/null @@ -1,242 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; - -import nl.hauntedmc.proxyfeatures.api.queue.QueueAdmissionAPI; -import nl.hauntedmc.proxyfeatures.api.queue.QueueObservabilityAPI; -import nl.hauntedmc.proxyfeatures.api.queue.QueueServerSnapshot; -import nl.hauntedmc.proxyfeatures.api.queue.QueueSnapshot; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class CapacityApiTest { - - @Test - void admissionIntentsExposeOnlyNormalAsQueueable() { - assertTrue(AdmissionIntent.NORMAL.isQueueable()); - for (AdmissionIntent intent : AdmissionIntent.values()) { - if (intent != AdmissionIntent.NORMAL) assertFalse(intent.isQueueable()); - } - } - - @Test - void capacityStatesExposeOpenAdmissionContract() { - assertTrue(CapacityState.OPEN.acceptsNormalAdmissions()); - assertFalse(CapacityState.DRAINING.acceptsNormalAdmissions()); - assertFalse(CapacityState.CLOSED.acceptsNormalAdmissions()); - assertFalse(CapacityState.OFFLINE.acceptsNormalAdmissions()); - } - - @Test - void capacityRequestNormalizesAndDefaultsIntent() { - UUID playerId = UUID.randomUUID(); - CapacityRequest request = new CapacityRequest( - playerId, - " Lobby ", - " Survival ", - null, - true, - false, - true - ); - - assertEquals(playerId, request.playerId()); - assertEquals("lobby", request.previousServer()); - assertEquals("survival", request.targetServer()); - assertEquals(AdmissionIntent.NORMAL, request.intent()); - assertTrue(request.reservedEligible()); - assertFalse(request.capacityBypass()); - assertTrue(request.stateBypass()); - assertEquals("", CapacityRequest.normalize(null)); - } - - @Test - void capacityRequestFactoryAndValidationAreStrict() { - UUID playerId = UUID.randomUUID(); - CapacityRequest request = CapacityRequest.normal(playerId, null, "SURVIVAL", true, true, false); - assertEquals("", request.previousServer()); - assertEquals("survival", request.targetServer()); - assertEquals(AdmissionIntent.NORMAL, request.intent()); - assertThrows(NullPointerException.class, () -> CapacityRequest.normal( - null, "", "survival", false, false, false)); - assertThrows(IllegalArgumentException.class, () -> CapacityRequest.normal( - playerId, "", " ", false, false, false)); - } - - @Test - void decisionsRequireLeaseForAllowedResults() { - TestLease lease = new TestLease(); - CapacityDecision allowed = CapacityDecision.allow(lease); - assertTrue(allowed.allowed()); - assertEquals(CapacityDenialReason.NONE, allowed.reason()); - assertEquals("", allowed.blockingScope()); - assertEquals(lease, allowed.lease()); - - CapacityDecision denied = CapacityDecision.deny(CapacityDenialReason.FULL, null); - assertFalse(denied.allowed()); - assertEquals(CapacityDenialReason.FULL, denied.reason()); - assertEquals("", denied.blockingScope()); - assertNull(denied.lease()); - - CapacityDecision normalized = new CapacityDecision(false, null, null, null); - assertEquals(CapacityDenialReason.INVALID_REQUEST, normalized.reason()); - assertThrows(NullPointerException.class, - () -> new CapacityDecision(true, CapacityDenialReason.NONE, "", null)); - } - - @Test - void scopeSnapshotCalculatesBothNormalAndAbsoluteHeadroom() { - CapacityScopeSnapshot scope = new CapacityScopeSnapshot( - "survival", 100, 10, 80, 4, 3, CapacityState.OPEN); - assertEquals(87, scope.effectiveUsed()); - assertEquals(90, scope.normalCapacity()); - assertEquals(3, scope.normalAvailable()); - assertEquals(13, scope.absoluteAvailable()); - - CapacityScopeSnapshot overfull = new CapacityScopeSnapshot( - "survival", 10, 2, 12, 3, 1, CapacityState.CLOSED); - assertEquals(16, overfull.effectiveUsed()); - assertEquals(0, overfull.normalAvailable()); - assertEquals(0, overfull.absoluteAvailable()); - } - - @Test - void snapshotsDefensivelyCopyMapsAndHandleNull() { - Map groups = new HashMap<>(); - groups.put("game", new CapacityScopeSnapshot("game", 10, 0, 1, 0, 0, CapacityState.OPEN)); - CapacitySnapshot snapshot = new CapacitySnapshot(null, null, groups, null, 2); - groups.clear(); - - assertEquals(1, snapshot.groups().size()); - assertTrue(snapshot.servers().isEmpty()); - assertThrows(UnsupportedOperationException.class, - () -> snapshot.groups().put("other", snapshot.groups().get("game"))); - - CapacitySnapshot empty = new CapacitySnapshot(null, null, null, null, 0); - assertNotSame(snapshot.groups(), empty.groups()); - assertTrue(empty.groups().isEmpty()); - assertTrue(empty.servers().isEmpty()); - } - - @Test - void queueCancellationFenceDefaultsToUnsupported() { - QueueAdmissionAPI queue = new QueueAdmissionAPI() { - @Override - public boolean isQueueEnabled(String serverName) { - return false; - } - - @Override - public boolean enqueue(com.velocitypowered.api.proxy.Player player, String serverName, - CapacityDenialReason reason, CapacityRequest admissionContext) { - return false; - } - - @Override - public void wake(String serverName) { - } - }; - - assertFalse(queue.consumeCancelledAdvance(UUID.randomUUID(), "survival")); - } - - @Test - void queueObservabilitySnapshotsNormalizeAndDefensivelyCopy() { - Instant oldest = Instant.parse("2026-08-03T10:00:00Z"); - QueueServerSnapshot server = new QueueServerSnapshot( - " Survival ", -1, 3, 2, 1, 4, oldest); - assertEquals("survival", server.server()); - assertEquals(0, server.waiting()); - assertEquals(3, server.connected()); - assertEquals(oldest, server.oldestEnqueuedAt()); - - Map values = new HashMap<>(); - values.put("survival", new QueueServerSnapshot("survival", 5, 3, 1, 2, 1, oldest)); - QueueSnapshot snapshot = new QueueSnapshot(values, null); - values.clear(); - - assertEquals(5, snapshot.totalWaiting()); - assertEquals(2, snapshot.totalInFlight()); - assertEquals(1, snapshot.servers().size()); - assertThrows(UnsupportedOperationException.class, - () -> snapshot.servers().put("other", server)); - - QueueObservabilityAPI observability = () -> snapshot; - assertEquals(snapshot, observability.snapshot()); - assertTrue(new QueueSnapshot(null, oldest).servers().isEmpty()); - } - - @Test - void leaseCloseDelegatesToRelease() { - TestLease lease = new TestLease(); - assertTrue(lease.isActive()); - lease.close(); - assertFalse(lease.isActive()); - assertTrue(lease.released.get()); - lease.commit(); - assertTrue(lease.committed.get()); - } - - @Test - void denialReasonEnumRemainsComplete() { - assertEquals(6, CapacityDenialReason.values().length); - assertEquals(CapacityDenialReason.FULL, CapacityDenialReason.valueOf("FULL")); - } - - private static final class TestLease implements CapacityLease { - private final UUID id = UUID.randomUUID(); - private final UUID playerId = UUID.randomUUID(); - private final AtomicBoolean released = new AtomicBoolean(); - private final AtomicBoolean committed = new AtomicBoolean(); - - @Override - public UUID id() { - return id; - } - - @Override - public UUID playerId() { - return playerId; - } - - @Override - public String targetServer() { - return "survival"; - } - - @Override - public AdmissionIntent intent() { - return AdmissionIntent.NORMAL; - } - - @Override - public Instant expiresAt() { - return Instant.MAX; - } - - @Override - public boolean isActive() { - return !released.get(); - } - - @Override - public void commit() { - committed.set(true); - } - - @Override - public void release() { - released.set(true); - } - } -} diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/SqliteCacheFileTest.java b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/SqliteCacheFileTest.java deleted file mode 100644 index ceaa5578..00000000 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/SqliteCacheFileTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache.impl; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.MockedStatic; - -import java.io.File; -import java.io.Serial; -import java.nio.file.Path; -import java.sql.Connection; -import java.sql.DriverManager; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mockStatic; - -class SqliteCacheFileTest { - - @TempDir - Path tempDir; - - @Test - void createsUnderlyingFileAndSupportsDeleteAndNoopMethods() { - Path db = tempDir.resolve("nested/cache.db"); - SqliteCacheFile file = new SqliteCacheFile(db.toFile()); - assertTrue(file.getUnderlyingFile().exists()); - assertFalse(file.isEmpty()); - - file.cleanupExpired(); - file.delete(); - assertFalse(file.getUnderlyingFile().exists()); - } - - @Test - void getConnectionEitherReturnsConnectionOrWrapsFailure() { - SqliteCacheFile file = new SqliteCacheFile(tempDir.resolve("c.db").toFile()); - try { - assertNotNull(file.getConnection()); - file.getConnection().close(); - } catch (RuntimeException ex) { - assertNotNull(ex.getCause()); - } catch (Exception ex) { - fail(ex); - } - } - - @Test - void constructorAndDeleteFailurePathsAreCovered() throws Exception { - Path parentFile = tempDir.resolve("not-a-dir"); - java.nio.file.Files.writeString(parentFile, "x"); - assertThrows(RuntimeException.class, () -> new SqliteCacheFile(parentFile.resolve("cache.db").toFile())); - - FailingDeleteFile failingDelete = new FailingDeleteFile(tempDir.resolve("fail-delete.db").toString()); - SqliteCacheFile file = new SqliteCacheFile(failingDelete); - file.delete(); - assertTrue(file.getUnderlyingFile().exists()); - } - - @Test - void getConnectionWrapsDriverManagerFailure() { - SqliteCacheFile file = new SqliteCacheFile(tempDir.resolve("driver-fail.db").toFile()); - try (MockedStatic mocked = mockStatic(DriverManager.class)) { - mocked.when(() -> DriverManager.getConnection(anyString())).thenThrow(new IllegalStateException("boom")); - RuntimeException ex = assertThrows(RuntimeException.class, file::getConnection); - assertNotNull(ex.getCause()); - } - } - - @Test - void getConnectionReturnsDriverManagerConnectionWhenAvailable() throws Exception { - SqliteCacheFile file = new SqliteCacheFile(tempDir.resolve("driver-ok.db").toFile()); - Connection connection = org.mockito.Mockito.mock(Connection.class); - try (MockedStatic mocked = mockStatic(DriverManager.class)) { - mocked.when(() -> DriverManager.getConnection(anyString())).thenReturn(connection); - assertSame(connection, file.getConnection()); - } - } - - private static final class FailingDeleteFile extends File { - @Serial - private static final long serialVersionUID = 1L; - - FailingDeleteFile(String pathname) { - super(pathname); - } - - @Override - public boolean delete() { - return false; - } - - @Override - public boolean exists() { - return true; - } - } -} diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/packet/PacketManagerTest.java b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/packet/PacketManagerTest.java deleted file mode 100644 index 01afbd1d..00000000 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/packet/PacketManagerTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.io.packet; - -import com.velocitypowered.api.proxy.Player; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.mockito.Mockito.*; - -class PacketManagerTest { - - @Test - void sendUnicastSendsAllPacketsToSinglePlayer() { - Player player = mock(Player.class); - Packet first = mock(Packet.class); - Packet second = mock(Packet.class); - - PacketManager.sendUnicast(player, first, second); - - verify(first).sendTo(player); - verify(second).sendTo(player); - } - - @Test - void sendMulticastSendsAllPacketsToAllTargets() { - Player a = mock(Player.class); - Player b = mock(Player.class); - Packet packet = mock(Packet.class); - - PacketManager.sendMulticast(List.of(a, b), packet); - - verify(packet).sendTo(a); - verify(packet).sendTo(b); - } - - @Test - void sendBroadcastUsesProvidedPlayers() { - Player a = mock(Player.class); - Player b = mock(Player.class); - Packet packet = mock(Packet.class); - - PacketManager.sendBroadcast(List.of(a, b), packet); - - verify(packet).sendTo(a); - verify(packet).sendTo(b); - } - - @Test - void sendMethodsHandleEmptyInputsWithoutInteraction() { - Player player = mock(Player.class); - Packet packet = mock(Packet.class); - PacketManager.sendUnicast(player); - PacketManager.sendMulticast(List.of(), packet); - PacketManager.sendBroadcast(List.of(), packet); - verifyNoInteractions(packet); - } -} diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/resource/ResourceHandlerTest.java b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/resource/ResourceHandlerTest.java deleted file mode 100644 index 10a5685a..00000000 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/resource/ResourceHandlerTest.java +++ /dev/null @@ -1,122 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.io.resource; - -import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesContext; -import net.kyori.adventure.text.logger.slf4j.ComponentLogger; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class ResourceHandlerTest { - - @TempDir - Path tempDir; - - @Test - void loadsSavesAndReloadsExistingResourceFile() throws Exception { - Path file = tempDir.resolve("lang/messages.yml"); - Files.createDirectories(file.getParent()); - Files.writeString(file, "general:\n usage: \"hello\"\n"); - - ProxyFeaturesContext plugin = mockPlugin(); - ResourceHandler handler = new ResourceHandler(plugin, "lang/messages.yml"); - assertNotNull(handler.getConfig()); - assertEquals("hello", handler.getConfig().node("general", "usage").getString()); - - handler.getConfig().node("general", "usage").raw("updated"); - handler.save(); - handler.reload(); - assertEquals("updated", handler.getConfig().node("general", "usage").getString()); - } - - @Test - void rejectsPathsThatEscapeDataDirectory() { - ProxyFeaturesContext plugin = mockPlugin(); - assertThrows(IllegalArgumentException.class, () -> new ResourceHandler(plugin, "../outside.yml")); - } - - @Test - void copiesDefaultResourceWhenMissing() throws Exception { - ProxyFeaturesContext plugin = mockPlugin(); - Path copied = tempDir.resolve("resource-default.yml"); - assertFalse(Files.exists(copied)); - - ResourceHandler handler = new ResourceHandler(plugin, "resource-default.yml"); - - assertNotNull(handler.getConfig()); - assertTrue(Files.exists(copied)); - assertTrue(Files.readString(copied).contains("from-test-resource")); - } - - @Test - void copiesNestedDefaultResourceWhenMissing() throws Exception { - ProxyFeaturesContext plugin = mockPlugin(); - Path copied = tempDir.resolve("nested/resource-default.yml"); - assertFalse(Files.exists(copied)); - - ResourceHandler handler = new ResourceHandler(plugin, "nested/resource-default.yml"); - - assertNotNull(handler.getConfig()); - assertTrue(Files.exists(copied)); - assertTrue(Files.readString(copied).contains("from-nested-test-resource")); - } - - @Test - void createsEmptyFileWhenDefaultResourceIsMissing() throws Exception { - ProxyFeaturesContext plugin = mockPlugin(); - Path copied = tempDir.resolve("missing-resource.yml"); - assertFalse(Files.exists(copied)); - - ResourceHandler handler = new ResourceHandler(plugin, "missing-resource.yml"); - - assertNotNull(handler.getConfig()); - assertTrue(Files.exists(copied)); - assertEquals("", Files.readString(copied)); - } - - @Test - void fallsBackToBasenameResourceWhenScopedPathIsMissing() throws Exception { - ProxyFeaturesContext plugin = mockPlugin(); - Path copied = tempDir.resolve("scoped/resource-default.yml"); - assertFalse(Files.exists(copied)); - - ResourceHandler handler = new ResourceHandler(plugin, "scoped/resource-default.yml"); - - assertNotNull(handler.getConfig()); - assertTrue(Files.exists(copied)); - assertTrue(Files.readString(copied).contains("from-test-resource")); - } - - @Test - void ensureAndSaveErrorBranchesAreHandled() throws Exception { - ProxyFeaturesContext plugin = mockPlugin(); - - Path blockedParent = tempDir.resolve("blocked"); - Files.writeString(blockedParent, "x"); - ResourceHandler blocked = new ResourceHandler(plugin, "blocked/child.yml"); - assertNotNull(blocked.getConfig()); - assertFalse(Files.exists(tempDir.resolve("blocked/child.yml"))); - - Path path = tempDir.resolve("save-fail.yml"); - Files.writeString(path, "v: 1\n"); - ResourceHandler handler = new ResourceHandler(plugin, "save-fail.yml"); - Files.delete(path); - Files.createDirectory(path); - - handler.save(); - assertEquals(1, handler.getConfig().node("v").getInt()); - } - - private ProxyFeaturesContext mockPlugin() { - ProxyFeaturesContext plugin = mock(ProxyFeaturesContext.class); - when(plugin.getDataDirectory()).thenReturn(tempDir); - when(plugin.getLogger()).thenReturn(ComponentLogger.logger("ResourceHandlerTest")); - when(plugin.getResourceClassLoader()).thenReturn(getClass().getClassLoader()); - return plugin; - } -} diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/http/DiscordUtilsTest.java b/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/http/DiscordUtilsTest.java deleted file mode 100644 index a0ee6a51..00000000 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/http/DiscordUtilsTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package nl.hauntedmc.proxyfeatures.api.util.http; - -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URL; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -class DiscordUtilsTest { - - @Test - void sendPayloadReturnsFalseForInvalidOrUnsupportedWebhookUrls() { - assertFalse(DiscordUtils.sendPayload(null, "{}")); - assertFalse(DiscordUtils.sendPayload(" ", "{}")); - assertFalse(DiscordUtils.sendPayload("http://localhost/webhook", "{}")); - assertFalse(DiscordUtils.sendPayload("not-a-url", "{}")); - } - - @Test - void sendPayloadHandlesRuntimeFailuresByReturningFalse() { - // HTTPS scheme passes validation, but connection should fail in tests. - assertFalse(DiscordUtils.sendPayload("https://127.0.0.1:1/webhook", "{\"a\":1}")); - } - - @Test - void sendPayloadHandlesSuccessNoContentAndErrorResponses() throws Exception { - URI uri = mock(URI.class); - URL url = mock(URL.class); - HttpURLConnection connection = mock(HttpURLConnection.class); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - - when(uri.getScheme()).thenReturn("https"); - when(uri.toURL()).thenReturn(url); - when(url.openConnection()).thenReturn(connection); - when(connection.getOutputStream()).thenReturn(output); - when(connection.getInputStream()).thenReturn(new ByteArrayInputStream("ok".getBytes(StandardCharsets.UTF_8))); - when(connection.getErrorStream()).thenReturn(new ByteArrayInputStream("err".getBytes(StandardCharsets.UTF_8))); - - try (MockedStatic mockedUri = mockStatic(URI.class)) { - mockedUri.when(() -> URI.create("https://example.test/hook")).thenReturn(uri); - - when(connection.getResponseCode()) - .thenReturn(HttpURLConnection.HTTP_OK) - .thenReturn(HttpURLConnection.HTTP_NO_CONTENT) - .thenReturn(HttpURLConnection.HTTP_BAD_REQUEST); - - assertTrue(DiscordUtils.sendPayload("https://example.test/hook", "{\"a\":1}")); - assertTrue(DiscordUtils.sendPayload("https://example.test/hook", null)); - assertFalse(DiscordUtils.sendPayload("https://example.test/hook", "{\"a\":1}")); - } - - String written = output.toString(StandardCharsets.UTF_8); - assertTrue(written.contains("{\"a\":1}")); - verify(connection, atLeastOnce()).disconnect(); - } -} diff --git a/proxyfeatures-api/src/test/resources/nested/resource-default.yml b/proxyfeatures-api/src/test/resources/nested/resource-default.yml deleted file mode 100644 index 46db1bda..00000000 --- a/proxyfeatures-api/src/test/resources/nested/resource-default.yml +++ /dev/null @@ -1 +0,0 @@ -message: from-nested-test-resource diff --git a/proxyfeatures-api/src/test/resources/resource-default.yml b/proxyfeatures-api/src/test/resources/resource-default.yml deleted file mode 100644 index 209a77a0..00000000 --- a/proxyfeatures-api/src/test/resources/resource-default.yml +++ /dev/null @@ -1 +0,0 @@ -message: from-test-resource diff --git a/proxyfeatures-contracts/pom.xml b/proxyfeatures-contracts/pom.xml index a9259561..c175f791 100644 --- a/proxyfeatures-contracts/pom.xml +++ b/proxyfeatures-contracts/pom.xml @@ -11,24 +11,14 @@ 0.09 - Platform-neutral persistence and messaging contracts shared with downstream server plugins. + Versioned messaging contracts shared with downstream server plugins. - - nl.hauntedmc.dataregistry - dataregistry-api - ${dataregistry.version} - nl.hauntedmc.dataprovider dataprovider-api ${dataprovider.version} provided - - jakarta.persistence - jakarta.persistence-api - ${jakarta.persistence.version} - org.junit.jupiter junit-jupiter diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleMessage.java b/proxyfeatures-contracts/src/main/java/nl/hauntedmc/proxyfeatures/contracts/messaging/RestartLifecycleMessage.java similarity index 98% rename from proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleMessage.java rename to proxyfeatures-contracts/src/main/java/nl/hauntedmc/proxyfeatures/contracts/messaging/RestartLifecycleMessage.java index 924b08bd..1d76ba70 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleMessage.java +++ b/proxyfeatures-contracts/src/main/java/nl/hauntedmc/proxyfeatures/contracts/messaging/RestartLifecycleMessage.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.features.restart.messaging; +package nl.hauntedmc.proxyfeatures.contracts.messaging; import nl.hauntedmc.dataprovider.database.messaging.api.AbstractEventMessage; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleMessageTest.java b/proxyfeatures-contracts/src/test/java/nl/hauntedmc/proxyfeatures/contracts/messaging/RestartLifecycleMessageTest.java similarity index 98% rename from proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleMessageTest.java rename to proxyfeatures-contracts/src/test/java/nl/hauntedmc/proxyfeatures/contracts/messaging/RestartLifecycleMessageTest.java index 1328126e..8143c6ca 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleMessageTest.java +++ b/proxyfeatures-contracts/src/test/java/nl/hauntedmc/proxyfeatures/contracts/messaging/RestartLifecycleMessageTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.features.restart.messaging; +package nl.hauntedmc.proxyfeatures.contracts.messaging; import com.google.gson.Gson; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-platform-acceptance/consumer/src/main/java/nl/hauntedmc/proxyfeatures/acceptance/ProxyFeaturesAcceptanceConsumer.java b/proxyfeatures-platform-acceptance/consumer/src/main/java/nl/hauntedmc/proxyfeatures/acceptance/ProxyFeaturesAcceptanceConsumer.java index 11fd29ea..dc72e881 100644 --- a/proxyfeatures-platform-acceptance/consumer/src/main/java/nl/hauntedmc/proxyfeatures/acceptance/ProxyFeaturesAcceptanceConsumer.java +++ b/proxyfeatures-platform-acceptance/consumer/src/main/java/nl/hauntedmc/proxyfeatures/acceptance/ProxyFeaturesAcceptanceConsumer.java @@ -7,7 +7,7 @@ import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; import nl.hauntedmc.dataregistry.api.DataRegistryApiProvider; -import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesContext; +import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesApi; import org.slf4j.Logger; /** Verifies the bundled ProxyFeatures artifact against the current shared platform APIs. */ @@ -36,8 +36,11 @@ public void onProxyInitialize(ProxyInitializeEvent event) { Object proxyFeatures = proxy.getPluginManager().getPlugin("proxyfeatures") .flatMap(container -> container.getInstance()) .orElseThrow(() -> new IllegalStateException("ProxyFeatures did not remain enabled.")); - if (!(proxyFeatures instanceof ProxyFeaturesContext)) { - throw new IllegalStateException("ProxyFeatures does not expose its public host contract."); + if (!(proxyFeatures instanceof ProxyFeaturesApi api)) { + throw new IllegalStateException("ProxyFeatures does not expose its public API."); + } + if (!api.version().apiVersion().equals("3.3.0")) { + throw new IllegalStateException("Unexpected ProxyFeatures API version: " + api.version()); } logger.info("PROXYFEATURES_ACCEPTANCE_PASS platform=velocity"); } catch (Exception exception) { diff --git a/proxyfeatures-platform-velocity/pom.xml b/proxyfeatures-platform-velocity/pom.xml index bfbba5f6..982237bb 100644 --- a/proxyfeatures-platform-velocity/pom.xml +++ b/proxyfeatures-platform-velocity/pom.xml @@ -20,13 +20,13 @@ ${project.groupId} - proxyfeatures-contracts + proxyfeatures-toolkit ${project.version} - io.github.classgraph - classgraph - ${classgraph.version} + ${project.groupId} + proxyfeatures-contracts + ${project.version} com.github.ben-manes.caffeine @@ -181,14 +181,6 @@ false - - io.github.classgraph - nl.hauntedmc.proxyfeatures.shaded.classgraph - - - nonapi.io.github.classgraph - nl.hauntedmc.proxyfeatures.shaded.nonapi.classgraph - com.github.benmanes.caffeine nl.hauntedmc.proxyfeatures.shaded.caffeine @@ -247,18 +239,18 @@ property="distribution.has.license"/> - - + - + - - - getDataRegistry() { .map(DataRegistryApiProvider.class::cast) .map(DataRegistryApiProvider::getDataRegistry); } + + /** Shared immutable player-reference resolver used by all feature persistence adapters. */ + public PlayerReferenceResolver getPlayerReferenceResolver() { + PlayerReferenceResolver current = playerReferenceResolver; + if (current != null) return current; + synchronized (this) { + current = playerReferenceResolver; + if (current == null) { + current = new PlayerReferenceResolver(getDataRegistry().orElseThrow( + () -> new IllegalStateException("DataRegistryApi is required for player persistence."))); + playerReferenceResolver = current; + } + return current; + } + } + + @Override + public ProxyFeaturesApiVersion version() { + return api.version(); + } + + @Override + public CapabilityRegistry capabilities() { + return api.capabilities(); + } + + @Override + public FeatureCatalog features() { + return api.features(); + } + + public DefaultCapabilityRegistry getCapabilityRegistry() { + return capabilityRegistry; + } + + public DefaultFeatureCatalog getFeatureCatalog() { + return featureCatalog; + } + + public InternalServiceRegistry getInternalServiceRegistry() { + return internalServiceRegistry; + } + + public DefaultMotdExtensions getMotdExtensions() { + return motdExtensions; + } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureContext.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureContext.java index f093e73e..309e200a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureContext.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureContext.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureFactory.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureFactory.java index 7130c00a..eb8e7d84 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureFactory.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureFactory.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class FeatureFactory { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeature.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeature.java index 240eacf4..48bce16d 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeature.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeature.java @@ -4,10 +4,10 @@ import nl.hauntedmc.dataprovider.database.DataAccess; import nl.hauntedmc.dataprovider.database.messaging.MessagingDatabaseProvider; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.feature.Feature; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.framework.feature.Feature; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureDataManager; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/Announcer.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/Announcer.java index 29bebfff..2f6b8b11 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/Announcer.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/Announcer.java @@ -2,8 +2,8 @@ import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.announcer.command.AnnouncerCommand; import nl.hauntedmc.proxyfeatures.features.announcer.entity.PlayerAnnouncerSettingsEntity; @@ -61,10 +61,7 @@ public void initialize() { PlayerAnnouncerSettingsEntity.class ).orElseThrow(); - PlayerReferenceResolver playerResolver = new PlayerReferenceResolver( - getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for Announcer.")) - ); + PlayerReferenceResolver playerResolver = getPlugin().getPlayerReferenceResolver(); settingsService = new AnnouncerSettingsService(ormContext, playerResolver); handler = new AnnouncerHandler(this, settingsService); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/command/AnnouncerCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/command/AnnouncerCommand.java index 1e67fbe6..e609166a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/command/AnnouncerCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/command/AnnouncerCommand.java @@ -10,7 +10,7 @@ import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; import nl.hauntedmc.proxyfeatures.features.announcer.Announcer; import nl.hauntedmc.proxyfeatures.features.announcer.internal.AnnouncementDefinition; import nl.hauntedmc.proxyfeatures.features.announcer.internal.AnnouncementEvaluation; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerHandler.java index 618dbb44..99fc3dbd 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerHandler.java @@ -7,9 +7,9 @@ import com.velocitypowered.api.scheduler.ScheduledTask; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.util.text.format.ComponentFormatter; -import nl.hauntedmc.proxyfeatures.api.util.text.format.TextFormatter; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.announcer.Announcer; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerRegistry.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerRegistry.java index 91f16f85..f90c0f71 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerRegistry.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerRegistry.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.announcer.internal; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigService; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigView; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigView; import nl.hauntedmc.proxyfeatures.features.announcer.Announcer; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; import org.spongepowered.configurate.CommentedConfigurationNode; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/meta/Meta.java index ae02837f..408ca0f7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.announcer.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/AntiBot.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/AntiBot.java index f57cef9c..e4653473 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/AntiBot.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/AntiBot.java @@ -2,8 +2,8 @@ import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.antibot.audit.AntiBotAuditLogService; import nl.hauntedmc.proxyfeatures.features.antibot.audit.PlayerAntiBotLogEntity; @@ -196,10 +196,7 @@ public void initialize() { if (orm == null) { getLogger().warn("AntiBot database audit logging is disabled because the ORM context is unavailable."); } - PlayerReferenceResolver playerResolver = new PlayerReferenceResolver( - getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for AntiBot.")) - ); + PlayerReferenceResolver playerResolver = getPlugin().getPlayerReferenceResolver(); this.auditLogService = new AntiBotAuditLogService(getLogger(), orm, playerResolver); Path knownPlayersFile = getLifecycleManager() diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/command/AntiBotCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/command/AntiBotCommand.java index 6c9aca44..ecdb63fd 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/command/AntiBotCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/command/AntiBotCommand.java @@ -8,7 +8,7 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.LiteralCommandNode; import com.velocitypowered.api.command.CommandSource; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; import nl.hauntedmc.proxyfeatures.features.antibot.AntiBot; import nl.hauntedmc.proxyfeatures.features.antibot.internal.AddressWhitelist; import nl.hauntedmc.proxyfeatures.features.antibot.internal.AntiBotConfig; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/AddressWhitelist.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/AddressWhitelist.java index 89cb4fc6..1239981d 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/AddressWhitelist.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/AddressWhitelist.java @@ -1,5 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.antibot.internal; +import nl.hauntedmc.proxyfeatures.framework.network.IpAddressUtil; + import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/AntiBotService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/AntiBotService.java index 961bd348..62770cc2 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/AntiBotService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/AntiBotService.java @@ -1,5 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.antibot.internal; +import nl.hauntedmc.proxyfeatures.framework.network.IpAddressUtil; + import nl.hauntedmc.proxyfeatures.features.antibot.AntiBot; import nl.hauntedmc.proxyfeatures.features.antibot.audit.AntiBotAuditLogService; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/listener/AntiBotLoginPolicy.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/listener/AntiBotLoginPolicy.java index 08c786ab..358989e5 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/listener/AntiBotLoginPolicy.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/listener/AntiBotLoginPolicy.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.antibot.listener; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.features.antibot.internal.IpAddressUtil; +import nl.hauntedmc.proxyfeatures.framework.network.IpAddressUtil; import java.net.InetSocketAddress; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/meta/Meta.java index 6d1cdae2..356af46d 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.antibot.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/AntiVPN.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/AntiVPN.java index 68bbf0c3..ac0a48c1 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/AntiVPN.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/AntiVPN.java @@ -2,13 +2,13 @@ import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheDirectory; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheType; -import nl.hauntedmc.proxyfeatures.api.io.cache.FileCacheStore; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheDirectory; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheType; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.FileCacheStore; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; -import nl.hauntedmc.proxyfeatures.features.antivpn.api.CountryAPI; +import nl.hauntedmc.proxyfeatures.api.capability.player.NetworkLocationApi; import nl.hauntedmc.proxyfeatures.features.antivpn.audit.AntiVpnAuditLogService; import nl.hauntedmc.proxyfeatures.features.antivpn.audit.PlayerAntiVpnLogEntity; import nl.hauntedmc.proxyfeatures.features.antivpn.command.AntiVPNCommand; @@ -151,10 +151,7 @@ public void initialize() { if (orm == null) { getLogger().warn("AntiVPN database audit logging is disabled because the ORM context is unavailable."); } - PlayerReferenceResolver playerResolver = new PlayerReferenceResolver( - getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for AntiVPN.")) - ); + PlayerReferenceResolver playerResolver = getPlugin().getPlayerReferenceResolver(); this.auditLogService = new AntiVpnAuditLogService(getLogger(), orm, playerResolver); this.metrics = new MetricsCollector(); @@ -177,7 +174,7 @@ public void initialize() { this.service = new AntiVPNService(this, cache, providerChain, notifications, metrics); // API - getLifecycleManager().getApiManager().registerService(CountryAPI.class, countryService); + getLifecycleManager().getApiManager().registerService(NetworkLocationApi.class, countryService); // Listener getLifecycleManager().getListenerManager().registerListener( diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/api/CountryAPI.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/api/CountryAPI.java deleted file mode 100644 index abecb25b..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/api/CountryAPI.java +++ /dev/null @@ -1,14 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.antivpn.api; - -import java.util.Optional; -import java.util.UUID; - -/** - * Simple API to get the player's ISO country code (e.g., "NL", "US"). - */ -public interface CountryAPI { - /** - * Returns the country code for the given player UUID, if known. - */ - Optional getCountry(UUID uuid); -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/command/AntiVPNCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/command/AntiVPNCommand.java index c6e9937e..766d9fe3 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/command/AntiVPNCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/command/AntiVPNCommand.java @@ -12,7 +12,7 @@ import net.kyori.adventure.text.Component; import nl.hauntedmc.dataregistry.api.player.PlayerData; import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; import nl.hauntedmc.proxyfeatures.features.antivpn.AntiVPN; import nl.hauntedmc.proxyfeatures.features.antivpn.internal.AntiVPNService; import nl.hauntedmc.proxyfeatures.features.antivpn.internal.IpWhitelist; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/CountryService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/CountryService.java index 0c051d45..b7bb843c 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/CountryService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/CountryService.java @@ -2,7 +2,7 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import nl.hauntedmc.proxyfeatures.features.antivpn.api.CountryAPI; +import nl.hauntedmc.proxyfeatures.api.capability.player.NetworkLocationApi; import java.time.Duration; import java.util.Locale; @@ -13,7 +13,7 @@ /** * Thread-safe storage of country codes keyed by UUID, with optional temporary username staging. */ -public final class CountryService implements CountryAPI { +public final class CountryService implements NetworkLocationApi { private final ConcurrentHashMap byUuid = new ConcurrentHashMap<>(); private final Cache stagedByUsernameLower; @@ -26,7 +26,7 @@ public CountryService(Duration usernameTtl) { } @Override - public Optional getCountry(UUID uuid) { + public Optional countryCode(UUID uuid) { String v = byUuid.get(uuid); return (v == null || v.isBlank()) ? Optional.empty() : Optional.of(v); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/PersistentIpCache.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/PersistentIpCache.java index 78deef72..ca7bb2dd 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/PersistentIpCache.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/PersistentIpCache.java @@ -2,8 +2,8 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheValue; -import nl.hauntedmc.proxyfeatures.api.io.cache.FileCacheStore; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheValue; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.FileCacheStore; import nl.hauntedmc.proxyfeatures.features.antivpn.AntiVPN; import java.time.Duration; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/listener/AntiVPNLoginPolicy.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/listener/AntiVPNLoginPolicy.java index 69edf712..ea6838e5 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/listener/AntiVPNLoginPolicy.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/listener/AntiVPNLoginPolicy.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.antivpn.listener; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.features.antibot.internal.IpAddressUtil; +import nl.hauntedmc.proxyfeatures.framework.network.IpAddressUtil; import java.net.InetAddress; import java.net.InetSocketAddress; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/meta/Meta.java index 9fb06a1b..3f684ba0 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.antivpn.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/Broadcast.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/Broadcast.java index aea046c5..b41d2ec2 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/Broadcast.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/Broadcast.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.broadcast; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.broadcast.command.BroadcastProxyCommand; import nl.hauntedmc.proxyfeatures.features.broadcast.meta.Meta; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommand.java index 37ced13d..a9b1ed83 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommand.java @@ -11,9 +11,9 @@ import com.velocitypowered.api.proxy.ProxyServer; import net.kyori.adventure.text.Component; import net.kyori.adventure.title.Title; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; -import nl.hauntedmc.proxyfeatures.api.util.text.format.ComponentFormatter; -import nl.hauntedmc.proxyfeatures.api.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; import nl.hauntedmc.proxyfeatures.features.broadcast.Broadcast; import org.jetbrains.annotations.NotNull; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/meta/Meta.java index ded703fb..fde67048 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.broadcast.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/Capacity.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/Capacity.java index 862042a8..22019072 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/Capacity.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/Capacity.java @@ -2,9 +2,9 @@ import com.velocitypowered.api.scheduler.ScheduledTask; import nl.hauntedmc.dataprovider.database.messaging.MessagingDataAccess; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityAPI; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityAPI; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.capacity.command.CapacityCommand; @@ -13,6 +13,8 @@ import nl.hauntedmc.proxyfeatures.features.capacity.internal.CapacityControlPlane; import nl.hauntedmc.proxyfeatures.features.capacity.internal.CapacityIntegrationSynchronizer; import nl.hauntedmc.proxyfeatures.features.capacity.internal.CapacityService; +import nl.hauntedmc.proxyfeatures.features.capacity.internal.AdmissionCapability; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionApi; import nl.hauntedmc.proxyfeatures.features.capacity.internal.CapacitySnapshotPublisher; import nl.hauntedmc.proxyfeatures.features.capacity.listener.CapacityListener; import nl.hauntedmc.proxyfeatures.features.capacity.meta.Meta; @@ -195,7 +197,11 @@ public void initialize() { integrationSynchronizer.start(); service.start(); - getLifecycleManager().getApiManager().registerService(CapacityAPI.class, service); + getLifecycleManager().getApiManager().registerInternalService(CapacityAPI.class, service); + getLifecycleManager().getApiManager().registerService( + AdmissionApi.class, + new AdmissionCapability(this, service) + ); getLifecycleManager().getListenerManager().registerListener(new CapacityListener(this, service)); getLifecycleManager().getCommandManager().registerFeatureCommand( new CapacityCommand(this, service, controlPlane) diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/command/CapacityCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/command/CapacityCommand.java index c500617a..954edba2 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/command/CapacityCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/command/CapacityCommand.java @@ -2,10 +2,10 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityScopeSnapshot; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacitySnapshot; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityScopeSnapshot; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacitySnapshot; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; import nl.hauntedmc.proxyfeatures.features.capacity.config.CapacityConfig; import nl.hauntedmc.proxyfeatures.features.capacity.config.CapacityConfigEditor; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfig.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfig.java index 24a4ded4..5d84affe 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfig.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfig.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.capacity.config; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; import java.math.BigDecimal; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfigStore.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfigStore.java index 3504812f..63cf24ef 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfigStore.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfigStore.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.capacity.config; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigView; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigView; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; import org.spongepowered.configurate.serialize.SerializationException; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/AdmissionCapability.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/AdmissionCapability.java new file mode 100644 index 00000000..1b4c4a48 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/AdmissionCapability.java @@ -0,0 +1,153 @@ +package nl.hauntedmc.proxyfeatures.features.capacity.internal; + +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionApi; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionDecision; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionDenialReason; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionIntent; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionLease; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionRequest; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionScopeSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionState; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; +import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDecision; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityScopeSnapshot; + +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** Public, permission-safe projection of the internal admission engine. */ +public final class AdmissionCapability implements AdmissionApi { + private final Capacity feature; + private final CapacityService service; + + public AdmissionCapability(Capacity feature, CapacityService service) { + this.feature = Objects.requireNonNull(feature, "feature"); + this.service = Objects.requireNonNull(service, "service"); + } + + @Override + public AdmissionDecision tryAcquire(AdmissionRequest request) { + Objects.requireNonNull(request, "request"); + var player = feature.getPlugin().getProxy().getPlayer(request.playerId()); + if (player.isEmpty()) { + return AdmissionDecision.deny(AdmissionDenialReason.PLAYER_OFFLINE, "player"); + } + var trusted = service.createRequest( + player.get(), + request.previousServer().map(ServerId::value).orElse(""), + request.targetServer().value(), + toInternalIntent(request.intent()) + ); + return toPublicDecision(service.tryAcquire(trusted)); + } + + @Override + public AdmissionSnapshot snapshot() { + var source = service.snapshot(); + Map groups = new LinkedHashMap<>(); + source.groups().forEach((name, scope) -> groups.put(name, toPublicScope(scope))); + Map servers = new LinkedHashMap<>(); + source.servers().forEach((name, scope) -> servers.put(ServerId.of(name), toPublicScope(scope))); + return new AdmissionSnapshot( + toPublicScope(source.proxy()), + toPublicScope(source.gameplay()), + groups, + servers, + source.activeLeases(), + Instant.now() + ); + } + + private static AdmissionDecision toPublicDecision(CapacityDecision decision) { + if (decision.allowed()) { + return AdmissionDecision.allow(new LeaseAdapter(decision.lease())); + } + return AdmissionDecision.deny(switch (decision.reason()) { + case NONE -> throw new IllegalStateException("Denied capacity decision has no reason"); + case FULL -> AdmissionDenialReason.CAPACITY; + case SERVER_STATE -> AdmissionDenialReason.SERVER_STATE; + case PROXY_HARD_LIMIT -> AdmissionDenialReason.PROXY_HARD_LIMIT; + case UNKNOWN_TARGET -> AdmissionDenialReason.UNKNOWN_TARGET; + case INVALID_REQUEST -> AdmissionDenialReason.INVALID_REQUEST; + }, decision.blockingScope()); + } + + private static AdmissionScopeSnapshot toPublicScope(CapacityScopeSnapshot source) { + return new AdmissionScopeSnapshot( + source.name(), + source.capacity(), + source.reservedSlots(), + source.occupied(), + source.pending(), + source.restorationReserved(), + AdmissionState.valueOf(source.state().name()) + ); + } + + private static nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent toInternalIntent( + AdmissionIntent intent + ) { + return switch (intent) { + case NORMAL -> nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent.NORMAL; + case QUEUE_ADVANCE -> nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent.QUEUE_ADVANCE; + case RESTART_RETURN -> nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent.RESTART_RETURN; + case MAINTENANCE_EVACUATION -> + nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent.MAINTENANCE_EVACUATION; + case SECURITY_ROUTE -> nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent.SECURITY_ROUTE; + case PLUGIN -> nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent.INTERNAL; + }; + } + + private static final class LeaseAdapter implements AdmissionLease { + private final nl.hauntedmc.proxyfeatures.framework.admission.CapacityLease delegate; + + private LeaseAdapter(nl.hauntedmc.proxyfeatures.framework.admission.CapacityLease delegate) { + this.delegate = Objects.requireNonNull(delegate, "delegate"); + } + + @Override + public java.util.UUID id() { + return delegate.id(); + } + + @Override + public java.util.UUID playerId() { + return delegate.playerId(); + } + + @Override + public ServerId targetServer() { + return ServerId.of(delegate.targetServer()); + } + + @Override + public AdmissionIntent intent() { + return AdmissionIntent.valueOf(delegate.intent().name().replace("INTERNAL", "PLUGIN")); + } + + @Override + public Instant expiresAt() { + return delegate.expiresAt(); + } + + @Override + public boolean isActive() { + return delegate.isActive(); + } + + @Override + public void commit() { + delegate.commit(); + } + + @Override + public void release() { + delegate.release(); + } + } +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityControlPlane.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityControlPlane.java index d30a3b77..eee7f105 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityControlPlane.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityControlPlane.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.capacity.internal; import com.velocitypowered.api.scheduler.ScheduledTask; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; import nl.hauntedmc.proxyfeatures.features.capacity.config.CapacityConfig; import nl.hauntedmc.proxyfeatures.features.capacity.config.CapacityConfigStore; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityIntegrationSynchronizer.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityIntegrationSynchronizer.java index 049629e9..95505bbe 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityIntegrationSynchronizer.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityIntegrationSynchronizer.java @@ -1,10 +1,12 @@ package nl.hauntedmc.proxyfeatures.features.capacity.internal; import com.velocitypowered.api.scheduler.ScheduledTask; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; -import nl.hauntedmc.proxyfeatures.features.maintenance.Maintenance; +import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceApi; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; +import nl.hauntedmc.proxyfeatures.framework.admission.RestartCoordinationPort; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; -import nl.hauntedmc.proxyfeatures.features.restart.Restart; +import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; +import nl.hauntedmc.proxyfeatures.framework.service.InternalServices; import java.time.Duration; import java.util.HashSet; @@ -19,7 +21,7 @@ public final class CapacityIntegrationSynchronizer { private final CapacityService service; private final Set maintainedServers = new HashSet<>(); private ScheduledTask task; - private Restart attachedRestart; + private RestartCoordinationPort attachedRestart; private boolean closed; public CapacityIntegrationSynchronizer(Capacity feature, CapacityService service) { @@ -83,14 +85,12 @@ private synchronized void sync() { private void syncMaintenance() { Set active = new HashSet<>(); - var loaded = feature.getPlugin().getFeatureLoadManager().getFeatureRegistry() - .getLoadedFeature("Maintenance"); - if (loaded instanceof Maintenance maintenance && maintenance.getHandler() != null) { - maintenance.getHandler().getActiveGamemodes().stream() - .map(CapacityIntegrationSynchronizer::normalize) - .filter(server -> !server.isBlank()) - .forEach(active::add); - } + FeatureServices.find(feature, MaintenanceApi.class) + .map(MaintenanceApi::snapshot) + .stream() + .flatMap(snapshot -> snapshot.servers().stream()) + .map(server -> server.value()) + .forEach(active::add); Set all = new HashSet<>(maintainedServers); all.addAll(active); @@ -104,25 +104,22 @@ private void syncMaintenance() { } private void syncRestart() { - var loaded = feature.getPlugin().getFeatureLoadManager().getFeatureRegistry() - .getLoadedFeature("Restart"); - Restart current = loaded instanceof Restart restart ? restart : null; + RestartCoordinationPort current = InternalServices.find(feature, RestartCoordinationPort.class) + .orElse(null); if (attachedRestart != null && attachedRestart != current) { - attachedRestart.detachCapacity(service); + attachedRestart.detachAdmission(service); attachedRestart = null; } - if (current != null && current.attachCapacity(service)) { + if (current != null) { + current.attachAdmission(service); attachedRestart = current; } } private void detachRestart() { if (attachedRestart == null) return; - attachedRestart.detachCapacity(service); + attachedRestart.detachAdmission(service); attachedRestart = null; } - private static String normalize(String value) { - return value == null ? "" : value.trim().toLowerCase(Locale.ROOT); - } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityService.java index 8c7faa16..b62344b1 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityService.java @@ -3,19 +3,23 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.scheduler.ScheduledTask; -import nl.hauntedmc.proxyfeatures.api.capacity.AdmissionIntent; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityAPI; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDecision; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDenialReason; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityLease; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityRequest; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityScopeSnapshot; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacitySnapshot; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; -import nl.hauntedmc.proxyfeatures.api.queue.QueueAdmissionAPI; +import nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityAPI; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDecision; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDenialReason; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityLease; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityRequest; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityScopeSnapshot; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacitySnapshot; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; +import nl.hauntedmc.proxyfeatures.framework.admission.QueueAdmissionPort; +import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceApi; +import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceScope; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; import nl.hauntedmc.proxyfeatures.features.capacity.config.CapacityConfig; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; +import nl.hauntedmc.proxyfeatures.framework.service.InternalServices; import java.time.Duration; import java.time.Instant; @@ -110,6 +114,33 @@ public CapacityConfig config() { return config; } + @Override + public CapacityRequest createRequest( + Player player, + String previousServer, + String targetServer, + AdmissionIntent intent + ) { + Objects.requireNonNull(player, "player"); + String target = normalize(targetServer); + boolean maintenanceBypass = ServerId.optional(target).flatMap(server -> + FeatureServices.find(feature, MaintenanceApi.class) + .map(api -> api.mayBypass( + player.getUniqueId(), + MaintenanceScope.server(server) + ))).orElse(false); + CapacityConfig current = config; + return new CapacityRequest( + player.getUniqueId(), + previousServer, + target, + intent, + player.hasPermission(current.reservedPermission()), + player.hasPermission(current.capacityBypassPermission()), + player.hasPermission(current.stateBypassPermission()) || maintenanceBypass + ); + } + public synchronized void shutdown() { if (closed) return; closed = true; @@ -338,7 +369,8 @@ public synchronized void setServerState(String serverName, CapacityState state, if (!effective.acceptsNormalAdmissions()) { revokeBlockedLeases(server); } else if (previous != CapacityState.OPEN) { - FeatureServices.find(feature, QueueAdmissionAPI.class).ifPresent(api -> api.wake(server)); + InternalServices.find(feature, QueueAdmissionPort.class) + .ifPresent(api -> api.capacityChanged(ServerId.of(server))); } } @@ -639,8 +671,8 @@ private Set configuredServerNames() { } private void wakeQueues() { - FeatureServices.find(feature, QueueAdmissionAPI.class).ifPresent(api -> - configuredServerNames().forEach(api::wake)); + InternalServices.find(feature, QueueAdmissionPort.class).ifPresent(api -> + configuredServerNames().forEach(server -> api.capacityChanged(ServerId.of(server)))); } private void commitLease(LeaseImpl lease) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacitySnapshotPublisher.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacitySnapshotPublisher.java index 8b726fc2..324c8e27 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacitySnapshotPublisher.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacitySnapshotPublisher.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.capacity.internal; import nl.hauntedmc.dataprovider.database.messaging.MessagingDataAccess; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityScopeSnapshot; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacitySnapshot; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityScopeSnapshot; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacitySnapshot; import nl.hauntedmc.proxyfeatures.contracts.messaging.CapacitySnapshotMessage; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/listener/CapacityListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/listener/CapacityListener.java index 3f1f45e3..b22f72e7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/listener/CapacityListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/listener/CapacityListener.java @@ -10,19 +10,22 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.queue.QueueAdmissionAPI; -import nl.hauntedmc.proxyfeatures.api.capacity.AdmissionIntent; -import nl.hauntedmc.proxyfeatures.api.capacity.RestartAdmissionAPI; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDecision; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDenialReason; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityRequest; -import nl.hauntedmc.proxyfeatures.features.maintenance.Maintenance; +import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceApi; +import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceScope; +import nl.hauntedmc.proxyfeatures.api.capability.operations.RestartApi; +import nl.hauntedmc.proxyfeatures.api.capability.operations.TwoFactorApi; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; +import nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDecision; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDenialReason; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityRequest; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; import nl.hauntedmc.proxyfeatures.features.capacity.config.CapacityConfig; import nl.hauntedmc.proxyfeatures.features.capacity.internal.CapacityService; -import nl.hauntedmc.proxyfeatures.features.twofactor.TwoFactor; +import nl.hauntedmc.proxyfeatures.framework.admission.QueueAdmissionPort; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; +import nl.hauntedmc.proxyfeatures.framework.service.InternalServices; import java.util.Locale; @@ -93,16 +96,7 @@ public void onPreConnect(ServerPreConnectEvent event) { .filter(prepared -> prepared == AdmissionIntent.QUEUE_ADVANCE || prepared == AdmissionIntent.RESTART_RETURN) .orElseGet(() -> resolveIntent(player, previous, original, targetName)); - boolean maintenanceBypass = hasMaintenanceBypass(player, targetName); - request = new CapacityRequest( - player.getUniqueId(), - previous, - targetName, - intent, - player.hasPermission(config.reservedPermission()), - player.hasPermission(config.capacityBypassPermission()), - player.hasPermission(config.stateBypassPermission()) || maintenanceBypass - ); + request = service.createRequest(player, previous, targetName, intent); decision = service.tryAcquire(request); } catch (Throwable error) { @@ -159,62 +153,43 @@ public void onDisconnect(DisconnectEvent event) { } private AdmissionIntent resolveIntent(Player player, String previous, String original, String target) { - Maintenance maintenance = maintenance(); - if (maintenance != null && maintenance.getHandler() != null) { + MaintenanceApi maintenance = FeatureServices.find(feature, MaintenanceApi.class).orElse(null); + if (maintenance != null) { boolean redirectedFromMaintained = !original.isBlank() && !original.equals(target) - && maintenance.getHandler().isGamemodeActive(original); + && maintenance.isActive(MaintenanceScope.server(ServerId.of(original))); boolean evacuatingMaintained = !previous.isBlank() && !previous.equals(target) - && maintenance.getHandler().isGamemodeActive(previous); + && maintenance.isActive(MaintenanceScope.server(ServerId.of(previous))); if (redirectedFromMaintained || evacuatingMaintained) { return AdmissionIntent.MAINTENANCE_EVACUATION; } } - boolean restartReturn = FeatureServices.find(feature, RestartAdmissionAPI.class) - .map(api -> api.isRestartReturn(player.getUniqueId(), target)) + boolean restartReturn = FeatureServices.find(feature, RestartApi.class) + .map(api -> api.isExpectedReturn(player.getUniqueId(), ServerId.of(target))) .orElse(false); if (restartReturn) return AdmissionIntent.RESTART_RETURN; - TwoFactor twoFactor = twoFactor(); + TwoFactorApi twoFactor = FeatureServices.find(feature, TwoFactorApi.class).orElse(null); boolean twoFactorRoute = previous.isBlank() && !original.isBlank() && !original.equals(target) && twoFactor != null - && twoFactor.getService() != null - && twoFactor.getService().isLocked(player) - && twoFactor.isLockServer(target); + && twoFactor.isLocked(player.getUniqueId()) + && twoFactor.isAuthenticationServer(ServerId.of(target)); return twoFactorRoute ? AdmissionIntent.SECURITY_ROUTE : AdmissionIntent.NORMAL; } - private boolean hasMaintenanceBypass(Player player, String target) { - Maintenance maintenance = maintenance(); - return maintenance != null && maintenance.getHandler() != null - && maintenance.getHandler().hasGamemodeBypass(player, target); - } - - private Maintenance maintenance() { - var loaded = feature.getPlugin().getFeatureLoadManager().getFeatureRegistry() - .getLoadedFeature("Maintenance"); - return loaded instanceof Maintenance maintenance ? maintenance : null; - } - - private TwoFactor twoFactor() { - var loaded = feature.getPlugin().getFeatureLoadManager().getFeatureRegistry() - .getLoadedFeature("TwoFactor"); - return loaded instanceof TwoFactor twoFactor ? twoFactor : null; - } - private boolean enqueue(Player player, String serverName, CapacityDenialReason reason, CapacityRequest request) { - return FeatureServices.find(feature, QueueAdmissionAPI.class) - .filter(api -> api.isQueueEnabled(serverName)) - .map(api -> api.enqueue(player, serverName, reason, request)) + ServerId server = ServerId.of(serverName); + return InternalServices.find(feature, QueueAdmissionPort.class) + .map(api -> api.enqueueDenied(player, server, reason, request)) .orElse(false); } private boolean consumeCancelledQueueAdvance(Player player, String serverName) { - return FeatureServices.find(feature, QueueAdmissionAPI.class) - .map(api -> api.consumeCancelledAdvance(player.getUniqueId(), serverName)) + return InternalServices.find(feature, QueueAdmissionPort.class) + .map(api -> api.consumeCancelledAdvance(player.getUniqueId(), ServerId.of(serverName))) .orElse(false); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/meta/Meta.java index bac9c0ee..bc454550 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.capacity.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public final class Meta implements BaseMeta { @Override diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/ClientInfo.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/ClientInfo.java index ecf596b1..3399649e 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/ClientInfo.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/ClientInfo.java @@ -2,8 +2,8 @@ import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.clientinfo.command.ClientInfoBrigadierCommand; import nl.hauntedmc.proxyfeatures.features.clientinfo.entity.PlayerClientInfoChannelEntity; @@ -180,10 +180,7 @@ public void initialize() { PlayerClientInfoChannelEntity.class) .orElseThrow(); - PlayerReferenceResolver playerResolver = new PlayerReferenceResolver( - getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for ClientInfo.")) - ); + PlayerReferenceResolver playerResolver = getPlugin().getPlayerReferenceResolver(); ClientInfoSettingsService settingsService = new ClientInfoSettingsService(ormContext, playerResolver); ClientTelemetryService telemetryService = new ClientTelemetryService(); ClientInfoPersistenceService persistenceService = new ClientInfoPersistenceService( diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/command/ClientInfoBrigadierCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/command/ClientInfoBrigadierCommand.java index 28a89b10..b0b197d4 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/command/ClientInfoBrigadierCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/command/ClientInfoBrigadierCommand.java @@ -9,7 +9,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; import nl.hauntedmc.proxyfeatures.features.clientinfo.ClientInfo; import nl.hauntedmc.proxyfeatures.features.clientinfo.internal.ClientInfoAdvisor; import org.jetbrains.annotations.NotNull; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoConfig.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoConfig.java index 705351d3..b02609e5 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoConfig.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoConfig.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.clientinfo.internal; import com.velocitypowered.api.proxy.player.PlayerSettings; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigView; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigView; import java.util.ArrayList; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/meta/Meta.java index aa07c193..4e7eebcb 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.clientinfo.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/CommandHider.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/CommandHider.java index aabfc2e2..af842369 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/CommandHider.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/CommandHider.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.commandhider; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.commandhider.command.CommandHiderCommand; import nl.hauntedmc.proxyfeatures.features.commandhider.internal.HiderHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/command/CommandHiderCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/command/CommandHiderCommand.java index aa6afbbe..423311c7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/command/CommandHiderCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/command/CommandHiderCommand.java @@ -7,7 +7,7 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.LiteralCommandNode; import com.velocitypowered.api.command.CommandSource; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; import nl.hauntedmc.proxyfeatures.features.commandhider.CommandHider; import nl.hauntedmc.proxyfeatures.features.commandhider.internal.HiderHandler; import org.jetbrains.annotations.NotNull; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/meta/Meta.java index c903d0c5..8b36e3db 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.commandhider.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/CommandLogger.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/CommandLogger.java index 2f3aac62..272886df 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/CommandLogger.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/CommandLogger.java @@ -2,8 +2,8 @@ import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.commandlogger.entity.CommandExecutionEntity; import nl.hauntedmc.proxyfeatures.features.commandlogger.internal.LogHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/meta/Meta.java index ff69fbb8..46139316 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.commandlogger.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogService.java index 2d2b4447..8a8aa496 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogService.java @@ -2,9 +2,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; import nl.hauntedmc.proxyfeatures.features.commandlogger.CommandLogger; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import nl.hauntedmc.proxyfeatures.features.commandlogger.entity.CommandExecutionEntity; @@ -19,18 +17,12 @@ public class CommandLogService { private final PlayerReferenceResolver playerResolver; public CommandLogService(CommandLogger feature) { - this(feature, feature.getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for CommandLogger."))); + this(feature, feature.getPlugin().getPlayerReferenceResolver()); } - CommandLogService(CommandLogger feature, DataRegistryApi dataRegistry) { + CommandLogService(CommandLogger feature, PlayerReferenceResolver playerResolver) { this.feature = feature; - this.playerResolver = new PlayerReferenceResolver(dataRegistry); - } - - CommandLogService(CommandLogger feature, PlayerDirectory playerDirectory) { - this.feature = feature; - this.playerResolver = new PlayerReferenceResolver(playerDirectory); + this.playerResolver = playerResolver; } /** diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/CommandRelay.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/CommandRelay.java index 8a9167e1..8ddb5840 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/CommandRelay.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/CommandRelay.java @@ -7,8 +7,8 @@ import nl.hauntedmc.proxyfeatures.features.commandrelay.audit.CommandRelayAuditLogEntity; import nl.hauntedmc.proxyfeatures.features.commandrelay.audit.CommandRelayAuditLogService; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.commandrelay.internal.EventBusHandler; import nl.hauntedmc.proxyfeatures.features.commandrelay.meta.Meta; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandler.java index 9379b1f2..70996dd3 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandler.java @@ -8,10 +8,10 @@ import nl.hauntedmc.dataprovider.database.messaging.durable.DurableMessagingDataAccess; import nl.hauntedmc.dataprovider.database.messaging.durable.DurableSubscription; import nl.hauntedmc.dataprovider.database.messaging.durable.PublishedDurableEvent; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheDirectory; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheType; -import nl.hauntedmc.proxyfeatures.api.io.cache.FileCacheStore; -import nl.hauntedmc.proxyfeatures.api.util.type.CastUtils; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheDirectory; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheType; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.FileCacheStore; +import nl.hauntedmc.proxyfeatures.toolkit.util.type.CastUtils; import nl.hauntedmc.proxyfeatures.contracts.messaging.CommandRelayMessage; import nl.hauntedmc.proxyfeatures.features.commandrelay.CommandRelay; import nl.hauntedmc.proxyfeatures.features.commandrelay.audit.CommandRelayAuditLogService; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/ProcessedCommandLedger.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/ProcessedCommandLedger.java index 330f91a7..bb12a717 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/ProcessedCommandLedger.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/ProcessedCommandLedger.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.commandrelay.internal; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheValue; -import nl.hauntedmc.proxyfeatures.api.io.cache.FileCacheStore; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheValue; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.FileCacheStore; import java.util.Objects; import java.util.Set; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/meta/Meta.java index 1f749e34..41da4415 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.commandrelay.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/ConnectionInfo.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/ConnectionInfo.java index dd06c54d..d6a34cab 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/ConnectionInfo.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/ConnectionInfo.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.connectioninfo; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.connectioninfo.command.ConnectionInfoCommand; import nl.hauntedmc.proxyfeatures.features.connectioninfo.command.PingCommand; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/command/ConnectionInfoCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/command/ConnectionInfoCommand.java index b6c8c6e0..a99b534d 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/command/ConnectionInfoCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/command/ConnectionInfoCommand.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.connectioninfo.ConnectionInfo; import java.net.InetSocketAddress; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/command/PingCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/command/PingCommand.java index 0541154e..3a125932 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/command/PingCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/command/PingCommand.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.connectioninfo.ConnectionInfo; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/meta/Meta.java index ad361232..20cb603d 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.connectioninfo.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/Friends.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/Friends.java index 6aed32b3..60c0d434 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/Friends.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/Friends.java @@ -1,9 +1,9 @@ package nl.hauntedmc.proxyfeatures.features.friends; import nl.hauntedmc.dataprovider.api.orm.ORMContext; -import nl.hauntedmc.proxyfeatures.api.friends.FriendshipApi; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.api.capability.social.FriendshipApi; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.friends.api.FriendshipApiImpl; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/api/FriendshipApiImpl.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/api/FriendshipApiImpl.java index e6c4d391..7c1f664d 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/api/FriendshipApiImpl.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/api/FriendshipApiImpl.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.friends.api; -import nl.hauntedmc.proxyfeatures.api.friends.FriendshipApi; +import nl.hauntedmc.proxyfeatures.api.capability.social.FriendshipApi; import nl.hauntedmc.proxyfeatures.features.friends.Friends; import nl.hauntedmc.proxyfeatures.features.friends.entity.FriendStatus; import nl.hauntedmc.proxyfeatures.features.friends.entity.FriendsService; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/AsyncFriendCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/AsyncFriendCommand.java index 8dd56431..5a8abd75 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/AsyncFriendCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/AsyncFriendCommand.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.friends.Friends; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/FriendCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/FriendCommand.java index 7d744641..ecd4d3d0 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/FriendCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/FriendCommand.java @@ -5,14 +5,14 @@ import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; -import nl.hauntedmc.proxyfeatures.api.util.tools.Paginator; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.util.tools.Paginator; import nl.hauntedmc.proxyfeatures.features.friends.Friends; import nl.hauntedmc.proxyfeatures.features.friends.entity.FriendStatus; import nl.hauntedmc.proxyfeatures.features.friends.entity.FriendsService; import nl.hauntedmc.proxyfeatures.features.friends.entity.PlayerRef; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.*; @@ -643,8 +643,8 @@ private PlayerRef resolvePlayerRef(String name) { } private boolean notVanished(Player pl) { - return FeatureServices.find(feature, VanishAPI.class) - .map(api -> !api.isVanished(pl.getUniqueId())) + return FeatureServices.find(feature, PresenceApi.class) + .map(api -> !api.isHidden(pl.getUniqueId())) .orElse(true); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsService.java index a28f7979..1f5e2387 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsService.java @@ -1,9 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.friends.entity; import jakarta.persistence.PersistenceException; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import nl.hauntedmc.proxyfeatures.features.friends.Friends; import nl.hauntedmc.proxyfeatures.features.friends.support.FriendsCache; @@ -20,20 +18,13 @@ public class FriendsService { private final PlayerReferenceResolver playerResolver; public FriendsService(Friends feature, FriendsCache cache) { - this(feature, cache, feature.getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for Friends."))); + this(feature, cache, feature.getPlugin().getPlayerReferenceResolver()); } - FriendsService(Friends feature, FriendsCache cache, DataRegistryApi dataRegistry) { + FriendsService(Friends feature, FriendsCache cache, PlayerReferenceResolver playerResolver) { this.feature = feature; this.cache = cache; - this.playerResolver = new PlayerReferenceResolver(dataRegistry); - } - - FriendsService(Friends feature, FriendsCache cache, PlayerDirectory playerDirectory) { - this.feature = feature; - this.cache = cache; - this.playerResolver = new PlayerReferenceResolver(playerDirectory); + this.playerResolver = playerResolver; } // -------- Player lookups (cached, lightweight) -------- diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/listener/FriendActivityListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/listener/FriendActivityListener.java index b1bd53da..9c166d46 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/listener/FriendActivityListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/listener/FriendActivityListener.java @@ -9,8 +9,8 @@ import nl.hauntedmc.proxyfeatures.features.friends.entity.FriendSnapshot; import nl.hauntedmc.proxyfeatures.features.friends.entity.FriendsService; import nl.hauntedmc.proxyfeatures.features.friends.entity.PlayerRef; -import nl.hauntedmc.proxyfeatures.features.vanish.event.VanishStateChangeEvent; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceChangedEvent; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.time.Duration; @@ -114,13 +114,13 @@ public void onDisconnect(DisconnectEvent event) { } @Subscribe - public void onVanishStateChange(VanishStateChangeEvent event) { - Optional currentVanishState = vanishState(event.playerUuid()); - if (currentVanishState.isEmpty() || currentVanishState.get() != event.vanished()) { + public void onPresenceChanged(PresenceChangedEvent event) { + Optional currentVanishState = vanishState(event.playerId()); + if (currentVanishState.isEmpty() || currentVanishState.get() != event.hidden()) { return; } - Optional subjectOptional = feature.getPlugin().getProxy().getPlayer(event.playerUuid()); + Optional subjectOptional = feature.getPlugin().getProxy().getPlayer(event.playerId()); if (subjectOptional.isEmpty()) { return; } @@ -130,8 +130,8 @@ public void onVanishStateChange(VanishStateChangeEvent event) { .map(current -> current.getServerInfo().getName()) .orElse(null); FriendPresenceTracker.Plan plan = presenceTracker.applyVanishState( - event.playerUuid(), - event.vanished(), + event.playerId(), + event.hidden(), currentServer ); if (!announceVanishStateChanges) { @@ -143,11 +143,11 @@ public void onVanishStateChange(VanishStateChangeEvent event) { : event.playerName(); if (plan.type() == FriendPresenceTracker.NotificationType.VANISH_OFFLINE) { feature.getLifecycleManager().getTaskManager().scheduleTask(() -> - notifyFriendsOffline(event.playerUuid(), username, plan.generation())); + notifyFriendsOffline(event.playerId(), username, plan.generation())); } else if (plan.type() == FriendPresenceTracker.NotificationType.VANISH_ONLINE) { feature.getLifecycleManager().getTaskManager().scheduleTask(() -> notifyFriendsOnline( - event.playerUuid(), + event.playerId(), username, plan.to(), plan.generation() @@ -288,8 +288,8 @@ private boolean isVanished(Player player) { private Optional vanishState(UUID playerUuid) { try { - return FeatureServices.find(feature, VanishAPI.class) - .map(api -> api.isVanished(playerUuid)); + return FeatureServices.find(feature, PresenceApi.class) + .map(api -> api.isHidden(playerUuid)); } catch (RuntimeException ignored) { return Optional.empty(); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/meta/Meta.java index 25eb6091..3b514076 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.friends.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/HLink.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/HLink.java index ec89140b..872f360e 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/HLink.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/HLink.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.hlink; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.hlink.command.HLinkCommand; import nl.hauntedmc.proxyfeatures.features.hlink.command.LinkCommand; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/HLinkCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/HLinkCommand.java index 83a6e0fd..3ee4254a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/HLinkCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/HLinkCommand.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.hlink.HLink; import nl.hauntedmc.proxyfeatures.features.hlink.internal.HLinkHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/LinkCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/LinkCommand.java index c4cc6b6c..38ec03ec 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/LinkCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/LinkCommand.java @@ -4,7 +4,7 @@ import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.hlink.HLink; import nl.hauntedmc.proxyfeatures.features.hlink.internal.HLinkHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/RegisterCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/RegisterCommand.java index 158ece6b..aa93b379 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/RegisterCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/command/RegisterCommand.java @@ -4,7 +4,7 @@ import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.hlink.HLink; import nl.hauntedmc.proxyfeatures.features.hlink.internal.HLinkHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/internal/HLinkHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/internal/HLinkHandler.java index 6964069f..2d3521cf 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/internal/HLinkHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/internal/HLinkHandler.java @@ -10,8 +10,8 @@ import net.luckperms.api.node.types.InheritanceNode; import net.luckperms.api.track.Track; import net.luckperms.api.track.TrackManager; -import nl.hauntedmc.proxyfeatures.api.util.http.SimpleHttpClient; -import nl.hauntedmc.proxyfeatures.api.util.http.SimpleHttpClient.FormParameter; +import nl.hauntedmc.proxyfeatures.toolkit.util.http.SimpleHttpClient; +import nl.hauntedmc.proxyfeatures.toolkit.util.http.SimpleHttpClient.FormParameter; import nl.hauntedmc.proxyfeatures.features.hlink.HLink; import nl.hauntedmc.proxyfeatures.features.hlink.internal.api.AccountRequest; import nl.hauntedmc.proxyfeatures.features.hlink.internal.api.LinkRequest; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/meta/Meta.java index 4402cd5d..061804c2 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.hlink.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/Hub.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/Hub.java index b39daf2b..281e17c7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/Hub.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/Hub.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.hub; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.hub.command.HubCommand; import nl.hauntedmc.proxyfeatures.features.hub.meta.Meta; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/command/HubCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/command/HubCommand.java index d763f37a..ca785242 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/command/HubCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/command/HubCommand.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.hub.Hub; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/meta/Meta.java index e6bbd310..a6982906 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.hub.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/Maintenance.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/Maintenance.java index 6b4e64ed..bdd6b789 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/Maintenance.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/Maintenance.java @@ -1,14 +1,18 @@ package nl.hauntedmc.proxyfeatures.features.maintenance; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.maintenance.command.MaintenanceCommand; import nl.hauntedmc.proxyfeatures.features.maintenance.internal.MaintenanceHandler; import nl.hauntedmc.proxyfeatures.features.maintenance.listener.MaintenanceConnectionListener; import nl.hauntedmc.proxyfeatures.features.maintenance.meta.Meta; -import nl.hauntedmc.proxyfeatures.features.motd.internal.MotdLine2OverrideRegistry; +import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceApi; +import nl.hauntedmc.proxyfeatures.api.extension.ExtensionRegistration; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContribution; +import nl.hauntedmc.proxyfeatures.api.extension.MotdExtensions; +import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.ArrayList; import java.util.List; @@ -18,6 +22,7 @@ public class Maintenance extends VelocityBaseFeature { private static final String MOTD_OVERRIDE_KEY = "maintenance"; private MaintenanceHandler handler; + private ExtensionRegistration motdRegistration; public Maintenance(FeatureContext context) { super(context); @@ -112,14 +117,23 @@ public MessageMap getDefaultMessages() { @Override public void initialize() { this.handler = new MaintenanceHandler(this); - MotdLine2OverrideRegistry.register(MOTD_OVERRIDE_KEY, 100, handler::resolveMotdLine2Override); + getLifecycleManager().getApiManager().registerService(MaintenanceApi.class, handler); + motdRegistration = FeatureServices.require(this, MotdExtensions.class).register( + MOTD_OVERRIDE_KEY, + 100, + context -> java.util.Optional.ofNullable(handler.resolveMotdLine2Override()) + .map(MotdContribution::secondLine) + ); getLifecycleManager().getCommandManager().registerFeatureCommand(new MaintenanceCommand(this)); getLifecycleManager().getListenerManager().registerListener(new MaintenanceConnectionListener(this)); } @Override public void disable() { - MotdLine2OverrideRegistry.unregister(MOTD_OVERRIDE_KEY); + if (motdRegistration != null) { + motdRegistration.close(); + motdRegistration = null; + } if (handler != null) { handler.shutdown(); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/command/MaintenanceCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/command/MaintenanceCommand.java index 06897472..c102e58b 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/command/MaintenanceCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/command/MaintenanceCommand.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.maintenance.command; import com.velocitypowered.api.command.CommandSource; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.maintenance.Maintenance; import nl.hauntedmc.proxyfeatures.features.maintenance.internal.MaintenanceHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/internal/MaintenanceHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/internal/MaintenanceHandler.java index f9553a39..c574d48e 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/internal/MaintenanceHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/internal/MaintenanceHandler.java @@ -5,6 +5,10 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; import net.kyori.adventure.title.Title; +import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceApi; +import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceScope; +import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceSnapshot; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; import nl.hauntedmc.proxyfeatures.features.maintenance.Maintenance; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; @@ -18,11 +22,12 @@ import java.util.Locale; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -public final class MaintenanceHandler { +public final class MaintenanceHandler implements MaintenanceApi { public enum ToggleResult { ENABLED, @@ -158,6 +163,32 @@ public boolean isGlobalActive() { return globalActive.get(); } + @Override + public boolean isActive(MaintenanceScope scope) { + if (scope.isGlobal()) { + return isGlobalActive(); + } + return scope.server().map(server -> isGamemodeActive(server.value())).orElse(false); + } + + @Override + public boolean mayBypass(UUID playerId, MaintenanceScope scope) { + if (playerId == null) { + return false; + } + return proxy.getPlayer(playerId).map(player -> scope.server() + .map(server -> hasGamemodeBypass(player, server.value())) + .orElseGet(() -> hasGlobalBypass(player))).orElse(false); + } + + @Override + public MaintenanceSnapshot snapshot() { + Set servers = activeGamemodes.stream() + .map(ServerId::of) + .collect(java.util.stream.Collectors.toUnmodifiableSet()); + return new MaintenanceSnapshot(isGlobalActive(), servers, java.time.Instant.now()); + } + public boolean isGlobalCountdownRunning() { return globalCountdownRunning.get(); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListener.java index 018249b6..0c68fabd 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListener.java @@ -6,9 +6,11 @@ import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; +import nl.hauntedmc.proxyfeatures.api.capability.operations.TwoFactorApi; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; import nl.hauntedmc.proxyfeatures.features.maintenance.Maintenance; -import nl.hauntedmc.proxyfeatures.features.twofactor.TwoFactor; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; +import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; public class MaintenanceConnectionListener { @@ -105,15 +107,10 @@ private boolean isSecurityRoute(ServerPreConnectEvent event, RegisteredServer ta return false; } - var loaded = feature.getPlugin().getFeatureLoadManager().getFeatureRegistry() - .getLoadedFeature("TwoFactor"); - if (!(loaded instanceof TwoFactor twoFactor)) { - return false; - } - var service = twoFactor.getService(); - return service != null - && service.isLocked(event.getPlayer()) - && twoFactor.isLockServer(targetName); + return FeatureServices.find(feature, TwoFactorApi.class) + .map(api -> api.isLocked(event.getPlayer().getUniqueId()) + && api.isAuthenticationServer(ServerId.of(targetName))) + .orElse(false); } @Subscribe diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/meta/Meta.java index b93c11d2..7e6555e9 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.maintenance.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/Messenger.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/Messenger.java index d9e4ff62..07bbae93 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/Messenger.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/Messenger.java @@ -1,9 +1,9 @@ package nl.hauntedmc.proxyfeatures.features.messager; import nl.hauntedmc.dataprovider.api.orm.ORMContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.messager.command.MessagingCommand; @@ -76,10 +76,7 @@ public void initialize() { PlayerMessageSettingsEntity.class, PlayerMessageLogEntity.class ).orElseThrow(); - PlayerReferenceResolver playerResolver = new PlayerReferenceResolver( - getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for Messenger.")) - ); + PlayerReferenceResolver playerResolver = getPlugin().getPlayerReferenceResolver(); this.messageHistoryLogService = new PlayerMessageHistoryLogService(getLogger(), ormContext, playerResolver); this.handler = new MessagingHandler(this); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingCommand.java index 913ad914..348c0434 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingCommand.java @@ -3,8 +3,8 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.internal.MessagingHandler; import nl.hauntedmc.proxyfeatures.features.messager.internal.MessengerTargetVisibility; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/command/ReplyCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/command/ReplyCommand.java index 865960a9..9f340699 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/command/ReplyCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/command/ReplyCommand.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.internal.MessagingHandler; import nl.hauntedmc.proxyfeatures.features.messager.internal.MessengerTargetVisibility; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/MessageModeConverter.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/MessageModeConverter.java index 54837357..02686f3c 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/MessageModeConverter.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/MessageModeConverter.java @@ -2,7 +2,7 @@ import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; /** * Persists message modes by stable name. diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntity.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntity.java index caf7e8c9..4c7468d4 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntity.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntity.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.messager.entity; import jakarta.persistence.*; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import java.util.HashSet; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagePrivacyPolicy.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagePrivacyPolicy.java index b3703e80..2a3c5ab7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagePrivacyPolicy.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagePrivacyPolicy.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.messager.internal; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import java.util.Objects; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandler.java index 57a5aea4..c2b86ac9 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandler.java @@ -1,9 +1,9 @@ package nl.hauntedmc.proxyfeatures.features.messager.internal; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.friends.FriendshipApi; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; -import nl.hauntedmc.proxyfeatures.api.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.api.capability.social.FriendshipApi; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.entity.PlayerMessageSettingsEntity; import nl.hauntedmc.proxyfeatures.features.messager.history.PlayerMessageHistoryLogService; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsService.java index fed8adc1..b41714fd 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsService.java @@ -1,8 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.messager.internal; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; -import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.entity.PlayerMessageSettingsEntity; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; @@ -19,18 +17,13 @@ public class MessagingSettingsService { private final PlayerReferenceResolver playerResolver; public MessagingSettingsService(Messenger feature) { - this(feature, feature.getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for Messenger."))); - } - - MessagingSettingsService(Messenger feature, DataRegistryApi dataRegistry) { this.feature = feature; - this.playerResolver = new PlayerReferenceResolver(dataRegistry); + this.playerResolver = feature.getPlugin().getPlayerReferenceResolver(); } - MessagingSettingsService(Messenger feature, PlayerDirectory playerDirectory) { + MessagingSettingsService(Messenger feature, PlayerReferenceResolver playerResolver) { this.feature = feature; - this.playerResolver = new PlayerReferenceResolver(playerDirectory); + this.playerResolver = playerResolver; } /** diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibility.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibility.java index a1859389..a398fb69 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibility.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibility.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.messager.internal; import com.velocitypowered.api.proxy.Player; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.List; @@ -48,9 +48,10 @@ public static List list(Messenger feature, Player viewer) { return List.copyOf(feature.getPlugin().getProxy().getAllPlayers()); } try { - return FeatureServices.find(feature, VanishAPI.class) - .map(VanishAPI::getAdjustedOnlinePlayers) - .map(List::copyOf) + return FeatureServices.find(feature, PresenceApi.class) + .map(api -> feature.getPlugin().getProxy().getAllPlayers().stream() + .filter(player -> !api.isHidden(player.getUniqueId())) + .toList()) .orElseGet(() -> List.copyOf(feature.getPlugin().getProxy().getAllPlayers())); } catch (RuntimeException failure) { feature.getPlugin().getLogger() @@ -64,8 +65,8 @@ private static boolean isVisible(Messenger feature, Player viewer, Player target return true; } try { - return FeatureServices.find(feature, VanishAPI.class) - .map(api -> !api.isVanished(target.getUniqueId())) + return FeatureServices.find(feature, PresenceApi.class) + .map(api -> !api.isHidden(target.getUniqueId())) .orElse(true); } catch (RuntimeException failure) { feature.getPlugin().getLogger() diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/meta/Meta.java index 5c6dd9e0..0db0d96d 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.messager.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/messaging/MessageMode.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/model/MessageMode.java similarity index 94% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/messaging/MessageMode.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/model/MessageMode.java index c807abc8..e4cce0c2 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/messaging/MessageMode.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/model/MessageMode.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.messaging; +package nl.hauntedmc.proxyfeatures.features.messager.model; import java.util.Locale; import java.util.Optional; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/Motd.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/Motd.java index 2bf4628d..4a3e6784 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/Motd.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/Motd.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.motd; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.motd.internal.MotdHandler; import nl.hauntedmc.proxyfeatures.features.motd.listener.PingListener; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandler.java index 1029e35a..08839c8a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandler.java @@ -3,12 +3,13 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.velocitypowered.api.proxy.server.ServerPing; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContext; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.util.text.format.ComponentFormatter; -import nl.hauntedmc.proxyfeatures.api.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; import nl.hauntedmc.proxyfeatures.features.motd.Motd; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; -import nl.hauntedmc.proxyfeatures.features.versioncheck.VersionCheck; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.api.capability.operations.VersionApi; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.ArrayList; @@ -52,14 +53,14 @@ public MotdHandler(Motd feature) { .build(); } - public ServerPing modifyServerPing(ServerPing unmodifiedPing) { - return createNewServerPing(unmodifiedPing); + public ServerPing modifyServerPing(ServerPing unmodifiedPing, MotdContext context) { + return createNewServerPing(unmodifiedPing, context); } - private ServerPing createNewServerPing(ServerPing unmodifiedPing) { + private ServerPing createNewServerPing(ServerPing unmodifiedPing, MotdContext context) { PlayerCountSnapshot playerCounts = getPlayerCountSnapshot(unmodifiedPing); ServerPing.Version version = getDisplayedVersion(unmodifiedPing); - Component motd = getMotd(playerCounts, version); + Component motd = getMotd(playerCounts, version, context); return new ServerPing(version, playerCounts.adjustedPlayers(), @@ -70,14 +71,12 @@ private ServerPing createNewServerPing(ServerPing unmodifiedPing) { private ServerPing.Version getDisplayedVersion(ServerPing unmodifiedPing) { ServerPing.Version version = unmodifiedPing.getVersion(); - if (feature.getPlugin().getFeatureLoadManager().getFeatureRegistry().isFeatureLoaded("VersionCheck")) { - VersionCheck versionCheck = (VersionCheck) feature.getPlugin() - .getFeatureLoadManager() - .getFeatureRegistry() - .getLoadedFeature("VersionCheck"); - if (versionCheck.getVersionHandler().isUnsupportedVersion(unmodifiedPing.getVersion().getProtocol())) { - int minProtocol = versionCheck.getVersionHandler().getMinimumProtcolVersion(); - String friendlyName = versionCheck.getVersionHandler().getFriendlyProtocolName() + "+"; + Optional versionApi = FeatureServices.find(feature, VersionApi.class); + if (versionApi.isPresent()) { + VersionApi supported = versionApi.get(); + if (!supported.isSupported(unmodifiedPing.getVersion().getProtocol())) { + int minProtocol = supported.minimumProtocolVersion(); + String friendlyName = supported.minimumVersionName() + "+"; version = new ServerPing.Version(minProtocol, friendlyName); } } @@ -95,8 +94,9 @@ private PlayerCountSnapshot getPlayerCountSnapshot(ServerPing unmodifiedPing) { int onlinePlayers = players.map(ServerPing.Players::getOnline).orElse(0); int maxPlayers = players.map(ServerPing.Players::getMax).orElse(onlinePlayers); List sample = players.map(ServerPing.Players::getSample).orElse(List.of()); - int vanishedPlayers = FeatureServices.find(feature, VanishAPI.class) - .map(VanishAPI::getVanishedCount) + int vanishedPlayers = FeatureServices.find(feature, PresenceApi.class) + .map(PresenceApi::snapshot) + .map(snapshot -> snapshot.hiddenCount()) .orElse(0); int visiblePlayers = Math.max(0, onlinePlayers - vanishedPlayers); @@ -115,9 +115,9 @@ private double readPlayerCountMultiplier() { return feature.getConfigHandler().get("player_count_multiplier", Double.class, 1.0D); } - private Component getMotd(PlayerCountSnapshot playerCounts, ServerPing.Version version) { + private Component getMotd(PlayerCountSnapshot playerCounts, ServerPing.Version version, MotdContext context) { MotdConfig config = configCache.get(TEMPLATE_CACHE_KEY, ignored -> MotdConfig.load(feature)); - String renderedMotd = renderMotd(config, playerCounts, version); + String renderedMotd = renderMotd(config, playerCounts, version, context); return motdCache.get(renderedMotd, this::buildComponent); } @@ -127,9 +127,16 @@ public void invalidateCache() { rotatingMessageCursor.set(0); } - private String renderMotd(MotdConfig config, PlayerCountSnapshot playerCounts, ServerPing.Version version) { + private String renderMotd( + MotdConfig config, + PlayerCountSnapshot playerCounts, + ServerPing.Version version, + MotdContext context + ) { String line1 = applyPlaceholders(config.line1(), playerCounts, version); - String line2Template = MotdLine2OverrideRegistry.resolve().orElseGet(() -> resolveLine2(config)); + String line2Template = feature.getPlugin().getMotdExtensions().resolve(context) + .flatMap(contribution -> contribution.secondLine()) + .orElseGet(() -> resolveLine2(config)); String line2 = applyPlaceholders(line2Template, playerCounts, version); return line1 + "\n" + line2; } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdLine2OverrideRegistry.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdLine2OverrideRegistry.java deleted file mode 100644 index 91065527..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdLine2OverrideRegistry.java +++ /dev/null @@ -1,75 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.motd.internal; - -import java.util.Comparator; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; - -/** - * Simple global registry for runtime line-2 MOTD overrides. - * - *

This keeps features such as Maintenance decoupled from Motd load order. - * Providers return a raw line-2 template, or {@code null} when they do not want - * to override the MOTD for the current moment. - * - *

If multiple features register an override at the same time, the entry with the - * highest priority wins. Ties are resolved deterministically by registry key. - */ -public final class MotdLine2OverrideRegistry { - - private static final Map ENTRIES = new ConcurrentHashMap<>(); - - private MotdLine2OverrideRegistry() { - } - - public static void register(String key, int priority, Supplier supplier) { - if (key == null || key.isBlank() || supplier == null) { - return; - } - ENTRIES.put(key, new Entry(priority, supplier)); - } - - public static void unregister(String key) { - if (key == null || key.isBlank()) { - return; - } - ENTRIES.remove(key); - } - - public static Optional resolve() { - return resolveRegistration().map(ResolvedOverride::value); - } - - public static Optional resolveRegistration() { - return ENTRIES.entrySet().stream() - .sorted(Comparator.>comparingInt(entry -> entry.getValue().priority()).reversed() - .thenComparing(Map.Entry::getKey)) - .map(entry -> { - String value = entry.getValue().supply(); - if (value == null || value.isBlank()) { - return null; - } - return new ResolvedOverride(entry.getKey(), entry.getValue().priority(), value); - }) - .filter(entry -> entry != null) - .findFirst(); - } - - static void clear() { - ENTRIES.clear(); - } - - public record ResolvedOverride(String key, int priority, String value) { - } - - private record Entry(int priority, Supplier supplier) { - private String supply() { - try { - return supplier.get(); - } catch (RuntimeException ignored) { - return null; - } - } - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/listener/PingListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/listener/PingListener.java index 25231d9f..7ffa2839 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/listener/PingListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/listener/PingListener.java @@ -3,6 +3,7 @@ import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.proxy.server.ServerPing; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContext; import nl.hauntedmc.proxyfeatures.features.motd.Motd; public class PingListener { @@ -15,7 +16,11 @@ public PingListener(Motd feature) { @Subscribe(priority = 10) public void onProxyPing(ProxyPingEvent event) { - ServerPing serverPing = feature.getMotdHandler().modifyServerPing(event.getPing()); + MotdContext context = new MotdContext( + event.getConnection().getRemoteAddress().getAddress(), + event.getConnection().getProtocolVersion().getProtocol() + ); + ServerPing serverPing = feature.getMotdHandler().modifyServerPing(event.getPing(), context); event.setPing(serverPing); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/meta/Meta.java index 44381627..6a761858 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.motd.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/PlayerCount.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/PlayerCount.java index 5473dae5..e8fb6af1 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/PlayerCount.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/PlayerCount.java @@ -1,14 +1,15 @@ package nl.hauntedmc.proxyfeatures.features.playercount; import nl.hauntedmc.dataprovider.database.messaging.MessagingDataAccess; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; -import nl.hauntedmc.proxyfeatures.features.playercount.internal.PlayerCountAPI; +import nl.hauntedmc.proxyfeatures.features.playercount.internal.PlayerCountService; import nl.hauntedmc.proxyfeatures.features.playercount.internal.PlayerCountPublisher; import nl.hauntedmc.proxyfeatures.features.playercount.meta.Meta; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCountApi; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.time.Duration; @@ -23,7 +24,7 @@ public final class PlayerCount extends VelocityBaseFeature { static final int DEFAULT_PUBLISH_INTERVAL_SECONDS = 2; static final String DEFAULT_PUBLISHER_ID = "proxy"; - private PlayerCountAPI api; + private PlayerCountService api; private PlayerCountPublisher publisher; public PlayerCount(FeatureContext context) { @@ -47,11 +48,11 @@ public MessageMap getDefaultMessages() { @Override public void initialize() { - api = new PlayerCountAPI( + api = new PlayerCountService( getPlugin().getProxy(), - () -> FeatureServices.find(this, VanishAPI.class) + () -> FeatureServices.find(this, PresenceApi.class) ); - getLifecycleManager().getApiManager().registerService(PlayerCountAPI.class, api); + getLifecycleManager().getApiManager().registerService(PlayerCountApi.class, api); Optional redisBus = registerRedisMessagingDataAccess( "redis", @@ -90,7 +91,7 @@ public void disable() { } } - public PlayerCountAPI getApi() { + public PlayerCountService getApi() { return api; } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountPublisher.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountPublisher.java index 09f2ef21..d8bc8f11 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountPublisher.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountPublisher.java @@ -2,6 +2,7 @@ import nl.hauntedmc.dataprovider.database.messaging.MessagingDataAccess; import nl.hauntedmc.proxyfeatures.contracts.messaging.PlayerCountSnapshotMessage; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCountSnapshot; import nl.hauntedmc.proxyfeatures.features.playercount.PlayerCount; import java.util.LinkedHashMap; @@ -22,7 +23,7 @@ public final class PlayerCountPublisher { private final PlayerCount feature; private final MessagingDataAccess redisBus; - private final PlayerCountAPI api; + private final PlayerCountService api; private final String channel; private final String publisherId; private final String publisherEpoch = UUID.randomUUID().toString(); @@ -34,7 +35,7 @@ public final class PlayerCountPublisher { public PlayerCountPublisher( PlayerCount feature, MessagingDataAccess redisBus, - PlayerCountAPI api, + PlayerCountService api, String channel, String publisherId ) { @@ -52,7 +53,7 @@ public void publishNow() { PlayerCountSnapshotMessage message; try { - PlayerCountSnapshot snapshot = api.capture(); + PlayerCountSnapshot snapshot = api.snapshot(); if (closed) { publishInFlight.set(false); return; @@ -104,8 +105,8 @@ public void close() { private PlayerCountSnapshotMessage toMessage(PlayerCountSnapshot snapshot, long nextSequence) { Map servers = new LinkedHashMap<>(); snapshot.servers().forEach((serverName, counts) -> servers.put( - serverName, - new PlayerCountSnapshotMessage.ServerCounts(counts.online(), counts.vanished()) + serverName.value(), + new PlayerCountSnapshotMessage.ServerCounts(counts.online(), counts.hidden()) )); return new PlayerCountSnapshotMessage( publisherId, @@ -113,7 +114,7 @@ private PlayerCountSnapshotMessage toMessage(PlayerCountSnapshot snapshot, long nextSequence, System.currentTimeMillis(), snapshot.network().online(), - snapshot.network().vanished(), + snapshot.network().hidden(), servers ); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountAPI.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountService.java similarity index 65% rename from proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountAPI.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountService.java index ec0d24fd..21b4d5a8 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountAPI.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountService.java @@ -2,33 +2,37 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCountApi; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCountSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCounts; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; import nl.hauntedmc.proxyfeatures.contracts.messaging.PlayerCountSnapshotMessage; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; -import java.util.Collection; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.time.Instant; import java.util.function.Supplier; /** * Reusable source of vanish-aware network and backend player counts. */ -public final class PlayerCountAPI { +public final class PlayerCountService implements PlayerCountApi { private final ProxyServer proxy; - private final Supplier> vanishApi; + private final Supplier> presenceApi; - public PlayerCountAPI(ProxyServer proxy, Supplier> vanishApi) { + public PlayerCountService(ProxyServer proxy, Supplier> presenceApi) { this.proxy = java.util.Objects.requireNonNull(proxy, "proxy"); - this.vanishApi = java.util.Objects.requireNonNull(vanishApi, "vanishApi"); + this.presenceApi = java.util.Objects.requireNonNull(presenceApi, "presenceApi"); } - public PlayerCountSnapshot capture() { + @Override + public PlayerCountSnapshot snapshot() { List players = List.copyOf(proxy.getAllPlayers()); Set vanishedPlayers = currentVanishedPlayers(); Map perServer = new LinkedHashMap<>(); @@ -64,34 +68,21 @@ public PlayerCountSnapshot capture() { }); } - Map immutableServers = new LinkedHashMap<>(); - perServer.forEach((serverName, counts) -> immutableServers.put(serverName, counts.snapshot())); + Map immutableServers = new LinkedHashMap<>(); + perServer.forEach((serverName, counts) -> immutableServers.put(ServerId.of(serverName), counts.snapshot())); return new PlayerCountSnapshot( - new PlayerCountSnapshot.Counts(players.size(), networkVanished), - immutableServers + new PlayerCounts(players.size(), networkVanished), + immutableServers, + Instant.now() ); } - public PlayerCountSnapshot.Counts getNetworkCounts() { - return capture().network(); - } - - public PlayerCountSnapshot.Counts getServerCounts(String serverName) { - return capture().server(serverName); - } - private Set currentVanishedPlayers() { - Optional current = vanishApi.get(); + Optional current = presenceApi.get(); if (current == null || current.isEmpty()) { return Set.of(); } - Collection vanished = current.get().getVanishedPlayers(); - if (vanished == null || vanished.isEmpty()) { - return Set.of(); - } - Set playerIds = new HashSet<>(); - vanished.forEach(player -> playerIds.add(player.getUniqueId())); - return Set.copyOf(playerIds); + return current.get().snapshot().hiddenPlayers(); } private static final class MutableCounts { @@ -105,8 +96,8 @@ private void increment(boolean isVanished) { } } - private PlayerCountSnapshot.Counts snapshot() { - return new PlayerCountSnapshot.Counts(online, vanished); + private PlayerCounts snapshot() { + return new PlayerCounts(online, vanished); } } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountSnapshot.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountSnapshot.java deleted file mode 100644 index 6239d31c..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountSnapshot.java +++ /dev/null @@ -1,61 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.playercount.internal; - -import nl.hauntedmc.proxyfeatures.contracts.messaging.PlayerCountSnapshotMessage; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; - -/** - * Immutable in-process view of current network and backend player counts. - */ -public record PlayerCountSnapshot( - Counts network, - Map servers -) { - - public PlayerCountSnapshot { - network = Objects.requireNonNull(network, "network"); - if (servers == null || servers.isEmpty()) { - servers = Map.of(); - } else { - Map normalized = new LinkedHashMap<>(); - servers.forEach((name, counts) -> { - String normalizedName = PlayerCountSnapshotMessage.normalizeServerName(name); - if (normalizedName.isEmpty()) { - throw new IllegalArgumentException("server name must not be blank"); - } - Counts normalizedCounts = Objects.requireNonNull(counts, "server counts"); - if (normalized.putIfAbsent(normalizedName, normalizedCounts) != null) { - throw new IllegalArgumentException( - "duplicate normalized server name: " + normalizedName - ); - } - }); - servers = Map.copyOf(normalized); - } - } - - public Counts server(String serverName) { - return servers.getOrDefault( - PlayerCountSnapshotMessage.normalizeServerName(serverName), - Counts.empty() - ); - } - - public record Counts(int online, int vanished) { - public Counts { - if (online < 0 || vanished < 0 || vanished > online) { - throw new IllegalArgumentException("invalid player counts"); - } - } - - public static Counts empty() { - return new Counts(0, 0); - } - - public int visible() { - return Math.max(0, online - vanished); - } - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/meta/Meta.java index 8f957f8b..25a0c06a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.playercount.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/PlayerInfo.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/PlayerInfo.java index bf6f7fb1..7643cd96 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/PlayerInfo.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/PlayerInfo.java @@ -1,18 +1,15 @@ package nl.hauntedmc.proxyfeatures.features.playerinfo; -import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.playerinfo.command.PlayerInfoCommand; import nl.hauntedmc.proxyfeatures.features.playerinfo.meta.Meta; import nl.hauntedmc.proxyfeatures.features.playerinfo.service.PlayerInfoService; -import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; public class PlayerInfo extends VelocityBaseFeature { - private ORMContext ormContext; private PlayerInfoService service; public PlayerInfo(FeatureContext context) { @@ -70,10 +67,6 @@ public MessageMap getDefaultMessages() { @Override public void initialize() { - ormContext = createPlayerOrmContext( - SanctionEntity.class - ).orElseThrow(); - service = new PlayerInfoService(this); // Command @@ -84,10 +77,6 @@ public void initialize() { public void disable() { } - public ORMContext getOrmContext() { - return ormContext; - } - public PlayerInfoService getService() { return service; } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/command/PlayerInfoCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/command/PlayerInfoCommand.java index 33e3a299..0b90d2c2 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/command/PlayerInfoCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/command/PlayerInfoCommand.java @@ -6,11 +6,11 @@ import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import nl.hauntedmc.dataregistry.api.player.PlayerNameHistoryEntry; import nl.hauntedmc.dataregistry.api.player.PlayerProfile; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionSnapshot; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; import nl.hauntedmc.proxyfeatures.features.playerinfo.PlayerInfo; import nl.hauntedmc.proxyfeatures.features.playerinfo.service.PlayerInfoService; -import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import java.util.List; import java.util.Locale; @@ -93,7 +93,7 @@ private void renderProfile( Optional languageOpt = svc.getLanguage(playerIdentity); // Active sanctions - List activeSanctions = svc.getActiveSanctions(playerIdentity); + var activeSanctionsStage = svc.getActiveSanctions(playerIdentity); // Header source.sendMessage(feature.getLocalizationHandler() @@ -158,9 +158,14 @@ private void renderProfile( } // Possible alts by last known IP (exclude self, A-Z) - svc.findUsernamesSharingLastIp(profile).whenComplete((altNames, altThrowable) -> + svc.findUsernamesSharingLastIp(profile) + .thenCombine(activeSanctionsStage, ProfileExtras::new) + .whenComplete((extras, extrasThrowable) -> feature.getLifecycleManager().getTaskManager().scheduleTask(() -> { - List safeAltNames = altThrowable == null && altNames != null ? altNames : List.of(); + List safeAltNames = extrasThrowable == null && extras != null + ? extras.altNames() : List.of(); + List activeSanctions = extrasThrowable == null && extras != null + ? extras.sanctions() : List.of(); if (safeAltNames.isEmpty()) { sendEntry(source, lblAlts, raw(source, "playerinfo.alts_none")); } else { @@ -172,14 +177,14 @@ private void renderProfile( } else { sendListHeader(source, lblPunishments); String permanentLabel = raw(source, "playerinfo.permanent"); - for (SanctionEntity s : activeSanctions) { - String expires = (s.getExpiresAt() == null) ? permanentLabel : svc.fmt(s.getExpiresAt()); - String created = svc.fmt(s.getCreatedAt()); + for (SanctionSnapshot sanction : activeSanctions) { + String expires = sanction.expiresAt().map(svc::fmt).orElse(permanentLabel); + String created = svc.fmt(sanction.createdAt()); source.sendMessage(feature.getLocalizationHandler() .getMessage("playerinfo.punishment_item") - .with("type", s.getType().name()) - .with("reason", s.getReason()) + .with("type", sanction.type().name()) + .with("reason", sanction.reason()) .with("expires", expires) .with("created", created) .forAudience(source) @@ -190,6 +195,9 @@ private void renderProfile( ); } + private record ProfileExtras(List altNames, List sanctions) { + } + private void sendList(CommandSource audience, String field, List values) { sendListHeader(audience, field); for (String value : values) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/meta/Meta.java index 09bad1f3..67c75508 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.playerinfo.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoService.java index 571b1bae..bfb63194 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoService.java @@ -7,9 +7,10 @@ import nl.hauntedmc.dataregistry.api.player.PlayerData; import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import nl.hauntedmc.dataregistry.api.player.PlayerProfile; -import nl.hauntedmc.proxyfeatures.features.playerlanguage.api.LanguageAPI; +import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionsApi; import nl.hauntedmc.proxyfeatures.features.playerinfo.PlayerInfo; -import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.time.Instant; @@ -92,32 +93,26 @@ public Optional getLanguage(PlayerIdentity player) { return Optional.empty(); } UUID uuid = player.uuid(); - return FeatureServices.find(feature, LanguageAPI.class) + return FeatureServices.find(feature, PlayerLanguageApi.class) .map(api -> { - var preference = api.getPreference(uuid); - if (preference == nl.hauntedmc.proxyfeatures.api.io.localization.Language.AUTO) { - return "AUTO (" + api.get(uuid).name() + ")"; + Optional preference = api.preference(uuid); + if (preference.isEmpty()) { + String effective = api.resolvedLanguage(uuid) + .map(java.util.Locale::toLanguageTag) + .orElse("unknown"); + return "AUTO (" + effective.toUpperCase(java.util.Locale.ROOT) + ")"; } - return preference.name(); + return preference.get().toLanguageTag().toUpperCase(java.util.Locale.ROOT); }); } - public List getActiveSanctions(PlayerIdentity player) { + public CompletionStage> getActiveSanctions(PlayerIdentity player) { if (player == null) { - return List.of(); + return CompletableFuture.completedFuture(List.of()); } - Instant now = Instant.now(); - return feature.getOrmContext().runInTransaction(session -> - session.createQuery( - "SELECT s FROM SanctionEntity s " + - "WHERE s.targetPlayerId = :playerId AND s.active = true " + - "AND (s.expiresAt IS NULL OR s.expiresAt > :now) " + - "ORDER BY s.createdAt DESC", - SanctionEntity.class) - .setParameter("playerId", player.playerId()) - .setParameter("now", now) - .getResultList() - ); + return FeatureServices.find(feature, SanctionsApi.class) + .map(api -> api.find(player.uuid(), true)) + .orElseGet(() -> CompletableFuture.completedFuture(List.of())); } public OnlineStatus getOnlineStatus(String nameOrUuid) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/PlayerLanguage.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/PlayerLanguage.java index 51300d77..b0b83786 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/PlayerLanguage.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/PlayerLanguage.java @@ -3,10 +3,10 @@ import nl.hauntedmc.dataregistry.api.DataRegistryApi; import nl.hauntedmc.dataregistry.api.DataRegistryFeature; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; -import nl.hauntedmc.proxyfeatures.features.playerlanguage.api.LanguageAPI; +import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; import nl.hauntedmc.proxyfeatures.features.playerlanguage.command.LanguageCommand; import nl.hauntedmc.proxyfeatures.features.playerlanguage.listener.LanguageListener; import nl.hauntedmc.proxyfeatures.features.playerlanguage.meta.Meta; @@ -69,7 +69,7 @@ public void initialize() { service = new LanguageService(this, dataRegistry); getLifecycleManager().getCommandManager().registerFeatureCommand(new LanguageCommand(this)); getLifecycleManager().getListenerManager().registerListener(new LanguageListener(this)); - getLifecycleManager().getApiManager().registerService(LanguageAPI.class, service); + getLifecycleManager().getApiManager().registerService(PlayerLanguageApi.class, service); } @Override diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/api/LanguageAPI.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/api/LanguageAPI.java deleted file mode 100644 index e56967b1..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/api/LanguageAPI.java +++ /dev/null @@ -1,13 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.playerlanguage.api; - -import nl.hauntedmc.proxyfeatures.api.io.localization.Language; - -import java.util.UUID; - -public interface LanguageAPI { - Language get(UUID playerUuid); - - Language getPreference(UUID playerUuid); - - void set(UUID playerUuid, Language language); -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommand.java index 631222e9..00619bf3 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommand.java @@ -2,8 +2,8 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.io.localization.Language; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; import nl.hauntedmc.proxyfeatures.features.playerlanguage.PlayerLanguage; import java.util.Arrays; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommandPolicy.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommandPolicy.java index 92dad1a5..a010ced0 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommandPolicy.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommandPolicy.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.playerlanguage.command; -import nl.hauntedmc.proxyfeatures.api.io.localization.Language; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; import java.util.ArrayList; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/meta/Meta.java index ebb71f4e..bc0a584f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.playerlanguage.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageService.java index 0a919816..6946d7f6 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageService.java @@ -4,10 +4,10 @@ import nl.hauntedmc.dataregistry.api.DataRegistryApi; import nl.hauntedmc.dataregistry.api.player.PlayerData; import nl.hauntedmc.dataregistry.api.player.PlayerLanguageSettings; -import nl.hauntedmc.proxyfeatures.api.io.localization.Language; -import nl.hauntedmc.proxyfeatures.features.antivpn.api.CountryAPI; +import nl.hauntedmc.proxyfeatures.api.capability.player.NetworkLocationApi; +import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; import nl.hauntedmc.proxyfeatures.features.playerlanguage.PlayerLanguage; -import nl.hauntedmc.proxyfeatures.features.playerlanguage.api.LanguageAPI; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.time.Duration; @@ -22,7 +22,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; -public final class LanguageService implements LanguageAPI { +public final class LanguageService implements PlayerLanguageApi { public record LanguageState(Language preference, Language effective) { } @@ -61,12 +61,10 @@ public void forget(UUID playerUuid) { stateCache.remove(playerUuid); } - @Override public Language get(UUID playerUuid) { return getState(playerUuid).effective(); } - @Override public Language getPreference(UUID playerUuid) { return getState(playerUuid).preference(); } @@ -75,7 +73,6 @@ public Language getEffectiveLanguage(UUID playerUuid) { return getState(playerUuid).effective(); } - @Override public void set(UUID playerUuid, Language language) { setAsync(playerUuid, language); } @@ -110,6 +107,27 @@ public CompletionStage setAsync(UUID playerUuid, Language language) { }); } + @Override + public Optional resolvedLanguage(UUID playerId) { + return Optional.of(toLocale(get(playerId))); + } + + @Override + public Optional preference(UUID playerId) { + Language preference = getPreference(playerId); + return preference == Language.AUTO ? Optional.empty() : Optional.of(toLocale(preference)); + } + + @Override + public CompletionStage setPreference(UUID playerId, Optional language) { + Language requested = language == null || language.isEmpty() + ? Language.AUTO + : fromLocale(language.get()); + return setAsync(playerId, requested).thenCompose(saved -> saved + ? CompletableFuture.completedFuture(null) + : CompletableFuture.failedFuture(new IllegalStateException("Language preference was not saved"))); + } + public Optional resolveUuidByName(String username) { if (username == null || username.isBlank()) { return Optional.empty(); @@ -244,8 +262,8 @@ private Language resolveEffectiveLanguage(UUID playerUuid, Language preference, } private String resolveCountryCode(UUID playerUuid) { - return FeatureServices.find(feature, CountryAPI.class) - .flatMap(countryApi -> countryApi.getCountry(playerUuid)) + return FeatureServices.find(feature, NetworkLocationApi.class) + .flatMap(countryApi -> countryApi.countryCode(playerUuid)) .orElse("UNKNOWN"); } @@ -320,6 +338,17 @@ private static String nullToEmpty(String value) { return value == null ? "" : value; } + private static Locale toLocale(Language language) { + return switch (language) { + case NL -> Locale.forLanguageTag("nl"); + case EN, AUTO -> Locale.ENGLISH; + }; + } + + private static Language fromLocale(Locale locale) { + return "nl".equalsIgnoreCase(locale.getLanguage()) ? Language.NL : Language.EN; + } + private static String rootMessage(Throwable throwable) { Throwable current = throwable; while (current.getCause() != null) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/PlayerList.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/PlayerList.java index aa4cd202..a3e661ef 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/PlayerList.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/PlayerList.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.playerlist; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.playerlist.command.GlobalListCommand; import nl.hauntedmc.proxyfeatures.features.playerlist.command.ListCommand; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/GlobalListCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/GlobalListCommand.java index cd809d69..f860efa7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/GlobalListCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/GlobalListCommand.java @@ -4,8 +4,8 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.type.CastUtils; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.type.CastUtils; import nl.hauntedmc.proxyfeatures.features.playerlist.PlayerList; import java.util.Collection; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/ListCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/ListCommand.java index 2d9fccd2..0aeb61e3 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/ListCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/ListCommand.java @@ -5,8 +5,8 @@ import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.type.CastUtils; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.type.CastUtils; import nl.hauntedmc.proxyfeatures.features.playerlist.PlayerList; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandler.java index 68e34d86..47ebbe4f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandler.java @@ -6,7 +6,7 @@ import net.kyori.adventure.text.JoinConfiguration; import net.kyori.adventure.text.TextComponent; import nl.hauntedmc.proxyfeatures.features.playerlist.PlayerList; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.*; @@ -184,13 +184,13 @@ public Component formatPlayerList(String serverName, Collection players, } private Predicate visiblePlayerPredicate() { - Optional vanishApi = FeatureServices.find(feature, VanishAPI.class); + Optional vanishApi = FeatureServices.find(feature, PresenceApi.class); if (vanishApi.isEmpty()) { return player -> true; } - VanishAPI api = vanishApi.get(); - return player -> !api.isVanished(player.getUniqueId()); + PresenceApi api = vanishApi.get(); + return player -> !api.isHidden(player.getUniqueId()); } private ServerView createServerView(RegisteredServer server, Predicate isVisible) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/meta/Meta.java index d6880a0e..c41355b7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.playerlist.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/ProxyInfo.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/ProxyInfo.java index 266d35b0..6f0f4cd4 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/ProxyInfo.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/ProxyInfo.java @@ -2,8 +2,8 @@ package nl.hauntedmc.proxyfeatures.features.proxyinfo; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.proxyinfo.command.ProxyInfoCommand; import nl.hauntedmc.proxyfeatures.features.proxyinfo.meta.Meta; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/command/ProxyInfoCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/command/ProxyInfoCommand.java index 0b774de6..8285c946 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/command/ProxyInfoCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/command/ProxyInfoCommand.java @@ -4,7 +4,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.server.RegisteredServer; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.proxyinfo.ProxyInfo; import java.lang.management.ManagementFactory; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/meta/Meta.java index 37a2c197..0a376c0e 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.proxyinfo.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/Queue.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/Queue.java index a2a069db..7c552dc5 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/Queue.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/Queue.java @@ -1,16 +1,16 @@ package nl.hauntedmc.proxyfeatures.features.queue; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; -import nl.hauntedmc.proxyfeatures.api.queue.QueueAdmissionAPI; -import nl.hauntedmc.proxyfeatures.api.queue.QueueObservabilityAPI; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityAPI; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueueApi; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityAPI; +import nl.hauntedmc.proxyfeatures.framework.admission.QueueAdmissionPort; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.queue.command.QueueCommand; import nl.hauntedmc.proxyfeatures.features.queue.listener.ConnectionListener; import nl.hauntedmc.proxyfeatures.features.queue.meta.Meta; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; +import nl.hauntedmc.proxyfeatures.framework.service.InternalServices; import java.time.Duration; import java.util.List; @@ -71,10 +71,10 @@ public MessageMap getDefaultMessages() { @Override public void initialize() { - CapacityAPI capacity = FeatureServices.require(this, CapacityAPI.class); + CapacityAPI capacity = InternalServices.require(this, CapacityAPI.class); manager = new QueueManager(this, getPlugin().getLogger(), capacity); - getLifecycleManager().getApiManager().registerService(QueueAdmissionAPI.class, manager); - getLifecycleManager().getApiManager().registerService(QueueObservabilityAPI.class, manager); + getLifecycleManager().getApiManager().registerService(QueueApi.class, manager); + getLifecycleManager().getApiManager().registerInternalService(QueueAdmissionPort.class, manager); getLifecycleManager().getListenerManager().registerListener(new ConnectionListener(manager)); getLifecycleManager().getCommandManager().registerFeatureCommand(new QueueCommand(this, manager)); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/QueueManager.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/QueueManager.java index 42cd49fb..64437378 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/QueueManager.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/QueueManager.java @@ -4,16 +4,22 @@ import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.scheduler.ScheduledTask; -import nl.hauntedmc.proxyfeatures.api.queue.QueueAdmissionAPI; -import nl.hauntedmc.proxyfeatures.api.queue.QueueObservabilityAPI; -import nl.hauntedmc.proxyfeatures.api.queue.QueueServerSnapshot; -import nl.hauntedmc.proxyfeatures.api.queue.QueueSnapshot; -import nl.hauntedmc.proxyfeatures.api.capacity.AdmissionIntent; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityAPI; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDecision; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDenialReason; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityLease; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityRequest; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueueApi; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueueJoinRequest; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueueJoinResult; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueueJoinStatus; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueueLeaveStatus; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueueServerSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueueSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueuedPlayerSnapshot; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; +import nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityAPI; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDecision; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDenialReason; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityLease; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityRequest; +import nl.hauntedmc.proxyfeatures.framework.admission.QueueAdmissionPort; import nl.hauntedmc.proxyfeatures.features.queue.model.QueueEntry; import nl.hauntedmc.proxyfeatures.features.queue.model.ServerQueue; import nl.hauntedmc.proxyfeatures.features.queue.util.PriorityResolver; @@ -30,12 +36,14 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** Priority/FIFO waiting and paced dispatch. Capacity is the sole capacity authority. */ -public final class QueueManager implements QueueAdmissionAPI, QueueObservabilityAPI { +public final class QueueManager implements QueueApi, QueueAdmissionPort { private static final class Dispatch { private final QueueEntry entry; @@ -158,24 +166,71 @@ public synchronized void shutdown() { actionbarCycle.clear(); } - @Override public boolean isQueueEnabled(String serverName) { return !closed && queues.containsKey(normalize(serverName)); } + @Override + public boolean isEnabled(ServerId server) { + return isQueueEnabled(server.value()); + } + public boolean isServerQueued(String serverName) { return isQueueEnabled(serverName); } @Override - public synchronized boolean enqueue(Player player, String serverName, CapacityDenialReason reason, - CapacityRequest admissionContext) { + public synchronized boolean enqueueDenied( + Player player, + ServerId server, + CapacityDenialReason reason, + CapacityRequest admissionContext + ) { + QueueJoinResult result = enqueueNow(player, server.value(), reason, admissionContext); + return result.entry().isPresent(); + } + + @Override + public CompletionStage join(QueueJoinRequest request) { + if (request == null || closed) { + return CompletableFuture.completedFuture(QueueJoinResult.failure(QueueJoinStatus.UNAVAILABLE)); + } + Player player = proxy.getPlayer(request.playerId()).orElse(null); + if (player == null) { + return CompletableFuture.completedFuture(QueueJoinResult.failure(QueueJoinStatus.PLAYER_OFFLINE)); + } + if (!isEnabled(request.targetServer())) { + return CompletableFuture.completedFuture(QueueJoinResult.failure(QueueJoinStatus.QUEUE_DISABLED)); + } + String previous = player.getCurrentServer() + .map(connection -> connection.getServerInfo().getName()) + .orElse(""); + CapacityRequest context = capacity.createRequest( + player, + previous, + request.targetServer().value(), + AdmissionIntent.INTERNAL + ); + return CompletableFuture.completedFuture(enqueueNow( + player, + request.targetServer().value(), + CapacityDenialReason.FULL, + context + )); + } + + private synchronized QueueJoinResult enqueueNow( + Player player, + String serverName, + CapacityDenialReason reason, + CapacityRequest admissionContext + ) { if (closed || player == null || admissionContext == null || reason != CapacityDenialReason.FULL) { - return false; + return QueueJoinResult.failure(QueueJoinStatus.REJECTED); } String server = normalize(serverName); ServerQueue target = queues.get(server); - if (target == null) return false; + if (target == null) return QueueJoinResult.failure(QueueJoinStatus.QUEUE_DISABLED); UUID playerId = player.getUniqueId(); admissionContexts.put(playerId, admissionContext); @@ -187,6 +242,11 @@ public synchronized boolean enqueue(Player player, String serverName, CapacityDe player.sendMessage(feature.getLocalizationHandler().getMessage("queue.join.already_in_queue") .with("server", server).with("position", position) .forAudience(player).build()); + actionbarCycle.putIfAbsent(playerId, -1); + return QueueJoinResult.success( + QueueJoinStatus.ALREADY_QUEUED, + snapshotEntry(target, target.find(playerId).orElseThrow(), position, false) + ); } else { cancelInFlight(playerId, false); ServerQueue old = queues.get(current); @@ -196,12 +256,15 @@ public synchronized boolean enqueue(Player player, String serverName, CapacityDe player.sendMessage(feature.getLocalizationHandler().getMessage("queue.join.moved_between_queues") .with("server", server).with("position", position) .forAudience(player).build()); + actionbarCycle.putIfAbsent(playerId, -1); + return QueueJoinResult.success( + QueueJoinStatus.MOVED, + snapshotEntry(target, target.find(playerId).orElseThrow(), position, false) + ); } - actionbarCycle.putIfAbsent(playerId, -1); - return true; } - target.enqueue(playerId, resolvePriority(player)); + QueueEntry entry = target.enqueue(playerId, resolvePriority(player)); int position = target.positionOf(playerId).orElse(0) + 1; player.sendMessage(feature.getLocalizationHandler().getMessage("queue.join.denied.full") .with("server", server).with("position", position) @@ -210,12 +273,15 @@ public synchronized boolean enqueue(Player player, String serverName, CapacityDe .with("seconds", graceSeconds).forAudience(player).build()); actionbarCycle.putIfAbsent(playerId, -1); scheduleDispatch(server, false); - return true; + return QueueJoinResult.success( + QueueJoinStatus.JOINED, + snapshotEntry(target, entry, position, false) + ); } @Override - public void wake(String serverName) { - scheduleDispatch(normalize(serverName), true); + public void capacityChanged(ServerId server) { + scheduleDispatch(server.value(), true); } private void scheduleDispatch(String server, boolean resetBackoff) { @@ -233,7 +299,7 @@ private void scheduleDispatch(String server, boolean resetBackoff) { } @Override - public synchronized boolean consumeCancelledAdvance(UUID playerId, String targetServer) { + public synchronized boolean consumeCancelledAdvance(UUID playerId, ServerId targetServer) { if (playerId == null) return false; long now = System.currentTimeMillis(); CancelledAdvance cancelled = cancelledAdvances.get(playerId); @@ -242,14 +308,14 @@ public synchronized boolean consumeCancelledAdvance(UUID playerId, String target cancelledAdvances.remove(playerId, cancelled); return false; } - if (!cancelled.server().equals(normalize(targetServer))) return false; + if (!cancelled.server().equals(targetServer.value())) return false; return cancelledAdvances.remove(playerId, cancelled); } @Override public synchronized QueueSnapshot snapshot() { long now = System.currentTimeMillis(); - Map snapshots = new LinkedHashMap<>(); + Map snapshots = new LinkedHashMap<>(); for (String server : queues.keySet().stream().sorted().toList()) { ServerQueue queue = queues.get(server); if (queue == null) continue; @@ -262,14 +328,15 @@ public synchronized QueueSnapshot snapshot() { int dispatching = (int) inFlight.values().stream() .filter(dispatch -> dispatch.server().equals(server)) .count(); - snapshots.put(server, new QueueServerSnapshot( - server, + ServerId serverId = ServerId.of(server); + snapshots.put(serverId, new QueueServerSnapshot( + serverId, queue.size(), connected.get(), queue.graceCount(), dispatching, blocked.get(), - queue.oldestEnqueuedAt().orElse(null) + queue.oldestEnqueuedAt() )); } return new QueueSnapshot(snapshots, Instant.ofEpochMilli(now)); @@ -283,7 +350,7 @@ public int resolvePriority(Player player) { return priorityResolver.resolve(player); } - public synchronized Optional leave(UUID playerId) { + public synchronized Optional leaveNow(UUID playerId) { Optional server = findQueueOf(playerId); if (server.isEmpty()) { clearPlayerState(playerId); @@ -299,6 +366,40 @@ public synchronized Optional leave(UUID playerId) { return server; } + @Override + public CompletionStage leave(UUID playerId) { + if (playerId == null || closed) { + return CompletableFuture.completedFuture(QueueLeaveStatus.UNAVAILABLE); + } + return CompletableFuture.completedFuture( + leaveNow(playerId).isPresent() ? QueueLeaveStatus.LEFT : QueueLeaveStatus.NOT_QUEUED + ); + } + + @Override + public synchronized Optional find(UUID playerId) { + if (playerId == null || closed) return Optional.empty(); + for (ServerQueue queue : queues.values()) { + Optional entry = queue.find(playerId); + if (entry.isPresent()) { + int position = queue.positionOf(playerId).orElse(0) + 1; + return Optional.of(snapshotEntry(queue, entry.get(), position, false)); + } + } + Dispatch dispatch = inFlight.get(playerId); + if (dispatch == null) return Optional.empty(); + ServerQueue queue = queues.get(dispatch.server()); + ServerId server = ServerId.of(dispatch.server()); + return Optional.of(new QueuedPlayerSnapshot( + playerId, + server, + 1, + dispatch.entry().priority(), + dispatch.entry().enqueuedAt(), + true + )); + } + public synchronized void onDisconnect(UUID playerId) { cancelledAdvances.remove(playerId); Dispatch dispatch = inFlight.remove(playerId); @@ -579,6 +680,22 @@ private static long deadline(long now, long delay) { return now + Math.max(0L, delay); } + private static QueuedPlayerSnapshot snapshotEntry( + ServerQueue queue, + QueueEntry entry, + int position, + boolean inFlight + ) { + return new QueuedPlayerSnapshot( + entry.playerId(), + ServerId.of(queue.serverName()), + position, + entry.priority(), + entry.enqueuedAt(), + inFlight + ); + } + private static String normalize(String value) { return value == null ? "" : value.trim().toLowerCase(Locale.ROOT); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/command/QueueCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/command/QueueCommand.java index 870ab697..c928ed48 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/command/QueueCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/command/QueueCommand.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.queue.Queue; import nl.hauntedmc.proxyfeatures.features.queue.QueueManager; import nl.hauntedmc.proxyfeatures.features.queue.model.ServerQueue; @@ -99,7 +99,7 @@ private void handleLeave(Invocation invocation) { .build()); return; } - Optional queueServer = manager.leave(player.getUniqueId()); + Optional queueServer = manager.leaveNow(player.getUniqueId()); if (queueServer.isEmpty()) { source.sendMessage(feature.getLocalizationHandler() .getMessage("queue.status.none") diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/meta/Meta.java index 500563d3..ffbf50a8 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.queue.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/model/ServerQueue.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/model/ServerQueue.java index cd6dc670..e4ee516a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/model/ServerQueue.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/model/ServerQueue.java @@ -107,6 +107,10 @@ public synchronized boolean contains(UUID playerId) { return index.containsKey(playerId); } + public synchronized Optional find(UUID playerId) { + return Optional.ofNullable(index.get(playerId)); + } + public synchronized int size() { return index.size(); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/ResourcePack.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/ResourcePack.java index a9b1c4b4..213ca584 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/ResourcePack.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/ResourcePack.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.resourcepack; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.resourcepack.command.ResourcePackCommand; import nl.hauntedmc.proxyfeatures.features.resourcepack.internal.ResourcePackHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/command/ResourcePackCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/command/ResourcePackCommand.java index a6da5d83..6cedbc3e 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/command/ResourcePackCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/command/ResourcePackCommand.java @@ -4,7 +4,7 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.player.ResourcePackInfo; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.resourcepack.ResourcePack; import nl.hauntedmc.proxyfeatures.features.resourcepack.internal.ResourcePackHandler; import nl.hauntedmc.proxyfeatures.features.resourcepack.util.ResourceUtils; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/internal/ResourcePackHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/internal/ResourcePackHandler.java index 182b6e0e..2c561c5a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/internal/ResourcePackHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/internal/ResourcePackHandler.java @@ -5,9 +5,9 @@ import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.api.scheduler.ScheduledTask; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigService; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigView; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigView; import nl.hauntedmc.proxyfeatures.features.resourcepack.ResourcePack; import nl.hauntedmc.proxyfeatures.features.resourcepack.util.ResourceUtils; import org.jetbrains.annotations.NotNull; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/meta/Meta.java index b4fa002d..c5be3677 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.resourcepack.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/Restart.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/Restart.java index 5931a84f..c661f4c9 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/Restart.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/Restart.java @@ -2,10 +2,11 @@ import nl.hauntedmc.dataprovider.database.messaging.MessagingDatabaseProvider; import nl.hauntedmc.dataprovider.database.messaging.durable.DurableMessagingDataAccess; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityAPI; -import nl.hauntedmc.proxyfeatures.api.capacity.RestartAdmissionAPI; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.api.capability.operations.RestartApi; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityAPI; +import nl.hauntedmc.proxyfeatures.framework.admission.RestartCoordinationPort; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.restart.command.AutoreconnectCommand; @@ -16,7 +17,7 @@ import nl.hauntedmc.proxyfeatures.features.restart.listener.BackendReconnectListener; import nl.hauntedmc.proxyfeatures.features.restart.messaging.RestartLifecycleBus; import nl.hauntedmc.proxyfeatures.features.restart.meta.Meta; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; +import nl.hauntedmc.proxyfeatures.framework.service.InternalServices; import java.util.ArrayList; import java.util.List; @@ -119,6 +120,12 @@ public MessageMap getDefaultMessages() { public void initialize() { handler = new RestartHandler(this); getLifecycleManager().getCommandManager().registerFeatureCommand(new ProxyRestartCommand(this)); + capacityCoordinator = new RestartCapacityCoordinator(this, null); + getLifecycleManager().getApiManager().registerService(RestartApi.class, capacityCoordinator); + getLifecycleManager().getApiManager().registerInternalService( + RestartCoordinationPort.class, + capacityCoordinator + ); if (!getConfigHandler().get("backend_autoreconnect.enabled", Boolean.class, true)) return; Optional redisProvider = registerRedisMessagingProvider("backend-autoreconnect-redis"); @@ -135,7 +142,10 @@ public void initialize() { getLifecycleManager().getCommandManager().registerFeatureCommand(autoreconnectCommand); getLifecycleManager().getListenerManager().registerListener(reconnectListener); - FeatureServices.find(this, CapacityAPI.class).ifPresent(this::attachCapacity); + restartLifecycleBus.setCapacityCoordinator(capacityCoordinator); + reconnectListener.setCapacityCoordinator(capacityCoordinator); + autoreconnectCommand.setCapacityCoordinator(capacityCoordinator); + InternalServices.find(this, CapacityAPI.class).ifPresent(this::attachCapacity); String configuredStream = getConfigHandler().get("backend_autoreconnect.stream", String.class, DEFAULT_RESTART_STREAM); @@ -164,12 +174,10 @@ public synchronized boolean attachCapacity(CapacityAPI capacity) { if (attachedCapacity != null) detachCapacityInternal(false); RestartCapacityCoordinator coordinator = capacityCoordinator; - if (coordinator == null) coordinator = new RestartCapacityCoordinator(this, capacity); - else coordinator.attachCapacity(capacity); + if (coordinator == null) return false; + coordinator.attachCapacity(capacity); attachedCapacity = capacity; - capacityCoordinator = coordinator; - getLifecycleManager().getApiManager().registerService(RestartAdmissionAPI.class, coordinator); if (restartLifecycleBus != null) restartLifecycleBus.setCapacityCoordinator(coordinator); if (reconnectListener != null) reconnectListener.setCapacityCoordinator(coordinator); if (autoreconnectCommand != null) autoreconnectCommand.setCapacityCoordinator(coordinator); @@ -187,7 +195,6 @@ private void detachCapacityInternal(boolean shutdownCoordinator) { RestartCapacityCoordinator coordinator = capacityCoordinator; CapacityAPI previousCapacity = attachedCapacity; attachedCapacity = null; - getLifecycleManager().getApiManager().unregisterService(RestartAdmissionAPI.class); if (coordinator == null) return; if (shutdownCoordinator) { if (restartLifecycleBus != null) restartLifecycleBus.setCapacityCoordinator(null); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/command/AutoreconnectCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/command/AutoreconnectCommand.java index fa68ee12..454e3e66 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/command/AutoreconnectCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/command/AutoreconnectCommand.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.restart.Restart; import nl.hauntedmc.proxyfeatures.features.restart.internal.BackendReconnectManager; import nl.hauntedmc.proxyfeatures.features.restart.internal.RestartCapacityCoordinator; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/command/ProxyRestartCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/command/ProxyRestartCommand.java index e512b9dc..de733114 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/command/ProxyRestartCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/command/ProxyRestartCommand.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.restart.command; import com.velocitypowered.api.command.CommandSource; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.restart.Restart; import nl.hauntedmc.proxyfeatures.features.restart.internal.RestartHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectManager.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectManager.java index 1fe00145..8602fdf0 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectManager.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectManager.java @@ -5,7 +5,7 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.scheduler.ScheduledTask; import nl.hauntedmc.proxyfeatures.features.restart.Restart; -import nl.hauntedmc.proxyfeatures.features.restart.messaging.RestartLifecycleMessage; +import nl.hauntedmc.proxyfeatures.contracts.messaging.RestartLifecycleMessage; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; import java.time.Duration; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/internal/RestartCapacityCoordinator.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/internal/RestartCapacityCoordinator.java index 16ff3d5e..55fb2c5a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/internal/RestartCapacityCoordinator.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/internal/RestartCapacityCoordinator.java @@ -2,11 +2,13 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.scheduler.ScheduledTask; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityAPI; -import nl.hauntedmc.proxyfeatures.api.capacity.RestartAdmissionAPI; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; +import nl.hauntedmc.proxyfeatures.api.capability.operations.RestartApi; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityAPI; +import nl.hauntedmc.proxyfeatures.framework.admission.RestartCoordinationPort; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; import nl.hauntedmc.proxyfeatures.features.restart.Restart; -import nl.hauntedmc.proxyfeatures.features.restart.messaging.RestartLifecycleMessage; +import nl.hauntedmc.proxyfeatures.contracts.messaging.RestartLifecycleMessage; import java.time.Duration; import java.time.Instant; @@ -23,7 +25,7 @@ * Mirrors validated backend restart lifecycle messages into Capacity state and return reservations. * The existing BackendReconnectManager remains authoritative for reconnect eligibility and pacing. */ -public final class RestartCapacityCoordinator implements RestartAdmissionAPI { +public final class RestartCapacityCoordinator implements RestartApi, RestartCoordinationPort { private static final String STATE_OWNER = "restart"; @@ -90,6 +92,16 @@ public RestartCapacityCoordinator(Restart feature, CapacityAPI capacity) { this.capacity = capacity; } + @Override + public void attachAdmission(CapacityAPI capacity) { + attachCapacity(capacity); + } + + @Override + public void detachAdmission(CapacityAPI capacity) { + detachCapacity(capacity); + } + /** Rebinds a replacement Capacity service without losing an active restart session. */ public synchronized void attachCapacity(CapacityAPI newCapacity) { if (closed || newCapacity == null) return; @@ -121,7 +133,7 @@ public synchronized void handle(RestartLifecycleMessage message) { } @Override - public synchronized boolean isRestartReturn(UUID playerId, String serverName) { + public synchronized boolean isExpectedReturn(UUID playerId, ServerId server) { if (closed || playerId == null) return false; Candidate candidate = candidatesByPlayer.get(playerId); if (candidate == null || !candidate.session.ready || candidate.phase != Phase.READY) return false; @@ -129,7 +141,19 @@ public synchronized boolean isRestartReturn(UUID playerId, String serverName) { removeSession(candidate.session, true); return false; } - return candidate.session.serverName.equals(normalize(serverName)); + return candidate.session.serverName.equals(server.value()); + } + + @Override + public synchronized boolean isDraining(ServerId server) { + if (closed) return false; + Session session = sessionsByServer.get(server.value()); + if (session == null) return false; + if (session.expiresAtMillis <= System.currentTimeMillis()) { + removeSession(session, true); + return false; + } + return !session.ready; } /** Records the authoritative kick before normal proxy fallback routing selects its destination. */ diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleBus.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleBus.java index 139a22dd..e5987bfe 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleBus.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleBus.java @@ -1,5 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.restart.messaging; +import nl.hauntedmc.proxyfeatures.contracts.messaging.RestartLifecycleMessage; + import nl.hauntedmc.dataprovider.database.messaging.durable.DurableDelivery; import nl.hauntedmc.dataprovider.database.messaging.durable.DurableMessagingDataAccess; import nl.hauntedmc.dataprovider.database.messaging.durable.DurableSubscription; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/meta/Meta.java index e32dee6b..ff1f01ad 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.restart.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { @Override diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/Sanctions.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/Sanctions.java index c7ec66b3..a832825a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/Sanctions.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/Sanctions.java @@ -1,9 +1,10 @@ package nl.hauntedmc.proxyfeatures.features.sanctions; import nl.hauntedmc.dataprovider.api.orm.ORMContext; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionsApi; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.sanctions.audit.PlayerSanctionsSecurityLogEntity; import nl.hauntedmc.proxyfeatures.features.sanctions.audit.SanctionsSecurityAuditLogService; @@ -13,6 +14,7 @@ import nl.hauntedmc.proxyfeatures.features.sanctions.meta.Meta; import nl.hauntedmc.proxyfeatures.features.sanctions.service.DiscordService; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionsService; +import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionsCapability; import nl.hauntedmc.proxyfeatures.features.sanctions.service.ServiceLookup; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; @@ -189,13 +191,14 @@ public void initialize() { SanctionEntity.class, PlayerSanctionsSecurityLogEntity.class) .orElseThrow(); - PlayerReferenceResolver playerResolver = new PlayerReferenceResolver( - getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for Sanctions.")) - ); + PlayerReferenceResolver playerResolver = getPlugin().getPlayerReferenceResolver(); securityAuditLogService = new SanctionsSecurityAuditLogService(getLogger(), orm, playerResolver); service = new SanctionsService(this); + getLifecycleManager().getApiManager().registerService( + SanctionsApi.class, + new SanctionsCapability(service) + ); serviceLookup = new ServiceLookup(this); discordService = new DiscordService(this); @@ -218,7 +221,7 @@ public void initialize() { getLifecycleManager().getTaskManager().scheduleRepeatingTask(() -> service.sweepExpiries(), Duration.ofSeconds(sweep)); } - private void registerAsyncCommand(nl.hauntedmc.proxyfeatures.api.command.FeatureCommand command) { + private void registerAsyncCommand(nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand command) { getLifecycleManager().getCommandManager() .registerFeatureCommand(new nl.hauntedmc.proxyfeatures.features.sanctions.command.AsyncSanctionsCommand( this, diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/AsyncSanctionsCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/AsyncSanctionsCommand.java index 8eee4a36..a3232ba8 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/AsyncSanctionsCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/AsyncSanctionsCommand.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/BanCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/BanCommand.java index 3e69b952..338ff4af 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/BanCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/BanCommand.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/BanIpCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/BanIpCommand.java index 306e193d..3084216f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/BanIpCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/BanIpCommand.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/KickCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/KickCommand.java index c139b931..dbcabfb6 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/KickCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/KickCommand.java @@ -3,8 +3,8 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/MuteCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/MuteCommand.java index a8299d80..a0736450 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/MuteCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/MuteCommand.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/SanctionListCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/SanctionListCommand.java index c91b0d5e..731e6937 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/SanctionListCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/SanctionListCommand.java @@ -3,10 +3,10 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.text.format.ComponentFormatter; -import nl.hauntedmc.proxyfeatures.api.util.text.format.TextFormatter; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionType; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanCommand.java index 0c89c95d..97ed88e7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanCommand.java @@ -3,8 +3,8 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionType; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionSuggestionLookup; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanIpCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanIpCommand.java index bdcbc605..3713807d 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanIpCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanIpCommand.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.sanctions.command; import com.velocitypowered.api.command.CommandSource; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import java.net.InetAddress; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnmuteCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnmuteCommand.java index 76b64816..1bfc0979 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnmuteCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnmuteCommand.java @@ -3,8 +3,8 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionType; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionSuggestionLookup; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/WarnCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/WarnCommand.java index 91044eda..ca8ead8f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/WarnCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/WarnCommand.java @@ -3,8 +3,8 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import java.util.*; diff --git a/proxyfeatures-contracts/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionEntity.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionEntity.java similarity index 100% rename from proxyfeatures-contracts/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionEntity.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionEntity.java diff --git a/proxyfeatures-contracts/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionType.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionType.java similarity index 100% rename from proxyfeatures-contracts/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionType.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionType.java diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListener.java index 867a7a75..09a7d9a7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListener.java @@ -8,7 +8,7 @@ import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionsService; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import java.net.InetAddress; import java.net.InetSocketAddress; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/meta/Meta.java index ad27cbd6..3eb37412 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.sanctions.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordService.java index 60e6f88d..93140036 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordService.java @@ -1,9 +1,9 @@ package nl.hauntedmc.proxyfeatures.features.sanctions.service; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.util.http.DiscordUtils; -import nl.hauntedmc.proxyfeatures.api.util.parse.JsonUtils; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.util.http.SimpleHttpClient; +import nl.hauntedmc.proxyfeatures.toolkit.util.parse.JsonUtils; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; @@ -11,10 +11,21 @@ public class DiscordService { + @FunctionalInterface + interface WebhookTransport { + void post(String url, String payload) throws java.io.IOException, InterruptedException; + } + private final Sanctions feature; + private final WebhookTransport transport; public DiscordService(Sanctions feature) { + this(feature, SimpleHttpClient::postJsonHttps); + } + + DiscordService(Sanctions feature, WebhookTransport transport) { this.feature = feature; + this.transport = java.util.Objects.requireNonNull(transport, "transport"); } /* ========================= Public API ========================= */ @@ -128,8 +139,12 @@ private void sendEmbed(String sanctionType, int color, String... fieldJsonParts) + "}"; feature.getLifecycleManager().getTaskManager().scheduleTask(() -> { - boolean delivered = DiscordUtils.sendPayload(webhookUrl, payload); - if (!delivered) { + try { + transport.post(webhookUrl, payload); + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + feature.getLogger().warn("[Sanctions/Discord] Webhook delivery was interrupted."); + } catch (java.io.IOException | RuntimeException error) { feature.getLogger().warn("[Sanctions/Discord] Failed to deliver webhook payload."); } }); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionSuggestionLookup.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionSuggestionLookup.java index 8ae23fb6..3778a556 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionSuggestionLookup.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionSuggestionLookup.java @@ -30,8 +30,7 @@ public static List activeTargetNames( } String prefix = startsWith == null ? "" : startsWith.toLowerCase(Locale.ROOT); - PlayerReferenceResolver resolver = new PlayerReferenceResolver(feature.getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for sanction suggestions."))); + PlayerReferenceResolver resolver = feature.getPlugin().getPlayerReferenceResolver(); return feature.getOrm().runInTransaction(session -> session.createQuery( diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsCapability.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsCapability.java new file mode 100644 index 00000000..eaff5cc2 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsCapability.java @@ -0,0 +1,48 @@ +package nl.hauntedmc.proxyfeatures.features.sanctions.service; + +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionsApi; +import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletionStage; + +/** Persistence-independent projection of the sanctions subsystem. */ +public final class SanctionsCapability implements SanctionsApi { + private final SanctionsService service; + + public SanctionsCapability(SanctionsService service) { + this.service = service; + } + + @Override + public CompletionStage> find(UUID playerId, boolean activeOnly) { + return service.findSanctionsByUuid(playerId, activeOnly) + .thenApply(entities -> entities.stream().map(entity -> snapshot(playerId, entity)).toList()); + } + + private static SanctionSnapshot snapshot(UUID playerId, SanctionEntity entity) { + Instant expiresAt = entity.getExpiresAt(); + boolean active = entity.isActive() && (expiresAt == null || expiresAt.isAfter(Instant.now())); + return new SanctionSnapshot( + entity.getId() == null ? 0L : entity.getId(), + playerId, + switch (entity.getType()) { + case BAN -> nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionType.BAN; + case BAN_IP -> nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionType.IP_BAN; + case MUTE -> nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionType.MUTE; + case WARN -> nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionType.WARNING; + case KICK -> nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionType.KICK; + }, + entity.getReason(), + entity.getActorName() == null || entity.getActorName().isBlank() + ? "CONSOLE" : entity.getActorName(), + entity.getCreatedAt(), + Optional.ofNullable(expiresAt), + active + ); + } +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsService.java index 82bb5321..2b6b5594 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsService.java @@ -1,11 +1,9 @@ package nl.hauntedmc.proxyfeatures.features.sanctions.service; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import nl.hauntedmc.dataregistry.api.player.PlayerData; -import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionType; @@ -32,25 +30,22 @@ public class SanctionsService { private final Map muteCache = new ConcurrentHashMap<>(); public SanctionsService(Sanctions feature) { - this(feature, feature.getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for Sanctions."))); - } - - SanctionsService(Sanctions feature, DataRegistryApi dataRegistry) { this.feature = feature; - this.playerResolver = new PlayerReferenceResolver(dataRegistry); - this.players = dataRegistry.players(); + this.playerResolver = feature.getPlugin().getPlayerReferenceResolver(); + this.players = feature.getPlugin().getDataRegistry() + .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for Sanctions.")) + .players(); } - SanctionsService(Sanctions feature, PlayerDirectory playerDirectory) { + SanctionsService(Sanctions feature, PlayerReferenceResolver playerResolver) { this.feature = feature; - this.playerResolver = new PlayerReferenceResolver(playerDirectory); + this.playerResolver = playerResolver; this.players = null; } - SanctionsService(Sanctions feature, PlayerDirectory playerDirectory, PlayerData players) { + SanctionsService(Sanctions feature, PlayerReferenceResolver playerResolver, PlayerData players) { this.feature = feature; - this.playerResolver = new PlayerReferenceResolver(playerDirectory); + this.playerResolver = playerResolver; this.players = Objects.requireNonNull(players, "players"); } @@ -670,6 +665,15 @@ public List listSanctionsForPlayer(PlayerReference p, boolean ac }); } + /** Resolves a public UUID without exposing DataRegistry or ORM types to callers. */ + public CompletionStage> findSanctionsByUuid(UUID playerId, boolean activeOnly) { + if (playerId == null) return CompletableFuture.completedFuture(List.of()); + return playerResolver.findByUuidAsync(playerId) + .thenApply(reference -> reference + .map(value -> listSanctionsForPlayer(value, activeOnly)) + .orElseGet(List::of)); + } + /** * Resolve a username for a (possibly detached) PlayerReference safely. */ diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/ServiceLookup.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/ServiceLookup.java index 199b9edf..71fefd9c 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/ServiceLookup.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/ServiceLookup.java @@ -1,8 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.sanctions.service; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; @@ -12,16 +10,11 @@ public class ServiceLookup { private final PlayerReferenceResolver playerResolver; public ServiceLookup(Sanctions feature) { - this(feature.getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for Sanctions."))); + this.playerResolver = feature.getPlugin().getPlayerReferenceResolver(); } - ServiceLookup(DataRegistryApi dataRegistry) { - this.playerResolver = new PlayerReferenceResolver(dataRegistry); - } - - ServiceLookup(PlayerDirectory playerDirectory) { - this.playerResolver = new PlayerReferenceResolver(playerDirectory); + ServiceLookup(PlayerReferenceResolver playerResolver) { + this.playerResolver = playerResolver; } public Optional byName(String name) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/ServerLinks.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/ServerLinks.java index eb276363..52e1eb4f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/ServerLinks.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/ServerLinks.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.serverlinks; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.serverlinks.internal.ServerLinksHandler; import nl.hauntedmc.proxyfeatures.features.serverlinks.listener.JoinListener; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/meta/Meta.java index 63565d5c..d0344051 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.serverlinks.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/SlashServer.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/SlashServer.java index acb358fb..dfa25525 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/SlashServer.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/SlashServer.java @@ -2,9 +2,9 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.slashserver.command.SlashServerAdminCommand; import nl.hauntedmc.proxyfeatures.features.slashserver.command.SlashServerCommand; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/command/SlashServerAdminCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/command/SlashServerAdminCommand.java index 3ea6087a..d942e15e 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/command/SlashServerAdminCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/command/SlashServerAdminCommand.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.command.CommandSource; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.slashserver.SlashServer; import java.util.ArrayList; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/command/SlashServerCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/command/SlashServerCommand.java index 454e5f92..142e2664 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/command/SlashServerCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/command/SlashServerCommand.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.slashserver.SlashServer; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/meta/Meta.java index 082f1597..c6a585ad 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.slashserver.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/StaffChat.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/StaffChat.java index 926fb80a..54c4e483 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/StaffChat.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/StaffChat.java @@ -2,8 +2,8 @@ import nl.hauntedmc.dataprovider.database.messaging.MessagingDataAccess; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.staffchat.internal.ChatChannelHandler; import nl.hauntedmc.proxyfeatures.features.staffchat.internal.messaging.EventBusHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/internal/ChatChannel.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/internal/ChatChannel.java index 207ef5b9..31fbe008 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/internal/ChatChannel.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/internal/ChatChannel.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; import nl.hauntedmc.proxyfeatures.features.staffchat.StaffChat; import java.util.Collections; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/listener/ConnectListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/listener/ConnectListener.java index 642064ef..1d9ab695 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/listener/ConnectListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/listener/ConnectListener.java @@ -6,7 +6,7 @@ import com.velocitypowered.api.event.player.ServerPostConnectEvent; import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.staffchat.StaffChat; import nl.hauntedmc.proxyfeatures.features.staffchat.internal.ChatChannel; import nl.hauntedmc.proxyfeatures.features.staffchat.internal.ChatChannelHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/meta/Meta.java index 3504ac26..71379afd 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.staffchat.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/TextCommands.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/TextCommands.java index e23d38e7..72ba4377 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/TextCommands.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/TextCommands.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.textcommands; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.textcommands.command.TextCommand; import nl.hauntedmc.proxyfeatures.features.textcommands.meta.Meta; @@ -47,7 +47,7 @@ public void initialize() { private void initializeTextCommands() { var commandNodes = getConfigHandler().node("commands").children(); - for (Map.Entry entry : commandNodes.entrySet()) { + for (Map.Entry entry : commandNodes.entrySet()) { String name = entry.getKey(); if (name == null || name.isBlank()) { continue; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/command/TextCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/command/TextCommand.java index f238edf2..89d778a3 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/command/TextCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/command/TextCommand.java @@ -3,8 +3,8 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.textcommands.TextCommands; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/meta/Meta.java index 6d094f7b..7b7662de 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.textcommands.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactor.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactor.java index ab3f1181..92f15c8f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactor.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactor.java @@ -4,8 +4,8 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.scheduler.ScheduledTask; import nl.hauntedmc.dataprovider.api.orm.ORMContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.twofactor.audit.PlayerTwoFactorLogEntity; @@ -20,6 +20,8 @@ import nl.hauntedmc.proxyfeatures.features.twofactor.persistence.PlayerTwoFactorEntity; import nl.hauntedmc.proxyfeatures.features.twofactor.persistence.TwoFactorAccountStore; import nl.hauntedmc.proxyfeatures.features.twofactor.service.TwoFactorService; +import nl.hauntedmc.proxyfeatures.features.twofactor.service.TwoFactorCapability; +import nl.hauntedmc.proxyfeatures.api.capability.operations.TwoFactorApi; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import java.nio.file.Path; @@ -170,10 +172,7 @@ public void initialize() { ORMContext orm = createPlayerOrmContext(PlayerTwoFactorEntity.class, PlayerTwoFactorLogEntity.class) .orElseThrow(); - PlayerReferenceResolver playerResolver = new PlayerReferenceResolver( - getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for TwoFactor.")) - ); + PlayerReferenceResolver playerResolver = getPlugin().getPlayerReferenceResolver(); auditLogService = new TwoFactorAuditLogService(getLogger(), orm, playerResolver); Path secretDir = getPlugin().getDataDirectory().resolve("local").resolve("twofactor"); @@ -182,6 +181,10 @@ public void initialize() { TotpService totp = new TotpService(config.digits(), config.periodSeconds(), config.allowedDriftWindows()); service = new TwoFactorService(getPlugin().getProxy(), config, store, crypto, totp); + getLifecycleManager().getApiManager().registerService( + TwoFactorApi.class, + new TwoFactorCapability(this, service) + ); getLifecycleManager().getCommandManager().registerFeatureCommand(new TwoFactorCommand(this)); getLifecycleManager().getListenerManager().registerListener(new TwoFactorListener(this)); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/command/TwoFactorCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/command/TwoFactorCommand.java index e66c9e21..c2137ef6 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/command/TwoFactorCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/command/TwoFactorCommand.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.twofactor.TwoFactor; import nl.hauntedmc.proxyfeatures.features.twofactor.persistence.TwoFactorAccountState; import nl.hauntedmc.proxyfeatures.features.twofactor.policy.TwoFactorPermissions; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/meta/Meta.java index bbccda31..0e50f64b 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/meta/Meta.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.twofactor.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/service/TwoFactorCapability.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/service/TwoFactorCapability.java new file mode 100644 index 00000000..fd798e42 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/service/TwoFactorCapability.java @@ -0,0 +1,29 @@ +package nl.hauntedmc.proxyfeatures.features.twofactor.service; + +import nl.hauntedmc.proxyfeatures.api.capability.operations.TwoFactorApi; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; +import nl.hauntedmc.proxyfeatures.features.twofactor.TwoFactor; + +import java.util.Objects; +import java.util.UUID; + +/** Public projection of active two-factor authentication locks. */ +public final class TwoFactorCapability implements TwoFactorApi { + private final TwoFactor feature; + private final TwoFactorService service; + + public TwoFactorCapability(TwoFactor feature, TwoFactorService service) { + this.feature = Objects.requireNonNull(feature, "feature"); + this.service = Objects.requireNonNull(service, "service"); + } + + @Override + public boolean isLocked(UUID playerId) { + return feature.getPlugin().getProxy().getPlayer(playerId).map(service::isLocked).orElse(false); + } + + @Override + public boolean isAuthenticationServer(ServerId server) { + return feature.isLockServer(server.value()); + } +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/Vanish.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/Vanish.java index 4532d4b1..0826e4db 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/Vanish.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/Vanish.java @@ -3,12 +3,13 @@ import nl.hauntedmc.dataprovider.database.messaging.MessagingDatabaseProvider; import nl.hauntedmc.dataprovider.database.messaging.durable.DurableMessagingDataAccess; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.feature.stateful.SnapshotState; -import nl.hauntedmc.proxyfeatures.api.feature.stateful.StatefulFeature; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.framework.feature.stateful.SnapshotState; +import nl.hauntedmc.proxyfeatures.framework.feature.stateful.StatefulFeature; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.features.vanish.internal.PresenceService; import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishRegistry; import nl.hauntedmc.proxyfeatures.features.vanish.internal.messaging.EventBusHandler; import nl.hauntedmc.proxyfeatures.features.vanish.listener.ConnectListener; @@ -26,7 +27,7 @@ public class Vanish extends VelocityBaseFeature implements StatefulFeature private VanishRegistry vanishRegistry; private EventBusHandler eventBusHandler; - private VanishAPI api; + private PresenceService presenceService; public Vanish(FeatureContext context) { super(context); @@ -50,9 +51,9 @@ public MessageMap getDefaultMessages() { public void initialize() { // Prepare registry & API this.vanishRegistry = new VanishRegistry(this); - this.api = new VanishAPI(this); + this.presenceService = new PresenceService(getPlugin().getProxy(), vanishRegistry); - getLifecycleManager().getApiManager().registerService(VanishAPI.class, this.api); + getLifecycleManager().getApiManager().registerService(PresenceApi.class, presenceService); // Optional Redis setup Optional redisProvider = registerRedisMessagingProvider("redis"); @@ -127,8 +128,8 @@ public EventBusHandler getEventBusHandler() { return eventBusHandler; } - public VanishAPI getVanishAPI() { - return api; + public PresenceApi getPresenceApi() { + return presenceService; } static String resolveStream(String configuredStream) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/event/VanishStateChangeEvent.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/event/VanishStateChangeEvent.java deleted file mode 100644 index 21f56e54..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/event/VanishStateChangeEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.vanish.event; - -import java.util.Objects; -import java.util.UUID; - -/** - * Fired after the proxy accepts and applies a real online player's vanish state transition. - * Snapshot restoration, duplicate revisions and disconnect cleanup do not fire this event. - */ -public record VanishStateChangeEvent(UUID playerUuid, String playerName, boolean vanished) { - - public VanishStateChangeEvent { - Objects.requireNonNull(playerUuid, "playerUuid"); - playerName = playerName == null ? "" : playerName; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/PresenceService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/PresenceService.java new file mode 100644 index 00000000..4fece3ad --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/PresenceService.java @@ -0,0 +1,45 @@ +package nl.hauntedmc.proxyfeatures.features.vanish.internal; + +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceSnapshot; + +import java.time.Clock; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** Velocity projection of the authoritative Vanish registry. */ +public final class PresenceService implements PresenceApi { + private final ProxyServer proxy; + private final VanishRegistry registry; + private final Clock clock; + + public PresenceService(ProxyServer proxy, VanishRegistry registry) { + this(proxy, registry, Clock.systemUTC()); + } + + PresenceService(ProxyServer proxy, VanishRegistry registry, Clock clock) { + this.proxy = Objects.requireNonNull(proxy, "proxy"); + this.registry = Objects.requireNonNull(registry, "registry"); + this.clock = Objects.requireNonNull(clock, "clock"); + } + + @Override + public boolean isHidden(UUID playerId) { + return registry.isVanished(playerId); + } + + @Override + public PresenceSnapshot snapshot() { + Set online = proxy.getAllPlayers().stream() + .map(Player::getUniqueId) + .collect(Collectors.toUnmodifiableSet()); + Set hidden = registry.snapshot().keySet().stream() + .filter(online::contains) + .collect(Collectors.toUnmodifiableSet()); + return new PresenceSnapshot(online, hidden, clock.instant()); + } +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishAPI.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishAPI.java deleted file mode 100644 index 10e5490d..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishAPI.java +++ /dev/null @@ -1,51 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.vanish.internal; - -import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.features.vanish.Vanish; - -import java.util.List; -import java.util.UUID; - -/** - * Public API other features can use to query vanish-aware player stats. - */ -public class VanishAPI { - - private final Vanish feature; - - public VanishAPI(Vanish feature) { - this.feature = feature; - } - - /** - * All online players minus the currently vanished online players. - */ - public int getAdjustedPlayerCount() { - return feature.getVanishRegistry().getAdjustedOnlineCount(); - } - - /** - * List of online players excluding those currently vanished. - */ - public List getAdjustedOnlinePlayers() { - return feature.getVanishRegistry().getAdjustedOnlinePlayers(); - } - - /** - * List of currently vanished online players. - */ - public List getVanishedPlayers() { - return feature.getVanishRegistry().getVanishedOnlinePlayers(); - } - - /** - * Number of currently vanished online players. - */ - public int getVanishedCount() { - return feature.getVanishRegistry().getVanishedOnlineCount(); - } - - public boolean isVanished(UUID uuid) { - return feature.getVanishRegistry().isVanished(uuid); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishRegistry.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishRegistry.java index d2ba804b..ea1f99ed 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishRegistry.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishRegistry.java @@ -1,9 +1,10 @@ package nl.hauntedmc.proxyfeatures.features.vanish.internal; import com.velocitypowered.api.proxy.Player; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceChangedEvent; import nl.hauntedmc.proxyfeatures.features.vanish.Vanish; -import nl.hauntedmc.proxyfeatures.features.vanish.event.VanishStateChangeEvent; +import java.time.Instant; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -48,7 +49,7 @@ public boolean applyUpdate( boolean vanished, long stateVersion ) { - VanishStateChangeEvent stateChange; + PresenceChangedEvent stateChange; synchronized (this) { if (uuid == null || stateVersion <= 0L) { return false; @@ -64,7 +65,7 @@ public boolean applyUpdate( return true; } - private VanishStateChangeEvent applyCurrentState(UUID uuid, String name, boolean vanished) { + private PresenceChangedEvent applyCurrentState(UUID uuid, String name, boolean vanished) { if (uuid == null) { return null; } @@ -90,10 +91,10 @@ private VanishStateChangeEvent applyCurrentState(UUID uuid, String name, boolean if (previouslyVanished == vanished) { return null; } - return new VanishStateChangeEvent(uuid, resolvedName, vanished); + return new PresenceChangedEvent(uuid, resolvedName, vanished, Instant.now()); } - private void publishStateChange(VanishStateChangeEvent stateChange) { + private void publishStateChange(PresenceChangedEvent stateChange) { if (stateChange == null) { return; } @@ -106,7 +107,7 @@ private void publishStateChange(VanishStateChangeEvent stateChange) { if (feature.getLogger() != null) { feature.getLogger().warn( "Could not publish proxy vanish state transition for " - + stateChange.playerUuid() + ": " + exception.getMessage() + + stateChange.playerId() + ": " + exception.getMessage() ); } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/listener/TabCompleteListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/listener/TabCompleteListener.java index 0f154204..b9ea44d7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/listener/TabCompleteListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/listener/TabCompleteListener.java @@ -31,7 +31,7 @@ public void onTabComplete(TabCompleteEvent event) { } Set vanishedNamesLower = VanishTabCompletePolicy.normalizeNamesLower( - feature.getVanishAPI().getVanishedPlayers().stream() + feature.getVanishRegistry().getVanishedOnlinePlayers().stream() .map(Player::getUsername) .toList() ); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/meta/Meta.java index cf50ca67..dff7510a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.vanish.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/VersionCheck.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/VersionCheck.java index 7458d55a..32818b63 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/VersionCheck.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/VersionCheck.java @@ -2,8 +2,8 @@ import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.versioncheck.audit.PlayerVersionLogEntity; import nl.hauntedmc.proxyfeatures.features.versioncheck.audit.VersionAuditLogService; @@ -11,6 +11,7 @@ import nl.hauntedmc.proxyfeatures.features.versioncheck.listener.ConnectionListener; import nl.hauntedmc.proxyfeatures.features.versioncheck.meta.Meta; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; +import nl.hauntedmc.proxyfeatures.api.capability.operations.VersionApi; public class VersionCheck extends VelocityBaseFeature { @@ -44,12 +45,10 @@ public void initialize() { if (orm == null) { getLogger().warn("VersionCheck database logging is disabled because the ORM context is unavailable."); } - PlayerReferenceResolver playerResolver = new PlayerReferenceResolver( - getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for VersionCheck.")) - ); + PlayerReferenceResolver playerResolver = getPlugin().getPlayerReferenceResolver(); auditLogService = new VersionAuditLogService(getLogger(), orm, playerResolver); versionHandler = new VersionHandler(this); + getLifecycleManager().getApiManager().registerService(VersionApi.class, versionHandler); getLifecycleManager().getListenerManager().registerListener(new ConnectionListener(this)); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/internal/VersionHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/internal/VersionHandler.java index fe10192e..a40ddadb 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/internal/VersionHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/internal/VersionHandler.java @@ -8,37 +8,38 @@ import nl.hauntedmc.proxyfeatures.features.versioncheck.VersionCheck; import nl.hauntedmc.proxyfeatures.features.versioncheck.audit.VersionAuditLogService; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; +import nl.hauntedmc.proxyfeatures.api.capability.operations.VersionApi; -public class VersionHandler { +public class VersionHandler implements VersionApi { private final VersionCheck feature; private final VersionAuditLogService auditLogService; - private final int minimum_protocol_version; - private final String friendly_protocol_name; + private final int minimumProtocolVersion; + private final String friendlyProtocolName; public VersionHandler(VersionCheck feature) { this.feature = feature; this.auditLogService = feature.getAuditLogService(); - minimum_protocol_version = feature.getConfigHandler().get("minimum_protocol_version", Integer.class, 0); + minimumProtocolVersion = feature.getConfigHandler().get("minimum_protocol_version", Integer.class, 0); String friendly = feature.getConfigHandler().get("friendly_protocol_name", String.class, ""); if (friendly == null || friendly.isBlank()) { friendly = "unsupported"; } - friendly_protocol_name = friendly; + friendlyProtocolName = friendly; } public void checkVersion(LoginEvent event) { Player player = event.getPlayer(); int protocolVersion = player.getProtocolVersion().getProtocol(); - if (isUnsupportedVersion(protocolVersion)) { - auditLogService.logObservation(player, "denied", minimum_protocol_version, friendly_protocol_name); + if (!isSupported(protocolVersion)) { + auditLogService.logObservation(player, "denied", minimumProtocolVersion, friendlyProtocolName); ConnectionLogHelper.logLoginDenied( feature.getLogger(), "unsupported_version", player, - "minimum_protocol", String.valueOf(minimum_protocol_version), - "minimum_version_name", friendly_protocol_name + "minimum_protocol", String.valueOf(minimumProtocolVersion), + "minimum_version_name", friendlyProtocolName ); event.setResult(LoginEvent.ComponentResult.denied( Component.join( @@ -46,26 +47,24 @@ public void checkVersion(LoginEvent event) { Component.text("Verbinding verbroken:", NamedTextColor.RED), feature.getLocalizationHandler().getMessage("versioncheck.unsupported_version") .forAudience(player) - .with("friendly_protocol_name", friendly_protocol_name) + .with("friendly_protocol_name", friendlyProtocolName) .build() ) )); return; } - auditLogService.logObservation(player, "allowed", minimum_protocol_version, friendly_protocol_name); + auditLogService.logObservation(player, "allowed", minimumProtocolVersion, friendlyProtocolName); } - public boolean isUnsupportedVersion(int protocolVersion) { - return protocolVersion < minimum_protocol_version; + @Override + public int minimumProtocolVersion() { + return minimumProtocolVersion; } - public int getMinimumProtcolVersion() { - return minimum_protocol_version; - } - - public String getFriendlyProtocolName() { - return friendly_protocol_name; + @Override + public String minimumVersionName() { + return friendlyProtocolName; } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/meta/Meta.java index 6e724dc5..f8559ecd 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.versioncheck.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; public class Meta implements BaseMeta { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/Votifier.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/Votifier.java index 90cdc957..83759d13 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/Votifier.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/Votifier.java @@ -4,8 +4,8 @@ import nl.hauntedmc.dataprovider.database.messaging.MessagingDatabaseProvider; import nl.hauntedmc.dataprovider.database.messaging.durable.DurableMessagingDataAccess; import nl.hauntedmc.dataprovider.database.messaging.durable.PublishedDurableEvent; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.votifier.audit.VotifierSecurityAuditLogService; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/command/ExtendedVotifierCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/command/ExtendedVotifierCommand.java index a29c91c2..03d2ac10 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/command/ExtendedVotifierCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/command/ExtendedVotifierCommand.java @@ -5,7 +5,7 @@ import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.velocitypowered.api.command.CommandSource; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; import nl.hauntedmc.proxyfeatures.features.votifier.Votifier; import org.jetbrains.annotations.NotNull; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/command/VotifierCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/command/VotifierCommand.java index e485c331..519ae515 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/command/VotifierCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/command/VotifierCommand.java @@ -14,7 +14,7 @@ import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; import nl.hauntedmc.proxyfeatures.features.votifier.Votifier; import nl.hauntedmc.proxyfeatures.features.votifier.internal.VoteLeaderboardEntry; import nl.hauntedmc.proxyfeatures.features.votifier.internal.VotePlayerStatsView; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsService.java index b99825fb..31036640 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsService.java @@ -1,8 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.votifier.internal; import nl.hauntedmc.dataprovider.api.orm.ORMContext; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; -import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import nl.hauntedmc.proxyfeatures.features.votifier.Votifier; @@ -72,8 +70,13 @@ public VoteStatsService(Votifier feature, ORMContext ormContext) { ORMContext systemOrmContext, boolean ensureSchemaGuard ) { - this(feature, playerOrmContext, systemOrmContext, ensureSchemaGuard, feature.getPlugin().getDataRegistry() - .orElseThrow(() -> new IllegalStateException("DataRegistryApi is required for Votifier."))); + this( + feature, + playerOrmContext, + systemOrmContext, + ensureSchemaGuard, + feature.getPlugin().getPlayerReferenceResolver() + ); } VoteStatsService( @@ -81,28 +84,12 @@ public VoteStatsService(Votifier feature, ORMContext ormContext) { ORMContext playerOrmContext, ORMContext systemOrmContext, boolean ensureSchemaGuard, - DataRegistryApi dataRegistry + PlayerReferenceResolver playerResolver ) { this.feature = feature; this.playerOrmContext = playerOrmContext; this.systemOrmContext = systemOrmContext; - this.playerResolver = new PlayerReferenceResolver(dataRegistry); - if (ensureSchemaGuard) { - ensureRemindColumnBestEffort(); - } - } - - VoteStatsService( - Votifier feature, - ORMContext playerOrmContext, - ORMContext systemOrmContext, - boolean ensureSchemaGuard, - PlayerDirectory playerDirectory - ) { - this.feature = feature; - this.playerOrmContext = playerOrmContext; - this.systemOrmContext = systemOrmContext; - this.playerResolver = new PlayerReferenceResolver(playerDirectory); + this.playerResolver = playerResolver; if (ensureSchemaGuard) { ensureRemindColumnBestEffort(); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/messaging/VoteDeliverySettings.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/messaging/VoteDeliverySettings.java index 78dd0509..c0a2255b 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/messaging/VoteDeliverySettings.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/messaging/VoteDeliverySettings.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.votifier.messaging; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.votifier.Votifier; import java.time.Duration; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/meta/Meta.java index 3b33544d..bb9cc9a3 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/meta/Meta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/meta/Meta.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.votifier.meta; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.List; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/AdmissionIntent.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/AdmissionIntent.java similarity index 88% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/AdmissionIntent.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/AdmissionIntent.java index bf5e4ffc..0a09a499 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/AdmissionIntent.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/AdmissionIntent.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; +package nl.hauntedmc.proxyfeatures.framework.admission; /** Describes why a backend connection is being attempted. */ public enum AdmissionIntent { diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityAPI.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityAPI.java similarity index 74% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityAPI.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityAPI.java index e3106f99..ba08f703 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityAPI.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityAPI.java @@ -1,5 +1,6 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; +package nl.hauntedmc.proxyfeatures.framework.admission; +import com.velocitypowered.api.proxy.Player; import java.time.Instant; import java.util.Collection; import java.util.Optional; @@ -7,6 +8,13 @@ /** Shared authoritative admission-control service exported by the Capacity feature. */ public interface CapacityAPI { + CapacityRequest createRequest( + Player player, + String previousServer, + String targetServer, + AdmissionIntent intent + ); + CapacityDecision tryAcquire(CapacityRequest request); Optional findPreparedLease(UUID playerId, String targetServer); diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityDecision.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityDecision.java similarity index 93% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityDecision.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityDecision.java index 8ce02b5c..30cda2eb 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityDecision.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityDecision.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; +package nl.hauntedmc.proxyfeatures.framework.admission; import java.util.Objects; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityDenialReason.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityDenialReason.java similarity index 71% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityDenialReason.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityDenialReason.java index 89142ce3..14693d1d 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityDenialReason.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityDenialReason.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; +package nl.hauntedmc.proxyfeatures.framework.admission; public enum CapacityDenialReason { NONE, diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityLease.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityLease.java similarity index 88% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityLease.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityLease.java index 7829dc59..8bce0716 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityLease.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityLease.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; +package nl.hauntedmc.proxyfeatures.framework.admission; import java.time.Instant; import java.util.UUID; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityRequest.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityRequest.java similarity index 95% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityRequest.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityRequest.java index 53cd696b..5e0ae4c6 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityRequest.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityRequest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; +package nl.hauntedmc.proxyfeatures.framework.admission; import java.util.Locale; import java.util.Objects; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityScopeSnapshot.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityScopeSnapshot.java similarity index 92% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityScopeSnapshot.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityScopeSnapshot.java index 322b1aea..ea729b90 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityScopeSnapshot.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityScopeSnapshot.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; +package nl.hauntedmc.proxyfeatures.framework.admission; /** Immutable operational view of one configured capacity scope. */ public record CapacityScopeSnapshot( diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacitySnapshot.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacitySnapshot.java similarity index 89% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacitySnapshot.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacitySnapshot.java index b41431c7..b223a90a 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacitySnapshot.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacitySnapshot.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; +package nl.hauntedmc.proxyfeatures.framework.admission; import java.util.Map; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityState.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityState.java similarity index 78% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityState.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityState.java index 670060cc..a001c151 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capacity/CapacityState.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/CapacityState.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.capacity; +package nl.hauntedmc.proxyfeatures.framework.admission; /** Runtime admission state of one backend. */ public enum CapacityState { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/QueueAdmissionPort.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/QueueAdmissionPort.java new file mode 100644 index 00000000..52f710fe --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/QueueAdmissionPort.java @@ -0,0 +1,20 @@ +package nl.hauntedmc.proxyfeatures.framework.admission; + +import com.velocitypowered.api.proxy.Player; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + +import java.util.UUID; + +/** Runtime-only collaboration port between the admission engine and Queue. */ +public interface QueueAdmissionPort { + boolean enqueueDenied( + Player player, + ServerId server, + CapacityDenialReason reason, + CapacityRequest admissionContext + ); + + void capacityChanged(ServerId server); + + boolean consumeCancelledAdvance(UUID playerId, ServerId targetServer); +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/RestartCoordinationPort.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/RestartCoordinationPort.java new file mode 100644 index 00000000..05d29160 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/admission/RestartCoordinationPort.java @@ -0,0 +1,8 @@ +package nl.hauntedmc.proxyfeatures.framework.admission; + +/** Runtime-only port used to bind restart lifecycle coordination to the admission engine. */ +public interface RestartCoordinationPort { + void attachAdmission(CapacityAPI capacity); + + void detachAdmission(CapacityAPI capacity); +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/command/FeatureCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/FeatureCommand.java similarity index 88% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/command/FeatureCommand.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/FeatureCommand.java index 03011cf5..f0742f79 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/command/FeatureCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/FeatureCommand.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.command; +package nl.hauntedmc.proxyfeatures.framework.command; import com.velocitypowered.api.command.SimpleCommand; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommand.java index 9050f1e5..a8f27f4f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommand.java @@ -11,7 +11,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.framework.loader.FeatureDescriptor; import nl.hauntedmc.proxyfeatures.framework.loader.disable.FeatureDisableResponse; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/command/brigadier/BrigadierCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/brigadier/BrigadierCommand.java similarity index 93% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/command/brigadier/BrigadierCommand.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/brigadier/BrigadierCommand.java index b44f3356..f01b211a 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/command/brigadier/BrigadierCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/brigadier/BrigadierCommand.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.command.brigadier; +package nl.hauntedmc.proxyfeatures.framework.command.brigadier; import com.mojang.brigadier.tree.LiteralCommandNode; import com.velocitypowered.api.command.CommandSource; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureConfigHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureConfigHandler.java index 71439293..412895fa 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureConfigHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureConfigHandler.java @@ -1,9 +1,9 @@ package nl.hauntedmc.proxyfeatures.framework.config; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigService; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigView; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigView; import org.slf4j.Logger; import java.util.Map; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureStoragePaths.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureStoragePaths.java index 9d7d004e..223aa3a6 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureStoragePaths.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureStoragePaths.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.framework.config; -import nl.hauntedmc.proxyfeatures.api.io.localization.Language; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; import java.util.Objects; import java.util.regex.Pattern; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/MainConfigHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/MainConfigHandler.java index fd1d043b..403cc533 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/MainConfigHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/config/MainConfigHandler.java @@ -1,10 +1,10 @@ package nl.hauntedmc.proxyfeatures.framework.config; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigService; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigView; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigView; import org.slf4j.Logger; import java.util.Map; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/extension/DefaultMotdExtensions.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/extension/DefaultMotdExtensions.java new file mode 100644 index 00000000..1ef06ec3 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/extension/DefaultMotdExtensions.java @@ -0,0 +1,70 @@ +package nl.hauntedmc.proxyfeatures.framework.extension; + +import nl.hauntedmc.proxyfeatures.api.extension.ExtensionRegistration; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContribution; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContext; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContributor; +import nl.hauntedmc.proxyfeatures.api.extension.MotdExtensions; + +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +/** Thread-safe, lifecycle-owned MOTD contribution registry. */ +public final class DefaultMotdExtensions implements MotdExtensions { + private record Entry(int priority, MotdContributor contributor) { + } + + private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); + + @Override + public ExtensionRegistration register(String owner, int priority, MotdContributor contributor) { + String key = requireOwner(owner); + Entry entry = new Entry(priority, Objects.requireNonNull(contributor, "contributor")); + if (entries.putIfAbsent(key, entry) != null) { + throw new IllegalStateException("A MOTD contributor is already registered by " + key); + } + AtomicBoolean closed = new AtomicBoolean(); + return () -> { + if (closed.compareAndSet(false, true)) { + entries.remove(key, entry); + } + }; + } + + public Optional resolve(MotdContext context) { + Objects.requireNonNull(context, "context"); + return entries.entrySet().stream() + .sorted(Comparator.>comparingInt(value -> value.getValue().priority()) + .reversed().thenComparing(Map.Entry::getKey)) + .map(value -> contribute(value.getValue(), context)) + .flatMap(Optional::stream) + .findFirst(); + } + + public int size() { + return entries.size(); + } + + private static Optional contribute(Entry entry, MotdContext context) { + try { + Optional contribution = entry.contributor().contribute(context); + return contribution == null ? Optional.empty() : contribution.filter(value -> + value.firstLine().isPresent() || value.secondLine().isPresent()); + } catch (RuntimeException ignored) { + return Optional.empty(); + } + } + + private static String requireOwner(String owner) { + Objects.requireNonNull(owner, "owner"); + String normalized = owner.trim().toLowerCase(java.util.Locale.ROOT); + if (normalized.isEmpty()) { + throw new IllegalArgumentException("owner must not be blank"); + } + return normalized; + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/Feature.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/Feature.java similarity index 82% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/Feature.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/Feature.java index b043f2c5..b348e8b1 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/Feature.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/Feature.java @@ -1,8 +1,8 @@ -package nl.hauntedmc.proxyfeatures.api.feature; +package nl.hauntedmc.proxyfeatures.framework.feature; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactory.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactory.java index d88f89b3..22815d10 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactory.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactory.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.framework.feature; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.config.FeatureStoragePaths; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/meta/BaseMeta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/meta/BaseMeta.java similarity index 86% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/meta/BaseMeta.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/meta/BaseMeta.java index 0d746f86..7dcb41df 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/meta/BaseMeta.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/meta/BaseMeta.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.feature.meta; +package nl.hauntedmc.proxyfeatures.framework.feature.meta; import java.util.List; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/stateful/SnapshotState.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/stateful/SnapshotState.java similarity index 67% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/stateful/SnapshotState.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/stateful/SnapshotState.java index 912211bf..dea73ecf 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/stateful/SnapshotState.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/stateful/SnapshotState.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.feature.stateful; +package nl.hauntedmc.proxyfeatures.framework.feature.stateful; /** * Marker for framework-managed feature state that may survive a full feature reload. diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/stateful/StatefulFeature.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/stateful/StatefulFeature.java similarity index 90% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/stateful/StatefulFeature.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/stateful/StatefulFeature.java index f4de5597..206805b1 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/feature/stateful/StatefulFeature.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/stateful/StatefulFeature.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.feature.stateful; +package nl.hauntedmc.proxyfeatures.framework.feature.stateful; import java.util.Optional; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureApiManager.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureApiManager.java index 0ed8f47c..b4656e44 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureApiManager.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureApiManager.java @@ -1,110 +1,88 @@ package nl.hauntedmc.proxyfeatures.framework.lifecycle; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; -import nl.hauntedmc.dataregistry.api.service.FeatureServiceHandle; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureId; +import nl.hauntedmc.proxyfeatures.framework.service.CapabilityRegistration; +import nl.hauntedmc.proxyfeatures.framework.service.DefaultCapabilityRegistry; +import nl.hauntedmc.proxyfeatures.framework.service.InternalServiceRegistry; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.function.Supplier; /** - * Tracks feature-owned API/service registrations and detaches them on cleanup. + * Tracks feature-owned capability registrations and detaches them on cleanup. */ public class FeatureApiManager { - private final Map, Object> registeredServices = new LinkedHashMap<>(); - private final Map, FeatureServiceHandle> dataRegistryServices = new LinkedHashMap<>(); - private String ownerPlugin; - private String ownerFeature; - private Supplier> dataRegistrySupplier = Optional::empty; + private final Map, RegisteredCapability> registeredServices = new LinkedHashMap<>(); + private DefaultCapabilityRegistry registry; + private InternalServiceRegistry internalRegistry; + private FeatureId owner; - /** - * Binds DataRegistryApi-backed service publication to this feature owner. - */ - public synchronized void bindDataRegistryCatalog( - String ownerPlugin, - String ownerFeature, - Supplier> dataRegistrySupplier + /** Binds this lifecycle manager to the authoritative registry and one feature owner. */ + public synchronized void bindRegistry( + DefaultCapabilityRegistry registry, + InternalServiceRegistry internalRegistry, + String ownerFeature ) { - this.ownerPlugin = requireText(ownerPlugin, "ownerPlugin"); - this.ownerFeature = requireText(ownerFeature, "ownerFeature"); - this.dataRegistrySupplier = Objects.requireNonNull(dataRegistrySupplier, "dataRegistrySupplier"); + this.registry = Objects.requireNonNull(registry, "registry"); + this.internalRegistry = Objects.requireNonNull(internalRegistry, "internalRegistry"); + this.owner = FeatureId.of(requireText(ownerFeature, "ownerFeature")); } - public synchronized void registerService(Class type, T instance) { + public synchronized void registerInternalService(Class type, T instance) { Objects.requireNonNull(type, "type"); Objects.requireNonNull(instance, "instance"); - - Object previous = registeredServices.get(type); - FeatureServiceHandle previousHandle = dataRegistryServices.get(type); - if (previous == instance && previousHandle != null) { + if (internalRegistry == null || owner == null) { + throw new IllegalStateException("Feature API manager is not bound to an internal service registry"); + } + RegisteredCapability previous = registeredServices.get(type); + if (previous != null && previous.instance() == instance) { return; } + if (previous != null) { + previous.registration().close(); + } + CapabilityRegistration registration = internalRegistry.register(owner, type, instance); + registeredServices.put(type, new RegisteredCapability(instance, registration)); + } - FeatureServiceHandle handle = registerWithDataRegistry(type, instance); - registeredServices.put(type, instance); + public synchronized void registerService(Class type, T instance) { + Objects.requireNonNull(type, "type"); + Objects.requireNonNull(instance, "instance"); - dataRegistryServices.remove(type); - if (handle != null) { - dataRegistryServices.put(type, handle); + if (registry == null || owner == null) { + throw new IllegalStateException("Feature API manager is not bound to a capability registry"); + } + RegisteredCapability previous = registeredServices.get(type); + if (previous != null && previous.instance() == instance) { + return; } - if (previousHandle != null) { - previousHandle.close(); + if (previous != null) { + previous.registration().close(); } + CapabilityRegistration registration = registry.register(owner, type, instance); + registeredServices.put(type, new RegisteredCapability(instance, registration)); } public synchronized void unregisterService(Class type) { Objects.requireNonNull(type, "type"); - registeredServices.remove(type); - closeDataRegistryHandle(type); + RegisteredCapability registered = registeredServices.remove(type); + if (registered != null) { + registered.registration().close(); + } } public synchronized void unregisterAllServices() { - if (registeredServices.isEmpty() && dataRegistryServices.isEmpty()) { - return; - } - - for (var entry : new LinkedHashMap<>(registeredServices).entrySet()) { - closeDataRegistryHandle(entry.getKey()); - } + registeredServices.values().forEach(value -> value.registration().close()); registeredServices.clear(); - dataRegistryServices.values().forEach(FeatureServiceHandle::close); - dataRegistryServices.clear(); } public synchronized int getRegisteredServiceCount() { return registeredServices.size(); } - private FeatureServiceHandle registerWithDataRegistry(Class type, T instance) { - if (ownerPlugin == null || ownerFeature == null) { - return null; - } - return currentDataRegistry() - .map(dataRegistry -> dataRegistry.featureServices().register( - ownerPlugin, - ownerFeature, - type, - instance - )) - .orElse(null); - } - - private Optional currentDataRegistry() { - Optional dataRegistry = dataRegistrySupplier.get(); - return dataRegistry == null ? Optional.empty() : dataRegistry; - } - - private void closeDataRegistryHandle(Class type) { - FeatureServiceHandle handle = dataRegistryServices.remove(type); - if (handle != null) { - handle.close(); - } - } - private static String requireText(String value, String fieldName) { Objects.requireNonNull(value, fieldName); String normalized = value.trim(); @@ -113,4 +91,7 @@ private static String requireText(String value, String fieldName) { } return normalized; } + + private record RegisteredCapability(Object instance, CapabilityRegistration registration) { + } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCacheManager.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCacheManager.java index def1d19c..efa90ddf 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCacheManager.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCacheManager.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.framework.lifecycle; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheDirectory; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheDirectory; import java.io.File; import java.util.Objects; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCommandManager.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCommandManager.java index acddb84d..4727ba8d 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCommandManager.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCommandManager.java @@ -5,8 +5,8 @@ import com.velocitypowered.api.command.CommandMeta; import com.velocitypowered.api.command.CommandSource; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; import java.util.*; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureLifecycleFactory.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureLifecycleFactory.java index ec734acc..bf736f0b 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureLifecycleFactory.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureLifecycleFactory.java @@ -80,10 +80,10 @@ public FeatureLifecycleManager createLifecycleManager(String featureName) { FeatureLifecycleManager lifecycleManager = createLifecycleManager(); lifecycleManager.getDataManager().bindToFeature(featureName); if (plugin != null) { - lifecycleManager.getApiManager().bindDataRegistryCatalog( - "ProxyFeatures", - featureName, - plugin::getDataRegistry + lifecycleManager.getApiManager().bindRegistry( + plugin.getCapabilityRegistry(), + plugin.getInternalServiceRegistry(), + featureName ); } return lifecycleManager; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeatures.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeatures.java new file mode 100644 index 00000000..eec5f771 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeatures.java @@ -0,0 +1,76 @@ +package nl.hauntedmc.proxyfeatures.framework.loader; + +import nl.hauntedmc.proxyfeatures.api.feature.FeatureClassification; + +import java.util.List; +import java.util.Set; + +/** Explicit manifest of every built-in feature shipped by ProxyFeatures 3.3.0. */ +public final class BuiltInFeatures { + private static final String ROOT = "nl.hauntedmc.proxyfeatures.features."; + + public record Definition( + String featureClassName, + FeatureClassification classification, + Set providedCapabilities + ) { + public Definition(String featureClassName, FeatureClassification classification, String... capabilities) { + this(featureClassName, classification, Set.of(capabilities)); + } + } + + private static final List DEFINITIONS = List.of( + internal("announcer", "Announcer"), + internal("antibot", "AntiBot"), + provider("antivpn", "AntiVPN", "NetworkLocationApi"), + internal("broadcast", "Broadcast"), + provider("capacity", "Capacity", "AdmissionApi"), + internal("clientinfo", "ClientInfo"), + internal("commandhider", "CommandHider"), + internal("commandlogger", "CommandLogger"), + internal("commandrelay", "CommandRelay"), + internal("connectioninfo", "ConnectionInfo"), + provider("friends", "Friends", "FriendshipApi"), + internal("hlink", "HLink"), + internal("hub", "Hub"), + provider("maintenance", "Maintenance", "MaintenanceApi"), + consumer("messager", "Messenger"), + consumer("motd", "Motd"), + provider("playercount", "PlayerCount", "PlayerCountApi"), + consumer("playerinfo", "PlayerInfo"), + provider("playerlanguage", "PlayerLanguage", "PlayerLanguageApi"), + consumer("playerlist", "PlayerList"), + internal("proxyinfo", "ProxyInfo"), + provider("queue", "Queue", "QueueApi"), + internal("resourcepack", "ResourcePack"), + provider("restart", "Restart", "RestartApi"), + provider("sanctions", "Sanctions", "SanctionsApi"), + internal("serverlinks", "ServerLinks"), + internal("slashserver", "SlashServer"), + internal("staffchat", "StaffChat"), + internal("textcommands", "TextCommands"), + provider("twofactor", "TwoFactor", "TwoFactorApi"), + provider("vanish", "Vanish", "PresenceApi"), + provider("versioncheck", "VersionCheck", "VersionApi"), + internal("votifier", "Votifier") + ); + + private BuiltInFeatures() { + } + + public static List definitions() { + return DEFINITIONS; + } + + private static Definition provider(String path, String type, String... capabilities) { + return new Definition(ROOT + path + '.' + type, FeatureClassification.CAPABILITY_PROVIDER, capabilities); + } + + private static Definition consumer(String path, String type) { + return new Definition(ROOT + path + '.' + type, FeatureClassification.CAPABILITY_CONSUMER); + } + + private static Definition internal(String path, String type) { + return new Definition(ROOT + path + '.' + type, FeatureClassification.INTERNAL); + } +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptor.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptor.java index 810f2abe..adf2fe13 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptor.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptor.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.framework.loader; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import java.util.Collections; import java.util.LinkedHashSet; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManager.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManager.java index e11fea1e..d7d38401 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManager.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManager.java @@ -1,9 +1,11 @@ package nl.hauntedmc.proxyfeatures.framework.loader; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.feature.stateful.SnapshotState; -import nl.hauntedmc.proxyfeatures.api.feature.stateful.StatefulFeature; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureId; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureState; +import nl.hauntedmc.proxyfeatures.framework.feature.stateful.SnapshotState; +import nl.hauntedmc.proxyfeatures.framework.feature.stateful.StatefulFeature; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import nl.hauntedmc.proxyfeatures.framework.feature.FeatureScopeFactory; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.features.FeatureFactory; @@ -46,21 +48,13 @@ public FeatureLoadManager(ProxyFeatures plugin, FeatureScopeFactory featureScope } private void discoverFeatures() { - plugin.getLogger().info("[FeatureScanner] Scanning for features..."); - try (var scanResult = new io.github.classgraph.ClassGraph() - .enableClassInfo() - .acceptPackages("nl.hauntedmc.proxyfeatures.features") - .scan()) { - scanResult.getSubclasses(VelocityBaseFeature.class.getName()).forEach(classInfo -> { - if (classInfo.isAbstract()) { - return; - } - - String registryName = classInfo.getSimpleName(); - String featureClassName = classInfo.getName(); + plugin.getLogger().info("Loading the explicit built-in feature manifest..."); + for (BuiltInFeatures.Definition definition : BuiltInFeatures.definitions()) { + String featureClassName = definition.featureClassName(); + String registryName = simpleClassName(featureClassName); Optional descriptorOptional = buildDescriptor(registryName, featureClassName); if (descriptorOptional.isEmpty()) { - return; + continue; } FeatureDescriptor descriptor = descriptorOptional.get(); @@ -76,11 +70,11 @@ private void discoverFeatures() { descriptor.registryName(), existing.featureClassName() ); - return; + continue; } featureRegistry.registerAvailableFeature(descriptor); - }); + plugin.getFeatureCatalog().register(toPublicDescriptor(descriptor, definition)); } pruneFeaturesWithMissingDependencies(); @@ -89,6 +83,23 @@ private void discoverFeatures() { plugin.getLogger().info("Discovered features: {}", featureRegistry.getAvailableFeatures().keySet()); } + private static nl.hauntedmc.proxyfeatures.api.feature.FeatureDescriptor toPublicDescriptor( + FeatureDescriptor descriptor, + BuiltInFeatures.Definition definition + ) { + Set dependencies = descriptor.featureDependencies().stream() + .map(FeatureId::of) + .collect(java.util.stream.Collectors.toUnmodifiableSet()); + return new nl.hauntedmc.proxyfeatures.api.feature.FeatureDescriptor( + FeatureId.of(descriptor.registryName()), + descriptor.featureName(), + descriptor.featureVersion(), + definition.classification(), + dependencies, + definition.providedCapabilities() + ); + } + private Optional buildDescriptor(String registryName, String featureClassName) { Optional metaOptional = resolveMeta(featureClassName); if (metaOptional.isEmpty()) { @@ -549,12 +560,15 @@ public FeatureDisableResponse disableFeature(String featureName) { } try { + plugin.getFeatureCatalog().transition(FeatureId.of(featureKey), FeatureState.STOPPING); feature.cleanup(); mainConfigHandler.setFeatureEnabled(featureKey, false); featureRegistry.deregisterLoadedFeature(featureKey); + plugin.getFeatureCatalog().transition(FeatureId.of(featureKey), FeatureState.DISABLED); plugin.getLogger().info("Feature disabled: {}", featureKey); return new FeatureDisableResponse(FeatureDisableResult.SUCCESS, featureKey, dependents); } catch (Throwable t) { + plugin.getFeatureCatalog().fail(FeatureId.of(featureKey), t); plugin.getLogger().error("Disable failed: {}", featureKey, t); return new FeatureDisableResponse(FeatureDisableResult.FAILED, featureKey, dependents); } @@ -591,8 +605,10 @@ public FeatureReloadResponse reloadFeature(String featureName) { VelocityBaseFeature feature = featureRegistry.getLoadedFeature(featureKey); Optional reloadState = captureReloadState(featureKey, feature); + plugin.getFeatureCatalog().transition(FeatureId.of(featureKey), FeatureState.STOPPING); feature.cleanup(); featureRegistry.deregisterLoadedFeature(featureKey); + plugin.getFeatureCatalog().transition(FeatureId.of(featureKey), FeatureState.DISABLED); boolean hasReloaded = reloadState.isPresent() ? loadFeature(featureKey, reloadState.get()) @@ -614,6 +630,7 @@ public FeatureReloadResponse reloadFeature(String featureName) { return new FeatureReloadResponse(FeatureReloadResult.SUCCESS, featureKey, reloadedDependents); } catch (Throwable t) { + plugin.getFeatureCatalog().fail(FeatureId.of(featureKey), t); plugin.getLogger().error("Reload failed for: {}", featureKey, t); return new FeatureReloadResponse(FeatureReloadResult.FAILED, featureKey, reloadedDependents); } @@ -663,50 +680,50 @@ private boolean loadFeature(String featureName, SnapshotState reloadState) { return false; } - FeatureContext context = createFeatureContext(descriptor); - if (context == null) { - return false; - } - - VelocityBaseFeature feature = FeatureFactory.createFeature(descriptor.featureClassName(), context); - if (feature == null) { - return false; - } + VelocityBaseFeature feature = null; + plugin.getFeatureCatalog().transition(FeatureId.of(featureKey), FeatureState.STARTING); + try { + FeatureContext context = createFeatureContext(descriptor); + if (context == null) { + throw new IllegalStateException("Unable to create the feature context"); + } - feature.getConfigHandler().injectDefaults(feature.getDefaultConfig()); - feature.getLocalizationHandler().registerDefaultMessages(feature.getDefaultMessages()); - feature.getConfigHandler().reloadConfig(); - feature.getLocalizationHandler().reloadLocalization(); + feature = FeatureFactory.createFeature(descriptor.featureClassName(), context); + if (feature == null) { + throw new IllegalStateException("Unable to instantiate the feature"); + } - try { + feature.getConfigHandler().injectDefaults(feature.getDefaultConfig()); + feature.getLocalizationHandler().registerDefaultMessages(feature.getDefaultMessages()); + feature.getConfigHandler().reloadConfig(); + feature.getLocalizationHandler().reloadLocalization(); feature.initialize(); - } catch (Throwable t) { - plugin.getLogger().error("Feature '{}' failed to initialize.", featureKey, t); - try { - feature.cleanup(); - } catch (Throwable cleanupError) { - plugin.getLogger().error("Feature '{}' failed to cleanup after initialization failure.", featureKey, cleanupError); - } - return false; - } - if (reloadState != null) { - try { + if (reloadState != null) { restoreReloadState(featureKey, feature, reloadState); - } catch (Throwable t) { - plugin.getLogger().error("Feature '{}' failed to restore reload state.", featureKey, t); + } + + featureRegistry.registerLoadedFeature(featureKey, feature); + plugin.getFeatureCatalog().transition(FeatureId.of(featureKey), FeatureState.ACTIVE); + plugin.getLogger().info("Feature loaded: {}", featureKey); + return true; + } catch (Throwable failure) { + plugin.getFeatureCatalog().fail(FeatureId.of(featureKey), failure); + plugin.getLogger().error("Feature '{}' failed to start.", featureKey, failure); + if (feature != null) { try { feature.cleanup(); } catch (Throwable cleanupError) { - plugin.getLogger().error("Feature '{}' failed to cleanup after reload-state restore failure.", featureKey, cleanupError); + plugin.getLogger().error( + "Feature '{}' failed to cleanup after its startup failure.", + featureKey, + cleanupError + ); } - return false; } + featureRegistry.deregisterLoadedFeature(featureKey); + return false; } - - featureRegistry.registerLoadedFeature(featureKey, feature); - plugin.getLogger().info("Feature loaded: {}", featureKey); - return true; } private Optional captureReloadState(String featureKey, VelocityBaseFeature feature) { @@ -762,16 +779,24 @@ public FeatureRegistry getFeatureRegistry() { public void unloadAllFeatures() { plugin.getLogger().info("Unloading all loaded features..."); List loadedFeatureNames = new ArrayList<>(featureRegistry.getLoadedFeatureNames()); + Collections.reverse(loadedFeatureNames); for (String featureName : loadedFeatureNames) { VelocityBaseFeature feature = featureRegistry.getLoadedFeature(featureName); try { if (feature != null) { + plugin.getFeatureCatalog().transition(FeatureId.of(featureName), FeatureState.STOPPING); feature.cleanup(); } } catch (Throwable t) { + plugin.getFeatureCatalog().fail(FeatureId.of(featureName), t); plugin.getLogger().error("Failed to cleanup feature during unload: {}", featureName, t); } finally { featureRegistry.deregisterLoadedFeature(featureName); + if (plugin.getFeatureCatalog().find(FeatureId.of(featureName)) + .filter(snapshot -> snapshot.state() != FeatureState.FAILED) + .isPresent()) { + plugin.getFeatureCatalog().transition(FeatureId.of(featureName), FeatureState.DISABLED); + } } } plugin.getLogger().info("All features have been unloaded."); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandler.java index bee432a7..f8db4623 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandler.java @@ -5,15 +5,15 @@ import net.kyori.adventure.text.Component; import org.slf4j.Logger; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigService; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigView; -import nl.hauntedmc.proxyfeatures.api.io.localization.Language; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; -import nl.hauntedmc.proxyfeatures.api.util.text.format.ComponentFormatter; -import nl.hauntedmc.proxyfeatures.api.util.text.format.TextFormatter; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; -import nl.hauntedmc.proxyfeatures.features.playerlanguage.api.LanguageAPI; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigView; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; import nl.hauntedmc.proxyfeatures.framework.config.FeatureStoragePaths; import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import org.spongepowered.configurate.CommentedConfigurationNode; @@ -48,8 +48,9 @@ public LocalizationHandler(ProxyFeatures plugin, ConfigService configService) { plugin.getClass().getClassLoader(), configService, player -> FeatureServices - .find(plugin, LanguageAPI.class) - .map(api -> api.get(player.getUniqueId())) + .find(plugin, PlayerLanguageApi.class) + .flatMap(api -> api.resolvedLanguage(player.getUniqueId())) + .map(locale -> "nl".equalsIgnoreCase(locale.getLanguage()) ? Language.NL : Language.EN) .orElse(Language.NL) ); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/log/ConnectionLogHelper.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/log/ConnectionLogHelper.java index 4cc12994..bf4dc5db 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/log/ConnectionLogHelper.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/log/ConnectionLogHelper.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.api.util.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; import java.net.InetAddress; import java.net.InetSocketAddress; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/IpAddressUtil.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/network/IpAddressUtil.java similarity index 70% rename from proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/IpAddressUtil.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/network/IpAddressUtil.java index 89854c86..0cc7bddf 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/internal/IpAddressUtil.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/network/IpAddressUtil.java @@ -1,23 +1,17 @@ -package nl.hauntedmc.proxyfeatures.features.antibot.internal; +package nl.hauntedmc.proxyfeatures.framework.network; import java.net.InetAddress; import java.net.UnknownHostException; +/** Shared strict IP-literal parser. It never performs DNS resolution for hostnames. */ public final class IpAddressUtil { - private IpAddressUtil() { } public static InetAddress parseLiteral(String input) { - if (input == null || input.isBlank()) { - return null; - } - + if (input == null || input.isBlank()) return null; String candidate = stripBrackets(input.trim()); - if (candidate.isEmpty()) { - return null; - } - + if (candidate.isEmpty()) return null; if (candidate.indexOf(':') >= 0) { try { return InetAddress.getByName(candidate); @@ -25,12 +19,8 @@ public static InetAddress parseLiteral(String input) { return null; } } - byte[] ipv4 = parseIpv4(candidate); - if (ipv4 == null) { - return null; - } - + if (ipv4 == null) return null; try { return InetAddress.getByAddress(ipv4); } catch (UnknownHostException ignored) { @@ -45,36 +35,26 @@ public static String normalizeLiteral(String input) { private static byte[] parseIpv4(String candidate) { String[] parts = candidate.split("\\.", -1); - if (parts.length != 4) { - return null; - } - + if (parts.length != 4) return null; byte[] bytes = new byte[4]; for (int index = 0; index < parts.length; index++) { String part = parts[index]; - if (part.isEmpty() || part.length() > 3) { - return null; - } - + if (part.isEmpty() || part.length() > 3) return null; int value = 0; for (int charIndex = 0; charIndex < part.length(); charIndex++) { char character = part.charAt(charIndex); - if (!Character.isDigit(character)) { - return null; - } + if (!Character.isDigit(character)) return null; value = (value * 10) + (character - '0'); } - - if (value < 0 || value > 255) { - return null; - } + if (value > 255) return null; bytes[index] = (byte) value; } return bytes; } private static String stripBrackets(String candidate) { - if (candidate.length() >= 2 && candidate.charAt(0) == '[' && candidate.charAt(candidate.length() - 1) == ']') { + if (candidate.length() >= 2 && candidate.charAt(0) == '[' + && candidate.charAt(candidate.length() - 1) == ']') { return candidate.substring(1, candidate.length() - 1); } return candidate; diff --git a/proxyfeatures-contracts/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReference.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReference.java similarity index 100% rename from proxyfeatures-contracts/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReference.java rename to proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReference.java diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/CapabilityRegistration.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/CapabilityRegistration.java new file mode 100644 index 00000000..24e37e5e --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/CapabilityRegistration.java @@ -0,0 +1,8 @@ +package nl.hauntedmc.proxyfeatures.framework.service; + +/** Internal idempotent handle for one feature-owned capability registration. */ +@FunctionalInterface +public interface CapabilityRegistration extends AutoCloseable { + @Override + void close(); +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultCapabilityRegistry.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultCapabilityRegistry.java new file mode 100644 index 00000000..4c6c2e7b --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultCapabilityRegistry.java @@ -0,0 +1,102 @@ +package nl.hauntedmc.proxyfeatures.framework.service; + +import nl.hauntedmc.proxyfeatures.api.feature.FeatureId; +import nl.hauntedmc.proxyfeatures.api.service.CapabilityRef; +import nl.hauntedmc.proxyfeatures.api.service.CapabilityRegistry; + +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +/** Thread-safe, ProxyFeatures-owned capability registry. */ +public final class DefaultCapabilityRegistry implements CapabilityRegistry { + + private record Provider(FeatureId owner, Object instance) { + } + + private final ConcurrentHashMap, Provider> providers = new ConcurrentHashMap<>(); + private final ConcurrentHashMap, CapabilityRef> references = new ConcurrentHashMap<>(); + + public CapabilityRegistration register(FeatureId owner, Class type, T instance) { + Objects.requireNonNull(owner, "owner"); + Objects.requireNonNull(type, "type"); + Objects.requireNonNull(instance, "instance"); + if (!type.isInterface()) { + throw new IllegalArgumentException("Capability contract must be an interface: " + type.getName()); + } + if (!type.getPackageName().startsWith("nl.hauntedmc.proxyfeatures.api.")) { + throw new IllegalArgumentException("Capability contract must come from proxyfeatures-api: " + type.getName()); + } + if (!type.isInstance(instance)) { + throw new IllegalArgumentException("Capability implementation does not implement " + type.getName()); + } + + Provider provider = new Provider(owner, instance); + providers.compute(type, (ignored, current) -> { + if (current != null) { + throw new IllegalStateException( + "Capability " + type.getName() + " is already provided by " + current.owner() + ); + } + return provider; + }); + + AtomicBoolean closed = new AtomicBoolean(); + return () -> { + if (closed.compareAndSet(false, true)) { + providers.remove(type, provider); + } + }; + } + + @Override + public CapabilityRef reference(Class type) { + Objects.requireNonNull(type, "type"); + CapabilityRef reference = references.computeIfAbsent(type, DefaultCapabilityRef::new); + return typeSafeReference(type, reference); + } + + @Override + public Set> availableTypes() { + return Set.copyOf(new LinkedHashSet<>(providers.keySet())); + } + + public Optional owner(Class type) { + Provider provider = providers.get(Objects.requireNonNull(type, "type")); + return provider == null ? Optional.empty() : Optional.of(provider.owner()); + } + + private Optional resolve(Class type) { + Provider provider = providers.get(type); + return provider == null ? Optional.empty() : Optional.of(type.cast(provider.instance())); + } + + @SuppressWarnings("unchecked") + private static CapabilityRef typeSafeReference(Class type, CapabilityRef reference) { + if (reference.type() != type) { + throw new IllegalStateException("Capability reference type mismatch"); + } + return (CapabilityRef) reference; + } + + private final class DefaultCapabilityRef implements CapabilityRef { + private final Class type; + + private DefaultCapabilityRef(Class type) { + this.type = type; + } + + @Override + public Class type() { + return type; + } + + @Override + public Optional get() { + return resolve(type); + } + } +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalog.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalog.java new file mode 100644 index 00000000..8cad9237 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalog.java @@ -0,0 +1,80 @@ +package nl.hauntedmc.proxyfeatures.framework.service; + +import nl.hauntedmc.proxyfeatures.api.feature.FeatureCatalog; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureDescriptor; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureId; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureSnapshot; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureState; + +import java.time.Clock; +import java.time.Instant; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** Thread-safe public projection of the runtime feature lifecycle. */ +public final class DefaultFeatureCatalog implements FeatureCatalog { + + private record Entry(FeatureDescriptor descriptor, FeatureState state, Optional failure) { + } + + private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); + private final Clock clock; + + public DefaultFeatureCatalog() { + this(Clock.systemUTC()); + } + + DefaultFeatureCatalog(Clock clock) { + this.clock = Objects.requireNonNull(clock, "clock"); + } + + public void register(FeatureDescriptor descriptor) { + Objects.requireNonNull(descriptor, "descriptor"); + entries.put(descriptor.id(), new Entry(descriptor, FeatureState.DISABLED, Optional.empty())); + } + + public void transition(FeatureId id, FeatureState state) { + transition(id, state, Optional.empty()); + } + + public void fail(FeatureId id, Throwable failure) { + Objects.requireNonNull(failure, "failure"); + String message = failure.getMessage(); + transition(id, FeatureState.FAILED, Optional.of( + message == null || message.isBlank() ? failure.getClass().getSimpleName() : message + )); + } + + @Override + public Optional find(FeatureId id) { + Entry entry = entries.get(Objects.requireNonNull(id, "id")); + return entry == null ? Optional.empty() : Optional.of(snapshot(entry, clock.instant())); + } + + @Override + public List snapshot() { + Instant observedAt = clock.instant(); + return entries.values().stream() + .map(entry -> snapshot(entry, observedAt)) + .sorted(Comparator.comparing(value -> value.descriptor().id())) + .toList(); + } + + private void transition(FeatureId id, FeatureState state, Optional failure) { + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(state, "state"); + entries.compute(id, (ignored, current) -> { + if (current == null) { + throw new IllegalArgumentException("Unknown feature: " + id); + } + return new Entry(current.descriptor(), state, failure); + }); + } + + private static FeatureSnapshot snapshot(Entry entry, Instant observedAt) { + return new FeatureSnapshot(entry.descriptor(), entry.state(), entry.failure(), observedAt); + } +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultProxyFeaturesApi.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultProxyFeaturesApi.java new file mode 100644 index 00000000..e14c19a1 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultProxyFeaturesApi.java @@ -0,0 +1,40 @@ +package nl.hauntedmc.proxyfeatures.framework.service; + +import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesApi; +import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesApiVersion; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureCatalog; +import nl.hauntedmc.proxyfeatures.api.service.CapabilityRegistry; + +import java.util.Objects; + +/** Immutable root API facade backed by reload-aware registries. */ +public final class DefaultProxyFeaturesApi implements ProxyFeaturesApi { + private final ProxyFeaturesApiVersion version; + private final CapabilityRegistry capabilities; + private final FeatureCatalog features; + + public DefaultProxyFeaturesApi( + ProxyFeaturesApiVersion version, + CapabilityRegistry capabilities, + FeatureCatalog features + ) { + this.version = Objects.requireNonNull(version, "version"); + this.capabilities = Objects.requireNonNull(capabilities, "capabilities"); + this.features = Objects.requireNonNull(features, "features"); + } + + @Override + public ProxyFeaturesApiVersion version() { + return version; + } + + @Override + public CapabilityRegistry capabilities() { + return capabilities; + } + + @Override + public FeatureCatalog features() { + return features; + } +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/FeatureServices.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/FeatureServices.java index 6bcec00c..fca09d56 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/FeatureServices.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/FeatureServices.java @@ -7,7 +7,7 @@ import java.util.Optional; /** - * Resolves APIs exported by enabled features through DataRegistryApi's shared service catalog. + * Internal convenience access to the authoritative ProxyFeatures capability registry. */ public final class FeatureServices { @@ -20,14 +20,13 @@ private FeatureServices() { public static Optional find(ProxyFeatures plugin, Class apiType) { Objects.requireNonNull(plugin, "plugin"); Objects.requireNonNull(apiType, "apiType"); - return plugin.getDataRegistry() - .flatMap(dataRegistry -> dataRegistry.featureServices().find(apiType)); + return plugin.capabilities().reference(apiType).get(); } /** * Resolves a required enabled feature service by API type. * - * @throws IllegalStateException when DataRegistryApi is unavailable or the owning feature did not publish the API. + * @throws IllegalStateException when the owning feature did not publish the capability */ public static T require(ProxyFeatures plugin, Class apiType) { return find(plugin, apiType).orElseThrow(() -> missing(apiType)); @@ -44,7 +43,7 @@ public static Optional find(VelocityBaseFeature feature, Class apiT /** * Resolves a required enabled feature service by API type. * - * @throws IllegalStateException when DataRegistryApi is unavailable or the owning feature did not publish the API. + * @throws IllegalStateException when the owning feature did not publish the capability */ public static T require(VelocityBaseFeature feature, Class apiType) { Objects.requireNonNull(feature, "feature"); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServiceRegistry.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServiceRegistry.java new file mode 100644 index 00000000..2a481499 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServiceRegistry.java @@ -0,0 +1,52 @@ +package nl.hauntedmc.proxyfeatures.framework.service; + +import nl.hauntedmc.proxyfeatures.api.feature.FeatureId; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +/** Runtime-only registry for collaboration ports that must never be exposed as public API. */ +public final class InternalServiceRegistry { + private record Provider(FeatureId owner, Object instance) { + } + + private final ConcurrentHashMap, Provider> providers = new ConcurrentHashMap<>(); + + public CapabilityRegistration register(FeatureId owner, Class type, T instance) { + Objects.requireNonNull(owner, "owner"); + Objects.requireNonNull(type, "type"); + Objects.requireNonNull(instance, "instance"); + if (!type.isInterface()) { + throw new IllegalArgumentException("Internal service contract must be an interface: " + type.getName()); + } + if (!type.isInstance(instance)) { + throw new IllegalArgumentException("Service implementation does not implement " + type.getName()); + } + Provider provider = new Provider(owner, instance); + providers.compute(type, (ignored, current) -> { + if (current != null) { + throw new IllegalStateException(type.getName() + " is already provided by " + current.owner()); + } + return provider; + }); + AtomicBoolean closed = new AtomicBoolean(); + return () -> { + if (closed.compareAndSet(false, true)) { + providers.remove(type, provider); + } + }; + } + + public Optional find(Class type) { + Provider provider = providers.get(Objects.requireNonNull(type, "type")); + return provider == null ? Optional.empty() : Optional.of(type.cast(provider.instance())); + } + + public T require(Class type) { + return find(type).orElseThrow(() -> new IllegalStateException( + "Internal feature service is unavailable: " + type.getName() + )); + } +} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServices.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServices.java new file mode 100644 index 00000000..f2b1df80 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServices.java @@ -0,0 +1,24 @@ +package nl.hauntedmc.proxyfeatures.framework.service; + +import nl.hauntedmc.proxyfeatures.ProxyFeatures; +import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; + +import java.util.Optional; + +/** Internal convenience access to runtime-only collaboration ports. */ +public final class InternalServices { + private InternalServices() { + } + + public static Optional find(VelocityBaseFeature feature, Class type) { + return feature.getPlugin().getInternalServiceRegistry().find(type); + } + + public static T require(VelocityBaseFeature feature, Class type) { + return feature.getPlugin().getInternalServiceRegistry().require(type); + } + + public static Optional find(ProxyFeatures plugin, Class type) { + return plugin.getInternalServiceRegistry().find(type); + } +} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/architecture/ArchitectureBoundaryTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/architecture/ArchitectureBoundaryTest.java new file mode 100644 index 00000000..894aa396 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/architecture/ArchitectureBoundaryTest.java @@ -0,0 +1,123 @@ +package nl.hauntedmc.proxyfeatures.architecture; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ArchitectureBoundaryTest { + + private static final Path MODULE = Path.of("").toAbsolutePath().normalize(); + private static final Path REPOSITORY = MODULE.getParent(); + private static final Pattern FEATURE_IMPORT = Pattern.compile( + "import\\s+(?:static\\s+)?nl\\.hauntedmc\\.proxyfeatures\\.features\\.([a-z0-9]+)\\." + ); + + @Test + void featureImplementationsDoNotImportOtherFeatureImplementations() throws IOException { + Path featureRoot = MODULE.resolve( + "src/main/java/nl/hauntedmc/proxyfeatures/features" + ); + List violations = new ArrayList<>(); + + for (Path source : javaSources(featureRoot)) { + Path relative = featureRoot.relativize(source); + if (relative.getNameCount() < 2) { + continue; + } + String ownerFeature = relative.getName(0).toString(); + Matcher imports = FEATURE_IMPORT.matcher(Files.readString(source)); + while (imports.find()) { + String importedFeature = imports.group(1); + if (!ownerFeature.equals(importedFeature)) { + violations.add(relative + " imports feature " + importedFeature); + } + } + } + + assertEquals(List.of(), violations, + "Cross-feature behavior must use public capabilities or runtime collaboration ports"); + } + + @Test + void publicApiHasNoPlatformToolkitOrThirdPartyDependencies() throws IOException { + Path apiRoot = REPOSITORY.resolve("proxyfeatures-api/src/main/java"); + List violations = new ArrayList<>(); + + for (Path source : javaSources(apiRoot)) { + for (String line : Files.readAllLines(source)) { + String trimmed = line.trim(); + if (!trimmed.startsWith("import ")) { + continue; + } + if (!trimmed.startsWith("import java.") + && !trimmed.startsWith("import nl.hauntedmc.proxyfeatures.api.")) { + violations.add(apiRoot.relativize(source) + ": " + trimmed); + } + } + } + + assertEquals(List.of(), violations, "proxyfeatures-api must remain dependency-free"); + String apiPom = Files.readString(REPOSITORY.resolve("proxyfeatures-api/pom.xml")); + assertFalse(apiPom.contains("compile")); + assertFalse(apiPom.contains("provided")); + } + + @Test + void contractsContainOnlyWireMessagesAndNoRuntimePersistenceTypes() throws IOException { + Path contractsRoot = REPOSITORY.resolve("proxyfeatures-contracts/src/main/java"); + List sources = javaSources(contractsRoot); + + assertEquals(7, sources.size()); + for (Path source : sources) { + String relative = contractsRoot.relativize(source).toString(); + String text = Files.readString(source); + assertTrue(relative.contains("/messaging/"), relative); + assertFalse(text.contains("jakarta.persistence"), relative); + assertFalse(text.contains("proxyfeatures.features"), relative); + assertFalse(text.contains("proxyfeatures.framework"), relative); + } + } + + @Test + void obsoleteDiscoveryAndApiSystemsCannotReturn() throws IOException { + Set forbidden = Set.of( + "io.github.classgraph", + "ProxyFeaturesContext", + "FeatureServiceDirectory", + "RestartAdmissionAPI" + ); + List violations = new ArrayList<>(); + + for (Path source : javaSources(MODULE.resolve("src/main/java"))) { + String text = Files.readString(source); + for (String token : forbidden) { + if (text.contains(token)) { + violations.add(MODULE.relativize(source) + " contains " + token); + } + } + } + + assertEquals(List.of(), violations); + } + + private static List javaSources(Path root) throws IOException { + try (Stream paths = Files.walk(root)) { + return paths.filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().endsWith(".java")) + .sorted() + .toList(); + } + } +} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/FeatureFactoryTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/FeatureFactoryTest.java index 3d2c986f..b53a706c 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/FeatureFactoryTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/FeatureFactoryTest.java @@ -1,9 +1,9 @@ package nl.hauntedmc.proxyfeatures.features; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigService; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.commandhider.meta.Meta; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.config.MainConfigHandler; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeatureTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeatureTest.java index 3485ffef..040c5cab 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeatureTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeatureTest.java @@ -2,9 +2,9 @@ import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/AntiVPNServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/AntiVPNServiceTest.java index 82e22bdd..9cf45385 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/AntiVPNServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/AntiVPNServiceTest.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.antivpn.internal; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.antivpn.AntiVPN; import nl.hauntedmc.proxyfeatures.features.antivpn.internal.PersistentIpCache.CacheHit; import nl.hauntedmc.proxyfeatures.features.antivpn.internal.PersistentIpCache.Source; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/CountryServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/CountryServiceTest.java index f4266b37..598886c1 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/CountryServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/CountryServiceTest.java @@ -18,10 +18,10 @@ void stagePromoteAndClearFlowWorks() { service.stageForUsername("Remy", "nl"); service.promoteToUuid("REMY", uuid); - assertEquals(Optional.of("NL"), service.getCountry(uuid)); + assertEquals(Optional.of("NL"), service.countryCode(uuid)); service.clear(uuid); - assertEquals(Optional.empty(), service.getCountry(uuid)); + assertEquals(Optional.empty(), service.countryCode(uuid)); } @Test @@ -35,6 +35,6 @@ void invalidInputsAreIgnoredWithoutThrowing() { service.promoteToUuid("x", null); service.clear(null); - assertTrue(service.getCountry(uuid).isEmpty()); + assertTrue(service.countryCode(uuid).isEmpty()); } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/IpWhitelistTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/IpWhitelistTest.java index 82d26117..888c026c 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/IpWhitelistTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/IpWhitelistTest.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.antivpn.internal; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.antivpn.AntiVPN; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/NotificationServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/NotificationServiceTest.java index 36acda30..b95da199 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/NotificationServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/NotificationServiceTest.java @@ -4,7 +4,7 @@ import com.velocitypowered.api.proxy.ProxyServer; import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.antivpn.AntiVPN; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/PersistentIpCacheTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/PersistentIpCacheTest.java index 3a80745b..6bee1d96 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/PersistentIpCacheTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/PersistentIpCacheTest.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.antivpn.internal; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheValue; -import nl.hauntedmc.proxyfeatures.api.io.cache.FileCacheStore; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheValue; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.FileCacheStore; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.antivpn.AntiVPN; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/ProviderRegistryTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/ProviderRegistryTest.java index 3d2c6371..0a820844 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/ProviderRegistryTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/ProviderRegistryTest.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.antivpn.internal.provider; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.antivpn.AntiVPN; import nl.hauntedmc.proxyfeatures.features.antivpn.internal.IPCheckResult; import nl.hauntedmc.proxyfeatures.features.antivpn.internal.provider.ip2location.IP2LocationProvider; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/ip2location/IP2LocationProviderTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/ip2location/IP2LocationProviderTest.java index 8b6bb97b..f64ae86c 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/ip2location/IP2LocationProviderTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/ip2location/IP2LocationProviderTest.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.antivpn.internal.provider.ip2location; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.antivpn.AntiVPN; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/proxycheck/ProxyCheckProviderTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/proxycheck/ProxyCheckProviderTest.java index 0824ee14..5398eba1 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/proxycheck/ProxyCheckProviderTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/antivpn/internal/provider/proxycheck/ProxyCheckProviderTest.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.antivpn.internal.provider.proxycheck; import com.google.gson.JsonObject; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.antivpn.AntiVPN; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommandTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommandTest.java index 535fd262..48308c8a 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommandTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommandTest.java @@ -7,7 +7,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.title.Title; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.broadcast.Broadcast; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/command/CapacityCommandTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/command/CapacityCommandTest.java index 67c04b78..5ef7566f 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/command/CapacityCommandTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/command/CapacityCommandTest.java @@ -6,7 +6,7 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; import nl.hauntedmc.proxyfeatures.features.capacity.config.CapacityConfig; import nl.hauntedmc.proxyfeatures.features.capacity.internal.CapacityControlPlane; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfigurationTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfigurationTest.java index 77fa0ba5..cfb795fc 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfigurationTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/config/CapacityConfigurationTest.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.capacity.config; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityServiceTest.java index 26c6de2a..3335cfed 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityServiceTest.java @@ -5,13 +5,14 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.capacity.AdmissionIntent; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDecision; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDenialReason; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityRequest; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; +import nl.hauntedmc.proxyfeatures.framework.admission.AdmissionIntent; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDecision; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDenialReason; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityRequest; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; import nl.hauntedmc.proxyfeatures.features.capacity.config.CapacityConfig; +import nl.hauntedmc.proxyfeatures.framework.service.InternalServiceRegistry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -47,6 +48,7 @@ void setUp() { when(feature.getPlugin()).thenReturn(plugin); when(plugin.getProxy()).thenReturn(proxy); + when(plugin.getInternalServiceRegistry()).thenReturn(new InternalServiceRegistry()); when(plugin.getDataRegistry()).thenReturn(Optional.empty()); when(survival.getServerInfo()).thenReturn(survivalInfo); when(survivalInfo.getName()).thenReturn("survival"); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacitySnapshotPublisherTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacitySnapshotPublisherTest.java index 90839dbb..4f12f4aa 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacitySnapshotPublisherTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacitySnapshotPublisherTest.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.features.capacity.internal; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityScopeSnapshot; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacitySnapshot; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityScopeSnapshot; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacitySnapshot; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; import nl.hauntedmc.proxyfeatures.contracts.messaging.CapacitySnapshotMessage; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoAdvisorNotifyPolicyTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoAdvisorNotifyPolicyTest.java index cf5b237f..2c14fc90 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoAdvisorNotifyPolicyTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoAdvisorNotifyPolicyTest.java @@ -5,7 +5,7 @@ import com.velocitypowered.api.proxy.player.PlayerSettings; import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.clientinfo.ClientInfo; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoAdvisorOutputConfigTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoAdvisorOutputConfigTest.java index 43d70cdb..91f56b89 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoAdvisorOutputConfigTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoAdvisorOutputConfigTest.java @@ -5,8 +5,8 @@ import com.velocitypowered.api.proxy.ProxyServer; import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigTypes; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigTypes; import nl.hauntedmc.proxyfeatures.features.clientinfo.ClientInfo; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoConfigTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoConfigTest.java index dcbbc7b4..f2a286be 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoConfigTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoConfigTest.java @@ -1,9 +1,9 @@ package nl.hauntedmc.proxyfeatures.features.clientinfo.internal; import com.velocitypowered.api.proxy.player.PlayerSettings; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigTypes; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigView; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigTypes; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigView; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogServiceTest.java index e5756400..febf0241 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogServiceTest.java @@ -5,6 +5,7 @@ import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; +import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import nl.hauntedmc.proxyfeatures.features.commandlogger.CommandLogger; import nl.hauntedmc.proxyfeatures.features.commandlogger.entity.CommandExecutionEntity; @@ -36,7 +37,9 @@ void logsConsoleSourceWithoutPlayerReference() { }); CommandSource source = mock(CommandSource.class); - CommandLogService service = new CommandLogService(feature, mock(PlayerDirectory.class)); + CommandLogService service = new CommandLogService( + feature, new PlayerReferenceResolver(mock(PlayerDirectory.class)) + ); service.logProxyCommand(source, "velocity info"); @SuppressWarnings("unchecked") @@ -70,7 +73,9 @@ void skipsPlayerCommandWhenIdentityIsUnavailable() { when(source.getUsername()).thenReturn("Remy"); when(playerDirectory.whenReady(uuid)).thenReturn(CompletableFuture.completedFuture(Optional.empty())); - CommandLogService service = new CommandLogService(feature, playerDirectory); + CommandLogService service = new CommandLogService( + feature, new PlayerReferenceResolver(playerDirectory) + ); service.logProxyCommand(source, "say hi"); verify(orm, never()).runInTransaction(any()); @@ -111,7 +116,9 @@ void logsPlayerCommandWithExistingManagedPlayerReferenceWithoutUpdatingUsername( return null; }); - CommandLogService service = new CommandLogService(feature, playerDirectory); + CommandLogService service = new CommandLogService( + feature, new PlayerReferenceResolver(playerDirectory) + ); service.logProxyCommand(source, "list"); verify(session, never()).merge(existing); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandlerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandlerTest.java index 21296bc5..a419b695 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandlerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandlerTest.java @@ -10,7 +10,7 @@ import nl.hauntedmc.dataprovider.database.messaging.durable.DurableMessagingDataAccess; import nl.hauntedmc.dataprovider.database.messaging.durable.DurableSubscription; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.cache.FileCacheStore; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.FileCacheStore; import nl.hauntedmc.proxyfeatures.contracts.messaging.CommandRelayMessage; import nl.hauntedmc.proxyfeatures.features.commandrelay.CommandRelay; import nl.hauntedmc.proxyfeatures.features.commandrelay.audit.CommandRelayAuditLogService; @@ -79,7 +79,7 @@ void completedReplayIsAcknowledgedWithoutExecutingAgain() { DurableMessagingDataAccess redis = mock(DurableMessagingDataAccess.class); CommandRelay feature = featureWithWhitelist(List.of("say")); FileCacheStore store = mock(FileCacheStore.class); - when(store.listAll()).thenReturn(Map.of("command.done", mock(nl.hauntedmc.proxyfeatures.api.io.cache.CacheValue.class))); + when(store.listAll()).thenReturn(Map.of("command.done", mock(nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheValue.class))); Consumer> consumer = installConsumer(redis); EventBusHandler handler = handler(feature, redis, store); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/ProcessedCommandLedgerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/ProcessedCommandLedgerTest.java index 3b464914..5e4490eb 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/ProcessedCommandLedgerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/ProcessedCommandLedgerTest.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.commandrelay.internal; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheValue; -import nl.hauntedmc.proxyfeatures.api.io.cache.FileCacheStore; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheValue; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.FileCacheStore; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsServiceTest.java index 3f19ee53..8d99425d 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsServiceTest.java @@ -2,6 +2,7 @@ import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; +import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import nl.hauntedmc.proxyfeatures.features.friends.Friends; import nl.hauntedmc.proxyfeatures.features.friends.support.FriendsCache; import org.hibernate.Session; @@ -32,7 +33,9 @@ void getOrCreateSettingsPersistsUsingTheScalarPlayerId() { return loader.get(); }); when(session.find(FriendSettingsEntity.class, 10L)).thenReturn(null); - FriendsService service = new FriendsService(feature, cache, mock(PlayerDirectory.class)); + FriendsService service = new FriendsService( + feature, cache, new PlayerReferenceResolver(mock(PlayerDirectory.class)) + ); FriendSettingsEntity settings = service.getOrCreateSettings(new PlayerRef(10L, "uuid", "name")); assertNotNull(settings); @@ -52,7 +55,9 @@ void createPendingPersistsWithoutDataRegistryEntitiesInTheSession() { ORMContext.TransactionCallback callback = invocation.getArgument(0); return callback.execute(session); }); - FriendsService service = new FriendsService(feature, cache, mock(PlayerDirectory.class)); + FriendsService service = new FriendsService( + feature, cache, new PlayerReferenceResolver(mock(PlayerDirectory.class)) + ); boolean created = service.createPending( new PlayerRef(1L, "uuid-1", "One"), new PlayerRef(2L, "uuid-2", "Two") diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListenerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListenerTest.java index 59593ee4..31e70a81 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListenerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListenerTest.java @@ -8,12 +8,11 @@ import com.velocitypowered.api.proxy.server.ServerInfo; import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.ProxyFeatures; +import nl.hauntedmc.proxyfeatures.api.capability.operations.TwoFactorApi; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; import nl.hauntedmc.proxyfeatures.features.maintenance.Maintenance; import nl.hauntedmc.proxyfeatures.features.maintenance.internal.MaintenanceHandler; -import nl.hauntedmc.proxyfeatures.features.twofactor.TwoFactor; -import nl.hauntedmc.proxyfeatures.features.twofactor.service.TwoFactorService; -import nl.hauntedmc.proxyfeatures.framework.loader.FeatureLoadManager; -import nl.hauntedmc.proxyfeatures.framework.loader.FeatureRegistry; +import nl.hauntedmc.proxyfeatures.test.MutableCapabilityRegistry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -27,7 +26,7 @@ class MaintenanceConnectionListenerTest { private Maintenance feature; private MaintenanceHandler handler; - private FeatureRegistry featureRegistry; + private MutableCapabilityRegistry capabilities; private MaintenanceConnectionListener listener; @BeforeEach @@ -35,12 +34,10 @@ void setUp() { feature = mock(Maintenance.class); handler = mock(MaintenanceHandler.class); ProxyFeatures plugin = mock(ProxyFeatures.class); - FeatureLoadManager featureLoadManager = mock(FeatureLoadManager.class); - featureRegistry = mock(FeatureRegistry.class); + capabilities = new MutableCapabilityRegistry(); when(feature.getHandler()).thenReturn(handler); when(feature.getPlugin()).thenReturn(plugin); - when(plugin.getFeatureLoadManager()).thenReturn(featureLoadManager); - when(featureLoadManager.getFeatureRegistry()).thenReturn(featureRegistry); + when(plugin.capabilities()).thenReturn(capabilities); listener = new MaintenanceConnectionListener(feature); } @@ -164,17 +161,15 @@ void twoFactorSecurityRouteIsNeverOverriddenByMaintenance() { RegisteredServer survival = server("survival"); RegisteredServer auth = server("auth"); ServerPreConnectEvent event = mock(ServerPreConnectEvent.class); - TwoFactor twoFactor = mock(TwoFactor.class); - TwoFactorService twoFactorService = mock(TwoFactorService.class); + TwoFactorApi twoFactor = mock(TwoFactorApi.class); when(event.getOriginalServer()).thenReturn(survival); when(event.getResult()).thenReturn(ServerPreConnectEvent.ServerResult.allowed(auth)); when(event.getPreviousServer()).thenReturn(null); when(event.getPlayer()).thenReturn(player); - doReturn(twoFactor).when(featureRegistry).getLoadedFeature("TwoFactor"); - when(twoFactor.getService()).thenReturn(twoFactorService); - when(twoFactorService.isLocked(player)).thenReturn(true); - when(twoFactor.isLockServer("auth")).thenReturn(true); + capabilities.register(TwoFactorApi.class, twoFactor); + when(twoFactor.isLocked(player.getUniqueId())).thenReturn(true); + when(twoFactor.isAuthenticationServer(ServerId.of("auth"))).thenReturn(true); when(handler.normalizeGamemodeName("auth")).thenReturn("auth"); when(handler.isGamemodeActive("auth")).thenReturn(true); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingModeCommandTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingModeCommandTest.java index fbb68bd9..f054f997 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingModeCommandTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingModeCommandTest.java @@ -6,7 +6,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.internal.MessagingHandler; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingVanishCommandTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingVanishCommandTest.java index 240bbe40..3335883c 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingVanishCommandTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/command/MessagingVanishCommandTest.java @@ -4,12 +4,12 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import net.kyori.adventure.text.Component; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.internal.MessagingHandler; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; +import nl.hauntedmc.proxyfeatures.test.MutableCapabilityRegistry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,7 +31,7 @@ class MessagingVanishCommandTest { private ProxyServer proxy; private MessagingHandler handler; private LocalizationHandler localization; - private VanishAPI vanishApi; + private PresenceApi vanishApi; private Player sender; private Player hiddenStaff; private UUID hiddenStaffId; @@ -44,8 +44,8 @@ void setUp() { handler = mock(MessagingHandler.class); localization = mock(LocalizationHandler.class); LocalizationHandler.MessageBuilder builder = mock(LocalizationHandler.MessageBuilder.class); - DataRegistryApi dataRegistry = mock(DataRegistryApi.class, org.mockito.Mockito.RETURNS_DEEP_STUBS); - vanishApi = mock(VanishAPI.class); + MutableCapabilityRegistry capabilities = new MutableCapabilityRegistry(); + vanishApi = mock(PresenceApi.class); sender = mock(Player.class); hiddenStaff = mock(Player.class); hiddenStaffId = UUID.randomUUID(); @@ -54,9 +54,8 @@ void setUp() { when(feature.getHandler()).thenReturn(handler); when(feature.getLocalizationHandler()).thenReturn(localization); when(plugin.getProxy()).thenReturn(proxy); - when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); - when(dataRegistry.featureServices().find(VanishAPI.class)) - .thenReturn(Optional.of(vanishApi)); + when(plugin.capabilities()).thenReturn(capabilities); + capabilities.register(PresenceApi.class, vanishApi); when(localization.getMessage(anyString())).thenReturn(builder); when(builder.with(anyString(), anyString())).thenReturn(builder); when(builder.forAudience(any())).thenReturn(builder); @@ -69,8 +68,8 @@ void setUp() { when(hiddenStaff.getUsername()).thenReturn("HiddenStaff"); when(proxy.getPlayer("HiddenStaff")).thenReturn(Optional.of(hiddenStaff)); when(proxy.getPlayer(hiddenStaffId)).thenReturn(Optional.of(hiddenStaff)); - when(vanishApi.isVanished(hiddenStaffId)).thenReturn(true); - when(vanishApi.getAdjustedOnlinePlayers()).thenReturn(List.of()); + when(vanishApi.isHidden(hiddenStaffId)).thenReturn(true); + when(proxy.getAllPlayers()).thenReturn(List.of()); } @Test diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/MessageModeConverterTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/MessageModeConverterTest.java index 99c072e5..385fdea4 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/MessageModeConverterTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/MessageModeConverterTest.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.messager.entity; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntityTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntityTest.java index ae6825f7..836f19e8 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntityTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntityTest.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.messager.entity; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagePrivacyPolicyTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagePrivacyPolicyTest.java index c224902b..b6e2daaf 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagePrivacyPolicyTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagePrivacyPolicyTest.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.messager.internal; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import org.junit.jupiter.api.Test; import static nl.hauntedmc.proxyfeatures.features.messager.internal.MessagePrivacyPolicy.Decision.ALLOW; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingFriendServiceAvailabilityTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingFriendServiceAvailabilityTest.java index 00321a73..b84c30e3 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingFriendServiceAvailabilityTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingFriendServiceAvailabilityTest.java @@ -10,13 +10,14 @@ import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.friends.FriendshipApi; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.api.capability.social.FriendshipApi; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.entity.PlayerMessageSettingsEntity; import nl.hauntedmc.proxyfeatures.features.messager.history.PlayerMessageHistoryLogService; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; +import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import org.hibernate.Session; import org.hibernate.query.Query; import org.junit.jupiter.api.Test; @@ -156,6 +157,7 @@ private static Fixture fixture( when(feature.getDefaultMessageMode()).thenReturn(MessageMode.FRIENDS); when(plugin.getProxy()).thenReturn(proxy); when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); + when(plugin.getPlayerReferenceResolver()).thenReturn(new PlayerReferenceResolver(directory)); when(plugin.getLogger()).thenReturn(logger); when(dataRegistry.players()).thenReturn(playerData); when(playerData.identities()).thenReturn(directory); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandlerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandlerTest.java index a0bd9ee7..604f929e 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandlerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandlerTest.java @@ -12,12 +12,13 @@ import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import nl.hauntedmc.dataregistry.api.player.PlayerLookup; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.friends.FriendshipApi; +import nl.hauntedmc.proxyfeatures.api.capability.social.FriendshipApi; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.entity.PlayerMessageSettingsEntity; import nl.hauntedmc.proxyfeatures.features.messager.history.PlayerMessageHistoryLogService; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; +import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import org.hibernate.Session; import org.hibernate.query.Query; import org.junit.jupiter.api.Test; @@ -56,6 +57,7 @@ void blockDoesNotThrowWhenTargetPlayerReferenceIsMissing() { when(feature.getPlugin()).thenReturn(plugin); when(plugin.getProxy()).thenReturn(proxy); when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); + when(plugin.getPlayerReferenceResolver()).thenReturn(new PlayerReferenceResolver(playerDirectory)); when(dataRegistry.players()).thenReturn(players); when(players.identities()).thenReturn(playerDirectory); when(proxy.getAllPlayers()).thenReturn(List.of()); @@ -97,6 +99,7 @@ void toggleMessagingLoadsPersistedStateBeforeFlipping() { when(feature.getPlugin()).thenReturn(plugin); when(plugin.getProxy()).thenReturn(proxy); when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); + when(plugin.getPlayerReferenceResolver()).thenReturn(new PlayerReferenceResolver(playerDirectory)); when(dataRegistry.players()).thenReturn(players); when(players.identities()).thenReturn(playerDirectory); when(proxy.getAllPlayers()).thenReturn(List.of()); @@ -157,6 +160,7 @@ void deliveredReplyLogsHistoryAndDropsUnauthorizedSpy() { when(feature.getPlugin()).thenReturn(plugin); when(plugin.getProxy()).thenReturn(proxy); when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); + when(plugin.getPlayerReferenceResolver()).thenReturn(new PlayerReferenceResolver(playerDirectory)); when(dataRegistry.players()).thenReturn(players); when(players.identities()).thenReturn(playerDirectory); when(proxy.getAllPlayers()).thenReturn(List.of()); @@ -251,6 +255,7 @@ void offlineBlockedPlayerCanBeSuggestedResolvedAndUnblocked() { when(feature.getPlugin()).thenReturn(plugin); when(plugin.getProxy()).thenReturn(proxy); when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); + when(plugin.getPlayerReferenceResolver()).thenReturn(new PlayerReferenceResolver(playerDirectory)); when(dataRegistry.players()).thenReturn(players); when(players.identities()).thenReturn(playerDirectory); when(feature.getOrmContext()).thenReturn(orm); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandlerVanishVisibilityTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandlerVanishVisibilityTest.java index b564c6b4..5b6996c4 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandlerVanishVisibilityTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandlerVanishVisibilityTest.java @@ -4,13 +4,13 @@ import com.velocitypowered.api.proxy.ProxyServer; import net.kyori.adventure.text.Component; import nl.hauntedmc.dataprovider.api.orm.ORMContext; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.friends.FriendshipApi; +import nl.hauntedmc.proxyfeatures.api.capability.social.FriendshipApi; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.history.PlayerMessageHistoryLogService; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; +import nl.hauntedmc.proxyfeatures.test.MutableCapabilityRegistry; import org.junit.jupiter.api.Test; import java.util.Optional; @@ -32,12 +32,12 @@ void rawHandlerCallCannotDeliverToVanishedTarget() { Messenger feature = mock(Messenger.class); ProxyFeatures plugin = mock(ProxyFeatures.class); ProxyServer proxy = mock(ProxyServer.class); - DataRegistryApi dataRegistry = mock(DataRegistryApi.class, org.mockito.Mockito.RETURNS_DEEP_STUBS); + MutableCapabilityRegistry capabilities = new MutableCapabilityRegistry(); ORMContext orm = mock(ORMContext.class); PlayerMessageHistoryLogService history = mock(PlayerMessageHistoryLogService.class); LocalizationHandler localization = mock(LocalizationHandler.class); LocalizationHandler.MessageBuilder builder = mock(LocalizationHandler.MessageBuilder.class); - VanishAPI vanishApi = mock(VanishAPI.class); + PresenceApi vanishApi = mock(PresenceApi.class); Player sender = mock(Player.class); Player hiddenStaff = mock(Player.class); @SuppressWarnings("unchecked") @@ -50,16 +50,15 @@ void rawHandlerCallCannotDeliverToVanishedTarget() { when(feature.getMessageHistoryLogService()).thenReturn(history); when(feature.getLocalizationHandler()).thenReturn(localization); when(plugin.getProxy()).thenReturn(proxy); - when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); - when(dataRegistry.featureServices().find(VanishAPI.class)) - .thenReturn(Optional.of(vanishApi)); + when(plugin.capabilities()).thenReturn(capabilities); + capabilities.register(PresenceApi.class, vanishApi); when(localization.getMessage(anyString())).thenReturn(builder); when(builder.forAudience(any())).thenReturn(builder); when(builder.build()).thenReturn(Component.text("offline")); when(sender.getUniqueId()).thenReturn(senderId); when(hiddenStaff.getUniqueId()).thenReturn(hiddenStaffId); when(proxy.getPlayer(hiddenStaffId)).thenReturn(Optional.of(hiddenStaff)); - when(vanishApi.isVanished(hiddenStaffId)).thenReturn(true); + when(vanishApi.isHidden(hiddenStaffId)).thenReturn(true); MessagingHandler handler = new MessagingHandler(feature, friendshipResolver); handler.processPrivateMessage(sender, hiddenStaff, "Hello"); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingPlayerStateCleanupTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingPlayerStateCleanupTest.java index c63d3240..8f37cc1f 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingPlayerStateCleanupTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingPlayerStateCleanupTest.java @@ -8,11 +8,12 @@ import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.entity.PlayerMessageSettingsEntity; import nl.hauntedmc.proxyfeatures.features.messager.history.PlayerMessageHistoryLogService; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; +import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import org.hibernate.Session; import org.hibernate.query.Query; import org.junit.jupiter.api.Test; @@ -59,6 +60,7 @@ void unloadingPlayerForcesPersistedPrivacyModeToReload() { when(feature.getDefaultMessageMode()).thenReturn(MessageMode.FRIENDS); when(plugin.getProxy()).thenReturn(proxy); when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); + when(plugin.getPlayerReferenceResolver()).thenReturn(new PlayerReferenceResolver(directory)); when(dataRegistry.players()).thenReturn(playerData); when(playerData.identities()).thenReturn(directory); when(player.getUniqueId()).thenReturn(playerId); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsServiceTest.java index 43237362..a5b3fe7c 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsServiceTest.java @@ -3,10 +3,11 @@ import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; -import nl.hauntedmc.proxyfeatures.api.messaging.MessageMode; +import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.entity.PlayerMessageSettingsEntity; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; +import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import org.hibernate.Session; import org.hibernate.query.Query; import org.junit.jupiter.api.Test; @@ -48,7 +49,7 @@ void loadSettingsKeepsKnownPersistedMode() { when(query.setParameter("pid", 10L)).thenReturn(query); when(query.uniqueResultOptional()).thenReturn(Optional.of(settings)); - PlayerMessageSettingsEntity loaded = new MessagingSettingsService(feature, directory) + PlayerMessageSettingsEntity loaded = service(feature, directory) .loadSettings(uuid, "Remy"); assertSame(settings, loaded); @@ -80,7 +81,7 @@ void loadSettingsRepairsUnknownModeWithConfiguredDefault() { when(query.setParameter("pid", 11L)).thenReturn(query); when(query.uniqueResultOptional()).thenReturn(Optional.of(settings)); - PlayerMessageSettingsEntity loaded = new MessagingSettingsService(feature, directory) + PlayerMessageSettingsEntity loaded = service(feature, directory) .loadSettings(uuid, "Remy"); assertEquals(MessageMode.ALL, loaded.getStoredMessageMode().orElseThrow()); @@ -109,7 +110,7 @@ void loadSettingsCreatesNewRowWithConfiguredDefault() { when(query.setParameter("pid", 12L)).thenReturn(query); when(query.uniqueResultOptional()).thenReturn(Optional.empty()); - PlayerMessageSettingsEntity loaded = new MessagingSettingsService(feature, directory) + PlayerMessageSettingsEntity loaded = service(feature, directory) .loadSettings(uuid, "NewPlayer"); assertEquals(MessageMode.ALL, loaded.getStoredMessageMode().orElseThrow()); @@ -126,7 +127,7 @@ void toggleSpyAndModeMutationsUpdateSettingsInTransaction() { settings.setMessageMode(MessageMode.FRIENDS); when(session.find(PlayerMessageSettingsEntity.class, 20L)).thenReturn(settings); - MessagingSettingsService service = new MessagingSettingsService(feature, mock(PlayerDirectory.class)); + MessagingSettingsService service = service(feature, mock(PlayerDirectory.class)); service.setToggle(me, false); service.setSpy(me, true); service.setMode(me, MessageMode.ALL); @@ -146,7 +147,7 @@ void newRowsCreatedByOtherMutationsReceiveConfiguredDefault() { when(feature.getDefaultMessageMode()).thenReturn(MessageMode.ALL); when(session.find(PlayerMessageSettingsEntity.class, 21L)).thenReturn(null); - new MessagingSettingsService(feature, mock(PlayerDirectory.class)).setToggle(me, false); + service(feature, mock(PlayerDirectory.class)).setToggle(me, false); ArgumentCaptor captor = ArgumentCaptor.forClass(PlayerMessageSettingsEntity.class); @@ -166,7 +167,7 @@ void blockAndUnblockMutationsManageBlockSet() { settings.setMessageMode(MessageMode.FRIENDS); when(session.find(PlayerMessageSettingsEntity.class, 1L)).thenReturn(settings); - MessagingSettingsService service = new MessagingSettingsService(feature, mock(PlayerDirectory.class)); + MessagingSettingsService service = service(feature, mock(PlayerDirectory.class)); service.block(me, target); assertTrue(settings.isBlocking(target)); service.unblock(me, target); @@ -184,7 +185,7 @@ void findPlayerReferenceQueriesPersistenceForOfflineUsername() { when(directory.findByIdentifier("offlinealice")) .thenReturn(CompletableFuture.completedFuture(Optional.of(identity))); - Optional found = new MessagingSettingsService(feature, directory) + Optional found = service(feature, directory) .findPlayerReference("offlinealice") .toCompletableFuture() .join(); @@ -213,7 +214,7 @@ void missingPlayerGetsTransientConfiguredDefaultWithoutPersistence() { when(playerQuery.setMaxResults(1)).thenReturn(playerQuery); when(playerQuery.uniqueResultOptional()).thenReturn(Optional.empty()); - PlayerMessageSettingsEntity loaded = new MessagingSettingsService(feature, directory) + PlayerMessageSettingsEntity loaded = service(feature, directory) .loadSettings(uuid, "Remy"); assertTrue(loaded.isMsgToggle()); @@ -228,6 +229,10 @@ private static ORMContext transactionalOrm(Messenger feature) { return orm; } + private static MessagingSettingsService service(Messenger feature, PlayerDirectory directory) { + return new MessagingSettingsService(feature, new PlayerReferenceResolver(directory)); + } + private static Session session(ORMContext orm) { Session session = mock(Session.class); when(orm.runInTransaction(any())).thenAnswer(invocation -> { diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibilityTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibilityTest.java index 85deb0ad..6c526435 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibilityTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibilityTest.java @@ -2,10 +2,10 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.test.MutableCapabilityRegistry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -25,8 +25,8 @@ class MessengerTargetVisibilityTest { private Messenger feature; private ProxyFeatures plugin; private ProxyServer proxy; - private DataRegistryApi dataRegistry; - private VanishAPI vanishApi; + private MutableCapabilityRegistry capabilities; + private PresenceApi vanishApi; private Player viewer; private Player target; private UUID targetId; @@ -36,17 +36,16 @@ void setUp() { feature = mock(Messenger.class); plugin = mock(ProxyFeatures.class); proxy = mock(ProxyServer.class); - dataRegistry = mock(DataRegistryApi.class, org.mockito.Mockito.RETURNS_DEEP_STUBS); - vanishApi = mock(VanishAPI.class); + capabilities = new MutableCapabilityRegistry(); + vanishApi = mock(PresenceApi.class); viewer = mock(Player.class); target = mock(Player.class); targetId = UUID.randomUUID(); when(feature.getPlugin()).thenReturn(plugin); when(plugin.getProxy()).thenReturn(proxy); - when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); - when(dataRegistry.featureServices().find(VanishAPI.class)) - .thenReturn(Optional.of(vanishApi)); + when(plugin.capabilities()).thenReturn(capabilities); + capabilities.register(PresenceApi.class, vanishApi); when(target.getUniqueId()).thenReturn(targetId); when(proxy.getPlayer(targetId)).thenReturn(Optional.of(target)); when(proxy.getPlayer("HiddenStaff")).thenReturn(Optional.of(target)); @@ -54,7 +53,7 @@ void setUp() { @Test void normalPlayerCannotResolveVanishedTargetByNameOrUuid() { - when(vanishApi.isVanished(targetId)).thenReturn(true); + when(vanishApi.isHidden(targetId)).thenReturn(true); assertTrue(MessengerTargetVisibility.find(feature, viewer, "HiddenStaff").isEmpty()); assertTrue(MessengerTargetVisibility.find(feature, viewer, targetId).isEmpty()); @@ -66,13 +65,14 @@ void vanishBypassCanResolveVanishedTarget() { assertEquals(target, MessengerTargetVisibility.find(feature, viewer, "HiddenStaff").orElseThrow()); assertEquals(target, MessengerTargetVisibility.find(feature, viewer, targetId).orElseThrow()); - verify(vanishApi, never()).isVanished(targetId); + verify(vanishApi, never()).isHidden(targetId); } @Test void suggestionsExcludeVanishedPlayersForNormalViewer() { Player visible = mock(Player.class); - when(vanishApi.getAdjustedOnlinePlayers()).thenReturn(List.of(visible)); + when(proxy.getAllPlayers()).thenReturn(List.of(visible)); + when(visible.getUniqueId()).thenReturn(UUID.randomUUID()); assertEquals(List.of(visible), MessengerTargetVisibility.list(feature, viewer)); } @@ -83,6 +83,5 @@ void suggestionsIncludeAllPlayersForVanishBypass() { when(proxy.getAllPlayers()).thenReturn(List.of(target)); assertEquals(List.of(target), MessengerTargetVisibility.list(feature, viewer)); - verify(vanishApi, never()).getAdjustedOnlinePlayers(); } } diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/messaging/MessageModeTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/model/MessageModeTest.java similarity index 91% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/messaging/MessageModeTest.java rename to proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/model/MessageModeTest.java index 78c51111..d6df90ca 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/messaging/MessageModeTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/model/MessageModeTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.messaging; +package nl.hauntedmc.proxyfeatures.features.messager.model; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandlerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandlerTest.java index 35418608..d28cf457 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandlerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandlerTest.java @@ -4,19 +4,20 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; -import nl.hauntedmc.dataregistry.api.service.FeatureServiceDirectory; -import nl.hauntedmc.proxyfeatures.test.TestFeatureServiceDirectory; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigService; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; import nl.hauntedmc.proxyfeatures.framework.feature.FeatureScopeFactory; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleFactory; import nl.hauntedmc.proxyfeatures.features.motd.Motd; import nl.hauntedmc.proxyfeatures.features.motd.meta.Meta; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceSnapshot; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContext; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContribution; +import nl.hauntedmc.proxyfeatures.framework.extension.DefaultMotdExtensions; +import nl.hauntedmc.proxyfeatures.test.MutableCapabilityRegistry; import nl.hauntedmc.proxyfeatures.framework.config.MainConfigHandler; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.slf4j.LoggerFactory; @@ -40,11 +41,6 @@ class MotdHandlerTest { @TempDir Path tempDir; - @AfterEach - void tearDown() { - MotdLine2OverrideRegistry.clear(); - } - @Test void modifyServerPingAppliesMultiplierAndVanishAdjustment() { Motd feature = feature(Map.of( @@ -55,12 +51,12 @@ void modifyServerPingAppliesMultiplierAndVanishAdjustment() { )); MotdHandler handler = new MotdHandler(feature); - VanishAPI vanishApi = mock(VanishAPI.class); - when(vanishApi.getVanishedCount()).thenReturn(2); + PresenceApi vanishApi = mock(PresenceApi.class); + when(vanishApi.snapshot()).thenReturn(presenceWithHidden(2)); registerVanishApi(feature, vanishApi); ServerPing original = ping(10, 100); - ServerPing updated = handler.modifyServerPing(original); + ServerPing updated = handler.modifyServerPing(original, context()); ServerPing.Players players = updated.getPlayers().orElseThrow(); assertEquals(16, players.getOnline()); @@ -77,12 +73,12 @@ void modifyServerPingClampsNegativeMultiplierToZero() { )); MotdHandler handler = new MotdHandler(feature); - VanishAPI vanishApi = mock(VanishAPI.class); - when(vanishApi.getVanishedCount()).thenReturn(0); + PresenceApi vanishApi = mock(PresenceApi.class); + when(vanishApi.snapshot()).thenReturn(presenceWithHidden(0)); registerVanishApi(feature, vanishApi); ServerPing original = ping(5, 20); - ServerPing updated = handler.modifyServerPing(original); + ServerPing updated = handler.modifyServerPing(original, context()); ServerPing.Players players = updated.getPlayers().orElseThrow(); assertEquals(0, players.getOnline()); @@ -99,11 +95,11 @@ void modifyServerPingResolvesStaticLine2Placeholders() { )); MotdHandler handler = new MotdHandler(feature); - VanishAPI vanishApi = mock(VanishAPI.class); - when(vanishApi.getVanishedCount()).thenReturn(2); + PresenceApi vanishApi = mock(PresenceApi.class); + when(vanishApi.snapshot()).thenReturn(presenceWithHidden(2)); registerVanishApi(feature, vanishApi); - ServerPing updated = handler.modifyServerPing(ping(10, 100)); + ServerPing updated = handler.modifyServerPing(ping(10, 100), context()); String description = PLAIN.serialize(updated.getDescriptionComponent()); assertTrue(description.contains("Header haunted")); @@ -120,8 +116,8 @@ void modifyServerPingCyclesConfiguredLine2MessagesSequentially() { )); MotdHandler handler = new MotdHandler(feature); - String first = PLAIN.serialize(handler.modifyServerPing(ping(10, 100)).getDescriptionComponent()); - String second = PLAIN.serialize(handler.modifyServerPing(ping(10, 100)).getDescriptionComponent()); + String first = PLAIN.serialize(handler.modifyServerPing(ping(10, 100), context()).getDescriptionComponent()); + String second = PLAIN.serialize(handler.modifyServerPing(ping(10, 100), context()).getDescriptionComponent()); assertTrue(first.contains("First status")); assertTrue(second.contains("Second status")); @@ -138,7 +134,7 @@ void modifyServerPingFallsBackToStaticLineWhenRandomWordsAreUnavailable() { )); MotdHandler handler = new MotdHandler(feature); - String description = PLAIN.serialize(handler.modifyServerPing(ping(10, 100)).getDescriptionComponent()); + String description = PLAIN.serialize(handler.modifyServerPing(ping(10, 100), context()).getDescriptionComponent()); assertTrue(description.contains("Static fallback")); } @@ -156,7 +152,7 @@ void modifyServerPingUsesConfiguredRandomWordSeparatorAndSuffix() { )); MotdHandler handler = new MotdHandler(feature); - String description = PLAIN.serialize(handler.modifyServerPing(ping(10, 100)).getDescriptionComponent()); + String description = PLAIN.serialize(handler.modifyServerPing(ping(10, 100), context()).getDescriptionComponent()); assertTrue(description.contains("Alpha + Beta + Tail") || description.contains("Beta + Alpha + Tail")); } @@ -173,7 +169,7 @@ void modifyServerPingCentersRandomWordLineByDefault() { )); MotdHandler handler = new MotdHandler(feature); - String description = PLAIN.serialize(handler.modifyServerPing(ping(10, 100)).getDescriptionComponent()); + String description = PLAIN.serialize(handler.modifyServerPing(ping(10, 100), context()).getDescriptionComponent()); String line2 = description.substring(description.indexOf('\n') + 1); int leadingSpaces = line2.length() - line2.stripLeading().length(); @@ -189,9 +185,13 @@ void modifyServerPingUsesRegisteredLine2OverrideBeforeConfiguredLine2() { "line2_static", "Normal line" )); MotdHandler handler = new MotdHandler(feature); - MotdLine2OverrideRegistry.register("test-maintenance", 100, () -> "Maintenance line"); + feature.getPlugin().getMotdExtensions().register( + "test-maintenance", + 100, + ignored -> Optional.of(MotdContribution.secondLine("Maintenance line")) + ); - String description = PLAIN.serialize(handler.modifyServerPing(ping(10, 100)).getDescriptionComponent()); + String description = PLAIN.serialize(handler.modifyServerPing(ping(10, 100), context()).getDescriptionComponent()); assertTrue(description.contains("Maintenance line")); assertTrue(!description.contains("Normal line")); @@ -199,12 +199,11 @@ void modifyServerPingUsesRegisteredLine2OverrideBeforeConfiguredLine2() { private Motd feature(Map settings) { ProxyFeatures plugin = mock(ProxyFeatures.class, RETURNS_DEEP_STUBS); - DataRegistryApi dataRegistry = mock(DataRegistryApi.class); - FeatureServiceDirectory featureServices = new TestFeatureServiceDirectory(); + MutableCapabilityRegistry featureServices = new MutableCapabilityRegistry(); when(plugin.getDataDirectory()).thenReturn(tempDir); when(plugin.getLogger()).thenReturn(ComponentLogger.logger("MotdHandlerTest")); - when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); - when(dataRegistry.featureServices()).thenReturn(featureServices); + when(plugin.capabilities()).thenReturn(featureServices); + when(plugin.getMotdExtensions()).thenReturn(new DefaultMotdExtensions()); when(plugin.getFeatureLoadManager().getFeatureRegistry().isFeatureLoaded("VersionCheck")) .thenReturn(false); ConfigService service = new ConfigService(tempDir, LoggerFactory.getLogger(MotdHandlerTest.class), getClass().getClassLoader()); @@ -225,13 +224,20 @@ private Motd feature(Map settings) { return feature; } - private static void registerVanishApi(Motd feature, VanishAPI vanishApi) { - feature.getPlugin().getDataRegistry().orElseThrow().featureServices().register( - "ProxyFeatures", - "Vanish", - VanishAPI.class, - vanishApi - ); + private static void registerVanishApi(Motd feature, PresenceApi vanishApi) { + ((MutableCapabilityRegistry) feature.getPlugin().capabilities()) + .register(PresenceApi.class, vanishApi); + } + + private static PresenceSnapshot presenceWithHidden(int count) { + java.util.Set players = java.util.stream.IntStream.range(0, count) + .mapToObj(ignored -> java.util.UUID.randomUUID()) + .collect(java.util.stream.Collectors.toUnmodifiableSet()); + return new PresenceSnapshot(players, players, java.time.Instant.now()); + } + + private static MotdContext context() { + return new MotdContext(java.net.InetAddress.getLoopbackAddress(), 763); } private static ServerPing ping(int online, int max) { diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdLine2OverrideRegistryTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdLine2OverrideRegistryTest.java deleted file mode 100644 index 60e5315e..00000000 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdLine2OverrideRegistryTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.motd.internal; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class MotdLine2OverrideRegistryTest { - - @AfterEach - void tearDown() { - MotdLine2OverrideRegistry.clear(); - } - - @Test - void resolveRegistrationPrefersHighestPriority() { - MotdLine2OverrideRegistry.register("low", 10, () -> "Low"); - MotdLine2OverrideRegistry.register("high", 100, () -> "High"); - - MotdLine2OverrideRegistry.ResolvedOverride resolved = MotdLine2OverrideRegistry.resolveRegistration().orElseThrow(); - - assertEquals("high", resolved.key()); - assertEquals(100, resolved.priority()); - assertEquals("High", resolved.value()); - } - - @Test - void resolveRegistrationUsesDeterministicKeyOrderWhenPriorityMatches() { - MotdLine2OverrideRegistry.register("zeta", 50, () -> "Zeta"); - MotdLine2OverrideRegistry.register("alpha", 50, () -> "Alpha"); - - MotdLine2OverrideRegistry.ResolvedOverride resolved = MotdLine2OverrideRegistry.resolveRegistration().orElseThrow(); - - assertEquals("alpha", resolved.key()); - assertEquals("Alpha", resolved.value()); - } - - @Test - void resolveSkipsBlankOrFailingSuppliers() { - MotdLine2OverrideRegistry.register("broken", 100, () -> { - throw new IllegalStateException("boom"); - }); - MotdLine2OverrideRegistry.register("blank", 90, () -> " "); - MotdLine2OverrideRegistry.register("usable", 10, () -> "Ready"); - - assertEquals("Ready", MotdLine2OverrideRegistry.resolve().orElseThrow()); - } - - @Test - void unregisterRemovesEntry() { - MotdLine2OverrideRegistry.register("maintenance", 100, () -> "Maintenance"); - MotdLine2OverrideRegistry.unregister("maintenance"); - - assertTrue(MotdLine2OverrideRegistry.resolve().isEmpty()); - } -} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountPublisherTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountPublisherTest.java index 75118dea..63e821d4 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountPublisherTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountPublisherTest.java @@ -1,5 +1,9 @@ package nl.hauntedmc.proxyfeatures.features.playercount.internal; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCountSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCounts; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; + import nl.hauntedmc.dataprovider.database.messaging.MessagingDataAccess; import nl.hauntedmc.proxyfeatures.contracts.messaging.PlayerCountSnapshotMessage; import nl.hauntedmc.proxyfeatures.features.playercount.PlayerCount; @@ -69,7 +73,7 @@ void suppressesOverlappingPublishesAndResumesAfterCompletion() { void releasesInFlightGuardAfterSnapshotPreparationFailure() { Fixture fixture = fixture(); PlayerCountSnapshot validSnapshot = snapshot(); - doReturn(null).doReturn(validSnapshot).when(fixture.api()).capture(); + doReturn(null).doReturn(validSnapshot).when(fixture.api()).snapshot(); when(fixture.redisBus().publish(eq("counts"), any(PlayerCountSnapshotMessage.class))) .thenReturn(CompletableFuture.completedFuture(null)); @@ -100,7 +104,7 @@ void closePreventsNewPublishes() { fixture.publisher().close(); fixture.publisher().publishNow(); - verify(fixture.api(), never()).capture(); + verify(fixture.api(), never()).snapshot(); verify(fixture.redisBus(), never()) .publish(eq("counts"), any(PlayerCountSnapshotMessage.class)); } @@ -109,10 +113,10 @@ private static Fixture fixture() { PlayerCount feature = mock(PlayerCount.class); FeatureLogger logger = mock(FeatureLogger.class); MessagingDataAccess redisBus = mock(MessagingDataAccess.class); - PlayerCountAPI api = mock(PlayerCountAPI.class); + PlayerCountService api = mock(PlayerCountService.class); PlayerCountSnapshot defaultSnapshot = snapshot(); when(feature.getLogger()).thenReturn(logger); - when(api.capture()).thenReturn(defaultSnapshot); + when(api.snapshot()).thenReturn(defaultSnapshot); return new Fixture( redisBus, api, @@ -122,14 +126,15 @@ private static Fixture fixture() { private static PlayerCountSnapshot snapshot() { return new PlayerCountSnapshot( - new PlayerCountSnapshot.Counts(7, 2), - Map.of("survival", new PlayerCountSnapshot.Counts(5, 1)) + new PlayerCounts(7, 2), + Map.of(ServerId.of("survival"), new PlayerCounts(5, 1)), + java.time.Instant.now() ); } private record Fixture( MessagingDataAccess redisBus, - PlayerCountAPI api, + PlayerCountService api, PlayerCountPublisher publisher ) { } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountAPITest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountServiceTest.java similarity index 66% rename from proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountAPITest.java rename to proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountServiceTest.java index 3ee4d578..859ae777 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountAPITest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountServiceTest.java @@ -5,7 +5,11 @@ import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCountSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCounts; +import nl.hauntedmc.proxyfeatures.api.model.ServerId; import org.junit.jupiter.api.Test; import java.util.List; @@ -17,12 +21,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class PlayerCountAPITest { +class PlayerCountServiceTest { @Test void capturesNetworkAndPerServerCountsIncludingVanishedPlayers() { ProxyServer proxy = mock(ProxyServer.class); - VanishAPI vanishApi = mock(VanishAPI.class); + PresenceApi vanishApi = mock(PresenceApi.class); RegisteredServer lobby = server("Lobby-1"); RegisteredServer survival = server("survival"); Player visibleLobby = player("Lobby-1"); @@ -37,14 +41,17 @@ void capturesNetworkAndPerServerCountsIncludingVanishedPlayers() { visibleSurvival, connecting )); - when(vanishApi.getVanishedPlayers()).thenReturn(List.of(vanishedLobby)); + UUID hiddenId = vanishedLobby.getUniqueId(); + java.util.Set online = java.util.Set.of( + visibleLobby.getUniqueId(), hiddenId, visibleSurvival.getUniqueId(), connecting.getUniqueId()); + when(vanishApi.snapshot()).thenReturn(new PresenceSnapshot(online, java.util.Set.of(hiddenId), java.time.Instant.now())); - PlayerCountAPI api = new PlayerCountAPI(proxy, () -> Optional.of(vanishApi)); - PlayerCountSnapshot snapshot = api.capture(); + PlayerCountService api = new PlayerCountService(proxy, () -> Optional.of(vanishApi)); + PlayerCountSnapshot snapshot = api.snapshot(); - assertEquals(new PlayerCountSnapshot.Counts(4, 1), snapshot.network()); - assertEquals(new PlayerCountSnapshot.Counts(2, 1), snapshot.server("lobby-1")); - assertEquals(new PlayerCountSnapshot.Counts(1, 0), snapshot.server("SURVIVAL")); + assertEquals(new PlayerCounts(4, 1), snapshot.network()); + assertEquals(new PlayerCounts(2, 1), snapshot.server(ServerId.of("lobby-1"))); + assertEquals(new PlayerCounts(1, 0), snapshot.server(ServerId.of("SURVIVAL"))); assertEquals(3, snapshot.network().visible()); } @@ -55,10 +62,10 @@ void keepsZeroEntriesForRegisteredServersWhenVanishFeatureIsUnavailable() { when(proxy.getAllServers()).thenReturn(List.of(creative)); when(proxy.getAllPlayers()).thenReturn(List.of()); - PlayerCountSnapshot snapshot = new PlayerCountAPI(proxy, Optional::empty).capture(); + PlayerCountSnapshot snapshot = new PlayerCountService(proxy, Optional::empty).snapshot(); - assertEquals(PlayerCountSnapshot.Counts.empty(), snapshot.network()); - assertEquals(PlayerCountSnapshot.Counts.empty(), snapshot.server("creative")); + assertEquals(PlayerCounts.empty(), snapshot.network()); + assertEquals(PlayerCounts.empty(), snapshot.server(ServerId.of("creative"))); assertEquals(1, snapshot.servers().size()); } @@ -70,9 +77,9 @@ void rejectsAmbiguousNormalizedBackendNames() { when(proxy.getAllServers()).thenReturn(List.of(firstLobby, duplicateLobby)); when(proxy.getAllPlayers()).thenReturn(List.of()); - PlayerCountAPI api = new PlayerCountAPI(proxy, Optional::empty); + PlayerCountService api = new PlayerCountService(proxy, Optional::empty); - assertThrows(IllegalStateException.class, api::capture); + assertThrows(IllegalStateException.class, api::snapshot); } @Test @@ -82,9 +89,9 @@ void rejectsBlankBackendNames() { when(proxy.getAllServers()).thenReturn(List.of(blankServer)); when(proxy.getAllPlayers()).thenReturn(List.of()); - PlayerCountAPI api = new PlayerCountAPI(proxy, Optional::empty); + PlayerCountService api = new PlayerCountService(proxy, Optional::empty); - assertThrows(IllegalStateException.class, api::capture); + assertThrows(IllegalStateException.class, api::snapshot); } private static RegisteredServer server(String name) { diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountSnapshotTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountSnapshotTest.java deleted file mode 100644 index 4242d7d4..00000000 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playercount/internal/PlayerCountSnapshotTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.playercount.internal; - -import org.junit.jupiter.api.Test; - -import java.util.LinkedHashMap; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -class PlayerCountSnapshotTest { - - @Test - void rejectsMissingNetworkAndServerCounts() { - assertThrows( - NullPointerException.class, - () -> new PlayerCountSnapshot(null, Map.of()) - ); - - Map servers = new LinkedHashMap<>(); - servers.put("survival", null); - assertThrows( - NullPointerException.class, - () -> new PlayerCountSnapshot(PlayerCountSnapshot.Counts.empty(), servers) - ); - } - - @Test - void rejectsDuplicateNormalizedServerNames() { - Map servers = new LinkedHashMap<>(); - servers.put("Lobby", PlayerCountSnapshot.Counts.empty()); - servers.put(" lobby ", PlayerCountSnapshot.Counts.empty()); - - assertThrows( - IllegalArgumentException.class, - () -> new PlayerCountSnapshot(PlayerCountSnapshot.Counts.empty(), servers) - ); - } -} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoServiceTest.java index 5473d97c..a2804bab 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoServiceTest.java @@ -5,19 +5,19 @@ import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.server.ServerInfo; import nl.hauntedmc.dataregistry.api.DataRegistryApi; -import nl.hauntedmc.dataprovider.api.orm.ORMContext; import nl.hauntedmc.dataregistry.api.player.PlayerConnectionSnapshot; import nl.hauntedmc.dataregistry.api.player.PlayerData; import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import nl.hauntedmc.dataregistry.api.player.PlayerNameHistoryEntry; import nl.hauntedmc.dataregistry.api.player.PlayerProfile; import nl.hauntedmc.proxyfeatures.ProxyFeatures; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionSnapshot; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionType; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionsApi; +import nl.hauntedmc.proxyfeatures.test.MutableCapabilityRegistry; import nl.hauntedmc.proxyfeatures.features.playerinfo.PlayerInfo; -import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; -import org.hibernate.Session; -import org.hibernate.query.Query; import org.junit.jupiter.api.Test; import java.net.InetSocketAddress; @@ -40,12 +40,10 @@ void constructorFallsBackForInvalidDatePatternAndFmtHandlesNull() { ProxyFeatures plugin = mock(ProxyFeatures.class); FeatureConfigHandler cfg = mock(FeatureConfigHandler.class); FeatureLogger logger = mock(FeatureLogger.class); - ORMContext orm = mock(ORMContext.class); DataRegistryApi dataRegistry = mock(DataRegistryApi.class); PlayerData players = mock(PlayerData.class); when(feature.getConfigHandler()).thenReturn(cfg); when(feature.getLogger()).thenReturn(logger); - when(feature.getOrmContext()).thenReturn(orm); when(feature.getPlugin()).thenReturn(plugin); when(plugin.getProxy()).thenReturn(mock(ProxyServer.class)); when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); @@ -66,12 +64,10 @@ void constructorHandlesBlankTimezoneAndBlankPattern() { ProxyFeatures plugin = mock(ProxyFeatures.class); FeatureConfigHandler cfg = mock(FeatureConfigHandler.class); FeatureLogger logger = mock(FeatureLogger.class); - ORMContext orm = mock(ORMContext.class); DataRegistryApi dataRegistry = mock(DataRegistryApi.class); PlayerData players = mock(PlayerData.class); when(feature.getConfigHandler()).thenReturn(cfg); when(feature.getLogger()).thenReturn(logger); - when(feature.getOrmContext()).thenReturn(orm); when(feature.getPlugin()).thenReturn(plugin); when(plugin.getProxy()).thenReturn(mock(ProxyServer.class)); when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); @@ -89,12 +85,10 @@ void constructorHandlesInvalidTimezone() { ProxyFeatures plugin = mock(ProxyFeatures.class); FeatureConfigHandler cfg = mock(FeatureConfigHandler.class); FeatureLogger logger = mock(FeatureLogger.class); - ORMContext orm = mock(ORMContext.class); DataRegistryApi dataRegistry = mock(DataRegistryApi.class); PlayerData players = mock(PlayerData.class); when(feature.getConfigHandler()).thenReturn(cfg); when(feature.getLogger()).thenReturn(logger); - when(feature.getOrmContext()).thenReturn(orm); when(feature.getPlugin()).thenReturn(plugin); when(plugin.getProxy()).thenReturn(mock(ProxyServer.class)); when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); @@ -175,18 +169,9 @@ void findPlayerProfileUsesFacadeIdentifierLookupAndSharedIpHelper() { @Test void queryHelpersForUuidConnectionInfoAndActiveSanctionsDelegateCorrectly() { PlayerInfo feature = playerInfoWithDefaults(); - ORMContext orm = feature.getOrmContext(); - Session session = mock(Session.class); - @SuppressWarnings("unchecked") - Query sanctionQuery = mock(Query.class); DataRegistryApi dataRegistry = feature.getPlugin().getDataRegistry().orElseThrow(); PlayerData players = dataRegistry.players(); - when(orm.runInTransaction(any())).thenAnswer(invocation -> { - ORMContext.TransactionCallback callback = invocation.getArgument(0); - return callback.execute(session); - }); - UUID uuid = UUID.randomUUID(); PlayerIdentity player = new PlayerIdentity(99L, uuid, "Remy"); PlayerConnectionSnapshot connection = new PlayerConnectionSnapshot( @@ -209,22 +194,21 @@ void queryHelpersForUuidConnectionInfoAndActiveSanctionsDelegateCorrectly() { List.of() )))); - when(session.createQuery( - "SELECT s FROM SanctionEntity s " + - "WHERE s.targetPlayerId = :playerId AND s.active = true " + - "AND (s.expiresAt IS NULL OR s.expiresAt > :now) " + - "ORDER BY s.createdAt DESC", - SanctionEntity.class)).thenReturn(sanctionQuery); - when(sanctionQuery.setParameter("playerId", 99L)).thenReturn(sanctionQuery); - when(sanctionQuery.setParameter(eq("now"), any(Instant.class))).thenReturn(sanctionQuery); - when(sanctionQuery.getResultList()).thenReturn(List.of(mock(SanctionEntity.class))); + SanctionsApi sanctions = mock(SanctionsApi.class); + when(sanctions.find(uuid, true)).thenReturn(CompletableFuture.completedFuture(List.of( + new SanctionSnapshot( + 1L, uuid, SanctionType.BAN, "reason", "actor", Instant.EPOCH, + Optional.empty(), true) + ))); + ((MutableCapabilityRegistry) feature.getPlugin().capabilities()) + .register(SanctionsApi.class, sanctions); PlayerInfoService service = new PlayerInfoService(feature); Optional profile = join(service.findPlayerProfile(uuid.toString())); assertTrue(profile.isPresent()); assertTrue(profile.get().connection().isPresent()); - assertEquals(1, service.getActiveSanctions(player).size()); + assertEquals(1, join(service.getActiveSanctions(player)).size()); } @Test @@ -297,19 +281,18 @@ private static PlayerInfo playerInfoWithDefaults() { PlayerInfo feature = mock(PlayerInfo.class); FeatureConfigHandler cfg = mock(FeatureConfigHandler.class); FeatureLogger logger = mock(FeatureLogger.class); - ORMContext orm = mock(ORMContext.class); ProxyFeatures plugin = mock(ProxyFeatures.class); ProxyServer proxy = mock(ProxyServer.class); DataRegistryApi dataRegistry = mock(DataRegistryApi.class); PlayerData players = mock(PlayerData.class); when(feature.getConfigHandler()).thenReturn(cfg); when(feature.getLogger()).thenReturn(logger); - when(feature.getOrmContext()).thenReturn(orm); when(feature.getPlugin()).thenReturn(plugin); when(cfg.get("timezone", String.class, "")).thenReturn("UTC"); when(cfg.get("datetimeFormat", String.class, "dd-MM-yyyy HH:mm:ss")).thenReturn("dd-MM-yyyy HH:mm:ss"); when(plugin.getProxy()).thenReturn(proxy); when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); + when(plugin.capabilities()).thenReturn(new MutableCapabilityRegistry()); when(dataRegistry.players()).thenReturn(players); when(proxy.getPlayer(anyString())).thenReturn(Optional.empty()); when(proxy.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommandPolicyTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommandPolicyTest.java index 30dd5dc5..2d9905c0 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommandPolicyTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/command/LanguageCommandPolicyTest.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.playerlanguage.command; -import nl.hauntedmc.proxyfeatures.api.io.localization.Language; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageServiceTest.java index f731cbfb..7db08529 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageServiceTest.java @@ -4,7 +4,7 @@ import nl.hauntedmc.dataregistry.api.DataRegistryApi; import nl.hauntedmc.dataregistry.api.player.PlayerData; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.localization.Language; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; import nl.hauntedmc.proxyfeatures.features.playerlanguage.PlayerLanguage; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandlerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandlerTest.java index 66e989f6..9464f276 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandlerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandlerTest.java @@ -7,12 +7,10 @@ import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerPing; import net.kyori.adventure.text.Component; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; -import nl.hauntedmc.dataregistry.api.service.FeatureServiceDirectory; -import nl.hauntedmc.proxyfeatures.test.TestFeatureServiceDirectory; +import nl.hauntedmc.proxyfeatures.test.MutableCapabilityRegistry; import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.features.playerlist.PlayerList; -import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishAPI; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -36,21 +34,19 @@ class PlayerListHandlerTest { private LocalizationHandler localization; private LocalizationHandler.MessageBuilder builder; private PlayerListHandler handler; - private FeatureServiceDirectory featureServices; + private MutableCapabilityRegistry featureServices; @BeforeEach void setUp() { feature = mock(PlayerList.class); plugin = mock(ProxyFeatures.class); proxy = mock(ProxyServer.class); - DataRegistryApi dataRegistry = mock(DataRegistryApi.class); - featureServices = new TestFeatureServiceDirectory(); + featureServices = new MutableCapabilityRegistry(); localization = mock(LocalizationHandler.class); builder = mock(LocalizationHandler.MessageBuilder.class); when(feature.getPlugin()).thenReturn(plugin); when(plugin.getProxy()).thenReturn(proxy); - when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); - when(dataRegistry.featureServices()).thenReturn(featureServices); + when(plugin.capabilities()).thenReturn(featureServices); when(feature.getLocalizationHandler()).thenReturn(localization); when(localization.getMessage(anyString())).thenReturn(builder); when(builder.with(anyString(), anyString())).thenReturn(builder); @@ -77,9 +73,9 @@ void formatPlayerListFiltersVanishedPlayersAndSeparatesStaff() { Player visibleStaff = player("Zed", visibleStaffId, true); Player vanished = player("Ghost", vanishedId, false); - VanishAPI vanishApi = mock(VanishAPI.class); - when(vanishApi.isVanished(vanishedId)).thenReturn(true); - featureServices.register("ProxyFeatures", "Vanish", VanishAPI.class, vanishApi); + PresenceApi vanishApi = mock(PresenceApi.class); + when(vanishApi.isHidden(vanishedId)).thenReturn(true); + featureServices.register(PresenceApi.class, vanishApi); handler.formatPlayerList("survival", List.of(visibleUser, visibleStaff, vanished), audience); @@ -107,9 +103,9 @@ void formatGlobalListUsesVisibleCountsAndServerOrder() { Player carl = player("Carl", carlId, false); Player ghost = player("Ghost", ghostId, false); - VanishAPI vanishApi = mock(VanishAPI.class); - when(vanishApi.isVanished(ghostId)).thenReturn(true); - featureServices.register("ProxyFeatures", "Vanish", VanishAPI.class, vanishApi); + PresenceApi vanishApi = mock(PresenceApi.class); + when(vanishApi.isHidden(ghostId)).thenReturn(true); + featureServices.register(PresenceApi.class, vanishApi); RegisteredServer survival = server("survival", List.of(alice, ghost), CompletableFuture.completedFuture(mock(ServerPing.class))); CompletableFuture failedPing = new CompletableFuture<>(); @@ -126,7 +122,7 @@ void formatGlobalListUsesVisibleCountsAndServerOrder() { verify(localization).getMessage("playerlist.server_bullet_offline"); verify(survival, times(1)).getPlayersConnected(); verify(lobby, times(1)).getPlayersConnected(); - verify(vanishApi, times(8)).isVanished(any(UUID.class)); + verify(vanishApi, times(8)).isHidden(any(UUID.class)); InOrder order = inOrder(builder); order.verify(builder).with("server", "lobby"); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/queue/QueueManagerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/queue/QueueManagerTest.java index b14b87ff..2daa11d5 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/queue/QueueManagerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/queue/QueueManagerTest.java @@ -7,11 +7,11 @@ import com.velocitypowered.api.scheduler.ScheduledTask; import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityAPI; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDecision; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityDenialReason; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityLease; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityRequest; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityAPI; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDecision; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityDenialReason; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityLease; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityRequest; import nl.hauntedmc.proxyfeatures.features.queue.model.ServerQueue; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; @@ -92,9 +92,9 @@ void enqueueAcceptsOnlyConfiguredFullTargets() { Player player = player(UUID.randomUUID()); CapacityRequest request = request(player, "survival"); - assertFalse(manager.enqueue(player, "survival", CapacityDenialReason.SERVER_STATE, request)); - assertFalse(manager.enqueue(player, "unknown", CapacityDenialReason.FULL, request)); - assertTrue(manager.enqueue(player, "survival", CapacityDenialReason.FULL, request)); + assertFalse(manager.enqueueDenied(player, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("survival"), CapacityDenialReason.SERVER_STATE, request)); + assertFalse(manager.enqueueDenied(player, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("unknown"), CapacityDenialReason.FULL, request)); + assertTrue(manager.enqueueDenied(player, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("survival"), CapacityDenialReason.FULL, request)); assertTrue(manager.getQueue("survival").orElseThrow().contains(player.getUniqueId())); } @@ -103,8 +103,8 @@ void duplicateEntryIsIdempotent() { Player player = player(UUID.randomUUID()); CapacityRequest request = request(player, "survival"); - assertTrue(manager.enqueue(player, "survival", CapacityDenialReason.FULL, request)); - assertTrue(manager.enqueue(player, "survival", CapacityDenialReason.FULL, request)); + assertTrue(manager.enqueueDenied(player, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("survival"), CapacityDenialReason.FULL, request)); + assertTrue(manager.enqueueDenied(player, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("survival"), CapacityDenialReason.FULL, request)); ServerQueue queue = manager.getQueue("survival").orElseThrow(); assertEquals(1, queue.size()); @@ -115,10 +115,10 @@ void duplicateEntryIsIdempotent() { void enqueueMovesMembershipBetweenTargets() { Player player = player(UUID.randomUUID()); - assertTrue(manager.enqueue(player, "lobby", CapacityDenialReason.FULL, request(player, "lobby"))); + assertTrue(manager.enqueueDenied(player, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("lobby"), CapacityDenialReason.FULL, request(player, "lobby"))); assertTrue(manager.getQueue("lobby").orElseThrow().contains(player.getUniqueId())); - assertTrue(manager.enqueue(player, "survival", CapacityDenialReason.FULL, request(player, "survival"))); + assertTrue(manager.enqueueDenied(player, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("survival"), CapacityDenialReason.FULL, request(player, "survival"))); assertFalse(manager.getQueue("lobby").orElseThrow().contains(player.getUniqueId())); assertTrue(manager.getQueue("survival").orElseThrow().contains(player.getUniqueId())); assertEquals("survival", manager.findQueueOf(player.getUniqueId()).orElseThrow()); @@ -127,11 +127,11 @@ void enqueueMovesMembershipBetweenTargets() { @Test void leaveRemovesMembershipAndTransientState() { Player player = player(UUID.randomUUID()); - manager.enqueue(player, "survival", CapacityDenialReason.FULL, request(player, "survival")); + manager.enqueueDenied(player, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("survival"), CapacityDenialReason.FULL, request(player, "survival")); - assertEquals("survival", manager.leave(player.getUniqueId()).orElseThrow()); + assertEquals("survival", manager.leaveNow(player.getUniqueId()).orElseThrow()); assertTrue(manager.findQueueOf(player.getUniqueId()).isEmpty()); - assertTrue(manager.leave(player.getUniqueId()).isEmpty()); + assertTrue(manager.leaveNow(player.getUniqueId()).isEmpty()); } @Test @@ -150,7 +150,7 @@ void leaveFencesConnectionRequestThatAlreadyEnteredVelocityPipeline() { when(player.createConnectionRequest(target)).thenReturn(connection); when(connection.connect()).thenReturn(pending); - manager.enqueue(player, "survival", CapacityDenialReason.FULL, request(player, "survival")); + manager.enqueueDenied(player, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("survival"), CapacityDenialReason.FULL, request(player, "survival")); ArgumentCaptor wake = ArgumentCaptor.forClass(Runnable.class); verify(tasks).scheduleTask(wake.capture()); wake.getValue().run(); @@ -159,16 +159,16 @@ void leaveFencesConnectionRequestThatAlreadyEnteredVelocityPipeline() { verify(tasks).scheduleDelayedTask(delayed.capture(), any(Duration.class)); delayed.getValue().run(); - assertEquals("survival", manager.leave(playerId).orElseThrow()); - assertFalse(manager.consumeCancelledAdvance(playerId, "lobby")); - assertTrue(manager.consumeCancelledAdvance(playerId, "survival")); - assertFalse(manager.consumeCancelledAdvance(playerId, "survival")); + assertEquals("survival", manager.leaveNow(playerId).orElseThrow()); + assertFalse(manager.consumeCancelledAdvance(playerId, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("lobby"))); + assertTrue(manager.consumeCancelledAdvance(playerId, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("survival"))); + assertFalse(manager.consumeCancelledAdvance(playerId, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("survival"))); } @Test void successfulPostConnectClearsTargetQueueEntry() { Player player = player(UUID.randomUUID()); - manager.enqueue(player, "survival", CapacityDenialReason.FULL, request(player, "survival")); + manager.enqueueDenied(player, nl.hauntedmc.proxyfeatures.api.model.ServerId.of("survival"), CapacityDenialReason.FULL, request(player, "survival")); manager.onPostConnect(player, "SURVIVAL"); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/queue/command/QueueCommandTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/queue/command/QueueCommandTest.java index c4e7da41..b5103446 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/queue/command/QueueCommandTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/queue/command/QueueCommandTest.java @@ -128,11 +128,11 @@ void leaveSubcommandUsesManagerCancellationAndConfirms() { Player player = mock(Player.class); UUID id = UUID.randomUUID(); when(player.getUniqueId()).thenReturn(id); - when(manager.leave(id)).thenReturn(Optional.of("survival")); + when(manager.leaveNow(id)).thenReturn(Optional.of("survival")); command.execute(invocation(player, "leave")); - verify(manager).leave(id); + verify(manager).leaveNow(id); verify(localization).getMessage("queue.cmd.leave.done"); verify(builder).with("server", "survival"); } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectCancellationTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectCancellationTest.java index 71e84c3b..380fb0b5 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectCancellationTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectCancellationTest.java @@ -9,7 +9,7 @@ import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.features.restart.Restart; -import nl.hauntedmc.proxyfeatures.features.restart.messaging.RestartLifecycleMessage; +import nl.hauntedmc.proxyfeatures.contracts.messaging.RestartLifecycleMessage; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectDelayedNoticeRaceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectDelayedNoticeRaceTest.java index 035a9f17..06639c74 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectDelayedNoticeRaceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectDelayedNoticeRaceTest.java @@ -10,7 +10,7 @@ import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.features.restart.Restart; -import nl.hauntedmc.proxyfeatures.features.restart.messaging.RestartLifecycleMessage; +import nl.hauntedmc.proxyfeatures.contracts.messaging.RestartLifecycleMessage; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectManagerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectManagerTest.java index 2542fe70..381e3305 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectManagerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/BackendReconnectManagerTest.java @@ -9,7 +9,7 @@ import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.features.restart.Restart; -import nl.hauntedmc.proxyfeatures.features.restart.messaging.RestartLifecycleMessage; +import nl.hauntedmc.proxyfeatures.contracts.messaging.RestartLifecycleMessage; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/PlayerAutoreconnectCancellationTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/PlayerAutoreconnectCancellationTest.java index e2860ff7..070904ba 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/PlayerAutoreconnectCancellationTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/PlayerAutoreconnectCancellationTest.java @@ -10,7 +10,7 @@ import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.features.restart.Restart; -import nl.hauntedmc.proxyfeatures.features.restart.messaging.RestartLifecycleMessage; +import nl.hauntedmc.proxyfeatures.contracts.messaging.RestartLifecycleMessage; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/RestartCapacityCoordinatorTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/RestartCapacityCoordinatorTest.java index bc5c1ba5..763c11d9 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/RestartCapacityCoordinatorTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/internal/RestartCapacityCoordinatorTest.java @@ -4,10 +4,10 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.scheduler.ScheduledTask; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityAPI; -import nl.hauntedmc.proxyfeatures.api.capacity.CapacityState; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityAPI; +import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; import nl.hauntedmc.proxyfeatures.features.restart.Restart; -import nl.hauntedmc.proxyfeatures.features.restart.messaging.RestartLifecycleMessage; +import nl.hauntedmc.proxyfeatures.contracts.messaging.RestartLifecycleMessage; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleBusTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleBusTest.java index 9b39ab44..66be4c96 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleBusTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/restart/messaging/RestartLifecycleBusTest.java @@ -1,5 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.restart.messaging; +import nl.hauntedmc.proxyfeatures.contracts.messaging.RestartLifecycleMessage; + import nl.hauntedmc.dataprovider.database.messaging.durable.DurableMessagingDataAccess; import nl.hauntedmc.dataprovider.database.messaging.durable.DurableSubscription; import nl.hauntedmc.proxyfeatures.features.restart.Restart; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/AsyncSanctionsCommandTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/AsyncSanctionsCommandTest.java index 40b94f50..200bae08 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/AsyncSanctionsCommandTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/AsyncSanctionsCommandTest.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.sanctions.command; import com.velocitypowered.api.command.SimpleCommand; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; diff --git a/proxyfeatures-contracts/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionEntityTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionEntityTest.java similarity index 100% rename from proxyfeatures-contracts/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionEntityTest.java rename to proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/entity/SanctionEntityTest.java diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListenerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListenerTest.java index c3965867..0af1aaf7 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListenerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListenerTest.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.audit.SanctionsSecurityAuditLogService; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionsService; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordServiceTest.java index f7549ea7..066b5f23 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordServiceTest.java @@ -1,8 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.sanctions.service; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.api.util.http.DiscordUtils; -import nl.hauntedmc.proxyfeatures.api.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; @@ -10,7 +9,6 @@ import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -33,25 +31,21 @@ void sendWarnSkipsWhenWebhookNotConfigured() { } @Test - void sendUnbanSchedulesTaskAndPostsPayload() { + void sendUnbanSchedulesTaskAndPostsPayload() throws Exception { Sanctions feature = configuredFeature("https://discord.example/hook"); FeatureLogger logger = feature.getLogger(); PlayerReference target = new PlayerReference(1L, null, "Remy"); - try (MockedStatic mocked = mockStatic(DiscordUtils.class)) { - mocked.when(() -> DiscordUtils.sendPayload(anyString(), anyString())).thenReturn(true); - - DiscordService service = new DiscordService(feature); - service.sendUnban(target, "Admin"); - - mocked.verify(() -> DiscordUtils.sendPayload( - eq("https://discord.example/hook"), - argThat(payload -> payload.contains("Sanctie Melding") - && payload.contains("Unban") - && payload.contains("Remy") - && payload.contains("Admin")))); - } + DiscordService.WebhookTransport transport = mock(DiscordService.WebhookTransport.class); + DiscordService service = new DiscordService(feature, transport); + service.sendUnban(target, "Admin"); + verify(transport).post( + eq("https://discord.example/hook"), + argThat(payload -> payload.contains("Sanctie Melding") + && payload.contains("Unban") + && payload.contains("Remy") + && payload.contains("Admin"))); verify(logger, never()).warn(contains("Failed to deliver webhook payload")); } @@ -63,12 +57,11 @@ void sendKickLogsWarningWhenDeliveryFails() { PlayerReference target = new PlayerReference(1L, null, "Remy"); - try (MockedStatic mocked = mockStatic(DiscordUtils.class)) { - mocked.when(() -> DiscordUtils.sendPayload(anyString(), anyString())).thenReturn(false); - - DiscordService service = new DiscordService(feature); - service.sendKick(target, "spam", "Admin"); - } + DiscordService.WebhookTransport transport = (url, payload) -> { + throw new java.io.IOException("offline"); + }; + DiscordService service = new DiscordService(feature, transport); + service.sendKick(target, "spam", "Admin"); verify(logger).warn(contains("Failed to deliver webhook payload")); } @@ -90,18 +83,10 @@ void sendBanMuteAndUnmuteUsePlaceholderPayloadData() { SanctionEntity sanction = new SanctionEntity(); java.util.List payloads = new java.util.ArrayList<>(); - try (MockedStatic mocked = mockStatic(DiscordUtils.class)) { - mocked.when(() -> DiscordUtils.sendPayload(anyString(), anyString())) - .thenAnswer(invocation -> { - payloads.add(invocation.getArgument(1, String.class)); - return true; - }); - - DiscordService service = new DiscordService(feature); - service.sendBan(sanction); - service.sendMute(sanction); - service.sendUnmute(target, "Admin"); - } + DiscordService service = new DiscordService(feature, (url, payload) -> payloads.add(payload)); + service.sendBan(sanction); + service.sendMute(sanction); + service.sendUnmute(target, "Admin"); verify(feature.getLifecycleManager().getTaskManager(), times(3)).scheduleTask(any(Runnable.class)); assertTrue(payloads.stream().anyMatch(p -> p.contains("\"value\":\"Ban\""))); @@ -114,17 +99,9 @@ void sendWarnAndKickHandleNullTargetAndBlankReasonWithDashFallback() { Sanctions feature = configuredFeature("https://discord.example/hook"); java.util.List payloads = new java.util.ArrayList<>(); - try (MockedStatic mocked = mockStatic(DiscordUtils.class)) { - mocked.when(() -> DiscordUtils.sendPayload(anyString(), anyString())) - .thenAnswer(invocation -> { - payloads.add(invocation.getArgument(1, String.class)); - return true; - }); - - DiscordService service = new DiscordService(feature); - service.sendWarn(null, " ", "Admin"); - service.sendKick(null, null, "Admin"); - } + DiscordService service = new DiscordService(feature, (url, payload) -> payloads.add(payload)); + service.sendWarn(null, " ", "Admin"); + service.sendKick(null, null, "Admin"); verify(feature.getLifecycleManager().getTaskManager(), times(2)).scheduleTask(any(Runnable.class)); assertTrue(payloads.stream().anyMatch(p -> p.contains("\"value\":\"Warn\""))); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsServiceParsingTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsServiceParsingTest.java index 6ec113c7..09bb533f 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsServiceParsingTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsServiceParsingTest.java @@ -2,6 +2,7 @@ import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; +import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import org.junit.jupiter.api.Test; import java.time.Instant; @@ -13,7 +14,7 @@ class SanctionsServiceParsingTest { @Test void parseLengthSupportsPermanentAndCompoundDurations() { - SanctionsService service = new SanctionsService(mock(Sanctions.class), mock(PlayerDirectory.class)); + SanctionsService service = service(); assertNull(service.parseLengthToExpiry("p")); assertNull(service.parseLengthToExpiry("perm")); @@ -29,7 +30,7 @@ void parseLengthSupportsPermanentAndCompoundDurations() { @Test void parseLengthRejectsMalformedTokensAndJunkInput() { - SanctionsService service = new SanctionsService(mock(Sanctions.class), mock(PlayerDirectory.class)); + SanctionsService service = service(); assertThrows(IllegalArgumentException.class, () -> service.parseLengthToExpiry(null)); assertThrows(IllegalArgumentException.class, () -> service.parseLengthToExpiry("")); @@ -42,7 +43,7 @@ void parseLengthRejectsMalformedTokensAndJunkInput() { @Test void sanitizeReasonAndHumanDurationHandleBoundaries() { - SanctionsService service = new SanctionsService(mock(Sanctions.class), mock(PlayerDirectory.class)); + SanctionsService service = service(); assertEquals("-", service.sanitizeReason(null)); assertEquals("-", service.sanitizeReason(" ")); @@ -57,4 +58,10 @@ void sanitizeReasonAndHumanDurationHandleBoundaries() { assertEquals("45s", service.humanDuration(from, from.plusSeconds(45))); assertEquals("1d 1h 1m", service.humanDuration(from, from.plusSeconds(90_061))); } + + private static SanctionsService service() { + return new SanctionsService( + mock(Sanctions.class), new PlayerReferenceResolver(mock(PlayerDirectory.class)) + ); + } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsServiceSharedIpTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsServiceSharedIpTest.java index 80062685..9bdbf979 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsServiceSharedIpTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsServiceSharedIpTest.java @@ -6,6 +6,7 @@ import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionType; +import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import org.hibernate.Session; import org.hibernate.query.Query; import org.junit.jupiter.api.Test; @@ -64,7 +65,9 @@ void findSharedIpSanctionsGroupsActiveBanAndMuteMatchesByPlayerId() { new Object[]{SanctionType.BAN, 1L} )); - SanctionsService service = new SanctionsService(feature, playerDirectory, players); + SanctionsService service = new SanctionsService( + feature, new PlayerReferenceResolver(playerDirectory), players + ); SanctionsService.SharedIpSanctions result = join(service.findSharedIpSanctions("203.0.113.7", 99L)); assertEquals(List.of("Alpha"), result.bannedUsernames()); @@ -74,7 +77,9 @@ void findSharedIpSanctionsGroupsActiveBanAndMuteMatchesByPlayerId() { @Test void findSharedIpSanctionsReturnsEmptyForBlankIp() { Sanctions feature = mock(Sanctions.class); - SanctionsService service = new SanctionsService(feature, mock(PlayerDirectory.class)); + SanctionsService service = new SanctionsService( + feature, new PlayerReferenceResolver(mock(PlayerDirectory.class)) + ); SanctionsService.SharedIpSanctions result = join(service.findSharedIpSanctions(" ", 99L)); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/ServiceLookupTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/ServiceLookupTest.java index db9be974..fb0f4e1f 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/ServiceLookupTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/ServiceLookupTest.java @@ -2,6 +2,7 @@ import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; +import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import org.junit.jupiter.api.Test; @@ -22,7 +23,7 @@ void byNameDelegatesToPlayerDirectory() { when(playerDirectory.snapshotActiveIdentities()) .thenReturn(Map.of(uuid.toString(), new PlayerIdentity(3L, uuid, "Remy"))); - ServiceLookup lookup = new ServiceLookup(playerDirectory); + ServiceLookup lookup = new ServiceLookup(new PlayerReferenceResolver(playerDirectory)); Optional result = lookup.byName("Remy"); assertTrue(result.isPresent()); @@ -39,7 +40,7 @@ void byUuidDelegatesToPlayerDirectoryAndCanReturnEmpty() { when(playerDirectory.findActiveIdentityCached(uuid)).thenReturn(Optional.empty()); - ServiceLookup lookup = new ServiceLookup(playerDirectory); + ServiceLookup lookup = new ServiceLookup(new PlayerReferenceResolver(playerDirectory)); Optional result = lookup.byUuid(uuid.toString()); assertTrue(result.isEmpty()); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/PresenceServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/PresenceServiceTest.java new file mode 100644 index 00000000..7d9cdd29 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/PresenceServiceTest.java @@ -0,0 +1,43 @@ +package nl.hauntedmc.proxyfeatures.features.vanish.internal; + +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import org.junit.jupiter.api.Test; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class PresenceServiceTest { + @Test + void exposesOnlyOnlineHiddenPlayers() { + ProxyServer proxy = mock(ProxyServer.class); + VanishRegistry registry = mock(VanishRegistry.class); + Player hidden = mock(Player.class); + Player visible = mock(Player.class); + UUID hiddenId = UUID.randomUUID(); + UUID visibleId = UUID.randomUUID(); + UUID staleId = UUID.randomUUID(); + Instant now = Instant.parse("2026-08-06T00:00:00Z"); + + when(hidden.getUniqueId()).thenReturn(hiddenId); + when(visible.getUniqueId()).thenReturn(visibleId); + when(proxy.getAllPlayers()).thenReturn(List.of(hidden, visible)); + when(registry.snapshot()).thenReturn(Map.of(hiddenId, "Hidden", staleId, "Offline")); + when(registry.isVanished(hiddenId)).thenReturn(true); + + PresenceService service = new PresenceService(proxy, registry, Clock.fixed(now, ZoneOffset.UTC)); + assertTrue(service.isHidden(hiddenId)); + assertEquals(java.util.Set.of(hiddenId, visibleId), service.snapshot().onlinePlayers()); + assertEquals(java.util.Set.of(hiddenId), service.snapshot().hiddenPlayers()); + assertEquals(now, service.snapshot().observedAt()); + } +} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishAPITest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishAPITest.java deleted file mode 100644 index c1c8aba2..00000000 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishAPITest.java +++ /dev/null @@ -1,39 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.vanish.internal; - -import com.velocitypowered.api.proxy.Player; -import nl.hauntedmc.proxyfeatures.features.vanish.Vanish; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class VanishAPITest { - - @Test - void apiDelegatesToRegistry() { - Vanish feature = mock(Vanish.class); - VanishRegistry registry = mock(VanishRegistry.class); - when(feature.getVanishRegistry()).thenReturn(registry); - - Player a = mock(Player.class); - Player b = mock(Player.class); - UUID id = UUID.randomUUID(); - - when(registry.getAdjustedOnlineCount()).thenReturn(12); - when(registry.getAdjustedOnlinePlayers()).thenReturn(List.of(a)); - when(registry.getVanishedOnlinePlayers()).thenReturn(List.of(b)); - when(registry.getVanishedOnlineCount()).thenReturn(1); - when(registry.isVanished(id)).thenReturn(true); - - VanishAPI api = new VanishAPI(feature); - assertEquals(12, api.getAdjustedPlayerCount()); - assertEquals(List.of(a), api.getAdjustedOnlinePlayers()); - assertEquals(List.of(b), api.getVanishedPlayers()); - assertEquals(1, api.getVanishedCount()); - assertTrue(api.isVanished(id)); - } -} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishRegistryEventTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishRegistryEventTest.java index 62a9a6a7..d723cbdd 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishRegistryEventTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/vanish/internal/VanishRegistryEventTest.java @@ -5,7 +5,7 @@ import com.velocitypowered.api.proxy.ProxyServer; import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.features.vanish.Vanish; -import nl.hauntedmc.proxyfeatures.features.vanish.event.VanishStateChangeEvent; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceChangedEvent; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -36,14 +36,14 @@ void acceptedOnlineStateChangesPublishExactlyOnce() { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Object.class); verify(fixture.eventManager(), times(2)).fireAndForget(eventCaptor.capture()); - VanishStateChangeEvent hidden = (VanishStateChangeEvent) eventCaptor.getAllValues().get(0); - assertEquals(fixture.playerUuid(), hidden.playerUuid()); + PresenceChangedEvent hidden = (PresenceChangedEvent) eventCaptor.getAllValues().get(0); + assertEquals(fixture.playerUuid(), hidden.playerId()); assertEquals("Remy", hidden.playerName()); - assertTrue(hidden.vanished()); + assertTrue(hidden.hidden()); - VanishStateChangeEvent visible = (VanishStateChangeEvent) eventCaptor.getAllValues().get(1); - assertEquals(fixture.playerUuid(), visible.playerUuid()); - assertFalse(visible.vanished()); + PresenceChangedEvent visible = (PresenceChangedEvent) eventCaptor.getAllValues().get(1); + assertEquals(fixture.playerUuid(), visible.playerId()); + assertFalse(visible.hidden()); } @Test diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/versioncheck/internal/VersionHandlerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/versioncheck/internal/VersionHandlerTest.java index bf8e8ce0..4fc9d39e 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/versioncheck/internal/VersionHandlerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/versioncheck/internal/VersionHandlerTest.java @@ -29,12 +29,10 @@ void constructorReadsConfigAndAppliesFriendlyFallback() { VersionHandler handler = new VersionHandler(feature); - assertEquals(763, handler.getMinimumProtcolVersion()); - assertEquals("unsupported", handler.getFriendlyProtocolName()); - assertTrue(handler.isUnsupportedVersion(762)); - assertFalse(handler.isUnsupportedVersion(763)); - assertTrue(handler.isUnsupportedVersion(762)); - assertFalse(handler.isUnsupportedVersion(763)); + assertEquals(763, handler.minimumProtocolVersion()); + assertEquals("unsupported", handler.minimumVersionName()); + assertFalse(handler.isSupported(762)); + assertTrue(handler.isSupported(763)); } @Test diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsServiceTest.java index f041c46a..b47ae44c 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsServiceTest.java @@ -11,6 +11,7 @@ import nl.hauntedmc.proxyfeatures.features.votifier.entity.PlayerVoteStatsEntity; import nl.hauntedmc.proxyfeatures.features.votifier.model.Vote; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; +import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import org.hibernate.LockMode; import org.hibernate.Session; import org.hibernate.query.Query; @@ -51,7 +52,7 @@ void offlineIdentityLookupUsesDataRegistryPersistence() { mock(ORMContext.class), mock(ORMContext.class), false, - directory + new PlayerReferenceResolver(directory) ); assertEquals( @@ -248,6 +249,7 @@ private static Votifier feature(Path dataDir, FeatureLogger logger) { when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); when(dataRegistry.players()).thenReturn(players); when(players.identities()).thenReturn(playerDirectory); + when(plugin.getPlayerReferenceResolver()).thenReturn(new PlayerReferenceResolver(playerDirectory)); Votifier feature = mock(Votifier.class); when(feature.getPlugin()).thenReturn(plugin); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VotifierConfigTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VotifierConfigTest.java index 85f04c4d..064d3c69 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VotifierConfigTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VotifierConfigTest.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.votifier.internal; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigNode; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigNode; import nl.hauntedmc.proxyfeatures.features.votifier.Votifier; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommandTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommandTest.java index 01d562b6..54bef7ea 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommandTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommandTest.java @@ -385,7 +385,7 @@ private VelocityBaseFeature feature(String name, String version, List FeatureListenerManager listeners = mock(FeatureListenerManager.class); FeatureDataManager data = mock(FeatureDataManager.class); - when(cmd.getRegisteredCommands()).thenReturn(Map.of(name.toLowerCase(), mock(nl.hauntedmc.proxyfeatures.api.command.FeatureCommand.class))); + when(cmd.getRegisteredCommands()).thenReturn(Map.of(name.toLowerCase(), mock(nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand.class))); when(task.getActiveTaskCount()).thenReturn(1); when(listeners.getRegisteredListenerCount()).thenReturn(2); when(data.getActiveConnectionCount()).thenReturn(3); diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/command/brigadier/BrigadierCommandTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/brigadier/BrigadierCommandTest.java similarity index 93% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/command/brigadier/BrigadierCommandTest.java rename to proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/brigadier/BrigadierCommandTest.java index 7d1e41c6..165ad255 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/command/brigadier/BrigadierCommandTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/brigadier/BrigadierCommandTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.command.brigadier; +package nl.hauntedmc.proxyfeatures.framework.command.brigadier; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.velocitypowered.api.command.CommandSource; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureConfigHandlerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureConfigHandlerTest.java index 46cccc73..2cad09b0 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureConfigHandlerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/config/FeatureConfigHandlerTest.java @@ -2,8 +2,8 @@ import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigService; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/config/MainConfigHandlerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/config/MainConfigHandlerTest.java index 696553f1..ba4db7d2 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/config/MainConfigHandlerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/config/MainConfigHandlerTest.java @@ -2,8 +2,8 @@ import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigService; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/extension/DefaultMotdExtensionsTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/extension/DefaultMotdExtensionsTest.java new file mode 100644 index 00000000..0e2cc66f --- /dev/null +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/extension/DefaultMotdExtensionsTest.java @@ -0,0 +1,51 @@ +package nl.hauntedmc.proxyfeatures.framework.extension; + +import nl.hauntedmc.proxyfeatures.api.extension.MotdContext; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContribution; +import org.junit.jupiter.api.Test; + +import java.net.InetAddress; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DefaultMotdExtensionsTest { + private final MotdContext context = new MotdContext(InetAddress.getLoopbackAddress(), 763); + + @Test + void resolveRegistrationPrefersHighestPriority() { + DefaultMotdExtensions extensions = new DefaultMotdExtensions(); + extensions.register("low", 10, ignored -> Optional.of(MotdContribution.secondLine("Low"))); + extensions.register("high", 100, ignored -> Optional.of(MotdContribution.secondLine("High"))); + assertEquals("High", extensions.resolve(context).orElseThrow().secondLine().orElseThrow()); + } + + @Test + void resolveRegistrationUsesDeterministicKeyOrderWhenPriorityMatches() { + DefaultMotdExtensions extensions = new DefaultMotdExtensions(); + extensions.register("zeta", 50, ignored -> Optional.of(MotdContribution.secondLine("Zeta"))); + extensions.register("alpha", 50, ignored -> Optional.of(MotdContribution.secondLine("Alpha"))); + assertEquals("Alpha", extensions.resolve(context).orElseThrow().secondLine().orElseThrow()); + } + + @Test + void resolveSkipsBlankOrFailingSuppliers() { + DefaultMotdExtensions extensions = new DefaultMotdExtensions(); + extensions.register("broken", 100, ignored -> { + throw new IllegalStateException("boom"); + }); + extensions.register("blank", 90, ignored -> Optional.of(MotdContribution.secondLine(" "))); + extensions.register("usable", 10, ignored -> Optional.of(MotdContribution.secondLine("Ready"))); + assertEquals("Ready", extensions.resolve(context).orElseThrow().secondLine().orElseThrow()); + } + + @Test + void unregisterRemovesEntry() { + DefaultMotdExtensions extensions = new DefaultMotdExtensions(); + var registration = extensions.register( + "maintenance", 100, ignored -> Optional.of(MotdContribution.secondLine("Maintenance"))); + registration.close(); + assertTrue(extensions.resolve(context).isEmpty()); + } +} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactoryTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactoryTest.java index 2caf2a89..d6b8bed0 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactoryTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactoryTest.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.framework.feature; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureApiManager; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureApiManagerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureApiManagerTest.java index b3482208..004baba5 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureApiManagerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureApiManagerTest.java @@ -1,128 +1,80 @@ package nl.hauntedmc.proxyfeatures.framework.lifecycle; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; -import nl.hauntedmc.dataregistry.api.service.FeatureServiceDirectory; -import nl.hauntedmc.proxyfeatures.test.TestFeatureServiceDirectory; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.framework.service.DefaultCapabilityRegistry; +import nl.hauntedmc.proxyfeatures.framework.service.InternalServiceRegistry; import org.junit.jupiter.api.Test; -import java.util.Optional; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; class FeatureApiManagerTest { - - @Test - void registerWithoutDataRegistryBindingStillTracksOwnershipForCleanup() { - FeatureApiManager manager = new FeatureApiManager(); - - manager.registerService(String.class, "value"); - assertEquals(1, manager.getRegisteredServiceCount()); - - manager.unregisterAllServices(); - assertEquals(0, manager.getRegisteredServiceCount()); - } - @Test - void unregisterServiceOnlyDetachesTrackedRegistration() { + void managerMustBeBoundBeforePublishing() { FeatureApiManager manager = new FeatureApiManager(); - - manager.registerService(String.class, "value"); - manager.unregisterService(String.class); - - assertEquals(0, manager.getRegisteredServiceCount()); + assertThrows(IllegalStateException.class, + () -> manager.registerService(PresenceApi.class, mock(PresenceApi.class))); } @Test - void differentOwnersCannotPublishSameApiType() { - DataRegistryApi dataRegistry = mock(DataRegistryApi.class); - FeatureServiceDirectory directory = new TestFeatureServiceDirectory(); - when(dataRegistry.featureServices()).thenReturn(directory); - FeatureApiManager first = new FeatureApiManager(); - FeatureApiManager second = new FeatureApiManager(); - first.bindDataRegistryCatalog("ProxyFeatures", "First", () -> Optional.of(dataRegistry)); - second.bindDataRegistryCatalog("ProxyFeatures", "Second", () -> Optional.of(dataRegistry)); - - first.registerService(String.class, "first"); - - assertThrows(IllegalStateException.class, () -> second.registerService(String.class, "second")); - assertEquals(0, second.getRegisteredServiceCount()); - assertEquals("first", directory.find(String.class).orElseThrow()); + void differentOwnersCannotPublishSameCapability() { + DefaultCapabilityRegistry registry = new DefaultCapabilityRegistry(); + InternalServiceRegistry internal = new InternalServiceRegistry(); + FeatureApiManager first = manager(registry, internal, "first"); + FeatureApiManager second = manager(registry, internal, "second"); + PresenceApi service = mock(PresenceApi.class); + first.registerService(PresenceApi.class, service); + + assertThrows(IllegalStateException.class, + () -> second.registerService(PresenceApi.class, mock(PresenceApi.class))); + assertSame(service, registry.reference(PresenceApi.class).require()); first.unregisterAllServices(); - assertTrue(directory.find(String.class).isEmpty()); - } - - @Test - void registerAndCleanupManageDataRegistryCatalogServices() { - DataRegistryApi dataRegistry = mock(DataRegistryApi.class); - FeatureServiceDirectory directory = new TestFeatureServiceDirectory(); - when(dataRegistry.featureServices()).thenReturn(directory); - - FeatureApiManager manager = new FeatureApiManager(); - manager.bindDataRegistryCatalog("ProxyFeatures", "Example", () -> Optional.of(dataRegistry)); - - manager.registerService(String.class, "value"); - - assertEquals("value", directory.find(String.class).orElseThrow()); - assertEquals("Example", directory.describe(String.class).orElseThrow().ownerFeature()); - - manager.unregisterAllServices(); - - assertTrue(directory.find(String.class).isEmpty()); + assertTrue(registry.reference(PresenceApi.class).get().isEmpty()); } @Test - void nullDataRegistrySupplierResultSkipsCatalogPublication() { - FeatureApiManager manager = new FeatureApiManager(); - manager.bindDataRegistryCatalog("ProxyFeatures", "Example", () -> null); - - manager.registerService(String.class, "value"); - + void replacementCleanupAndIdempotencyAreOwned() { + DefaultCapabilityRegistry registry = new DefaultCapabilityRegistry(); + FeatureApiManager manager = manager(registry, new InternalServiceRegistry(), "example"); + PresenceApi first = mock(PresenceApi.class); + PresenceApi second = mock(PresenceApi.class); + + manager.registerService(PresenceApi.class, first); + manager.registerService(PresenceApi.class, first); assertEquals(1, manager.getRegisteredServiceCount()); + manager.registerService(PresenceApi.class, second); + assertSame(second, registry.reference(PresenceApi.class).require()); + + manager.unregisterService(PresenceApi.class); + assertTrue(registry.reference(PresenceApi.class).get().isEmpty()); } @Test - void replacingServiceClosesPreviousDataRegistryHandle() { - DataRegistryApi dataRegistry = mock(DataRegistryApi.class); - FeatureServiceDirectory directory = new TestFeatureServiceDirectory(); - when(dataRegistry.featureServices()).thenReturn(directory); - - FeatureApiManager manager = new FeatureApiManager(); - manager.bindDataRegistryCatalog("ProxyFeatures", "Example", () -> Optional.of(dataRegistry)); + void internalPortsAreNotPublishedPublicly() { + DefaultCapabilityRegistry registry = new DefaultCapabilityRegistry(); + InternalServiceRegistry internal = new InternalServiceRegistry(); + FeatureApiManager manager = manager(registry, internal, "example"); + Runnable port = mock(Runnable.class); - manager.registerService(String.class, "first"); - manager.registerService(String.class, "second"); + manager.registerInternalService(Runnable.class, port); + assertSame(port, internal.require(Runnable.class)); + assertTrue(registry.availableTypes().isEmpty()); - assertEquals("second", directory.find(String.class).orElseThrow()); - - manager.unregisterService(String.class); - - assertTrue(directory.find(String.class).isEmpty()); + manager.unregisterAllServices(); + assertTrue(internal.find(Runnable.class).isEmpty()); } - @Test - void registeringSameInstanceIsIdempotent() { - DataRegistryApi dataRegistry = mock(DataRegistryApi.class); - FeatureServiceDirectory directory = new TestFeatureServiceDirectory(); - when(dataRegistry.featureServices()).thenReturn(directory); - + private static FeatureApiManager manager( + DefaultCapabilityRegistry registry, + InternalServiceRegistry internal, + String owner + ) { FeatureApiManager manager = new FeatureApiManager(); - manager.bindDataRegistryCatalog("ProxyFeatures", "Example", () -> Optional.of(dataRegistry)); - Object service = new Object(); - - manager.registerService(Object.class, service); - manager.registerService(Object.class, service); - - assertSame(service, directory.find(Object.class).orElseThrow()); - assertEquals(1, manager.getRegisteredServiceCount()); - - manager.unregisterService(Object.class); - - assertTrue(directory.find(Object.class).isEmpty()); + manager.bindRegistry(registry, internal, owner); + return manager; } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCacheManagerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCacheManagerTest.java index edf5fbef..759441e5 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCacheManagerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCacheManagerTest.java @@ -2,7 +2,7 @@ import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheDirectory; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheDirectory; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCommandManagerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCommandManagerTest.java index 5f83415a..bda659a0 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCommandManagerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/lifecycle/FeatureCommandManagerTest.java @@ -6,8 +6,8 @@ import com.velocitypowered.api.command.CommandSource; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.api.command.brigadier.BrigadierCommand; +import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; +import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeaturesTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeaturesTest.java new file mode 100644 index 00000000..92414071 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeaturesTest.java @@ -0,0 +1,60 @@ +package nl.hauntedmc.proxyfeatures.framework.loader; + +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class BuiltInFeaturesTest { + + private static final Set PUBLIC_CAPABILITIES = Set.of( + "AdmissionApi", + "FriendshipApi", + "MaintenanceApi", + "NetworkLocationApi", + "PlayerCountApi", + "PlayerLanguageApi", + "PresenceApi", + "QueueApi", + "RestartApi", + "SanctionsApi", + "TwoFactorApi", + "VersionApi" + ); + + @Test + void manifestExplicitlyDefinesEveryShippedFeatureExactlyOnce() throws Exception { + assertEquals(33, BuiltInFeatures.definitions().size()); + Set featureTypes = new HashSet<>(); + + for (BuiltInFeatures.Definition definition : BuiltInFeatures.definitions()) { + assertTrue(featureTypes.add(definition.featureClassName()), + () -> "Duplicate feature definition: " + definition.featureClassName()); + Class featureType = Class.forName(definition.featureClassName()); + assertNotNull(featureType); + + String packageName = definition.featureClassName().substring( + 0, + definition.featureClassName().lastIndexOf('.') + ); + Class metaType = Class.forName(packageName + ".meta.Meta"); + assertTrue(BaseMeta.class.isAssignableFrom(metaType)); + assertTrue(PUBLIC_CAPABILITIES.containsAll(definition.providedCapabilities()), + () -> "Unknown public capability in " + definition.featureClassName()); + } + } + + @Test + void providerClassificationsAlwaysPublishAtLeastOneCapability() { + BuiltInFeatures.definitions().forEach(definition -> { + switch (definition.classification()) { + case CAPABILITY_PROVIDER -> assertFalse(definition.providedCapabilities().isEmpty()); + case CAPABILITY_CONSUMER, INTERNAL, EXTENSION_PROVIDER -> + assertTrue(definition.providedCapabilities().isEmpty()); + } + }); + } +} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptorTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptorTest.java index 61c41b7e..10f66a54 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptorTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptorTest.java @@ -1,6 +1,6 @@ package nl.hauntedmc.proxyfeatures.framework.loader; -import nl.hauntedmc.proxyfeatures.api.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManagerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManagerTest.java index 0238d52e..7317b736 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManagerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManagerTest.java @@ -4,16 +4,19 @@ import com.velocitypowered.api.plugin.PluginManager; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.feature.stateful.SnapshotState; -import nl.hauntedmc.proxyfeatures.api.feature.stateful.StatefulFeature; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.framework.feature.stateful.SnapshotState; +import nl.hauntedmc.proxyfeatures.framework.feature.stateful.StatefulFeature; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.FeatureFactory; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.config.MainConfigHandler; import nl.hauntedmc.proxyfeatures.framework.feature.FeatureScopeFactory; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleFactory; +import nl.hauntedmc.proxyfeatures.framework.service.DefaultFeatureCatalog; +import nl.hauntedmc.proxyfeatures.framework.service.DefaultCapabilityRegistry; +import nl.hauntedmc.proxyfeatures.framework.service.InternalServiceRegistry; import nl.hauntedmc.proxyfeatures.framework.loader.disable.FeatureDisableResult; import nl.hauntedmc.proxyfeatures.framework.loader.enable.FeatureEnableResult; import nl.hauntedmc.proxyfeatures.framework.loader.reload.FeatureReloadResult; @@ -53,6 +56,9 @@ void setUp() { when(plugin.getLocalizationHandler()).thenReturn(localization); when(plugin.getLogger()).thenReturn(logger); when(plugin.getPluginManager()).thenReturn(pluginManager); + when(plugin.getFeatureCatalog()).thenReturn(mock(DefaultFeatureCatalog.class)); + when(plugin.getCapabilityRegistry()).thenReturn(new DefaultCapabilityRegistry()); + when(plugin.getInternalServiceRegistry()).thenReturn(new InternalServiceRegistry()); when(mainConfig.isFeatureEnabled(anyString())).thenReturn(true); when(mainConfig.openFeatureConfig(anyString())).thenReturn(mock(FeatureConfigHandler.class)); when(localization.openFeatureLocalization(anyString())).thenReturn(localization); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandlerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandlerTest.java index 78219b74..989e8803 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandlerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandlerTest.java @@ -5,14 +5,12 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import nl.hauntedmc.dataregistry.api.DataRegistryApi; -import nl.hauntedmc.dataregistry.api.service.FeatureServiceDirectory; -import nl.hauntedmc.proxyfeatures.test.TestFeatureServiceDirectory; +import nl.hauntedmc.proxyfeatures.test.MutableCapabilityRegistry; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.api.io.config.ConfigService; -import nl.hauntedmc.proxyfeatures.api.io.localization.Language; -import nl.hauntedmc.proxyfeatures.api.io.localization.MessageMap; -import nl.hauntedmc.proxyfeatures.features.playerlanguage.api.LanguageAPI; +import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; +import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; +import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.slf4j.LoggerFactory; @@ -21,6 +19,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; +import java.util.Locale; import java.util.UUID; import java.util.function.Consumer; @@ -113,16 +112,13 @@ void messageBuilderUsesPlayerTranslationAndFeatureLanguageOverrides() throws IOE status: "&bEnglish queue" """); - LanguageAPI languageApi = mock(LanguageAPI.class); + PlayerLanguageApi languageApi = mock(PlayerLanguageApi.class); UUID uuid = UUID.randomUUID(); - when(languageApi.get(uuid)).thenReturn(Language.EN); - - LocalizationHandler framework = new LocalizationHandler(mockPlugin(services -> services.register( - "ProxyFeatures", - "PlayerLanguage", - LanguageAPI.class, - languageApi - )), service()); + when(languageApi.resolvedLanguage(uuid)).thenReturn(Optional.of(Locale.ENGLISH)); + + LocalizationHandler framework = new LocalizationHandler(mockPlugin( + services -> services.register(PlayerLanguageApi.class, languageApi) + ), service()); LocalizationHandler feature = framework.openFeatureLocalization("Queue"); Player player = mock(Player.class); when(player.getUniqueId()).thenReturn(uuid); @@ -164,15 +160,13 @@ private ProxyFeatures mockPlugin() { }); } - private ProxyFeatures mockPlugin(Consumer servicesSetup) { + private ProxyFeatures mockPlugin(Consumer servicesSetup) { ProxyFeatures plugin = mock(ProxyFeatures.class); - DataRegistryApi dataRegistry = mock(DataRegistryApi.class); - FeatureServiceDirectory services = new TestFeatureServiceDirectory(); + MutableCapabilityRegistry services = new MutableCapabilityRegistry(); servicesSetup.accept(services); when(plugin.getDataDirectory()).thenReturn(tempDir); when(plugin.getLogger()).thenReturn(ComponentLogger.logger("LocalizationHandlerTest")); - when(plugin.getDataRegistry()).thenReturn(Optional.of(dataRegistry)); - when(dataRegistry.featureServices()).thenReturn(services); + when(plugin.capabilities()).thenReturn(services); return plugin; } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultCapabilityRegistryTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultCapabilityRegistryTest.java new file mode 100644 index 00000000..0d6f2968 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultCapabilityRegistryTest.java @@ -0,0 +1,76 @@ +package nl.hauntedmc.proxyfeatures.framework.service; + +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceSnapshot; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureId; +import nl.hauntedmc.proxyfeatures.api.service.CapabilityRef; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.Set; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class DefaultCapabilityRegistryTest { + + @Test + void stableReferenceTracksRegistrationReplacementAndRemoval() { + DefaultCapabilityRegistry registry = new DefaultCapabilityRegistry(); + CapabilityRef reference = registry.reference(PresenceApi.class); + PresenceApi first = presence(false); + + CapabilityRegistration firstRegistration = registry.register( + FeatureId.of("vanish"), PresenceApi.class, first + ); + assertSame(reference, registry.reference(PresenceApi.class)); + assertSame(first, reference.require()); + assertEquals(Set.of(PresenceApi.class), registry.availableTypes()); + assertEquals(FeatureId.of("vanish"), registry.owner(PresenceApi.class).orElseThrow()); + + firstRegistration.close(); + firstRegistration.close(); + assertTrue(reference.get().isEmpty()); + assertTrue(registry.owner(PresenceApi.class).isEmpty()); + + PresenceApi replacement = presence(true); + CapabilityRegistration replacementRegistration = registry.register( + FeatureId.of("vanish"), PresenceApi.class, replacement + ); + assertSame(replacement, reference.require()); + replacementRegistration.close(); + assertFalse(reference.isAvailable()); + } + + @Test + void registryRejectsConflictsAndNonApiContracts() { + DefaultCapabilityRegistry registry = new DefaultCapabilityRegistry(); + PresenceApi existing = presence(false); + registry.register(FeatureId.of("vanish"), PresenceApi.class, existing); + + assertThrows(IllegalStateException.class, + () -> registry.register(FeatureId.of("other"), PresenceApi.class, presence(true))); + assertThrows(IllegalStateException.class, + () -> registry.register(FeatureId.of("vanish"), PresenceApi.class, existing)); + assertThrows(IllegalArgumentException.class, + () -> registry.register(FeatureId.of("bad"), String.class, "not-an-interface")); + assertThrows(IllegalArgumentException.class, + () -> registry.register(FeatureId.of("bad"), Runnable.class, () -> { })); + assertThrows(NullPointerException.class, () -> registry.reference(null)); + assertThrows(NullPointerException.class, () -> registry.owner(null)); + } + + private static PresenceApi presence(boolean hidden) { + return new PresenceApi() { + @Override + public boolean isHidden(UUID playerId) { + return hidden; + } + + @Override + public PresenceSnapshot snapshot() { + return new PresenceSnapshot(Set.of(), Set.of(), Instant.EPOCH); + } + }; + } +} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalogTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalogTest.java new file mode 100644 index 00000000..4fa2c3f1 --- /dev/null +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalogTest.java @@ -0,0 +1,88 @@ +package nl.hauntedmc.proxyfeatures.framework.service; + +import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesApiVersion; +import nl.hauntedmc.proxyfeatures.api.feature.*; +import org.junit.jupiter.api.Test; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.Optional; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class DefaultFeatureCatalogTest { + + private static final Instant NOW = Instant.parse("2026-08-06T00:00:00Z"); + + @Test + void catalogProjectsSortedPointInTimeLifecycleSnapshots() { + DefaultFeatureCatalog catalog = new DefaultFeatureCatalog(Clock.fixed(NOW, ZoneOffset.UTC)); + FeatureDescriptor vanish = descriptor("vanish"); + FeatureDescriptor queue = descriptor("queue"); + catalog.register(vanish); + catalog.register(queue); + + assertEquals(FeatureState.DISABLED, + catalog.find(FeatureId.of("queue")).orElseThrow().state()); + catalog.transition(FeatureId.of("queue"), FeatureState.STARTING); + catalog.transition(FeatureId.of("queue"), FeatureState.ACTIVE); + + var snapshots = catalog.snapshot(); + assertEquals(2, snapshots.size()); + assertEquals(FeatureId.of("queue"), snapshots.get(0).descriptor().id()); + assertEquals(FeatureState.ACTIVE, snapshots.get(0).state()); + assertEquals(NOW, snapshots.get(0).observedAt()); + assertEquals(NOW, snapshots.get(1).observedAt()); + assertTrue(catalog.find(FeatureId.of("missing")).isEmpty()); + } + + @Test + void failuresExposeAUsefulMessageAndUnknownTransitionsFailFast() { + DefaultFeatureCatalog catalog = new DefaultFeatureCatalog(Clock.fixed(NOW, ZoneOffset.UTC)); + FeatureId queue = FeatureId.of("queue"); + catalog.register(descriptor("queue")); + + catalog.fail(queue, new IllegalStateException("startup failed")); + assertEquals(Optional.of("startup failed"), catalog.find(queue).orElseThrow().failure()); + + catalog.register(descriptor("queue")); + catalog.fail(queue, new IllegalArgumentException()); + assertEquals(Optional.of("IllegalArgumentException"), + catalog.find(queue).orElseThrow().failure()); + assertThrows(IllegalArgumentException.class, + () -> catalog.transition(FeatureId.of("missing"), FeatureState.ACTIVE)); + assertThrows(NullPointerException.class, () -> catalog.register(null)); + assertThrows(NullPointerException.class, () -> catalog.fail(queue, null)); + } + + @Test + void rootFacadeRetainsItsStableDependencies() { + DefaultCapabilityRegistry capabilities = new DefaultCapabilityRegistry(); + DefaultFeatureCatalog features = new DefaultFeatureCatalog(); + ProxyFeaturesApiVersion version = ProxyFeaturesApiVersion.current("runtime"); + DefaultProxyFeaturesApi api = new DefaultProxyFeaturesApi(version, capabilities, features); + + assertSame(version, api.version()); + assertSame(capabilities, api.capabilities()); + assertSame(features, api.features()); + assertThrows(NullPointerException.class, + () -> new DefaultProxyFeaturesApi(null, capabilities, features)); + assertThrows(NullPointerException.class, + () -> new DefaultProxyFeaturesApi(version, null, features)); + assertThrows(NullPointerException.class, + () -> new DefaultProxyFeaturesApi(version, capabilities, null)); + } + + private static FeatureDescriptor descriptor(String id) { + return new FeatureDescriptor( + FeatureId.of(id), + id, + "1.0.0", + FeatureClassification.INTERNAL, + Set.of(), + Set.of() + ); + } +} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/test/MutableCapabilityRegistry.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/test/MutableCapabilityRegistry.java new file mode 100644 index 00000000..bd8bb35e --- /dev/null +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/test/MutableCapabilityRegistry.java @@ -0,0 +1,42 @@ +package nl.hauntedmc.proxyfeatures.test; + +import nl.hauntedmc.proxyfeatures.api.service.CapabilityRef; +import nl.hauntedmc.proxyfeatures.api.service.CapabilityRegistry; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** Small mutable registry for capability-consuming unit tests. */ +public final class MutableCapabilityRegistry implements CapabilityRegistry { + private final Map, Object> values = new ConcurrentHashMap<>(); + + public void register(Class type, T value) { + values.put(type, type.cast(value)); + } + + public void clear() { + values.clear(); + } + + @Override + public CapabilityRef reference(Class type) { + return new CapabilityRef<>() { + @Override + public Class type() { + return type; + } + + @Override + public Optional get() { + return Optional.ofNullable(values.get(type)).map(type::cast); + } + }; + } + + @Override + public Set> availableTypes() { + return Set.copyOf(values.keySet()); + } +} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/test/TestFeatureServiceDirectory.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/test/TestFeatureServiceDirectory.java deleted file mode 100644 index 96f2e4ed..00000000 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/test/TestFeatureServiceDirectory.java +++ /dev/null @@ -1,59 +0,0 @@ -package nl.hauntedmc.proxyfeatures.test; - -import nl.hauntedmc.dataregistry.api.service.FeatureServiceDirectory; -import nl.hauntedmc.dataregistry.api.service.FeatureServiceHandle; -import nl.hauntedmc.dataregistry.api.service.FeatureServiceInfo; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -/** API-only in-memory feature catalog used by consumer tests. */ -public final class TestFeatureServiceDirectory implements FeatureServiceDirectory { - private final Map, Entry> entries = new ConcurrentHashMap<>(); - - @Override - public FeatureServiceHandle register(String plugin, String feature, Class type, T service) { - FeatureServiceInfo info = new FeatureServiceInfo(plugin, feature, type, service.getClass().getName()); - Entry entry = new Entry<>(info, service); - entries.compute(type, (ignored, existing) -> { - if (existing != null - && (!existing.info().ownerPlugin().equals(info.ownerPlugin()) - || !existing.info().ownerFeature().equals(info.ownerFeature()))) { - throw new IllegalStateException("Feature service is already registered by another owner: " - + type.getName()); - } - return entry; - }); - return new FeatureServiceHandle() { - @Override public FeatureServiceInfo info() { return info; } - @Override public void close() { entries.remove(type, entry); } - }; - } - - @Override public Optional find(Class type) { - Entry entry = entries.get(type); - return entry == null ? Optional.empty() : Optional.of(type.cast(entry.service())); - } - @Override public T require(Class type) { return find(type).orElseThrow(); } - @Override public boolean contains(Class type) { return entries.containsKey(type); } - @Override public Optional describe(Class type) { - Entry entry = entries.get(type); - return entry == null ? Optional.empty() : Optional.of(entry.info()); - } - @Override public List list() { return entries.values().stream().map(Entry::info).toList(); } - @Override public boolean unregister(Class type, Object service) { - Entry entry = entries.get(type); - return entry != null && entry.service() == service && entries.remove(type, entry); - } - @Override public int unregisterOwner(String plugin, String feature) { - int before = entries.size(); - entries.entrySet().removeIf(entry -> entry.getValue().info().ownerPlugin().equals(plugin) - && entry.getValue().info().ownerFeature().equals(feature)); - return before - entries.size(); - } - @Override public void clear() { entries.clear(); } - - private record Entry(FeatureServiceInfo info, T service) { } -} diff --git a/proxyfeatures-toolkit/pom.xml b/proxyfeatures-toolkit/pom.xml new file mode 100644 index 00000000..2996048f --- /dev/null +++ b/proxyfeatures-toolkit/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + nl.hauntedmc.proxyfeatures + proxyfeatures-parent + ${revision} + + proxyfeatures-toolkit + ProxyFeatures Toolkit + Optional reusable configuration, cache, HTTP, and text implementations. + + 0.90 + + + + net.kyori + adventure-api + provided + + + net.kyori + adventure-text-minimessage + provided + + + net.kyori + adventure-text-serializer-gson + provided + + + net.kyori + adventure-text-serializer-legacy + provided + + + net.kyori + adventure-text-serializer-plain + provided + + + net.kyori + adventure-text-logger-slf4j + provided + + + org.spongepowered + configurate-yaml + ${configurate.version} + + + org.spongepowered + configurate-core + ${configurate.version} + + + com.google.code.gson + gson + + + org.slf4j + slf4j-api + provided + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + ${project.groupId} + proxyfeatures-testkit + ${project.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + + + + maven-jar-plugin + + + + nl.hauntedmc.proxyfeatures.toolkit + + + + + + + diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/ToolkitContext.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/ToolkitContext.java new file mode 100644 index 00000000..1688bddc --- /dev/null +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/ToolkitContext.java @@ -0,0 +1,16 @@ +package nl.hauntedmc.proxyfeatures.toolkit; + +import org.slf4j.Logger; + +import java.nio.file.Path; + +/** Minimal host contract required by toolkit configuration and resource services. */ +public interface ToolkitContext { + Path getDataDirectory(); + + Logger getLogger(); + + default ClassLoader getResourceClassLoader() { + return getClass().getClassLoader(); + } +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheDirectory.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheDirectory.java similarity index 85% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheDirectory.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheDirectory.java index dd866c62..5463358b 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheDirectory.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheDirectory.java @@ -1,7 +1,6 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache; +package nl.hauntedmc.proxyfeatures.toolkit.io.cache; -import nl.hauntedmc.proxyfeatures.api.io.cache.impl.JsonCacheFile; -import nl.hauntedmc.proxyfeatures.api.io.cache.impl.SqliteCacheFile; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.impl.JsonCacheFile; import java.io.File; import java.io.IOException; @@ -49,7 +48,7 @@ public File getDirectory() { * Create or open a cache store file inside this directory. * * @param fileName name without extension — e.g. a player name or "logs" - * @param type YAML, JSON, or SQLITE + * @param type cache serialization type */ public CacheStore getStore(String fileName, CacheType type) { File file; @@ -59,10 +58,6 @@ public CacheStore getStore(String fileName, CacheType type) { file = new File(dir, safeName + ".json"); yield new JsonCacheFile(file); } - case SQLITE -> { - file = new File(dir, safeName + ".db"); - yield new SqliteCacheFile(file); - } }; } diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheStore.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheStore.java similarity index 91% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheStore.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheStore.java index 0204d9f9..314c8541 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheStore.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheStore.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache; +package nl.hauntedmc.proxyfeatures.toolkit.io.cache; import java.io.File; diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheType.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheType.java new file mode 100644 index 00000000..fa7f87bc --- /dev/null +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheType.java @@ -0,0 +1,8 @@ +package nl.hauntedmc.proxyfeatures.toolkit.io.cache; + +/** + * Supported cache back-ends. + */ +public enum CacheType { + JSON +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheValue.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheValue.java similarity index 97% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheValue.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheValue.java index 4c83c328..86f8d754 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheValue.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheValue.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache; +package nl.hauntedmc.proxyfeatures.toolkit.io.cache; import java.util.LinkedHashMap; import java.util.Map; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/FileCacheStore.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/FileCacheStore.java similarity index 91% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/FileCacheStore.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/FileCacheStore.java index 89e34bbd..6c4360ad 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/FileCacheStore.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/FileCacheStore.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache; +package nl.hauntedmc.proxyfeatures.toolkit.io.cache; import java.util.Map; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/JsonCacheFile.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/impl/JsonCacheFile.java similarity index 82% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/JsonCacheFile.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/impl/JsonCacheFile.java index e47bc672..529ef51d 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/JsonCacheFile.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/impl/JsonCacheFile.java @@ -1,12 +1,17 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache.impl; +package nl.hauntedmc.proxyfeatures.toolkit.io.cache.impl; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheValue; -import nl.hauntedmc.proxyfeatures.api.io.cache.FileCacheStore; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheValue; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.FileCacheStore; import java.io.*; import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; @@ -62,8 +67,25 @@ private void load() { } private void saveLocked() { - try (Writer w = new FileWriter(file)) { - gson.toJson(rawMap, w); + Path target = file.toPath().toAbsolutePath(); + Path parent = target.getParent(); + try { + if (parent != null) Files.createDirectories(parent); + Path temporary = Files.createTempFile(parent, file.getName(), ".tmp"); + try { + try (Writer writer = Files.newBufferedWriter(temporary, StandardCharsets.UTF_8)) { + gson.toJson(rawMap, writer); + } + try { + Files.move(temporary, target, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } catch (AtomicMoveNotSupportedException ignored) { + Files.move(temporary, target, StandardCopyOption.REPLACE_EXISTING); + } + } finally { + Files.deleteIfExists(temporary); + } } catch (IOException ex) { throw new IllegalStateException("Cannot save cache file " + file, ex); } diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigMap.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigMap.java similarity index 96% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigMap.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigMap.java index c47b9372..75c5c732 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigMap.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigMap.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; import java.util.HashMap; import java.util.Map; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigNode.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigNode.java similarity index 98% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigNode.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigNode.java index aa7228dc..988a9029 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigNode.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigNode.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; import java.util.*; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigService.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigService.java similarity index 96% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigService.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigService.java index f5068d94..fb7ab7b7 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigService.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigService.java @@ -1,6 +1,6 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; -import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesContext; +import nl.hauntedmc.proxyfeatures.toolkit.ToolkitContext; import org.slf4j.Logger; import java.io.IOException; @@ -24,7 +24,7 @@ public final class ConfigService { private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); /** Preferred: build from the plugin for data dir, logger, and resource classloader. */ - public ConfigService(ProxyFeaturesContext plugin) { + public ConfigService(ToolkitContext plugin) { this(Objects.requireNonNull(plugin.getDataDirectory(), "dataDirectory"), Objects.requireNonNull(plugin.getLogger(), "logger"), plugin.getResourceClassLoader()); diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigTypes.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigTypes.java similarity index 99% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigTypes.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigTypes.java index a019a754..52ac95a5 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigTypes.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigTypes.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; import java.util.ArrayList; import java.util.LinkedHashMap; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigView.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigView.java similarity index 99% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigView.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigView.java index 5f8e6d1e..2399f149 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigView.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigView.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; import org.spongepowered.configurate.CommentedConfigurationNode; import org.spongepowered.configurate.serialize.SerializationException; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/YamlFile.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/YamlFile.java similarity index 98% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/YamlFile.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/YamlFile.java index 1acc39e5..1b6a96ad 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/config/YamlFile.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/YamlFile.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; import org.slf4j.Logger; import org.spongepowered.configurate.CommentedConfigurationNode; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/localization/Language.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/localization/Language.java similarity index 92% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/localization/Language.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/localization/Language.java index 063de474..6e650c51 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/localization/Language.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/localization/Language.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.localization; +package nl.hauntedmc.proxyfeatures.toolkit.io.localization; import java.util.Arrays; import java.util.List; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/localization/MessageMap.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/localization/MessageMap.java similarity index 89% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/localization/MessageMap.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/localization/MessageMap.java index 026e3268..1e582fc0 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/io/localization/MessageMap.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/localization/MessageMap.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.localization; +package nl.hauntedmc.proxyfeatures.toolkit.io.localization; import java.util.HashMap; import java.util.Map; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/http/SimpleHttpClient.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClient.java similarity index 81% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/http/SimpleHttpClient.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClient.java index a5207f3f..9314716f 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/http/SimpleHttpClient.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClient.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.http; +package nl.hauntedmc.proxyfeatures.toolkit.util.http; import java.io.IOException; import java.io.InputStream; @@ -38,6 +38,11 @@ public static String postHttps(String url, List args) throws IOEx return post(url, args, CLIENT, true); } + /** Sends a bounded JSON request over HTTPS using the shared transport and timeout policy. */ + public static String postJsonHttps(String url, String payload) throws IOException, InterruptedException { + return send(url, "application/json", payload == null ? "" : payload, CLIENT, true); + } + static String post(String url, List args, HttpClient httpClient) throws IOException, InterruptedException { return post(url, args, httpClient, false); @@ -60,6 +65,17 @@ private static String post( .map(p -> encodeFormComponent(p.name()) + "=" + encodeFormComponent(p.value())) .collect(Collectors.joining("&")); + return send(url, "application/x-www-form-urlencoded", form, httpClient, requireHttps); + } + + private static String send( + String url, + String contentType, + String body, + HttpClient httpClient, + boolean requireHttps + ) throws IOException, InterruptedException { + Objects.requireNonNull(httpClient, "httpClient"); URI uri = URI.create(Objects.requireNonNull(url, "url")); String scheme = uri.getScheme(); if (scheme == null || (!scheme.equalsIgnoreCase("https") && !scheme.equalsIgnoreCase("http"))) { @@ -72,8 +88,8 @@ private static String post( HttpRequest request = HttpRequest.newBuilder() .uri(uri) .timeout(REQUEST_TIMEOUT) - .header("Content-Type", "application/x-www-form-urlencoded") - .POST(HttpRequest.BodyPublishers.ofString(form)) + .header("Content-Type", contentType) + .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); if (requireHttps && !"https".equalsIgnoreCase(response.uri().getScheme())) { @@ -83,16 +99,16 @@ private static String post( } throw new IOException("HTTPS is required for the final response"); } - String body; + String responseText; try (InputStream stream = response.body()) { byte[] bytes = stream.readNBytes(MAX_RESPONSE_BYTES + 1); if (bytes.length > MAX_RESPONSE_BYTES) { throw new IOException("Response too large"); } - body = new String(bytes, StandardCharsets.UTF_8); + responseText = new String(bytes, StandardCharsets.UTF_8); } if (response.statusCode() >= 200 && response.statusCode() < 300) { - return body; + return responseText; } else { throw new IOException("Unexpected response code: " + response.statusCode()); } diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/parse/JsonUtils.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtils.java similarity index 86% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/parse/JsonUtils.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtils.java index aeda671b..abdbbb56 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/parse/JsonUtils.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtils.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.parse; +package nl.hauntedmc.proxyfeatures.toolkit.util.parse; public class JsonUtils { diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/TextPatterns.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatterns.java similarity index 97% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/TextPatterns.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatterns.java index 1a73fa28..d50b4061 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/TextPatterns.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatterns.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text; +package nl.hauntedmc.proxyfeatures.toolkit.util.text; import java.time.format.DateTimeFormatter; import java.util.regex.Pattern; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/ComponentFormatter.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatter.java similarity index 99% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/ComponentFormatter.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatter.java index 33858819..987cfde6 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/ComponentFormatter.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatter.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.format; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.format; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -10,8 +10,8 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import nl.hauntedmc.proxyfeatures.api.util.text.TextPatterns; -import nl.hauntedmc.proxyfeatures.api.util.text.format.constants.FormatConstants; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.TextPatterns; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.constants.FormatConstants; import java.util.*; import java.util.function.UnaryOperator; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/TextFormatter.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatter.java similarity index 98% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/TextFormatter.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatter.java index 05a7b4b4..752019cd 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/TextFormatter.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatter.java @@ -1,9 +1,9 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.format; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.format; import net.kyori.adventure.text.minimessage.MiniMessage; -import nl.hauntedmc.proxyfeatures.api.util.text.TextPatterns; -import nl.hauntedmc.proxyfeatures.api.util.text.format.color.LegacyColorUtils; -import nl.hauntedmc.proxyfeatures.api.util.text.format.constants.FormatConstants; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.TextPatterns; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.color.LegacyColorUtils; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.constants.FormatConstants; import java.util.EnumSet; import java.util.Objects; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/color/LegacyColorUtils.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtils.java similarity index 98% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/color/LegacyColorUtils.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtils.java index 322036c9..3b864a4e 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/color/LegacyColorUtils.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtils.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.format.color; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.color; import java.util.Map; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/constants/FormatConstants.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstants.java similarity index 82% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/constants/FormatConstants.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstants.java index d1aedf01..9fa01472 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/constants/FormatConstants.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstants.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.format.constants; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.constants; public class FormatConstants { public static final char AMP_CHAR = '&'; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/inspect/FormatInspector.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspector.java similarity index 96% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/inspect/FormatInspector.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspector.java index a42750a8..22757ec4 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/format/inspect/FormatInspector.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspector.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.format.inspect; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.inspect; import net.kyori.adventure.text.*; import net.kyori.adventure.text.event.ClickEvent; @@ -6,8 +6,8 @@ import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; -import nl.hauntedmc.proxyfeatures.api.util.text.TextPatterns; -import nl.hauntedmc.proxyfeatures.api.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.TextPatterns; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; import java.util.EnumSet; import java.util.Objects; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/placeholder/MessagePlaceholders.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholders.java similarity index 98% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/placeholder/MessagePlaceholders.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholders.java index 06821531..ec43afa7 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/text/placeholder/MessagePlaceholders.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholders.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.placeholder; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/tools/Paginator.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/Paginator.java similarity index 93% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/tools/Paginator.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/Paginator.java index 0daaa557..90bb098d 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/tools/Paginator.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/Paginator.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.tools; +package nl.hauntedmc.proxyfeatures.toolkit.util.tools; import java.util.Collections; import java.util.List; diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/type/CastUtils.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtils.java similarity index 94% rename from proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/type/CastUtils.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtils.java index 6554ae31..bf14141b 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/util/type/CastUtils.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtils.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.type; +package nl.hauntedmc.proxyfeatures.toolkit.util.type; import java.util.ArrayList; import java.util.Collections; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheDirectoryTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheDirectoryTest.java similarity index 91% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheDirectoryTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheDirectoryTest.java index bece75f6..baa400db 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheDirectoryTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheDirectoryTest.java @@ -1,7 +1,6 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache; +package nl.hauntedmc.proxyfeatures.toolkit.io.cache; -import nl.hauntedmc.proxyfeatures.api.io.cache.impl.JsonCacheFile; -import nl.hauntedmc.proxyfeatures.api.io.cache.impl.SqliteCacheFile; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.impl.JsonCacheFile; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -25,9 +24,7 @@ void createsSanitizedDirectoryAndBuildsRequestedStoreTypes() { assertTrue(directory.getDirectory().getName().contains("cache")); CacheStore json = directory.getStore("../players", CacheType.JSON); - CacheStore sqlite = directory.getStore("db", CacheType.SQLITE); assertInstanceOf(JsonCacheFile.class, json); - assertInstanceOf(SqliteCacheFile.class, sqlite); } @Test diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheValueTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheValueTest.java similarity index 95% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheValueTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheValueTest.java index 80596b31..a96f5bec 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/CacheValueTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/CacheValueTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache; +package nl.hauntedmc.proxyfeatures.toolkit.io.cache; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/JsonCacheFileTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/impl/JsonCacheFileTest.java similarity index 97% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/JsonCacheFileTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/impl/JsonCacheFileTest.java index 656fb8b3..7b4ae231 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/cache/impl/JsonCacheFileTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/cache/impl/JsonCacheFileTest.java @@ -1,6 +1,6 @@ -package nl.hauntedmc.proxyfeatures.api.io.cache.impl; +package nl.hauntedmc.proxyfeatures.toolkit.io.cache.impl; -import nl.hauntedmc.proxyfeatures.api.io.cache.CacheValue; +import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheValue; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigMapTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigMapTest.java similarity index 95% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigMapTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigMapTest.java index 32e7b512..dfb4f1c4 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigMapTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigMapTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigNodeTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigNodeTest.java similarity index 97% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigNodeTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigNodeTest.java index bbd9ae4d..4a309397 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigNodeTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigNodeTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigServiceTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigServiceTest.java similarity index 94% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigServiceTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigServiceTest.java index d07a37c5..67d14cf0 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigServiceTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigServiceTest.java @@ -1,6 +1,6 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; -import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesContext; +import nl.hauntedmc.proxyfeatures.toolkit.ToolkitContext; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -53,7 +53,7 @@ public InputStream getResourceAsStream(String name) { @Test void viewHelpersReturnUsableViews() { ComponentLogger logger = ComponentLogger.logger("ConfigServiceTest"); - ProxyFeaturesContext plugin = mock(ProxyFeaturesContext.class); + ToolkitContext plugin = mock(ToolkitContext.class); when(plugin.getDataDirectory()).thenReturn(tempDir); when(plugin.getLogger()).thenReturn(logger); diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigTypesTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigTypesTest.java similarity index 98% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigTypesTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigTypesTest.java index 552d70bd..06fc1b71 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigTypesTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigTypesTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigViewTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigViewTest.java similarity index 99% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigViewTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigViewTest.java index 2acd4b9a..00a975cc 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/ConfigViewTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigViewTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/YamlFileTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/YamlFileTest.java similarity index 97% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/YamlFileTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/YamlFileTest.java index 5124829e..c553729d 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/config/YamlFileTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/YamlFileTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.config; +package nl.hauntedmc.proxyfeatures.toolkit.io.config; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/localization/LanguageTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/localization/LanguageTest.java similarity index 83% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/localization/LanguageTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/localization/LanguageTest.java index 2ef8f163..bf5a8bc0 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/io/localization/LanguageTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/io/localization/LanguageTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.io.localization; +package nl.hauntedmc.proxyfeatures.toolkit.io.localization; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/http/SimpleHttpClientTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClientTest.java similarity index 99% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/http/SimpleHttpClientTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClientTest.java index c2dff919..c645aec7 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/http/SimpleHttpClientTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClientTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.http; +package nl.hauntedmc.proxyfeatures.toolkit.util.http; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/parse/JsonUtilsTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtilsTest.java similarity index 84% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/parse/JsonUtilsTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtilsTest.java index 0351f092..d6e86647 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/parse/JsonUtilsTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtilsTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.parse; +package nl.hauntedmc.proxyfeatures.toolkit.util.parse; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/TextPatternsTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatternsTest.java similarity index 96% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/TextPatternsTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatternsTest.java index ff02a020..a4749bbd 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/TextPatternsTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatternsTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text; +package nl.hauntedmc.proxyfeatures.toolkit.util.text; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/ComponentFormatterTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatterTest.java similarity index 98% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/ComponentFormatterTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatterTest.java index 94b9b7da..512631a9 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/ComponentFormatterTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatterTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.format; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.format; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -6,7 +6,7 @@ import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import nl.hauntedmc.proxyfeatures.api.util.text.format.inspect.FormatInspector; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.inspect.FormatInspector; import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/TextFormatterTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatterTest.java similarity index 98% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/TextFormatterTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatterTest.java index 73a877fe..5efb7529 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/TextFormatterTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatterTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.format; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.format; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/color/LegacyColorUtilsTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtilsTest.java similarity index 93% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/color/LegacyColorUtilsTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtilsTest.java index 98dcb36f..ee21fe5d 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/color/LegacyColorUtilsTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtilsTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.format.color; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.color; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/constants/FormatConstantsTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstantsTest.java similarity index 88% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/constants/FormatConstantsTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstantsTest.java index 680fabca..4867ed5c 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/constants/FormatConstantsTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstantsTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.format.constants; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.constants; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/inspect/FormatInspectorTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspectorTest.java similarity index 96% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/inspect/FormatInspectorTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspectorTest.java index b87d34c2..3b708a54 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/format/inspect/FormatInspectorTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspectorTest.java @@ -1,11 +1,11 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.format.inspect; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.inspect; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; -import nl.hauntedmc.proxyfeatures.api.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; import org.junit.jupiter.api.Test; import java.util.EnumSet; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/placeholder/MessagePlaceholdersTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholdersTest.java similarity index 97% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/placeholder/MessagePlaceholdersTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholdersTest.java index ae5591fa..63d1bf9a 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/text/placeholder/MessagePlaceholdersTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholdersTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.text.placeholder; +package nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder; import net.kyori.adventure.text.Component; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/tools/PaginatorTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/PaginatorTest.java similarity index 95% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/tools/PaginatorTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/PaginatorTest.java index d60e29b2..5f0fae72 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/tools/PaginatorTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/PaginatorTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.tools; +package nl.hauntedmc.proxyfeatures.toolkit.util.tools; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/type/CastUtilsTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtilsTest.java similarity index 94% rename from proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/type/CastUtilsTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtilsTest.java index ef9ed851..bfa236c8 100644 --- a/proxyfeatures-api/src/test/java/nl/hauntedmc/proxyfeatures/api/util/type/CastUtilsTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtilsTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.api.util.type; +package nl.hauntedmc.proxyfeatures.toolkit.util.type; import org.junit.jupiter.api.Test; From 36c28c90d0999a745b94a115ae01b3b4bbbc2ee6 Mon Sep 17 00:00:00 2001 From: Remy Duijsens Date: Thu, 6 Aug 2026 09:28:38 +0200 Subject: [PATCH 002/110] ci: export api-redesign source for validation --- .../workflows/api-redesign-source-export.yml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/api-redesign-source-export.yml diff --git a/.github/workflows/api-redesign-source-export.yml b/.github/workflows/api-redesign-source-export.yml new file mode 100644 index 00000000..8b7ef797 --- /dev/null +++ b/.github/workflows/api-redesign-source-export.yml @@ -0,0 +1,26 @@ +name: API Redesign Source Export + +on: + push: + branches: + - api-redesign + +permissions: + contents: read + +jobs: + export: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Upload source snapshot + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: api-redesign-source + include-hidden-files: true + path: | + . + !.git From 263b5934fbae65dd53d5a2cb018ef2b30180b0c2 Mon Sep 17 00:00:00 2001 From: Remy Duijsens Date: Thu, 6 Aug 2026 09:29:17 +0200 Subject: [PATCH 003/110] ci: expose source snapshot for full redesign validation --- .github/workflows/ci-tests-and-coverage.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/ci-tests-and-coverage.yml b/.github/workflows/ci-tests-and-coverage.yml index 7b71aadd..02a29c5a 100644 --- a/.github/workflows/ci-tests-and-coverage.yml +++ b/.github/workflows/ci-tests-and-coverage.yml @@ -11,6 +11,25 @@ on: - main jobs: + source-export: + name: Source Export + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Upload source snapshot + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: api-redesign-source + include-hidden-files: true + path: | + . + !.git + tests: name: Tests and Coverage runs-on: ubuntu-24.04 From 5a801a0fdd0c79ec80fbaed35a9b70e5487a30fc Mon Sep 17 00:00:00 2001 From: Remy Duijsens Date: Thu, 6 Aug 2026 10:44:17 +0200 Subject: [PATCH 004/110] chore: stage reviewed API redesign patch --- .github/api-redesign-patch/part00_00 | 1 + .github/api-redesign-patch/part00_01 | 1 + .github/api-redesign-patch/part00_02 | 1 + .github/api-redesign-patch/part00_03 | 1 + .github/api-redesign-patch/part01 | 1 + .github/api-redesign-patch/part02_00 | 1 + .github/api-redesign-patch/part02_01 | 1 + .github/api-redesign-patch/part02_02 | 1 + .github/api-redesign-patch/part02_03 | 1 + .github/api-redesign-patch/part03 | 1 + .../workflows/apply-api-redesign-review.yml | 36 +++++++++++++++++++ 11 files changed, 46 insertions(+) create mode 100644 .github/api-redesign-patch/part00_00 create mode 100644 .github/api-redesign-patch/part00_01 create mode 100644 .github/api-redesign-patch/part00_02 create mode 100644 .github/api-redesign-patch/part00_03 create mode 100644 .github/api-redesign-patch/part01 create mode 100644 .github/api-redesign-patch/part02_00 create mode 100644 .github/api-redesign-patch/part02_01 create mode 100644 .github/api-redesign-patch/part02_02 create mode 100644 .github/api-redesign-patch/part02_03 create mode 100644 .github/api-redesign-patch/part03 create mode 100644 .github/workflows/apply-api-redesign-review.yml diff --git a/.github/api-redesign-patch/part00_00 b/.github/api-redesign-patch/part00_00 new file mode 100644 index 00000000..86e68a66 --- /dev/null +++ b/.github/api-redesign-patch/part00_00 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part00_01 b/.github/api-redesign-patch/part00_01 new file mode 100644 index 00000000..699365c7 --- /dev/null +++ b/.github/api-redesign-patch/part00_01 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part00_02 b/.github/api-redesign-patch/part00_02 new file mode 100644 index 00000000..993a798d --- /dev/null +++ b/.github/api-redesign-patch/part00_02 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part00_03 b/.github/api-redesign-patch/part00_03 new file mode 100644 index 00000000..07e322bd --- /dev/null +++ b/.github/api-redesign-patch/part00_03 @@ -0,0 +1 @@ +tIiEa7Nu4BFODvJEhwrow9t1gasEXOsR4YMQ1fylyfyw0rL4F07eaq5PEYuSNAVvnQAQu2VmUJLr7n2aIz3OPHUr5CdmGB/jNboMhVuyc8/h0C7h2soazduJvhx80w5VmOcfeQ0e12fR5eMEtawVjOtLxIqHMhQs7OD9J9K/gjV1H62mlEWFLgUfM8paq9H8+5MgRmVjxpSjNoeYt2+ZphaKnNEUnFlEPsEL1/75GHmuAdijp3HrJY6CHSJGgdwVWBU4Atoo/g0DIeKmJACU2OF2+HxsuIm7fhIBKrT8uG83RRjDN6Zdh+kPeKYE9S+jvxGUbGvrkNCWfBNVhpeszNQIy7pXCY8ksKyRCnRN31U6x1lmdftRx4MFVFh8go6XWKqWcf+qIITeNZC8A+aWrxzXmWyT2hj1bvg54ImoCcU0iNiGA2k6aNdf9fDvr3IJ0EbYu75W01ZFbz9zVVdGNiiJH07aquzjiFTdoU/EXHWGVMZxy9MT2umllFP6pw+o3TzmgVOeROCef+BcTW6UpCO3QIRmHDSPgzBlRsF1fkfa/O/J4tX4PpyGn4GuCH7cywZmVnKI+bjU8bFPpzfix/p/v6rPzowXzn01SLT4QTLtfM89UeEgV9MWhFbdHFoZN21eRGs2jWSQXHH/COyMo7DbjtGCpUiYVNewp60S9+2Y1l0TfFI7nQhBxzzb5TqVqJ6z2sI2MvMlsDtJKB+CGz+539REFiGM9n2cjhFcrYJ12gzmj31m4nkO7mzPMs8wfBZzUpY0iLkumW7yXQfbUcjzSzsIdU+T9g0naFLiBaIe7nLwGppMZccrP10D4THFHZ1/VN87eDrAnyFcAF8vJ0iObI3bxl8obXR4UXsIFo4tYzMWOiT6F0u41qXeBkVP2TvQwFZDKjIcALfjux5JgP+PKAHaxy28Bd52ajDYViXyokkQ2DafrKkZkYGFkuzQ9XFw0XcdoNKo8HFiYGU190oJ5ZMpdhNb3HYtvldi+mV8VYNTl3BgbP0Oh8yA2td9083hNXfcSSew/kR84uut2dLFHi9lmaba5GnHl58j1WeL37gX6KEw0VnePqtHhRTWtGo1aa/wqKmySHwNdPivI1Z6wWA17KT16Zzw2Gv/wakcxp50mGGJi2Y7eGE69maFAJzEJLm2OqafQrFqectHU1mF+UTZgr922T/zLtce09b/kvI4HSHdCLsjX3jNA0s4Yluxu3hNozf8i1/swytrH4PBjqF+pAMR3Xxunzpypm2pZkCacJawY+F0Yk8HINOzrbdei5+nzpWwnXUeNhXe/XGpL+yEt3uLJ04gWCOA4bhWfGU7J8qmzjbQuikKRl11ywM0jo79h3BEgykAJ6V5yRuIGhIt5X4i1kiFdP24bXLtnVxpJ2hQ82QMdCiauAa4M3piZp3hGRheD3qESH7aKnGzUodfpaYj6Gzi74Oi/qMtN6fINKTAhkqDhGSpApMVyWYH33VNnPEk3uSxRH6uqRMrvof+I1BAEhKfMQ3n3p6Prs2bdt4CD0G5t8QDhijrgmMfHwDbGVl90nfb5jiLnq6yvRZ8maBKw8FLHpLM1WVwbAiGihQv3tSbVs5E3Dc8BezXalkyqJ1R+LTIW5rDV04wDgUVbsYL6m5YTtrT/iFle1FDnET9pfmSmJgJwZbcPMTZjTyEN8Mo1udwsPBeV2+oQ7kX2YFTx9XgEjgR7JeTPjJKe3SWG+YHP9LXJHhGXEyW3yofvnC+3V4iI9kOGgqassmIDIGGm9Ucjjuy4UhYmIaoMgfd9Y13aI48ok11jGZe4OOT3U7UZFnHX/qzIQAsUStUCzkAuy56WuREhkqFDyluHSCNYGsP3EQvZyKsUNu8K5t0iDqMYkoTfCIbf1Ui+TggDr0qrYqgD84s8jpbDWMcDVu8krBMZu3SM6Tdyr7kwpR1WRvw2fnghYdvzzyeSJyfi2rmA08JK8Vn1K1HJyCCvB7S54naJb/tK/iTvXZ1ousSUKsjaQxpPnWYuiH8f+SExUe0woKN1/sh2MThYvuDnHh56cSOYNR7GOgy8a/7fOlDv1dzYIWAoSZtSnFUoFm+W1Q7wSEcW2Nzvppt+FuvHub1VD8aCBFmzjnMOAoIb7gn3BuikaSUQZQOqfvDR1wWAWKHvCZDCwSCkCP4wUI7kdk9qoEcecYhpTz1Oa5cxsXvOlAspeyQ5nkwfw68KjyfSGa1FUl6by3qasWDuPzECpInIoARhV6u3WwOMm++q79HPGZOmnWUKk5w/Szoadq5EpXfcSwiowIJ74oDdXnkfVijuGzrwfCVMtQnteI7sKc5jHWjUNVJSS+1gGf+v1uilf3I3sCLFw3jBLRBM2ThzS8CikpS0WRZFJsZDmhncwOhY6R2bsLn/ixzygzsTomlcaTMzo/vQ57Uh8Ob3FZgAiGoPMZtzBE9zO6TAKCzWC2ufcY504To4MnK4nzEzMkhlX9PnhRNLRSS/eiabZDRocm2z/v2uH/tVDl3CPpS/nwVIh+8QtlEtZ2nr316ROIf89JB5RfN5Tka1oxd3Se5rDiy7slVw9CLG+d9fbkWdA/Cmof/Urr0fqWDFXNdz2W9J92mUO5Ho6rYbx++Y7NCEqK3v06WXh6YGtnT7/nNSu5ZNWWKbJ01t9qffb1B194ZD4iCEkz1rGa2YruLNo7l4i+k3sgnPHXj3mFFAsZSbTMh1UMP4Syaz6/y+jBYjNCo/ANIFYcVqyE6s7kVfuGKrtXbnBO+yQpe/9NnslYfrpUVn2FD+nCuq9nYoCFNrjmQxuKUJwBPl43Lo2WAjgk8+ufmpZZNGuenPxoBK77leLaqpBGFoaOBOavHP8pL+dRtDxSe+mWxTOmmlOvPa/p0yCDlvTMDQxqa+ytXQw3725R5mg8eECW21vu8EmszM8U8evwe9bc/Y4QolxxTbREFgopdGydHNKcTSg1gIWC1aEm3qNq/rpWnd0qx2SqtJnD+fPXWupiBmcqEFTgMVk5iGIqrZNVWX0gHtEm+WktekTJjeBBYb9KIqJIO89s8ozAbyK4yCwnpTMhQ3OrSv/MlcI+X5+Dv5unQlnTq7bd102e2RT04m0gYFwHETqfKjr8eSlUihZQ1KyLNei/Kv6SJ4U5VC6ZWVmUjty3zSnLdIPRW0LhX/CU4YFrjS/PxsGfx3vvVwzdsmswVSiu4bCWBL7JqKhdrJfTL6zSQXWqvXczjQcleHDHYKCJdUCiDihN2FPAgArpZMWIA+OFB/tDGZkSo8wQnS85Mn5hvFuOwwps7Wl7i35iQ6XotBG3KPqH2cUCnTUecP2RXtktN5Ak6czccpP5kO66QDDA8e/XN5ii4COjMFA3BAepvml2ejhcR+kwHPTEA8XH1EUj2XY/BtTadiU+Pju6tSAKGGt30h0GMoxTyMGhM2i7jivZsKhlICqtk26MRSJ4KqDlZhLLhpG/KqABotKQoJkei9Li+2Oqrl/vcBmM7+1StyszMhchn4FlW2jVtmkXpOseu1/6ADLk9WJm+tH0OxkdlcWzAexofIjWPNmh9fiAqEiwxKuO6Q/UhtTxC4JEbzXVRyg1sCbbCEbtdgEScmQUWDbEo4JyPYS4ASgTDZgbcvPMoutvLQ1Deugu0foETHWg1l09ESuLPK9neXMS7f0c5LpLJIDvj8gyaRO9YKu3R3A4HlpzDelrWg1EU5N5oyd579WfIqZjWuS7NOn8+hStbudyPsXUaRktyJ2icS+PRcQNKWLpbIWqECVJDDaXXtAdjiebMGQSwbKd4UZ0MPhOrZy+IEs1kRCZqFQ/8SpXjasW2pVwGWtSWQf2eTT8mFEJOIzgEgP4VYlvxZo6P6hOe/xqOspE0XRoLoJIQbxzOc9bTVKFOuREVdgOfGdWcR6fq2webq1ZTkp/VZQCo9SKNG2D9E1UMiMpvFF6D2NH26cMJyDpdZcDrJsXigA1kyKnCA4CftYGk4ji+JxfrR8mCghchADuvqqqkSa7VgGV6jQeaPaU6aNN2d/X8J8Si76WVwmVkhP9huybMTL0amTU3S7Gc7c+/ZRuwVyaFXyD7jxoPY3ZQTL94jXto8A0ux0JNJj01c4k8+Y29+q1kwlTbqa6m9bivpZVTecyzgv9y9jlc6GmIgYFsklNN8tfilrv8YJ+NNoj7aPxoLOKv4+x6LQVh80l4tpIwBvih5PynqpAzFWUnRKAd5e8aPGNtO4Z4c6Z1nisiTCJMGj+wgqiFgMLOnX/y+uLRC3J44PQNsQ6UwPbgpp8yjR925npuRm8RNacjrqJ6OgfRoK2zFiJBm3Ry1X7nOlE3nN56QMi2z/Fveng2bq8Jk8XVMr4dIw4iYQPF5W6QKqSlmjO81k3WPoNSGUtXuhaGIllOT/LrQUQp4oCbMHUDwnUN5jSOmCGXdDjtpcmYLCiJ0q2Iq7N9hhVUEwzP7vqkaWq8p15hbHoFco2W7DKWvjhLSMe4bh02vBPlz40EFHvPvn3UsvNs2lgqXsqgKTlwkpi/0D3Stix7FJNQyd6sWnYz0Wnt2UN0D2+edD46TUzMvWK2AyWUNIDO1wSYajMgXapvhPjCLixSrO1BZbYccXFRHuTv+um+CD4F9T/ZUqvj8kd+MDgpR1ExjdFFB4SxkIYxHBKVk28Xt4WQjsZiVJYdRmcubvDLHlIoStXtOYS6Og2AdDx/0ES9u7lH+dq+IkQ11EXJ1PT1n8elVndGjR6BFkP7QjQ7hS5/wn2ePTRjcWFLzmK4KZl8lmSky4AAassxdQSJfU0zetPb6MzMoy7BU9h+yAYxm980yVWb5ZgMoxErUBBKhe58bDp19D4Xg4MQvPO0ek8P/rJDq8c7O5xsc8r/wpNRcq9BkoWmQC/l4+76GZTz2ecQOGN4FeYW5yZua3ZROxeQCkRSdOFKHUub3F522BIX6gfULmDPDYXy1eWJCHjN7LTSEsjb2y90K2i1DoKKbg3PwaX9k9bfd7Z67KXKStSfobi1Dji5fkaD/BiHlDHjRzVdZqbp \ No newline at end of file diff --git a/.github/api-redesign-patch/part01 b/.github/api-redesign-patch/part01 new file mode 100644 index 00000000..cdab8624 --- /dev/null +++ b/.github/api-redesign-patch/part01 @@ -0,0 +1 @@ +OxsolFapCMOY7LV0+q5xVoMpPSpsexPrmBcFe09s5+quiPkcLrVsXK7c9DtwUh9tVf1yK1eUkzOlRuuna6cqZyqWrtdY+Vf5rYNv4mWFsldTOKp7SEYyrRCqWiXOqdoNKk2i+xdyfgzuBgaz5Y2UuUJYACtYQsv52kLJDZflrgbxrgyyrdFRuvyTlYqNWRGsEBOJUfEIdOledLs0kaxoAQOZmjHPbRTS3qQ6BsNHFNtdYQW37MsyjwKA/iLO0jX5kuY9yvm3zrYwftJikaXuSvjH/ljOZFxJlz7ef7p5G+2vuap72hBb8KlXHotxh0Q3ZrKli+gB30B/VhUWthb3voun0tILyN/meuE2yJhFH2Q/xHYr9J5nzRgfw05pjBq24kmbli/6a+MvlL4SAm8O8rcMrJ2Llh8PQEyQmzKeT/QoAFM7+icaRWdOzhWuxnYK9MG03pliyGGQRWiLnAsU20pWc/dbXaG+v8a73F4PhwnqfoNZ9BcNIo59lZo9DhIun+g/QuHwm5XsHUk0QQ2EJ2ccrnY83GTRzCuFI6mWouu09HljhhkohLwhtbQMt1L1LsYrHmpbBGXdOdns38hW70/RRPLUFDKeXRGEqJ/nXRknVQTaK90xZrwj96whPQxWpfqwybzLqT62zOEWaFriu8kdmRc7e+w4zCUJeqJ0ECYU3E7HiQ2Fy+Fu1tvB5XQCRIqwav5hDoB5RYYjXZ4GAbBfW0+Fli/l7dvjN/gMnYThEUDS0rcLBQPMBxVs9TJOgZ08d0VvL00cpLUNYOw0JT+ew858ZtmA/sS8KImwPsr95D3GeOrJgDg3EBZl+K81ywBizrn5ADceuyEgkjAd0QeS+rirSH1ndsL6yZe0k24hFGzQUOEcdrNMtxBvzHPc41cfcI9DLT5viupY0S+dfNtGZhEtQhMHMma4bLOh/6PJTIJt03yYfo8Il+5yCyj0rxMcpzYbPQ1jMhqiGZySa7BhDNt1KSqGes+hsmdn9XjNi1eE6PLLSfgXrTaxcWjt6+EHg+rVM3EEj7HQtyz5RZNUbL3BGlGENBl7EfEONHPGIrGw3BX6XA6Umyiup9BHBiw/YokvvBQ+JUi0U7fxCAuiO7MFh7+doowbPQIB9DKARMKXi0QDjcN25nMTYzU+fUZRynAoNWUJebLo+jrpGbXP49pfgVGxAukdByUYpaMCgo8YystfBrbXUrBvWnvgp4PIB7D4Z8c/uVH6iylt5x3DfTGrRamrAvr4tYYtZjdW0VOe3GCW3Z76vHf9Nu6pRYO48cpE2XNSLBlsCfm/+dk1qI0u9/tAEDTSQRTpLL1S3jak6rGSKHe4F3B2FsJngK6Yj15CcUOIuvYubTQeaZ3MYOsvvc5vOgxRFsywVhOe2EGWOH7VtlAL+s+GyDQ2sOUqZe+CstRkHK7f8iW5bynedC3V1/9a2U8AxcHvGO/OYS/SHpQ+79Z/TtPiU4Hu9QpqeC5g+mMp58VBgrzd5r3kZxzFkc1JFeI6jwU3k5ghxWOL82XA/a0WEjt0XBv5oLOARly7EbapuErkDDZPA7rcexo05wAWZV6jNB+/v1239yRLhyxPuhBt/UV04f7Jb1cpMUkh2gbK9k3jXTCzfiW2OIF27WLIhs9UCRPKuWLlthfRWqvCFppuLmIrzYuzHpvvBwSf6Be5/UXkOKC8/r8LL2Buj15WBTGuA3IsTNmqcR/i8e1hxdv+cOk87+n+4iuiUVPZb/HlLRxfIpotkpUa+ePE/R6pewEasUa2QfRPQbaHC485PSA9iMWY2HO4vGeSPniR1TVvPpew1Won7gEN6q01CzFfKKmQU6QBGOj2Lx2pZuNBJTv+5E07RwMYE6HaFqPGqTYoXFdkNx4qC0krKK1+5f6u78Zi0K3fgOsOkebT3xNXuiDaFk9JmCDP2HjS5lGBMsCeg2YzPG4UY5i+0nWdn7S1hx5RFsqfCtt7xJ4TaON2SOi99iZQFeFNKtJi1g5+xk0Vnef6vNbo3W3SF443pN2TViARhAUPtjkMYVkuQC1ZERlr1ILmoFxCSDpHBxSVu6eSv1hjdn6eJmE9LudRbLZCC/SJO4ItcBqD88LW3AkytGU9ZmGJxjvgq/tnHkjCT4x9qsdDAcrixM8wh3rSzUVzsiKu95+uOhC4RhOBl0vJSsloFRb91japCQxvgyMHgPDy1CCMxWdVoQC20rvV6ujShTmU8PQqcmQdCZSYxd13yt5w0bZ0yhbM6ldtF9/WS6C2NSHZrBMEXr7fgSKWs9DDfsM72fZP2Azn/xcd+YRW3tLuSdu0pVCYlrchrCh7aqEBoVu6arwpBbbXJsuOTD9iC016bSM++7Wu5ZemdtKCgJmdJZdJsNEvE3TMAfp1YIFZt2bWCvAC80iw/WiHmQO1hB7uG9FeZ3nhp09VRYV1vuAvwuh065O++BA2W4lYMP2obGN1w61wIxxlyJ0NissiXRXdoa5SJC5PczqS85UL1GUIx4zbgXJjJ2LfTr21R9skmpccH4hsY3jSdQzGX4rTMqAwNHp+GChGigU212uvSD4wIMjCDMK17bV88tmk2WpPe5dO6aCnBko4e5ZHWt5WJ0ua6OSmi16t3JPrCN1MUgdRvIs/TOmIrJWfxAGy4BXmFTkCQFyRX/3enGTukcAkTo5DTc/N62xlPPXpxr6H8LzpkJZvK0wGsopJSE4CbR6lY2vYfQyE3U46BS4wFz8HqxaQ4Yxuebp0YZMxcxZ+sP56nWq/b1YwzoVd5OKaqQHXWe/G3TzQUf8hfeRecJhziFqm8l+nNyv40Xq9s4WiV+UT0ml2mg7TBUyLMuNyKmMd07GlsSYzUDoJQYfykj4Exw669IjefTVw+zbfZP5SfgUbeTB4r5Ti7FcagXRkABN2hGhXbI16/p1yY7uheMK4PhAs5JQO2146TK69wtHXWxRToq8DYFDJmk9QuoRAI6T81gb8LcY8Osdj7+Dv2lEB2uOLre/Xn2xb+I4L0mODex9ocfxYhAzw4dWj5kxh2XYZW+X17IzDpPRwDCnvPpOFnv79xdNDFxZPDDZPkoXQHuLxH8NIJUN7K9EAgEF0/QeH2rp/25iE2RslROGlaWN5EXeY3E9BAU38D3sKtKnBRf0WH3qtmWD2z9JjV7u4nrdro/554psIC504scr189LpzadQlJxGZDVDeArXUfofwiHKyXaF8QAdxYE0vpy9Bm/z0HWV9MOFCUQiQqSRd5PPNE+p96LqKNqw+aXiU/gQK4N1zFLkNMhbJLaqo4Vc6FCzUEhetG8Bdu0H7fiOVZln+vEgkbNY3Mpg3iBb4QMvQ1GkCBEEGcdNf4t9f2BZVAJMlzHj16V8vmyC/5d97ZniQUrZ4MtZiELPs4yIUX43W3HY+ighpc71QKtPL/bfW1v6LNHIFjj4A4FrG4W79IT1utRFw+Jr35aluh3MeTxvT3UvZWGK2SRsoiu4seFRpxMKsA5kDzEp3zTqQJMC856G3viWJNZcRMsps9XxIuC/dC714hWAhB30Bxjk2mqV9pZ4ObiXvaGNvdVrQXtQj6E/Zkl6hZbyzaJPOBVWDKJXxqwo6YpA4vf/9gzbFLFKo2Ej/oTg5qobPXVpmTi3AzKtsm0S7olZUIGKMWH+0DWWGUEUi8FNvmjLm7sC8mszDUUm0G/C+pHGcB4Cm3LdCjdQRetCOozUxnJEhCNSN8ARLeY3PtcktWiiSlB77Vv9MX3QqSbndWDMM5afw/vEEtgHctzfbZEw9RZsCFDmPfUrPH6iv6QHk/ZzmABKM36Rmme1UQ6ZRQ+lt750T7IpoUHIFuj5c6Xlv7QK24w/4OAkPSj+7b+O14j5ShxkmFhIZmWfaZ7rsIFokbyVw9PTjlAAxcIFLKrv1wwio233uEoq4cIEF6bJpHSnp83Mii0x9V2mCLKPVPAVd0esK1lUH2vJRAs13Z937EjLiTbNKBeBKI6B88hGLlEhLkNJDMrc1LChVLrs2Ud0GHf1Ejx3vYPcEyQB8m0+mUi593GAW5bshxsUeIsPmr3LnsZUa5GgBpi3vom+RWJnzwyeX5LIGHq0t8PkZO65YHHrzitLwHxDbi7gtqgfz2MS/BUV78B2TRTQBFluEUaKPeDBo45iG/kBJN954mtJfUZnBbIgjvbIjvcTBFp26XjPGc7Xezff1gn7lWg6h8WfZMFcfUSo8yDN4lX0nwXeCZumgSOdmp/Ja6SwMAean1IH8FYnWtPLev7uEEa1gMH1ik0xYmKKFOTvvmz+zVqdJckFQS4dgiDT/3BrmrSr2OLpcV4zKDrUwnynZKcuRaUXpB/3eTi6RBVW3hIM35Yl3EkNMRmbxnm64ZLWR3TC2flaS1l9HSO2f1wImI1aFbJoaHXBXdFi4ue6TT/vZahXWfgrH/leKav3ABQgReRJx+PRf+WxZldrD3/7nbr9fSBjwXspx3iP6Mb6hgl49gXVOxs9XZVnb2em5VmmsIH0iUX44dCldOhUgK2l26Om3Z72MG+1k45h9C1F5fQWvxGLMNqU3NUj6pbKUGwxQqa76pNhPsAFu4xjt3rNG+ZYPUz1xHX9Hh/7YF/+Qig/KxzmhSh9gIgbFk7MnOx47OhqBKi1/c3vm9+zdcYsKxdTgdLoUB3oItLDxFRtnhCfCkrkkGFH0oI/hzpl7ybNN4c1Hm6Yak0p3NFvD+/XuVijbohW3co8V1DLcRXzUTAy1fKE/upqqFdtMMAnxbEP8TceuSP9Rz5VtP78cJ2PWJCIS1uaMnhIPaPIrEFI9b77HgJSP5FTtru8dbc1poT3PsIbpj5BLZ0RNjDU9yYnfs0QghWvzRUkfYFSBEva2uTQ43szX/SAF3YeSuwtWBU4rOBsVOlclMYjQQcAcLZ8if+bUiYj7hypb5eMCAphzSvn2vnXDp1ABWMnlUXE+peezkcRTIKfHiVhCKXOHNPe/ltmBnrEfk6ktxSQK+KgTVDrxw0bXn3cg2Xb/mqHjwSbwFOri3mO4yyWAnYef7tDdl6YemLm+VIA2cyfye617xw/l5NXFGTuC24pf6jUweEhmaGTsiD8I228yn6qiSAeCbuTXgzIkmeudymUEaYYi9nTnedlM55rXDX9pymP6FypKEW/WzeBdMLsSkp4i5occrfDOHVPXLMHNaCTZzHi0V08nhjffUfgkwIB/1EyZCJfvdystiQ3KXgnF6fYb3V2pX0SKLcLbGG0kA0TpSZLD6O3jQX10X++zinsCGixu2QGdFEnUe1Eyha6XNQjU77biaFpwvfBkUUe2Ikq0Zio84rAex6a/g4cwiiSwQjqbKLfkThFOSjIQCu6ObgAdkb3ouhIHmTdNf+jE2u27KjGmpABVl9nFAgLWUJpUJCHeMKiV20gfk2nl9EKbmy+iQzabFE6SOXjHV7yos6w0W81aeNULVux3QEZdbGXt76JBYpgMoA4Bq0cVdT+yLWTJvIQhvlXEjT63n4qH/cWdokSDgsUtCs/R7eX8yKNPoboeaVo/H1GqqonI27avXV0iIc4B2Xt3kyQKeudjhV9qxzKy7zAfFpx9ULm7AgOHPZHTr0u1H9wWRnfx6Id3Wn30wjO/2bYNk1Tmm35B3VmT78OoZmN+p1d1aEOKZD6LNftXep8xQYH3bdBlLea3OPuYwPwNk1b3cqjMMmK1Q9EV/Vjh/twbrH/3bEHuXPcav6L8Xakvda5lSiDFKfcnV4a7W0Da/RVIywlugnmp/gmcY1G4MJ1tDJ/6mJ1jErWedAW3Q7CpZ0nWS8wwbyVrvba31W+2tLF+qIIKabqcLin/CEjXM/R5WX++yzk+HElFaflHkaBxAkhJwwRt/9ZpJfEMBbGT+PJ/wlPdeRqIE7xg5wNX8oj3aIKe6n7TyQ+F96IC0N6P6jXrOv36uny+31cwgVIIhruKB72SdvKl9IEKL1Nd+WdwsPIWfhaydeUpEvSVs2aNrl6vNFp/Bm06mZmuSTHIWI4RTFkUZoFpN9Ck9XFPcMHAMl3+ic84jZyKTPJPwj877baoUv1EjIXDm/wMqCBEBZzEIf632kik9IDphc7GX60O7FrnReAtn3U1gg+nwA2BmsWYwFcl3wth1lAgdMc5rl+0hLsboCfhIwor2rA/oHAhnKVN2PePBKj8dZsul7wdmwFyK7B9tPnEHFcFKKQ8rPaGeUc2iVIhNMK6klZ5eOB7MOSPSah9LDJ6/MQPb+NEWMRQKWcx+oSvNVf0iE4BvDaTQg3mLWSchZVczhiizkDOfC1/KCoMJ4Fz4wtdn+vlP+jAe3T5C4zU/lZ1nPR9NIRejfu4tL0MHDKu4G9LefP4jDQqb2UNQZHnvTwqodWES0SynviNgq34hpb2gn8RwvlZDg4P2iByKNCHoSAbsVAVx8PXWKCHX/HTs8dUS7o7ttFBX19un7WZ0qDODzt5fhU81RSIS8M/kx3qIAQMa5axl/SOIyItd1trcTqyfjiUFzxbbI6rxmRF82GCIPkNHb2J1KdpDjFFpHOjzQyEnXdlW8EbaCzO/unpxDZJthacjPJYRXY7P/+WZABjWSEJUT6vbVi8QcmvtvwnM5n8Ph5fV0qnHKDxs9R5QarljU1fuJ4DoWuk0OTg2r/5ofBm74mvnJHfSa2jbBTGR5iGpkBkw/hH4OG0xJYwLB5K4iw7Ys72IEqss1zAkFkRt/Xs+Jz8Ykh93m/3ziPRNuHh4UHJfwX40A14wNpCZNM6TUSLa1Fq+/eOkc3tZSSQPABVoPegTKDwsXl024MzRWuYIxXjU3371L4dE9Z1To8k7Q2Ga2WJF9hobWRzazNz68BEDuoxxHgf2ND8B888bNofXrVaLxj8QeMukqN4+l8TcWA2ALLxsLIQm762fBuuVq8UcZegLwRLCIYXonbKJ/fEcgd/ioT8xGY+vCzlUIPvJbbyPDe9DNt3coBN92MxfaD11iP4Qh7Q4ILl0lRMr5RSjSbQWMcP6Wnsz7i4uNruZLvRvgQvA6ZyyoTmxsdeLisCVRKS9pXyTXxJU9wSDtJdxq1/ANOjRirtx2C3Vn363B2yrQdlJpJlegU+/D2tvgy0RJ6QdL2zBwHtEeqrfTl7q8Xq0Rf/iCeFpHJorp6UVQBWWROWWRemVYQ7Zat4zrUsJ4H3kVHkvSwc+Jj+9nRUy4DwjrDZlwuYwXg1iqrnPPx/NNHRTWZq4kOpkkiLO1+DGcKplpRBaI5lH1nzt2+H93ZV484H/zj4P6CP0rgLfoTV+CDRGQ+Ua4QVGGFZ2K6TCZikMTPPtzmNUMfz2vVpMWqWUrBPg2y/hcwKqDwe4s00K24V17z/MD8vO2v0PYv8UedNH6HJV0dj5U0G37vHvT0M7oGZfeaiGXuz6xNwkjpQ/ahJbXdgYrUGKB2+Stj/9s91tcS+TLT2kz26Ppg+yFEiasn9UitoqL5UrccStjOuUNr2w1F6mKi+B3j8vMxx99N59kbCck2KS4sDkXKVyT8h7jvjrRNxLIhWYxVxQoxe3HkvUoCWJa36G9/y0M+TOIDUHJvDrxH6ZcoM1fUr0N25UDnrWW45BtCTbdFWJrxZuLXzgSiJm7TzpCVGwgqOnX/9EJX8u8JCsmZ4EQZQOF9adEqJ8KhQ9GVap3xljl011TGmuR3Zun+kznCXMDHsosJABJDCMpzEOT5O0FTPgJ8mNGSLd6VmC2ZCLOgvGUJislVviulhoyL+QxTDsk+nC0AI+umky7JCMUtkgoWM/pKh/KwAMKuzhX62ZrxH0uyJyO9aqEG63v2pvZt/sgN1QHFIDK3dPUtCwjYBK5bBgfe8XQLTOzNkvlTUZsfZqFi4NENByhMmGtFoJzYieeiCBYXYNSc2ChLtivdOQ7bLn59RS3YadYYt9MwELR9CKC/Xy76la3gxCXuOWga+MU6bc9tdI+TnOv05jsfoHVxaD80v9J0tUzkvWIQtxZd/mIMcCGwsPwrqcOtIHkPqYybp8vn46WMRM6NtyulqIdqPdYm4GX3gu1PGbC2N/83XkMQl711HJLDrGBh+gaO8n63WbpFNaiAvrp1ZvH6hOS4YVIHxjDnS40TzTg2lmbCxQ0pKXowA7ft6XyhkBAQ0S1Up/eP1UPDI7QfV1pgZlAuX0tMHVDDq/SfnOzgd6FSG2o8OYga9BL95Zpccu/D/mtakw9FUu4oYRGBFVVaDZSS0cyADHgzYpEhLMu+X6QRJRrdbIJVVNS4gwBmTDwDI7HHQERj2vCrQwMi0FLTZREe6ls9fg2Y0/03x3+CbsxGyJhxsZAeruPRoIwpk6ANSe2R4ADsbEcKIUyV6399sfye2ebgJrYd7crKQXhVR7SvSRhQmEK8h/XKQfZPwtk29WDHKtTJs0pPjHqtq2Y+qL3Vj7pL8Necx4M38XgMAA9g7MUsAcM4yWzdyjGaS8VVPsIMho6Mhf/vXSDTXiOA9xZCEGU2OROdeYi0IDintjUv0FdOyKO2NRm5K+xXKCS3XovX6JHGj/bfJ/GFbHCg9GqmLoUgQc+e93XiHFNkq+50Zq//LlGCYBDMlsSaUib6Z7sF2Zm7PH48tZ0VhmZr7bNKoMwYoy85lfITdE3wl5T4JtfDBKbFhSTUR1gsjEudyhVLPPzvcS+9h87YcXXiZVr5OHU+udrhlQO/EzmRCLLYJgl3JO03WA3XfgKgeibDuyPaN4nm+U+osLqXbFV5m0PxFEHi04lLlp2eVWrU11chlPuLG6Vdzz6+OVLtTRFiSj3BGVndNfaiyDqxG8StuejqYU6a9ZmDBc0HvXKdtwj2FM7NUz6ia6IOaaDcUW2n6LBRrYJJuy+UtKJUDipnhz7+w2TutQJo143ZKLlWn7UTgAE1NA/LbfThK+v+yBBaX70UFqjXnrsozqDqV74le3p1KF3/VYH4dmaYTW/Ch/Ki6uN8ErX0eNA3t+YdxJCqeg88cyHtmuiOTsZHUs5MA5i1VdK+cYHhrWvsrifuZOs86EpwGPSU77HB9rUwjnJ/4EdVNbIVdSYPnPmSIvkrHw+PBhqHsqiMH/dPg3yII4CHGDY5hVMq+1txGr4Kfb5Rs4iZe3GQE0wXTaWzSLHSoh17xS+DFeDd9qU8DNVQ6zD2wZKty85hrLSuc+X13EjJdYDBGVPvVSMMphAzvkNkKe5O40PBVjL3Fv/jSBJ/LFJpqboXRlUe6nNTC0saJp/5JFxZT/kwQY4wYyhAHBQX1Ogd3R4UmmRiEmTXwcO88hvd3lZNeXN4rwAkDyqCo75QJF0aYiijsZRyj6plSw31KnEg3gIY1Fg8BxAl0ILoXTlSorLXYq8joC/39/XCItfmB3H+c3D0nY/Ap80bM/H+aQpp9OS9j7kFCAPVY80xInBfh+ByA6cdAeZ/UNO5WOjdco5bbBGvvEKRF1QoB5ZjPzvRSCrnljN5eaCJu+M4UpN3XPCM3z0phK8GaHmwSIrDppT4to2q1PGRzwQzsYf8v7pPboJSS4w4yyW+DpeEODt/1eG7B0IzaulTqX5yP7dXqS8pc2OXiQo6FPJafK5+jKHimcAMi0agaRU4WcfQ5gEI0fYRW5y8SsYfMHbO6PEkLPxvGGhcUwd+gIdbZ0BmqwXrl1UktJQoul5QJd6nP98TKtxcLQBocPchU9E2VHfzjNVSvxUCunNG+lEzPkPyzDzpEuoPNFwOrI6QggK+hsCFRqnhJGZCLlFRLxaXTIVKnkBUREJRqa1yzmLNhShwj2AG5ClTNUTVl0lPYDb9nohdKOJX0svn5IwxRdVrecHe07aGou1XkeFbkY1tAK4+z12uqiz1lJCwS4D4boLCGRxTGFAe5hszhgiF+Df30tcZIneY3DfhOAF14kdw1gntIB9ktR/VnbMjFz5Yy32MxLU5RFaEtx8AEl7/0ElXyCiou8pQnAUAoF5KahqQp7/vsvwBeenx0jd1XVq1Ff2W+FriaahlkEoMK0U89/EvlZG4FPKFKiz2aMrOBAQGRY1w12ydeTtSiAXm56KPlBlxoVcCAei4APHRwsAfBdAB9Ww+azCSUtDj2zC6AsDW6WqwTv4B2ctt8B9CFFNoAxuwWWN/JK7BoWmKlEcaG3ym5yKdi95yg3z7+HMf1igQeY4obQIyAUaWBr+n55lHZd72r5vMJS+nXAhrj649uv46IqIpY1PZxTP4csBowhMiZC1w+9xsrGaSDhoAOx/ePru16fG3OwRSiMIDwaxUkXUdD4W9j3MevzEEhGxMXVs+07FzzZc6qXUCmUf5OMr4EbHG6PoLZ0gMGi/wmo7Y9IMocH7XcNAUOO7Lh7gTWGGXwVOKjMj0ozh5zevsBk5IBLtvMbp5ul9U8DBSt5rX0c4HfHH3ZtV2RThLc7cdC+NDx/vmhUoPi0/3gqyrF0MMlZr8x8Ydgs+lw+MD9lzJRFGIhWZbz79gMnF6bZkhT+LwSwMx5UyJbM5ulzYXZLV4QQ0wsYsa7qecY3HWu9h1kPNSEmeBdZ4yVm39ZvBcu9h7aYRcdBpr+88gVK9CShijF7+JbZL/bXRSbUfOI+bs/J2yQsye4d8QSYhQY2jJu+yesXRyZ4s3HIfkaU0qFnMq+rO/hc2reBuDYsAOvoBUO+zXoZSIQ/MOj2zeq2FWSzEwLDBxPiJwHuKstFJqddJ2ufADY37VjzIR60lRs7ZRTlxtNsFAF0nZPIFDo4/Jz5wkVDshvO+gj6itD+b8xRqZ4fC++2HyAUj5/5o9AbDXgJlxBV1rXx+PeEGOodL3oHSAYFplxLqz1hN07f/7WwQsRxhBoo6FB+H5bQZADmnEIioHTSK6WQ/n1x1LhQx2u4qZVieFkMMSGNRwjn2s+EGuZTjmqUH8t1kTkKHt3lZAi5PzCh1amdbWbDrAfKK17KtkZHDgDES33CWiLCw6wyQCxMJ2VrINC3HLD4jMtQvsFJBvMn+iE8SOaHKhV6WWPuKH1+nzfQsy8jgSn8KRm9OVsPgAb8EKdu27bWH09Nn2Ih4GFZWzpGFRyuL/djvQexVRhOcnrliTfRjYHvkIed8+Y1PBpEshKYb5yFbm4YStVrEKVJB1VaSrHHodxkG9RaSQLaU1DaJPBoCI2vSQDknTtB951+xBNZuWFzthoGb9ZYk0yjV7vd7aMeji2RXB56jyqSSU7nd9KbnG2n++Lx30PdJ3R1AfRIN5sqYwl5Lg6aa+AzBK7H0zhrt4e0MN+K14TspakBsKS1/G6i3Gu6q8AlSWapbL54FVENywOwWpRvHT7CfJIuo29a9GGk7r2dwiMlfQgI6rAKCN1OcyxsmrkJ/LxDayuGmmcSXvETS5ENWxBHJA7xPFViz8NlbSjMcFpx0CEA0v/nrThn79M8uyLPBOlJTl+mM7aDq04iIm4XIDRCuu6nXoSBWFjU90yRf1KloUkOFXesb/n+v2HdU133UGuPb871GNRlBQ//L+CwcwzA2rponfIF5tVIiOXj3gUCNeodn6/g9T6h6JSr0cPhqgmK5iZ/7rZt1baEcpt/+t8aj85wDkYR8uD6XfwqxXIxH9FD2uZb0wwSqL13BNGhWnFm7hL3ptHA6bHcv4PU4+nCSDl3XGrY8bB0w262lxIzDnc9E8G2cK0LOz+7Fs+yerbUE1kf1x3xRfyQLQnm6BEIlkE+ZxapLxkFj5P6lbXjTUyEOE+WfSgGABFVXP6SQhujZbBqTspIR74v2G99gYHnhl1AorxOwF0E8VkJh7a0XPk1Els0JoBi3LIreAXWCrPPsr2eLKIhppJgAzjYnOAB6t1tfToI0O9ZV8epmYlptVZNgmw71Wy2eduOt0v4Ow6SfzlhNm4UwI5Dk5z4/cWIldlTs1TFSj+JGBOyAF+2ClwxDHzaRFjqBqCfdZC05Yx+16sXZkE8nR2V6LH54qWUXntTWwSss1m1bjhO9iO4jQGNG+is7JahBr6JtR5NNPWaPiMwE0eydFhJEe08OeVNK/lPaGqqVsmxYnuJquhYiZ8vPxjJxGvo3vodMdFfihlLpUHVu9BSdF6XMlXEe2Cz5wyb9vbWuMLrR2CYtYMbMLa92/Bz40PZtEN9ktyWXpPu4uQvr9WN6iDan4gODqOUGPiYxqLfPyOzDZzyJUdBzb2iuzsaPeiG9pJb3mCy18ntI8WONc9ebHYEhlMrmoCqw5QqZFm1iGnr9xHbTWKP1sL1O3flM9G6n7OPx7yAWWoVniqtNbD3RGf7BvRADQUA0GR6rMoYt6ogTZdboGMmPhVg4+/tXNZFD4mQ6YOUNVoJQJ6DMkRozpUTFDb+j+W+C+nOxaL4pcAPZJa41UYDAVZS2LHEbc2v91qeJMXunEOtIfrMd92qIq68mfs5bOlaeAWp3Vju8WIkfCPZXYAqNsvN0+RLTYAs3pC2/sb7jraPIidOqbApAMNnPTx3ljejzXpni0ifWZkgaPwYp20SZSZPDGYjbDwrGug0nwy1ulohpFosqY0xR8yjwB8WVh0JTp+5qDAs4a4GfCPVXUBO4opbUKxjW2nsyJ0qIc1L0ipBKjc93AwgSQgk2u2SaqjDdEWWuFG3RuSMVIZU9MA/rtO9b/JCyoWY3gt6l/rnxUxYD0erYjb3s9BdDPA6aXPzwYhvRkVtiaMSP9yXsyF1jrs4PRbhDX+VfdSxai2oFmYyhJ3GG0Of/NGfzZmYhIuAHMziJorvkr639IJdP4wbxIGCjuC4zzyG85dX2wY4eUXJLPprrGhEk4P+nRXnIVHuifgMZKxrKp1BPi9BLjIcQU9y4rnORSqmqioWJgrZpLo56pFQ8b9xSjYkOb4iM7fMsrB4j73KSZQ66nRlL74Y5bWrG1zfPSvHnfS/XoBFBtbAJsJl8NwdOT7KAKVQdTqjzSsn0Ubx9DGigjp9s39UMf7UzAV9uNNHu9z7hz6nbQeIhjcT4nhZTqOpQwdRwNvPZsLQB/jkbpnqzCMCUMLTlmM7FWWb8GVeVblOWp9sov6BZeZ3HuBf7veX3c6+nbe2r8hbD7PrPG4KVysqesQ7DVcUqo0MVNs1mnGen/XOdTEulQ8anr1yxGuxXJ2CLwM5KBMXFKajLoQUauQgltFRK+MtcbWp/2uHOd9bNbD5cOl1qNT5M+krwtweulHQBUnxuzS9+JFCYIAhJCAVqdL79iV25jgCFmCKYAC+dJ15JB3bCkyX2VtDWF2SH89HIP2kM3uwYetyK0xcJKtU4YStGiNXSprbMmPIm/zGrUne0I7thHmd9+RkfeCBqzD/+PD6bBmV08R8MEX38jRJSqHgsOZg+8Jc0ZSnkEoGEeB971jqj/G9kz9HHbTfV3iBoXjC9Zf3mTIuzAjrwrQzQ6TvARqZ9vwq65kakay5tg0HBsbTsOxxFztiEAFf3nFxT53/3bpfm6vosf6rIR7q0eEL6Ai5fMRv+5DBl5PPkBCFu4cJR4EZ7KpCfM1hroiwr2QLCwT3d1e55y7MRUyi3SarIIWDmz10w6QAgSHlmIHPR1JRmW5mFE99vTfTTfInCznWuCNbR9fePXlpHFej/D8m+PNkRS1hgHyGIWCxlfoAlRJ6U6AU4bNdqPoN0vs000+sYdDO6Jbg7BzumOuJZKbNljmoHLiAcnFosawmhJUQ/KH/h3tBqc2X0v0vpgK7bAfh3XP3Hx4USFu5wSxMRTBUdSLF0Xfq9z9hqE6vAgO4JngSWhqpaHy2sHjRR1YGSpN2kKDpJI7mymgA6shNeY9yoDnd3yTRIsqVWlDAcQHGcKFoTpF2MiYFG4TwQo6x8YTXzxyoMjCcGBv6U/cY/8gnRrg/F3wmufx6DOd+K0FSGq3RGAOqa+8GedFv9ncsXWPez3XVFR5sQmuWHMRl1kpItQGNVqtOOv7mTLTTvwjzwIKnXz3d6jv7EzdlxGGFfo+s1ilQtklyXVlOVKleU0E/S0dlmZy5aj4AMQAu4adpLigSxItA8KS54FwkNbhm9AzAVdBLHowEDkxSDIlPOPT8Wd+Cutl6olXA0sK5xYuRH2e9tLb1bzzjXireNUcrrd9TeVcmLDz+Y/RLLWQF0n5V+Mau4fuz9BIEubYAf0MyYysUyhBbzHkT+HVCYKLJORI1l/oN3BEMFmYsAG/ZrzaPYPo+ZDBZbI7ltDzt/4DHvFxNRu+92qfSi78Jr/GaQB1reOvxpkXeU1T3DdMoW8hg288hdP6fOpluAdoFkq5CkG67x8Z2J/R0b1+xDiq0/lZlsIW47vV+fsDiGdDssFjKQ3qpGCUuH6sP5FPhX5KAJ1lsB15RvDnWbaalMoWykQNsi2dXB/5LJw9651SYuF2o/Amb/UvewkYwTZcZAqmEA/Sk0tFspQEN8iJfMVp8dgxrkFS2ux0PVtvzrAo8xGD6KcCEJsKrlFV7h21JpAZeAp25BqybbgbTQ57SmfXOZqVHpPzDfoTa8Gw1bGWe2AXGf/8LcOoFO481lCNwNQWNHRqeNakwdwijNInevoumVhD90bYkhFNI6LaPlpLAY5AUMeUVEn++3hUJmsDi+Uhe8ixzoGp13+sg0i6ZtUv0UOAy525fWsSUSx7wiummfPJ0QqBsRxB66gGU6lORJNqh9pjPIJe6+fKyoPAGOj6oolZKF/2lqHQgckksn+RwdN31QW7JwCR4pKqbBGJOt8fmCDW8yYWdLXkUAFh3zNnCWeoNUFSl2n6WVMlS8Y1mDjTf+9vPRMHuM+/vThhqhPDOXLzgf1Rj17koV8rg0hSoNFWp68yESxCyMo76ECDbFxluSuizF2YYW4NKRPHn+6Ix4IQRw5f8hWE6LNhFQwANU/xCpoQSXybxGYeTpY6voJK6UlMtFX68CWR5ktWMsnafo/+lxybdaX43Hd3r2Dz1ap6v4XKE58qEWR4/4RFPVh5K41NY1w5WeJtJ/x5kenDhuMnorZlV04otJw0K/JjBsFyxaAk6scqJ0/f5gPBOIk1H+E4FOMwaOjT3UWNRIM/IPER3V04/bvppriBFPbI4s5ccr3ampiKk6bUCM7z4UuzPz/VGdQHhUQHUp3micFpZw/C5mRYXfvty9qBAYC0U7vDbJE6NyxcEpxFuCnouwXdZdRbgmdyqy+Kd1LB0OAnrYbwsO/vmfRmb/PoHD/G60Tuv2Wep1VYIFtaHLReEUUW+0d/cT0NgfmKnTKU3xCqCBMdJm0rdols4+2puiqkO/5hCtabzTDSp85jOXMmR6A1Skzsmv4zxx1ITfjf+n1PoLZu2mvL0zw9WZTrgDsDsgCCW+TOENIuuC2WDDl2c0hDbBmZzPijTizd3gDtdH3zccntUQbYlcOAJjR34lEZ8aT1RbMsrhyblMxW/hyRueeKs92uTXMFKr9yoD0gtLzCB7fWwWYdWgH7AsB/0nzlaDE1aqd+595s8jbyxaieFFuAzDiYl1vVDFaDZWUMengLZpEL6gjaophZM6ASfIoC0t5YfGmqpwu3PlZ5TZzBOqdV/LtQMswoqtZ2RwW5URmNCoqKZRBL/FdpA+odyoKKufZRn7I7MtPo5QsN0NkT8ZNlMn5I3ywUm0+NkyQ/Nyc8wgV7h9AhNH+XdIyS0oDFyfPGC5RCbJOhEHeExr8kEAg0ALbqwQOOYUSO4n7ZFGQSJxfFpO7N5s0bhwQsYd/TFjeoc8ULVdHGj1pTxx9Wg5z1cFP00VuI+pHu4HGaISrpPBV8Xiarxgh9exRHfA1b8yeM2f1riUvmPEO1pn7gkmbweXXSy15c8nOXbvcgMhmFtC6q7I4V71KfW9VFRuQsDJHRSee9Ra84YcBTHxYvmbtRWisne8Tv3Ktnl35iZjo2QJvnnsc7F45zXEh4uUeIBkzarNaZB5saIo61yQSSLSKcipSNi/ryShDhiK0qyB8AXCm57yainx2T4rvQSwpsZ3p/E9Q6Ma1r3b8G8QxkE9nFEnfVaKeYMx4NrXR4OzlcCT4JgTsYeX/i9V42OzfToL9Sf23zewi+WNg3C8fCN7Q1i8tqR1fs4C9CQU/Yu2KoRjfmP45jY4S9obmsCbjB+1T8imYduUemOXbTTJ8ML/eGoxx/pwM8sCzmW1WE+vZfGbLRDpcwvwG0WN85Dm97ejZiKxA7/F3DM/S6wMmE5THXoVA+Uuj/QZorQsFy8h9qItnJiFmrJCYJ12IIPS3Xo7426YoUhrCT8HlGtsywSFMtPlXmGRUZuiYI4yBOfIIP8ssYcqcmw2MSRjPohxhXQLoLonW8ECL5zA574x9koQ2qWdLmTvwV8NRlOcj3fiN0LAFvuVQNWuc/40oVOalGilLSADSQgWB1BhC9/2Q4+qhjeP5uNAsLVHZPwlOxcNASsgfNkbLoBj3g7Rdku6tVl3E2/fJiz2dNTiYqO+lQTI6o/S0soMZu+rNzAzpHks0xihE5Q7egSyZCm+CBCF9e11iB4RdcsekA8fqmkBZTjp3/Y1WAbZsDWNXBHdC8mxW3/O+u7CHhxm2aRgVupu0w712cnrCYLESS/rEjQZNYubLgHabOa9gIjd7dnRx4uEDpEeN8SZm9i6lOBbwaGcP0kkyptKvROZCWo+pmxStEFcrvin6ZwwlGM2ATVqZC1ZIjsZCjM/Zt08GUia7cZ6sSFlfy+ZKZV9Iv3Y8XIhhe/YKtTXggXFPdLNrZNMEI10Qgw3VwIEgA5N+O/nvfGOF47q9App+uvpV2JL8SrTTnPfkOrMpZ1Chj9mjo6S3Gf+yTO/ND9rlWeTi58LIkRx0kDiPSXLoS/AqnBJluGQZGFPXSYwcz6RlnpfAV6NIiVO7ZZKbvFSboHYcTwFhYZHVmbzvcb9OROr8u8gekBwl6tQAHWFNxOIzu8d4di8miV3EXtCykczX7IDYdV/tc03TD6BpSn9+926hMOCXO7L0f4YrRsQOgrVH2fpyTzz3OZ2fDc5p4b1d23K9qUZbNBUKvHtwEk9Jq7uFOi7gQjUP+tBCKGn2/Vq1jGTfTacvfqxzhD7Ub4eMGPEfRkrSqPzZ0nOJJI64bL7ytHPh4gztXXoZCNn6ArvjUkRTCPqyZSGLb3K6Jeych9UTDYMKn+jgLEL1D+nWBgy0z9q2GRcaZ6gF4ecwDGnXqyK+qhCG5xDUM2CLaReY5DZHKwflRGtzEVgLZQdppFilooP48fKswNv3uXlEhepI7U056crh4DamdK1dypRECUcfPE2O/0n+EW+aErWyS1teFfWpWPiZMCB9E3DwIwkIk8gtlH4HC/YLg2ZkhlGuf9IUDDw8qA5V5dl5ywM29eKbbRDjRr2ODP9Ib5jH78N0ChGmD427jKwqNb6d2ukeQxkqy9iNxJy4BvZyXCCSoSNJPfLHkgBK8DJeEGgG6OiQWMp63wNAi7ODYFMlZNPE9CHIReopSkv9zgCyB/ol3sNaYWJXvV9G76rd84iAK9oYPPopWK+zaRwq4H4RVvAUjVWEpM8s1eype52dOxawqzbrbn+i49oRmM8M78uZLfop/Qrajguap5JahKzcBNuBfNObLpQjLCZjGSjoe4RrJ8ZCujOUP0LyQVSUP1YtFEEJ3FVjs0BZZDSA0TSbX7LHQFJzParJfzgtzXMUKdwvUS5Z63WU8QEWuCYAkjXD+jbKPfFW/ae6in+l4rHBdivW8L29y99kuplyRjGtmuwXE/JrtdPOvTIDrCnsORXOX43xVCWEaKJTCVlkYeIQBa4B+axQoeSLjK+vG8rT+GGz5xdhfUQ35QkfFkb1bXmvcD7PaGK0sN3NMi1fWDa2reCYmYOdEk4JhMWb/KBZi8x+ZZV7aUAVAAfHzVyNEj/3XFxnems0pdjN/V+l2Y3IAmQ8Io66tXVSjqzZaENvDGqhwHisqF6og9KbiHnOx06yeD9NefZxOWi3lBwLpAxKWq5yB7mXnUVKCBFReAf9TNunVVNTZ6MKnmRsnWQX68D/wjxZ6eTTCkB0JtsuxHOH1ufcJsQnOQKXuPCZ/r+l01cF5WLAUsL9qCIXZnT6hnn8iFQyInRZzZzWtsJnFQSDQxGrC70bR0tPM4Yh2HEJV18ZqFt8HUJn51VaccUmk+rnEIZTWo0ElQ0yUdNj6EEJrtArp6qvuXKZYAI7IY4Hho3zwUvvMj8DrNDQ3XB7EldT0UKaeEvwe17W4iKJNMlFVo7xshEKyCJ3Y9s2nmXdHjb94iQyMybzbiEzpn9sopML0N1wh7r0Fxc2wX9GxR31YKsI6fn1nSkFxWO5bclFyCqK66+jJDh4R23ouobsGw1iy4m63udJ1MTso/lHeGOBy/UN21jR8qo4hOWRShG2fouO+5EUrXnJK4yCYxYxiP0FPOtOPATfK4Kn+cWiSZj9u4vpnNs4FneRusguj0gHiZIzrbtzNLkMZPY+Z/g6Gan5drmaGf3NAR3yUNlJk3o8YtiM+mUjc5VUQaPv268BvxQsjxcKFlgyxna3rekIykUUkC4aoDPVJcVsPFAiOhN3u3P6yKG7BGv+oYkzCMkljd6QIgFUCi6auVkRiuW9v0L5jh+R93fJXV3B0OyeRdgRuvDAstGLo7hk9Qu9FOW5wVtq5dq75Q/rsMi1sxFr1ng8W8lGkoQ6dBeIMBb5dnejFfwzMc7wLDWNdwaX4KLoM8URn1Uqt7wEnHEMYheyieQf/VXhSY0VbIztovtuiH4+lGpLFzXE4nzZkQiZvAoDXBiDGe1JhpUTyg/ogeGGZbljOgAzQll1lzDzmrksHpKN2haqRBPEadJKk8H/9c0eprfyuI1H0Vl37cOauvNji9HzKmyF03BCwWhBbWEzAPhpWaDmYycHXvpqhY5eykNgmFvLWWB+S4RmXQliL0zOGwA+NbmHjfRs73ItgNf9AbZP6zM9u0EqvvDtEbzCbC/qvtXzPYe4gBHT7vG9FZ1mNIenZrK7X/NCFVTHjoDLjz+ri2n8kOj89ewVnGnZR4BmwRVlQ0NtWO7NaDt5AV2D0yQ8lnErhk6ubojJ+B89yxi/QQnSyj18Yq0CgGQX7z3z+5Azksml694GI+z+FZdIrXqPy80CLoTpwAU6SaGhmrp++VSE07EgRXlquwBqQhTP0U/d6tivqKgv6/yWCDnwVeQRtvrgRO3ggu2l3E5GOIHE2RgMCTqYv3jYLdMyHoY0brPtmVP2kerOFezsgVm164xku+VSbqNFBdp7BDy1SXeOvKjW4Ol43suOCdaAXWkNGh1AOD6eVBa9R84OYaf9DOcA6XiAUETEpe5BI3RCHrLqAKI+34Nk4IeUI3U5oTDnFLhJ/oekEHXOsudmSvw8bLe43OlvEqR4lMcOHFIgsw3f42O4AJvi5Eecl7uzLcwAJKLJDhGWRV4zakQy8Xor3eseiXrZXgNs0KxVvNygPBu0dy7wmGY/iCiZOv29/FLjjN6zEgoEamj5Ls8BARFVrnzbylUA7nd0d0CQfAnBtLL3HH3eodQ+z0XEr2gqBXZT5gZ07Bh/EfqxXSlkkBnWhSY8jL9qsAavOavSskoXSlMScnAfkYiTfIxXeLM2pkQxsm5nQPKFyVzTqCFH8spk0Niy6xcWOmwUhSUYOT4dLjEgY4+IDPd1A0bsCTacTIZXFT0r3vBUrCHNiUCpic+kNFgkbBQvCel4+HVsKuEUTsKysesAMDiC2Yviu93kMatOZQbPFlQxWZyHMPhE7FOFU0/jG245hQrNT/Q6yVnYRNPaWLJ0wAgY/FSBYZFj0LvQhn+MTcQXdXi40mFTrl2Q2gcsGGaFOnGi77Bq8ug3Tvtng5dY9kJb14HtmYogxl0BzlJCJw9WpTEGBuRnd/se69JA6B2B+7O31xy8JB1u+/j66VdS9Jej9HUo5x7xF0jHirCSSZ/0JjXUjneHs5Nm5JTTVzTkNtc5CpDH4SMNimnH35+m4EMjz/tHaTKEd8mxcHStHY6VeVM7JVSJiOh+bouAb3ZBcz5+x3UBjrGTiiXprEcsdiTBae71aW9SNrrYJagrw2sC0uu/4TcksC7hN8tkXw6OTi9foIJVLm5iQL0Bk3EBwmXhjq3b7IRNSUGCK6PIyKk227515/iBAwq1zWoeeqGSVvvVQCuanqiAded0uEPCUKRFey7oq8PYhABd7duEULHOBUg4H7kjWVfLvSsvsTfh \ No newline at end of file diff --git a/.github/api-redesign-patch/part02_00 b/.github/api-redesign-patch/part02_00 new file mode 100644 index 00000000..9bb91a68 --- /dev/null +++ b/.github/api-redesign-patch/part02_00 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part02_01 b/.github/api-redesign-patch/part02_01 new file mode 100644 index 00000000..93158933 --- /dev/null +++ b/.github/api-redesign-patch/part02_01 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part02_02 b/.github/api-redesign-patch/part02_02 new file mode 100644 index 00000000..f3180b4c --- /dev/null +++ b/.github/api-redesign-patch/part02_02 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part02_03 b/.github/api-redesign-patch/part02_03 new file mode 100644 index 00000000..0eb7b276 --- /dev/null +++ b/.github/api-redesign-patch/part02_03 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part03 b/.github/api-redesign-patch/part03 new file mode 100644 index 00000000..8d4400dc --- /dev/null +++ b/.github/api-redesign-patch/part03 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/workflows/apply-api-redesign-review.yml b/.github/workflows/apply-api-redesign-review.yml new file mode 100644 index 00000000..97c69541 --- /dev/null +++ b/.github/workflows/apply-api-redesign-review.yml @@ -0,0 +1,36 @@ +name: Apply API Redesign Review + +on: + push: + branches: + - api-redesign + +permissions: + contents: write + +jobs: + apply: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: api-redesign + fetch-depth: 0 + persist-credentials: true + + - name: Apply reviewed patch + shell: bash + run: | + cat .github/api-redesign-patch/part* | base64 --decode > /tmp/api-redesign.patch.xz + echo "864e85462a0e8a0e7f85e9aaee12dca8997b56e1bfb16d32d40c6c16c9ac049c /tmp/api-redesign.patch.xz" | sha256sum --check - + xz -dc /tmp/api-redesign.patch.xz > /tmp/api-redesign.patch + git apply --index --whitespace=error-all /tmp/api-redesign.patch + rm -rf .github/api-redesign-patch + rm -f .github/workflows/apply-api-redesign-review.yml + git add -A + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -m "refactor: complete API redesign review" + git push origin HEAD:api-redesign From bc242798bd57e6bd3b27c8fe6c8e676a533cfc38 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 08:44:32 +0000 Subject: [PATCH 005/110] refactor: complete API redesign review --- .github/api-redesign-patch/part00_00 | 1 - .github/api-redesign-patch/part00_01 | 1 - .github/api-redesign-patch/part00_02 | 1 - .github/api-redesign-patch/part00_03 | 1 - .github/api-redesign-patch/part01 | 1 - .github/api-redesign-patch/part02_00 | 1 - .github/api-redesign-patch/part02_01 | 1 - .github/api-redesign-patch/part02_02 | 1 - .github/api-redesign-patch/part02_03 | 1 - .github/api-redesign-patch/part03 | 1 - .../workflows/api-redesign-source-export.yml | 26 -- .../workflows/apply-api-redesign-review.yml | 36 -- .github/workflows/ci-tests-and-coverage.yml | 19 - docs/ARCHITECTURE.md | 2 +- docs/features/messager.md | 4 +- docs/features/playerinfo.md | 2 +- docs/features/playerlist.md | 2 +- .../capability/moderation/SanctionFilter.java | 7 + .../capability/moderation/SanctionsApi.java | 9 +- .../ProxyFeaturesAcceptanceConsumer.java | 113 +++++- .../run-platform-acceptance.sh | 3 +- .../proxyfeatures/ProxyFeatures.java | 12 +- .../features/FeatureContext.java | 76 ++-- .../features/FeatureFactory.java | 32 -- .../features/VelocityBaseFeature.java | 39 +- .../features/announcer/Announcer.java | 5 +- .../announcer/internal/AnnouncerHandler.java | 6 +- .../internal/AnnouncerSettingsService.java | 2 +- .../features/announcer/meta/Meta.java | 23 -- .../features/antibot/AntiBot.java | 5 +- .../features/antibot/meta/Meta.java | 16 - .../features/antivpn/AntiVPN.java | 5 +- .../features/antivpn/meta/Meta.java | 24 -- .../features/broadcast/Broadcast.java | 5 +- .../command/BroadcastProxyCommand.java | 4 +- .../features/broadcast/meta/Meta.java | 17 - .../features/capacity/Capacity.java | 5 +- .../internal/AdmissionCapability.java | 1 - .../CapacityIntegrationSynchronizer.java | 7 +- .../capacity/internal/CapacityService.java | 8 +- .../capacity/listener/CapacityListener.java | 12 +- .../features/capacity/meta/Meta.java | 15 - .../features/clientinfo/ClientInfo.java | 5 +- .../ClientInfoPersistenceService.java | 2 +- .../internal/ClientInfoSettingsService.java | 2 +- .../features/clientinfo/meta/Meta.java | 24 -- .../features/commandhider/CommandHider.java | 5 +- .../features/commandhider/meta/Meta.java | 18 - .../features/commandlogger/CommandLogger.java | 5 +- .../features/commandlogger/meta/Meta.java | 24 -- .../service/CommandLogService.java | 2 +- .../features/commandrelay/CommandRelay.java | 5 +- .../internal/EventBusHandler.java | 8 +- .../features/commandrelay/meta/Meta.java | 25 -- .../connectioninfo/ConnectionInfo.java | 5 +- .../features/connectioninfo/meta/Meta.java | 18 - .../features/friends/Friends.java | 5 +- .../friends/command/FriendCommand.java | 7 +- .../friends/entity/FriendsService.java | 4 +- .../listener/FriendActivityListener.java | 3 +- .../features/friends/meta/Meta.java | 28 -- .../proxyfeatures/features/hlink/HLink.java | 5 +- .../features/hlink/internal/HLinkHandler.java | 14 +- .../features/hlink/meta/Meta.java | 24 -- .../proxyfeatures/features/hub/Hub.java | 5 +- .../proxyfeatures/features/hub/meta/Meta.java | 16 - .../features/maintenance/Maintenance.java | 8 +- .../MaintenanceConnectionListener.java | 3 +- .../features/maintenance/meta/Meta.java | 16 - .../features/messager/Messenger.java | 5 +- .../entity/PlayerMessageSettingsEntity.java | 13 +- .../messager/internal/MessagingHandler.java | 9 +- .../internal/MessagingSettingsService.java | 12 +- .../internal/MessengerTargetVisibility.java | 5 +- .../features/messager/meta/Meta.java | 24 -- .../proxyfeatures/features/motd/Motd.java | 5 +- .../features/motd/internal/MotdHandler.java | 9 +- .../features/motd/meta/Meta.java | 17 - .../features/playercount/PlayerCount.java | 8 +- .../features/playercount/meta/Meta.java | 23 -- .../features/playerinfo/PlayerInfo.java | 5 +- .../playerinfo/command/PlayerInfoCommand.java | 2 +- .../features/playerinfo/meta/Meta.java | 24 -- .../playerinfo/service/PlayerInfoService.java | 8 +- .../playerlanguage/PlayerLanguage.java | 5 +- .../features/playerlanguage/meta/Meta.java | 28 -- .../service/LanguageService.java | 3 +- .../features/playerlist/PlayerList.java | 5 +- .../playerlist/command/GlobalListCommand.java | 3 +- .../playerlist/command/ListCommand.java | 3 +- .../internal/PlayerListHandler.java | 3 +- .../features/playerlist/meta/Meta.java | 24 -- .../features/proxyinfo/ProxyInfo.java | 5 +- .../features/proxyinfo/meta/Meta.java | 17 - .../proxyfeatures/features/queue/Queue.java | 8 +- .../features/queue/meta/Meta.java | 22 -- .../features/resourcepack/ResourcePack.java | 5 +- .../internal/ResourcePackHandler.java | 5 - .../features/resourcepack/meta/Meta.java | 17 - .../features/restart/Restart.java | 8 +- .../features/restart/meta/Meta.java | 15 - .../features/sanctions/Sanctions.java | 5 +- .../sanctions/command/KickCommand.java | 2 +- .../command/SanctionListCommand.java | 6 +- .../sanctions/command/UnbanCommand.java | 2 +- .../sanctions/command/UnbanIpCommand.java | 2 +- .../sanctions/command/UnmuteCommand.java | 2 +- .../sanctions/command/WarnCommand.java | 2 +- .../sanctions/listener/ConnectListener.java | 2 +- .../features/sanctions/meta/Meta.java | 24 -- .../sanctions/service/DiscordService.java | 10 +- .../service/SanctionSuggestionLookup.java | 2 +- .../service/SanctionsCapability.java | 5 +- .../sanctions/service/SanctionsService.java | 12 +- .../features/serverlinks/ServerLinks.java | 5 +- .../features/serverlinks/meta/Meta.java | 17 - .../features/slashserver/SlashServer.java | 5 +- .../features/slashserver/meta/Meta.java | 17 - .../features/staffchat/StaffChat.java | 5 +- .../staffchat/internal/ChatChannel.java | 2 +- .../staffchat/listener/ConnectListener.java | 2 +- .../features/staffchat/meta/Meta.java | 24 -- .../features/textcommands/TextCommands.java | 5 +- .../textcommands/command/TextCommand.java | 2 +- .../features/textcommands/meta/Meta.java | 17 - .../features/twofactor/TwoFactor.java | 5 +- .../features/twofactor/meta/Meta.java | 21 - .../persistence/OrmTwoFactorAccountStore.java | 4 +- .../proxyfeatures/features/vanish/Vanish.java | 5 +- .../features/vanish/meta/Meta.java | 24 -- .../features/versioncheck/VersionCheck.java | 5 +- .../features/versioncheck/meta/Meta.java | 17 - .../features/votifier/Votifier.java | 5 +- .../votifier/internal/VoteStatsService.java | 2 +- .../features/votifier/meta/Meta.java | 24 -- .../command/ProxyFeaturesCommand.java | 10 +- .../feature/FeatureScopeFactory.java | 18 +- .../framework/feature/meta/BaseMeta.java | 20 - .../framework/loader/BuiltInFeatures.java | 363 +++++++++++++++--- .../framework/loader/FeatureDescriptor.java | 179 +++------ .../framework/loader/FeatureLoadManager.java | 330 ++++++---------- .../framework/loader/FeatureRegistry.java | 8 +- .../localization/LocalizationHandler.java | 10 +- .../framework/log/ConnectionLogHelper.java | 2 +- .../persistence/DataRegistryIdentityGate.java | 2 +- .../persistence/PlayerReferenceResolver.java | 24 +- .../audit/AbstractPlayerAuditLogService.java | 2 +- .../service/DefaultProxyFeaturesApi.java | 40 -- .../framework/service/FeatureServices.java | 57 --- .../framework/service/InternalServices.java | 24 -- .../V2__proxyfeatures_scalar_player_ids.sql | 45 --- .../migration/V3__messenger_message_mode.sql | 5 - .../ArchitectureBoundaryTest.java | 33 +- .../features/FeatureFactoryTest.java | 153 -------- .../features/VelocityBaseFeatureTest.java | 41 +- .../AnnouncerSettingsServiceTest.java | 2 +- .../ClientInfoSettingsServiceTest.java | 2 +- .../features/friends/FriendsDefaultsTest.java | 8 +- .../PlayerMessageSettingsEntityTest.java | 13 +- .../MessagingSettingsServiceTest.java | 47 +-- .../motd/internal/MotdHandlerTest.java | 7 +- .../service/PlayerInfoServiceTest.java | 3 +- .../listener/ConnectListenerTest.java | 2 +- .../sanctions/service/DiscordServiceTest.java | 3 +- .../features/twofactor/TwoFactorTest.java | 50 ++- .../OrmTwoFactorAccountStoreTest.java | 2 +- .../command/ProxyFeaturesCommandTest.java | 16 +- .../feature/FeatureScopeFactoryTest.java | 36 +- .../framework/loader/BuiltInFeaturesTest.java | 96 +++-- .../loader/FeatureDescriptorTest.java | 91 ++--- .../loader/FeatureLoadManagerTest.java | 160 ++++---- .../framework/loader/FeatureRegistryTest.java | 6 +- .../FeatureDependencyManagerTest.java | 8 +- .../localization/LocalizationHandlerTest.java | 1 - .../PlayerReferenceResolverTest.java | 20 +- .../service/DefaultFeatureCatalogTest.java | 19 - proxyfeatures-toolkit/pom.xml | 5 + .../HttpTransport.java} | 6 +- .../toolkit/io/config/ConfigView.java | 4 +- .../JsonUtils.java => json/JsonStrings.java} | 4 +- .../{util/tools => pagination}/Paginator.java | 2 +- .../toolkit/{util => }/text/TextPatterns.java | 2 +- .../text/format/ComponentFormatter.java | 6 +- .../{util => }/text/format/TextFormatter.java | 8 +- .../text/format/color/LegacyColorUtils.java | 2 +- .../format/constants/FormatConstants.java | 2 +- .../text/format/inspect/FormatInspector.java | 6 +- .../text/placeholder/MessagePlaceholders.java | 2 +- .../toolkit/util/type/CastUtils.java | 29 -- .../HttpTransportTest.java} | 33 +- .../JsonStringsTest.java} | 6 +- .../tools => pagination}/PaginatorTest.java | 2 +- .../{util => }/text/TextPatternsTest.java | 2 +- .../text/format/ComponentFormatterTest.java | 4 +- .../text/format/TextFormatterTest.java | 2 +- .../format/color/LegacyColorUtilsTest.java | 2 +- .../format/constants/FormatConstantsTest.java | 2 +- .../format/inspect/FormatInspectorTest.java | 4 +- .../placeholder/MessagePlaceholdersTest.java | 2 +- .../toolkit/util/type/CastUtilsTest.java | 33 -- 200 files changed, 1259 insertions(+), 2341 deletions(-) delete mode 100644 .github/api-redesign-patch/part00_00 delete mode 100644 .github/api-redesign-patch/part00_01 delete mode 100644 .github/api-redesign-patch/part00_02 delete mode 100644 .github/api-redesign-patch/part00_03 delete mode 100644 .github/api-redesign-patch/part01 delete mode 100644 .github/api-redesign-patch/part02_00 delete mode 100644 .github/api-redesign-patch/part02_01 delete mode 100644 .github/api-redesign-patch/part02_02 delete mode 100644 .github/api-redesign-patch/part02_03 delete mode 100644 .github/api-redesign-patch/part03 delete mode 100644 .github/workflows/api-redesign-source-export.yml delete mode 100644 .github/workflows/apply-api-redesign-review.yml create mode 100644 proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionFilter.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureFactory.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/meta/Meta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/meta/BaseMeta.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultProxyFeaturesApi.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/FeatureServices.java delete mode 100644 proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServices.java delete mode 100644 proxyfeatures-platform-velocity/src/main/resources/db/migration/V2__proxyfeatures_scalar_player_ids.sql delete mode 100644 proxyfeatures-platform-velocity/src/main/resources/db/migration/V3__messenger_message_mode.sql delete mode 100644 proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/FeatureFactoryTest.java rename proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/{util/http/SimpleHttpClient.java => http/HttpTransport.java} (97%) rename proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/{util/parse/JsonUtils.java => json/JsonStrings.java} (80%) rename proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/{util/tools => pagination}/Paginator.java (93%) rename proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/TextPatterns.java (97%) rename proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/format/ComponentFormatter.java (99%) rename proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/format/TextFormatter.java (98%) rename proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/format/color/LegacyColorUtils.java (98%) rename proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/format/constants/FormatConstants.java (82%) rename proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/format/inspect/FormatInspector.java (96%) rename proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/placeholder/MessagePlaceholders.java (98%) delete mode 100644 proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtils.java rename proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/{util/http/SimpleHttpClientTest.java => http/HttpTransportTest.java} (77%) rename proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/{util/parse/JsonUtilsTest.java => json/JsonStringsTest.java} (59%) rename proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/{util/tools => pagination}/PaginatorTest.java (95%) rename proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/TextPatternsTest.java (96%) rename proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/format/ComponentFormatterTest.java (98%) rename proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/format/TextFormatterTest.java (98%) rename proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/format/color/LegacyColorUtilsTest.java (93%) rename proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/format/constants/FormatConstantsTest.java (88%) rename proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/format/inspect/FormatInspectorTest.java (96%) rename proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/{util => }/text/placeholder/MessagePlaceholdersTest.java (97%) delete mode 100644 proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtilsTest.java diff --git a/.github/api-redesign-patch/part00_00 b/.github/api-redesign-patch/part00_00 deleted file mode 100644 index 86e68a66..00000000 --- a/.github/api-redesign-patch/part00_00 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part00_01 b/.github/api-redesign-patch/part00_01 deleted file mode 100644 index 699365c7..00000000 --- a/.github/api-redesign-patch/part00_01 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part00_02 b/.github/api-redesign-patch/part00_02 deleted file mode 100644 index 993a798d..00000000 --- a/.github/api-redesign-patch/part00_02 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part00_03 b/.github/api-redesign-patch/part00_03 deleted file mode 100644 index 07e322bd..00000000 --- a/.github/api-redesign-patch/part00_03 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part01 b/.github/api-redesign-patch/part01 deleted file mode 100644 index cdab8624..00000000 --- a/.github/api-redesign-patch/part01 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part02_00 b/.github/api-redesign-patch/part02_00 deleted file mode 100644 index 9bb91a68..00000000 --- a/.github/api-redesign-patch/part02_00 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part02_01 b/.github/api-redesign-patch/part02_01 deleted file mode 100644 index 93158933..00000000 --- a/.github/api-redesign-patch/part02_01 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part02_02 b/.github/api-redesign-patch/part02_02 deleted file mode 100644 index f3180b4c..00000000 --- a/.github/api-redesign-patch/part02_02 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part02_03 b/.github/api-redesign-patch/part02_03 deleted file mode 100644 index 0eb7b276..00000000 --- a/.github/api-redesign-patch/part02_03 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/api-redesign-patch/part03 b/.github/api-redesign-patch/part03 deleted file mode 100644 index 8d4400dc..00000000 --- a/.github/api-redesign-patch/part03 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/workflows/api-redesign-source-export.yml b/.github/workflows/api-redesign-source-export.yml deleted file mode 100644 index 8b7ef797..00000000 --- a/.github/workflows/api-redesign-source-export.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: API Redesign Source Export - -on: - push: - branches: - - api-redesign - -permissions: - contents: read - -jobs: - export: - runs-on: ubuntu-24.04 - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Upload source snapshot - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: api-redesign-source - include-hidden-files: true - path: | - . - !.git diff --git a/.github/workflows/apply-api-redesign-review.yml b/.github/workflows/apply-api-redesign-review.yml deleted file mode 100644 index 97c69541..00000000 --- a/.github/workflows/apply-api-redesign-review.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Apply API Redesign Review - -on: - push: - branches: - - api-redesign - -permissions: - contents: write - -jobs: - apply: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-24.04 - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: api-redesign - fetch-depth: 0 - persist-credentials: true - - - name: Apply reviewed patch - shell: bash - run: | - cat .github/api-redesign-patch/part* | base64 --decode > /tmp/api-redesign.patch.xz - echo "864e85462a0e8a0e7f85e9aaee12dca8997b56e1bfb16d32d40c6c16c9ac049c /tmp/api-redesign.patch.xz" | sha256sum --check - - xz -dc /tmp/api-redesign.patch.xz > /tmp/api-redesign.patch - git apply --index --whitespace=error-all /tmp/api-redesign.patch - rm -rf .github/api-redesign-patch - rm -f .github/workflows/apply-api-redesign-review.yml - git add -A - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git commit -m "refactor: complete API redesign review" - git push origin HEAD:api-redesign diff --git a/.github/workflows/ci-tests-and-coverage.yml b/.github/workflows/ci-tests-and-coverage.yml index 02a29c5a..7b71aadd 100644 --- a/.github/workflows/ci-tests-and-coverage.yml +++ b/.github/workflows/ci-tests-and-coverage.yml @@ -11,25 +11,6 @@ on: - main jobs: - source-export: - name: Source Export - runs-on: ubuntu-24.04 - permissions: - contents: read - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Upload source snapshot - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: api-redesign-source - include-hidden-files: true - path: | - . - !.git - tests: name: Tests and Coverage runs-on: ubuntu-24.04 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 5f9592db..0d34ebd2 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -113,7 +113,7 @@ explicit 33-feature manifest, and the absence of the removed discovery/service s Generic implementation code lives outside feature packages: - one cached `PlayerReferenceResolver` per plugin runtime serves persistence-aware features; -- `SimpleHttpClient` is the shared HTTP transport, including Discord webhook JSON posts; +- `HttpTransport` is the shared HTTP transport, including Discord webhook JSON posts; - `IpAddressUtil` is shared by AntiBot and AntiVPN; - `DefaultMotdExtensions` owns ordered, lifecycle-safe MOTD contributions; - toolkit config/cache writes use centralized path handling, validation, and atomic file replacement. diff --git a/docs/features/messager.md b/docs/features/messager.md index dcd32e0f..dbe722c7 100644 --- a/docs/features/messager.md +++ b/docs/features/messager.md @@ -9,7 +9,7 @@ Messenger provides online-only private messaging across the proxy. It persists p | Key | Default | Meaning | |---|---|---| | `enabled` | `false` | Loads ORM, handler, commands and listener. | -| `default_message_mode` | `FRIENDS` | Mode assigned to new rows and legacy null values. Supported: `ALL`, `FRIENDS`. | +| `default_message_mode` | `FRIENDS` | Mode assigned when new settings rows are created. Supported: `ALL`, `FRIENDS`. | Invalid default mode warns and falls back to `FRIENDS`. @@ -25,7 +25,7 @@ Primary key is canonical DataRegistry `player_id`. |---|---| | `msg_toggle` | `true` | | `msg_spy` | `false` | -| `message_mode` | nullable legacy field; initialized to configured default when loaded | +| `message_mode` | required privacy mode (`ALL` or `FRIENDS`) | Blocked targets are stored in element-collection table: diff --git a/docs/features/playerinfo.md b/docs/features/playerinfo.md index f7849dba..86f416cb 100644 --- a/docs/features/playerinfo.md +++ b/docs/features/playerinfo.md @@ -114,7 +114,7 @@ Because this is a second asynchronous phase, main profile output can appear firs ## Active sanctions -PlayerInfo resolves `SanctionsApi` for each request and calls `find(playerUuid, true)`. When Sanctions is disabled or +PlayerInfo resolves `SanctionsApi` for each request and calls `find(playerUuid, SanctionFilter.ACTIVE)`. When Sanctions is disabled or the capability lookup/query fails, the section degrades to an empty result without importing its entity or ORM code. This keeps persistence ownership inside Sanctions and makes feature reloads safe. diff --git a/docs/features/playerlist.md b/docs/features/playerlist.md index 697d60d2..c738f6f5 100644 --- a/docs/features/playerlist.md +++ b/docs/features/playerlist.md @@ -13,7 +13,7 @@ PlayerList provides `/list` and `/glist` for player-facing network population vi The blacklist is read once into each command object at initialization. Changes require feature reload. -Matching is case-sensitive and untrimmed after `CastUtils.safeCastToList`. Configure names exactly as Velocity registers them. +Entries are read as strings, matched case-sensitively, and are not trimmed. Configure server names exactly as Velocity registers them. The current server used by bare `/list` is **not** checked against the blacklist, so players on a blacklisted backend can still view that backend's local list. diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionFilter.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionFilter.java new file mode 100644 index 00000000..00b81def --- /dev/null +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionFilter.java @@ -0,0 +1,7 @@ +package nl.hauntedmc.proxyfeatures.api.capability.moderation; + +/** Selects which sanctions are included in a history query. */ +public enum SanctionFilter { + ACTIVE, + ALL +} diff --git a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionsApi.java b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionsApi.java index 835b8e36..3377750a 100644 --- a/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionsApi.java +++ b/proxyfeatures-api/src/main/java/nl/hauntedmc/proxyfeatures/api/capability/moderation/SanctionsApi.java @@ -6,5 +6,12 @@ /** Read-only moderation history capability. */ public interface SanctionsApi { - CompletionStage> find(UUID playerId, boolean activeOnly); + + /** + * Returns immutable sanction snapshots matching the requested filter. + * + *

The query may perform persistence I/O. Callers must not block a platform event thread while + * waiting for the returned stage.

+ */ + CompletionStage> find(UUID playerId, SanctionFilter filter); } diff --git a/proxyfeatures-platform-acceptance/consumer/src/main/java/nl/hauntedmc/proxyfeatures/acceptance/ProxyFeaturesAcceptanceConsumer.java b/proxyfeatures-platform-acceptance/consumer/src/main/java/nl/hauntedmc/proxyfeatures/acceptance/ProxyFeaturesAcceptanceConsumer.java index dc72e881..84c99e29 100644 --- a/proxyfeatures-platform-acceptance/consumer/src/main/java/nl/hauntedmc/proxyfeatures/acceptance/ProxyFeaturesAcceptanceConsumer.java +++ b/proxyfeatures-platform-acceptance/consumer/src/main/java/nl/hauntedmc/proxyfeatures/acceptance/ProxyFeaturesAcceptanceConsumer.java @@ -8,12 +8,27 @@ import com.velocitypowered.api.proxy.ProxyServer; import nl.hauntedmc.dataregistry.api.DataRegistryApiProvider; import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesApi; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionApi; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueueApi; +import nl.hauntedmc.proxyfeatures.api.extension.ExtensionRegistration; +import nl.hauntedmc.proxyfeatures.api.extension.MotdContribution; +import nl.hauntedmc.proxyfeatures.api.extension.MotdExtensions; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureId; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureState; +import nl.hauntedmc.proxyfeatures.api.service.CapabilityRef; import org.slf4j.Logger; -/** Verifies the bundled ProxyFeatures artifact against the current shared platform APIs. */ +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +/** Verifies the bundled runtime through only its published API artifact. */ @Plugin(id = "proxyfeatures-acceptance", name = "ProxyFeatures Acceptance", version = "1.0.0", dependencies = {@Dependency(id = "dataprovider"), @Dependency(id = "dataregistry"), @Dependency(id = "proxyfeatures")}) public final class ProxyFeaturesAcceptanceConsumer { + private static final FeatureId CAPACITY = FeatureId.of("capacity"); + private static final int EXPECTED_FEATURES = 33; + private final ProxyServer proxy; private final Logger logger; @@ -25,32 +40,96 @@ public ProxyFeaturesAcceptanceConsumer(ProxyServer proxy, Logger logger) { @Subscribe public void onProxyInitialize(ProxyInitializeEvent event) { + proxy.getScheduler().buildTask(this, this::verifyInitialState).schedule(); + } + + private void verifyInitialState() { + try { + DataRegistryApiProvider provider = proxy.getPluginManager().getPlugin("dataregistry") + .flatMap(container -> container.getInstance()) + .filter(DataRegistryApiProvider.class::isInstance) + .map(DataRegistryApiProvider.class::cast) + .orElseThrow(() -> new IllegalStateException("DataRegistry does not expose its public API.")); + awaitReady(provider); + + ProxyFeaturesApi api = proxy.getPluginManager().getPlugin("proxyfeatures") + .flatMap(container -> container.getInstance()) + .filter(ProxyFeaturesApi.class::isInstance) + .map(ProxyFeaturesApi.class::cast) + .orElseThrow(() -> new IllegalStateException("ProxyFeatures does not expose its public API.")); + require(api.version().apiVersion().equals("3.3.0"), + "Unexpected ProxyFeatures API version: " + api.version()); + require(api.features().snapshot().size() == EXPECTED_FEATURES, + "Expected all 33 built-in features in the public catalog."); + require(api.features().find(CAPACITY) + .filter(snapshot -> snapshot.state() == FeatureState.ACTIVE) + .isPresent(), + "Capacity is not active in the public feature catalog."); + + CapabilityRef admission = api.capabilities().reference(AdmissionApi.class); + AdmissionApi initialProvider = admission.require(); + require(!api.capabilities().reference(QueueApi.class).isAvailable(), + "Disabled Queue capability was unexpectedly available."); + verifyExtensionLifecycle(api); + + logger.info("PROXYFEATURES_ACCEPTANCE_READY platform=velocity"); + verifyReloadTransition(api, admission, initialProvider); + } catch (Exception exception) { + logger.error("PROXYFEATURES_ACCEPTANCE_FAIL platform=velocity", exception); + } + } + + private void verifyExtensionLifecycle(ProxyFeaturesApi api) { + MotdExtensions extensions = api.capabilities().reference(MotdExtensions.class).require(); + ExtensionRegistration registration = extensions.register( + "proxyfeatures-acceptance", + Integer.MAX_VALUE, + context -> Optional.of(MotdContribution.secondLine("acceptance")) + ); + registration.close(); + registration.close(); + } + + private void verifyReloadTransition( + ProxyFeaturesApi api, + CapabilityRef admission, + AdmissionApi initialProvider + ) { + long deadline = System.nanoTime() + Duration.ofSeconds(60).toNanos(); + AtomicBoolean complete = new AtomicBoolean(); proxy.getScheduler().buildTask(this, () -> { + if (complete.get()) { + return; + } try { - Object registry = proxy.getPluginManager().getPlugin("dataregistry").flatMap(container -> container.getInstance()) - .orElseThrow(() -> new IllegalStateException("DataRegistry plugin instance is unavailable.")); - if (!(registry instanceof DataRegistryApiProvider provider)) { - throw new IllegalStateException("DataRegistry does not expose its public API."); - } - awaitReady(provider); - Object proxyFeatures = proxy.getPluginManager().getPlugin("proxyfeatures") - .flatMap(container -> container.getInstance()) - .orElseThrow(() -> new IllegalStateException("ProxyFeatures did not remain enabled.")); - if (!(proxyFeatures instanceof ProxyFeaturesApi api)) { - throw new IllegalStateException("ProxyFeatures does not expose its public API."); + Optional current = admission.get(); + if (current.isPresent() && current.get() != initialProvider) { + require(api.features().find(CAPACITY) + .filter(snapshot -> snapshot.state() == FeatureState.ACTIVE) + .isPresent(), + "Capacity did not return to ACTIVE after reload."); + complete.set(true); + logger.info("PROXYFEATURES_ACCEPTANCE_PASS platform=velocity"); + return; } - if (!api.version().apiVersion().equals("3.3.0")) { - throw new IllegalStateException("Unexpected ProxyFeatures API version: " + api.version()); + if (System.nanoTime() >= deadline) { + throw new IllegalStateException("CapabilityRef did not resolve a replacement provider after reload."); } - logger.info("PROXYFEATURES_ACCEPTANCE_PASS platform=velocity"); } catch (Exception exception) { + complete.set(true); logger.error("PROXYFEATURES_ACCEPTANCE_FAIL platform=velocity", exception); } - }).schedule(); + }).repeat(Duration.ofMillis(100)).schedule(); + } + + private static void require(boolean condition, String message) { + if (!condition) { + throw new IllegalStateException(message); + } } private static void awaitReady(DataRegistryApiProvider provider) throws InterruptedException { - long deadline = System.nanoTime() + 60_000_000_000L; + long deadline = System.nanoTime() + Duration.ofSeconds(60).toNanos(); while (System.nanoTime() < deadline) { try { if (provider.getDataRegistry().isReady()) { diff --git a/proxyfeatures-platform-acceptance/run-platform-acceptance.sh b/proxyfeatures-platform-acceptance/run-platform-acceptance.sh index 10a31525..e6f7f3d8 100644 --- a/proxyfeatures-platform-acceptance/run-platform-acceptance.sh +++ b/proxyfeatures-platform-acceptance/run-platform-acceptance.sh @@ -169,7 +169,7 @@ mkfifo "$work_directory/velocity/console.in" velocity_pid=$! exec {velocity_input_fd}>"$work_directory/velocity/console.in" capacity_config="$work_directory/velocity/plugins/proxyfeatures/features/Capacity/config.yml" -wait_for_log "$work_directory/velocity/velocity.log" 'PROXYFEATURES_ACCEPTANCE_PASS platform=velocity' +wait_for_log "$work_directory/velocity/velocity.log" 'PROXYFEATURES_ACCEPTANCE_READY platform=velocity' wait_for_log "$work_directory/velocity/velocity.log" 'Capacity started with config.yml as its only persistent storage' [[ "$(mysql_scalar 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = "minecraft" AND TABLE_NAME LIKE "capacity_%"')" == "0" ]] \ @@ -195,6 +195,7 @@ wait_for_file_pattern "$capacity_config" 'acceptance-manual-state' \ printf 'capacity reload\n' >&"$velocity_input_fd" wait_for_log "$work_directory/velocity/velocity.log" 'handmatige states zijn opnieuw uit config.yml geladen' +wait_for_log "$work_directory/velocity/velocity.log" 'PROXYFEATURES_ACCEPTANCE_PASS platform=velocity' printf 'capacity state survival clear\n' >&"$velocity_input_fd" wait_for_file_absence "$capacity_config" 'acceptance-manual-state' \ diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/ProxyFeatures.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/ProxyFeatures.java index 454f2c41..7a17c2c5 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/ProxyFeatures.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/ProxyFeatures.java @@ -34,7 +34,6 @@ import nl.hauntedmc.proxyfeatures.framework.extension.DefaultMotdExtensions; import nl.hauntedmc.proxyfeatures.framework.service.DefaultCapabilityRegistry; import nl.hauntedmc.proxyfeatures.framework.service.DefaultFeatureCatalog; -import nl.hauntedmc.proxyfeatures.framework.service.DefaultProxyFeaturesApi; import nl.hauntedmc.proxyfeatures.framework.service.InternalServiceRegistry; import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; import nl.hauntedmc.proxyfeatures.toolkit.ToolkitContext; @@ -72,11 +71,6 @@ public class ProxyFeatures implements ProxyFeaturesApi, ToolkitContext { @SuppressWarnings("FieldCanBeLocal") private final nl.hauntedmc.proxyfeatures.framework.service.CapabilityRegistration motdExtensionRegistration = capabilityRegistry.register(FeatureId.of("core"), MotdExtensions.class, motdExtensions); - private final ProxyFeaturesApi api = new DefaultProxyFeaturesApi( - ProxyFeaturesApiVersion.current(ProxyFeaturesApiVersion.CURRENT), - capabilityRegistry, - featureCatalog - ); @Inject public ProxyFeatures(ProxyServer proxy, @@ -241,17 +235,17 @@ public PlayerReferenceResolver getPlayerReferenceResolver() { @Override public ProxyFeaturesApiVersion version() { - return api.version(); + return ProxyFeaturesApiVersion.current(ProxyFeaturesApiVersion.CURRENT); } @Override public CapabilityRegistry capabilities() { - return api.capabilities(); + return capabilityRegistry; } @Override public FeatureCatalog features() { - return api.features(); + return featureCatalog; } public DefaultCapabilityRegistry getCapabilityRegistry() { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureContext.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureContext.java index 309e200a..0c99008e 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureContext.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureContext.java @@ -1,60 +1,44 @@ package nl.hauntedmc.proxyfeatures.features; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; +import java.util.List; import java.util.Objects; -public final class FeatureContext { - - private final ProxyFeatures plugin; - private final T meta; - private final FeatureConfigHandler configHandler; - private final FeatureLifecycleManager lifecycleManager; - private final FeatureLogger logger; - private final LocalizationHandler localizationHandler; - - public FeatureContext( - ProxyFeatures plugin, - T meta, - FeatureConfigHandler configHandler, - FeatureLifecycleManager lifecycleManager, - FeatureLogger logger, - LocalizationHandler localizationHandler - ) { - this.plugin = Objects.requireNonNull(plugin, "plugin"); - this.meta = Objects.requireNonNull(meta, "meta"); - this.configHandler = Objects.requireNonNull(configHandler, "configHandler"); - this.lifecycleManager = Objects.requireNonNull(lifecycleManager, "lifecycleManager"); - this.logger = Objects.requireNonNull(logger, "logger"); - this.localizationHandler = Objects.requireNonNull(localizationHandler, "localizationHandler"); - } - - public ProxyFeatures plugin() { - return plugin; - } - - public T meta() { - return meta; - } - - public FeatureConfigHandler configHandler() { - return configHandler; - } - - public FeatureLifecycleManager lifecycleManager() { - return lifecycleManager; - } - - public FeatureLogger logger() { - return logger; +/** Immutable runtime resources and descriptor data owned by one feature instance. */ +public record FeatureContext( + ProxyFeatures plugin, + String featureName, + String featureVersion, + List featureDependencies, + List pluginDependencies, + FeatureConfigHandler configHandler, + FeatureLifecycleManager lifecycleManager, + FeatureLogger logger, + LocalizationHandler localizationHandler +) { + public FeatureContext { + Objects.requireNonNull(plugin, "plugin"); + featureName = requireText(featureName, "featureName"); + featureVersion = requireText(featureVersion, "featureVersion"); + featureDependencies = List.copyOf(Objects.requireNonNull(featureDependencies, "featureDependencies")); + pluginDependencies = List.copyOf(Objects.requireNonNull(pluginDependencies, "pluginDependencies")); + Objects.requireNonNull(configHandler, "configHandler"); + Objects.requireNonNull(lifecycleManager, "lifecycleManager"); + Objects.requireNonNull(logger, "logger"); + Objects.requireNonNull(localizationHandler, "localizationHandler"); } - public LocalizationHandler localizationHandler() { - return localizationHandler; + private static String requireText(String value, String fieldName) { + Objects.requireNonNull(value, fieldName); + String normalized = value.trim(); + if (normalized.isEmpty()) { + throw new IllegalArgumentException(fieldName + " must not be blank"); + } + return normalized; } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureFactory.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureFactory.java deleted file mode 100644 index eb8e7d84..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/FeatureFactory.java +++ /dev/null @@ -1,32 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features; - -import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class FeatureFactory { - - public static VelocityBaseFeature createFeature(String featureClassName, FeatureContext context) { - if (featureClassName == null || featureClassName.isBlank()) { - context.plugin().getLogger().error("Failed to instantiate feature: missing feature class name."); - return null; - } - - try { - ProxyFeatures plugin = context.plugin(); - Class rawClass = Class.forName(featureClassName, true, plugin.getClass().getClassLoader()); - if (!VelocityBaseFeature.class.isAssignableFrom(rawClass)) { - plugin.getLogger().error("Feature class does not extend VelocityBaseFeature: {}", featureClassName); - return null; - } - - @SuppressWarnings("unchecked") - Class> featureClass = (Class>) rawClass; - var ctor = featureClass.getDeclaredConstructor(FeatureContext.class); - ctor.setAccessible(true); - return ctor.newInstance(context); - } catch (ReflectiveOperationException | LinkageError t) { - context.plugin().getLogger().error("Failed to instantiate feature class: {}", featureClassName, t); - return null; - } - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeature.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeature.java index 48bce16d..1b4d3dab 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeature.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeature.java @@ -5,7 +5,6 @@ import nl.hauntedmc.dataprovider.database.messaging.MessagingDatabaseProvider; import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.framework.feature.Feature; -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; @@ -17,31 +16,31 @@ import java.util.List; import java.util.Optional; -public abstract class VelocityBaseFeature implements Feature { +public abstract class VelocityBaseFeature implements Feature { - private final FeatureContext context; + private final FeatureContext context; - protected VelocityBaseFeature(FeatureContext context) { + protected VelocityBaseFeature(FeatureContext context) { this.context = context; } public String getFeatureName() { - return context.meta().getFeatureName(); + return context.featureName(); } public String getFeatureVersion() { - return context.meta().getFeatureVersion(); + return context.featureVersion(); } public List getDependencies() { - return context.meta().getDependencies(); + return context.featureDependencies(); } public List getPluginDependencies() { - return context.meta().getPluginDependencies(); + return context.pluginDependencies(); } - public FeatureContext getContext() { + public FeatureContext getContext() { return context; } @@ -108,6 +107,28 @@ public LocalizationHandler getLocalizationHandler() { return context.localizationHandler(); } + /** Resolves an optional public capability through the stable plugin-owned registry. */ + public final Optional findCapability(Class capabilityType) { + return getPlugin().capabilities().reference(capabilityType).get(); + } + + /** Resolves a required public capability or fails with a descriptive startup error. */ + public final T requireCapability(Class capabilityType) { + return findCapability(capabilityType).orElseThrow(() -> new IllegalStateException( + "Required capability is unavailable for " + getFeatureName() + ": " + capabilityType.getName() + )); + } + + /** Resolves a runtime-only collaboration port owned by another feature. */ + public final Optional findInternalService(Class serviceType) { + return getPlugin().getInternalServiceRegistry().find(serviceType); + } + + /** Resolves a required runtime-only collaboration port. */ + public final T requireInternalService(Class serviceType) { + return getPlugin().getInternalServiceRegistry().require(serviceType); + } + /** * Each feature should define its default settings. */ diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/Announcer.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/Announcer.java index 2f6b8b11..2782bdce 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/Announcer.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/Announcer.java @@ -10,13 +10,12 @@ import nl.hauntedmc.proxyfeatures.features.announcer.internal.AnnouncerHandler; import nl.hauntedmc.proxyfeatures.features.announcer.internal.AnnouncerSettingsService; import nl.hauntedmc.proxyfeatures.features.announcer.listener.AnnouncerPlayerListener; -import nl.hauntedmc.proxyfeatures.features.announcer.meta.Meta; import nl.hauntedmc.proxyfeatures.framework.persistence.DataRegistryIdentityGate; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import java.util.List; -public class Announcer extends VelocityBaseFeature { +public class Announcer extends VelocityBaseFeature { public static final String ADMIN_PERMISSION = "proxyfeatures.feature.announcer.command"; public static final String TOGGLE_PERMISSION = ADMIN_PERMISSION + ".toggle"; @@ -24,7 +23,7 @@ public class Announcer extends VelocityBaseFeature { private AnnouncerSettingsService settingsService; private AnnouncerHandler handler; - public Announcer(FeatureContext context) { + public Announcer(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerHandler.java index 99fc3dbd..6226eac0 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerHandler.java @@ -7,9 +7,9 @@ import com.velocitypowered.api.scheduler.ScheduledTask; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.announcer.Announcer; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerSettingsService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerSettingsService.java index f5ec32c5..9968acfd 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerSettingsService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerSettingsService.java @@ -112,6 +112,6 @@ private boolean loadEnabledState(UUID uuid, String username, boolean forceEnable } private PlayerReference findPlayer(Session session, UUID uuid, String username) { - return playerResolver.resolveManaged(session, uuid); + return playerResolver.resolveReference(uuid); } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/meta/Meta.java deleted file mode 100644 index 408ca0f7..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/announcer/meta/Meta.java +++ /dev/null @@ -1,23 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.announcer.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "Announcer"; - } - - @Override - public String getFeatureVersion() { - return "2.0.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/AntiBot.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/AntiBot.java index e4653473..728a8c49 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/AntiBot.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/AntiBot.java @@ -13,14 +13,13 @@ import nl.hauntedmc.proxyfeatures.features.antibot.internal.AntiBotService; import nl.hauntedmc.proxyfeatures.features.antibot.internal.KnownPlayerStore; import nl.hauntedmc.proxyfeatures.features.antibot.listener.AntiBotListener; -import nl.hauntedmc.proxyfeatures.features.antibot.meta.Meta; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import java.nio.file.Path; import java.time.Duration; import java.util.List; -public final class AntiBot extends VelocityBaseFeature { +public final class AntiBot extends VelocityBaseFeature { public static final String BYPASS_PERMISSION = "proxyfeatures.feature.antibot.bypass"; public static final String NOTIFY_PERMISSION = "proxyfeatures.feature.antibot.notify"; @@ -29,7 +28,7 @@ public final class AntiBot extends VelocityBaseFeature { private AntiBotService service; private AntiBotAuditLogService auditLogService; - public AntiBot(FeatureContext context) { + public AntiBot(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/meta/Meta.java deleted file mode 100644 index 356af46d..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antibot/meta/Meta.java +++ /dev/null @@ -1,16 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.antibot.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "AntiBot"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/AntiVPN.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/AntiVPN.java index ac0a48c1..165bf1d4 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/AntiVPN.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/AntiVPN.java @@ -20,13 +20,12 @@ import nl.hauntedmc.proxyfeatures.features.antivpn.internal.provider.ProviderChain; import nl.hauntedmc.proxyfeatures.features.antivpn.internal.provider.ProviderRegistry; import nl.hauntedmc.proxyfeatures.features.antivpn.listener.AntiVPNListener; -import nl.hauntedmc.proxyfeatures.features.antivpn.meta.Meta; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import java.time.Duration; import java.util.List; -public class AntiVPN extends VelocityBaseFeature { +public class AntiVPN extends VelocityBaseFeature { private CountryService countryService; private MetricsCollector metrics; @@ -34,7 +33,7 @@ public class AntiVPN extends VelocityBaseFeature { private AntiVPNService service; private AntiVpnAuditLogService auditLogService; - public AntiVPN(FeatureContext context) { + public AntiVPN(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/meta/Meta.java deleted file mode 100644 index 3f684ba0..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/antivpn/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.antivpn.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "AntiVPN"; - } - - @Override - public String getFeatureVersion() { - return "1.1.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/Broadcast.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/Broadcast.java index b41d2ec2..6cf59191 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/Broadcast.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/Broadcast.java @@ -5,11 +5,10 @@ import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.broadcast.command.BroadcastProxyCommand; -import nl.hauntedmc.proxyfeatures.features.broadcast.meta.Meta; -public class Broadcast extends VelocityBaseFeature { +public class Broadcast extends VelocityBaseFeature { - public Broadcast(FeatureContext context) { + public Broadcast(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommand.java index a9b1ed83..394a57f7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/command/BroadcastProxyCommand.java @@ -12,8 +12,8 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.title.Title; import nl.hauntedmc.proxyfeatures.framework.command.brigadier.BrigadierCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.TextFormatter; import nl.hauntedmc.proxyfeatures.features.broadcast.Broadcast; import org.jetbrains.annotations.NotNull; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/meta/Meta.java deleted file mode 100644 index fde67048..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/broadcast/meta/Meta.java +++ /dev/null @@ -1,17 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.broadcast.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "Broadcast"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/Capacity.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/Capacity.java index 22019072..b6291572 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/Capacity.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/Capacity.java @@ -17,7 +17,6 @@ import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionApi; import nl.hauntedmc.proxyfeatures.features.capacity.internal.CapacitySnapshotPublisher; import nl.hauntedmc.proxyfeatures.features.capacity.listener.CapacityListener; -import nl.hauntedmc.proxyfeatures.features.capacity.meta.Meta; import java.time.Duration; import java.util.LinkedHashMap; @@ -25,7 +24,7 @@ import java.util.Map; import java.util.Optional; -public final class Capacity extends VelocityBaseFeature { +public final class Capacity extends VelocityBaseFeature { private CapacityService service; private CapacityIntegrationSynchronizer integrationSynchronizer; private CapacityControlPlane controlPlane; @@ -36,7 +35,7 @@ public final class Capacity extends VelocityBaseFeature { private String snapshotPublisherId; private int snapshotIntervalSeconds; - public Capacity(FeatureContext context) { + public Capacity(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/AdmissionCapability.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/AdmissionCapability.java index 1b4c4a48..50d3f52e 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/AdmissionCapability.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/AdmissionCapability.java @@ -18,7 +18,6 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; -import java.util.Optional; /** Public, permission-safe projection of the internal admission engine. */ public final class AdmissionCapability implements AdmissionApi { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityIntegrationSynchronizer.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityIntegrationSynchronizer.java index 95505bbe..fd8fe3d8 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityIntegrationSynchronizer.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityIntegrationSynchronizer.java @@ -5,12 +5,9 @@ import nl.hauntedmc.proxyfeatures.framework.admission.CapacityState; import nl.hauntedmc.proxyfeatures.framework.admission.RestartCoordinationPort; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; -import nl.hauntedmc.proxyfeatures.framework.service.InternalServices; import java.time.Duration; import java.util.HashSet; -import java.util.Locale; import java.util.Set; /** Mirrors independently owned operational restrictions into Capacity state claims. */ @@ -85,7 +82,7 @@ private synchronized void sync() { private void syncMaintenance() { Set active = new HashSet<>(); - FeatureServices.find(feature, MaintenanceApi.class) + feature.findCapability(MaintenanceApi.class) .map(MaintenanceApi::snapshot) .stream() .flatMap(snapshot -> snapshot.servers().stream()) @@ -104,7 +101,7 @@ private void syncMaintenance() { } private void syncRestart() { - RestartCoordinationPort current = InternalServices.find(feature, RestartCoordinationPort.class) + RestartCoordinationPort current = feature.findInternalService(RestartCoordinationPort.class) .orElse(null); if (attachedRestart != null && attachedRestart != current) { attachedRestart.detachAdmission(service); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityService.java index b62344b1..181ce094 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/internal/CapacityService.java @@ -18,8 +18,6 @@ import nl.hauntedmc.proxyfeatures.api.model.ServerId; import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; import nl.hauntedmc.proxyfeatures.features.capacity.config.CapacityConfig; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; -import nl.hauntedmc.proxyfeatures.framework.service.InternalServices; import java.time.Duration; import java.time.Instant; @@ -124,7 +122,7 @@ public CapacityRequest createRequest( Objects.requireNonNull(player, "player"); String target = normalize(targetServer); boolean maintenanceBypass = ServerId.optional(target).flatMap(server -> - FeatureServices.find(feature, MaintenanceApi.class) + feature.findCapability(MaintenanceApi.class) .map(api -> api.mayBypass( player.getUniqueId(), MaintenanceScope.server(server) @@ -369,7 +367,7 @@ public synchronized void setServerState(String serverName, CapacityState state, if (!effective.acceptsNormalAdmissions()) { revokeBlockedLeases(server); } else if (previous != CapacityState.OPEN) { - InternalServices.find(feature, QueueAdmissionPort.class) + feature.findInternalService(QueueAdmissionPort.class) .ifPresent(api -> api.capacityChanged(ServerId.of(server))); } } @@ -671,7 +669,7 @@ private Set configuredServerNames() { } private void wakeQueues() { - InternalServices.find(feature, QueueAdmissionPort.class).ifPresent(api -> + feature.findInternalService(QueueAdmissionPort.class).ifPresent(api -> configuredServerNames().forEach(server -> api.capacityChanged(ServerId.of(server)))); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/listener/CapacityListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/listener/CapacityListener.java index b22f72e7..d8ae8340 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/listener/CapacityListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/listener/CapacityListener.java @@ -24,8 +24,6 @@ import nl.hauntedmc.proxyfeatures.features.capacity.internal.CapacityService; import nl.hauntedmc.proxyfeatures.framework.admission.QueueAdmissionPort; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; -import nl.hauntedmc.proxyfeatures.framework.service.InternalServices; import java.util.Locale; @@ -153,7 +151,7 @@ public void onDisconnect(DisconnectEvent event) { } private AdmissionIntent resolveIntent(Player player, String previous, String original, String target) { - MaintenanceApi maintenance = FeatureServices.find(feature, MaintenanceApi.class).orElse(null); + MaintenanceApi maintenance = feature.findCapability(MaintenanceApi.class).orElse(null); if (maintenance != null) { boolean redirectedFromMaintained = !original.isBlank() && !original.equals(target) @@ -165,12 +163,12 @@ private AdmissionIntent resolveIntent(Player player, String previous, String ori return AdmissionIntent.MAINTENANCE_EVACUATION; } } - boolean restartReturn = FeatureServices.find(feature, RestartApi.class) + boolean restartReturn = feature.findCapability(RestartApi.class) .map(api -> api.isExpectedReturn(player.getUniqueId(), ServerId.of(target))) .orElse(false); if (restartReturn) return AdmissionIntent.RESTART_RETURN; - TwoFactorApi twoFactor = FeatureServices.find(feature, TwoFactorApi.class).orElse(null); + TwoFactorApi twoFactor = feature.findCapability(TwoFactorApi.class).orElse(null); boolean twoFactorRoute = previous.isBlank() && !original.isBlank() && !original.equals(target) @@ -182,13 +180,13 @@ private AdmissionIntent resolveIntent(Player player, String previous, String ori private boolean enqueue(Player player, String serverName, CapacityDenialReason reason, CapacityRequest request) { ServerId server = ServerId.of(serverName); - return InternalServices.find(feature, QueueAdmissionPort.class) + return feature.findInternalService(QueueAdmissionPort.class) .map(api -> api.enqueueDenied(player, server, reason, request)) .orElse(false); } private boolean consumeCancelledQueueAdvance(Player player, String serverName) { - return InternalServices.find(feature, QueueAdmissionPort.class) + return feature.findInternalService(QueueAdmissionPort.class) .map(api -> api.consumeCancelledAdvance(player.getUniqueId(), ServerId.of(serverName))) .orElse(false); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/meta/Meta.java deleted file mode 100644 index bc454550..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/capacity/meta/Meta.java +++ /dev/null @@ -1,15 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.capacity.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public final class Meta implements BaseMeta { - @Override - public String getFeatureName() { - return "Capacity"; - } - - @Override - public String getFeatureVersion() { - return "1.4.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/ClientInfo.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/ClientInfo.java index 3399649e..be2b15ae 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/ClientInfo.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/ClientInfo.java @@ -16,18 +16,17 @@ import nl.hauntedmc.proxyfeatures.features.clientinfo.internal.ClientInfoSettingsService; import nl.hauntedmc.proxyfeatures.features.clientinfo.internal.ClientTelemetryService; import nl.hauntedmc.proxyfeatures.features.clientinfo.listener.PlayerListener; -import nl.hauntedmc.proxyfeatures.features.clientinfo.meta.Meta; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import java.util.Map; -public class ClientInfo extends VelocityBaseFeature { +public class ClientInfo extends VelocityBaseFeature { public static final String STAFF_DETAILS_PERMISSION = "proxyfeatures.feature.clientinfo.command.staff"; private ClientInfoAdvisor advisor; - public ClientInfo(FeatureContext context) { + public ClientInfo(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoPersistenceService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoPersistenceService.java index 6988c2a1..bf6ecae0 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoPersistenceService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoPersistenceService.java @@ -74,7 +74,7 @@ private void runPersist(UUID uuid, long generation) { private void persistSnapshot(ClientTelemetryService.SessionSnapshot snapshot) { try { orm.runInTransaction(session -> { - PlayerReference player = playerResolver.resolveManaged(session, snapshot.uuid()); + PlayerReference player = playerResolver.resolveReference(snapshot.uuid()); if (player == null) { return null; } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoSettingsService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoSettingsService.java index 5290cdde..92a30fbf 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoSettingsService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoSettingsService.java @@ -61,7 +61,7 @@ private PlayerClientInfoSettingsEntity loadSettings(UUID uuid, String username) } private PlayerReference findPlayer(org.hibernate.Session session, UUID uuid, String username) { - return playerResolver.resolveManaged(session, uuid); + return playerResolver.resolveReference(uuid); } public Optional getPlayerReference(UUID uuid) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/meta/Meta.java deleted file mode 100644 index 4e7eebcb..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/clientinfo/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.clientinfo.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "ClientInfo"; - } - - @Override - public String getFeatureVersion() { - return "1.1.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/CommandHider.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/CommandHider.java index af842369..c7f3dca7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/CommandHider.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/CommandHider.java @@ -7,15 +7,14 @@ import nl.hauntedmc.proxyfeatures.features.commandhider.command.CommandHiderCommand; import nl.hauntedmc.proxyfeatures.features.commandhider.internal.HiderHandler; import nl.hauntedmc.proxyfeatures.features.commandhider.listener.AvailableCommandListener; -import nl.hauntedmc.proxyfeatures.features.commandhider.meta.Meta; import java.util.List; -public final class CommandHider extends VelocityBaseFeature { +public final class CommandHider extends VelocityBaseFeature { private HiderHandler hiderHandler; - public CommandHider(FeatureContext context) { + public CommandHider(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/meta/Meta.java deleted file mode 100644 index 8b36e3db..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandhider/meta/Meta.java +++ /dev/null @@ -1,18 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.commandhider.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "CommandHider"; - } - - @Override - public String getFeatureVersion() { - return "1.1.0"; - } - -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/CommandLogger.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/CommandLogger.java index 272886df..0e07442b 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/CommandLogger.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/CommandLogger.java @@ -8,16 +8,15 @@ import nl.hauntedmc.proxyfeatures.features.commandlogger.entity.CommandExecutionEntity; import nl.hauntedmc.proxyfeatures.features.commandlogger.internal.LogHandler; import nl.hauntedmc.proxyfeatures.features.commandlogger.listener.CommandListener; -import nl.hauntedmc.proxyfeatures.features.commandlogger.meta.Meta; import nl.hauntedmc.proxyfeatures.features.commandlogger.service.CommandLogService; -public class CommandLogger extends VelocityBaseFeature { +public class CommandLogger extends VelocityBaseFeature { private LogHandler logHandler; private CommandLogService commandLogService; private ORMContext ormContext; - public CommandLogger(FeatureContext context) { + public CommandLogger(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/meta/Meta.java deleted file mode 100644 index 46139316..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.commandlogger.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "CommandLogger"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogService.java index 8a8aa496..e1d3d65b 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandlogger/service/CommandLogService.java @@ -66,7 +66,7 @@ private void schedulePersist(java.util.UUID playerUuid, String sourceLabel, Stri private void persist(java.util.UUID playerUuid, String sourceLabel, String fullCommand, long timestamp) { feature.getOrmContext().runInTransaction(session -> { - PlayerReference playerEntity = playerUuid == null ? null : playerResolver.resolveManaged(session, playerUuid); + PlayerReference playerEntity = playerUuid == null ? null : playerResolver.resolveReference(playerUuid); if (playerUuid != null && playerEntity == null) { return null; } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/CommandRelay.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/CommandRelay.java index 8ddb5840..6a6b238c 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/CommandRelay.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/CommandRelay.java @@ -11,13 +11,12 @@ import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.commandrelay.internal.EventBusHandler; -import nl.hauntedmc.proxyfeatures.features.commandrelay.meta.Meta; import java.util.List; import java.util.Locale; import java.util.Optional; -public class CommandRelay extends VelocityBaseFeature { +public class CommandRelay extends VelocityBaseFeature { private static final String STREAM = "proxy.commandrelay.command"; private static final String DEFAULT_CONSUMER_GROUP = "proxyfeatures.commandrelay.proxy"; @@ -26,7 +25,7 @@ public class CommandRelay extends VelocityBaseFeature { private EventBusHandler eventBusHandler; private CommandRelayAuditLogService auditLogService; - public CommandRelay(FeatureContext context) { + public CommandRelay(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandler.java index 70996dd3..8c278d09 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/internal/EventBusHandler.java @@ -11,7 +11,6 @@ import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheDirectory; import nl.hauntedmc.proxyfeatures.toolkit.io.cache.CacheType; import nl.hauntedmc.proxyfeatures.toolkit.io.cache.FileCacheStore; -import nl.hauntedmc.proxyfeatures.toolkit.util.type.CastUtils; import nl.hauntedmc.proxyfeatures.contracts.messaging.CommandRelayMessage; import nl.hauntedmc.proxyfeatures.features.commandrelay.CommandRelay; import nl.hauntedmc.proxyfeatures.features.commandrelay.audit.CommandRelayAuditLogService; @@ -142,9 +141,10 @@ private void handleIncoming(String stream, DurableDelivery ? full.substring(0, full.indexOf(' ')) : full; - List whitelist = CastUtils.safeCastToList( - feature.getConfigHandler().get("command_whitelist"), - String.class + List whitelist = feature.getConfigHandler().getList( + "command_whitelist", + String.class, + List.of() ); Set allowed = whitelist.stream() .filter(value -> value != null && !value.isBlank()) diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/meta/Meta.java deleted file mode 100644 index 41da4415..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/commandrelay/meta/Meta.java +++ /dev/null @@ -1,25 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.commandrelay.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "CommandRelay"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER); - } - -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/ConnectionInfo.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/ConnectionInfo.java index d6a34cab..c786edea 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/ConnectionInfo.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/ConnectionInfo.java @@ -8,13 +8,12 @@ import nl.hauntedmc.proxyfeatures.features.connectioninfo.command.PingCommand; import nl.hauntedmc.proxyfeatures.features.connectioninfo.internal.SessionHandler; import nl.hauntedmc.proxyfeatures.features.connectioninfo.listener.PlayerListener; -import nl.hauntedmc.proxyfeatures.features.connectioninfo.meta.Meta; -public class ConnectionInfo extends VelocityBaseFeature { +public class ConnectionInfo extends VelocityBaseFeature { private SessionHandler sessionHandler; - public ConnectionInfo(FeatureContext context) { + public ConnectionInfo(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/meta/Meta.java deleted file mode 100644 index 20cb603d..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/connectioninfo/meta/Meta.java +++ /dev/null @@ -1,18 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.connectioninfo.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "ConnectionInfo"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } - -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/Friends.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/Friends.java index 60c0d434..c4138892 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/Friends.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/Friends.java @@ -12,17 +12,16 @@ import nl.hauntedmc.proxyfeatures.features.friends.entity.FriendSettingsEntity; import nl.hauntedmc.proxyfeatures.features.friends.entity.FriendsService; import nl.hauntedmc.proxyfeatures.features.friends.listener.FriendActivityListener; -import nl.hauntedmc.proxyfeatures.features.friends.meta.Meta; import nl.hauntedmc.proxyfeatures.features.friends.support.FriendsCache; -public class Friends extends VelocityBaseFeature { +public class Friends extends VelocityBaseFeature { private ORMContext orm; private FriendsService service; private FriendsCache cache; private FriendshipApiImpl friendshipApi; - public Friends(FeatureContext context) { + public Friends(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/FriendCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/FriendCommand.java index ecd4d3d0..f0f74ada 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/FriendCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/command/FriendCommand.java @@ -6,14 +6,13 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; -import nl.hauntedmc.proxyfeatures.toolkit.util.tools.Paginator; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.pagination.Paginator; import nl.hauntedmc.proxyfeatures.features.friends.Friends; import nl.hauntedmc.proxyfeatures.features.friends.entity.FriendStatus; import nl.hauntedmc.proxyfeatures.features.friends.entity.FriendsService; import nl.hauntedmc.proxyfeatures.features.friends.entity.PlayerRef; import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -643,7 +642,7 @@ private PlayerRef resolvePlayerRef(String name) { } private boolean notVanished(Player pl) { - return FeatureServices.find(feature, PresenceApi.class) + return feature.findCapability(PresenceApi.class) .map(api -> !api.isHidden(pl.getUniqueId())) .orElse(true); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsService.java index 1f5e2387..a475c5b9 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/entity/FriendsService.java @@ -551,7 +551,7 @@ private String displayName(Long playerId) { if (playerId == null) { return "-"; } - return playerResolver.findActiveIdentityById(playerId) + return playerResolver.findIdentityById(playerId) .map(identity -> identity.username()) .orElse("#" + playerId); } @@ -560,7 +560,7 @@ private FriendSnapshot snapshot(Long playerId) { if (playerId == null) { return new FriendSnapshot(null, null, "-"); } - return playerResolver.findActiveIdentityById(playerId) + return playerResolver.findIdentityById(playerId) .map(identity -> new FriendSnapshot( identity.playerId(), identity.uuid().toString(), identity.username())) .orElseGet(() -> new FriendSnapshot(playerId, null, "#" + playerId)); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/listener/FriendActivityListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/listener/FriendActivityListener.java index 9c166d46..0863fb89 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/listener/FriendActivityListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/listener/FriendActivityListener.java @@ -11,7 +11,6 @@ import nl.hauntedmc.proxyfeatures.features.friends.entity.PlayerRef; import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceChangedEvent; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.time.Duration; import java.util.ArrayList; @@ -288,7 +287,7 @@ private boolean isVanished(Player player) { private Optional vanishState(UUID playerUuid) { try { - return FeatureServices.find(feature, PresenceApi.class) + return feature.findCapability(PresenceApi.class) .map(api -> api.isHidden(playerUuid)); } catch (RuntimeException ignored) { return Optional.empty(); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/meta/Meta.java deleted file mode 100644 index 3b514076..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/friends/meta/Meta.java +++ /dev/null @@ -1,28 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.friends.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "Friends"; - } - - @Override - public String getFeatureVersion() { - return "1.4.0"; - } - - @Override - public List getDependencies() { - return List.of(); - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/HLink.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/HLink.java index 872f360e..c7eaeb7b 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/HLink.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/HLink.java @@ -9,14 +9,13 @@ import nl.hauntedmc.proxyfeatures.features.hlink.command.RegisterCommand; import nl.hauntedmc.proxyfeatures.features.hlink.internal.HLinkHandler; import nl.hauntedmc.proxyfeatures.features.hlink.internal.hook.LuckPermsHook; -import nl.hauntedmc.proxyfeatures.features.hlink.meta.Meta; -public class HLink extends VelocityBaseFeature { +public class HLink extends VelocityBaseFeature { private HLinkHandler hlinkHandler; private LuckPermsHook luckPermsHook; - public HLink(FeatureContext context) { + public HLink(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/internal/HLinkHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/internal/HLinkHandler.java index 2d3521cf..e3ffcd66 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/internal/HLinkHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/internal/HLinkHandler.java @@ -10,8 +10,8 @@ import net.luckperms.api.node.types.InheritanceNode; import net.luckperms.api.track.Track; import net.luckperms.api.track.TrackManager; -import nl.hauntedmc.proxyfeatures.toolkit.util.http.SimpleHttpClient; -import nl.hauntedmc.proxyfeatures.toolkit.util.http.SimpleHttpClient.FormParameter; +import nl.hauntedmc.proxyfeatures.toolkit.http.HttpTransport; +import nl.hauntedmc.proxyfeatures.toolkit.http.HttpTransport.FormParameter; import nl.hauntedmc.proxyfeatures.features.hlink.HLink; import nl.hauntedmc.proxyfeatures.features.hlink.internal.api.AccountRequest; import nl.hauntedmc.proxyfeatures.features.hlink.internal.api.LinkRequest; @@ -164,7 +164,7 @@ private boolean updatePlayerData(UUID uuid, String username) { args.add(new FormParameter("groups", primaryGroup)); try { - SimpleHttpClient.postHttps(apiUrl + "/updatePlayerCache", args); + HttpTransport.postHttps(apiUrl + "/updatePlayerCache", args); updateCache.put(uuid, new CachedPlayerData(username, primaryGroup)); return true; } catch (InterruptedException e) { @@ -238,7 +238,7 @@ public String doesKeyExist(String uuid, int keyType) { args.add(new FormParameter("uuid", uuid)); args.add(new FormParameter("key_type", String.valueOf(keyType))); try { - String response = SimpleHttpClient.postHttps(apiUrl + "/checkForExistingLink", args); + String response = HttpTransport.postHttps(apiUrl + "/checkForExistingLink", args); LinkRequest request = gson.fromJson(response, LinkRequest.class); if (request != null && request.getResults() != null && !request.getResults().isEmpty()) { return request.getResults(); @@ -259,7 +259,7 @@ public boolean alreadyRegistered(String uuid) { args.add(new FormParameter("api_key", apiKey)); args.add(new FormParameter("uuid", uuid)); try { - String response = SimpleHttpClient.postHttps(apiUrl + "/checkUserAccountExists", args); + String response = HttpTransport.postHttps(apiUrl + "/checkUserAccountExists", args); AccountRequest request = gson.fromJson(response, AccountRequest.class); return request != null && request.getExists(); } catch (InterruptedException e) { @@ -346,8 +346,8 @@ private LinkResult addNewKey(UUID playerId, String username, int keyType) { argsUpdate.add(new FormParameter("groups", groups)); try { - SimpleHttpClient.postHttps(apiUrl + "/createLinkKey", argsCreate); - SimpleHttpClient.postHttps(apiUrl + "/updatePlayerCache", argsUpdate); + HttpTransport.postHttps(apiUrl + "/createLinkKey", argsCreate); + HttpTransport.postHttps(apiUrl + "/updatePlayerCache", argsUpdate); } catch (InterruptedException e) { Thread.currentThread().interrupt(); feature.getLogger().warn(Component.text("Link-key creation interrupted for " + username)); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/meta/Meta.java deleted file mode 100644 index 061804c2..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hlink/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.hlink.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "HLink"; - } - - @Override - public String getFeatureVersion() { - return "1.1.0"; - } - - @Override - public List getPluginDependencies() { - return List.of("luckperms"); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/Hub.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/Hub.java index 281e17c7..04b98ada 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/Hub.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/Hub.java @@ -5,11 +5,10 @@ import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.hub.command.HubCommand; -import nl.hauntedmc.proxyfeatures.features.hub.meta.Meta; -public class Hub extends VelocityBaseFeature { +public class Hub extends VelocityBaseFeature { - public Hub(FeatureContext context) { + public Hub(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/meta/Meta.java deleted file mode 100644 index a6982906..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/hub/meta/Meta.java +++ /dev/null @@ -1,16 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.hub.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "Hub"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/Maintenance.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/Maintenance.java index bdd6b789..0e52e3d9 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/Maintenance.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/Maintenance.java @@ -7,24 +7,22 @@ import nl.hauntedmc.proxyfeatures.features.maintenance.command.MaintenanceCommand; import nl.hauntedmc.proxyfeatures.features.maintenance.internal.MaintenanceHandler; import nl.hauntedmc.proxyfeatures.features.maintenance.listener.MaintenanceConnectionListener; -import nl.hauntedmc.proxyfeatures.features.maintenance.meta.Meta; import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceApi; import nl.hauntedmc.proxyfeatures.api.extension.ExtensionRegistration; import nl.hauntedmc.proxyfeatures.api.extension.MotdContribution; import nl.hauntedmc.proxyfeatures.api.extension.MotdExtensions; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.ArrayList; import java.util.List; -public class Maintenance extends VelocityBaseFeature { +public class Maintenance extends VelocityBaseFeature { private static final String MOTD_OVERRIDE_KEY = "maintenance"; private MaintenanceHandler handler; private ExtensionRegistration motdRegistration; - public Maintenance(FeatureContext context) { + public Maintenance(FeatureContext context) { super(context); } @@ -118,7 +116,7 @@ public MessageMap getDefaultMessages() { public void initialize() { this.handler = new MaintenanceHandler(this); getLifecycleManager().getApiManager().registerService(MaintenanceApi.class, handler); - motdRegistration = FeatureServices.require(this, MotdExtensions.class).register( + motdRegistration = requireCapability(MotdExtensions.class).register( MOTD_OVERRIDE_KEY, 100, context -> java.util.Optional.ofNullable(handler.resolveMotdLine2Override()) diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListener.java index 0c68fabd..ee466076 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/listener/MaintenanceConnectionListener.java @@ -10,7 +10,6 @@ import nl.hauntedmc.proxyfeatures.api.model.ServerId; import nl.hauntedmc.proxyfeatures.features.maintenance.Maintenance; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; public class MaintenanceConnectionListener { @@ -107,7 +106,7 @@ private boolean isSecurityRoute(ServerPreConnectEvent event, RegisteredServer ta return false; } - return FeatureServices.find(feature, TwoFactorApi.class) + return feature.findCapability(TwoFactorApi.class) .map(api -> api.isLocked(event.getPlayer().getUniqueId()) && api.isAuthenticationServer(ServerId.of(targetName))) .orElse(false); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/meta/Meta.java deleted file mode 100644 index 7e6555e9..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/maintenance/meta/Meta.java +++ /dev/null @@ -1,16 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.maintenance.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "Maintenance"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/Messenger.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/Messenger.java index 07bbae93..6b55b2e0 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/Messenger.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/Messenger.java @@ -13,17 +13,16 @@ import nl.hauntedmc.proxyfeatures.features.messager.history.PlayerMessageLogEntity; import nl.hauntedmc.proxyfeatures.features.messager.internal.MessagingHandler; import nl.hauntedmc.proxyfeatures.features.messager.listener.PlayerListener; -import nl.hauntedmc.proxyfeatures.features.messager.meta.Meta; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; -public class Messenger extends VelocityBaseFeature { +public class Messenger extends VelocityBaseFeature { private MessagingHandler handler; private ORMContext ormContext; private PlayerMessageHistoryLogService messageHistoryLogService; private MessageMode defaultMessageMode = MessageMode.FRIENDS; - public Messenger(FeatureContext context) { + public Messenger(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntity.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntity.java index 4c7468d4..cfe58dc1 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntity.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntity.java @@ -6,7 +6,6 @@ import java.util.HashSet; import java.util.Objects; -import java.util.Optional; import java.util.Set; @Entity @@ -28,8 +27,8 @@ public class PlayerMessageSettingsEntity { private boolean msgSpy = false; @Convert(converter = MessageModeConverter.class) - @Column(name = "message_mode", length = 32) - private MessageMode messageMode; + @Column(name = "message_mode", length = 32, nullable = false) + private MessageMode messageMode = MessageMode.FRIENDS; @ElementCollection(fetch = FetchType.LAZY) @CollectionTable( @@ -63,12 +62,8 @@ public void setMsgSpy(boolean msgSpy) { this.msgSpy = msgSpy; } - public Optional getStoredMessageMode() { - return Optional.ofNullable(messageMode); - } - - public MessageMode getMessageMode(MessageMode fallback) { - return messageMode == null ? Objects.requireNonNull(fallback, "fallback") : messageMode; + public MessageMode getMessageMode() { + return messageMode; } public void setMessageMode(MessageMode messageMode) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandler.java index c2b86ac9..911883d6 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingHandler.java @@ -3,12 +3,11 @@ import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.api.capability.social.FriendshipApi; import nl.hauntedmc.proxyfeatures.features.messager.model.MessageMode; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.TextFormatter; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; import nl.hauntedmc.proxyfeatures.features.messager.entity.PlayerMessageSettingsEntity; import nl.hauntedmc.proxyfeatures.features.messager.history.PlayerMessageHistoryLogService; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -35,7 +34,7 @@ public final class MessagingHandler { private final Map lastMessageFrom = new ConcurrentHashMap<>(); public MessagingHandler(Messenger feature) { - this(feature, () -> FeatureServices.find(feature, FriendshipApi.class)); + this(feature, () -> feature.findCapability(FriendshipApi.class)); } MessagingHandler(Messenger feature, FriendshipApi friendshipApi) { @@ -66,14 +65,14 @@ public boolean loadPlayerSettings(Player player) { if (loadedSettings.contains(id)) return true; try { - PlayerMessageSettingsEntity s = settings.loadSettings(id, player.getUsername()); + PlayerMessageSettingsEntity s = settings.loadSettings(id); // messaging toggle if (!s.isMsgToggle()) toggledOff.add(id); else toggledOff.remove(id); // privacy mode - messageModes.put(id, s.getMessageMode(feature.getDefaultMessageMode())); + messageModes.put(id, s.getMessageMode()); // spy mode if (s.isMsgSpy()) spies.add(id); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsService.java index b41714fd..ee2e108a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsService.java @@ -30,10 +30,10 @@ public MessagingSettingsService(Messenger feature) { * Loads MessageSettings for an existing DataRegistryApi player. * If the player row is not available yet, returns transient configured defaults without writing PlayerReference. */ - public PlayerMessageSettingsEntity loadSettings(UUID uuid, String username) { + public PlayerMessageSettingsEntity loadSettings(UUID uuid) { return feature.getOrmContext().runInTransaction(session -> { MessageMode defaultMode = defaultMessageMode(); - PlayerReference playerEnt = findPlayer(session, uuid, username); + PlayerReference playerEnt = playerResolver.resolveReference(uuid); if (playerEnt == null) { PlayerMessageSettingsEntity transientSettings = new PlayerMessageSettingsEntity(); transientSettings.setMessageMode(defaultMode); @@ -49,10 +49,6 @@ public PlayerMessageSettingsEntity loadSettings(UUID uuid, String username) { PlayerMessageSettingsEntity settings; if (existing.isPresent()) { settings = existing.get(); - if (settings.getStoredMessageMode().isEmpty()) { - settings.setMessageMode(defaultMode); - session.merge(settings); - } } else { settings = new PlayerMessageSettingsEntity(playerEnt); settings.setMessageMode(defaultMode); @@ -65,10 +61,6 @@ public PlayerMessageSettingsEntity loadSettings(UUID uuid, String username) { }); } - private PlayerReference findPlayer(org.hibernate.Session session, UUID uuid, String username) { - return playerResolver.resolveManaged(session, uuid); - } - public void setToggle(PlayerReference player, boolean enabled) { feature.getOrmContext().runInTransaction(session -> { PlayerMessageSettingsEntity s = session.find(PlayerMessageSettingsEntity.class, player.getId()); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibility.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibility.java index a398fb69..d8ea102e 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibility.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessengerTargetVisibility.java @@ -3,7 +3,6 @@ import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.features.messager.Messenger; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.List; import java.util.Objects; @@ -48,7 +47,7 @@ public static List list(Messenger feature, Player viewer) { return List.copyOf(feature.getPlugin().getProxy().getAllPlayers()); } try { - return FeatureServices.find(feature, PresenceApi.class) + return feature.findCapability(PresenceApi.class) .map(api -> feature.getPlugin().getProxy().getAllPlayers().stream() .filter(player -> !api.isHidden(player.getUniqueId())) .toList()) @@ -65,7 +64,7 @@ private static boolean isVisible(Messenger feature, Player viewer, Player target return true; } try { - return FeatureServices.find(feature, PresenceApi.class) + return feature.findCapability(PresenceApi.class) .map(api -> !api.isHidden(target.getUniqueId())) .orElse(true); } catch (RuntimeException failure) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/meta/Meta.java deleted file mode 100644 index 0db0d96d..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/messager/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.messager.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "Messenger"; - } - - @Override - public String getFeatureVersion() { - return "1.1.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/Motd.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/Motd.java index 4a3e6784..d4ab13f7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/Motd.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/Motd.java @@ -6,7 +6,6 @@ import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.motd.internal.MotdHandler; import nl.hauntedmc.proxyfeatures.features.motd.listener.PingListener; -import nl.hauntedmc.proxyfeatures.features.motd.meta.Meta; import java.util.List; @@ -35,11 +34,11 @@ *
  • {@code random_words}: compose line 2 from random words plus the configured suffix.
  • * */ -public class Motd extends VelocityBaseFeature { +public class Motd extends VelocityBaseFeature { private MotdHandler motdHandler; - public Motd(FeatureContext context) { + public Motd(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandler.java index 08839c8a..2d4a3532 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandler.java @@ -5,12 +5,11 @@ import com.velocitypowered.api.proxy.server.ServerPing; import nl.hauntedmc.proxyfeatures.api.extension.MotdContext; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.TextFormatter; import nl.hauntedmc.proxyfeatures.features.motd.Motd; import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.api.capability.operations.VersionApi; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.ArrayList; import java.util.Collections; @@ -71,7 +70,7 @@ private ServerPing createNewServerPing(ServerPing unmodifiedPing, MotdContext co private ServerPing.Version getDisplayedVersion(ServerPing unmodifiedPing) { ServerPing.Version version = unmodifiedPing.getVersion(); - Optional versionApi = FeatureServices.find(feature, VersionApi.class); + Optional versionApi = feature.findCapability(VersionApi.class); if (versionApi.isPresent()) { VersionApi supported = versionApi.get(); if (!supported.isSupported(unmodifiedPing.getVersion().getProtocol())) { @@ -94,7 +93,7 @@ private PlayerCountSnapshot getPlayerCountSnapshot(ServerPing unmodifiedPing) { int onlinePlayers = players.map(ServerPing.Players::getOnline).orElse(0); int maxPlayers = players.map(ServerPing.Players::getMax).orElse(onlinePlayers); List sample = players.map(ServerPing.Players::getSample).orElse(List.of()); - int vanishedPlayers = FeatureServices.find(feature, PresenceApi.class) + int vanishedPlayers = feature.findCapability(PresenceApi.class) .map(PresenceApi::snapshot) .map(snapshot -> snapshot.hiddenCount()) .orElse(0); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/meta/Meta.java deleted file mode 100644 index 6a761858..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/motd/meta/Meta.java +++ /dev/null @@ -1,17 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.motd.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "Motd"; - } - - @Override - public String getFeatureVersion() { - return "1.2.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/PlayerCount.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/PlayerCount.java index e8fb6af1..dd1bebd3 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/PlayerCount.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/PlayerCount.java @@ -7,10 +7,8 @@ import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.playercount.internal.PlayerCountService; import nl.hauntedmc.proxyfeatures.features.playercount.internal.PlayerCountPublisher; -import nl.hauntedmc.proxyfeatures.features.playercount.meta.Meta; import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCountApi; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.time.Duration; import java.util.Optional; @@ -18,7 +16,7 @@ /** * Publishes vanish-aware player counts for the complete network and every backend. */ -public final class PlayerCount extends VelocityBaseFeature { +public final class PlayerCount extends VelocityBaseFeature { static final String DEFAULT_CHANNEL = "proxy.playercount.snapshot"; static final int DEFAULT_PUBLISH_INTERVAL_SECONDS = 2; @@ -27,7 +25,7 @@ public final class PlayerCount extends VelocityBaseFeature { private PlayerCountService api; private PlayerCountPublisher publisher; - public PlayerCount(FeatureContext context) { + public PlayerCount(FeatureContext context) { super(context); } @@ -50,7 +48,7 @@ public MessageMap getDefaultMessages() { public void initialize() { api = new PlayerCountService( getPlugin().getProxy(), - () -> FeatureServices.find(this, PresenceApi.class) + () -> findCapability(PresenceApi.class) ); getLifecycleManager().getApiManager().registerService(PlayerCountApi.class, api); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/meta/Meta.java deleted file mode 100644 index 25a0c06a..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playercount/meta/Meta.java +++ /dev/null @@ -1,23 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.playercount.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public final class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "PlayerCount"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/PlayerInfo.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/PlayerInfo.java index 7643cd96..1ad4aa3a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/PlayerInfo.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/PlayerInfo.java @@ -5,14 +5,13 @@ import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.playerinfo.command.PlayerInfoCommand; -import nl.hauntedmc.proxyfeatures.features.playerinfo.meta.Meta; import nl.hauntedmc.proxyfeatures.features.playerinfo.service.PlayerInfoService; -public class PlayerInfo extends VelocityBaseFeature { +public class PlayerInfo extends VelocityBaseFeature { private PlayerInfoService service; - public PlayerInfo(FeatureContext context) { + public PlayerInfo(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/command/PlayerInfoCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/command/PlayerInfoCommand.java index 0b90d2c2..9e950b71 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/command/PlayerInfoCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/command/PlayerInfoCommand.java @@ -8,7 +8,7 @@ import nl.hauntedmc.dataregistry.api.player.PlayerProfile; import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionSnapshot; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.ComponentFormatter; import nl.hauntedmc.proxyfeatures.features.playerinfo.PlayerInfo; import nl.hauntedmc.proxyfeatures.features.playerinfo.service.PlayerInfoService; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/meta/Meta.java deleted file mode 100644 index 67c75508..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.playerinfo.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "PlayerInfo"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoService.java index bfb63194..24776f26 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoService.java @@ -8,10 +8,10 @@ import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import nl.hauntedmc.dataregistry.api.player.PlayerProfile; import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionFilter; import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionSnapshot; import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionsApi; import nl.hauntedmc.proxyfeatures.features.playerinfo.PlayerInfo; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.time.Instant; import java.time.ZoneId; @@ -93,7 +93,7 @@ public Optional getLanguage(PlayerIdentity player) { return Optional.empty(); } UUID uuid = player.uuid(); - return FeatureServices.find(feature, PlayerLanguageApi.class) + return feature.findCapability(PlayerLanguageApi.class) .map(api -> { Optional preference = api.preference(uuid); if (preference.isEmpty()) { @@ -110,8 +110,8 @@ public CompletionStage> getActiveSanctions(PlayerIdentity if (player == null) { return CompletableFuture.completedFuture(List.of()); } - return FeatureServices.find(feature, SanctionsApi.class) - .map(api -> api.find(player.uuid(), true)) + return feature.findCapability(SanctionsApi.class) + .map(api -> api.find(player.uuid(), SanctionFilter.ACTIVE)) .orElseGet(() -> CompletableFuture.completedFuture(List.of())); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/PlayerLanguage.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/PlayerLanguage.java index b0b83786..befd3944 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/PlayerLanguage.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/PlayerLanguage.java @@ -9,12 +9,11 @@ import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; import nl.hauntedmc.proxyfeatures.features.playerlanguage.command.LanguageCommand; import nl.hauntedmc.proxyfeatures.features.playerlanguage.listener.LanguageListener; -import nl.hauntedmc.proxyfeatures.features.playerlanguage.meta.Meta; import nl.hauntedmc.proxyfeatures.features.playerlanguage.service.LanguageService; import java.util.List; -public class PlayerLanguage extends VelocityBaseFeature { +public class PlayerLanguage extends VelocityBaseFeature { public static final String DEFAULT_LANGUAGE = "AUTO"; public static final String DEFAULT_AUTO_FALLBACK_LANGUAGE = "EN"; @@ -22,7 +21,7 @@ public class PlayerLanguage extends VelocityBaseFeature { private LanguageService service; - public PlayerLanguage(FeatureContext context) { + public PlayerLanguage(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/meta/Meta.java deleted file mode 100644 index bc0a584f..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/meta/Meta.java +++ /dev/null @@ -1,28 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.playerlanguage.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "PlayerLanguage"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } - - @Override - public List getDependencies() { - return List.of("AntiVPN"); - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageService.java index 6946d7f6..2def2dc1 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlanguage/service/LanguageService.java @@ -8,7 +8,6 @@ import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; import nl.hauntedmc.proxyfeatures.features.playerlanguage.PlayerLanguage; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.time.Duration; import java.util.Locale; @@ -262,7 +261,7 @@ private Language resolveEffectiveLanguage(UUID playerUuid, Language preference, } private String resolveCountryCode(UUID playerUuid) { - return FeatureServices.find(feature, NetworkLocationApi.class) + return feature.findCapability(NetworkLocationApi.class) .flatMap(countryApi -> countryApi.countryCode(playerUuid)) .orElse("UNKNOWN"); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/PlayerList.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/PlayerList.java index a3e661ef..0c16085a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/PlayerList.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/PlayerList.java @@ -7,15 +7,14 @@ import nl.hauntedmc.proxyfeatures.features.playerlist.command.GlobalListCommand; import nl.hauntedmc.proxyfeatures.features.playerlist.command.ListCommand; import nl.hauntedmc.proxyfeatures.features.playerlist.internal.PlayerListHandler; -import nl.hauntedmc.proxyfeatures.features.playerlist.meta.Meta; import java.util.List; -public class PlayerList extends VelocityBaseFeature { +public class PlayerList extends VelocityBaseFeature { private PlayerListHandler playerListHandler; - public PlayerList(FeatureContext context) { + public PlayerList(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/GlobalListCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/GlobalListCommand.java index f860efa7..951c387a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/GlobalListCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/GlobalListCommand.java @@ -5,7 +5,6 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.type.CastUtils; import nl.hauntedmc.proxyfeatures.features.playerlist.PlayerList; import java.util.Collection; @@ -19,7 +18,7 @@ public class GlobalListCommand implements FeatureCommand { public GlobalListCommand(PlayerList feature) { this.feature = feature; - blacklist = CastUtils.safeCastToList(feature.getConfigHandler().get("blacklist"), String.class); + blacklist = feature.getConfigHandler().getList("blacklist", String.class, List.of()); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/ListCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/ListCommand.java index 0aeb61e3..8e1617c1 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/ListCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/command/ListCommand.java @@ -6,7 +6,6 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.type.CastUtils; import nl.hauntedmc.proxyfeatures.features.playerlist.PlayerList; import java.util.List; @@ -21,7 +20,7 @@ public class ListCommand implements FeatureCommand { public ListCommand(PlayerList feature) { this.feature = feature; - this.blacklist = CastUtils.safeCastToList(feature.getConfigHandler().get("blacklist"), String.class); + this.blacklist = feature.getConfigHandler().getList("blacklist", String.class, List.of()); } public void execute(Invocation invocation) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandler.java index 47ebbe4f..ad102612 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/internal/PlayerListHandler.java @@ -7,7 +7,6 @@ import net.kyori.adventure.text.TextComponent; import nl.hauntedmc.proxyfeatures.features.playerlist.PlayerList; import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import java.util.*; import java.util.concurrent.TimeUnit; @@ -184,7 +183,7 @@ public Component formatPlayerList(String serverName, Collection players, } private Predicate visiblePlayerPredicate() { - Optional vanishApi = FeatureServices.find(feature, PresenceApi.class); + Optional vanishApi = feature.findCapability(PresenceApi.class); if (vanishApi.isEmpty()) { return player -> true; } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/meta/Meta.java deleted file mode 100644 index c41355b7..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/playerlist/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.playerlist.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "PlayerList"; - } - - @Override - public String getFeatureVersion() { - return "1.1.0"; - } - - @Override - public List getDependencies() { - return List.of(); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/ProxyInfo.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/ProxyInfo.java index 6f0f4cd4..0252df33 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/ProxyInfo.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/ProxyInfo.java @@ -6,14 +6,13 @@ import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.proxyinfo.command.ProxyInfoCommand; -import nl.hauntedmc.proxyfeatures.features.proxyinfo.meta.Meta; import java.time.Instant; -public class ProxyInfo extends VelocityBaseFeature { +public class ProxyInfo extends VelocityBaseFeature { private final Instant startTime; - public ProxyInfo(FeatureContext context) { + public ProxyInfo(FeatureContext context) { super(context); this.startTime = Instant.now(); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/meta/Meta.java deleted file mode 100644 index 0a376c0e..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/proxyinfo/meta/Meta.java +++ /dev/null @@ -1,17 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.proxyinfo.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "ProxyInfo"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/Queue.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/Queue.java index 7c552dc5..902eae29 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/Queue.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/Queue.java @@ -9,16 +9,14 @@ import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.queue.command.QueueCommand; import nl.hauntedmc.proxyfeatures.features.queue.listener.ConnectionListener; -import nl.hauntedmc.proxyfeatures.features.queue.meta.Meta; -import nl.hauntedmc.proxyfeatures.framework.service.InternalServices; import java.time.Duration; import java.util.List; -public class Queue extends VelocityBaseFeature { +public class Queue extends VelocityBaseFeature { private QueueManager manager; - public Queue(FeatureContext context) { + public Queue(FeatureContext context) { super(context); } @@ -71,7 +69,7 @@ public MessageMap getDefaultMessages() { @Override public void initialize() { - CapacityAPI capacity = InternalServices.require(this, CapacityAPI.class); + CapacityAPI capacity = requireInternalService(CapacityAPI.class); manager = new QueueManager(this, getPlugin().getLogger(), capacity); getLifecycleManager().getApiManager().registerService(QueueApi.class, manager); getLifecycleManager().getApiManager().registerInternalService(QueueAdmissionPort.class, manager); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/meta/Meta.java deleted file mode 100644 index ffbf50a8..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/queue/meta/Meta.java +++ /dev/null @@ -1,22 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.queue.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - @Override - public String getFeatureName() { - return "Queue"; - } - - @Override - public String getFeatureVersion() { - return "2.0.0"; - } - - @Override - public List getDependencies() { - return List.of("Capacity"); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/ResourcePack.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/ResourcePack.java index 213ca584..8dc0555a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/ResourcePack.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/ResourcePack.java @@ -8,13 +8,12 @@ import nl.hauntedmc.proxyfeatures.features.resourcepack.internal.ResourcePackHandler; import nl.hauntedmc.proxyfeatures.features.resourcepack.listener.PlayerListener; import nl.hauntedmc.proxyfeatures.features.resourcepack.listener.ResourcePackStatusListener; -import nl.hauntedmc.proxyfeatures.features.resourcepack.meta.Meta; -public class ResourcePack extends VelocityBaseFeature { +public class ResourcePack extends VelocityBaseFeature { private ResourcePackHandler handler; - public ResourcePack(FeatureContext context) { + public ResourcePack(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/internal/ResourcePackHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/internal/ResourcePackHandler.java index 2c561c5a..88c1fe15 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/internal/ResourcePackHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/internal/ResourcePackHandler.java @@ -202,11 +202,6 @@ private void rememberPackIds(Map loadedPacks) { .build(); } - /** For legacy callers that didn’t specify force/prompt. Defaults: force=true, prompt_key=resourcepack.prompt */ - public @NotNull ResourcePackInfo buildPackInfo(String url, byte[] hash) { - return buildPackInfo(url, hash, true, "resourcepack.prompt"); - } - public ConfigurationToken blockConfiguration( Player player, ResourcePackInfo packInfo, diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/meta/Meta.java deleted file mode 100644 index c5be3677..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/resourcepack/meta/Meta.java +++ /dev/null @@ -1,17 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.resourcepack.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "ResourcePack"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/Restart.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/Restart.java index c661f4c9..68b348d4 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/Restart.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/Restart.java @@ -16,15 +16,13 @@ import nl.hauntedmc.proxyfeatures.features.restart.internal.RestartCapacityCoordinator; import nl.hauntedmc.proxyfeatures.features.restart.listener.BackendReconnectListener; import nl.hauntedmc.proxyfeatures.features.restart.messaging.RestartLifecycleBus; -import nl.hauntedmc.proxyfeatures.features.restart.meta.Meta; -import nl.hauntedmc.proxyfeatures.framework.service.InternalServices; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Optional; -public class Restart extends VelocityBaseFeature { +public class Restart extends VelocityBaseFeature { private static final String DEFAULT_RESTART_STREAM = "server.restart.lifecycle"; private static final String DEFAULT_CONSUMER_GROUP = "proxyfeatures.restart.autoreconnect"; private static final int MAX_CONSUMER_GROUP_LENGTH = 128; @@ -37,7 +35,7 @@ public class Restart extends VelocityBaseFeature { private BackendReconnectListener reconnectListener; private AutoreconnectCommand autoreconnectCommand; - public Restart(FeatureContext context) { + public Restart(FeatureContext context) { super(context); } @@ -145,7 +143,7 @@ public void initialize() { restartLifecycleBus.setCapacityCoordinator(capacityCoordinator); reconnectListener.setCapacityCoordinator(capacityCoordinator); autoreconnectCommand.setCapacityCoordinator(capacityCoordinator); - InternalServices.find(this, CapacityAPI.class).ifPresent(this::attachCapacity); + findInternalService(CapacityAPI.class).ifPresent(this::attachCapacity); String configuredStream = getConfigHandler().get("backend_autoreconnect.stream", String.class, DEFAULT_RESTART_STREAM); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/meta/Meta.java deleted file mode 100644 index ff1f01ad..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/restart/meta/Meta.java +++ /dev/null @@ -1,15 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.restart.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - @Override - public String getFeatureName() { - return "Restart"; - } - - @Override - public String getFeatureVersion() { - return "1.4.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/Sanctions.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/Sanctions.java index a832825a..97ffc1bc 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/Sanctions.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/Sanctions.java @@ -11,7 +11,6 @@ import nl.hauntedmc.proxyfeatures.features.sanctions.command.*; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.features.sanctions.listener.ConnectListener; -import nl.hauntedmc.proxyfeatures.features.sanctions.meta.Meta; import nl.hauntedmc.proxyfeatures.features.sanctions.service.DiscordService; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionsService; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionsCapability; @@ -20,7 +19,7 @@ import java.time.Duration; -public class Sanctions extends VelocityBaseFeature { +public class Sanctions extends VelocityBaseFeature { private ORMContext orm; private SanctionsService service; @@ -28,7 +27,7 @@ public class Sanctions extends VelocityBaseFeature { private DiscordService discordService; private SanctionsSecurityAuditLogService securityAuditLogService; - public Sanctions(FeatureContext context) { + public Sanctions(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/KickCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/KickCommand.java index dbcabfb6..a3b875ab 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/KickCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/KickCommand.java @@ -4,7 +4,7 @@ import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/SanctionListCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/SanctionListCommand.java index 731e6937..dd2924af 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/SanctionListCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/SanctionListCommand.java @@ -4,9 +4,9 @@ import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionType; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanCommand.java index 97ed88e7..3c4162c6 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanCommand.java @@ -4,7 +4,7 @@ import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionType; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionSuggestionLookup; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanIpCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanIpCommand.java index 3713807d..34973e68 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanIpCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnbanIpCommand.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.command.CommandSource; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import java.net.InetAddress; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnmuteCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnmuteCommand.java index 1bfc0979..6fef4836 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnmuteCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/UnmuteCommand.java @@ -4,7 +4,7 @@ import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionType; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionSuggestionLookup; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/WarnCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/WarnCommand.java index ca8ead8f..12ad3259 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/WarnCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/command/WarnCommand.java @@ -4,7 +4,7 @@ import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import java.util.*; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListener.java index 09a7d9a7..821f7041 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListener.java @@ -8,7 +8,7 @@ import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionsService; import nl.hauntedmc.proxyfeatures.framework.log.ConnectionLogHelper; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import java.net.InetAddress; import java.net.InetSocketAddress; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/meta/Meta.java deleted file mode 100644 index 3eb37412..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.sanctions.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "Sanctions"; - } - - @Override - public String getFeatureVersion() { - return "1.1.1"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordService.java index 93140036..b635277a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordService.java @@ -1,9 +1,9 @@ package nl.hauntedmc.proxyfeatures.features.sanctions.service; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.toolkit.util.http.SimpleHttpClient; -import nl.hauntedmc.proxyfeatures.toolkit.util.parse.JsonUtils; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.http.HttpTransport; +import nl.hauntedmc.proxyfeatures.toolkit.json.JsonStrings; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; @@ -20,7 +20,7 @@ interface WebhookTransport { private final WebhookTransport transport; public DiscordService(Sanctions feature) { - this(feature, SimpleHttpClient::postJsonHttps); + this(feature, HttpTransport::postJsonHttps); } DiscordService(Sanctions feature, WebhookTransport transport) { @@ -171,7 +171,7 @@ private String joinWithCommas(String[] parts) { } private String json(String s) { - return JsonUtils.escapeJson(s == null ? "" : s); + return JsonStrings.escapeJson(s == null ? "" : s); } private String nullToDash(String s) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionSuggestionLookup.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionSuggestionLookup.java index 3778a556..2589b1a1 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionSuggestionLookup.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionSuggestionLookup.java @@ -41,7 +41,7 @@ public static List activeTargetNames( Long.class) .setParameter("type", type) .list().stream() - .map(resolver::findActiveIdentityById) + .map(resolver::findIdentityById) .flatMap(java.util.Optional::stream) .map(identity -> identity.username()) .filter(Objects::nonNull) diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsCapability.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsCapability.java index eaff5cc2..1ebd1f1c 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsCapability.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsCapability.java @@ -1,5 +1,6 @@ package nl.hauntedmc.proxyfeatures.features.sanctions.service; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionFilter; import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionSnapshot; import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionsApi; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; @@ -19,8 +20,8 @@ public SanctionsCapability(SanctionsService service) { } @Override - public CompletionStage> find(UUID playerId, boolean activeOnly) { - return service.findSanctionsByUuid(playerId, activeOnly) + public CompletionStage> find(UUID playerId, SanctionFilter filter) { + return service.findSanctionsByUuid(playerId, filter == SanctionFilter.ACTIVE) .thenApply(entities -> entities.stream().map(entity -> snapshot(playerId, entity)).toList()); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsService.java index 2b6b5594..968c6829 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/SanctionsService.java @@ -3,7 +3,7 @@ import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; import nl.hauntedmc.dataregistry.api.player.PlayerData; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionType; @@ -103,8 +103,8 @@ private SanctionEntity createActive(SanctionType type, PlayerReference target, S String sanitized = sanitizeReason(reason); Instant now = Instant.now(); return feature.getOrm().runInTransaction(session -> { - PlayerReference managedTarget = target == null ? null : playerResolver.resolveManagedById(session, target.getId()); - PlayerReference managedActor = actor == null ? null : playerResolver.resolveManagedById(session, actor.getId()); + PlayerReference managedTarget = target == null ? null : playerResolver.resolveReferenceById(target.getId()); + PlayerReference managedActor = actor == null ? null : playerResolver.resolveReferenceById(actor.getId()); // Deactivate any pre-existing active sanctions of same type & target/IP in the same transaction if (managedTarget != null) { session.createMutationQuery( @@ -144,8 +144,8 @@ private void createInstant(SanctionType type, PlayerReference target, String ip, String sanitized = sanitizeReason(reason); Instant now = Instant.now(); feature.getOrm().runInTransaction(session -> { - PlayerReference managedTarget = target == null ? null : playerResolver.resolveManagedById(session, target.getId()); - PlayerReference managedActor = actor == null ? null : playerResolver.resolveManagedById(session, actor.getId()); + PlayerReference managedTarget = target == null ? null : playerResolver.resolveReferenceById(target.getId()); + PlayerReference managedActor = actor == null ? null : playerResolver.resolveReferenceById(actor.getId()); SanctionEntity s = new SanctionEntity(); s.setType(type); s.setTargetPlayer(managedTarget); @@ -514,7 +514,7 @@ private String resolveUsername(PlayerReference ref) { } Long id = ref.getId(); if (id == null) return null; - return playerResolver.findActiveIdentityById(id) + return playerResolver.findIdentityById(id) .map(nl.hauntedmc.dataregistry.api.player.PlayerIdentity::username) .orElse(null); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/ServerLinks.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/ServerLinks.java index 52e1eb4f..132d314b 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/ServerLinks.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/ServerLinks.java @@ -6,14 +6,13 @@ import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.serverlinks.internal.ServerLinksHandler; import nl.hauntedmc.proxyfeatures.features.serverlinks.listener.JoinListener; -import nl.hauntedmc.proxyfeatures.features.serverlinks.meta.Meta; -public class ServerLinks extends VelocityBaseFeature { +public class ServerLinks extends VelocityBaseFeature { private ServerLinksHandler serverLinksHandler; - public ServerLinks(FeatureContext context) { + public ServerLinks(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/meta/Meta.java deleted file mode 100644 index d0344051..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/serverlinks/meta/Meta.java +++ /dev/null @@ -1,17 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.serverlinks.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "ServerLinks"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/SlashServer.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/SlashServer.java index dfa25525..6fe66aac 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/SlashServer.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/SlashServer.java @@ -8,7 +8,6 @@ import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.slashserver.command.SlashServerAdminCommand; import nl.hauntedmc.proxyfeatures.features.slashserver.command.SlashServerCommand; -import nl.hauntedmc.proxyfeatures.features.slashserver.meta.Meta; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -19,11 +18,11 @@ import java.util.Optional; import java.util.Set; -public class SlashServer extends VelocityBaseFeature { +public class SlashServer extends VelocityBaseFeature { private final Set registeredShorthandCommands = new LinkedHashSet<>(); - public SlashServer(FeatureContext context) { + public SlashServer(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/meta/Meta.java deleted file mode 100644 index c6a585ad..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/slashserver/meta/Meta.java +++ /dev/null @@ -1,17 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.slashserver.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "SlashServer"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/StaffChat.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/StaffChat.java index 54c4e483..1a8b975f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/StaffChat.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/StaffChat.java @@ -8,14 +8,13 @@ import nl.hauntedmc.proxyfeatures.features.staffchat.internal.ChatChannelHandler; import nl.hauntedmc.proxyfeatures.features.staffchat.internal.messaging.EventBusHandler; import nl.hauntedmc.proxyfeatures.features.staffchat.listener.ConnectListener; -import nl.hauntedmc.proxyfeatures.features.staffchat.meta.Meta; -public class StaffChat extends VelocityBaseFeature { +public class StaffChat extends VelocityBaseFeature { private ChatChannelHandler chatChannelHandler; private EventBusHandler eventBusHandler; - public StaffChat(FeatureContext context) { + public StaffChat(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/internal/ChatChannel.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/internal/ChatChannel.java index 31fbe008..dbf64edb 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/internal/ChatChannel.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/internal/ChatChannel.java @@ -2,7 +2,7 @@ import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.TextFormatter; import nl.hauntedmc.proxyfeatures.features.staffchat.StaffChat; import java.util.Collections; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/listener/ConnectListener.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/listener/ConnectListener.java index 1d9ab695..c257109e 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/listener/ConnectListener.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/listener/ConnectListener.java @@ -6,7 +6,7 @@ import com.velocitypowered.api.event.player.ServerPostConnectEvent; import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.staffchat.StaffChat; import nl.hauntedmc.proxyfeatures.features.staffchat.internal.ChatChannel; import nl.hauntedmc.proxyfeatures.features.staffchat.internal.ChatChannelHandler; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/meta/Meta.java deleted file mode 100644 index 71379afd..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/staffchat/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.staffchat.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "StaffChat"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/TextCommands.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/TextCommands.java index 72ba4377..690f676f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/TextCommands.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/TextCommands.java @@ -5,20 +5,19 @@ import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.features.textcommands.command.TextCommand; -import nl.hauntedmc.proxyfeatures.features.textcommands.meta.Meta; import java.util.LinkedHashMap; import java.util.HashMap; import java.util.Map; -public class TextCommands extends VelocityBaseFeature { +public class TextCommands extends VelocityBaseFeature { /** * name -> command definition (message key + placeholders) */ private Map commands; - public TextCommands(FeatureContext context) { + public TextCommands(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/command/TextCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/command/TextCommand.java index 89d778a3..92b457df 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/command/TextCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/command/TextCommand.java @@ -4,7 +4,7 @@ import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; import nl.hauntedmc.proxyfeatures.framework.command.FeatureCommand; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.textcommands.TextCommands; import java.util.List; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/meta/Meta.java deleted file mode 100644 index 7b7662de..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/textcommands/meta/Meta.java +++ /dev/null @@ -1,17 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.textcommands.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "TextCommands"; - } - - @Override - public String getFeatureVersion() { - return "1.1.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactor.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactor.java index 92f15c8f..6f0c7892 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactor.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactor.java @@ -15,7 +15,6 @@ import nl.hauntedmc.proxyfeatures.features.twofactor.crypto.TotpService; import nl.hauntedmc.proxyfeatures.features.twofactor.crypto.TwoFactorCrypto; import nl.hauntedmc.proxyfeatures.features.twofactor.listener.TwoFactorListener; -import nl.hauntedmc.proxyfeatures.features.twofactor.meta.Meta; import nl.hauntedmc.proxyfeatures.features.twofactor.persistence.OrmTwoFactorAccountStore; import nl.hauntedmc.proxyfeatures.features.twofactor.persistence.PlayerTwoFactorEntity; import nl.hauntedmc.proxyfeatures.features.twofactor.persistence.TwoFactorAccountStore; @@ -44,7 +43,7 @@ * secure operating mode requires a dedicated lock server where unauthenticated * players are held until they complete {@code /2fa }. */ -public class TwoFactor extends VelocityBaseFeature { +public class TwoFactor extends VelocityBaseFeature { private static final Duration LOCK_PROMPT_DELAY = Duration.ofMillis(750); @@ -54,7 +53,7 @@ public class TwoFactor extends VelocityBaseFeature { private final Set lockTransfersInFlight = ConcurrentHashMap.newKeySet(); private final ConcurrentHashMap trustedExpiryTasks = new ConcurrentHashMap<>(); - public TwoFactor(FeatureContext context) { + public TwoFactor(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/meta/Meta.java deleted file mode 100644 index 0e50f64b..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/meta/Meta.java +++ /dev/null @@ -1,21 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.twofactor.meta; - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "TwoFactor"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } - - @Override - public java.util.List getPluginDependencies() { - return java.util.List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/persistence/OrmTwoFactorAccountStore.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/persistence/OrmTwoFactorAccountStore.java index 3c732005..bae26ec7 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/persistence/OrmTwoFactorAccountStore.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/twofactor/persistence/OrmTwoFactorAccountStore.java @@ -61,7 +61,7 @@ public List listKnownUsernames() { Long.class ) .list().stream() - .map(playerResolver::findActiveIdentityById) + .map(playerResolver::findIdentityById) .flatMap(Optional::stream) .map(identity -> identity.username()) .sorted(String.CASE_INSENSITIVE_ORDER) @@ -141,7 +141,7 @@ private StoredAccount toStoredAccount(PlayerTwoFactorEntity entity, UUID uuid, S } private PlayerReference findPlayer(Session session, UUID uuid) { - return playerResolver.resolveManaged(session, uuid); + return playerResolver.resolveReference(uuid); } private static String normalizeIdentifier(String identifier) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/Vanish.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/Vanish.java index 0826e4db..30fc93db 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/Vanish.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/Vanish.java @@ -13,14 +13,13 @@ import nl.hauntedmc.proxyfeatures.features.vanish.internal.VanishRegistry; import nl.hauntedmc.proxyfeatures.features.vanish.internal.messaging.EventBusHandler; import nl.hauntedmc.proxyfeatures.features.vanish.listener.ConnectListener; -import nl.hauntedmc.proxyfeatures.features.vanish.meta.Meta; import java.util.Map; import java.util.Locale; import java.util.Optional; import java.util.UUID; -public class Vanish extends VelocityBaseFeature implements StatefulFeature { +public class Vanish extends VelocityBaseFeature implements StatefulFeature { private static final String DEFAULT_STREAM = "proxy.vanish.update"; private static final String DEFAULT_CONSUMER_GROUP = "proxyfeatures.vanish.proxy"; @@ -29,7 +28,7 @@ public class Vanish extends VelocityBaseFeature implements StatefulFeature private EventBusHandler eventBusHandler; private PresenceService presenceService; - public Vanish(FeatureContext context) { + public Vanish(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/meta/Meta.java deleted file mode 100644 index dff7510a..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/vanish/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.vanish.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "Vanish"; - } - - @Override - public String getFeatureVersion() { - return "1.0.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/VersionCheck.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/VersionCheck.java index 32818b63..1bd336b4 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/VersionCheck.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/VersionCheck.java @@ -9,16 +9,15 @@ import nl.hauntedmc.proxyfeatures.features.versioncheck.audit.VersionAuditLogService; import nl.hauntedmc.proxyfeatures.features.versioncheck.internal.VersionHandler; import nl.hauntedmc.proxyfeatures.features.versioncheck.listener.ConnectionListener; -import nl.hauntedmc.proxyfeatures.features.versioncheck.meta.Meta; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReferenceResolver; import nl.hauntedmc.proxyfeatures.api.capability.operations.VersionApi; -public class VersionCheck extends VelocityBaseFeature { +public class VersionCheck extends VelocityBaseFeature { private VersionHandler versionHandler; private VersionAuditLogService auditLogService; - public VersionCheck(FeatureContext context) { + public VersionCheck(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/meta/Meta.java deleted file mode 100644 index f8559ecd..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/versioncheck/meta/Meta.java +++ /dev/null @@ -1,17 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.versioncheck.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "VersionCheck"; - } - - @Override - public String getFeatureVersion() { - return "1.1.0"; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/Votifier.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/Votifier.java index 83759d13..fc9f5f93 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/Votifier.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/Votifier.java @@ -17,19 +17,18 @@ import nl.hauntedmc.proxyfeatures.features.votifier.internal.VotifierService; import nl.hauntedmc.proxyfeatures.features.votifier.listener.VotifierPlayerListener; import nl.hauntedmc.proxyfeatures.features.votifier.messaging.TargetedVoteTestPublisher; -import nl.hauntedmc.proxyfeatures.features.votifier.meta.Meta; import nl.hauntedmc.proxyfeatures.framework.persistence.DataRegistryIdentityGate; import java.util.List; import java.util.concurrent.CompletableFuture; -public class Votifier extends VelocityBaseFeature { +public class Votifier extends VelocityBaseFeature { private volatile VotifierService service; private volatile VotifierSecurityAuditLogService securityAuditLogService; private volatile TargetedVoteTestPublisher targetedVoteTestPublisher; - public Votifier(FeatureContext context) { + public Votifier(FeatureContext context) { super(context); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsService.java index 31036640..918cce1a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/internal/VoteStatsService.java @@ -309,7 +309,7 @@ public List winnersLeaderboard(int limit) { } private String displayName(long playerId) { - return playerResolver.findActiveIdentityById(playerId) + return playerResolver.findIdentityById(playerId) .map(identity -> identity.username()) .orElse("#" + playerId); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/meta/Meta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/meta/Meta.java deleted file mode 100644 index bb9cc9a3..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/features/votifier/meta/Meta.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features.votifier.meta; - - -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; - -import java.util.List; - -public class Meta implements BaseMeta { - - @Override - public String getFeatureName() { - return "Votifier"; - } - - @Override - public String getFeatureVersion() { - return "1.6.0"; - } - - @Override - public List getPluginDependencies() { - return List.of(DATA_PROVIDER, DATA_REGISTRY); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommand.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommand.java index a8f27f4f..d0554a15 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommand.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommand.java @@ -170,7 +170,7 @@ void handleInfo(CommandSource sender, String featureName) { var reg = plugin.getFeatureLoadManager().getFeatureRegistry(); // Direct lookup (exact) among loaded - VelocityBaseFeature loaded = reg.getLoadedFeature(featureName); + VelocityBaseFeature loaded = reg.getLoadedFeature(featureName); // Case-insensitive fallback among loaded if (loaded == null) { @@ -410,12 +410,12 @@ void handleReloadLocal(CommandSource sender, String feature) { } void sendPluginStatus(CommandSource sender) { - List> loaded = plugin.getFeatureLoadManager().getFeatureRegistry().getLoadedFeatures(); + List loaded = plugin.getFeatureLoadManager().getFeatureRegistry().getLoadedFeatures(); List cmds = new ArrayList<>(); int loadedCount = loaded.size(); int tasks = 0, listeners = 0, commands = 0, conns = 0; - for (VelocityBaseFeature f : loaded) { + for (VelocityBaseFeature f : loaded) { var registered = f.getLifecycleManager().getCommandManager().getRegisteredCommands(); commands += (registered != null ? registered.size() : 0); if (registered != null) cmds.addAll(registered.keySet()); @@ -436,7 +436,7 @@ void sendPluginStatus(CommandSource sender) { /* ============================ Lists & Rendering ============================ */ void listLoadedFeaturesOneLine(CommandSource sender, boolean withVersion) { - List> loaded = new ArrayList<>(plugin.getFeatureLoadManager() + List loaded = new ArrayList<>(plugin.getFeatureLoadManager() .getFeatureRegistry().getLoadedFeatures()); // Alphabetize by feature name (case-insensitive, null-safe) @@ -452,7 +452,7 @@ void listLoadedFeaturesOneLine(CommandSource sender, boolean withVersion) { Component list = Component.empty(); for (int i = 0; i < loaded.size(); i++) { - VelocityBaseFeature f = loaded.get(i); + VelocityBaseFeature f = loaded.get(i); String name = Objects.toString(f.getFeatureName(), "?"); String version = Objects.toString(f.getFeatureVersion(), "?"); diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactory.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactory.java index 22815d10..1a439a65 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactory.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactory.java @@ -1,8 +1,8 @@ package nl.hauntedmc.proxyfeatures.framework.feature; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import nl.hauntedmc.proxyfeatures.features.FeatureContext; +import nl.hauntedmc.proxyfeatures.framework.loader.FeatureDescriptor; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.config.FeatureStoragePaths; import nl.hauntedmc.proxyfeatures.framework.config.MainConfigHandler; @@ -11,6 +11,7 @@ import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; +import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -53,13 +54,16 @@ public FeatureScopeFactory( this.loggerFactory = Objects.requireNonNull(loggerFactory, "loggerFactory"); } - public FeatureContext createContext(T meta) { - Objects.requireNonNull(meta, "meta"); - FeatureScope scope = getScope(meta.getFeatureName()); - FeatureLifecycleManager lifecycleManager = lifecycleManagerFactory.apply(meta.getFeatureName()); - return new FeatureContext<>( + public FeatureContext createContext(FeatureDescriptor descriptor) { + Objects.requireNonNull(descriptor, "descriptor"); + FeatureScope scope = getScope(descriptor.featureName()); + FeatureLifecycleManager lifecycleManager = lifecycleManagerFactory.apply(descriptor.registryName()); + return new FeatureContext( plugin, - meta, + descriptor.featureName(), + descriptor.featureVersion(), + List.copyOf(descriptor.featureDependencies()), + List.copyOf(descriptor.pluginDependencies()), scope.configHandler(), lifecycleManager, scope.logger(), diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/meta/BaseMeta.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/meta/BaseMeta.java deleted file mode 100644 index 7dcb41df..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/feature/meta/BaseMeta.java +++ /dev/null @@ -1,20 +0,0 @@ -package nl.hauntedmc.proxyfeatures.framework.feature.meta; - -import java.util.List; - -public interface BaseMeta { - String DATA_PROVIDER = "dataprovider"; - String DATA_REGISTRY = "dataregistry"; - - String getFeatureName(); - - String getFeatureVersion(); - - default List getDependencies() { - return List.of(); - } - - default List getPluginDependencies() { - return List.of(); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeatures.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeatures.java index eec5f771..efd726bc 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeatures.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeatures.java @@ -1,58 +1,234 @@ package nl.hauntedmc.proxyfeatures.framework.loader; import nl.hauntedmc.proxyfeatures.api.feature.FeatureClassification; +import nl.hauntedmc.proxyfeatures.api.extension.MotdExtensions; +import nl.hauntedmc.proxyfeatures.features.announcer.Announcer; +import nl.hauntedmc.proxyfeatures.features.antibot.AntiBot; +import nl.hauntedmc.proxyfeatures.features.antivpn.AntiVPN; +import nl.hauntedmc.proxyfeatures.features.broadcast.Broadcast; +import nl.hauntedmc.proxyfeatures.features.capacity.Capacity; +import nl.hauntedmc.proxyfeatures.features.clientinfo.ClientInfo; +import nl.hauntedmc.proxyfeatures.features.commandhider.CommandHider; +import nl.hauntedmc.proxyfeatures.features.commandlogger.CommandLogger; +import nl.hauntedmc.proxyfeatures.features.commandrelay.CommandRelay; +import nl.hauntedmc.proxyfeatures.features.connectioninfo.ConnectionInfo; +import nl.hauntedmc.proxyfeatures.features.friends.Friends; +import nl.hauntedmc.proxyfeatures.features.hlink.HLink; +import nl.hauntedmc.proxyfeatures.features.hub.Hub; +import nl.hauntedmc.proxyfeatures.features.maintenance.Maintenance; +import nl.hauntedmc.proxyfeatures.features.messager.Messenger; +import nl.hauntedmc.proxyfeatures.features.motd.Motd; +import nl.hauntedmc.proxyfeatures.features.playercount.PlayerCount; +import nl.hauntedmc.proxyfeatures.features.playerinfo.PlayerInfo; +import nl.hauntedmc.proxyfeatures.features.playerlanguage.PlayerLanguage; +import nl.hauntedmc.proxyfeatures.features.playerlist.PlayerList; +import nl.hauntedmc.proxyfeatures.features.proxyinfo.ProxyInfo; +import nl.hauntedmc.proxyfeatures.features.queue.Queue; +import nl.hauntedmc.proxyfeatures.features.resourcepack.ResourcePack; +import nl.hauntedmc.proxyfeatures.features.restart.Restart; +import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; +import nl.hauntedmc.proxyfeatures.features.serverlinks.ServerLinks; +import nl.hauntedmc.proxyfeatures.features.slashserver.SlashServer; +import nl.hauntedmc.proxyfeatures.features.staffchat.StaffChat; +import nl.hauntedmc.proxyfeatures.features.textcommands.TextCommands; +import nl.hauntedmc.proxyfeatures.features.twofactor.TwoFactor; +import nl.hauntedmc.proxyfeatures.features.vanish.Vanish; +import nl.hauntedmc.proxyfeatures.features.versioncheck.VersionCheck; +import nl.hauntedmc.proxyfeatures.features.votifier.Votifier; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionApi; +import nl.hauntedmc.proxyfeatures.api.capability.social.FriendshipApi; +import nl.hauntedmc.proxyfeatures.api.capability.operations.MaintenanceApi; +import nl.hauntedmc.proxyfeatures.api.capability.player.NetworkLocationApi; +import nl.hauntedmc.proxyfeatures.api.capability.network.PlayerCountApi; +import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; +import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; +import nl.hauntedmc.proxyfeatures.api.capability.queue.QueueApi; +import nl.hauntedmc.proxyfeatures.api.capability.operations.RestartApi; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionsApi; +import nl.hauntedmc.proxyfeatures.api.capability.operations.TwoFactorApi; +import nl.hauntedmc.proxyfeatures.api.capability.operations.VersionApi; +import nl.hauntedmc.proxyfeatures.features.FeatureContext; +import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.function.Function; -/** Explicit manifest of every built-in feature shipped by ProxyFeatures 3.3.0. */ +/** Authoritative typed manifest of every built-in feature shipped by ProxyFeatures 3.3.0. */ public final class BuiltInFeatures { - private static final String ROOT = "nl.hauntedmc.proxyfeatures.features."; + + public enum StartupPhase { + FOUNDATION, + SECURITY, + DOMAIN, + CONTENT, + OPERATIONS, + INTEGRATION + } + + public enum FailurePolicy { + ISOLATE_FEATURE + } + + public enum ReloadSupport { + FULL + } public record Definition( - String featureClassName, + String featureName, + String featureVersion, + Class implementationType, + Function constructor, + StartupPhase startupPhase, FeatureClassification classification, - Set providedCapabilities + Set> requiredCapabilities, + Set> optionalCapabilities, + Set> providedCapabilities, + Set pluginDependencies, + ReloadSupport reloadSupport, + FailurePolicy failurePolicy ) { - public Definition(String featureClassName, FeatureClassification classification, String... capabilities) { - this(featureClassName, classification, Set.of(capabilities)); + public Definition { + featureName = requireText(featureName, "featureName"); + featureVersion = requireText(featureVersion, "featureVersion"); + Objects.requireNonNull(implementationType, "implementationType"); + Objects.requireNonNull(constructor, "constructor"); + Objects.requireNonNull(startupPhase, "startupPhase"); + Objects.requireNonNull(classification, "classification"); + requiredCapabilities = immutableCapabilities(requiredCapabilities, "requiredCapabilities"); + optionalCapabilities = immutableCapabilities(optionalCapabilities, "optionalCapabilities"); + providedCapabilities = immutableCapabilities(providedCapabilities, "providedCapabilities"); + pluginDependencies = immutableText(pluginDependencies, "pluginDependencies"); + Objects.requireNonNull(reloadSupport, "reloadSupport"); + Objects.requireNonNull(failurePolicy, "failurePolicy"); + + ensureDisjoint(requiredCapabilities, optionalCapabilities, "required", "optional"); + ensureDisjoint(requiredCapabilities, providedCapabilities, "required", "provided"); + ensureDisjoint(optionalCapabilities, providedCapabilities, "optional", "provided"); + validateClassification(classification, requiredCapabilities, optionalCapabilities, providedCapabilities); + } + + public FeatureDescriptor descriptor(Set featureDependencies) { + return new FeatureDescriptor( + featureName, + featureName, + featureVersion, + implementationType, + constructor, + featureDependencies, + pluginDependencies + ); } } private static final List DEFINITIONS = List.of( - internal("announcer", "Announcer"), - internal("antibot", "AntiBot"), - provider("antivpn", "AntiVPN", "NetworkLocationApi"), - internal("broadcast", "Broadcast"), - provider("capacity", "Capacity", "AdmissionApi"), - internal("clientinfo", "ClientInfo"), - internal("commandhider", "CommandHider"), - internal("commandlogger", "CommandLogger"), - internal("commandrelay", "CommandRelay"), - internal("connectioninfo", "ConnectionInfo"), - provider("friends", "Friends", "FriendshipApi"), - internal("hlink", "HLink"), - internal("hub", "Hub"), - provider("maintenance", "Maintenance", "MaintenanceApi"), - consumer("messager", "Messenger"), - consumer("motd", "Motd"), - provider("playercount", "PlayerCount", "PlayerCountApi"), - consumer("playerinfo", "PlayerInfo"), - provider("playerlanguage", "PlayerLanguage", "PlayerLanguageApi"), - consumer("playerlist", "PlayerList"), - internal("proxyinfo", "ProxyInfo"), - provider("queue", "Queue", "QueueApi"), - internal("resourcepack", "ResourcePack"), - provider("restart", "Restart", "RestartApi"), - provider("sanctions", "Sanctions", "SanctionsApi"), - internal("serverlinks", "ServerLinks"), - internal("slashserver", "SlashServer"), - internal("staffchat", "StaffChat"), - internal("textcommands", "TextCommands"), - provider("twofactor", "TwoFactor", "TwoFactorApi"), - provider("vanish", "Vanish", "PresenceApi"), - provider("versioncheck", "VersionCheck", "VersionApi"), - internal("votifier", "Votifier") + feature("Announcer", "2.0.0", Announcer.class, Announcer::new, + StartupPhase.INTEGRATION, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of("dataprovider", "dataregistry")), + feature("AntiBot", "1.0.0", AntiBot.class, AntiBot::new, + StartupPhase.SECURITY, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of()), + feature("AntiVPN", "1.1.0", AntiVPN.class, AntiVPN::new, + StartupPhase.SECURITY, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(), Set.of(), Set.of(NetworkLocationApi.class), Set.of("dataprovider", "dataregistry")), + feature("Broadcast", "1.0.0", Broadcast.class, Broadcast::new, + StartupPhase.CONTENT, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of()), + feature("Capacity", "1.4.0", Capacity.class, Capacity::new, + StartupPhase.FOUNDATION, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(), Set.of(MaintenanceApi.class, RestartApi.class, TwoFactorApi.class), + Set.of(AdmissionApi.class), Set.of()), + feature("ClientInfo", "1.1.0", ClientInfo.class, ClientInfo::new, + StartupPhase.DOMAIN, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of("dataprovider", "dataregistry")), + feature("CommandHider", "1.1.0", CommandHider.class, CommandHider::new, + StartupPhase.DOMAIN, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of()), + feature("CommandLogger", "1.0.0", CommandLogger.class, CommandLogger::new, + StartupPhase.INTEGRATION, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of("dataprovider", "dataregistry")), + feature("CommandRelay", "1.0.0", CommandRelay.class, CommandRelay::new, + StartupPhase.INTEGRATION, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of("dataprovider")), + feature("ConnectionInfo", "1.0.0", ConnectionInfo.class, ConnectionInfo::new, + StartupPhase.DOMAIN, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of()), + feature("Friends", "1.4.0", Friends.class, Friends::new, + StartupPhase.DOMAIN, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(), Set.of(PresenceApi.class), Set.of(FriendshipApi.class), + Set.of("dataprovider", "dataregistry")), + feature("HLink", "1.1.0", HLink.class, HLink::new, + StartupPhase.INTEGRATION, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of("luckperms")), + feature("Hub", "1.0.0", Hub.class, Hub::new, + StartupPhase.DOMAIN, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of()), + feature("Maintenance", "1.0.0", Maintenance.class, Maintenance::new, + StartupPhase.OPERATIONS, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(MotdExtensions.class), Set.of(TwoFactorApi.class), + Set.of(MaintenanceApi.class), Set.of()), + feature("Messenger", "1.1.0", Messenger.class, Messenger::new, + StartupPhase.DOMAIN, FeatureClassification.CAPABILITY_CONSUMER, + Set.of(), Set.of(FriendshipApi.class, PresenceApi.class), Set.of(), + Set.of("dataprovider", "dataregistry")), + feature("Motd", "1.2.0", Motd.class, Motd::new, + StartupPhase.CONTENT, FeatureClassification.CAPABILITY_CONSUMER, + Set.of(), Set.of(PresenceApi.class, VersionApi.class), Set.of(), Set.of()), + feature("PlayerCount", "1.0.0", PlayerCount.class, PlayerCount::new, + StartupPhase.DOMAIN, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(), Set.of(PresenceApi.class), Set.of(PlayerCountApi.class), Set.of("dataprovider")), + feature("PlayerInfo", "1.0.0", PlayerInfo.class, PlayerInfo::new, + StartupPhase.DOMAIN, FeatureClassification.CAPABILITY_CONSUMER, + Set.of(), Set.of(PlayerLanguageApi.class, SanctionsApi.class), Set.of(), + Set.of("dataprovider", "dataregistry")), + feature("PlayerLanguage", "1.0.0", PlayerLanguage.class, PlayerLanguage::new, + StartupPhase.DOMAIN, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(), Set.of(NetworkLocationApi.class), Set.of(PlayerLanguageApi.class), + Set.of("dataprovider", "dataregistry")), + feature("PlayerList", "1.1.0", PlayerList.class, PlayerList::new, + StartupPhase.DOMAIN, FeatureClassification.CAPABILITY_CONSUMER, + Set.of(), Set.of(PresenceApi.class), Set.of(), Set.of()), + feature("ProxyInfo", "1.0.0", ProxyInfo.class, ProxyInfo::new, + StartupPhase.OPERATIONS, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of()), + feature("Queue", "2.0.0", Queue.class, Queue::new, + StartupPhase.FOUNDATION, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(AdmissionApi.class), Set.of(), Set.of(QueueApi.class), Set.of()), + feature("ResourcePack", "1.0.0", ResourcePack.class, ResourcePack::new, + StartupPhase.CONTENT, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of()), + feature("Restart", "1.4.0", Restart.class, Restart::new, + StartupPhase.OPERATIONS, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(), Set.of(), Set.of(RestartApi.class), Set.of()), + feature("Sanctions", "1.1.1", Sanctions.class, Sanctions::new, + StartupPhase.SECURITY, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(), Set.of(), Set.of(SanctionsApi.class), Set.of("dataprovider", "dataregistry")), + feature("ServerLinks", "1.0.0", ServerLinks.class, ServerLinks::new, + StartupPhase.DOMAIN, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of()), + feature("SlashServer", "1.0.0", SlashServer.class, SlashServer::new, + StartupPhase.DOMAIN, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of()), + feature("StaffChat", "1.0.0", StaffChat.class, StaffChat::new, + StartupPhase.DOMAIN, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of("dataprovider")), + feature("TextCommands", "1.1.0", TextCommands.class, TextCommands::new, + StartupPhase.CONTENT, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of()), + feature("TwoFactor", "1.0.0", TwoFactor.class, TwoFactor::new, + StartupPhase.SECURITY, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(), Set.of(), Set.of(TwoFactorApi.class), Set.of("dataprovider", "dataregistry")), + feature("Vanish", "1.0.0", Vanish.class, Vanish::new, + StartupPhase.DOMAIN, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(), Set.of(), Set.of(PresenceApi.class), Set.of("dataprovider")), + feature("VersionCheck", "1.1.0", VersionCheck.class, VersionCheck::new, + StartupPhase.OPERATIONS, FeatureClassification.CAPABILITY_PROVIDER, + Set.of(), Set.of(), Set.of(VersionApi.class), Set.of()), + feature("Votifier", "1.6.0", Votifier.class, Votifier::new, + StartupPhase.INTEGRATION, FeatureClassification.INTERNAL, + Set.of(), Set.of(), Set.of(), Set.of("dataprovider", "dataregistry")) ); private BuiltInFeatures() { @@ -62,15 +238,112 @@ public static List definitions() { return DEFINITIONS; } - private static Definition provider(String path, String type, String... capabilities) { - return new Definition(ROOT + path + '.' + type, FeatureClassification.CAPABILITY_PROVIDER, capabilities); + private static Definition feature( + String name, + String version, + Class implementationType, + Function constructor, + StartupPhase startupPhase, + FeatureClassification classification, + Set> requiredCapabilities, + Set> optionalCapabilities, + Set> providedCapabilities, + Set pluginDependencies + ) { + return new Definition( + name, + version, + implementationType, + constructor, + startupPhase, + classification, + requiredCapabilities, + optionalCapabilities, + providedCapabilities, + pluginDependencies, + ReloadSupport.FULL, + FailurePolicy.ISOLATE_FEATURE + ); + } + + + private static void validateClassification( + FeatureClassification classification, + Set> requiredCapabilities, + Set> optionalCapabilities, + Set> providedCapabilities + ) { + switch (classification) { + case CAPABILITY_PROVIDER -> { + if (providedCapabilities.isEmpty()) { + throw new IllegalArgumentException("Capability providers must declare a provided capability"); + } + } + case EXTENSION_PROVIDER -> { + if (providedCapabilities.isEmpty()) { + throw new IllegalArgumentException("Extension providers must declare a provided extension contract"); + } + } + case CAPABILITY_CONSUMER -> { + if (requiredCapabilities.isEmpty() && optionalCapabilities.isEmpty()) { + throw new IllegalArgumentException("Capability consumers must declare a consumed capability"); + } + if (!providedCapabilities.isEmpty()) { + throw new IllegalArgumentException("Capability consumers cannot declare provided capabilities"); + } + } + case INTERNAL -> { + if (!providedCapabilities.isEmpty()) { + throw new IllegalArgumentException("Internal features cannot declare public capabilities"); + } + } + } } - private static Definition consumer(String path, String type) { - return new Definition(ROOT + path + '.' + type, FeatureClassification.CAPABILITY_CONSUMER); + private static void ensureDisjoint( + Set> left, + Set> right, + String leftName, + String rightName + ) { + for (Class capability : left) { + if (right.contains(capability)) { + throw new IllegalArgumentException( + "Capability cannot be both " + leftName + " and " + rightName + ": " + capability.getName() + ); + } + } } - private static Definition internal(String path, String type) { - return new Definition(ROOT + path + '.' + type, FeatureClassification.INTERNAL); + private static Set> immutableCapabilities(Set> values, String fieldName) { + Objects.requireNonNull(values, fieldName); + LinkedHashSet> copy = new LinkedHashSet<>(); + for (Class value : values) { + Class capability = Objects.requireNonNull(value, fieldName + " element"); + if (!capability.isInterface() + || !capability.getPackageName().startsWith("nl.hauntedmc.proxyfeatures.api.")) { + throw new IllegalArgumentException("Invalid public capability: " + capability.getName()); + } + copy.add(capability); + } + return Set.copyOf(copy); + } + + private static Set immutableText(Set values, String fieldName) { + Objects.requireNonNull(values, fieldName); + LinkedHashSet copy = new LinkedHashSet<>(); + for (String value : values) { + copy.add(requireText(value, fieldName + " element")); + } + return Set.copyOf(copy); + } + + private static String requireText(String value, String fieldName) { + Objects.requireNonNull(value, fieldName); + String clean = value.trim(); + if (clean.isEmpty()) { + throw new IllegalArgumentException(fieldName + " must not be blank"); + } + return clean; } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptor.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptor.java index adf2fe13..abe58f3a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptor.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptor.java @@ -1,151 +1,100 @@ package nl.hauntedmc.proxyfeatures.framework.loader; -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.features.FeatureContext; +import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import java.util.Collections; import java.util.LinkedHashSet; -import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.function.Function; + +/** Fully explicit runtime descriptor; no package scanning or reflective construction is involved. */ +public final class FeatureDescriptor { + private final String registryName; + private final String featureName; + private final String featureVersion; + private final Class implementationType; + private final Function constructor; + private final Set featureDependencies; + private final Set pluginDependencies; -public record FeatureDescriptor( - String registryName, - String featureClassName, - Class metaClass, - String featureName, - String featureVersion, - Set featureDependencies, - Set pluginDependencies -) { public FeatureDescriptor( String registryName, - String featureClassName, String featureName, String featureVersion, + Class implementationType, + Function constructor, Set featureDependencies, Set pluginDependencies ) { - this( - registryName, - featureClassName, - (Class) null, - featureName, - featureVersion, - featureDependencies, - pluginDependencies - ); - } - - public FeatureDescriptor( - String registryName, - String featureClassName, - BaseMeta meta, - String featureName, - String featureVersion, - Set featureDependencies, - Set pluginDependencies - ) { - this( - registryName, - featureClassName, - meta == null ? null : meta.getClass().asSubclass(BaseMeta.class), - featureName, - featureVersion, - featureDependencies, - pluginDependencies - ); - } - - public FeatureDescriptor( - String registryName, - String featureClassName, - Class metaClass, - String featureName, - String featureVersion, - Set featureDependencies, - Set pluginDependencies - ) { - this.registryName = registryName; - this.featureClassName = featureClassName; - this.featureName = featureName == null ? "" : featureName; - this.featureVersion = featureVersion == null ? "" : featureVersion; + this.registryName = requireText(registryName, "registryName"); + this.featureName = requireText(featureName, "featureName"); + this.featureVersion = requireText(featureVersion, "featureVersion"); + this.implementationType = Objects.requireNonNull(implementationType, "implementationType"); + this.constructor = Objects.requireNonNull(constructor, "constructor"); this.featureDependencies = normalizeDependencies(featureDependencies, registryName); this.pluginDependencies = normalizeDependencies(pluginDependencies, null); - this.metaClass = metaClass; } - public BaseMeta createMeta() { - if (metaClass == null) { - return createStaticMeta(); - } - - try { - return metaClass.getDeclaredConstructor().newInstance(); - } catch (ReflectiveOperationException | LinkageError ignored) { - return createStaticMeta(); - } + public String registryName() { + return registryName; } - private BaseMeta createStaticMeta() { - return new StaticMeta(featureName, featureVersion, featureDependencies, pluginDependencies); + public String featureName() { + return featureName; } - private static Set normalizeDependencies(Set dependencies, String selfDependencyName) { - if (dependencies == null || dependencies.isEmpty()) { - return Set.of(); - } - - LinkedHashSet normalized = new LinkedHashSet<>(); - for (String dependency : dependencies) { - if (dependency == null) { - continue; - } - - String clean = dependency.trim(); - if (clean.isEmpty()) { - continue; - } + public String featureVersion() { + return featureVersion; + } - if (selfDependencyName != null && clean.equalsIgnoreCase(selfDependencyName)) { - continue; - } + public Class implementationType() { + return implementationType; + } - normalized.add(clean); - } + public Set featureDependencies() { + return featureDependencies; + } - if (normalized.isEmpty()) { - return Set.of(); - } - return Collections.unmodifiableSet(normalized); + public Set pluginDependencies() { + return pluginDependencies; } - private record StaticMeta( - String featureName, - String featureVersion, - List dependencies, - List pluginDependencies - ) implements BaseMeta { - private StaticMeta(String featureName, String featureVersion, Set dependencies, Set pluginDependencies) { - this(featureName, featureVersion, List.copyOf(dependencies), List.copyOf(pluginDependencies)); + public VelocityBaseFeature create(FeatureContext context) { + VelocityBaseFeature feature = constructor.apply(Objects.requireNonNull(context, "context")); + if (feature == null) { + throw new IllegalStateException("Feature constructor returned null: " + implementationType.getName()); } - - @Override - public String getFeatureName() { - return featureName; + if (!implementationType.isInstance(feature)) { + throw new IllegalStateException( + "Feature constructor returned " + feature.getClass().getName() + + " instead of " + implementationType.getName() + ); } + return feature; + } - @Override - public String getFeatureVersion() { - return featureVersion; + private static String requireText(String value, String fieldName) { + Objects.requireNonNull(value, fieldName); + String clean = value.trim(); + if (clean.isEmpty()) { + throw new IllegalArgumentException(fieldName + " must not be blank"); } + return clean; + } - @Override - public List getDependencies() { - return dependencies; + private static Set normalizeDependencies(Set dependencies, String selfDependencyName) { + if (dependencies == null || dependencies.isEmpty()) { + return Set.of(); } - - @Override - public List getPluginDependencies() { - return pluginDependencies; + LinkedHashSet normalized = new LinkedHashSet<>(); + for (String dependency : dependencies) { + String clean = requireText(dependency, "dependency"); + if (selfDependencyName == null || !clean.equalsIgnoreCase(selfDependencyName)) { + normalized.add(clean); + } } + return normalized.isEmpty() ? Set.of() : Collections.unmodifiableSet(normalized); } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManager.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManager.java index d7d38401..3c7ea13f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManager.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManager.java @@ -5,10 +5,8 @@ import nl.hauntedmc.proxyfeatures.api.feature.FeatureState; import nl.hauntedmc.proxyfeatures.framework.feature.stateful.SnapshotState; import nl.hauntedmc.proxyfeatures.framework.feature.stateful.StatefulFeature; -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import nl.hauntedmc.proxyfeatures.framework.feature.FeatureScopeFactory; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.features.FeatureFactory; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.framework.config.MainConfigHandler; import nl.hauntedmc.proxyfeatures.framework.loader.dependency.DependencyCheckResult; @@ -49,40 +47,100 @@ public FeatureLoadManager(ProxyFeatures plugin, FeatureScopeFactory featureScope private void discoverFeatures() { plugin.getLogger().info("Loading the explicit built-in feature manifest..."); - for (BuiltInFeatures.Definition definition : BuiltInFeatures.definitions()) { - String featureClassName = definition.featureClassName(); - String registryName = simpleClassName(featureClassName); - Optional descriptorOptional = buildDescriptor(registryName, featureClassName); - if (descriptorOptional.isEmpty()) { - continue; - } - FeatureDescriptor descriptor = descriptorOptional.get(); - String conflictingKey = findCaseInsensitiveMatch( + Map, BuiltInFeatures.Definition> providers = capabilityProviders(); + validateCapabilityReferences(providers); + List definitions = new ArrayList<>(BuiltInFeatures.definitions()); + definitions.sort(Comparator.comparing(BuiltInFeatures.Definition::startupPhase)); + + for (BuiltInFeatures.Definition definition : definitions) { + Set dependencies = resolveFeatureDependencies(definition, providers); + if (dependencies == null) { + continue; + } + + FeatureDescriptor descriptor = definition.descriptor(dependencies); + String conflictingKey = findCaseInsensitiveMatch( + descriptor.registryName(), + featureRegistry.getAvailableFeatures().keySet() + ); + if (conflictingKey != null) { + FeatureDescriptor existing = featureRegistry.getAvailableFeature(conflictingKey); + plugin.getLogger().error( + "Skipping feature implementation '{}' because feature key '{}' conflicts with '{}'.", + descriptor.implementationType().getName(), descriptor.registryName(), - featureRegistry.getAvailableFeatures().keySet() + existing.implementationType().getName() ); - if (conflictingKey != null) { - FeatureDescriptor existing = featureRegistry.getAvailableFeature(conflictingKey); - plugin.getLogger().error( - "Skipping feature class '{}' because feature key '{}' conflicts with '{}'.", - descriptor.featureClassName(), - descriptor.registryName(), - existing.featureClassName() - ); - continue; - } + continue; + } - featureRegistry.registerAvailableFeature(descriptor); - plugin.getFeatureCatalog().register(toPublicDescriptor(descriptor, definition)); + featureRegistry.registerAvailableFeature(descriptor); + plugin.getFeatureCatalog().register(toPublicDescriptor(descriptor, definition)); } pruneFeaturesWithMissingDependencies(); prepareFeatureStorage(); - plugin.getLogger().info("Discovered features: {}", featureRegistry.getAvailableFeatures().keySet()); } + private Map, BuiltInFeatures.Definition> capabilityProviders() { + Map, BuiltInFeatures.Definition> providers = new LinkedHashMap<>(); + for (BuiltInFeatures.Definition definition : BuiltInFeatures.definitions()) { + for (Class capability : definition.providedCapabilities()) { + BuiltInFeatures.Definition previous = providers.putIfAbsent(capability, definition); + if (previous != null) { + throw new IllegalStateException( + "Capability " + capability.getName() + " is provided by both " + + previous.featureName() + " and " + definition.featureName() + ); + } + } + } + return Map.copyOf(providers); + } + + private void validateCapabilityReferences(Map, BuiltInFeatures.Definition> providers) { + Set> bootstrapCapabilities = plugin.capabilities().availableTypes(); + for (BuiltInFeatures.Definition definition : BuiltInFeatures.definitions()) { + LinkedHashSet> referenced = new LinkedHashSet<>(definition.requiredCapabilities()); + referenced.addAll(definition.optionalCapabilities()); + for (Class capability : referenced) { + if (!providers.containsKey(capability) && !bootstrapCapabilities.contains(capability)) { + throw new IllegalStateException( + "Feature " + definition.featureName() + + " references capability without a provider: " + capability.getName() + ); + } + } + } + } + + private Set resolveFeatureDependencies( + BuiltInFeatures.Definition definition, + Map, BuiltInFeatures.Definition> providers + ) { + LinkedHashSet dependencies = new LinkedHashSet<>(); + for (Class requiredCapability : definition.requiredCapabilities()) { + BuiltInFeatures.Definition provider = providers.get(requiredCapability); + if (provider == null) { + if (plugin.capabilities().availableTypes().contains(requiredCapability)) { + continue; + } + plugin.getLogger().error( + "Skipping feature '{}' because required capability '{}' is unavailable.", + definition.featureName(), + requiredCapability.getName() + ); + return null; + } + if (!provider.featureName().equalsIgnoreCase(definition.featureName())) { + dependencies.add(provider.featureName()); + } + } + return Set.copyOf(dependencies); + } + private static nl.hauntedmc.proxyfeatures.api.feature.FeatureDescriptor toPublicDescriptor( FeatureDescriptor descriptor, BuiltInFeatures.Definition definition @@ -90,136 +148,19 @@ private static nl.hauntedmc.proxyfeatures.api.feature.FeatureDescriptor toPublic Set dependencies = descriptor.featureDependencies().stream() .map(FeatureId::of) .collect(java.util.stream.Collectors.toUnmodifiableSet()); + Set capabilities = definition.providedCapabilities().stream() + .map(Class::getSimpleName) + .collect(java.util.stream.Collectors.toUnmodifiableSet()); return new nl.hauntedmc.proxyfeatures.api.feature.FeatureDescriptor( FeatureId.of(descriptor.registryName()), descriptor.featureName(), descriptor.featureVersion(), definition.classification(), dependencies, - definition.providedCapabilities() + capabilities ); } - private Optional buildDescriptor(String registryName, String featureClassName) { - Optional metaOptional = resolveMeta(featureClassName); - if (metaOptional.isEmpty()) { - int lastDot = featureClassName.lastIndexOf('.'); - String expectedMetaClass = lastDot < 0 - ? featureClassName + ".meta.Meta" - : featureClassName.substring(0, lastDot) + ".meta.Meta"; - plugin.getLogger().error( - "Skipping feature class '{}' because required meta class '{}' is missing or invalid.", - featureClassName, - expectedMetaClass - ); - return Optional.empty(); - } - - BaseMeta meta = metaOptional.get(); - String featureName = (meta.getFeatureName() == null || meta.getFeatureName().isBlank()) - ? registryName - : meta.getFeatureName().trim(); - if (featureName.isBlank()) { - plugin.getLogger().error( - "Skipping feature class '{}' because getFeatureName() produced an empty name.", - featureClassName - ); - return Optional.empty(); - } - - String featureKey = featureName; - if (!isValidFeatureKey(featureKey)) { - plugin.getLogger().error( - "Skipping feature class '{}' because getFeatureName() produced an invalid key: '{}'." - + " Allowed characters: letters, digits, '_' and '-'.", - featureClassName, - featureKey - ); - return Optional.empty(); - } - - String featureVersion = (meta.getFeatureVersion() == null || meta.getFeatureVersion().isBlank()) - ? "?" - : meta.getFeatureVersion(); - Set featureDependencies = normalizeFeatureDependencies(featureClassName, featureKey, meta.getDependencies()); - if (featureDependencies == null) { - return Optional.empty(); - } - Set pluginDependencies = meta.getPluginDependencies() == null - ? Set.of() - : new LinkedHashSet<>(meta.getPluginDependencies()); - - return Optional.of(new FeatureDescriptor( - featureKey, - featureClassName, - meta, - featureName, - featureVersion, - featureDependencies, - pluginDependencies - )); - } - - private Optional resolveMeta(String featureClassName) { - int lastDot = featureClassName.lastIndexOf('.'); - if (lastDot < 0) { - return Optional.empty(); - } - - String packageName = featureClassName.substring(0, lastDot); - String metaClassName = packageName + ".meta.Meta"; - - try { - Class metaClass = Class.forName(metaClassName, true, plugin.getClass().getClassLoader()); - if (!BaseMeta.class.isAssignableFrom(metaClass)) { - plugin.getLogger().warn("Meta class does not implement BaseMeta: {}", metaClassName); - return Optional.empty(); - } - - BaseMeta meta = (BaseMeta) metaClass.getDeclaredConstructor().newInstance(); - return Optional.of(meta); - } catch (ClassNotFoundException e) { - plugin.getLogger().warn("Meta class not found: {}", metaClassName); - return Optional.empty(); - } catch (ReflectiveOperationException | LinkageError t) { - plugin.getLogger().warn("Could not resolve meta for {}", featureClassName, t); - return Optional.empty(); - } - } - - private Set normalizeFeatureDependencies(String featureClassName, String featureKey, Collection dependencies) { - if (dependencies == null || dependencies.isEmpty()) { - return Set.of(); - } - - LinkedHashSet normalized = new LinkedHashSet<>(); - for (String rawDependency : dependencies) { - String dependencyKey = rawDependency == null ? "" : rawDependency.trim(); - if (dependencyKey.isEmpty()) { - plugin.getLogger().error( - "Skipping feature class '{}' because dependency name is invalid: '{}'.", - featureClassName, - rawDependency - ); - return null; - } - if (!isValidFeatureKey(dependencyKey)) { - plugin.getLogger().error( - "Skipping feature class '{}' because dependency key is invalid: '{}'." - + " Allowed characters: letters, digits, '_' and '-'.", - featureClassName, - dependencyKey - ); - return null; - } - - if (!dependencyKey.equalsIgnoreCase(featureKey)) { - normalized.add(dependencyKey); - } - } - return normalized; - } - private void pruneFeaturesWithMissingDependencies() { boolean changed; do { @@ -247,24 +188,34 @@ private void pruneFeaturesWithMissingDependencies() { } private void prepareFeatureStorage() { - for (FeatureDescriptor descriptor : new ArrayList<>(featureRegistry.getAvailableFeatures().values())) { - FeatureContext context = createFeatureContext(descriptor); - if (context == null) { - plugin.getLogger().warn("Skipping storage preparation for feature '{}': unable to create feature context.", descriptor.registryName()); - continue; - } - - VelocityBaseFeature template = FeatureFactory.createFeature(descriptor.featureClassName(), context); - if (template == null) { - plugin.getLogger().warn("Skipping storage preparation for feature '{}': unable to instantiate template.", descriptor.registryName()); - continue; + for (FeatureDescriptor descriptor : featureRegistry.getAvailableFeatures().values()) { + FeatureContext context = createFeatureContext(descriptor); + Throwable failure = null; + try { + VelocityBaseFeature feature = descriptor.create(context); + feature.getConfigHandler().injectDefaults(feature.getDefaultConfig()); + feature.getLocalizationHandler().registerDefaultMessages(feature.getDefaultMessages()); + } catch (Throwable preparationFailure) { + failure = preparationFailure; + } finally { + try { + context.lifecycleManager().cleanup(); + } catch (Throwable cleanupFailure) { + if (failure == null) { + failure = cleanupFailure; + } else { + failure.addSuppressed(cleanupFailure); + } + } } - try { - template.getConfigHandler().injectDefaults(template.getDefaultConfig()); - template.getLocalizationHandler().registerDefaultMessages(template.getDefaultMessages()); - } catch (Throwable t) { - plugin.getLogger().error("Failed preparing config/localization storage for feature '{}'.", descriptor.registryName(), t); + if (failure != null) { + plugin.getFeatureCatalog().fail(FeatureId.of(descriptor.registryName()), failure); + plugin.getLogger().error( + "Failed preparing config/localization storage for feature '{}'.", + descriptor.registryName(), + failure + ); } } } @@ -380,14 +331,14 @@ public String resolveFeatureKey(String inputName) { return descriptor.registryName(); } - String simpleClassName = simpleClassName(descriptor.featureClassName()); + String simpleClassName = descriptor.implementationType().getSimpleName(); if (candidate.equalsIgnoreCase(simpleClassName)) { return descriptor.registryName(); } } for (String loadedKey : featureRegistry.getLoadedFeatureNames()) { - VelocityBaseFeature loadedFeature = featureRegistry.getLoadedFeature(loadedKey); + VelocityBaseFeature loadedFeature = featureRegistry.getLoadedFeature(loadedKey); if (loadedFeature == null) { continue; } @@ -410,27 +361,6 @@ private String findCaseInsensitiveMatch(String candidate, Collection val return null; } - private String simpleClassName(String className) { - if (className == null || className.isBlank()) { - return ""; - } - int lastDot = className.lastIndexOf('.'); - return lastDot < 0 ? className : className.substring(lastDot + 1); - } - - private boolean isValidFeatureKey(String value) { - if (value == null || value.isBlank()) { - return false; - } - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (!Character.isLetterOrDigit(c) && c != '_' && c != '-') { - return false; - } - } - return true; - } - private DependencyCheckResult diagnoseDependenciesRecursively(String featureName) { String featureKey = resolveFeatureKey(featureName); if (featureKey == null) { @@ -545,7 +475,7 @@ public FeatureDisableResponse disableFeature(String featureName) { return new FeatureDisableResponse(FeatureDisableResult.NOT_LOADED, featureName, Set.of()); } - VelocityBaseFeature feature = featureRegistry.getLoadedFeature(featureKey); + VelocityBaseFeature feature = featureRegistry.getLoadedFeature(featureKey); if (feature == null) { plugin.getLogger().warn("Feature not currently loaded: {}", featureKey); return new FeatureDisableResponse(FeatureDisableResult.NOT_LOADED, featureName, Set.of()); @@ -582,7 +512,7 @@ public FeatureSoftReloadResponse softReloadFeature(String featureName) { } try { - VelocityBaseFeature feature = featureRegistry.getLoadedFeature(featureKey); + VelocityBaseFeature feature = featureRegistry.getLoadedFeature(featureKey); feature.getConfigHandler().reloadConfig(); feature.getLocalizationHandler().reloadLocalization(); plugin.getLogger().info("Feature {} soft reloaded.", featureKey); @@ -602,7 +532,7 @@ public FeatureReloadResponse reloadFeature(String featureName) { Set reloadedDependents = new LinkedHashSet<>(); try { - VelocityBaseFeature feature = featureRegistry.getLoadedFeature(featureKey); + VelocityBaseFeature feature = featureRegistry.getLoadedFeature(featureKey); Optional reloadState = captureReloadState(featureKey, feature); plugin.getFeatureCatalog().transition(FeatureId.of(featureKey), FeatureState.STOPPING); @@ -680,18 +610,10 @@ private boolean loadFeature(String featureName, SnapshotState reloadState) { return false; } - VelocityBaseFeature feature = null; + VelocityBaseFeature feature = null; plugin.getFeatureCatalog().transition(FeatureId.of(featureKey), FeatureState.STARTING); try { - FeatureContext context = createFeatureContext(descriptor); - if (context == null) { - throw new IllegalStateException("Unable to create the feature context"); - } - - feature = FeatureFactory.createFeature(descriptor.featureClassName(), context); - if (feature == null) { - throw new IllegalStateException("Unable to instantiate the feature"); - } + feature = descriptor.create(createFeatureContext(descriptor)); feature.getConfigHandler().injectDefaults(feature.getDefaultConfig()); feature.getLocalizationHandler().registerDefaultMessages(feature.getDefaultMessages()); @@ -726,7 +648,7 @@ private boolean loadFeature(String featureName, SnapshotState reloadState) { } } - private Optional captureReloadState(String featureKey, VelocityBaseFeature feature) { + private Optional captureReloadState(String featureKey, VelocityBaseFeature feature) { if (feature == null || !(feature instanceof StatefulFeature statefulFeature)) { return Optional.empty(); } @@ -741,7 +663,7 @@ private Optional captureReloadState(String featureKey, VelocityBa } } - private void restoreReloadState(String featureKey, VelocityBaseFeature feature, SnapshotState reloadState) { + private void restoreReloadState(String featureKey, VelocityBaseFeature feature, SnapshotState reloadState) { if (!(feature instanceof StatefulFeature statefulFeature)) { throw new IllegalStateException("Captured reload state exists, but feature does not implement StatefulFeature."); } @@ -781,7 +703,7 @@ public void unloadAllFeatures() { List loadedFeatureNames = new ArrayList<>(featureRegistry.getLoadedFeatureNames()); Collections.reverse(loadedFeatureNames); for (String featureName : loadedFeatureNames) { - VelocityBaseFeature feature = featureRegistry.getLoadedFeature(featureName); + VelocityBaseFeature feature = featureRegistry.getLoadedFeature(featureName); try { if (feature != null) { plugin.getFeatureCatalog().transition(FeatureId.of(featureName), FeatureState.STOPPING); @@ -799,6 +721,8 @@ public void unloadAllFeatures() { } } } + + featureScopeFactory.clearCachedScopes(); plugin.getLogger().info("All features have been unloaded."); } @@ -806,7 +730,7 @@ private boolean isPluginLoaded(String pluginName) { return plugin.getPluginManager().getPlugin(pluginName).isPresent(); } - private FeatureContext createFeatureContext(FeatureDescriptor descriptor) { - return featureScopeFactory.createContext(descriptor.createMeta()); + private FeatureContext createFeatureContext(FeatureDescriptor descriptor) { + return featureScopeFactory.createContext(descriptor); } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureRegistry.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureRegistry.java index 04f4cce9..4471234f 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureRegistry.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureRegistry.java @@ -12,7 +12,7 @@ import java.util.Set; public final class FeatureRegistry { - private final Map> loadedFeatures = new LinkedHashMap<>(); + private final Map loadedFeatures = new LinkedHashMap<>(); private final Map availableFeatures = new LinkedHashMap<>(); public void registerAvailableFeature(FeatureDescriptor descriptor) { @@ -26,7 +26,7 @@ public void deregisterAvailableFeature(String featureName) { availableFeatures.remove(featureName); } - public void registerLoadedFeature(String featureName, VelocityBaseFeature feature) { + public void registerLoadedFeature(String featureName, VelocityBaseFeature feature) { loadedFeatures.put(featureName, feature); } @@ -34,7 +34,7 @@ public void deregisterLoadedFeature(String featureName) { loadedFeatures.remove(featureName); } - public VelocityBaseFeature getLoadedFeature(String featureName) { + public VelocityBaseFeature getLoadedFeature(String featureName) { return loadedFeatures.get(featureName); } @@ -54,7 +54,7 @@ public FeatureDescriptor getAvailableFeature(String featureName) { return availableFeatures.get(featureName); } - public List> getLoadedFeatures() { + public List getLoadedFeatures() { return new ArrayList<>(loadedFeatures.values()); } } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandler.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandler.java index f8db4623..3044108a 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandler.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandler.java @@ -10,12 +10,11 @@ import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigView; import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; import nl.hauntedmc.proxyfeatures.framework.config.FeatureStoragePaths; -import nl.hauntedmc.proxyfeatures.framework.service.FeatureServices; import org.spongepowered.configurate.CommentedConfigurationNode; import org.spongepowered.configurate.yaml.NodeStyle; import org.spongepowered.configurate.yaml.YamlConfigurationLoader; @@ -47,8 +46,7 @@ public LocalizationHandler(ProxyFeatures plugin, ConfigService configService) { plugin.getLogger(), plugin.getClass().getClassLoader(), configService, - player -> FeatureServices - .find(plugin, PlayerLanguageApi.class) + player -> plugin.capabilities().reference(PlayerLanguageApi.class).get() .flatMap(api -> api.resolvedLanguage(player.getUniqueId())) .map(locale -> "nl".equalsIgnoreCase(locale.getLanguage()) ? Language.NL : Language.EN) .orElse(Language.NL) diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/log/ConnectionLogHelper.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/log/ConnectionLogHelper.java index bf4dc5db..aad831d6 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/log/ConnectionLogHelper.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/log/ConnectionLogHelper.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.ComponentFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.ComponentFormatter; import java.net.InetAddress; import java.net.InetSocketAddress; diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/DataRegistryIdentityGate.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/DataRegistryIdentityGate.java index 18e50707..81e51571 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/DataRegistryIdentityGate.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/DataRegistryIdentityGate.java @@ -25,7 +25,7 @@ private DataRegistryIdentityGate() { * @param operationName short name included in failure logs. */ public static void runWhenReady( - VelocityBaseFeature feature, + VelocityBaseFeature feature, Player player, Consumer action, String operationName diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReferenceResolver.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReferenceResolver.java index 793f0553..0e20b08b 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReferenceResolver.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReferenceResolver.java @@ -4,8 +4,6 @@ import nl.hauntedmc.dataregistry.api.player.PlayerDirectory; import nl.hauntedmc.dataregistry.api.player.PlayerIdentity; import nl.hauntedmc.dataregistry.api.player.PlayerLookup; -import org.hibernate.Session; - import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -24,10 +22,9 @@ /** * Resolves immutable DataRegistry player snapshots without importing or managing DataRegistry entities. * - *

    Synchronous methods are compatibility helpers. They are cache-first and may use a bounded - * persisted fallback only from background threads. They never wait for persistence on a likely - * server, event-loop, or Netty thread. New code that needs offline-player correctness should use - * the explicit asynchronous methods.

    + *

    Synchronous methods are cache-first and may use a bounded persisted fallback only from + * background threads. They never wait for persistence on a likely server, event-loop, or Netty + * thread. Callers that require offline-player correctness should use the asynchronous methods.

    */ public final class PlayerReferenceResolver { @@ -198,11 +195,8 @@ public CompletionStage> findIdentityByIdentifierAsync(S : playerDirectory.findByIdentifier(normalized); } - /** - * Compatibility lookup by stable player id. Despite the historical method name, this method is - * cache-first and may use persistence from a background thread. - */ - public Optional findActiveIdentityById(Long playerId) { + /** Cache-first lookup by stable player id with a bounded background-thread fallback. */ + public Optional findIdentityById(Long playerId) { if (playerId == null || playerId <= 0L) { return Optional.empty(); } @@ -234,19 +228,19 @@ public CompletableFuture> whenReady(UUID uuid) { return playerDirectory.whenReady(uuid); } - public PlayerReference resolveManaged(Session ignored, UUID uuid) { + public PlayerReference resolveReference(UUID uuid) { return findByUuid(uuid).orElse(null); } - public PlayerReference resolveManaged(Session ignored, String uuid) { + public PlayerReference resolveReference(String uuid) { return findByUuid(uuid).orElse(null); } - public PlayerReference resolveManagedById(Session ignored, Long playerId) { + public PlayerReference resolveReferenceById(Long playerId) { if (playerId == null || playerId <= 0L) { return null; } - return findActiveIdentityById(playerId) + return findIdentityById(playerId) .map(PlayerReference::from) .orElseGet(() -> PlayerReference.byId(playerId)); } diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/security/audit/AbstractPlayerAuditLogService.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/security/audit/AbstractPlayerAuditLogService.java index 865205b9..590c81a9 100644 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/security/audit/AbstractPlayerAuditLogService.java +++ b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/security/audit/AbstractPlayerAuditLogService.java @@ -38,7 +38,7 @@ protected final void persist(String action, Consumer writer) { } protected final PlayerReference resolvePlayer(Session session, UUID uuid) { - return playerResolver.resolveManaged(session, uuid); + return playerResolver.resolveReference(uuid); } protected final String normalize(String value, int maxLength) { diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultProxyFeaturesApi.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultProxyFeaturesApi.java deleted file mode 100644 index e14c19a1..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultProxyFeaturesApi.java +++ /dev/null @@ -1,40 +0,0 @@ -package nl.hauntedmc.proxyfeatures.framework.service; - -import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesApi; -import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesApiVersion; -import nl.hauntedmc.proxyfeatures.api.feature.FeatureCatalog; -import nl.hauntedmc.proxyfeatures.api.service.CapabilityRegistry; - -import java.util.Objects; - -/** Immutable root API facade backed by reload-aware registries. */ -public final class DefaultProxyFeaturesApi implements ProxyFeaturesApi { - private final ProxyFeaturesApiVersion version; - private final CapabilityRegistry capabilities; - private final FeatureCatalog features; - - public DefaultProxyFeaturesApi( - ProxyFeaturesApiVersion version, - CapabilityRegistry capabilities, - FeatureCatalog features - ) { - this.version = Objects.requireNonNull(version, "version"); - this.capabilities = Objects.requireNonNull(capabilities, "capabilities"); - this.features = Objects.requireNonNull(features, "features"); - } - - @Override - public ProxyFeaturesApiVersion version() { - return version; - } - - @Override - public CapabilityRegistry capabilities() { - return capabilities; - } - - @Override - public FeatureCatalog features() { - return features; - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/FeatureServices.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/FeatureServices.java deleted file mode 100644 index fca09d56..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/FeatureServices.java +++ /dev/null @@ -1,57 +0,0 @@ -package nl.hauntedmc.proxyfeatures.framework.service; - -import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; - -import java.util.Objects; -import java.util.Optional; - -/** - * Internal convenience access to the authoritative ProxyFeatures capability registry. - */ -public final class FeatureServices { - - private FeatureServices() { - } - - /** - * Finds an enabled feature service by API type. - */ - public static Optional find(ProxyFeatures plugin, Class apiType) { - Objects.requireNonNull(plugin, "plugin"); - Objects.requireNonNull(apiType, "apiType"); - return plugin.capabilities().reference(apiType).get(); - } - - /** - * Resolves a required enabled feature service by API type. - * - * @throws IllegalStateException when the owning feature did not publish the capability - */ - public static T require(ProxyFeatures plugin, Class apiType) { - return find(plugin, apiType).orElseThrow(() -> missing(apiType)); - } - - /** - * Finds an enabled feature service by API type. - */ - public static Optional find(VelocityBaseFeature feature, Class apiType) { - Objects.requireNonNull(feature, "feature"); - return find(feature.getPlugin(), apiType); - } - - /** - * Resolves a required enabled feature service by API type. - * - * @throws IllegalStateException when the owning feature did not publish the capability - */ - public static T require(VelocityBaseFeature feature, Class apiType) { - Objects.requireNonNull(feature, "feature"); - return require(feature.getPlugin(), apiType); - } - - private static IllegalStateException missing(Class apiType) { - Objects.requireNonNull(apiType, "apiType"); - return new IllegalStateException("Feature service is not available: " + apiType.getName() + "."); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServices.java b/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServices.java deleted file mode 100644 index f2b1df80..00000000 --- a/proxyfeatures-platform-velocity/src/main/java/nl/hauntedmc/proxyfeatures/framework/service/InternalServices.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.hauntedmc.proxyfeatures.framework.service; - -import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; - -import java.util.Optional; - -/** Internal convenience access to runtime-only collaboration ports. */ -public final class InternalServices { - private InternalServices() { - } - - public static Optional find(VelocityBaseFeature feature, Class type) { - return feature.getPlugin().getInternalServiceRegistry().find(type); - } - - public static T require(VelocityBaseFeature feature, Class type) { - return feature.getPlugin().getInternalServiceRegistry().require(type); - } - - public static Optional find(ProxyFeatures plugin, Class type) { - return plugin.getInternalServiceRegistry().find(type); - } -} diff --git a/proxyfeatures-platform-velocity/src/main/resources/db/migration/V2__proxyfeatures_scalar_player_ids.sql b/proxyfeatures-platform-velocity/src/main/resources/db/migration/V2__proxyfeatures_scalar_player_ids.sql deleted file mode 100644 index c4104e29..00000000 --- a/proxyfeatures-platform-velocity/src/main/resources/db/migration/V2__proxyfeatures_scalar_player_ids.sql +++ /dev/null @@ -1,45 +0,0 @@ --- ProxyFeatures 2.11 / DataRegistry 1.11 compatibility migration (MySQL 8). --- Existing player-id values remain unchanged. Feature tables retain only those scalar ids; --- player names and UUIDs are resolved through DataRegistry's public API at read time. - -DELIMITER $$ -CREATE PROCEDURE proxyfeatures_drop_player_entity_foreign_keys() -BEGIN - DECLARE finished INTEGER DEFAULT 0; - DECLARE feature_table VARCHAR(64); - DECLARE foreign_key_name VARCHAR(64); - DECLARE foreign_keys CURSOR FOR - SELECT DISTINCT kcu.TABLE_NAME, kcu.CONSTRAINT_NAME - FROM information_schema.KEY_COLUMN_USAGE kcu - WHERE kcu.CONSTRAINT_SCHEMA = DATABASE() - AND kcu.REFERENCED_TABLE_NAME = 'player_entity' - AND kcu.TABLE_NAME IN ( - 'player_announcer_settings', 'player_antibot_logs', 'player_antivpn_logs', - 'player_clientinfo', 'player_clientinfo_channels', 'player_clientinfo_mods', - 'player_clientinfo_settings', 'player_command_executions', 'player_friend_settings', - 'player_friends', 'player_message_blocks', 'player_message_logs', - 'player_message_settings', 'player_sanctions', 'player_sanctions_security_logs', - 'player_twofactor_accounts', 'player_twofactor_logs', 'player_version_logs', - 'player_vote_monthly', 'player_vote_stats' - ); - DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1; - - OPEN foreign_keys; - drop_loop: LOOP - FETCH foreign_keys INTO feature_table, foreign_key_name; - IF finished = 1 THEN - LEAVE drop_loop; - END IF; - SET @drop_fk = CONCAT( - 'ALTER TABLE `', REPLACE(feature_table, '`', '``'), - '` DROP FOREIGN KEY `', REPLACE(foreign_key_name, '`', '``'), '`' - ); - PREPARE statement FROM @drop_fk; - EXECUTE statement; - DEALLOCATE PREPARE statement; - END LOOP; - CLOSE foreign_keys; -END$$ -CALL proxyfeatures_drop_player_entity_foreign_keys()$$ -DROP PROCEDURE proxyfeatures_drop_player_entity_foreign_keys$$ -DELIMITER ; diff --git a/proxyfeatures-platform-velocity/src/main/resources/db/migration/V3__messenger_message_mode.sql b/proxyfeatures-platform-velocity/src/main/resources/db/migration/V3__messenger_message_mode.sql deleted file mode 100644 index 3a69513e..00000000 --- a/proxyfeatures-platform-velocity/src/main/resources/db/migration/V3__messenger_message_mode.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Add persisted Messenger privacy mode without imposing a database-level default. --- Existing rows remain NULL and are repaired to Messenger.default_message_mode --- when their settings are first loaded. -ALTER TABLE player_message_settings - ADD COLUMN message_mode VARCHAR(32) NULL; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/architecture/ArchitectureBoundaryTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/architecture/ArchitectureBoundaryTest.java index 894aa396..ba61f019 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/architecture/ArchitectureBoundaryTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/architecture/ArchitectureBoundaryTest.java @@ -96,7 +96,12 @@ void obsoleteDiscoveryAndApiSystemsCannotReturn() throws IOException { "io.github.classgraph", "ProxyFeaturesContext", "FeatureServiceDirectory", - "RestartAdmissionAPI" + "RestartAdmissionAPI", + "BaseMeta", + "FeatureFactory", + "FeatureServices", + "InternalServices", + "DefaultProxyFeaturesApi" ); List violations = new ArrayList<>(); @@ -112,6 +117,32 @@ void obsoleteDiscoveryAndApiSystemsCannotReturn() throws IOException { assertEquals(List.of(), violations); } + @Test + void obsoleteMetadataAndGenericUtilityPackagesAreAbsent() throws IOException { + Path featureRoot = MODULE.resolve("src/main/java/nl/hauntedmc/proxyfeatures/features"); + List violations = new ArrayList<>(); + + try (Stream paths = Files.walk(featureRoot)) { + paths.filter(Files::isDirectory) + .filter(path -> path.getFileName().toString().equals("meta")) + .forEach(path -> violations.add("obsolete feature metadata directory: " + path)); + } + + for (Path module : List.of( + REPOSITORY.resolve("proxyfeatures-toolkit/src/main/java"), + MODULE.resolve("src/main/java") + )) { + for (Path source : javaSources(module)) { + String text = Files.readString(source); + if (text.contains("nl.hauntedmc.proxyfeatures.toolkit.util")) { + violations.add(REPOSITORY.relativize(source) + " imports generic toolkit.util"); + } + } + } + + assertEquals(List.of(), violations); + } + private static List javaSources(Path root) throws IOException { try (Stream paths = Files.walk(root)) { return paths.filter(Files::isRegularFile) diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/FeatureFactoryTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/FeatureFactoryTest.java deleted file mode 100644 index b53a706c..00000000 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/FeatureFactoryTest.java +++ /dev/null @@ -1,153 +0,0 @@ -package nl.hauntedmc.proxyfeatures.features; - -import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; -import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; -import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; -import nl.hauntedmc.proxyfeatures.features.commandhider.meta.Meta; -import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; -import nl.hauntedmc.proxyfeatures.framework.config.MainConfigHandler; -import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureApiManager; -import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureCacheManager; -import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureCommandManager; -import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureDataManager; -import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; -import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureListenerManager; -import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureTaskManager; -import nl.hauntedmc.proxyfeatures.framework.localization.LocalizationHandler; -import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; -import nl.hauntedmc.proxyfeatures.testutil.ComponentLoggerRecorder; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.slf4j.LoggerFactory; - -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -class FeatureFactoryTest { - - @Test - void createFeatureReturnsNullAndLogsWhenClassIsMissing() { - ProxyFeatures plugin = mock(ProxyFeatures.class); - ComponentLoggerRecorder loggerRecorder = ComponentLoggerRecorder.create(); - when(plugin.getLogger()).thenReturn(loggerRecorder.logger()); - FeatureContext context = mockContext(plugin); - - VelocityBaseFeature created = FeatureFactory.createFeature(null, context); - assertNull(created); - assertTrue(loggerRecorder.hasStringArgumentContaining("error", "missing feature class name")); - } - - @Test - void createFeatureReturnsNullWhenConstructorSignatureDoesNotMatch() { - ProxyFeatures plugin = mock(ProxyFeatures.class); - ComponentLoggerRecorder loggerRecorder = ComponentLoggerRecorder.create(); - when(plugin.getLogger()).thenReturn(loggerRecorder.logger()); - FeatureContext context = mockContext(plugin); - - VelocityBaseFeature created = FeatureFactory.createFeature(NoProxyCtorFeature.class.getName(), context); - assertNull(created); - assertTrue(loggerRecorder.hasStringArgumentContaining("error", "Failed to instantiate feature")); - } - - @Test - void createFeatureInstantiatesFeatureWhenConstructorMatches(@TempDir Path tempDir) { - ProxyFeatures plugin = mock(ProxyFeatures.class); - when(plugin.getLogger()).thenReturn(ComponentLoggerRecorder.create().logger()); - when(plugin.getDataDirectory()).thenReturn(tempDir); - ConfigService service = new ConfigService(tempDir, LoggerFactory.getLogger(FeatureFactoryTest.class), getClass().getClassLoader()); - MainConfigHandler mainConfig = new MainConfigHandler(plugin, service); - LocalizationHandler localization = new LocalizationHandler(plugin, service); - when(plugin.getConfigHandler()).thenReturn(mainConfig); - when(plugin.getLocalizationHandler()).thenReturn(localization); - FeatureContext context = new FeatureContext<>( - plugin, - new Meta(), - mainConfig.openFeatureConfig("CommandHider"), - mockLifecycleManager(), - new FeatureLogger(plugin.getLogger(), "CommandHider"), - localization.openFeatureLocalization("CommandHider") - ); - - VelocityBaseFeature created = FeatureFactory.createFeature(ValidCtorFeature.class.getName(), context); - assertNotNull(created); - assertInstanceOf(ValidCtorFeature.class, created); - } - - private FeatureContext mockContext(ProxyFeatures plugin) { - return new FeatureContext<>( - plugin, - new Meta(), - mock(FeatureConfigHandler.class), - mockLifecycleManager(), - mock(FeatureLogger.class), - mock(LocalizationHandler.class) - ); - } - - private FeatureLifecycleManager mockLifecycleManager() { - return new FeatureLifecycleManager( - mock(FeatureTaskManager.class), - mock(FeatureCommandManager.class), - mock(FeatureListenerManager.class), - mock(FeatureDataManager.class), - mock(FeatureCacheManager.class), - mock(FeatureApiManager.class) - ); - } - - private static final class NoProxyCtorFeature extends VelocityBaseFeature { - NoProxyCtorFeature() { - super(mockContext()); - } - - @Override - public ConfigMap getDefaultConfig() { - return new ConfigMap(); - } - - @Override - public MessageMap getDefaultMessages() { - return new MessageMap(); - } - - @SuppressWarnings("unchecked") // Mockito cannot preserve the generic type token at runtime. - private static FeatureContext mockContext() { - return mock(FeatureContext.class); - } - - @Override - public void initialize() { - } - - @Override - public void disable() { - } - } - - private static final class ValidCtorFeature extends VelocityBaseFeature { - ValidCtorFeature(FeatureContext context) { - super(context); - } - - @Override - public ConfigMap getDefaultConfig() { - return new ConfigMap(); - } - - @Override - public MessageMap getDefaultMessages() { - return new MessageMap(); - } - - @Override - public void initialize() { - } - - @Override - public void disable() { - } - } -} diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeatureTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeatureTest.java index 040c5cab..06bb0300 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeatureTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/VelocityBaseFeatureTest.java @@ -2,7 +2,6 @@ import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; @@ -29,9 +28,12 @@ void cleanupAlwaysRunsLifecycleCleanupWhenDisableFails() { when(plugin.getLogger()).thenReturn(ComponentLogger.logger("VelocityBaseFeatureTest")); FeatureLifecycleManager lifecycle = mock(FeatureLifecycleManager.class); - TestFeature feature = new TestFeature(new FeatureContext<>( + TestFeature feature = new TestFeature(new FeatureContext( plugin, - new TestMeta(), + "Queue", + "1.0", + List.of(), + List.of(), mock(FeatureConfigHandler.class), lifecycle, mock(FeatureLogger.class), @@ -54,9 +56,12 @@ void cleanupSuppressesLifecycleFailureBehindDisableFailure() { RuntimeException lifecycleFailure = new RuntimeException("lifecycle"); doThrow(lifecycleFailure).when(lifecycle).cleanup(); - TestFeature feature = new TestFeature(new FeatureContext<>( + TestFeature feature = new TestFeature(new FeatureContext( plugin, - new TestMeta(), + "Queue", + "1.0", + List.of(), + List.of(), mock(FeatureConfigHandler.class), lifecycle, mock(FeatureLogger.class), @@ -71,10 +76,10 @@ void cleanupSuppressesLifecycleFailureBehindDisableFailure() { assertSame(lifecycleFailure, thrown.getSuppressed()[0]); } - private static final class TestFeature extends VelocityBaseFeature { + private static final class TestFeature extends VelocityBaseFeature { private RuntimeException disableFailure; - private TestFeature(FeatureContext context) { + private TestFeature(FeatureContext context) { super(context); } @@ -99,26 +104,4 @@ public void disable() { } } } - - private static final class TestMeta implements BaseMeta { - @Override - public String getFeatureName() { - return "Queue"; - } - - @Override - public String getFeatureVersion() { - return "1.0"; - } - - @Override - public List getDependencies() { - return List.of(); - } - - @Override - public List getPluginDependencies() { - return List.of(); - } - } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerSettingsServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerSettingsServiceTest.java index 82500c2b..8a2e71fd 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerSettingsServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/announcer/internal/AnnouncerSettingsServiceTest.java @@ -195,7 +195,7 @@ private static PlayerReferenceResolver resolverReturning( PlayerReference player ) { PlayerReferenceResolver resolver = mock(PlayerReferenceResolver.class); - when(resolver.resolveManaged(session, uuid)).thenReturn(player); + when(resolver.resolveReference(uuid)).thenReturn(player); return resolver; } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoSettingsServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoSettingsServiceTest.java index cf26d1f3..1cc312b0 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoSettingsServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/clientinfo/internal/ClientInfoSettingsServiceTest.java @@ -150,7 +150,7 @@ private static PlayerReferenceResolver resolverReturning( PlayerReference player ) { PlayerReferenceResolver resolver = mock(PlayerReferenceResolver.class); - when(resolver.resolveManaged(session, uuid)).thenReturn(player); + when(resolver.resolveReference(uuid)).thenReturn(player); return resolver; } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/friends/FriendsDefaultsTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/friends/FriendsDefaultsTest.java index 33c9e3e8..e69c0059 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/friends/FriendsDefaultsTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/friends/FriendsDefaultsTest.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.friends; import nl.hauntedmc.proxyfeatures.features.FeatureContext; -import nl.hauntedmc.proxyfeatures.features.friends.meta.Meta; +import nl.hauntedmc.proxyfeatures.framework.loader.BuiltInFeatures; import org.junit.jupiter.api.Test; import java.util.Map; @@ -25,6 +25,10 @@ void vanishPresenceNotificationsAreEnabledByDefault() { @Test void featureVersionReflectsThePresenceNotificationUpdate() { - assertEquals("1.4.0", new Meta().getFeatureVersion()); + assertEquals("1.4.0", BuiltInFeatures.definitions().stream() + .filter(definition -> definition.implementationType() == Friends.class) + .findFirst() + .orElseThrow() + .featureVersion()); } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntityTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntityTest.java index 836f19e8..abc25366 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntityTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/entity/PlayerMessageSettingsEntityTest.java @@ -11,7 +11,7 @@ class PlayerMessageSettingsEntityTest { @Test - void constructorLeavesModeAvailableForConfiguredDefault() { + void constructorUsesSafeNonNullDefault() { PlayerReference player = mock(PlayerReference.class); when(player.getId()).thenReturn(42L); @@ -19,9 +19,7 @@ void constructorLeavesModeAvailableForConfiguredDefault() { assertTrue(settings.isMsgToggle()); assertFalse(settings.isMsgSpy()); - assertTrue(settings.getStoredMessageMode().isEmpty()); - assertEquals(MessageMode.ALL, settings.getMessageMode(MessageMode.ALL)); - assertEquals(MessageMode.FRIENDS, settings.getMessageMode(MessageMode.FRIENDS)); + assertEquals(MessageMode.FRIENDS, settings.getMessageMode()); assertTrue(settings.getBlockedPlayerIds().isEmpty()); } @@ -55,16 +53,15 @@ void toggleSpyAndModeCanBeUpdated() { assertFalse(settings.isMsgToggle()); assertTrue(settings.isMsgSpy()); - assertEquals(MessageMode.ALL, settings.getStoredMessageMode().orElseThrow()); - assertEquals(MessageMode.ALL, settings.getMessageMode(MessageMode.FRIENDS)); + assertEquals(MessageMode.ALL, settings.getMessageMode()); } @Test - void noArgConstructorSupportsConfigurableFallbackState() { + void noArgConstructorUsesSafeNonNullDefault() { PlayerMessageSettingsEntity settings = new PlayerMessageSettingsEntity(); assertTrue(settings.isMsgToggle()); assertFalse(settings.isMsgSpy()); - assertTrue(settings.getStoredMessageMode().isEmpty()); + assertEquals(MessageMode.FRIENDS, settings.getMessageMode()); assertTrue(settings.getBlockedPlayerIds().isEmpty()); } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsServiceTest.java index a5b3fe7c..01d7874e 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/messager/internal/MessagingSettingsServiceTest.java @@ -50,44 +50,13 @@ void loadSettingsKeepsKnownPersistedMode() { when(query.uniqueResultOptional()).thenReturn(Optional.of(settings)); PlayerMessageSettingsEntity loaded = service(feature, directory) - .loadSettings(uuid, "Remy"); + .loadSettings(uuid); assertSame(settings, loaded); - assertEquals(MessageMode.ALL, loaded.getStoredMessageMode().orElseThrow()); + assertEquals(MessageMode.ALL, loaded.getMessageMode()); verify(session, never()).merge(settings); } - @Test - void loadSettingsRepairsUnknownModeWithConfiguredDefault() { - Messenger feature = mock(Messenger.class); - ORMContext orm = transactionalOrm(feature); - PlayerDirectory directory = mock(PlayerDirectory.class); - Session session = session(orm); - @SuppressWarnings("unchecked") - Query query = mock(Query.class); - - UUID uuid = UUID.randomUUID(); - PlayerReference player = new PlayerReference(11L, uuid.toString(), "Remy"); - PlayerMessageSettingsEntity settings = new PlayerMessageSettingsEntity(player); - - when(feature.getDefaultMessageMode()).thenReturn(MessageMode.ALL); - when(directory.findActiveIdentityCached(uuid)) - .thenReturn(Optional.of(new PlayerIdentity(11L, uuid, "Remy"))); - when(session.getReference(PlayerReference.class, 11L)).thenReturn(player); - when(session.createQuery( - "FROM PlayerMessageSettingsEntity s WHERE s.playerId = :pid", - PlayerMessageSettingsEntity.class - )).thenReturn(query); - when(query.setParameter("pid", 11L)).thenReturn(query); - when(query.uniqueResultOptional()).thenReturn(Optional.of(settings)); - - PlayerMessageSettingsEntity loaded = service(feature, directory) - .loadSettings(uuid, "Remy"); - - assertEquals(MessageMode.ALL, loaded.getStoredMessageMode().orElseThrow()); - verify(session).merge(settings); - } - @Test void loadSettingsCreatesNewRowWithConfiguredDefault() { Messenger feature = mock(Messenger.class); @@ -111,9 +80,9 @@ void loadSettingsCreatesNewRowWithConfiguredDefault() { when(query.uniqueResultOptional()).thenReturn(Optional.empty()); PlayerMessageSettingsEntity loaded = service(feature, directory) - .loadSettings(uuid, "NewPlayer"); + .loadSettings(uuid); - assertEquals(MessageMode.ALL, loaded.getStoredMessageMode().orElseThrow()); + assertEquals(MessageMode.ALL, loaded.getMessageMode()); verify(session).persist(loaded); } @@ -134,7 +103,7 @@ void toggleSpyAndModeMutationsUpdateSettingsInTransaction() { assertFalse(settings.isMsgToggle()); assertTrue(settings.isMsgSpy()); - assertEquals(MessageMode.ALL, settings.getStoredMessageMode().orElseThrow()); + assertEquals(MessageMode.ALL, settings.getMessageMode()); verify(session, times(3)).merge(settings); } @@ -152,7 +121,7 @@ void newRowsCreatedByOtherMutationsReceiveConfiguredDefault() { ArgumentCaptor captor = ArgumentCaptor.forClass(PlayerMessageSettingsEntity.class); verify(session).persist(captor.capture()); - assertEquals(MessageMode.ALL, captor.getValue().getStoredMessageMode().orElseThrow()); + assertEquals(MessageMode.ALL, captor.getValue().getMessageMode()); verify(session).merge(captor.getValue()); } @@ -215,10 +184,10 @@ void missingPlayerGetsTransientConfiguredDefaultWithoutPersistence() { when(playerQuery.uniqueResultOptional()).thenReturn(Optional.empty()); PlayerMessageSettingsEntity loaded = service(feature, directory) - .loadSettings(uuid, "Remy"); + .loadSettings(uuid); assertTrue(loaded.isMsgToggle()); - assertEquals(MessageMode.ALL, loaded.getStoredMessageMode().orElseThrow()); + assertEquals(MessageMode.ALL, loaded.getMessageMode()); verify(session, never()).persist(any(PlayerReference.class)); verify(session, never()).persist(any(PlayerMessageSettingsEntity.class)); } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandlerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandlerTest.java index d28cf457..11658510 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandlerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/motd/internal/MotdHandlerTest.java @@ -7,9 +7,9 @@ import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; import nl.hauntedmc.proxyfeatures.framework.feature.FeatureScopeFactory; +import nl.hauntedmc.proxyfeatures.framework.loader.FeatureDescriptor; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleFactory; import nl.hauntedmc.proxyfeatures.features.motd.Motd; -import nl.hauntedmc.proxyfeatures.features.motd.meta.Meta; import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceApi; import nl.hauntedmc.proxyfeatures.api.capability.presence.PresenceSnapshot; import nl.hauntedmc.proxyfeatures.api.extension.MotdContext; @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -218,7 +219,9 @@ private Motd feature(Map settings) { new FeatureLifecycleFactory(plugin) ); - Motd feature = new Motd(featureScopeFactory.createContext(new Meta())); + Motd feature = new Motd(featureScopeFactory.createContext(new FeatureDescriptor( + "Motd", "Motd", "1.2.0", Motd.class, Motd::new, Set.of(), Set.of() + ))); feature.getConfigHandler().globals().put("server_name", "haunted"); settings.forEach((key, value) -> feature.getConfigHandler().put(key, value)); return feature; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoServiceTest.java index a2804bab..b96d6e7a 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/playerinfo/service/PlayerInfoServiceTest.java @@ -11,6 +11,7 @@ import nl.hauntedmc.dataregistry.api.player.PlayerNameHistoryEntry; import nl.hauntedmc.dataregistry.api.player.PlayerProfile; import nl.hauntedmc.proxyfeatures.ProxyFeatures; +import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionFilter; import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionSnapshot; import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionType; import nl.hauntedmc.proxyfeatures.api.capability.moderation.SanctionsApi; @@ -195,7 +196,7 @@ void queryHelpersForUuidConnectionInfoAndActiveSanctionsDelegateCorrectly() { )))); SanctionsApi sanctions = mock(SanctionsApi.class); - when(sanctions.find(uuid, true)).thenReturn(CompletableFuture.completedFuture(List.of( + when(sanctions.find(uuid, SanctionFilter.ACTIVE)).thenReturn(CompletableFuture.completedFuture(List.of( new SanctionSnapshot( 1L, uuid, SanctionType.BAN, "reason", "actor", Instant.EPOCH, Optional.empty(), true) diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListenerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListenerTest.java index 0af1aaf7..f51fee82 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListenerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/listener/ConnectListenerTest.java @@ -3,7 +3,7 @@ import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.proxy.Player; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.audit.SanctionsSecurityAuditLogService; import nl.hauntedmc.proxyfeatures.features.sanctions.service.SanctionsService; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordServiceTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordServiceTest.java index 066b5f23..0ea998e9 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordServiceTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/sanctions/service/DiscordServiceTest.java @@ -1,7 +1,7 @@ package nl.hauntedmc.proxyfeatures.features.sanctions.service; import nl.hauntedmc.proxyfeatures.framework.persistence.PlayerReference; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder.MessagePlaceholders; +import nl.hauntedmc.proxyfeatures.toolkit.text.placeholder.MessagePlaceholders; import nl.hauntedmc.proxyfeatures.features.sanctions.Sanctions; import nl.hauntedmc.proxyfeatures.features.sanctions.entity.SanctionEntity; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; @@ -12,7 +12,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; class DiscordServiceTest { diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactorTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactorTest.java index 63d697c5..ebcdd671 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactorTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/twofactor/TwoFactorTest.java @@ -11,7 +11,6 @@ import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.features.FeatureContext; import nl.hauntedmc.proxyfeatures.features.twofactor.config.TwoFactorConfig; -import nl.hauntedmc.proxyfeatures.features.twofactor.meta.Meta; import nl.hauntedmc.proxyfeatures.features.twofactor.service.TwoFactorService; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureLifecycleManager; @@ -68,9 +67,12 @@ void sendDelayedLockPromptSchedulesPromptOnLockServerOnly() throws Exception { return null; }); - TwoFactor feature = new TwoFactor(new FeatureContext<>( + TwoFactor feature = new TwoFactor(new FeatureContext( plugin, - new Meta(), + "TwoFactor", + "1.0.0", + List.of(), + List.of("dataprovider", "dataregistry"), configHandler, lifecycleManager, logger, @@ -129,9 +131,12 @@ void sendLockPromptDoesNothingWhenPlayerIsNotLocked() throws Exception { TwoFactorService.LockReason.AUTH_REQUIRED )); - TwoFactor feature = new TwoFactor(new FeatureContext<>( + TwoFactor feature = new TwoFactor(new FeatureContext( plugin, - new Meta(), + "TwoFactor", + "1.0.0", + List.of(), + List.of("dataprovider", "dataregistry"), configHandler, lifecycleManager, logger, @@ -174,9 +179,12 @@ void movePlayerToLockServerDisconnectsWhenForcedTransferFails() throws Exception when(request.connect()).thenReturn(CompletableFuture.completedFuture(result)); when(result.isSuccessful()).thenReturn(false); - TwoFactor feature = new TwoFactor(new FeatureContext<>( + TwoFactor feature = new TwoFactor(new FeatureContext( plugin, - new Meta(), + "TwoFactor", + "1.0.0", + List.of(), + List.of("dataprovider", "dataregistry"), configHandler, lifecycleManager, logger, @@ -233,9 +241,12 @@ void enforcePlayerLockStateMovesLockedPlayerOffNonLockServer() throws Exception when(currentServer.getServerInfo()).thenReturn(new ServerInfo("survival", new InetSocketAddress("127.0.0.1", 25565))); AtomicBoolean transferRequested = new AtomicBoolean(); - TwoFactor feature = new TwoFactor(new FeatureContext<>( + TwoFactor feature = new TwoFactor(new FeatureContext( plugin, - new Meta(), + "TwoFactor", + "1.0.0", + List.of(), + List.of("dataprovider", "dataregistry"), configHandler, lifecycleManager, logger, @@ -305,9 +316,12 @@ void movePlayerToLockServerSkipsDuplicateTransferWhilePreviousAttemptIsPending() when(player.createConnectionRequest(lockServer)).thenReturn(request); when(request.connect()).thenReturn(pending); - TwoFactor feature = new TwoFactor(new FeatureContext<>( + TwoFactor feature = new TwoFactor(new FeatureContext( plugin, - new Meta(), + "TwoFactor", + "1.0.0", + List.of(), + List.of("dataprovider", "dataregistry"), configHandler, lifecycleManager, logger, @@ -363,9 +377,12 @@ void refreshTrustedExpiryMonitorSchedulesExpiryTaskForTrustedLogin() throws Exce when(service.trustedLoginExpiry(player)).thenReturn(OptionalLong.of(System.currentTimeMillis() + 5000L)); when(taskManager.scheduleDelayedTask(any(Runnable.class), any(Duration.class))).thenReturn(scheduledTask); - TwoFactor feature = new TwoFactor(new FeatureContext<>( + TwoFactor feature = new TwoFactor(new FeatureContext( plugin, - new Meta(), + "TwoFactor", + "1.0.0", + List.of(), + List.of("dataprovider", "dataregistry"), configHandler, lifecycleManager, logger, @@ -392,9 +409,12 @@ void clearTrustedExpiryMonitorCancelsScheduledTask() throws Exception { when(lifecycleManager.getTaskManager()).thenReturn(taskManager); - TwoFactor feature = new TwoFactor(new FeatureContext<>( + TwoFactor feature = new TwoFactor(new FeatureContext( plugin, - new Meta(), + "TwoFactor", + "1.0.0", + List.of(), + List.of("dataprovider", "dataregistry"), configHandler, lifecycleManager, logger, diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/twofactor/persistence/OrmTwoFactorAccountStoreTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/twofactor/persistence/OrmTwoFactorAccountStoreTest.java index 54bda934..47e5a186 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/twofactor/persistence/OrmTwoFactorAccountStoreTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/features/twofactor/persistence/OrmTwoFactorAccountStoreTest.java @@ -111,7 +111,7 @@ private static PlayerReferenceResolver resolverReturning( PlayerReference player ) { PlayerReferenceResolver resolver = mock(PlayerReferenceResolver.class); - when(resolver.resolveManaged(session, uuid)).thenReturn(player); + when(resolver.resolveReference(uuid)).thenReturn(player); return resolver; } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommandTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommandTest.java index 54bef7ea..0f861fbe 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommandTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/command/ProxyFeaturesCommandTest.java @@ -121,7 +121,7 @@ void metadataAndDispatcherExecutionWorkForAllSubcommands() throws Exception { assertEquals("proxyfeatures", command.name()); assertTrue(command.description().contains("framework command")); - VelocityBaseFeature queue = feature("Queue", "1.0", List.of("luckperms"), List.of()); + VelocityBaseFeature queue = feature("Queue", "1.0", List.of("luckperms"), List.of()); registry.registerLoadedFeature("Queue", queue); registry.registerAvailableFeature(descriptor("Queue", "1.0", Set.of(), Set.of("luckperms"))); registry.registerAvailableFeature(descriptor("Friends", "1.0", Set.of(), Set.of())); @@ -162,7 +162,7 @@ void metadataAndDispatcherExecutionWorkForAllSubcommands() throws Exception { @Test void handleInfoCoversBlankLoadedCaseInsensitiveAvailableAndMissing() { - VelocityBaseFeature queue = feature("Queue", "2.0", List.of("A"), List.of("B")); + VelocityBaseFeature queue = feature("Queue", "2.0", List.of("A"), List.of("B")); registry.registerLoadedFeature("Queue", queue); registry.registerAvailableFeature(descriptor("Friends", "1.0", Set.of(), Set.of())); @@ -264,8 +264,8 @@ void enableDisableSoftReloadAndReloadSwitchBranchesAreCovered() { @Test void suggestionAndRenderingHelpersReturnExpectedValues() { - VelocityBaseFeature queue = feature("Queue", "1.0", List.of(), List.of()); - VelocityBaseFeature vanish = feature("Vanish", "1.0", List.of(), List.of()); + VelocityBaseFeature queue = feature("Queue", "1.0", List.of(), List.of()); + VelocityBaseFeature vanish = feature("Vanish", "1.0", List.of(), List.of()); registry.registerLoadedFeature("Queue", queue); registry.registerLoadedFeature("Vanish", vanish); registry.registerAvailableFeature(descriptor("Queue", "1.0", Set.of(), Set.of())); @@ -308,8 +308,8 @@ void suggestionAndRenderingHelpersReturnExpectedValues() { @Test void statusAndListHelpersEmitMessages() { - VelocityBaseFeature queue = feature("Queue", "1.0", List.of(), List.of()); - VelocityBaseFeature friends = feature("Friends", "2.0", List.of(), List.of()); + VelocityBaseFeature queue = feature("Queue", "1.0", List.of(), List.of()); + VelocityBaseFeature friends = feature("Friends", "2.0", List.of(), List.of()); registry.registerLoadedFeature("Queue", queue); registry.registerLoadedFeature("Friends", friends); @@ -372,8 +372,8 @@ void commandTreeEnforcesRootAndSubcommandPermissions() throws Exception { verify(sender, times(1)).sendMessage(any(Component.class)); } - private VelocityBaseFeature feature(String name, String version, List pluginDeps, List featureDeps) { - VelocityBaseFeature feature = mock(VelocityBaseFeature.class); + private VelocityBaseFeature feature(String name, String version, List pluginDeps, List featureDeps) { + VelocityBaseFeature feature = mock(VelocityBaseFeature.class); when(feature.getFeatureName()).thenReturn(name); when(feature.getFeatureVersion()).thenReturn(version); when(feature.getPluginDependencies()).thenReturn(pluginDeps); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactoryTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactoryTest.java index d6b8bed0..8fb20fcd 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactoryTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/feature/FeatureScopeFactoryTest.java @@ -1,8 +1,9 @@ package nl.hauntedmc.proxyfeatures.framework.feature; import nl.hauntedmc.proxyfeatures.ProxyFeatures; -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; import nl.hauntedmc.proxyfeatures.features.FeatureContext; +import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; +import nl.hauntedmc.proxyfeatures.framework.loader.FeatureDescriptor; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureApiManager; import nl.hauntedmc.proxyfeatures.framework.lifecycle.FeatureCacheManager; @@ -15,7 +16,7 @@ import nl.hauntedmc.proxyfeatures.framework.log.FeatureLogger; import org.junit.jupiter.api.Test; -import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.jupiter.api.Assertions.assertNotSame; @@ -56,9 +57,12 @@ void createContextCachesFeatureScopedResourcesButBuildsFreshLifecycleManagers() } ); - TestMeta meta = new TestMeta("Queue"); - FeatureContext first = factory.createContext(meta); - FeatureContext second = factory.createContext(meta); + FeatureDescriptor descriptor = new FeatureDescriptor( + "Queue", "Queue", "1.0", VelocityBaseFeature.class, + context -> mock(VelocityBaseFeature.class), Set.of(), Set.of() + ); + FeatureContext first = factory.createContext(descriptor); + FeatureContext second = factory.createContext(descriptor); assertSame(configHandler, first.configHandler()); assertSame(configHandler, second.configHandler()); @@ -73,26 +77,4 @@ void createContextCachesFeatureScopedResourcesButBuildsFreshLifecycleManagers() org.junit.jupiter.api.Assertions.assertEquals(1, loggerCounter.get()); verify(dataManager, times(2)).bindToFeature("Queue"); } - - private record TestMeta(String featureName) implements BaseMeta { - @Override - public String getFeatureName() { - return featureName; - } - - @Override - public String getFeatureVersion() { - return "1.0"; - } - - @Override - public List getDependencies() { - return List.of(); - } - - @Override - public List getPluginDependencies() { - return List.of(); - } - } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeaturesTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeaturesTest.java index 92414071..a8333e5d 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeaturesTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/BuiltInFeaturesTest.java @@ -1,60 +1,84 @@ package nl.hauntedmc.proxyfeatures.framework.loader; -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.api.capability.admission.AdmissionApi; +import nl.hauntedmc.proxyfeatures.api.capability.player.NetworkLocationApi; +import nl.hauntedmc.proxyfeatures.api.extension.MotdExtensions; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureClassification; import org.junit.jupiter.api.Test; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; class BuiltInFeaturesTest { - private static final Set PUBLIC_CAPABILITIES = Set.of( - "AdmissionApi", - "FriendshipApi", - "MaintenanceApi", - "NetworkLocationApi", - "PlayerCountApi", - "PlayerLanguageApi", - "PresenceApi", - "QueueApi", - "RestartApi", - "SanctionsApi", - "TwoFactorApi", - "VersionApi" - ); - @Test - void manifestExplicitlyDefinesEveryShippedFeatureExactlyOnce() throws Exception { + void manifestExplicitlyDefinesEveryShippedFeatureExactlyOnce() { assertEquals(33, BuiltInFeatures.definitions().size()); - Set featureTypes = new HashSet<>(); + Set> featureTypes = new HashSet<>(); + Set featureNames = new HashSet<>(); + Map, BuiltInFeatures.Definition> providers = new HashMap<>(); for (BuiltInFeatures.Definition definition : BuiltInFeatures.definitions()) { - assertTrue(featureTypes.add(definition.featureClassName()), - () -> "Duplicate feature definition: " + definition.featureClassName()); - Class featureType = Class.forName(definition.featureClassName()); - assertNotNull(featureType); - - String packageName = definition.featureClassName().substring( - 0, - definition.featureClassName().lastIndexOf('.') - ); - Class metaType = Class.forName(packageName + ".meta.Meta"); - assertTrue(BaseMeta.class.isAssignableFrom(metaType)); - assertTrue(PUBLIC_CAPABILITIES.containsAll(definition.providedCapabilities()), - () -> "Unknown public capability in " + definition.featureClassName()); + assertTrue(featureTypes.add(definition.implementationType()), + () -> "Duplicate feature implementation: " + definition.implementationType().getName()); + assertTrue(featureNames.add(definition.featureName().toLowerCase(java.util.Locale.ROOT)), + () -> "Duplicate feature name: " + definition.featureName()); + assertNotNull(definition.constructor()); + definition.providedCapabilities().forEach(capability -> + assertTrue(providers.putIfAbsent(capability, definition) == null, + () -> "Duplicate capability provider: " + capability.getName())); } + + assertSame(provider(providers, AdmissionApi.class), feature("Capacity")); + assertSame(provider(providers, NetworkLocationApi.class), feature("AntiVPN")); + } + + @Test + void allReferencedCapabilitiesHaveExactlyOneBuiltInOrBootstrapProvider() { + Map, BuiltInFeatures.Definition> providers = new HashMap<>(); + BuiltInFeatures.definitions().forEach(definition -> + definition.providedCapabilities().forEach(capability -> providers.put(capability, definition))); + Set> bootstrapCapabilities = Set.of(MotdExtensions.class); + + BuiltInFeatures.definitions().forEach(definition -> { + Set> referenced = new HashSet<>(definition.requiredCapabilities()); + referenced.addAll(definition.optionalCapabilities()); + referenced.forEach(capability -> + assertTrue(providers.containsKey(capability) || bootstrapCapabilities.contains(capability), + () -> definition.featureName() + " references unprovided " + capability.getName())); + }); } @Test void providerClassificationsAlwaysPublishAtLeastOneCapability() { BuiltInFeatures.definitions().forEach(definition -> { - switch (definition.classification()) { - case CAPABILITY_PROVIDER -> assertFalse(definition.providedCapabilities().isEmpty()); - case CAPABILITY_CONSUMER, INTERNAL, EXTENSION_PROVIDER -> - assertTrue(definition.providedCapabilities().isEmpty()); + if (definition.classification() == FeatureClassification.CAPABILITY_PROVIDER) { + assertFalse(definition.providedCapabilities().isEmpty()); + } else { + assertTrue(definition.providedCapabilities().isEmpty()); } }); } + + private static BuiltInFeatures.Definition feature(String name) { + return BuiltInFeatures.definitions().stream() + .filter(definition -> definition.featureName().equals(name)) + .findFirst() + .orElseThrow(); + } + + private static BuiltInFeatures.Definition provider( + Map, BuiltInFeatures.Definition> providers, + Class capability + ) { + return java.util.Objects.requireNonNull(providers.get(capability)); + } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptorTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptorTest.java index 10f66a54..a48919c3 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptorTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureDescriptorTest.java @@ -1,79 +1,58 @@ package nl.hauntedmc.proxyfeatures.framework.loader; -import nl.hauntedmc.proxyfeatures.framework.feature.meta.BaseMeta; +import nl.hauntedmc.proxyfeatures.features.FeatureContext; +import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import org.junit.jupiter.api.Test; -import java.util.List; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; class FeatureDescriptorTest { @Test - void createMetaBuildsFreshInstancesFromMetaClass() { - FeatureDescriptor descriptor = new FeatureDescriptor( - "Queue", - "example.QueueFeature", - MutableMeta.class, - "Queue", - "1.0", - Set.of("Friends"), - Set.of("luckperms") - ); - - BaseMeta first = descriptor.createMeta(); - BaseMeta second = descriptor.createMeta(); - - assertNotSame(first, second); - assertTrue(first instanceof MutableMeta); - assertTrue(second instanceof MutableMeta); + void createUsesTypedConstructorAndValidatesResultType() { + FeatureContext context = org.mockito.Mockito.mock(FeatureContext.class); + TestFeature expected = org.mockito.Mockito.mock(TestFeature.class); + FeatureDescriptor descriptor = descriptor(ignored -> expected); + + assertSame(expected, descriptor.create(context)); + assertEquals(Set.of("Friends"), descriptor.featureDependencies()); + assertEquals(Set.of("luckperms"), descriptor.pluginDependencies()); } @Test - void createMetaFallsBackToImmutableSnapshotWhenOnlyDescriptorDataExists() { - FeatureDescriptor descriptor = new FeatureDescriptor( - "Queue", - "example.QueueFeature", - "Queue", - "1.0", - Set.of("Friends"), - Set.of("luckperms") - ); - - BaseMeta first = descriptor.createMeta(); - BaseMeta second = descriptor.createMeta(); + void createRejectsNullOrWrongImplementation() { + FeatureContext context = org.mockito.Mockito.mock(FeatureContext.class); + assertThrows(IllegalStateException.class, () -> descriptor(ignored -> null).create(context)); - assertNotSame(first, second); - assertEquals("Queue", first.getFeatureName()); - assertEquals("1.0", first.getFeatureVersion()); - assertEquals(List.of("Friends"), first.getDependencies()); - assertEquals(List.of("luckperms"), first.getPluginDependencies()); - assertEquals(first.getDependencies(), second.getDependencies()); - assertEquals(first.getPluginDependencies(), second.getPluginDependencies()); + FeatureDescriptor wrong = new FeatureDescriptor( + "Queue", "Queue", "1.0", TestFeature.class, + ignored -> org.mockito.Mockito.mock(OtherFeature.class), Set.of(), Set.of() + ); + assertThrows(IllegalStateException.class, () -> wrong.create(context)); } - public static final class MutableMeta implements BaseMeta { - @Override - public String getFeatureName() { - return "Queue"; - } - - @Override - public String getFeatureVersion() { - return "1.0"; - } + private static FeatureDescriptor descriptor( + java.util.function.Function constructor + ) { + return new FeatureDescriptor( + "Queue", "Queue", "1.0", TestFeature.class, constructor, + Set.of("Friends", "Queue"), Set.of("luckperms") + ); + } - @Override - public List getDependencies() { - return List.of(); + private abstract static class TestFeature extends VelocityBaseFeature { + private TestFeature(FeatureContext context) { + super(context); } + } - @Override - public List getPluginDependencies() { - return List.of(); + private abstract static class OtherFeature extends VelocityBaseFeature { + private OtherFeature(FeatureContext context) { + super(context); } } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManagerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManagerTest.java index 7317b736..9ba2d8f5 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManagerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureLoadManagerTest.java @@ -4,11 +4,12 @@ import com.velocitypowered.api.plugin.PluginManager; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import nl.hauntedmc.proxyfeatures.ProxyFeatures; +import nl.hauntedmc.proxyfeatures.api.feature.FeatureId; +import nl.hauntedmc.proxyfeatures.api.extension.MotdExtensions; import nl.hauntedmc.proxyfeatures.framework.feature.stateful.SnapshotState; import nl.hauntedmc.proxyfeatures.framework.feature.stateful.StatefulFeature; import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigMap; import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; -import nl.hauntedmc.proxyfeatures.features.FeatureFactory; import nl.hauntedmc.proxyfeatures.features.VelocityBaseFeature; import nl.hauntedmc.proxyfeatures.framework.config.FeatureConfigHandler; import nl.hauntedmc.proxyfeatures.framework.config.MainConfigHandler; @@ -25,11 +26,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InOrder; -import org.mockito.MockedStatic; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.HashMap; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; import java.util.Set; import static org.junit.jupiter.api.Assertions.*; @@ -43,9 +46,11 @@ class FeatureLoadManagerTest { private ComponentLogger logger; private PluginManager pluginManager; private FeatureScopeFactory featureScopeFactory; + private final Map> constructors = new HashMap<>(); @BeforeEach void setUp() { + constructors.clear(); plugin = mock(ProxyFeatures.class); mainConfig = mock(MainConfigHandler.class); localization = mock(LocalizationHandler.class); @@ -57,7 +62,10 @@ void setUp() { when(plugin.getLogger()).thenReturn(logger); when(plugin.getPluginManager()).thenReturn(pluginManager); when(plugin.getFeatureCatalog()).thenReturn(mock(DefaultFeatureCatalog.class)); - when(plugin.getCapabilityRegistry()).thenReturn(new DefaultCapabilityRegistry()); + DefaultCapabilityRegistry capabilityRegistry = new DefaultCapabilityRegistry(); + capabilityRegistry.register(FeatureId.of("core"), MotdExtensions.class, mock(MotdExtensions.class)); + when(plugin.capabilities()).thenReturn(capabilityRegistry); + when(plugin.getCapabilityRegistry()).thenReturn(capabilityRegistry); when(plugin.getInternalServiceRegistry()).thenReturn(new InternalServiceRegistry()); when(mainConfig.isFeatureEnabled(anyString())).thenReturn(true); when(mainConfig.openFeatureConfig(anyString())).thenReturn(mock(FeatureConfigHandler.class)); @@ -105,34 +113,28 @@ void enableFeatureCoversAllResultTypes() { assertEquals(FeatureEnableResult.NOT_FOUND, manager.enableFeature("Missing").result()); - VelocityBaseFeature loaded = feature("Queue", List.of(), List.of()); + VelocityBaseFeature loaded = feature("Queue", List.of(), List.of()); manager.getFeatureRegistry().registerAvailableFeature(descriptor("Queue", Set.of(), Set.of())); manager.getFeatureRegistry().registerLoadedFeature("Queue", loaded); assertEquals(FeatureEnableResult.ALREADY_LOADED, manager.enableFeature("Queue").result()); manager.getFeatureRegistry().deregisterLoadedFeature("Queue"); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(null); - assertEquals(FeatureEnableResult.FAILED, manager.enableFeature("Queue").result()); - } + constructorResult("Queue", null); + assertEquals(FeatureEnableResult.FAILED, manager.enableFeature("Queue").result()); clearRegistry(manager); manager.getFeatureRegistry().registerAvailableFeature(descriptor("Queue", Set.of(), Set.of("luckperms"))); - VelocityBaseFeature missingPlugin = feature("Queue", List.of(), List.of("luckperms")); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(missingPlugin); - when(pluginManager.getPlugin("luckperms")).thenReturn(Optional.empty()); - assertEquals(FeatureEnableResult.MISSING_PLUGIN_DEPENDENCY, manager.enableFeature("Queue").result()); - } + VelocityBaseFeature missingPlugin = feature("Queue", List.of(), List.of("luckperms")); + constructorResult("Queue", missingPlugin); + when(pluginManager.getPlugin("luckperms")).thenReturn(Optional.empty()); + assertEquals(FeatureEnableResult.MISSING_PLUGIN_DEPENDENCY, manager.enableFeature("Queue").result()); clearRegistry(manager); manager.getFeatureRegistry().registerAvailableFeature(descriptor("Queue", Set.of("Friends"), Set.of())); - VelocityBaseFeature missingFeature = feature("Queue", List.of("Friends"), List.of()); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(missingFeature); - assertEquals(FeatureEnableResult.MISSING_FEATURE_DEPENDENCY, manager.enableFeature("Queue").result()); - } + VelocityBaseFeature missingFeature = feature("Queue", List.of("Friends"), List.of()); + constructorResult("Queue", missingFeature); + assertEquals(FeatureEnableResult.MISSING_FEATURE_DEPENDENCY, manager.enableFeature("Queue").result()); clearRegistry(manager); manager.getFeatureRegistry().registerAvailableFeature(descriptor("Queue", Set.of(), Set.of())); @@ -171,8 +173,8 @@ void disableSoftReloadAndReloadHandleSuccessAndFailures() { assertEquals(FeatureSoftReloadResult.NOT_LOADED, manager.softReloadFeature("Queue").result()); assertEquals(FeatureReloadResult.NOT_LOADED, manager.reloadFeature("Queue").result()); - VelocityBaseFeature queue = feature("Queue", List.of(), List.of()); - VelocityBaseFeature dependent = feature("Dependent", List.of("Queue"), List.of()); + VelocityBaseFeature queue = feature("Queue", List.of(), List.of()); + VelocityBaseFeature dependent = feature("Dependent", List.of("Queue"), List.of()); doThrow(new RuntimeException("cleanup failed")).when(dependent).cleanup(); manager.getFeatureRegistry().registerLoadedFeature("Queue", queue); manager.getFeatureRegistry().registerLoadedFeature("Dependent", dependent); @@ -180,17 +182,17 @@ void disableSoftReloadAndReloadHandleSuccessAndFailures() { assertEquals(FeatureDisableResult.SUCCESS, manager.disableFeature("Queue").result()); clearRegistry(manager); - VelocityBaseFeature failDisable = feature("Fail", List.of(), List.of()); + VelocityBaseFeature failDisable = feature("Fail", List.of(), List.of()); doThrow(new RuntimeException("boom")).when(failDisable).cleanup(); manager.getFeatureRegistry().registerLoadedFeature("Fail", failDisable); assertEquals(FeatureDisableResult.FAILED, manager.disableFeature("Fail").result()); clearRegistry(manager); - VelocityBaseFeature soft = feature("Soft", List.of(), List.of()); + VelocityBaseFeature soft = feature("Soft", List.of(), List.of()); manager.getFeatureRegistry().registerLoadedFeature("Soft", soft); assertEquals(FeatureSoftReloadResult.SUCCESS, manager.softReloadFeature("Soft").result()); - VelocityBaseFeature softFail = feature("SoftFail", List.of(), List.of()); + VelocityBaseFeature softFail = feature("SoftFail", List.of(), List.of()); FeatureConfigHandler cfg = mock(FeatureConfigHandler.class); when(softFail.getConfigHandler()).thenReturn(cfg); doThrow(new RuntimeException("boom")).when(cfg).reloadConfig(); @@ -204,8 +206,8 @@ void reloadFeatureCoversFailedAndSuccessPathsIncludingDependents() { FeatureLoadManager manager = spy(base); clearRegistry(manager); - VelocityBaseFeature queue = feature("Queue", List.of(), List.of()); - VelocityBaseFeature dependent = feature("Dependent", List.of("Queue"), List.of()); + VelocityBaseFeature queue = feature("Queue", List.of(), List.of()); + VelocityBaseFeature dependent = feature("Dependent", List.of("Queue"), List.of()); manager.getFeatureRegistry().registerLoadedFeature("Queue", queue); manager.getFeatureRegistry().registerLoadedFeature("Dependent", dependent); @@ -231,17 +233,15 @@ void reloadFeatureCapturesAndRestoresFrameworkManagedReloadState() { manager.getFeatureRegistry().registerAvailableFeature(descriptor("Queue", Set.of(), Set.of())); - VelocityBaseFeature oldFeature = reloadStatefulFeature("Queue", List.of(), List.of()); + VelocityBaseFeature oldFeature = reloadStatefulFeature("Queue", List.of(), List.of()); TestReloadState snapshot = new TestReloadState("payload"); when(reloadStateful(oldFeature).captureReloadState()).thenReturn(Optional.of(snapshot)); - VelocityBaseFeature reloadedFeature = reloadStatefulFeature("Queue", List.of(), List.of()); + VelocityBaseFeature reloadedFeature = reloadStatefulFeature("Queue", List.of(), List.of()); manager.getFeatureRegistry().registerLoadedFeature("Queue", oldFeature); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(reloadedFeature); - assertEquals(FeatureReloadResult.SUCCESS, manager.reloadFeature("Queue").result()); - } + constructorResult("Queue", reloadedFeature); + assertEquals(FeatureReloadResult.SUCCESS, manager.reloadFeature("Queue").result()); verify(reloadStateful(reloadedFeature)).restoreReloadState(snapshot); assertSame(reloadedFeature, manager.getFeatureRegistry().getLoadedFeature("Queue")); @@ -254,16 +254,14 @@ void reloadFeatureCapturesStateBeforeCleanup() { manager.getFeatureRegistry().registerAvailableFeature(descriptor("Queue", Set.of(), Set.of())); - VelocityBaseFeature oldFeature = reloadStatefulFeature("Queue", List.of(), List.of()); + VelocityBaseFeature oldFeature = reloadStatefulFeature("Queue", List.of(), List.of()); when(reloadStateful(oldFeature).captureReloadState()).thenReturn(Optional.of(new TestReloadState("payload"))); - VelocityBaseFeature reloadedFeature = reloadStatefulFeature("Queue", List.of(), List.of()); + VelocityBaseFeature reloadedFeature = reloadStatefulFeature("Queue", List.of(), List.of()); manager.getFeatureRegistry().registerLoadedFeature("Queue", oldFeature); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(reloadedFeature); - assertEquals(FeatureReloadResult.SUCCESS, manager.reloadFeature("Queue").result()); - } + constructorResult("Queue", reloadedFeature); + assertEquals(FeatureReloadResult.SUCCESS, manager.reloadFeature("Queue").result()); InOrder order = inOrder(oldFeature); order.verify(reloadStateful(oldFeature)).captureReloadState(); @@ -277,18 +275,16 @@ void reloadFeatureFailsAndCleansUpWhenReloadStateRestoreFails() { manager.getFeatureRegistry().registerAvailableFeature(descriptor("Queue", Set.of(), Set.of())); - VelocityBaseFeature oldFeature = reloadStatefulFeature("Queue", List.of(), List.of()); + VelocityBaseFeature oldFeature = reloadStatefulFeature("Queue", List.of(), List.of()); TestReloadState snapshot = new TestReloadState("payload"); when(reloadStateful(oldFeature).captureReloadState()).thenReturn(Optional.of(snapshot)); - VelocityBaseFeature reloadedFeature = reloadStatefulFeature("Queue", List.of(), List.of()); + VelocityBaseFeature reloadedFeature = reloadStatefulFeature("Queue", List.of(), List.of()); doThrow(new RuntimeException("restore failed")).when(reloadStateful(reloadedFeature)).restoreReloadState(snapshot); manager.getFeatureRegistry().registerLoadedFeature("Queue", oldFeature); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(reloadedFeature); - assertEquals(FeatureReloadResult.FAILED, manager.reloadFeature("Queue").result()); - } + constructorResult("Queue", reloadedFeature); + assertEquals(FeatureReloadResult.FAILED, manager.reloadFeature("Queue").result()); verify(reloadedFeature).cleanup(); assertFalse(manager.getFeatureRegistry().isFeatureLoaded("Queue")); @@ -299,7 +295,7 @@ void reloadFeatureFailsWhenStateCaptureFails() { FeatureLoadManager manager = new FeatureLoadManager(plugin, featureScopeFactory); clearRegistry(manager); - VelocityBaseFeature oldFeature = reloadStatefulFeature("Queue", List.of(), List.of()); + VelocityBaseFeature oldFeature = reloadStatefulFeature("Queue", List.of(), List.of()); doThrow(new RuntimeException("capture failed")).when(reloadStateful(oldFeature)).captureReloadState(); manager.getFeatureRegistry().registerLoadedFeature("Queue", oldFeature); @@ -320,55 +316,42 @@ void loadFeatureCoversRegistrationDependencyAndInitializationBranches() { assertFalse(manager.loadFeature("Queue")); manager.getFeatureRegistry().deregisterLoadedFeature("Queue"); - VelocityBaseFeature feature = feature("Queue", List.of(), List.of()); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(null); - assertFalse(manager.loadFeature("Queue")); - } + constructorResult("Queue", null); + assertFalse(manager.loadFeature("Queue")); clearInvocations(mainConfig, localization); when(mainConfig.isFeatureEnabled("Queue")).thenReturn(false); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - assertFalse(manager.loadFeature("Queue")); - factory.verifyNoInteractions(); - } + assertFalse(manager.loadFeature("Queue")); clearRegistry(manager); manager.getFeatureRegistry().registerAvailableFeature(descriptor("Queue", Set.of(), Set.of("required"))); when(mainConfig.isFeatureEnabled("Queue")).thenReturn(true); when(pluginManager.getPlugin("required")).thenReturn(Optional.empty()); - VelocityBaseFeature depsFeature = feature("Queue", List.of(), List.of("required")); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(depsFeature); - assertFalse(manager.loadFeature("Queue")); - } + VelocityBaseFeature depsFeature = feature("Queue", List.of(), List.of("required")); + constructorResult("Queue", depsFeature); + assertFalse(manager.loadFeature("Queue")); clearRegistry(manager); manager.getFeatureRegistry().registerAvailableFeature(descriptor("Queue", Set.of(), Set.of())); when(pluginManager.getPlugin("required")).thenReturn(Optional.of(mock(PluginContainer.class))); - VelocityBaseFeature success = feature("Queue", List.of(), List.of()); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(success); - assertTrue(manager.loadFeature("Queue")); - assertTrue(manager.getFeatureRegistry().isFeatureLoaded("Queue")); - } + VelocityBaseFeature success = feature("Queue", List.of(), List.of()); + constructorResult("Queue", success); + assertTrue(manager.loadFeature("Queue")); + assertTrue(manager.getFeatureRegistry().isFeatureLoaded("Queue")); manager.getFeatureRegistry().deregisterLoadedFeature("Queue"); - VelocityBaseFeature initFail = feature("Queue", List.of(), List.of()); + VelocityBaseFeature initFail = feature("Queue", List.of(), List.of()); doThrow(new RuntimeException("boom")).when(initFail).initialize(); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(initFail); - assertFalse(manager.loadFeature("Queue")); - verify(initFail).cleanup(); - } + constructorResult("Queue", initFail); + assertFalse(manager.loadFeature("Queue")); + verify(initFail).cleanup(); - VelocityBaseFeature initFailCleanupFail = feature("Queue", List.of(), List.of()); + VelocityBaseFeature initFailCleanupFail = feature("Queue", List.of(), List.of()); doThrow(new RuntimeException("boom")).when(initFailCleanupFail).initialize(); doThrow(new RuntimeException("cleanup boom")).when(initFailCleanupFail).cleanup(); - try (MockedStatic factory = mockStatic(FeatureFactory.class)) { - factory.when(() -> FeatureFactory.createFeature(anyString(), any())).thenReturn(initFailCleanupFail); - assertFalse(manager.loadFeature("Queue")); - } + constructorResult("Queue", initFailCleanupFail); + assertFalse(manager.loadFeature("Queue")); + } @Test @@ -376,8 +359,8 @@ void unloadAllFeaturesHandlesCleanupErrorsAndNullEntries() { FeatureLoadManager manager = new FeatureLoadManager(plugin, featureScopeFactory); clearRegistry(manager); - VelocityBaseFeature ok = feature("Queue", List.of(), List.of()); - VelocityBaseFeature failing = feature("Fail", List.of(), List.of()); + VelocityBaseFeature ok = feature("Queue", List.of(), List.of()); + VelocityBaseFeature failing = feature("Fail", List.of(), List.of()); doThrow(new RuntimeException("boom")).when(failing).cleanup(); manager.getFeatureRegistry().registerLoadedFeature("Queue", ok); @@ -388,8 +371,8 @@ void unloadAllFeaturesHandlesCleanupErrorsAndNullEntries() { assertTrue(manager.getFeatureRegistry().getLoadedFeatureNames().isEmpty()); } - private VelocityBaseFeature feature(String name, List dependencies, List pluginDependencies) { - VelocityBaseFeature feature = mock(VelocityBaseFeature.class); + private VelocityBaseFeature feature(String name, List dependencies, List pluginDependencies) { + VelocityBaseFeature feature = mock(VelocityBaseFeature.class); when(feature.getFeatureName()).thenReturn(name); when(feature.getDependencies()).thenReturn(dependencies); when(feature.getPluginDependencies()).thenReturn(pluginDependencies); @@ -402,8 +385,8 @@ private VelocityBaseFeature feature(String name, List dependencies, L return feature; } - private VelocityBaseFeature reloadStatefulFeature(String name, List dependencies, List pluginDependencies) { - VelocityBaseFeature feature = mock(VelocityBaseFeature.class, withSettings().extraInterfaces(StatefulFeature.class)); + private VelocityBaseFeature reloadStatefulFeature(String name, List dependencies, List pluginDependencies) { + VelocityBaseFeature feature = mock(VelocityBaseFeature.class, withSettings().extraInterfaces(StatefulFeature.class)); when(feature.getFeatureName()).thenReturn(name); when(feature.getDependencies()).thenReturn(dependencies); when(feature.getPluginDependencies()).thenReturn(pluginDependencies); @@ -417,7 +400,7 @@ private VelocityBaseFeature reloadStatefulFeature(String name, List d } @SuppressWarnings("unchecked") - private StatefulFeature reloadStateful(VelocityBaseFeature feature) { + private StatefulFeature reloadStateful(VelocityBaseFeature feature) { return (StatefulFeature) feature; } @@ -434,16 +417,25 @@ private void clearRegistry(FeatureLoadManager manager) { } private FeatureDescriptor descriptor(String name, Set featureDependencies, Set pluginDependencies) { + AtomicReference constructor = constructors.computeIfAbsent( + name, + ignored -> new AtomicReference<>(feature(name, List.copyOf(featureDependencies), List.copyOf(pluginDependencies))) + ); return new FeatureDescriptor( name, - VelocityBaseFeature.class.getName(), name, "1.0", + VelocityBaseFeature.class, + ignored -> constructor.get(), featureDependencies, pluginDependencies ); } + private void constructorResult(String name, VelocityBaseFeature feature) { + constructors.computeIfAbsent(name, ignored -> new AtomicReference<>()).set(feature); + } + private record TestReloadState(String value) implements SnapshotState { } } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureRegistryTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureRegistryTest.java index 04cb759f..74a841ca 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureRegistryTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/FeatureRegistryTest.java @@ -15,7 +15,7 @@ class FeatureRegistryTest { void tracksAvailableAndLoadedFeatures() { FeatureRegistry registry = new FeatureRegistry(); - VelocityBaseFeature loaded = mock(VelocityBaseFeature.class); + VelocityBaseFeature loaded = mock(VelocityBaseFeature.class); FeatureDescriptor descriptor = new FeatureDescriptor( "Queue", VelocityBaseFeature.class.getName(), @@ -38,7 +38,7 @@ void tracksAvailableAndLoadedFeatures() { void deregisterRemovesLoadedFeatureAndLoadedFeaturesReturnsCopy() { FeatureRegistry registry = new FeatureRegistry(); - VelocityBaseFeature loaded = mock(VelocityBaseFeature.class); + VelocityBaseFeature loaded = mock(VelocityBaseFeature.class); FeatureDescriptor descriptor = new FeatureDescriptor( "Queue", VelocityBaseFeature.class.getName(), @@ -51,7 +51,7 @@ void deregisterRemovesLoadedFeatureAndLoadedFeaturesReturnsCopy() { registry.registerAvailableFeature(descriptor); registry.registerLoadedFeature("Queue", loaded); - List> copy = registry.getLoadedFeatures(); + List copy = registry.getLoadedFeatures(); copy.clear(); assertEquals(1, registry.getLoadedFeatures().size()); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/dependency/FeatureDependencyManagerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/dependency/FeatureDependencyManagerTest.java index 631a608c..4cf5751d 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/dependency/FeatureDependencyManagerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/loader/dependency/FeatureDependencyManagerTest.java @@ -91,10 +91,10 @@ void pluginDependencyChecksAreDelegated() { @Test void getDependentFeaturesReturnsLoadedFeaturesThatDependOnTarget() { - VelocityBaseFeature queue = mock(VelocityBaseFeature.class); + VelocityBaseFeature queue = mock(VelocityBaseFeature.class); when(queue.getDependencies()).thenReturn(List.of()); - VelocityBaseFeature friends = mock(VelocityBaseFeature.class); + VelocityBaseFeature friends = mock(VelocityBaseFeature.class); when(friends.getDependencies()).thenReturn(List.of("Queue")); registry.registerAvailableFeature(descriptor("Queue", Set.of(), Set.of())); @@ -107,10 +107,10 @@ void getDependentFeaturesReturnsLoadedFeaturesThatDependOnTarget() { @Test void getDependentFeaturesSkipsNullLoadedEntries() { - VelocityBaseFeature queue = mock(VelocityBaseFeature.class); + VelocityBaseFeature queue = mock(VelocityBaseFeature.class); when(queue.getDependencies()).thenReturn(List.of()); - VelocityBaseFeature friends = mock(VelocityBaseFeature.class); + VelocityBaseFeature friends = mock(VelocityBaseFeature.class); when(friends.getDependencies()).thenReturn(List.of("Queue")); registry.registerAvailableFeature(descriptor("Queue", Set.of(), Set.of())); diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandlerTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandlerTest.java index 989e8803..8e34c7fa 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandlerTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/localization/LocalizationHandlerTest.java @@ -8,7 +8,6 @@ import nl.hauntedmc.proxyfeatures.test.MutableCapabilityRegistry; import nl.hauntedmc.proxyfeatures.ProxyFeatures; import nl.hauntedmc.proxyfeatures.toolkit.io.config.ConfigService; -import nl.hauntedmc.proxyfeatures.toolkit.io.localization.Language; import nl.hauntedmc.proxyfeatures.toolkit.io.localization.MessageMap; import nl.hauntedmc.proxyfeatures.api.capability.player.PlayerLanguageApi; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReferenceResolverTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReferenceResolverTest.java index ac5104f6..fafa4b16 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReferenceResolverTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/persistence/PlayerReferenceResolverTest.java @@ -27,7 +27,7 @@ class PlayerReferenceResolverTest { @Test - void resolveManagedUsesActiveIdentityWithoutQueryingPersistence() { + void resolveReferenceUsesActiveIdentityWithoutQueryingPersistence() { PlayerDirectory directory = mock(PlayerDirectory.class); Session session = mock(Session.class); UUID uuid = UUID.randomUUID(); @@ -35,7 +35,7 @@ void resolveManagedUsesActiveIdentityWithoutQueryingPersistence() { when(directory.findActiveIdentityCached(uuid)).thenReturn(Optional.of(identity)); - PlayerReference result = new PlayerReferenceResolver(directory).resolveManaged(session, uuid); + PlayerReference result = new PlayerReferenceResolver(directory).resolveReference(uuid); assertEquals(PlayerReference.from(identity), result); verify(directory, never()).findByUuid(uuid); @@ -43,7 +43,7 @@ void resolveManagedUsesActiveIdentityWithoutQueryingPersistence() { } @Test - void resolveManagedUsesPersistedIdentityFromBackgroundWorkerWhenPlayerIsOffline() throws Exception { + void resolveReferenceUsesPersistedIdentityFromBackgroundWorkerWhenPlayerIsOffline() throws Exception { PlayerDirectory directory = mock(PlayerDirectory.class); Session session = mock(Session.class); UUID uuid = UUID.randomUUID(); @@ -53,7 +53,7 @@ void resolveManagedUsesPersistedIdentityFromBackgroundWorkerWhenPlayerIsOffline( when(directory.findByUuid(uuid)).thenReturn(CompletableFuture.completedFuture(Optional.of(identity))); PlayerReferenceResolver resolver = new PlayerReferenceResolver(directory); - PlayerReference result = runOnThread("ProxyFeatures-Votifier-worker", () -> resolver.resolveManaged(session, uuid)); + PlayerReference result = runOnThread("ProxyFeatures-Votifier-worker", () -> resolver.resolveReference(uuid)); assertEquals(PlayerReference.from(identity), result); verify(directory).findByUuid(uuid); @@ -99,7 +99,7 @@ void asyncLookupQueriesPersistenceWithoutBlockingEventThread() throws Exception } @Test - void resolveManagedReturnsNullWhenPersistedIdentityDoesNotExist() throws Exception { + void resolveReferenceReturnsNullWhenPersistedIdentityDoesNotExist() throws Exception { PlayerDirectory directory = mock(PlayerDirectory.class); Session session = mock(Session.class); UUID uuid = UUID.randomUUID(); @@ -108,7 +108,7 @@ void resolveManagedReturnsNullWhenPersistedIdentityDoesNotExist() throws Excepti when(directory.findByUuid(uuid)).thenReturn(CompletableFuture.completedFuture(Optional.empty())); PlayerReferenceResolver resolver = new PlayerReferenceResolver(directory); - PlayerReference result = runOnThread("ProxyFeatures-worker", () -> resolver.resolveManaged(session, uuid)); + PlayerReference result = runOnThread("ProxyFeatures-worker", () -> resolver.resolveReference(uuid)); assertNull(result); verify(directory).findByUuid(uuid); @@ -162,7 +162,7 @@ void findByIdsAsyncCombinesActiveAndPersistedPlayersInRequestedOrder() { } @Test - void resolveManagedByIdUsesActiveThenPersistedIdentityAndFinallyScalarId() throws Exception { + void resolveReferenceByIdUsesActiveThenPersistedIdentityAndFinallyScalarId() throws Exception { PlayerDirectory directory = mock(PlayerDirectory.class); Session session = mock(Session.class); UUID activeUuid = UUID.randomUUID(); @@ -181,14 +181,14 @@ void resolveManagedByIdUsesActiveThenPersistedIdentityAndFinallyScalarId() throw PlayerReferenceResolver resolver = new PlayerReferenceResolver(directory); - assertEquals(PlayerReference.from(activeIdentity), resolver.resolveManagedById(session, 22L)); + assertEquals(PlayerReference.from(activeIdentity), resolver.resolveReferenceById(22L)); assertEquals( PlayerReference.from(offlineIdentity), - runOnThread("ProxyFeatures-worker", () -> resolver.resolveManagedById(session, 23L)) + runOnThread("ProxyFeatures-worker", () -> resolver.resolveReferenceById(23L)) ); assertEquals( PlayerReference.byId(24L), - runOnThread("ProxyFeatures-worker", () -> resolver.resolveManagedById(session, 24L)) + runOnThread("ProxyFeatures-worker", () -> resolver.resolveReferenceById(24L)) ); verifyNoInteractions(session); } diff --git a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalogTest.java b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalogTest.java index 4fa2c3f1..11ecf4e6 100644 --- a/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalogTest.java +++ b/proxyfeatures-platform-velocity/src/test/java/nl/hauntedmc/proxyfeatures/framework/service/DefaultFeatureCatalogTest.java @@ -1,6 +1,5 @@ package nl.hauntedmc.proxyfeatures.framework.service; -import nl.hauntedmc.proxyfeatures.api.ProxyFeaturesApiVersion; import nl.hauntedmc.proxyfeatures.api.feature.*; import org.junit.jupiter.api.Test; @@ -57,24 +56,6 @@ void failuresExposeAUsefulMessageAndUnknownTransitionsFailFast() { assertThrows(NullPointerException.class, () -> catalog.fail(queue, null)); } - @Test - void rootFacadeRetainsItsStableDependencies() { - DefaultCapabilityRegistry capabilities = new DefaultCapabilityRegistry(); - DefaultFeatureCatalog features = new DefaultFeatureCatalog(); - ProxyFeaturesApiVersion version = ProxyFeaturesApiVersion.current("runtime"); - DefaultProxyFeaturesApi api = new DefaultProxyFeaturesApi(version, capabilities, features); - - assertSame(version, api.version()); - assertSame(capabilities, api.capabilities()); - assertSame(features, api.features()); - assertThrows(NullPointerException.class, - () -> new DefaultProxyFeaturesApi(null, capabilities, features)); - assertThrows(NullPointerException.class, - () -> new DefaultProxyFeaturesApi(version, null, features)); - assertThrows(NullPointerException.class, - () -> new DefaultProxyFeaturesApi(version, capabilities, null)); - } - private static FeatureDescriptor descriptor(String id) { return new FeatureDescriptor( FeatureId.of(id), diff --git a/proxyfeatures-toolkit/pom.xml b/proxyfeatures-toolkit/pom.xml index 2996048f..35ac2989 100644 --- a/proxyfeatures-toolkit/pom.xml +++ b/proxyfeatures-toolkit/pom.xml @@ -18,6 +18,11 @@ adventure-api provided + + net.kyori + adventure-key + provided + net.kyori adventure-text-minimessage diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClient.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/http/HttpTransport.java similarity index 97% rename from proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClient.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/http/HttpTransport.java index 9314716f..85c78b33 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClient.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/http/HttpTransport.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.http; +package nl.hauntedmc.proxyfeatures.toolkit.http; import java.io.IOException; import java.io.InputStream; @@ -13,7 +13,7 @@ import java.util.Objects; import java.util.stream.Collectors; -public final class SimpleHttpClient { +public final class HttpTransport { private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(5); private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(8); private static final int MAX_RESPONSE_BYTES = 1024 * 1024; @@ -23,7 +23,7 @@ public final class SimpleHttpClient { .followRedirects(HttpClient.Redirect.NORMAL) .build(); - private SimpleHttpClient() { + private HttpTransport() { } public static String post(String url, List args) throws IOException, InterruptedException { diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigView.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigView.java index 2399f149..9e5103e7 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigView.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/io/config/ConfigView.java @@ -136,8 +136,8 @@ public void appendToList(String dottedPath, Object value) { list.add(value); n.set(list); file.saveNow(); - } catch (SerializationException | RuntimeException ex) { - // Keep legacy behavior for list nodes lacking serializer support. + } catch (SerializationException | RuntimeException exception) { + throw new IllegalStateException("Unable to append configuration list: " + p, exception); } finally { file.lock().writeLock().unlock(); } diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtils.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/json/JsonStrings.java similarity index 80% rename from proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtils.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/json/JsonStrings.java index abdbbb56..5766a385 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtils.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/json/JsonStrings.java @@ -1,6 +1,6 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.parse; +package nl.hauntedmc.proxyfeatures.toolkit.json; -public class JsonUtils { +public class JsonStrings { /** * Escapes special characters in a string for JSON payloads. diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/Paginator.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/pagination/Paginator.java similarity index 93% rename from proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/Paginator.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/pagination/Paginator.java index 90bb098d..b5e2d6fa 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/Paginator.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/pagination/Paginator.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.tools; +package nl.hauntedmc.proxyfeatures.toolkit.pagination; import java.util.Collections; import java.util.List; diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatterns.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/TextPatterns.java similarity index 97% rename from proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatterns.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/TextPatterns.java index d50b4061..ce9a9e6e 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatterns.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/TextPatterns.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text; +package nl.hauntedmc.proxyfeatures.toolkit.text; import java.time.format.DateTimeFormatter; import java.util.regex.Pattern; diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatter.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/ComponentFormatter.java similarity index 99% rename from proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatter.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/ComponentFormatter.java index 987cfde6..f394e02a 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatter.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/ComponentFormatter.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.format; +package nl.hauntedmc.proxyfeatures.toolkit.text.format; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -10,8 +10,8 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.TextPatterns; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.constants.FormatConstants; +import nl.hauntedmc.proxyfeatures.toolkit.text.TextPatterns; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.constants.FormatConstants; import java.util.*; import java.util.function.UnaryOperator; diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatter.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/TextFormatter.java similarity index 98% rename from proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatter.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/TextFormatter.java index 752019cd..e56a56fd 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatter.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/TextFormatter.java @@ -1,9 +1,9 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.format; +package nl.hauntedmc.proxyfeatures.toolkit.text.format; import net.kyori.adventure.text.minimessage.MiniMessage; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.TextPatterns; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.color.LegacyColorUtils; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.constants.FormatConstants; +import nl.hauntedmc.proxyfeatures.toolkit.text.TextPatterns; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.color.LegacyColorUtils; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.constants.FormatConstants; import java.util.EnumSet; import java.util.Objects; diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtils.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/color/LegacyColorUtils.java similarity index 98% rename from proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtils.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/color/LegacyColorUtils.java index 3b864a4e..6adba73f 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtils.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/color/LegacyColorUtils.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.color; +package nl.hauntedmc.proxyfeatures.toolkit.text.format.color; import java.util.Map; diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstants.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/constants/FormatConstants.java similarity index 82% rename from proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstants.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/constants/FormatConstants.java index 9fa01472..e1e4a830 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstants.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/constants/FormatConstants.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.constants; +package nl.hauntedmc.proxyfeatures.toolkit.text.format.constants; public class FormatConstants { public static final char AMP_CHAR = '&'; diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspector.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/inspect/FormatInspector.java similarity index 96% rename from proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspector.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/inspect/FormatInspector.java index 22757ec4..6e8ed3ad 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspector.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/inspect/FormatInspector.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.inspect; +package nl.hauntedmc.proxyfeatures.toolkit.text.format.inspect; import net.kyori.adventure.text.*; import net.kyori.adventure.text.event.ClickEvent; @@ -6,8 +6,8 @@ import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.TextPatterns; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.TextPatterns; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.TextFormatter; import java.util.EnumSet; import java.util.Objects; diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholders.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/placeholder/MessagePlaceholders.java similarity index 98% rename from proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholders.java rename to proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/placeholder/MessagePlaceholders.java index ec43afa7..eacac67a 100644 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholders.java +++ b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/text/placeholder/MessagePlaceholders.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder; +package nl.hauntedmc.proxyfeatures.toolkit.text.placeholder; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; diff --git a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtils.java b/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtils.java deleted file mode 100644 index bf14141b..00000000 --- a/proxyfeatures-toolkit/src/main/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtils.java +++ /dev/null @@ -1,29 +0,0 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.type; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class CastUtils { - - private CastUtils() { - } - - public static List safeCastToList(Object obj, Class clazz) { - if (obj instanceof List rawList) { - List result = new ArrayList<>(); - for (Object item : rawList) { - if (item == null) { - throw new ClassCastException("Expected a " + clazz.getName() + ", but found: null"); - } - if (clazz.isInstance(item)) { - result.add(clazz.cast(item)); - } else { - throw new ClassCastException("Expected a " + clazz.getName() + ", but found: " + item.getClass().getName()); - } - } - return result; - } - return Collections.emptyList(); - } -} diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClientTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/http/HttpTransportTest.java similarity index 77% rename from proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClientTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/http/HttpTransportTest.java index c645aec7..2e1f0644 100644 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/http/SimpleHttpClientTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/http/HttpTransportTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.http; +package nl.hauntedmc.proxyfeatures.toolkit.http; import org.junit.jupiter.api.Test; @@ -10,7 +10,6 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; -import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -19,7 +18,7 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -class SimpleHttpClientTest { +class HttpTransportTest { @Test void postReturnsBodyFor2xxUsingInjectedHttpClient() throws Exception { @@ -31,10 +30,10 @@ void postReturnsBodyFor2xxUsingInjectedHttpClient() throws Exception { when(mockClient.send(any(HttpRequest.class), org.mockito.ArgumentMatchers.>any())).thenReturn(response); - String body = SimpleHttpClient.post("http://example.test/path", + String body = HttpTransport.post("http://example.test/path", List.of( - new SimpleHttpClient.FormParameter("a b", "x/y"), - new SimpleHttpClient.FormParameter("nullable", null) + new HttpTransport.FormParameter("a b", "x/y"), + new HttpTransport.FormParameter("nullable", null) ), mockClient); assertEquals("ok", body); @@ -51,9 +50,9 @@ void postThrowsForNon2xxAndOversizedResponses() throws Exception { org.mockito.ArgumentMatchers.>any())).thenReturn(non2xx); assertThrows(IOException.class, - () -> SimpleHttpClient.post( + () -> HttpTransport.post( "http://example.test/error", - List.of(new SimpleHttpClient.FormParameter("k", "v")), + List.of(new HttpTransport.FormParameter("k", "v")), mockClient )); @@ -65,17 +64,17 @@ void postThrowsForNon2xxAndOversizedResponses() throws Exception { org.mockito.ArgumentMatchers.>any())).thenReturn(oversized); assertThrows(IOException.class, - () -> SimpleHttpClient.post("http://example.test/large", List.of(), mockClient)); + () -> HttpTransport.post("http://example.test/large", List.of(), mockClient)); } @Test void postRejectsUnsupportedSchemesAndNullUrl() { Exception ex = assertThrows(IOException.class, - () -> SimpleHttpClient.post("file:///tmp/x", List.of(), mock(HttpClient.class))); + () -> HttpTransport.post("file:///tmp/x", List.of(), mock(HttpClient.class))); assertTrue(ex.getMessage().contains("Unsupported URI scheme")); assertThrows(NullPointerException.class, - () -> SimpleHttpClient.post(null, List.of(new SimpleHttpClient.FormParameter("a b", "x/y")))); + () -> HttpTransport.post(null, List.of(new HttpTransport.FormParameter("a b", "x/y")))); } @Test @@ -83,9 +82,9 @@ void postHttpsRejectsInsecureRequestAndRedirectDowngrade() throws Exception { HttpClient mockClient = mock(HttpClient.class); IOException insecureRequest = assertThrows(IOException.class, - () -> SimpleHttpClient.postHttps( + () -> HttpTransport.postHttps( "http://example.test/path", - List.of(new SimpleHttpClient.FormParameter("k", "v")), + List.of(new HttpTransport.FormParameter("k", "v")), mockClient )); assertTrue(insecureRequest.getMessage().contains("HTTPS is required")); @@ -99,7 +98,7 @@ void postHttpsRejectsInsecureRequestAndRedirectDowngrade() throws Exception { org.mockito.ArgumentMatchers.>any())).thenReturn(downgraded); IOException redirect = assertThrows(IOException.class, - () -> SimpleHttpClient.postHttps( + () -> HttpTransport.postHttps( "https://example.test/path", List.of(), mockClient @@ -109,11 +108,11 @@ void postHttpsRejectsInsecureRequestAndRedirectDowngrade() throws Exception { @Test void formParametersRequireNames() { - assertThrows(NullPointerException.class, () -> new SimpleHttpClient.FormParameter(null, "value")); + assertThrows(NullPointerException.class, () -> new HttpTransport.FormParameter(null, "value")); assertThrows(NullPointerException.class, - () -> SimpleHttpClient.post("http://example.test", null, mock(HttpClient.class))); + () -> HttpTransport.post("http://example.test", null, mock(HttpClient.class))); assertThrows(NullPointerException.class, - () -> SimpleHttpClient.post("http://example.test", Collections.singletonList(null), + () -> HttpTransport.post("http://example.test", Collections.singletonList(null), mock(HttpClient.class))); } } diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtilsTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/json/JsonStringsTest.java similarity index 59% rename from proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtilsTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/json/JsonStringsTest.java index d6e86647..3c818e27 100644 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/parse/JsonUtilsTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/json/JsonStringsTest.java @@ -1,14 +1,14 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.parse; +package nl.hauntedmc.proxyfeatures.toolkit.json; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -class JsonUtilsTest { +class JsonStringsTest { @Test void escapeJsonEscapesBackslashQuotesAndNewlines() { String input = "a\\b\"c\nd"; - assertEquals("a\\\\b\\\"c\\nd", JsonUtils.escapeJson(input)); + assertEquals("a\\\\b\\\"c\\nd", JsonStrings.escapeJson(input)); } } diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/PaginatorTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/pagination/PaginatorTest.java similarity index 95% rename from proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/PaginatorTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/pagination/PaginatorTest.java index 5f0fae72..0d2c4535 100644 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/tools/PaginatorTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/pagination/PaginatorTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.tools; +package nl.hauntedmc.proxyfeatures.toolkit.pagination; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatternsTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/TextPatternsTest.java similarity index 96% rename from proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatternsTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/TextPatternsTest.java index a4749bbd..f0663eea 100644 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/TextPatternsTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/TextPatternsTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text; +package nl.hauntedmc.proxyfeatures.toolkit.text; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatterTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/ComponentFormatterTest.java similarity index 98% rename from proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatterTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/ComponentFormatterTest.java index 512631a9..58f353a7 100644 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/ComponentFormatterTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/ComponentFormatterTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.format; +package nl.hauntedmc.proxyfeatures.toolkit.text.format; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -6,7 +6,7 @@ import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.inspect.FormatInspector; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.inspect.FormatInspector; import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatterTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/TextFormatterTest.java similarity index 98% rename from proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatterTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/TextFormatterTest.java index 5efb7529..4f2237ba 100644 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/TextFormatterTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/TextFormatterTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.format; +package nl.hauntedmc.proxyfeatures.toolkit.text.format; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtilsTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/color/LegacyColorUtilsTest.java similarity index 93% rename from proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtilsTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/color/LegacyColorUtilsTest.java index ee21fe5d..1f6d90c7 100644 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/color/LegacyColorUtilsTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/color/LegacyColorUtilsTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.color; +package nl.hauntedmc.proxyfeatures.toolkit.text.format.color; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstantsTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/constants/FormatConstantsTest.java similarity index 88% rename from proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstantsTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/constants/FormatConstantsTest.java index 4867ed5c..0d7ac102 100644 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/constants/FormatConstantsTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/constants/FormatConstantsTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.constants; +package nl.hauntedmc.proxyfeatures.toolkit.text.format.constants; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspectorTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/inspect/FormatInspectorTest.java similarity index 96% rename from proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspectorTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/inspect/FormatInspectorTest.java index 3b708a54..c42c5299 100644 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/format/inspect/FormatInspectorTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/format/inspect/FormatInspectorTest.java @@ -1,11 +1,11 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.format.inspect; +package nl.hauntedmc.proxyfeatures.toolkit.text.format.inspect; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; -import nl.hauntedmc.proxyfeatures.toolkit.util.text.format.TextFormatter; +import nl.hauntedmc.proxyfeatures.toolkit.text.format.TextFormatter; import org.junit.jupiter.api.Test; import java.util.EnumSet; diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholdersTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/placeholder/MessagePlaceholdersTest.java similarity index 97% rename from proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholdersTest.java rename to proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/placeholder/MessagePlaceholdersTest.java index 63d1bf9a..89f53e8e 100644 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/text/placeholder/MessagePlaceholdersTest.java +++ b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/text/placeholder/MessagePlaceholdersTest.java @@ -1,4 +1,4 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.text.placeholder; +package nl.hauntedmc.proxyfeatures.toolkit.text.placeholder; import net.kyori.adventure.text.Component; import org.junit.jupiter.api.Test; diff --git a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtilsTest.java b/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtilsTest.java deleted file mode 100644 index bfa236c8..00000000 --- a/proxyfeatures-toolkit/src/test/java/nl/hauntedmc/proxyfeatures/toolkit/util/type/CastUtilsTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package nl.hauntedmc.proxyfeatures.toolkit.util.type; - -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class CastUtilsTest { - - @Test - void safeCastToListReturnsTypedListWhenAllItemsMatch() { - List result = CastUtils.safeCastToList(List.of("a", "b"), String.class); - assertEquals(List.of("a", "b"), result); - } - - @Test - void safeCastToListThrowsWhenItemTypeMismatches() { - assertThrows(ClassCastException.class, () -> CastUtils.safeCastToList(List.of("a", 1), String.class)); - } - - @Test - void safeCastToListThrowsClassCastForNullItems() { - ClassCastException ex = assertThrows(ClassCastException.class, - () -> CastUtils.safeCastToList(java.util.Arrays.asList("a", null), String.class)); - assertEquals("Expected a java.lang.String, but found: null", ex.getMessage()); - } - - @Test - void safeCastToListReturnsEmptyWhenNotAList() { - assertEquals(List.of(), CastUtils.safeCastToList("not-a-list", String.class)); - } -} From 6aeb6f80152fa94d642a8b51419d7159fe4da067 Mon Sep 17 00:00:00 2001 From: Remy Duijsens Date: Thu, 6 Aug 2026 10:58:34 +0200 Subject: [PATCH 006/110] chore: stage API redesign runtime fixes --- .github/api-redesign-fix.patch.xz | Bin 0 -> 7580 bytes .../apply-api-redesign-runtime-fixes.yml | 35 ++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 .github/api-redesign-fix.patch.xz create mode 100644 .github/workflows/apply-api-redesign-runtime-fixes.yml diff --git a/.github/api-redesign-fix.patch.xz b/.github/api-redesign-fix.patch.xz new file mode 100644 index 0000000000000000000000000000000000000000..fa3d6de138472080bf48a1d7b20c6c233f4b5a8e GIT binary patch literal 7580 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