├── .gitignore ├── .idea └── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── LICENSE ├── README.md ├── build.gradle.kts ├── devtools ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── gg │ └── tropic │ └── practice │ ├── PracticeDevTools.kt │ ├── command │ └── MapManageCommands.kt │ ├── feature │ └── CloudSyncFeature.kt │ └── map │ └── MapManageServices.kt ├── docs ├── infrastructure.md └── setup.md ├── game ├── build.gradle.kts └── src │ └── main │ ├── java │ └── gg │ │ └── tropic │ │ └── practice │ │ ├── command │ │ └── ChunkDumpCommand.java │ │ ├── games │ │ └── ranked │ │ │ └── PotPvPEloCalculator.java │ │ └── utilities │ │ └── PlayerRespawnUtilities.java │ └── kotlin │ └── gg │ └── tropic │ └── practice │ ├── Extensions.kt │ ├── PracticeGame.kt │ ├── autoscale │ └── ReplicationAutoScaleTask.kt │ ├── command │ ├── NoSpeedCommand.kt │ └── ReplicationHealthCommand.kt │ ├── cooldown │ └── EnderPearlCooldown.kt │ ├── expectation │ └── ExpectationService.kt │ ├── feature │ ├── ApolloFeature.kt │ ├── CloudSyncFeature.kt │ └── GameReportFeature.kt │ ├── games │ ├── GameExt.kt │ ├── GameImpl.kt │ ├── GameService.kt │ ├── event │ │ └── GameStartEvent.kt │ ├── loadout │ │ ├── CustomLoadout.kt │ │ ├── DefaultLoadout.kt │ │ └── SelectedLoadout.kt │ ├── ranked │ │ ├── CalculationResult.kt │ │ └── EloCalculator.kt │ └── tasks │ │ ├── GameStartTask.kt │ │ └── GameStopTask.kt │ ├── integration │ └── StaffReportsDataProvider.kt │ ├── map │ ├── BuiltMapReplication.kt │ ├── MapReplicationService.kt │ └── ReadyMapTemplate.kt │ ├── metrics │ ├── GameServerCollection.kt │ ├── MetricsService.kt │ └── collectors │ │ ├── PlayerDistributionCollector.kt │ │ └── ReplicationCountCollector.kt │ ├── resources │ ├── DuelsNametagImpl.kt │ ├── DuelsVisibilityImpl.kt │ └── GameScoreboardAdapter.kt │ └── services │ └── SpectateRequestService.kt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lobby ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── gg │ └── tropic │ └── practice │ ├── PracticeLobby.kt │ ├── category │ ├── SettingExt.kt │ └── visibility │ │ └── SpawnPlayerVisibility.kt │ ├── commands │ ├── DuelCommands.kt │ ├── DuelGamesCommand.kt │ ├── DuelRequestsCommand.kt │ ├── KitEditorCommand.kt │ ├── LeaderboardsCommand.kt │ ├── SpawnCommand.kt │ ├── SpectateCommand.kt │ ├── StatResetTokenCommand.kt │ ├── StatisticsCommand.kt │ ├── SwitchLobbyServerCommand.kt │ ├── TogglePlayerVisibilityCommand.kt │ ├── ToggleSpectatorsCommand.kt │ ├── TournamentCommand.kt │ └── admin │ │ ├── LobbyAdminCommands.kt │ │ └── SpawnHologramCommand.kt │ ├── duel │ └── DuelRequestUtilities.kt │ ├── feature │ └── CloudSyncFeature.kt │ ├── hologram │ ├── AbstractScrollingLeaderboard.kt │ ├── ScrollingKitLeaderboardHologram.kt │ ├── ScrollingLeaderboardService.kt │ └── ScrollingTypeLeaderboardHologram.kt │ ├── menu │ ├── CasualQueueSelectSizeMenu.kt │ ├── DuelGamesMenu.kt │ ├── JoinQueueMenu.kt │ ├── LeaderboardsMenu.kt │ ├── PlayerMainMenu.kt │ ├── StatisticsMenu.kt │ ├── editor │ │ ├── AllowRemoveItemsWithinInventory.kt │ │ ├── EditLoadoutContentsMenu.kt │ │ ├── EditorKitSelectionMenu.kt │ │ └── ExtraContentSelectionMenu.kt │ ├── party │ │ ├── PartyPlayGameSelectMenu.kt │ │ └── PartyPlayTVTFights.kt │ ├── pipeline │ │ └── DuelRequestPipeline.kt │ ├── template │ │ ├── TemplateKitMenu.kt │ │ └── TemplateMapMenu.kt │ └── tournaments │ │ └── TournamentCreationPipeline.kt │ ├── player │ ├── IntRangeUtils.kt │ ├── LobbyPlayer.kt │ ├── LobbyPlayerExt.kt │ ├── LobbyPlayerService.kt │ ├── PlayerState.kt │ ├── hotbar │ │ └── LobbyHotbarService.kt │ └── prevention │ │ └── PreventionListeners.kt │ ├── provider │ ├── SettingProvider.kt │ └── impl │ │ ├── BasicsSettingProvider.kt │ │ └── LemonSettingProvider.kt │ ├── queue │ └── QueueService.kt │ ├── scoreboard │ ├── LobbyScoreboardAdapter.kt │ └── ScoreboardInfoService.kt │ └── statresets │ └── StatResetTokens.kt ├── renovate.json ├── services ├── application │ ├── api │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── gg │ │ │ └── tropic │ │ │ └── practice │ │ │ └── application │ │ │ └── api │ │ │ ├── DPSDataSync.kt │ │ │ ├── DPSDataSyncKeys.kt │ │ │ ├── DPSDataSyncSource.kt │ │ │ ├── DPSRedisService.kt │ │ │ ├── DPSRedisShared.kt │ │ │ └── defaults │ │ │ ├── game │ │ │ ├── ImmutableAbstractGame.kt │ │ │ └── ImmutableDuelExpectation.kt │ │ │ ├── kit │ │ │ ├── ImmutableKit.kt │ │ │ ├── ImmutableKitContainer.kt │ │ │ ├── KitDataSync.kt │ │ │ └── group │ │ │ │ ├── ImmutableKitContainer.kt │ │ │ │ ├── ImmutableKitGroup.kt │ │ │ │ └── KitGroupDataSync.kt │ │ │ └── map │ │ │ ├── ImmutableMap.kt │ │ │ ├── ImmutableMapContainer.kt │ │ │ └── MapDataSync.kt │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── gg │ │ └── tropic │ │ └── practice │ │ └── application │ │ └── ApplicationServer.kt ├── games │ └── game-manager │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ └── kotlin │ │ └── gg │ │ └── tropic │ │ └── practice │ │ └── games │ │ └── manager │ │ └── GameManager.kt ├── queue │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── gg │ │ └── tropic │ │ └── practice │ │ └── queue │ │ ├── GameQueue.kt │ │ └── GameQueueManager.kt ├── replications │ ├── replication-manager │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── gg │ │ │ └── tropic │ │ │ └── practice │ │ │ └── replications │ │ │ └── manager │ │ │ ├── ReplicationManager.kt │ │ │ └── ReplicationModels.kt │ └── replication-models │ │ └── src │ │ └── main │ │ └── kotlin │ │ └── gg │ │ └── tropic │ │ └── practice │ │ └── replications │ │ └── models │ │ ├── Replication.kt │ │ └── ReplicationStatus.kt ├── statistics │ ├── build.gradle.kts │ ├── leaderboards │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── gg │ │ │ └── tropic │ │ │ └── practice │ │ │ └── statistics │ │ │ └── leaderboards │ │ │ ├── DateFloorUtils.kt │ │ │ ├── ImmutablePracticeProfile.kt │ │ │ ├── Leaderboard.kt │ │ │ ├── LeaderboardManager.kt │ │ │ └── LeaderboardType.kt │ └── src │ │ └── main │ │ └── kotlin │ │ └── gg │ │ └── tropic │ │ └── practice │ │ └── statistics │ │ ├── ApplyUpdates.kt │ │ ├── ApplyUpdatesStateless.kt │ │ ├── Counter.kt │ │ ├── GlobalStatistics.kt │ │ ├── KitStatistics.kt │ │ ├── Volatile.kt │ │ ├── VolatileImpls.kt │ │ └── ranked │ │ └── RankedKitStatistics.kt └── tournaments │ ├── build.gradle.kts │ └── src │ └── main │ └── kotlin │ └── gg │ └── tropic │ └── practice │ └── tournaments │ ├── StateMachine.kt │ ├── Tournament.kt │ └── TournamentManager.kt ├── settings.gradle.kts └── shared ├── build.gradle.kts └── src └── main └── kotlin └── gg └── tropic └── practice ├── Environment.kt ├── PracticeShared.kt ├── commands ├── CommandExt.kt ├── KitCommands.kt ├── KitGroupCommands.kt ├── MapCommands.kt ├── MatchInventoryCommand.kt ├── RegionCommand.kt ├── admin │ ├── MatchInfoCommand.kt │ ├── RankedBanCommand.kt │ ├── ResetStatsCommand.kt │ ├── TerminateMatchCommand.kt │ └── matchlist │ │ ├── MatchListCommand.kt │ │ └── MatchListMenu.kt ├── customizers │ ├── KitCommandCustomizers.kt │ ├── KitGroupCommandCustomizers.kt │ └── MapCommandCustomizers.kt └── menu │ └── MapRatingOverviewMenu.kt ├── configuration ├── PracticeConfiguration.kt ├── PracticeConfigurationService.kt └── subconfig │ └── DataSampleThresholds.kt ├── expectation └── GameExpectation.kt ├── friendship ├── FriendshipRequirement.kt ├── FriendshipStateSetting.kt ├── Friendships.kt ├── NoOpFriendshipRequirement.kt └── PluginFriendshipRequirement.kt ├── games ├── AbstractGame.kt ├── GameReference.kt ├── GameReport.kt ├── GameReportSnapshot.kt ├── GameReportStatus.kt ├── GameState.kt ├── GameStatus.kt ├── GameStatusIndexes.kt ├── duels │ └── DuelRequest.kt ├── spectate │ └── SpectateRequest.kt └── team │ ├── GameTeam.kt │ └── GameTeamSide.kt ├── guilds ├── GuildPluginGuildProvider.kt ├── GuildProvider.kt ├── Guilds.kt └── NoOpGuildProvider.kt ├── integration └── StaffReportsService.kt ├── kit ├── Kit.kt ├── KitContainer.kt ├── KitService.kt ├── feature │ └── FeatureFlag.kt └── group │ ├── KitGroup.kt │ ├── KitGroupContainer.kt │ └── KitGroupService.kt ├── leaderboards └── Leaderboards.kt ├── libraries └── SharedLibraries.kt ├── map ├── Map.kt ├── MapContainer.kt ├── MapService.kt ├── metadata │ ├── AbstractMapMetadata.kt │ ├── anonymous │ │ ├── Bounds.kt │ │ ├── BukkitExt.kt │ │ └── Position.kt │ ├── impl │ │ ├── MapLevelMetadata.kt │ │ ├── MapSpawnMetadata.kt │ │ └── MapZoneMetadata.kt │ ├── scanner │ │ ├── AbstractMapMetadataScanner.kt │ │ ├── MetadataScannerUtilities.kt │ │ └── impl │ │ │ ├── MapLevelMetadataScanner.kt │ │ │ ├── MapSpawnMetadataScanner.kt │ │ │ └── MapZoneMetadataScanner.kt │ └── sign │ │ ├── MapSignMetadataModel.kt │ │ └── SignParserUtilities.kt ├── rating │ ├── MapRating.kt │ └── MapRatingService.kt └── utilities │ ├── MapBlockFaceUtilities.kt │ ├── MapMetadata.kt │ └── MapMetadataScanUtilities.kt ├── party └── WParty.kt ├── profile ├── PracticeProfile.kt ├── PracticeProfileService.kt ├── loadout │ └── Loadout.kt └── ranked │ └── RankedBan.kt ├── queue ├── MinMaxRangedNumber.kt ├── QueueEntry.kt ├── QueueState.kt └── QueueType.kt ├── region ├── PlayerRegionFromRedisProxy.kt └── Region.kt ├── reports ├── GameReportService.kt └── menu │ ├── PlayerViewMenu.kt │ ├── SelectPlayerMenu.kt │ └── utility │ └── RomanNumerals.kt ├── serializable └── Message.kt ├── services ├── GameManagerService.kt ├── LeaderboardManagerService.kt ├── MIPPlayerCache.kt ├── ReplicationManagerService.kt ├── ScoreboardTitleService.kt └── TournamentManagerService.kt ├── settings ├── ChatVisibility.kt ├── DuelsSettingCategory.kt ├── SettingsExt.kt ├── particles │ └── FlightEffectSetting.kt ├── restriction │ └── RangeRestriction.kt └── scoreboard │ ├── LobbyScoreboardView.kt │ └── ScoreboardStyle.kt ├── tournaments ├── ScheduledMatchList.kt ├── TournamentConfig.kt ├── TournamentMember.kt └── TournamentMemberList.kt └── utilities ├── Items.kt └── Ping.kt /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scala Practice 2 | A highly-scalable practice plugin used on the `potpvp.com` and `solara.gg` servers. 3 | 4 | ## Docs 5 | - [Infrastructure Overview](docs/infrastructure.md) 6 | - [Setup](docs/setup.md) 7 | 8 | ----- 9 | ![yklogo](https://github.com/scalagg/scpracticemi/assets/62861393/45cbbfbd-f936-4347-8516-61ca24a0d1f4) 10 | 11 | YourKit supports open source projects with innovative and intelligent tools 12 | for monitoring and profiling Java and .NET applications. 13 | YourKit is the creator of YourKit Java Profiler, 14 | YourKit .NET Profiler, 15 | and YourKit YouMonitor. 16 | for monitoring and profiling Java and .NET applications. 17 | 18 | ----- 19 | ## Credits 20 | - GrowlyX 21 | - DripW 22 | - Elb1to 23 | -------------------------------------------------------------------------------- /devtools/build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven("https://repo.glaremasters.me/repository/concuncan/") 3 | maven("https://repo.dmulloy2.net/repository/public/") 4 | } 5 | 6 | dependencies { 7 | api(project(":shared")) 8 | compileOnly("com.grinderwolf:slimeworldmanager-plugin:2.2.1") 9 | } 10 | -------------------------------------------------------------------------------- /devtools/src/main/kotlin/gg/tropic/practice/PracticeDevTools.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice 2 | 3 | import gg.scala.commons.ExtendedScalaPlugin 4 | import gg.scala.commons.core.plugin.* 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 9/22/2023 9 | */ 10 | @Plugin( 11 | name = "TropicPractice-DevTools", 12 | version = "%remote%/%branch%/%id%" 13 | ) 14 | @PluginAuthor("Tropic") 15 | @PluginWebsite("https://tropic.gg") 16 | @PluginDependencyComposite( 17 | PluginDependency("scala-commons"), 18 | PluginDependency("Lemon"), 19 | PluginDependency("ScBasics", soft = true) 20 | ) 21 | class PracticeDevTools : ExtendedScalaPlugin() 22 | { 23 | init 24 | { 25 | PracticeShared 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /devtools/src/main/kotlin/gg/tropic/practice/feature/CloudSyncFeature.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.feature 2 | 3 | import gg.scala.cloudsync.shared.discovery.CloudSyncDiscoveryService 4 | import gg.scala.commons.agnostic.sync.ServerSync 5 | import gg.scala.commons.annotations.plugin.SoftDependency 6 | import gg.scala.flavor.service.Configure 7 | import gg.scala.flavor.service.Service 8 | import gg.scala.flavor.service.ignore.IgnoreAutoScan 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 8/5/2022 13 | */ 14 | @Service 15 | @IgnoreAutoScan 16 | @SoftDependency("cloudsync") 17 | object CloudSyncFeature 18 | { 19 | @Configure 20 | fun configure() 21 | { 22 | CloudSyncDiscoveryService 23 | .discoverable.assets.add( 24 | "gg.tropic.practice:devtools:TropicPractice-devtools${ 25 | if ("dev" in ServerSync.getLocalGameServer().groups) ":gradle-dev" else "" 26 | }" 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /devtools/src/main/kotlin/gg/tropic/practice/map/MapManageServices.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map 2 | 3 | import com.grinderwolf.swm.api.SlimePlugin 4 | import com.grinderwolf.swm.api.loaders.SlimeLoader 5 | import gg.scala.flavor.inject.Inject 6 | import gg.scala.flavor.service.Configure 7 | import gg.scala.flavor.service.Service 8 | import gg.tropic.practice.PracticeDevTools 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 9/22/2023 13 | */ 14 | @Service 15 | object MapManageServices 16 | { 17 | @Inject 18 | lateinit var plugin: PracticeDevTools 19 | 20 | lateinit var slimePlugin: SlimePlugin 21 | lateinit var loader: SlimeLoader 22 | 23 | @Configure 24 | fun configure() 25 | { 26 | slimePlugin = plugin.server.pluginManager 27 | .getPlugin("SlimeWorldManager") as SlimePlugin 28 | 29 | loader = slimePlugin.getLoader("mongodb") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/infrastructure.md: -------------------------------------------------------------------------------- 1 | # Infrastructure 2 | 3 | **Components:** 4 | - **Game Server:** Hosts a certain amount of games each. Runs the plugin built from the game module. 5 | - **Lobby Server:** Frontend for all other components listed here. 6 | - **Application:** Standalone application computing data shared on all server instances. 7 | - **Queue:** List of players for each GameType, TeamSize & Kit tuple which is iterated through and matched based on the amount of players in the queue entry, and other constraints (matchmaking settings like ping/ELO restrictions). Each queue is iterated through independently. 8 | - **Leaderboards:** Runs and caches leaderboards for all player profiles. Caches leaderboard entries in a sorted set in Redis. 9 | - **GameManager:** Keeps statuses of ongoing games for each game server. Similar to ReplicationManager's behavior, games are invalidated if no status update is received by its server in under 2 seconds of its last update. 10 | - **ReplicationManager:** Keeps statuses of available map replications for each game server. 11 | - If it does not receive another status update within 2 seconds, the game server is marked as unhealthy and replications are not used for any new matches. 12 | 13 | **Game Servers:** 14 | - Contains replications of Maps. Maps and replications are decoupled. Replications exist within a game server's lifetime, and more specifically, a single game's lifetime. The model for a single replication in code is a `BuiltMapReplication`. The replications are built from the map template. Map world templates and the data model itself is decoupled, meaning the template can be updated without the need for Map synchronization. 15 | - Map world templates are stored in GridFS buckets in MongoDB through SlimeWorldManager. The model for an in-memory map template that can be used to generation replications in code is a `ReadyMapTemplate`. 16 | - 16 replications for each available map is generated on startup to be used for any incoming maps. 17 | - Each game server pushes its local replication status to the ReplicationManager every half-second. 18 | - **Replications:** 19 | - Have globally unique IDs. 20 | - Are broadcast to the ReplicationManager. 21 | - Are bound to (when in use) to a game instance. 22 | - Are invalidated when the game instance bound to it is disposed of. 23 | - **Generation:** 24 | - 8 of each map is generated on startup. Replications are generated on-demand if a replication request is received and there is no available replication. The generation process is fairly quick, and can be run asynchronously. 25 | -------------------------------------------------------------------------------- /docs/setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | **Required at least:** 4 | - *(optional)* A lobby server 5 | - A DevTools server 6 | - Ensure SWM `async-world-generation` is `false` on your DevTools server. 7 | - Ensure MongoDB details are configured properly for SWM on **all game and devtools** server instances. 8 | - Ensure the `TropicPractice-DevTools` plugin is in your DevTools server. 9 | 10 | **Setting up a map:** 11 | - Create a new SWM world in your Mongo database using `/swm create mongo`. 12 | - Paste in your map. **DO NOT ADD YOUR METADATA SIGNS YET!** 13 | - Use the command `/mapmanage create ` using the same `templateName` you used to create you SWM template world. 14 | - You will be then teleported to a locally-generated copy of your SWM template world. **SET UP YOUR METADATA SIGNS NOW.** A prompt will be started in chat asking you to go to the lowest/highest corners and type something in chat. Once this is done, your map should be saved. 15 | - **Metadata:** 16 | - Metadata comes in the form of signs. You can place these signs in any orientation except against blocks (they must be standalone signs). The first line should be the metadata type (i.e. `[spawn]`), and the second line can be the metadata ID (i.e. `a` or `b`). These signs are scanned, stored, and then removed from the world on map creation. 17 | 18 | **Managing maps:** 19 | - To edit the map data model itself, you can join any of your practice instances. You can then use the `/map` command to edit anything map related. 20 | 21 | **Setting up a kit:** 22 | - Use the `/kit create ` command to create a new kit. 23 | - **Feature Flags:** 24 | - Control the behavior of kits when used in a game and when shown to players in frontend components of the plugin. Feature flags can be added with or without additional context (metadata) which is given in the form of a pair of Strings. 25 | - **Example:** 26 | - `/kit features add nodebuff PlaceBlocks`: Adds the `PlaceBlocks` feature flag with no metadata. 27 | - `/kit features add nodebuff ExpirePlacedBlocksAfterNSeconds`: Adds the `ExpirePlacedBlocksAfterNSeconds` with default metadata. 28 | - `/kit features metadata add nodebuff ExpirePlacedBlocksAfterNSeconds time 10`: Adds the `time=10` metadata to the `ExpirePlacedBlocksAfterNSeconds` feature flag. 29 | -------------------------------------------------------------------------------- /game/build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven("https://repo.glaremasters.me/repository/concuncan/") 3 | maven("https://repo.dmulloy2.net/repository/public/") 4 | maven("https://repo.lunarclient.dev") 5 | } 6 | 7 | dependencies { 8 | api(project(":shared")) 9 | api(project(":services:statistics")) 10 | api(project(":services:replications:replication-models")) 11 | 12 | compileOnly("dev.cubxity.plugins:unifiedmetrics-api:0.3.8") 13 | compileOnly("com.grinderwolf:slimeworldmanager-plugin:2.2.1") 14 | compileOnly("com.comphenix.protocol:ProtocolLib:4.7.0") 15 | 16 | compileOnly("gg.tropic.game.extensions:tropic-core-game-extensions:1.2.8") 17 | 18 | compileOnly("com.lunarclient:apollo-api:1.0.6") 19 | compileOnly("com.lunarclient:apollo-extra-adventure4:1.0.6") 20 | } 21 | -------------------------------------------------------------------------------- /game/src/main/java/gg/tropic/practice/games/ranked/PotPvPEloCalculator.java: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.ranked; 2 | 3 | import gg.scala.commons.annotations.inject.AutoBind; 4 | import org.jetbrains.annotations.Contract; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * @author Colin McDonald 9 | * @since 2/18/2017 10 | */ 11 | @AutoBind 12 | public final class PotPvPEloCalculator implements EloCalculator { 13 | 14 | public static final PotPvPEloCalculator INSTANCE = 15 | new PotPvPEloCalculator(35.0, 7, 25, 7, 25); 16 | 17 | private final double kPower; 18 | private final int minEloGain; 19 | private final int maxEloGain; 20 | private final int minEloLoss; 21 | private final int maxEloLoss; 22 | 23 | public PotPvPEloCalculator(double kPower, int minEloGain, int maxEloGain, int minEloLoss, int maxEloLoss) { 24 | this.kPower = kPower; 25 | this.minEloGain = minEloGain; 26 | this.maxEloGain = maxEloGain; 27 | this.minEloLoss = minEloLoss; 28 | this.maxEloLoss = maxEloLoss; 29 | } 30 | 31 | @Contract("_, _ -> new") 32 | public @NotNull CalculationResult calculate(int winnerElo, int loserElo) { 33 | double winnerQ = Math.pow(10, ((double) winnerElo) / 300D); 34 | double loserQ = Math.pow(10, ((double) loserElo) / 300D); 35 | 36 | double winnerE = winnerQ / (winnerQ + loserQ); 37 | double loserE = loserQ / (winnerQ + loserQ); 38 | 39 | int winnerGain = (int) (kPower * (1 - winnerE)); 40 | int loserGain = (int) (kPower * (0 - loserE)); 41 | 42 | winnerGain = Math.min(winnerGain, maxEloGain); 43 | winnerGain = Math.max(winnerGain, minEloGain); 44 | 45 | // loserGain will be negative so pay close attention here 46 | loserGain = Math.min(loserGain, -minEloLoss); 47 | loserGain = Math.max(loserGain, -maxEloLoss); 48 | 49 | return new CalculationResult(winnerElo, winnerGain, loserElo, loserGain); 50 | } 51 | 52 | @NotNull 53 | @Override 54 | public CalculationResult getNewRating(int winnerElo, int loserElo) { 55 | double winnerQ = Math.pow(10, ((double) winnerElo) / 300D); 56 | double loserQ = Math.pow(10, ((double) loserElo) / 300D); 57 | 58 | double winnerE = winnerQ / (winnerQ + loserQ); 59 | double loserE = loserQ / (winnerQ + loserQ); 60 | 61 | int winnerGain = (int) (kPower * (1 - winnerE)); 62 | int loserGain = (int) (kPower * (0 - loserE)); 63 | 64 | winnerGain = Math.min(winnerGain, maxEloGain); 65 | winnerGain = Math.max(winnerGain, minEloGain); 66 | 67 | // loserGain will be negative so pay close attention here 68 | loserGain = Math.min(loserGain, -minEloLoss); 69 | loserGain = Math.max(loserGain, -maxEloLoss); 70 | 71 | return new CalculationResult(winnerElo, winnerGain, loserElo, loserGain); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /game/src/main/java/gg/tropic/practice/utilities/PlayerRespawnUtilities.java: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.utilities; 2 | 3 | import net.minecraft.server.v1_8_R3.EnumDifficulty; 4 | import net.minecraft.server.v1_8_R3.PacketPlayOutRespawn; 5 | import net.minecraft.server.v1_8_R3.WorldSettings; 6 | import net.minecraft.server.v1_8_R3.WorldType; 7 | import org.bukkit.Location; 8 | import org.bukkit.entity.Player; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import static xyz.xenondevs.particle.utils.ReflectionUtils.sendPacket; 12 | 13 | /** 14 | * @author GrowlyX 15 | * @since 12/24/2023 16 | */ 17 | public class PlayerRespawnUtilities { 18 | 19 | public static void mockPlayerRespawn(@NotNull final Player player) { 20 | final byte actualDimension = (byte) player.getWorld().getEnvironment().getId(); 21 | final EnumDifficulty difficulty = switch (player.getWorld().getDifficulty()) { 22 | case PEACEFUL -> EnumDifficulty.PEACEFUL; 23 | case EASY -> EnumDifficulty.EASY; 24 | case HARD -> EnumDifficulty.HARD; 25 | default -> EnumDifficulty.NORMAL; 26 | }; 27 | final WorldType worldType = switch (player.getWorld().getWorldType()) { 28 | case NORMAL -> WorldType.NORMAL; 29 | case VERSION_1_1 -> WorldType.NORMAL_1_1; 30 | case LARGE_BIOMES -> WorldType.LARGE_BIOMES; 31 | case AMPLIFIED -> WorldType.AMPLIFIED; 32 | default -> WorldType.FLAT; 33 | }; 34 | final WorldSettings.EnumGamemode gameMode = switch (player.getGameMode()) { 35 | case CREATIVE -> WorldSettings.EnumGamemode.CREATIVE; 36 | case ADVENTURE -> WorldSettings.EnumGamemode.ADVENTURE; 37 | default -> WorldSettings.EnumGamemode.SURVIVAL; 38 | }; 39 | 40 | final Location location = player.getLocation(); 41 | sendPacket( 42 | player, 43 | new PacketPlayOutRespawn( 44 | actualDimension, difficulty, 45 | worldType, gameMode 46 | ) 47 | ); 48 | player.teleport(location); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/Extensions.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice 2 | 3 | import org.bukkit.GameMode 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 8/5/2022 9 | */ 10 | fun Player.resetAttributes() 11 | { 12 | this.health = this.maxHealth 13 | 14 | this.foodLevel = 20 15 | this.saturation = 12.8f 16 | this.maximumNoDamageTicks = 20 17 | this.fireTicks = 0 18 | this.fallDistance = 0.0f 19 | this.level = 0 20 | this.exp = 0.0f 21 | this.walkSpeed = 0.2f 22 | this.inventory.heldItemSlot = 0 23 | this.allowFlight = false 24 | 25 | this.inventory.clear() 26 | this.inventory.armorContents = null 27 | 28 | this.closeInventory() 29 | 30 | this.gameMode = GameMode.SURVIVAL 31 | this.fireTicks = 0 32 | 33 | for (potionEffect in this.activePotionEffects) 34 | { 35 | this.removePotionEffect(potionEffect.type) 36 | } 37 | 38 | this.updateInventory() 39 | } 40 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/PracticeGame.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice 2 | 3 | import gg.scala.basics.plugin.profile.BasicsProfileService 4 | import gg.scala.commons.ExtendedScalaPlugin 5 | import gg.scala.commons.agnostic.sync.ServerSync 6 | import gg.scala.commons.annotations.container.ContainerEnable 7 | import gg.scala.commons.core.plugin.* 8 | import gg.scala.lemon.channel.ChatChannelService 9 | import gg.scala.lemon.redirection.aggregate.ServerAggregateHandler 10 | import gg.scala.lemon.redirection.aggregate.impl.LeastTrafficServerAggregateHandler 11 | import gg.tropic.practice.settings.ChatVisibility 12 | import gg.tropic.practice.settings.DuelsSettingCategory 13 | import org.bukkit.Bukkit 14 | 15 | /** 16 | * @author GrowlyX 17 | * @since 8/4/2022 18 | */ 19 | @Plugin( 20 | name = "TropicPractice", 21 | version = "%remote%/%branch%/%id%" 22 | ) 23 | @PluginAuthor("Tropic") 24 | @PluginWebsite("https://tropic.gg") 25 | @PluginDependencyComposite( 26 | PluginDependency("scala-commons"), 27 | PluginDependency("Lemon"), 28 | PluginDependency("SlimeWorldManager"), 29 | PluginDependency("CoreGameExtensions"), 30 | PluginDependency("ScBasics"), 31 | PluginDependency("Parties"), 32 | PluginDependency("UnifiedMetrics", soft = true), 33 | PluginDependency("cloudsync", soft = true), 34 | PluginDependency("Friends", soft = true), 35 | PluginDependency("ScStaff", soft = true), 36 | PluginDependency("Apollo-Bukkit", soft = true) 37 | ) 38 | class PracticeGame : ExtendedScalaPlugin() 39 | { 40 | init 41 | { 42 | PracticeShared 43 | } 44 | 45 | @ContainerEnable 46 | fun containerEnable() 47 | { 48 | devProvider = { 49 | "mipgameDEV" in ServerSync.getLocalGameServer().groups 50 | } 51 | 52 | ChatChannelService.default 53 | .displayToPlayer { player, other -> 54 | val chatVisibility = BasicsProfileService.find(other) 55 | ?.setting( 56 | "${DuelsSettingCategory.DUEL_SETTING_PREFIX}:chat-visibility", 57 | ChatVisibility.Global 58 | ) 59 | ?: ChatVisibility.Global 60 | 61 | val bukkitPlayer = Bukkit.getPlayer(player) 62 | when (chatVisibility) 63 | { 64 | ChatVisibility.Global -> true 65 | ChatVisibility.Match -> bukkitPlayer != null && bukkitPlayer.world.name == other.world.name 66 | } 67 | } 68 | 69 | val lobbyRedirector = LeastTrafficServerAggregateHandler( 70 | lobbyGroup().suffixWhenDev() 71 | ) 72 | lobbyRedirector.subscribe() 73 | 74 | flavor { 75 | bind() to lobbyRedirector 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/autoscale/ReplicationAutoScaleTask.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.autoscale 2 | 3 | import gg.scala.commons.ExtendedScalaPlugin 4 | import gg.scala.flavor.inject.Inject 5 | import gg.scala.flavor.service.Close 6 | import gg.scala.flavor.service.Configure 7 | import gg.scala.flavor.service.Service 8 | import gg.tropic.practice.map.Map 9 | import gg.tropic.practice.map.MapReplicationService 10 | import gg.tropic.practice.map.MapService 11 | import me.lucko.helper.Events 12 | import me.lucko.helper.Schedulers 13 | import java.util.logging.Level 14 | 15 | /** 16 | * @author GrowlyX 17 | * @since 1/1/2024 18 | */ 19 | @Service(priority = 30) 20 | object ReplicationAutoScaleTask : Thread("replication-auto-scale") 21 | { 22 | @Inject 23 | lateinit var plugin: ExtendedScalaPlugin 24 | 25 | private const val TARGET_REPLICATIONS = 32 26 | private const val FLOOR_REQUIRED_FOR_AUTO_SCALE = 0.85 27 | 28 | var lastAutoScaleCheck = 0L 29 | 30 | var lastAutoScaleEvent = 0L 31 | var lastAutoScaleEventCount = 0 32 | 33 | private fun runSilently() 34 | { 35 | if (!plugin.isEnabled) 36 | { 37 | return 38 | } 39 | 40 | val mappings = mutableMapOf() 41 | for (map in MapService.maps()) 42 | { 43 | val replications = MapReplicationService 44 | .findAllAvailableReplications(map) 45 | 46 | if (replications.size <= TARGET_REPLICATIONS * FLOOR_REQUIRED_FOR_AUTO_SCALE) 47 | { 48 | mappings[map] = TARGET_REPLICATIONS - replications.size 49 | } 50 | } 51 | 52 | if (mappings.isNotEmpty()) 53 | { 54 | MapReplicationService 55 | .generateMapReplications(mappings) 56 | .thenAccept { 57 | val generated = mappings.values.sum() 58 | lastAutoScaleEvent = System.currentTimeMillis() 59 | lastAutoScaleEventCount = generated 60 | 61 | plugin.logger.info("Generated $generated new map replications to comply with auto-scale policy of $FLOOR_REQUIRED_FOR_AUTO_SCALE.") 62 | } 63 | .join() 64 | } 65 | 66 | lastAutoScaleCheck = System.currentTimeMillis() 67 | } 68 | 69 | override fun run() 70 | { 71 | while (true) 72 | { 73 | runCatching(::runSilently) 74 | .onFailure { 75 | plugin.logger.log( 76 | Level.SEVERE, 77 | "Failed to auto scale replications", 78 | it 79 | ) 80 | } 81 | 82 | sleep(1000L) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/command/NoSpeedCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.command 2 | 3 | import gg.scala.commons.acf.ConditionFailedException 4 | import gg.scala.commons.acf.annotation.CommandAlias 5 | import gg.scala.commons.annotations.commands.AutoRegister 6 | import gg.scala.commons.command.ScalaCommand 7 | import gg.scala.commons.issuer.ScalaPlayer 8 | import net.evilblock.cubed.util.CC 9 | import org.bukkit.potion.PotionEffectType 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 10/28/2023 14 | */ 15 | @AutoRegister 16 | object NoSpeedCommand : ScalaCommand() 17 | { 18 | @CommandAlias("nospeed") 19 | fun onNoSpeed(player: ScalaPlayer) 20 | { 21 | if (!player.bukkit().hasPotionEffect(PotionEffectType.SPEED)) 22 | { 23 | throw ConditionFailedException("You do not have speed!") 24 | } 25 | 26 | player.bukkit().removePotionEffect(PotionEffectType.SPEED) 27 | player.sendMessage("${CC.RED}You no longer have speed!") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/command/ReplicationHealthCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.command 2 | 3 | import gg.scala.commons.acf.annotation.CommandAlias 4 | import gg.scala.commons.acf.annotation.CommandPermission 5 | import gg.scala.commons.annotations.commands.AutoRegister 6 | import gg.scala.commons.command.ScalaCommand 7 | import gg.tropic.practice.autoscale.ReplicationAutoScaleTask 8 | import net.evilblock.cubed.util.CC 9 | import net.evilblock.cubed.util.bukkit.Constants 10 | import net.evilblock.cubed.util.time.TimeUtil 11 | import org.bukkit.command.CommandSender 12 | 13 | /** 14 | * @author GrowlyX 15 | * @since 3/8/2024 16 | */ 17 | @AutoRegister 18 | object ReplicationHealthCommand : ScalaCommand() 19 | { 20 | @CommandAlias("replicationhealth") 21 | @CommandPermission("op") 22 | fun onReplicationHealth(sender: CommandSender) 23 | { 24 | sender.sendMessage( 25 | "${CC.PRI}Last autoscale check: ${CC.GREEN}${ 26 | TimeUtil.formatIntoAbbreviatedString(((System.currentTimeMillis() - ReplicationAutoScaleTask.lastAutoScaleCheck) / 1000).toInt()) 27 | }" 28 | ) 29 | 30 | sender.sendMessage( 31 | "${CC.GRAY}${Constants.THIN_VERTICAL_LINE} ${CC.WHITE}Last autoscale event: ${ 32 | TimeUtil.formatIntoAbbreviatedString(((System.currentTimeMillis() - ReplicationAutoScaleTask.lastAutoScaleEvent) / 1000).toInt()) 33 | }" 34 | ) 35 | 36 | sender.sendMessage( 37 | "${CC.GRAY}${Constants.THIN_VERTICAL_LINE} ${CC.WHITE}Last autoscale event generation count: ${ReplicationAutoScaleTask.lastAutoScaleEventCount}" 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/feature/ApolloFeature.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.feature 2 | 3 | import com.lunarclient.apollo.Apollo 4 | import com.lunarclient.apollo.module.combat.CombatModule 5 | import gg.scala.commons.annotations.plugin.SoftDependency 6 | import gg.scala.flavor.service.Configure 7 | import gg.scala.flavor.service.Service 8 | import gg.scala.flavor.service.ignore.IgnoreAutoScan 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 8/5/2022 13 | */ 14 | @Service 15 | @IgnoreAutoScan 16 | @SoftDependency("Apollo-Bukkit") 17 | object ApolloFeature 18 | { 19 | @Configure 20 | fun configure() 21 | { 22 | Apollo.getModuleManager() 23 | .getModule(CombatModule::class.java) 24 | .options.set( 25 | CombatModule.DISABLE_MISS_PENALTY, true 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/feature/CloudSyncFeature.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.feature 2 | 3 | import gg.scala.cloudsync.shared.discovery.CloudSyncDiscoveryService 4 | import gg.scala.commons.agnostic.sync.ServerSync 5 | import gg.scala.commons.annotations.plugin.SoftDependency 6 | import gg.scala.flavor.service.Configure 7 | import gg.scala.flavor.service.Service 8 | import gg.scala.flavor.service.ignore.IgnoreAutoScan 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 8/5/2022 13 | */ 14 | @Service 15 | @IgnoreAutoScan 16 | @SoftDependency("cloudsync") 17 | object CloudSyncFeature 18 | { 19 | @Configure 20 | fun configure() 21 | { 22 | CloudSyncDiscoveryService 23 | .discoverable.assets.add( 24 | "gg.tropic.practice:game:TropicPractice-game${ 25 | if ("dev" in ServerSync.getLocalGameServer().groups) ":gradle-dev" else "" 26 | }" 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/feature/GameReportFeature.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.feature 2 | 3 | import gg.scala.flavor.service.Service 4 | import gg.tropic.practice.games.GameReport 5 | import gg.tropic.practice.namespace 6 | import gg.tropic.practice.suffixWhenDev 7 | import net.evilblock.cubed.ScalaCommonsSpigot 8 | import net.evilblock.cubed.serializers.Serializers 9 | import java.util.concurrent.CompletableFuture 10 | import java.util.concurrent.TimeUnit 11 | 12 | /** 13 | * @author GrowlyX 14 | * @since 1/2/2023 15 | */ 16 | @Service 17 | object GameReportFeature 18 | { 19 | private val connection = ScalaCommonsSpigot.instance.kvConnection 20 | private val matchPersistCacheMillis = TimeUnit.DAYS.toSeconds(3L) 21 | 22 | fun saveSnapshotForAllParticipants(snapshot: GameReport): CompletableFuture 23 | { 24 | return CompletableFuture 25 | .runAsync { 26 | connection.sync().setex( 27 | "${namespace().suffixWhenDev()}:snapshots:matches:${snapshot.identifier}", 28 | matchPersistCacheMillis, 29 | Serializers.gson.toJson(snapshot) 30 | ) 31 | } 32 | .thenRun { 33 | listOf(snapshot.winners, snapshot.losers) 34 | .flatten() 35 | .forEach { 36 | connection.sync().setex( 37 | "${namespace().suffixWhenDev()}:snapshots:players:$it:matches:${snapshot.identifier}", 38 | matchPersistCacheMillis, snapshot.identifier.toString() 39 | ) 40 | } 41 | } 42 | .exceptionally { 43 | it.printStackTrace() 44 | return@exceptionally null 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/games/GameExt.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games 2 | 3 | import gg.tropic.practice.profile.PracticeProfile 4 | import gg.tropic.practice.queue.QueueType 5 | import gg.tropic.practice.statistics.KitStatistics 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 10/25/2023 10 | */ 11 | fun PracticeProfile.useKitStatistics( 12 | game: GameImpl, block: KitStatistics.() -> Unit 13 | ) 14 | { 15 | when (game.expectationModel.queueType) 16 | { 17 | QueueType.Casual -> block( 18 | getCasualStatsFor(game.kit) 19 | ) 20 | QueueType.Ranked -> block( 21 | getRankedStatsFor(game.kit) 22 | ) 23 | null -> {} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/games/event/GameStartEvent.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.event 2 | 3 | import gg.tropic.practice.games.GameImpl 4 | import gg.scala.commons.event.StatefulEvent 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 8/5/2022 9 | */ 10 | class GameStartEvent( 11 | val game: GameImpl, 12 | var cancelMessage: String = "Failed to start game" 13 | ) : StatefulEvent() 14 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/games/loadout/CustomLoadout.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.loadout 2 | 3 | import gg.tropic.practice.utilities.deepClone 4 | import gg.tropic.practice.kit.Kit 5 | import gg.tropic.practice.profile.loadout.Loadout 6 | import org.bukkit.entity.Player 7 | 8 | /** 9 | * @author GrowlyX 10 | * @since 9/25/2023 11 | */ 12 | class CustomLoadout( 13 | private val loadout: Loadout, 14 | private val kit: Kit 15 | ) : SelectedLoadout 16 | { 17 | override fun displayName() = loadout.name 18 | override fun apply(player: Player) 19 | { 20 | kit.populate(player) 21 | player.inventory.contents = loadout.inventoryContents.deepClone() 22 | player.updateInventory() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/games/loadout/DefaultLoadout.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.loadout 2 | 3 | import gg.tropic.practice.kit.Kit 4 | import net.evilblock.cubed.util.CC 5 | import org.bukkit.entity.Player 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 9/25/2023 10 | */ 11 | data class DefaultLoadout(private val kit: Kit) : SelectedLoadout 12 | { 13 | override fun displayName() = "${CC.D_GREEN}Default" 14 | override fun apply(player: Player) 15 | { 16 | kit.populateAndUpdate(player) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/games/loadout/SelectedLoadout.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.loadout 2 | 3 | import org.bukkit.entity.Player 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 9/25/2023 8 | */ 9 | interface SelectedLoadout 10 | { 11 | fun displayName(): String 12 | fun apply(player: Player) 13 | } 14 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/games/ranked/CalculationResult.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.ranked 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 12/23/2023 6 | */ 7 | data class CalculationResult( 8 | val winnerOld: Int, 9 | val winnerGain: Int, 10 | val loserOld: Int, 11 | val loserGain: Int 12 | ) 13 | { 14 | val winnerNew = winnerOld + winnerGain 15 | val loserNew = loserOld + loserGain 16 | } 17 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/games/ranked/EloCalculator.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.ranked 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 8/20/2022 6 | */ 7 | interface EloCalculator 8 | { 9 | fun getNewRating(winner: Int, loser: Int): CalculationResult 10 | } 11 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/integration/StaffReportsDataProvider.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.integration 2 | 3 | import gg.scala.commons.annotations.plugin.SoftDependency 4 | import gg.scala.flavor.service.Configure 5 | import gg.scala.flavor.service.Service 6 | import gg.scala.flavor.service.ignore.IgnoreAutoScan 7 | import gg.scala.lemon.util.QuickAccess.username 8 | import gg.scala.staff.reports.ReportProcessor 9 | import gg.scala.staff.reports.ResponseAction 10 | import gg.tropic.practice.games.GameService 11 | import gg.tropic.practice.queue.QueueType 12 | import net.evilblock.cubed.util.CC 13 | import org.bukkit.Bukkit 14 | 15 | /** 16 | * @author GrowlyX 17 | * @since 1/16/2024 18 | */ 19 | @Service 20 | @IgnoreAutoScan 21 | @SoftDependency("ScStaff") 22 | object StaffReportsDataProvider 23 | { 24 | @Configure 25 | fun configure() 26 | { 27 | ReportProcessor.provideAdditionalServerData { _, uuid -> 28 | val bukkitPlayer = Bukkit.getPlayer(uuid) 29 | ?: return@provideAdditionalServerData mapOf() 30 | 31 | val gameOfPlayer = GameService.byPlayer(bukkitPlayer) 32 | ?: return@provideAdditionalServerData mapOf() 33 | 34 | return@provideAdditionalServerData mapOf( 35 | "Kit" to gameOfPlayer.kit.displayName, 36 | "Ranked" to if (gameOfPlayer.expectationModel.queueType == QueueType.Ranked) 37 | "${CC.GREEN}Yes" else "${CC.RED}No" 38 | ) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/map/BuiltMapReplication.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map 2 | 3 | import org.bukkit.World 4 | import java.util.UUID 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 9/21/2023 9 | */ 10 | data class BuiltMapReplication( 11 | val associatedMap: Map, 12 | val world: World, 13 | var scheduledForExpectedGame: UUID? = null, 14 | var inUse: Boolean = false 15 | ) 16 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/map/ReadyMapTemplate.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map 2 | 3 | import com.grinderwolf.swm.api.world.SlimeWorld 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 9/21/2023 8 | */ 9 | data class ReadyMapTemplate(val slimeWorld: SlimeWorld) 10 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/metrics/GameServerCollection.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.metrics 2 | 3 | import dev.cubxity.plugins.metrics.api.metric.collector.CollectorCollection 4 | import gg.tropic.practice.metrics.collectors.PlayerDistributionCollector 5 | import gg.tropic.practice.metrics.collectors.ReplicationCountCollector 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 3/2/2024 10 | */ 11 | object GameServerCollection : CollectorCollection 12 | { 13 | override val collectors = listOf( 14 | ReplicationCountCollector, 15 | PlayerDistributionCollector 16 | ) 17 | 18 | override val isAsync = true 19 | } 20 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/metrics/MetricsService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.metrics 2 | 3 | import dev.cubxity.plugins.metrics.api.UnifiedMetrics 4 | import gg.scala.commons.annotations.plugin.SoftDependency 5 | import gg.scala.flavor.inject.Inject 6 | import gg.scala.flavor.service.Configure 7 | import gg.scala.flavor.service.Service 8 | import gg.scala.flavor.service.ignore.IgnoreAutoScan 9 | import gg.tropic.practice.PracticeGame 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 3/2/2024 14 | */ 15 | @Service 16 | @IgnoreAutoScan 17 | @SoftDependency("UnifiedMetrics") 18 | object MetricsService 19 | { 20 | @Inject 21 | lateinit var plugin: PracticeGame 22 | 23 | @Configure 24 | fun configure() 25 | { 26 | val provider = plugin.server.servicesManager 27 | .getRegistration(UnifiedMetrics::class.java) 28 | ?.provider 29 | ?: return 30 | 31 | provider.metricsManager.registerCollection( 32 | GameServerCollection 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/metrics/collectors/PlayerDistributionCollector.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.metrics.collectors 2 | 3 | import dev.cubxity.plugins.metrics.api.metric.collector.Collector 4 | import dev.cubxity.plugins.metrics.api.metric.data.GaugeMetric 5 | import gg.tropic.practice.games.GameService 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 3/2/2024 10 | */ 11 | object PlayerDistributionCollector : Collector 12 | { 13 | override fun collect() = listOf( 14 | GaugeMetric( 15 | "practice_match_spectator_count", mapOf(), 16 | GameService.gameMappings.values.sumOf { it.expectedSpectators.size } 17 | ) 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/metrics/collectors/ReplicationCountCollector.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.metrics.collectors 2 | 3 | import dev.cubxity.plugins.metrics.api.metric.collector.Collector 4 | import dev.cubxity.plugins.metrics.api.metric.data.GaugeMetric 5 | import gg.tropic.practice.map.MapReplicationService 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 3/2/2024 10 | */ 11 | object ReplicationCountCollector : Collector 12 | { 13 | override fun collect() = listOf( 14 | GaugeMetric( 15 | "practice_replication_count", mapOf(), 16 | MapReplicationService.mapReplications.size 17 | ), 18 | GaugeMetric( 19 | "practice_replication_inuse_count", mapOf(), 20 | MapReplicationService.mapReplications.count { it.inUse } 21 | ), 22 | ) 23 | 24 | } 25 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/resources/DuelsNametagImpl.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.resources 2 | 3 | import gg.tropic.practice.games.GameService 4 | import net.evilblock.cubed.nametag.NametagInfo 5 | import net.evilblock.cubed.nametag.NametagProvider 6 | import net.evilblock.cubed.nametag.NametagProviderRegister 7 | import net.evilblock.cubed.util.CC 8 | import org.bukkit.entity.Player 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 8/9/2022 13 | */ 14 | @NametagProviderRegister 15 | object DuelsNametagImpl : NametagProvider("practice", Int.MAX_VALUE) 16 | { 17 | override fun fetchNametag( 18 | toRefresh: Player, refreshFor: Player 19 | ): NametagInfo? 20 | { 21 | val playerGame = GameService 22 | .byPlayerOrSpectator(toRefresh.uniqueId) 23 | ?: return null 24 | 25 | val targetGame = GameService 26 | .byPlayerOrSpectator(refreshFor.uniqueId) 27 | ?: return null 28 | 29 | if (targetGame.expectation == playerGame.expectation) 30 | { 31 | if (refreshFor.hasMetadata("spectator")) 32 | { 33 | return createNametag(CC.GRAY, "", "zzz") 34 | } 35 | 36 | return runCatching { 37 | if ( 38 | playerGame.getTeamOf(refreshFor).side == 39 | playerGame.getTeamOf(toRefresh).side 40 | ) 41 | { 42 | createNametag(CC.GREEN, "") 43 | } else 44 | { 45 | createNametag(CC.RED, "") 46 | } 47 | }.getOrNull() ?: createNametag( 48 | CC.GREEN, "" 49 | ) 50 | } 51 | 52 | return null 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/resources/DuelsVisibilityImpl.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.resources 2 | 3 | import gg.scala.basics.plugin.profile.BasicsProfileService 4 | import gg.scala.basics.plugin.settings.defaults.values.StateSettingValue 5 | import gg.tropic.practice.games.GameService 6 | import gg.tropic.practice.settings.DuelsSettingCategory 7 | import gg.tropic.practice.settings.isASilentSpectator 8 | import net.evilblock.cubed.visibility.VisibilityAction 9 | import net.evilblock.cubed.visibility.VisibilityAdapter 10 | import net.evilblock.cubed.visibility.VisibilityAdapterRegister 11 | import org.bukkit.entity.Player 12 | 13 | /** 14 | * @author GrowlyX 15 | * @since 8/9/2022 16 | */ 17 | @VisibilityAdapterRegister("duels") 18 | object DuelsVisibilityImpl : VisibilityAdapter 19 | { 20 | override fun getAction( 21 | toRefresh: Player, refreshFor: Player 22 | ): VisibilityAction 23 | { 24 | val playerGame = GameService 25 | .byPlayerOrSpectator(refreshFor.uniqueId) 26 | ?: return VisibilityAction.HIDE 27 | 28 | val targetGame = GameService 29 | .byPlayerOrSpectator(toRefresh.uniqueId) 30 | ?: return VisibilityAction.HIDE 31 | 32 | if (targetGame.expectation == playerGame.expectation) 33 | { 34 | if ( 35 | toRefresh.hasMetadata("spectator") && 36 | !refreshFor.hasMetadata("spectator") 37 | ) 38 | { 39 | return VisibilityAction.HIDE 40 | } 41 | 42 | if ( 43 | toRefresh.hasMetadata("spectator") && 44 | refreshFor.hasMetadata("spectator") 45 | ) 46 | { 47 | if (toRefresh.isASilentSpectator()) 48 | { 49 | return VisibilityAction.HIDE 50 | } 51 | } 52 | 53 | return VisibilityAction.NEUTRAL 54 | } 55 | 56 | return VisibilityAction.HIDE 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /game/src/main/kotlin/gg/tropic/practice/services/SpectateRequestService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.services 2 | 3 | import gg.scala.aware.AwareBuilder 4 | import gg.scala.aware.codec.codecs.interpretation.AwareMessageCodec 5 | import gg.scala.aware.message.AwareMessage 6 | import gg.scala.aware.thread.AwareThreadContext 7 | import gg.scala.commons.agnostic.sync.ServerSync 8 | import gg.scala.flavor.service.Configure 9 | import gg.scala.flavor.service.Service 10 | import gg.tropic.practice.games.GameService 11 | import gg.tropic.practice.games.spectate.SpectateRequest 12 | import gg.tropic.practice.suffixWhenDev 13 | import java.util.* 14 | import java.util.logging.Logger 15 | 16 | /** 17 | * @author GrowlyX 18 | * @since 10/20/2023 19 | */ 20 | @Service 21 | object SpectateRequestService 22 | { 23 | private val aware by lazy { 24 | AwareBuilder 25 | .of("practice:queue-inhabitants".suffixWhenDev()) 26 | .codec(AwareMessageCodec) 27 | .logger(Logger.getAnonymousLogger()) 28 | .build() 29 | } 30 | 31 | private fun createMessage(packet: String, vararg pairs: Pair): AwareMessage = 32 | AwareMessage.of(packet, aware, *pairs) 33 | 34 | @Configure 35 | fun configure() 36 | { 37 | aware.listen("request-spectate") { 38 | val server = retrieve("server") 39 | if (ServerSync.local.id != server) 40 | { 41 | return@listen 42 | } 43 | 44 | val request = retrieve("request") 45 | val game = retrieve("game") 46 | val requestID = retrieve("requestID") 47 | 48 | val gameImpl = GameService.gameMappings[game] 49 | ?: return@listen 50 | 51 | gameImpl.expectedSpectators += request.player 52 | 53 | createMessage( 54 | "spectate-ready", 55 | "requestID" to requestID 56 | ).publish( 57 | AwareThreadContext.SYNC, 58 | "practice:queue".suffixWhenDev() 59 | ) 60 | } 61 | aware.connect().toCompletableFuture().join() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalagg/scala-practice/be39878cb97755374883b54556975e5f8ec92d3b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /lobby/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(project(":shared")) 3 | api(project(":services:statistics")) 4 | 5 | api(project(":services:replications:replication-models")) 6 | } 7 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/PracticeLobby.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice 2 | 3 | import gg.scala.basics.plugin.settings.SettingMenu 4 | import gg.scala.commons.ExtendedScalaPlugin 5 | import gg.scala.commons.agnostic.sync.ServerSync 6 | import gg.scala.commons.annotations.container.ContainerEnable 7 | import gg.scala.commons.core.plugin.* 8 | import gg.tropic.practice.provider.SettingProvider 9 | import gg.tropic.practice.provider.impl.LemonSettingProvider 10 | import gg.tropic.practice.services.GameManagerService 11 | 12 | /** 13 | * @author GrowlyX 14 | * @since 8/5/2022 15 | */ 16 | @Plugin( 17 | name = "TropicPractice", 18 | version = "%remote%/%branch%/%id%" 19 | ) 20 | @PluginAuthor("Tropic") 21 | @PluginWebsite("https://tropic.gg") 22 | @PluginDependencyComposite( 23 | PluginDependency("scala-commons"), 24 | PluginDependency("Lemon"), 25 | PluginDependency("ScBasics"), 26 | PluginDependency("Parties"), 27 | PluginDependency("ScStaff", soft = true), 28 | PluginDependency("Friends", soft = true), 29 | PluginDependency("CoreGameExtensions", soft = true) 30 | ) 31 | class PracticeLobby : ExtendedScalaPlugin() 32 | { 33 | var settingProvider: SettingProvider = LemonSettingProvider 34 | 35 | init 36 | { 37 | PracticeShared 38 | } 39 | 40 | @ContainerEnable 41 | fun containerEnable() 42 | { 43 | devProvider = { 44 | "miplobbyDEV" in ServerSync.getLocalGameServer().groups 45 | } 46 | 47 | SettingMenu.defaultCategory = "Practice" 48 | GameManagerService.bindToMetadataService() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/category/SettingExt.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.category 2 | 3 | import gg.scala.basics.plugin.profile.BasicsProfileService 4 | import gg.tropic.practice.settings.restriction.RangeRestriction 5 | import gg.tropic.practice.settings.DuelsSettingCategory 6 | import org.bukkit.entity.Player 7 | 8 | /** 9 | * @author GrowlyX 10 | * @since 10/15/2023 11 | */ 12 | val Player.pingRange: RangeRestriction 13 | get() = BasicsProfileService.find(this) 14 | ?.setting( 15 | "${DuelsSettingCategory.DUEL_SETTING_PREFIX}:restriction-ping", 16 | RangeRestriction.None 17 | ) 18 | ?: RangeRestriction.None 19 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/category/visibility/SpawnPlayerVisibility.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.category.visibility 2 | 3 | import gg.scala.basics.plugin.profile.BasicsProfileService 4 | import gg.scala.basics.plugin.settings.defaults.values.StateSettingValue 5 | import gg.tropic.practice.settings.DuelsSettingCategory 6 | import net.evilblock.cubed.visibility.VisibilityAction 7 | import net.evilblock.cubed.visibility.VisibilityAdapter 8 | import net.evilblock.cubed.visibility.VisibilityAdapterRegister 9 | import org.bukkit.entity.Player 10 | 11 | /** 12 | * @author Elb1to 13 | * @since 10/18/2023 14 | */ 15 | @VisibilityAdapterRegister("duels-visibility-adapter") 16 | object SpawnPlayerVisibility : VisibilityAdapter 17 | { 18 | override fun getAction( 19 | toRefresh: Player, refreshFor: Player 20 | ): VisibilityAction 21 | { 22 | val profile = BasicsProfileService.find(refreshFor) 23 | ?: return VisibilityAction.NEUTRAL 24 | 25 | val messagesRef = profile.settings["${DuelsSettingCategory.DUEL_SETTING_PREFIX}:spawn-visibility"]!! 26 | val visible = messagesRef.map() 27 | 28 | return if (visible == StateSettingValue.DISABLED) 29 | VisibilityAction.HIDE else 30 | if (toRefresh.hasPermission("practice.show-spawn-visibility")) 31 | VisibilityAction.NEUTRAL else VisibilityAction.HIDE 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/commands/DuelGamesCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.commons.acf.annotation.* 4 | import gg.scala.commons.annotations.commands.AutoRegister 5 | import gg.scala.commons.command.ScalaCommand 6 | import gg.scala.commons.issuer.ScalaPlayer 7 | import gg.scala.lemon.player.wrapper.AsyncLemonPlayer 8 | import gg.tropic.practice.menu.DuelGamesMenu 9 | import gg.tropic.practice.reports.GameReportService 10 | import net.evilblock.cubed.util.bukkit.Tasks 11 | import java.util.concurrent.CompletableFuture 12 | 13 | /** 14 | * @author GrowlyX 15 | * @since 8/5/2022 16 | */ 17 | @AutoRegister 18 | @CommandAlias("games|matchhistory|mh|matchhist") 19 | object DuelGamesCommand : ScalaCommand() 20 | { 21 | @Default 22 | @CommandCompletion("@mip-players") 23 | fun onDefault( 24 | @Conditions("cooldown:duration=10,unit=SECONDS") 25 | player: ScalaPlayer, 26 | @Optional 27 | @CommandPermission("practice.command.matchhistory.view-other-profiles") 28 | target: AsyncLemonPlayer? 29 | ): CompletableFuture 30 | { 31 | if (target == null) 32 | { 33 | return GameReportService 34 | .loadSnapshotsForParticipant(player.uniqueId) 35 | .thenAccept { 36 | Tasks.sync { 37 | DuelGamesMenu(it, player.uniqueId).openMenu(player.bukkit()) 38 | } 39 | } 40 | } 41 | 42 | return target.validatePlayers(player.bukkit(), false) { lemonPlayer -> 43 | GameReportService 44 | .loadSnapshotsForParticipant(lemonPlayer.uniqueId) 45 | .thenAccept { 46 | Tasks.sync { 47 | DuelGamesMenu(it, lemonPlayer.uniqueId).openMenu(player.bukkit()) 48 | } 49 | } 50 | .join() 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/commands/DuelRequestsCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.basics.plugin.profile.BasicsProfileService 4 | import gg.scala.commons.acf.ConditionFailedException 5 | import gg.scala.commons.acf.annotation.CommandAlias 6 | import gg.scala.commons.annotations.commands.AutoRegister 7 | import gg.scala.commons.command.ScalaCommand 8 | import gg.scala.commons.issuer.ScalaPlayer 9 | import gg.tropic.practice.friendship.FriendshipStateSetting 10 | import gg.tropic.practice.settings.DuelsSettingCategory 11 | import net.evilblock.cubed.util.CC 12 | import java.util.concurrent.CompletableFuture 13 | 14 | /** 15 | * @author GrowlyX 16 | * @since 8/5/2022 17 | */ 18 | @AutoRegister 19 | object DuelRequestsCommand : ScalaCommand() 20 | { 21 | @CommandAlias( 22 | "tdr|toggleduelrequests|duelrequests" 23 | ) 24 | fun onDuelRequests(player: ScalaPlayer): CompletableFuture 25 | { 26 | val profile = BasicsProfileService.find(player.bukkit()) 27 | ?: throw ConditionFailedException( 28 | "Sorry, your profile did not load properly." 29 | ) 30 | 31 | val allowDuelRequests = profile.settings["${DuelsSettingCategory.DUEL_SETTING_PREFIX}:duel-requests-fr"]!! 32 | 33 | player.sendMessage( 34 | "${CC.GREEN}You have set your duel request settings to: ${ 35 | when (allowDuelRequests.map()) 36 | { 37 | FriendshipStateSetting.Enabled -> 38 | { 39 | allowDuelRequests.value = "FriendsOnly" 40 | "${CC.GOLD}Friends Only" 41 | } 42 | FriendshipStateSetting.FriendsOnly -> 43 | { 44 | allowDuelRequests.value = "Disabled" 45 | "${CC.RED}Disabled" 46 | } 47 | FriendshipStateSetting.Disabled -> 48 | { 49 | allowDuelRequests.value = "Enabled" 50 | "${CC.GREEN}Enabled" 51 | } 52 | } 53 | }" 54 | ) 55 | 56 | return profile.save() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/commands/KitEditorCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.commons.acf.ConditionFailedException 4 | import gg.scala.commons.acf.annotation.CommandAlias 5 | import gg.scala.commons.annotations.commands.AutoRegister 6 | import gg.scala.commons.command.ScalaCommand 7 | import gg.scala.commons.issuer.ScalaPlayer 8 | import gg.tropic.practice.menu.editor.EditorKitSelectionMenu 9 | import gg.tropic.practice.player.LobbyPlayerService 10 | import gg.tropic.practice.player.PlayerState 11 | import gg.tropic.practice.profile.PracticeProfileService 12 | 13 | @AutoRegister 14 | object KitEditorCommand : ScalaCommand() 15 | { 16 | @CommandAlias("kiteditor") 17 | fun onKitEditor(player: ScalaPlayer) 18 | { 19 | val profile = PracticeProfileService.find(player.uniqueId) 20 | ?: throw ConditionFailedException( 21 | "You profile doesn't exist!" 22 | ) 23 | 24 | with(LobbyPlayerService.find(player.bukkit())) { 25 | if (this?.state != PlayerState.Idle) 26 | { 27 | throw ConditionFailedException( 28 | "You must be at spawn to enter the kit editor!" 29 | ) 30 | } 31 | } 32 | 33 | EditorKitSelectionMenu(profile).openMenu(player.bukkit()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/commands/LeaderboardsCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.commons.acf.annotation.CommandAlias 4 | import gg.scala.commons.annotations.commands.AutoRegister 5 | import gg.scala.commons.command.ScalaCommand 6 | import gg.scala.commons.issuer.ScalaPlayer 7 | import gg.tropic.practice.menu.LeaderboardsMenu 8 | 9 | /** 10 | * @author GrowlyX 11 | * @since 12/16/2023 12 | */ 13 | @AutoRegister 14 | object LeaderboardsCommand : ScalaCommand() 15 | { 16 | @CommandAlias("leaderboards|lbs|lb") 17 | fun onLeaderboards(player: ScalaPlayer) 18 | { 19 | LeaderboardsMenu().openMenu(player.bukkit()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/commands/SpawnCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.commons.acf.annotation.CommandAlias 4 | import gg.scala.commons.acf.annotation.Conditions 5 | import gg.scala.commons.annotations.commands.AutoRegister 6 | import gg.scala.commons.command.ScalaCommand 7 | import gg.scala.commons.issuer.ScalaPlayer 8 | import gg.tropic.practice.configuration.PracticeConfigurationService 9 | import gg.tropic.practice.player.LobbyPlayerService 10 | import gg.tropic.practice.player.hotbar.LobbyHotbarService 11 | import org.bukkit.Bukkit 12 | 13 | /** 14 | * @author GrowlyX 15 | * @since 10/19/2023 16 | */ 17 | @AutoRegister 18 | object SpawnCommand : ScalaCommand() 19 | { 20 | @CommandAlias("spawn") 21 | fun onSpawn( 22 | @Conditions("cooldown:duration=2,unit=SECONDS") 23 | player: ScalaPlayer 24 | ) 25 | { 26 | with(PracticeConfigurationService.cached()) { 27 | player.teleport( 28 | spawnLocation 29 | .toLocation( 30 | Bukkit.getWorlds().first() 31 | ) 32 | ) 33 | 34 | LobbyPlayerService.find(player.bukkit()) 35 | ?.apply { 36 | LobbyHotbarService 37 | .get(state) 38 | .applyToPlayer(player.bukkit()) 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/commands/SpectateCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.commons.acf.ConditionFailedException 4 | import gg.scala.commons.acf.annotation.CommandAlias 5 | import gg.scala.commons.acf.annotation.CommandCompletion 6 | import gg.scala.commons.annotations.commands.AutoRegister 7 | import gg.scala.commons.command.ScalaCommand 8 | import gg.scala.commons.issuer.ScalaPlayer 9 | import gg.scala.lemon.player.wrapper.AsyncLemonPlayer 10 | import gg.tropic.practice.games.spectate.SpectateRequest 11 | import gg.tropic.practice.player.LobbyPlayerService 12 | import gg.tropic.practice.player.PlayerState 13 | import gg.tropic.practice.queue.QueueService 14 | import net.evilblock.cubed.util.CC 15 | 16 | /** 17 | * @author Elb1to 18 | * @since 10/19/2023 19 | */ 20 | @AutoRegister 21 | object SpectateCommand : ScalaCommand() 22 | { 23 | @CommandAlias("spectate|spec") 24 | @CommandCompletion("@mip-players") 25 | fun onSpectate( 26 | player: ScalaPlayer, 27 | target: AsyncLemonPlayer 28 | ) = target.validatePlayers( 29 | player.bukkit(), false 30 | ) { 31 | val lobbyProfile = LobbyPlayerService 32 | .find(player.bukkit()) 33 | ?: return@validatePlayers 34 | 35 | if (lobbyProfile.state != PlayerState.Idle) 36 | { 37 | throw ConditionFailedException("You cannot spectate a match right now!") 38 | } 39 | 40 | it.identifier.offlineProfile 41 | player.bukkit().sendMessage( 42 | "${CC.GREEN}Joining ${CC.B_GREEN}${it.name}'s${CC.GREEN} game..." 43 | ) 44 | 45 | QueueService.spectate( 46 | SpectateRequest( 47 | player.uniqueId, 48 | it.uniqueId, 49 | player.bukkit().hasPermission( 50 | "practice.spectate.bypass-allowance-settings" 51 | ) 52 | ) 53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/commands/StatisticsCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.commons.acf.ConditionFailedException 4 | import gg.scala.commons.acf.annotation.CommandAlias 5 | import gg.scala.commons.acf.annotation.CommandCompletion 6 | import gg.scala.commons.acf.annotation.Optional 7 | import gg.scala.commons.annotations.commands.AutoRegister 8 | import gg.scala.commons.command.ScalaCommand 9 | import gg.scala.commons.issuer.ScalaPlayer 10 | import gg.scala.lemon.player.wrapper.AsyncLemonPlayer 11 | import gg.tropic.practice.menu.StatisticsMenu 12 | import gg.tropic.practice.profile.PracticeProfileService 13 | import java.util.concurrent.CompletableFuture 14 | 15 | /** 16 | * @author Elb1to 17 | * @since 10/19/2023 18 | */ 19 | @AutoRegister 20 | object StatisticsCommand : ScalaCommand() 21 | { 22 | @CommandAlias("stats|statistics|stat") 23 | @CommandCompletion("@mip-players") 24 | fun onStatistics( 25 | player: ScalaPlayer, 26 | @Optional target: AsyncLemonPlayer? 27 | ): CompletableFuture 28 | { 29 | if (target != null) 30 | { 31 | return target.validatePlayers(player.bukkit(), false) { 32 | StatisticsMenu( 33 | it.identifier.offlineProfile, 34 | StatisticsMenu.StatisticMenuState.Casual 35 | ).openMenu(player.bukkit()) 36 | } 37 | } 38 | 39 | val profile = PracticeProfileService.find(player.bukkit()) 40 | ?: throw ConditionFailedException( 41 | "Your profile has not loaded in properly, log out and try again." 42 | ) 43 | 44 | StatisticsMenu( 45 | profile, 46 | StatisticsMenu.StatisticMenuState.Casual 47 | ).openMenu(player.bukkit()) 48 | 49 | return CompletableFuture.completedFuture(null) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/commands/TogglePlayerVisibilityCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.basics.plugin.profile.BasicsProfileService 4 | import gg.scala.basics.plugin.settings.defaults.values.StateSettingValue 5 | import gg.scala.commons.acf.ConditionFailedException 6 | import gg.scala.commons.acf.annotation.CommandAlias 7 | import gg.scala.commons.annotations.commands.AutoRegister 8 | import gg.scala.commons.command.ScalaCommand 9 | import gg.scala.commons.issuer.ScalaPlayer 10 | import gg.tropic.practice.settings.DuelsSettingCategory 11 | import net.evilblock.cubed.util.CC 12 | import net.evilblock.cubed.visibility.VisibilityHandler 13 | 14 | /** 15 | * @author Elb1to 16 | * @since 10/18/2023 17 | */ 18 | @AutoRegister 19 | object TogglePlayerVisibilityCommand : ScalaCommand() 20 | { 21 | @CommandAlias( 22 | "tpv|toggleplayervisibility|togglevisibility" 23 | ) 24 | fun onToggleVisibility(player: ScalaPlayer) 25 | { 26 | val profile = BasicsProfileService.find(player.bukkit()) 27 | ?: throw ConditionFailedException( 28 | "Sorry, your profile did not load properly." 29 | ) 30 | 31 | val spawnVisibility = profile.settings["${DuelsSettingCategory.DUEL_SETTING_PREFIX}:spawn-visibility"]!! 32 | val mapped = spawnVisibility.map() 33 | 34 | if (mapped == StateSettingValue.ENABLED) 35 | { 36 | spawnVisibility.value = "DISABLED" 37 | player.sendMessage( 38 | "${CC.RED}You can no longer see players at spawn." 39 | ) 40 | } else 41 | { 42 | spawnVisibility.value = "ENABLED" 43 | player.sendMessage( 44 | "${CC.GREEN}You can now see players at spawn." 45 | ) 46 | } 47 | 48 | VisibilityHandler.update(player.bukkit()) 49 | profile.save() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/commands/ToggleSpectatorsCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.basics.plugin.profile.BasicsProfileService 4 | import gg.scala.basics.plugin.settings.defaults.values.StateSettingValue 5 | import gg.scala.commons.acf.ConditionFailedException 6 | import gg.scala.commons.acf.annotation.CommandAlias 7 | import gg.scala.commons.annotations.commands.AutoRegister 8 | import gg.scala.commons.command.ScalaCommand 9 | import gg.scala.commons.issuer.ScalaPlayer 10 | import gg.tropic.practice.settings.DuelsSettingCategory 11 | import net.evilblock.cubed.util.CC 12 | import java.util.concurrent.CompletableFuture 13 | 14 | /** 15 | * @author Elb1to 16 | * @since 10/19/2023 17 | */ 18 | @AutoRegister 19 | object ToggleSpectatorsCommand : ScalaCommand() 20 | { 21 | @CommandAlias( 22 | "tsp|togglespecs|togglespectators" 23 | ) 24 | fun onSpectateToggle(player: ScalaPlayer): CompletableFuture 25 | { 26 | val profile = BasicsProfileService.find(player.bukkit()) 27 | ?: throw ConditionFailedException( 28 | "Sorry, your profile did not load properly." 29 | ) 30 | 31 | val allowSpectators = profile.settings["${DuelsSettingCategory.DUEL_SETTING_PREFIX}:allow-spectators"]!! 32 | val mapped = allowSpectators.map() 33 | 34 | if (mapped == StateSettingValue.ENABLED) 35 | { 36 | allowSpectators.value = "DISABLED" 37 | player.sendMessage( 38 | "${CC.RED}Players are no longer able to spectate your matches." 39 | ) 40 | } else 41 | { 42 | allowSpectators.value = "ENABLED" 43 | player.sendMessage( 44 | "${CC.GREEN}Players are now able to spectate your matches." 45 | ) 46 | } 47 | 48 | return profile.save() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/duel/DuelRequestUtilities.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.duel 2 | 3 | import gg.tropic.practice.games.duels.DuelRequest 4 | import gg.tropic.practice.kit.Kit 5 | import gg.tropic.practice.namespace 6 | import gg.tropic.practice.suffixWhenDev 7 | import net.evilblock.cubed.ScalaCommonsSpigot 8 | import net.evilblock.cubed.serializers.Serializers 9 | import java.util.* 10 | import java.util.concurrent.CompletableFuture 11 | 12 | /** 13 | * @author GrowlyX 14 | * @since 10/21/2023 15 | */ 16 | object DuelRequestUtilities 17 | { 18 | fun duelRequestExists(sender: UUID, target: UUID, kit: Kit) = CompletableFuture 19 | .supplyAsync { 20 | ScalaCommonsSpigot 21 | .instance.kvConnection 22 | .sync() 23 | .hexists( 24 | "${namespace().suffixWhenDev()}:duelrequests:${sender}:${kit.id}", 25 | target.toString() 26 | ) 27 | } 28 | 29 | fun duelRequest(sender: UUID, target: UUID, kit: Kit) = CompletableFuture 30 | .supplyAsync { 31 | val request = ScalaCommonsSpigot 32 | .instance.kvConnection 33 | .sync() 34 | .hget( 35 | "${namespace().suffixWhenDev()}:duelrequests:${sender}:${kit.id}", 36 | target.toString() 37 | ) 38 | 39 | if (request != null) 40 | { 41 | Serializers.gson.fromJson( 42 | request, DuelRequest::class.java 43 | ) 44 | } else 45 | { 46 | null 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/feature/CloudSyncFeature.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.feature 2 | 3 | import gg.scala.cloudsync.shared.discovery.CloudSyncDiscoveryService 4 | import gg.scala.commons.agnostic.sync.ServerSync 5 | import gg.scala.commons.annotations.plugin.SoftDependency 6 | import gg.scala.flavor.service.Configure 7 | import gg.scala.flavor.service.Service 8 | import gg.scala.flavor.service.ignore.IgnoreAutoScan 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 8/5/2022 13 | */ 14 | @Service 15 | @IgnoreAutoScan 16 | @SoftDependency("cloudsync") 17 | object CloudSyncFeature 18 | { 19 | @Configure 20 | fun configure() 21 | { 22 | CloudSyncDiscoveryService 23 | .discoverable.assets.add( 24 | "gg.tropic.practice:lobby:TropicPractice-lobby${ 25 | if ("dev" in ServerSync.getLocalGameServer().groups) ":gradle-dev" else "" 26 | }" 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/hologram/ScrollingKitLeaderboardHologram.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.hologram 2 | 3 | import gg.tropic.practice.leaderboards.Reference 4 | import gg.tropic.practice.leaderboards.ReferenceLeaderboardType 5 | import org.bukkit.Location 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 6/30/2023 10 | */ 11 | class ScrollingKitLeaderboardHologram( 12 | private val leaderboardType: ReferenceLeaderboardType, 13 | private val kits: List, 14 | scrollTime: Int, location: Location 15 | ) : AbstractScrollingLeaderboard(scrollTime, location) 16 | { 17 | override fun getNextReference(current: Reference?) = Reference( 18 | leaderboardType = leaderboardType, 19 | kitID = current?.kitID 20 | ?.let { 21 | kits.getOrNull( 22 | kits.indexOf(it) + 1 23 | ) 24 | } 25 | ?: kits.first() 26 | ) 27 | 28 | override fun getAbstractType() = ScrollingKitLeaderboardHologram::class.java 29 | } 30 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/hologram/ScrollingLeaderboardService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.hologram 2 | 3 | import gg.scala.commons.ExtendedScalaPlugin 4 | import gg.scala.flavor.inject.Inject 5 | import gg.scala.flavor.service.Configure 6 | import gg.scala.flavor.service.Service 7 | import me.lucko.helper.Events 8 | import me.lucko.helper.Schedulers 9 | import net.evilblock.cubed.entity.EntityHandler 10 | import net.evilblock.cubed.serializers.Serializers 11 | import net.evilblock.cubed.serializers.impl.AbstractTypeSerializer 12 | import org.bukkit.event.player.PlayerQuitEvent 13 | 14 | /** 15 | * @author GrowlyX 16 | * @since 12/29/2023 17 | */ 18 | @Service 19 | object ScrollingLeaderboardService 20 | { 21 | @Inject 22 | lateinit var plugin: ExtendedScalaPlugin 23 | 24 | @Configure 25 | fun configure() 26 | { 27 | Serializers.create { 28 | registerTypeAdapter( 29 | AbstractScrollingLeaderboard::class.java, 30 | AbstractTypeSerializer() 31 | ) 32 | } 33 | 34 | EntityHandler 35 | .getEntitiesOfType() 36 | .forEach { 37 | it.secondsUntilRefresh = it.scrollTime 38 | } 39 | 40 | Events 41 | .subscribe(PlayerQuitEvent::class.java) 42 | .handler { 43 | EntityHandler 44 | .getEntitiesOfType() 45 | .forEach { leaderboard -> 46 | leaderboard.invalidateCacheEntries(it.player) 47 | } 48 | } 49 | 50 | Schedulers 51 | .async() 52 | .runRepeating({ _ -> 53 | EntityHandler 54 | .getEntitiesOfType() 55 | .forEach { 56 | it.secondsUntilRefresh = it 57 | .secondsUntilRefresh!! 58 | .minus(1) 59 | 60 | if (it.secondsUntilRefresh!! <= 0) 61 | { 62 | val currentReference = it.currentReference 63 | it.secondsUntilRefresh = it.scrollTime 64 | it.currentReference = it.getNextReference(currentReference) 65 | } 66 | } 67 | }, 0L, 20L) 68 | .bindWith(plugin) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/hologram/ScrollingTypeLeaderboardHologram.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.hologram 2 | 3 | import gg.tropic.practice.leaderboards.Reference 4 | import gg.tropic.practice.leaderboards.ReferenceLeaderboardType 5 | import org.bukkit.Location 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 12/29/2023 10 | */ 11 | class ScrollingTypeLeaderboardHologram( 12 | private val kit: String?, 13 | private val leaderboardTypes: List, 14 | scrollTime: Int, location: Location 15 | ) : AbstractScrollingLeaderboard(scrollTime, location) 16 | { 17 | override fun getNextReference(current: Reference?) = Reference( 18 | leaderboardType = current?.leaderboardType 19 | ?.let { 20 | leaderboardTypes.getOrNull( 21 | leaderboardTypes.indexOf(it) + 1 22 | ) 23 | } 24 | ?: leaderboardTypes.first(), 25 | kitID = kit 26 | ) 27 | 28 | override fun getAbstractType() = ScrollingTypeLeaderboardHologram::class.java 29 | } 30 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/menu/CasualQueueSelectSizeMenu.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.menu 2 | 3 | import com.cryptomorin.xseries.XMaterial 4 | import gg.tropic.practice.queue.QueueType 5 | import net.evilblock.cubed.menu.Button 6 | import net.evilblock.cubed.menu.Menu 7 | import net.evilblock.cubed.util.CC 8 | import net.evilblock.cubed.util.bukkit.ItemBuilder 9 | import org.bukkit.entity.Player 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 10/15/2023 14 | */ 15 | class CasualQueueSelectSizeMenu : Menu("Casual Queue") 16 | { 17 | init 18 | { 19 | placeholder = true 20 | } 21 | 22 | override fun size(buttons: Map) = 27 23 | 24 | override fun getButtons(player: Player) = mutableMapOf( 25 | 12 to ItemBuilder 26 | .of(XMaterial.ORANGE_DYE) 27 | .name("${CC.B_GOLD}Solos") 28 | .addToLore( 29 | "${CC.WHITE}Queue for a casual", 30 | "${CC.WHITE}solos game with no", 31 | "${CC.WHITE}loss penalty.", 32 | "", 33 | "${CC.WHITE}Playing: ${CC.GOLD}0", 34 | "", 35 | "${CC.GREEN}Click to open!" 36 | ) 37 | .toButton { _, _ -> 38 | Button.playNeutral(player) 39 | JoinQueueMenu(QueueType.Casual, 1).openMenu(player) 40 | }, 41 | 14 to ItemBuilder 42 | .of(XMaterial.LIGHT_BLUE_DYE) 43 | .name("${CC.B_AQUA}Duos") 44 | .addToLore( 45 | "${CC.WHITE}Queue for a casual", 46 | "${CC.WHITE}duos game with no", 47 | "${CC.WHITE}loss penalty.", 48 | "", 49 | "${CC.WHITE}Playing: ${CC.AQUA}0", 50 | "", 51 | "${CC.GREEN}Click to open!" 52 | ) 53 | .toButton { _, _ -> 54 | Button.playNeutral(player) 55 | JoinQueueMenu(QueueType.Casual, 2).openMenu(player) 56 | } 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/menu/PlayerMainMenu.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.menu 2 | 3 | import net.evilblock.cubed.menu.Button 4 | import net.evilblock.cubed.menu.Menu 5 | import net.evilblock.cubed.util.CC 6 | import net.evilblock.cubed.util.bukkit.ItemBuilder 7 | import org.bukkit.Material 8 | import org.bukkit.entity.Player 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 12/17/2023 13 | */ 14 | class PlayerMainMenu : Menu("Navigator") 15 | { 16 | init 17 | { 18 | placeholder = true 19 | } 20 | 21 | override fun size(buttons: Map) = 27 22 | override fun getButtons(player: Player) = mapOf( 23 | 11 to ItemBuilder 24 | .of(Material.BLAZE_POWDER) 25 | .name("${CC.GOLD}Cosmetics") 26 | .addToLore( 27 | "${CC.GRAY}Customize everything about", 28 | "${CC.GRAY}your in-game profile using", 29 | "${CC.GRAY}the cosmetics menu!", 30 | "", 31 | "${CC.GREEN}Click to open!" 32 | ) 33 | .toButton { _, _ -> 34 | player.performCommand("cosmetics") 35 | }, 36 | 13 to ItemBuilder 37 | .of(Material.FIREBALL) 38 | .name("${CC.GREEN}Statistics") 39 | .addToLore( 40 | "${CC.GRAY}View practice statistics", 41 | "${CC.GRAY}in all categories!", 42 | "", 43 | "${CC.GREEN}Click to open!" 44 | ) 45 | .toButton { _, _ -> 46 | player.performCommand("statistics") 47 | }, 48 | 15 to ItemBuilder 49 | .of(Material.LEASH) 50 | .name("${CC.D_PURPLE}Events") 51 | .addToLore( 52 | "${CC.GRAY}Host or join an event!", 53 | "", 54 | "${CC.GRAY}Events include:", 55 | "${CC.WHITE}- Sumo", 56 | "", 57 | "${CC.GREEN}Click to open!" 58 | ) 59 | .toButton { _, _ -> 60 | player.performCommand("events") 61 | }, 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/menu/editor/AllowRemoveItemsWithinInventory.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.menu.editor 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 10/28/2023 6 | */ 7 | interface AllowRemoveItemsWithinInventory 8 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/menu/editor/ExtraContentSelectionMenu.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.menu.editor 2 | 3 | import gg.tropic.practice.kit.Kit 4 | import net.evilblock.cubed.menu.Button 5 | import net.evilblock.cubed.menu.Menu 6 | import net.evilblock.cubed.util.bukkit.ItemBuilder 7 | import net.evilblock.cubed.util.bukkit.Tasks 8 | import org.bukkit.entity.Player 9 | 10 | class ExtraContentSelectionMenu( 11 | private val kit: Kit, 12 | private val _contentsMenu: EditLoadoutContentsMenu, 13 | private val contentsMenu: EditLoadoutContentsMenu = EditLoadoutContentsMenu(kit, _contentsMenu.loadout, _contentsMenu.practiceProfile) 14 | ) : Menu( 15 | "Selecting extra contents..." 16 | ), AllowRemoveItemsWithinInventory 17 | { 18 | override fun size(buttons: Map) = 27 19 | 20 | override fun getButtons(player: Player): Map 21 | { 22 | val buttons = mutableMapOf() 23 | for ((index, content) in kit.additionalContents.withIndex()) 24 | { 25 | if (content == null) 26 | { 27 | continue 28 | } 29 | 30 | buttons[index] = ItemBuilder 31 | .copyOf(content) 32 | .toButton { _, _ -> 33 | Button.playNeutral(player) 34 | player.inventory.addItem(content) 35 | } 36 | } 37 | 38 | return buttons 39 | } 40 | 41 | override fun onOpen(player: Player) 42 | { 43 | super.onOpen(player) 44 | contentsMenu.onOpen(player) 45 | } 46 | 47 | override fun onClose(player: Player, manualClose: Boolean) 48 | { 49 | if (manualClose) 50 | { 51 | Tasks.sync { 52 | contentsMenu.handleLoadoutSave(player) 53 | .thenRun { 54 | contentsMenu.openMenu(player) 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/menu/party/PartyPlayGameSelectMenu.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.menu.party 2 | 3 | import gg.tropic.practice.party.WParty 4 | import gg.tropic.practice.player.LobbyPlayerService 5 | import net.evilblock.cubed.menu.Menu 6 | import net.evilblock.cubed.util.CC 7 | import net.evilblock.cubed.util.bukkit.ItemBuilder 8 | import org.bukkit.Material 9 | import org.bukkit.entity.Player 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 2/9/2024 14 | */ 15 | class PartyPlayGameSelectMenu() : Menu("Select a game") 16 | { 17 | init 18 | { 19 | placeholder = true 20 | } 21 | 22 | override fun getButtons(player: Player) = mapOf( 23 | 4 to ItemBuilder 24 | .of(Material.GOLD_SWORD) 25 | .name("${CC.AQUA}Team vs. Team Fights") 26 | .addToLore( 27 | "${CC.WHITE}Create two teams with your", 28 | "${CC.WHITE}party members and fight", 29 | "${CC.WHITE}against one another!", 30 | "", 31 | "${CC.GREEN}Click to play!" 32 | ) 33 | .toButton { _, _ -> 34 | val lobbyPlayer = LobbyPlayerService.find(player) 35 | ?: return@toButton 36 | 37 | player.closeInventory() 38 | if (!lobbyPlayer.isInParty()) 39 | { 40 | player.sendMessage("${CC.RED}You are no longer in a party!") 41 | return@toButton 42 | } 43 | 44 | lobbyPlayer.partyOf() 45 | .onlinePracticePlayersInLobby() 46 | .thenAccept { 47 | if (it.keys.size < 2) 48 | { 49 | player.sendMessage("${CC.RED}You must have at least two players in your party to start a Team vs. Team fight!") 50 | return@thenAccept 51 | } 52 | 53 | PartyPlayTVTFights(it.keys.toList()).openMenu(player) 54 | } 55 | } 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/menu/template/TemplateMapMenu.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.menu.template 2 | 3 | import gg.tropic.practice.map.MapService 4 | import net.evilblock.cubed.menu.Button 5 | import net.evilblock.cubed.menu.pagination.PaginatedMenu 6 | import net.evilblock.cubed.util.CC 7 | import net.evilblock.cubed.util.bukkit.ItemBuilder 8 | import org.bukkit.entity.Player 9 | import org.bukkit.event.inventory.ClickType 10 | import org.bukkit.inventory.ItemFlag 11 | 12 | /** 13 | * @author GrowlyX 14 | * @since 10/21/2023 15 | */ 16 | abstract class TemplateMapMenu : PaginatedMenu() 17 | { 18 | init 19 | { 20 | placeholdBorders = true 21 | } 22 | 23 | abstract fun filterDisplayOfMap(map: gg.tropic.practice.map.Map): Boolean 24 | abstract fun itemDescriptionOf(player: Player, map: gg.tropic.practice.map.Map): List 25 | abstract fun itemClicked(player: Player, map: gg.tropic.practice.map.Map, type: ClickType) 26 | 27 | open fun itemTitleFor(player: Player, map: gg.tropic.practice.map.Map): String 28 | { 29 | return "${CC.GREEN}${map.displayName}" 30 | } 31 | 32 | override fun size(buttons: Map) = 45 33 | override fun getMaxItemsPerPage(player: Player) = 21 34 | 35 | override fun getAllPagesButtonSlots() = 36 | (10..16) + (19..25) + (28..34) 37 | 38 | override fun getPageButtonSlots() = 18 to 27 39 | 40 | private val filteredMaps = MapService 41 | .cached() 42 | .maps 43 | .values 44 | .filter { 45 | !it.locked && filterDisplayOfMap(it) 46 | } 47 | 48 | fun getAvailableMaps() = filteredMaps 49 | fun ensureMapsAvailable() = filteredMaps.isNotEmpty() 50 | 51 | override fun getAllPagesButtons(player: Player): Map 52 | { 53 | val buttons = mutableMapOf() 54 | 55 | filteredMaps.forEach { 56 | buttons[buttons.size] = ItemBuilder 57 | .copyOf(it.displayIcon) 58 | .name(itemTitleFor(player, it)) 59 | .addFlags( 60 | ItemFlag.HIDE_ATTRIBUTES, 61 | ItemFlag.HIDE_ENCHANTS, 62 | ItemFlag.HIDE_POTION_EFFECTS 63 | ) 64 | .addToLore( 65 | *itemDescriptionOf(player, it) 66 | .toTypedArray() 67 | ) 68 | .toButton { _, type -> 69 | itemClicked(player, it, type!!) 70 | } 71 | } 72 | 73 | return buttons 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/player/IntRangeUtils.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.player 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 12/23/2023 6 | */ 7 | fun IntRange.formattedDomain() = "[${kotlin.math.max(0, first)} ➡ $last]" 8 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/player/LobbyPlayerExt.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.player 2 | 3 | import net.kyori.adventure.text.Component 4 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer 5 | import org.bukkit.Bukkit 6 | import org.bukkit.entity.Player 7 | 8 | /** 9 | * @author GrowlyX 10 | * @since 10/13/2023 11 | */ 12 | val LobbyPlayer.player: Player 13 | get() = Bukkit.getPlayer(uniqueId) 14 | 15 | val String.component: Component 16 | get() = LegacyComponentSerializer 17 | .legacySection() 18 | .deserialize(this) 19 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/player/PlayerState.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.player 2 | 3 | enum class PlayerState 4 | { 5 | InParty, InQueue, InTournament, Idle, None 6 | } 7 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/provider/SettingProvider.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.provider 2 | 3 | import org.bukkit.entity.Player 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 10/16/2022 8 | */ 9 | interface SettingProvider 10 | { 11 | fun provideSetting(player: Player, setting: String): Boolean 12 | } 13 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/provider/impl/BasicsSettingProvider.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.provider.impl 2 | 3 | import gg.tropic.practice.PracticeLobby 4 | import gg.scala.basics.plugin.profile.BasicsProfileService 5 | import gg.scala.basics.plugin.settings.defaults.values.StateSettingValue 6 | import gg.scala.commons.annotations.plugin.SoftDependency 7 | import gg.scala.flavor.inject.Inject 8 | import gg.scala.flavor.service.Configure 9 | import gg.scala.flavor.service.Service 10 | import gg.scala.flavor.service.ignore.IgnoreAutoScan 11 | import gg.tropic.practice.provider.SettingProvider 12 | import org.bukkit.entity.Player 13 | 14 | /** 15 | * @author GrowlyX 16 | * @since 10/16/2022 17 | */ 18 | @Service 19 | @IgnoreAutoScan 20 | @SoftDependency("ScBasics") 21 | object BasicsSettingProvider : SettingProvider 22 | { 23 | @Inject 24 | lateinit var plugin: PracticeLobby 25 | 26 | @Configure 27 | fun configure() 28 | { 29 | plugin.settingProvider = this 30 | } 31 | 32 | override fun provideSetting(player: Player, setting: String): Boolean 33 | { 34 | val profile = BasicsProfileService.find(player) 35 | ?: return false 36 | 37 | return profile.setting(setting, StateSettingValue.ENABLED) == StateSettingValue.DISABLED 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/provider/impl/LemonSettingProvider.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.provider.impl 2 | 3 | import gg.scala.lemon.handler.PlayerHandler 4 | import gg.tropic.practice.provider.SettingProvider 5 | import org.bukkit.entity.Player 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 10/16/2022 10 | */ 11 | object LemonSettingProvider : SettingProvider 12 | { 13 | override fun provideSetting(player: Player, setting: String): Boolean 14 | { 15 | return PlayerHandler.find(player.uniqueId)?.hasMetadata(setting) ?: false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lobby/src/main/kotlin/gg/tropic/practice/statresets/StatResetTokens.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statresets 2 | 3 | import gg.tropic.practice.namespace 4 | import net.evilblock.cubed.ScalaCommonsSpigot 5 | import java.util.UUID 6 | import java.util.concurrent.CompletableFuture 7 | 8 | /** 9 | * @author GrowlyX 10 | * @since 7/12/2023 11 | */ 12 | object StatResetTokens 13 | { 14 | fun of(user: UUID) = CompletableFuture 15 | .supplyAsync { 16 | ScalaCommonsSpigot.instance.kvConnection 17 | .sync() 18 | .hget( 19 | "${namespace()}:statreset-tokens:tokens", 20 | user.toString() 21 | ) 22 | ?.toIntOrNull() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /services/application/api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly(project(":shared")) 3 | } 4 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/DPSDataSyncKeys.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api 2 | 3 | import net.kyori.adventure.key.Key 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 2/10/2023 8 | */ 9 | interface DPSDataSyncKeys 10 | { 11 | fun store(): Key 12 | 13 | /** 14 | * It's incredible stupid that we're using 15 | * a [Key] for the store template. 16 | */ 17 | fun newStore(): String 18 | { 19 | return store().asString() 20 | } 21 | fun sync(): Key 22 | 23 | fun keyOf(namespace: String, value: String) = 24 | Key.key(namespace, value) 25 | } 26 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/DPSDataSyncSource.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 11/14/2023 6 | */ 7 | enum class DPSDataSyncSource 8 | { 9 | Mongo, Redis 10 | } 11 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/DPSRedisService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api 2 | 3 | import gg.scala.aware.Aware 4 | import gg.scala.aware.AwareBuilder 5 | import gg.scala.aware.codec.codecs.interpretation.AwareMessageCodec 6 | import gg.scala.aware.message.AwareMessage 7 | import java.util.logging.Logger 8 | 9 | /** 10 | * @author GrowlyX 11 | * @since 9/24/2023 12 | */ 13 | class DPSRedisService( 14 | private val channel: String, 15 | private val raw: Boolean = false 16 | ) 17 | { 18 | private val aware by lazy { 19 | AwareBuilder 20 | .of("${ 21 | if (!raw) "practice:" else "" 22 | }$channel") 23 | .codec(AwareMessageCodec) 24 | .logger(Logger.getAnonymousLogger()) 25 | .build() 26 | } 27 | 28 | fun configure( 29 | lambda: Aware.() -> T 30 | ): T 31 | { 32 | return this.aware.lambda() 33 | } 34 | 35 | fun createMessage(packet: String, vararg pairs: Pair): AwareMessage = 36 | AwareMessage.of(packet, this.aware, *pairs) 37 | 38 | fun start() 39 | { 40 | this.aware.connect() 41 | .toCompletableFuture() 42 | .join() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/DPSRedisShared.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api 2 | 3 | import gg.scala.aware.thread.AwareThreadContext 4 | import gg.tropic.practice.serializable.Message 5 | import io.lettuce.core.api.StatefulRedisConnection 6 | import net.evilblock.cubed.serializers.Serializers 7 | import java.util.UUID 8 | 9 | /** 10 | * @author GrowlyX 11 | * @since 9/24/2023 12 | */ 13 | object DPSRedisShared 14 | { 15 | val keyValueCache: StatefulRedisConnection = DPSRedisService("dpskv") 16 | .configure { internal().connect() } 17 | 18 | private val lobbyBridge = DPSRedisService("lobbies") 19 | .apply(DPSRedisService::start) 20 | 21 | val applicationBridge = DPSRedisService("application") 22 | .apply(DPSRedisService::start) 23 | 24 | fun redirect(players: List, server: String) 25 | { 26 | lobbyBridge.createMessage( 27 | packet = "redirect", 28 | "playerIDs" to players, 29 | "server" to server 30 | ).publish( 31 | AwareThreadContext.SYNC 32 | ) 33 | } 34 | 35 | fun sendMessage(players: List, messages: List) 36 | { 37 | lobbyBridge.createMessage( 38 | packet = "send-message", 39 | "playerIDs" to players, 40 | "message" to messages.joinToString("\n") 41 | ).publish( 42 | AwareThreadContext.SYNC 43 | ) 44 | } 45 | 46 | fun sendMessage(players: List, message: Message) 47 | { 48 | lobbyBridge.createMessage( 49 | packet = "send-action-message", 50 | "playerIDs" to players, 51 | "message" to Serializers.gson.toJson(message) 52 | ).publish( 53 | AwareThreadContext.SYNC 54 | ) 55 | } 56 | 57 | fun sendBroadcast(message: Message) 58 | { 59 | lobbyBridge.createMessage( 60 | packet = "send-action-broadcast", 61 | "message" to Serializers.gson.toJson(message) 62 | ).publish( 63 | AwareThreadContext.SYNC 64 | ) 65 | } 66 | 67 | fun sendNotificationSound(players: List, setting: String) 68 | { 69 | lobbyBridge.createMessage( 70 | packet = "send-notification-sound", 71 | "playerIDs" to players, 72 | "setting" to setting 73 | ).publish( 74 | AwareThreadContext.SYNC 75 | ) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/game/ImmutableAbstractGame.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.game 2 | 3 | import gg.scala.store.storage.storable.IDataStoreObject 4 | import gg.tropic.practice.application.api.defaults.kit.ImmutableKit 5 | import java.util.* 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 9/24/2023 10 | */ 11 | enum class GameTeamSide 12 | { 13 | A, B 14 | } 15 | 16 | class GameTeam( 17 | val side: GameTeamSide, 18 | val players: List 19 | ) 20 | 21 | class AbstractGame( 22 | val expectation: UUID, 23 | val teams: Map, 24 | val kit: ImmutableKit 25 | ) : IDataStoreObject 26 | { 27 | override val identifier: UUID 28 | get() = this.expectation 29 | 30 | var startTimestamp = -1L 31 | } 32 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/game/ImmutableDuelExpectation.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.game 2 | 3 | import gg.tropic.practice.queue.QueueType 4 | import java.util.* 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 9/24/2023 9 | */ 10 | data class GameExpectation( 11 | val identifier: UUID, 12 | val players: List, 13 | val teams: Map, 14 | val kitId: String, 15 | val mapId: String, 16 | /** 17 | * Null queue types mean it is a private duel. 18 | */ 19 | val queueType: QueueType? = null, 20 | val queueId: String? = null 21 | ) 22 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/kit/ImmutableKit.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.kit 2 | 3 | import gg.tropic.practice.kit.feature.FeatureFlag 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 9/24/2023 8 | */ 9 | data class ImmutableKit( 10 | val id: String, 11 | val displayName: String, 12 | val features: Map> = mutableMapOf() 13 | ) 14 | { 15 | fun features(flag: FeatureFlag) = features.containsKey(flag) 16 | fun featureConfig(flag: FeatureFlag, key: String) = 17 | features[flag]?.get(key) ?: flag.schema[key]!! 18 | } 19 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/kit/ImmutableKitContainer.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.kit 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/24/2023 6 | */ 7 | data class ImmutableKitContainer( 8 | val kits: Map = mapOf() 9 | ) 10 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/kit/KitDataSync.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.kit 2 | 3 | import gg.tropic.practice.application.api.DPSDataSync 4 | import gg.tropic.practice.application.api.DPSDataSyncKeys 5 | import gg.tropic.practice.application.api.DPSDataSyncSource 6 | import gg.tropic.practice.namespace 7 | import gg.tropic.practice.suffixWhenDev 8 | import net.kyori.adventure.key.Key 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 9/24/2023 13 | */ 14 | object KitDataSync : DPSDataSync() 15 | { 16 | object DPSMapKeys : DPSDataSyncKeys 17 | { 18 | override fun newStore() = "mi-practice-kits" 19 | 20 | override fun store() = Key.key(namespace(), "kits") 21 | override fun sync() = Key.key(namespace().suffixWhenDev(), "ksync") 22 | } 23 | 24 | override fun locatedIn() = DPSDataSyncSource.Mongo 25 | 26 | override fun keys() = DPSMapKeys 27 | override fun type() = ImmutableKitContainer::class.java 28 | 29 | private val hooks = mutableListOf<() -> Unit>() 30 | 31 | fun onReload(hook: () -> Unit) = hooks.add(hook) 32 | 33 | override fun postReload() 34 | { 35 | super.postReload() 36 | hooks.forEach { it() } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/kit/group/ImmutableKitContainer.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.kit.group 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/24/2023 6 | */ 7 | data class ImmutableKitContainer( 8 | val backingGroups: List = listOf() 9 | ) 10 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/kit/group/ImmutableKitGroup.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.kit.group 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/24/2023 6 | */ 7 | data class ImmutableKitGroup( 8 | val id: String, 9 | val contains: MutableList = mutableListOf() 10 | ) 11 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/kit/group/KitGroupDataSync.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.kit.group 2 | 3 | import gg.tropic.practice.application.api.DPSDataSync 4 | import gg.tropic.practice.application.api.DPSDataSyncKeys 5 | import gg.tropic.practice.application.api.DPSDataSyncSource 6 | import gg.tropic.practice.application.api.defaults.kit.ImmutableKit 7 | import gg.tropic.practice.namespace 8 | import gg.tropic.practice.suffixWhenDev 9 | import net.kyori.adventure.key.Key 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 9/24/2023 14 | */ 15 | object KitGroupDataSync : DPSDataSync() 16 | { 17 | object DPSKitGroupKeys : DPSDataSyncKeys 18 | { 19 | override fun newStore() = "mi-practice-kits-groups" 20 | 21 | override fun store() = Key.key(namespace(), "groups") 22 | override fun sync() = Key.key(namespace().suffixWhenDev(), "gsync") 23 | } 24 | 25 | override fun locatedIn() = DPSDataSyncSource.Mongo 26 | 27 | override fun keys() = DPSKitGroupKeys 28 | override fun type() = ImmutableKitContainer::class.java 29 | 30 | fun groupsOf(kit: ImmutableKit) = cached().backingGroups 31 | .filter { 32 | kit.id in it.contains 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/map/ImmutableMap.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.map 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/24/2023 6 | */ 7 | data class ImmutableMap( 8 | val name: String, 9 | val displayName: String, 10 | val associatedSlimeTemplate: String, 11 | val associatedKitGroups: Set, 12 | val locked: Boolean 13 | ) 14 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/map/ImmutableMapContainer.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.map 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/24/2023 6 | */ 7 | data class ImmutableMapContainer(val maps: Map = mapOf()) 8 | -------------------------------------------------------------------------------- /services/application/api/src/main/kotlin/gg/tropic/practice/application/api/defaults/map/MapDataSync.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.application.api.defaults.map 2 | 3 | import gg.tropic.practice.application.api.DPSDataSync 4 | import gg.tropic.practice.application.api.DPSDataSyncKeys 5 | import gg.tropic.practice.application.api.DPSDataSyncSource 6 | import gg.tropic.practice.application.api.defaults.kit.ImmutableKit 7 | import gg.tropic.practice.application.api.defaults.kit.group.ImmutableKitGroup 8 | import gg.tropic.practice.application.api.defaults.kit.group.KitGroupDataSync 9 | import gg.tropic.practice.namespace 10 | import gg.tropic.practice.suffixWhenDev 11 | import net.kyori.adventure.key.Key 12 | 13 | /** 14 | * @author GrowlyX 15 | * @since 9/24/2023 16 | */ 17 | object MapDataSync : DPSDataSync() 18 | { 19 | object DPSMapKeys : DPSDataSyncKeys 20 | { 21 | override fun newStore() = "mi-practice-maps" 22 | 23 | override fun store() = Key.key(namespace(), "maps") 24 | override fun sync() = Key.key(namespace().suffixWhenDev(), "msync") 25 | } 26 | 27 | override fun locatedIn() = DPSDataSyncSource.Mongo 28 | 29 | override fun keys() = DPSMapKeys 30 | override fun type() = ImmutableMapContainer::class.java 31 | 32 | fun selectRandomMapCompatibleWith(kit: ImmutableKit): ImmutableMap? 33 | { 34 | val groups = KitGroupDataSync.groupsOf(kit) 35 | .map(ImmutableKitGroup::id) 36 | 37 | return cached().maps.values 38 | .filterNot(ImmutableMap::locked) 39 | .shuffled() 40 | .firstOrNull { 41 | groups.intersect(it.associatedKitGroups).isNotEmpty() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /services/application/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | } 4 | 5 | dependencies { 6 | implementation(kotlin("stdlib")) 7 | 8 | implementation(project(":services:games:game-manager")) 9 | implementation(project(":services:replications:replication-manager")) 10 | implementation(project(":services:replications:replication-models")) 11 | implementation(project(":shared")) 12 | implementation(project(":services:queue")) 13 | implementation(project(":services:tournaments")) 14 | implementation(project(":services:statistics")) 15 | implementation(project(":services:application:api")) 16 | implementation(project(":services:statistics:leaderboards")) 17 | 18 | implementation("org.apache.commons:commons-lang3:3.14.0") 19 | 20 | implementation("org.apache.logging.log4j:log4j-api:2.20.0") 21 | implementation("org.apache.logging.log4j:log4j-core:2.20.0") 22 | 23 | implementation("gg.scala.commons:store:3.5.1") 24 | implementation("gg.scala.commons:serversync:3.5.1") 25 | implementation("gg.scala.commons:serializers:3.5.1") 26 | 27 | implementation("net.kyori:adventure-key:4.11.0") 28 | implementation("net.kyori:adventure-text-serializer-gson:4.11.0") 29 | 30 | implementation("com.github.ben-manes.caffeine:caffeine:3.1.8") 31 | implementation("org.mongodb:mongo-java-driver:3.12.14") 32 | 33 | implementation("com.xenomachina:kotlin-argparser:2.0.7") 34 | implementation("gg.scala.store:shared:0.1.8") 35 | 36 | implementation("com.google.code.gson:gson:2.10.1") 37 | implementation("gg.scala.aware:aware:1.1.9") 38 | implementation("io.lettuce:lettuce-core:6.2.6.RELEASE") 39 | 40 | implementation("org.litote.kmongo:kmongo:4.11.0") 41 | } 42 | 43 | application { 44 | mainClass.set("gg.tropic.practice.application.ApplicationServerKt") 45 | } 46 | -------------------------------------------------------------------------------- /services/games/game-manager/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly(project(":shared")) 3 | compileOnly(project(":services:application:api")) 4 | } 5 | -------------------------------------------------------------------------------- /services/games/game-manager/src/main/kotlin/gg/tropic/practice/games/manager/GameManager.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.manager 2 | 3 | import com.github.benmanes.caffeine.cache.Caffeine 4 | import gg.tropic.practice.application.api.DPSRedisService 5 | import gg.tropic.practice.application.api.DPSRedisShared 6 | import gg.tropic.practice.games.GameStatus 7 | import gg.tropic.practice.games.GameStatusIndexes 8 | import gg.tropic.practice.namespace 9 | import gg.tropic.practice.suffixWhenDev 10 | import net.evilblock.cubed.serializers.Serializers 11 | import java.util.concurrent.CompletableFuture 12 | import java.util.concurrent.ForkJoinPool 13 | import java.util.concurrent.TimeUnit 14 | 15 | /** 16 | * @author GrowlyX 17 | * @since 9/25/2023 18 | */ 19 | object GameManager 20 | { 21 | private val redis = DPSRedisService("gamemanager") 22 | .apply(DPSRedisService::start) 23 | 24 | private val gameListingCache = Caffeine.newBuilder() 25 | .expireAfterWrite(2L, TimeUnit.SECONDS) 26 | .removalListener { _, _, _ -> syncStatusIndexes() } 27 | .build() 28 | 29 | private val dpsCache = DPSRedisShared.keyValueCache 30 | 31 | fun allGames() = allGameStatuses() 32 | .thenApply { 33 | it.values 34 | .flatMap(GameStatus::games) 35 | } 36 | 37 | fun allGameStatuses() = CompletableFuture 38 | .supplyAsync { 39 | gameListingCache 40 | .asMap().mapValues { (_, value) -> 41 | dpsCache 42 | .sync() 43 | .get(value) 44 | .let { 45 | Serializers.gson.fromJson( 46 | it, GameStatus::class.java 47 | ) 48 | } 49 | } 50 | } 51 | 52 | private fun syncStatusIndexes() 53 | { 54 | ForkJoinPool.commonPool().submit { 55 | dpsCache.sync().set( 56 | "${namespace().suffixWhenDev()}:gamemanager:status-indexes", 57 | Serializers.gson.toJson( 58 | GameStatusIndexes(gameListingCache.asMap()) 59 | ) 60 | ) 61 | } 62 | } 63 | 64 | fun load() 65 | { 66 | redis.configure { 67 | listen("status") { 68 | val server = retrieve("server") 69 | val status = retrieve("status") 70 | 71 | val key = "${namespace().suffixWhenDev()}:gamemanager:status:$server" 72 | 73 | dpsCache.sync().psetex( 74 | key, 1000 * 2, 75 | Serializers.gson.toJson(status) 76 | ) 77 | 78 | gameListingCache.put(server, key) 79 | syncStatusIndexes() 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /services/queue/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly(project(":shared")) 3 | compileOnly(project(":services:application:api")) 4 | compileOnly(project(":services:replications:replication-manager")) 5 | compileOnly(project(":services:games:game-manager")) 6 | 7 | implementation("net.md-5:bungeecord-chat:1.20-R0.1") 8 | } 9 | -------------------------------------------------------------------------------- /services/replications/replication-manager/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly(project(":shared")) 3 | 4 | compileOnly(project(":services:application:api")) 5 | compileOnly(project(":services:replications:replication-models")) 6 | } 7 | -------------------------------------------------------------------------------- /services/replications/replication-manager/src/main/kotlin/gg/tropic/practice/replications/manager/ReplicationModels.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.replications.manager 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/24/2023 6 | */ 7 | data class Replication( 8 | val server: String, 9 | val associatedMapName: String, 10 | val name: String, 11 | val inUse: Boolean = false 12 | ) 13 | 14 | data class ReplicationStatus( 15 | val replications: Map> 16 | ) 17 | -------------------------------------------------------------------------------- /services/replications/replication-models/src/main/kotlin/gg/tropic/practice/replications/models/Replication.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.replications.models 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 10/9/2023 6 | */ 7 | data class Replication( 8 | val server: String, 9 | val associatedMapName: String, 10 | val name: String, 11 | val inUse: Boolean = false 12 | ) 13 | -------------------------------------------------------------------------------- /services/replications/replication-models/src/main/kotlin/gg/tropic/practice/replications/models/ReplicationStatus.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.replications.models 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 10/9/2023 6 | */ 7 | data class ReplicationStatus( 8 | val replications: Map> 9 | ) 10 | -------------------------------------------------------------------------------- /services/statistics/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly("joda-time:joda-time:2.12.5") 3 | } 4 | -------------------------------------------------------------------------------- /services/statistics/leaderboards/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly("org.litote.kmongo:kmongo:4.11.0") 3 | 4 | compileOnly(project(":shared")) 5 | compileOnly(project(":services:application:api")) 6 | } 7 | -------------------------------------------------------------------------------- /services/statistics/leaderboards/src/main/kotlin/gg/tropic/practice/statistics/leaderboards/DateFloorUtils.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics.leaderboards 2 | 3 | import org.joda.time.DateTime 4 | import org.joda.time.Period 5 | import org.joda.time.PeriodType 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 12/16/2023 10 | */ 11 | fun floorDateTime(lastValidation: Long, lifetime: DateTime.() -> DateTime) = Period( 12 | DateTime.now(), 13 | lifetime( 14 | DateTime(lastValidation) 15 | ), 16 | PeriodType.millis() 17 | ).millis 18 | -------------------------------------------------------------------------------- /services/statistics/leaderboards/src/main/kotlin/gg/tropic/practice/statistics/leaderboards/ImmutablePracticeProfile.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics.leaderboards 2 | 3 | import gg.scala.store.storage.storable.IDataStoreObject 4 | import gg.tropic.practice.statistics.GlobalStatistics 5 | import gg.tropic.practice.statistics.KitStatistics 6 | import gg.tropic.practice.statistics.ranked.RankedKitStatistics 7 | import java.util.* 8 | 9 | /** 10 | * @author GrowlyX 11 | * @since 12/16/2023 12 | */ 13 | data class ImmutablePracticeProfile( 14 | override val identifier: UUID 15 | ) : IDataStoreObject 16 | { 17 | val globalStatistics: GlobalStatistics = GlobalStatistics() 18 | 19 | val casualStatistics: Map = mapOf() 20 | val rankedStatistics: Map = mapOf() 21 | } 22 | -------------------------------------------------------------------------------- /services/statistics/leaderboards/src/main/kotlin/gg/tropic/practice/statistics/leaderboards/Leaderboard.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics.leaderboards 2 | 3 | import gg.tropic.practice.application.api.defaults.kit.ImmutableKit 4 | import java.util.logging.Logger 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 12/16/2023 9 | */ 10 | data class Leaderboard( 11 | val leaderboardType: LeaderboardType, 12 | val kit: ImmutableKit?, 13 | val initial: Boolean = false 14 | ) 15 | { 16 | init 17 | { 18 | if (initial) 19 | { 20 | Logger.getGlobal().info( 21 | "[leaderboards] Configured a leaderboard ${leaderboardId()}" 22 | ) 23 | } 24 | } 25 | 26 | fun leaderboardId() = "${leaderboardType.name}:${kit?.id ?: "global"}" 27 | } 28 | -------------------------------------------------------------------------------- /services/statistics/leaderboards/src/main/kotlin/gg/tropic/practice/statistics/leaderboards/LeaderboardType.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics.leaderboards 2 | 3 | import gg.tropic.practice.application.api.defaults.kit.ImmutableKit 4 | import gg.tropic.practice.statistics.KitStatistics 5 | import gg.tropic.practice.statistics.ranked.RankedKitStatistics 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 9/25/2023 10 | */ 11 | enum class LeaderboardType( 12 | val fromKit: ImmutablePracticeProfile.(ImmutableKit) -> Long?, 13 | val fromGlobal: ImmutablePracticeProfile.() -> Long?, 14 | val enforceRanked: Boolean = false 15 | ) 16 | { 17 | ELO( 18 | fromKit = { 19 | rankedStatistics[it.id]?.elo?.toLong() 20 | }, 21 | fromGlobal = { 22 | val average = rankedStatistics.values 23 | .map(RankedKitStatistics::elo) 24 | .average() 25 | 26 | if (average.isNaN()) null else average.toLong() 27 | }, 28 | enforceRanked = true 29 | ), 30 | CasualWins( 31 | fromKit = { 32 | casualStatistics[it.id]?.wins?.toLong() 33 | }, 34 | fromGlobal = { 35 | casualStatistics.values 36 | .sumOf(KitStatistics::wins) 37 | .toLong() 38 | } 39 | ), 40 | RankedWins( 41 | fromKit = { 42 | rankedStatistics[it.id]?.wins?.toLong() 43 | }, 44 | fromGlobal = { 45 | rankedStatistics.values 46 | .sumOf(KitStatistics::wins) 47 | .toLong() 48 | } 49 | ), 50 | CasualWinStreak( 51 | fromKit = context@{ 52 | val volatile = casualStatistics[it.id]?.dailyStreak 53 | ?: return@context null 54 | 55 | volatile().toLong() 56 | }, 57 | fromGlobal = { 58 | val result = casualStatistics.maxOfOrNull { 59 | val volatile = casualStatistics[it.key]?.dailyStreak 60 | ?: return@maxOfOrNull -1L 61 | 62 | volatile().toLong() 63 | } 64 | 65 | if (result == -1L) null else result 66 | } 67 | ), 68 | RankedWinStreak( 69 | fromKit = context@{ 70 | val volatile = rankedStatistics[it.id]?.dailyStreak 71 | ?: return@context null 72 | 73 | volatile().toLong() 74 | }, 75 | fromGlobal = { 76 | val result = rankedStatistics.maxOfOrNull { 77 | val volatile = rankedStatistics[it.key]?.dailyStreak 78 | ?: return@maxOfOrNull -1L 79 | 80 | volatile().toLong() 81 | } 82 | 83 | if (result == -1L) null else result 84 | }, 85 | enforceRanked = true 86 | ) 87 | } 88 | -------------------------------------------------------------------------------- /services/statistics/src/main/kotlin/gg/tropic/practice/statistics/ApplyUpdates.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/21/2023 6 | */ 7 | class ApplyUpdates( 8 | private val update: List<(T) -> Unit> 9 | ) 10 | { 11 | fun apply(value: T) 12 | { 13 | update.forEach { it(value) } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /services/statistics/src/main/kotlin/gg/tropic/practice/statistics/ApplyUpdatesStateless.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/21/2023 6 | */ 7 | class ApplyUpdatesStateless( 8 | private val update: List<() -> Unit>, 9 | private val inherits: ApplyUpdatesStateless? = null 10 | ) 11 | { 12 | fun apply() 13 | { 14 | update.forEach { it() } 15 | inherits?.apply() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /services/statistics/src/main/kotlin/gg/tropic/practice/statistics/Counter.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics 2 | 3 | import java.util.* 4 | import java.util.concurrent.ConcurrentHashMap 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 12/21/2023 9 | */ 10 | data class Counter( 11 | private val owner: UUID, 12 | private val mappings: ConcurrentHashMap = ConcurrentHashMap() 13 | ) 14 | { 15 | fun update(id: String, value: Int) = mappings.put(id, value.toDouble()) 16 | fun update(id: String, value: Double) = mappings.put(id, value) 17 | 18 | fun reset(id: String) 19 | { 20 | mappings[id] = 0.0 21 | } 22 | 23 | fun increment(id: String, amount: Double = 1.0) 24 | { 25 | mappings.compute(id) { _, value -> 26 | val old = (value ?: 0.0) 27 | // TODO: Debug 28 | /*Bukkit.getPlayer(owner)?.apply { 29 | sendMessage("$id: $old -> ${old + amount}") 30 | }*/ 31 | old + amount 32 | } 33 | } 34 | 35 | fun valueOf(id: String) = mappings.getOrPut(id) { 0.0 } 36 | } 37 | -------------------------------------------------------------------------------- /services/statistics/src/main/kotlin/gg/tropic/practice/statistics/GlobalStatistics.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/21/2023 6 | */ 7 | class GlobalStatistics 8 | { 9 | var totalPlays = 0 10 | private set 11 | var totalWins = 0 12 | private set 13 | var totalLosses = 0 14 | private set 15 | var totalKills = 0 16 | private set 17 | var totalDeaths = 0 18 | private set 19 | 20 | fun sharedGamePlays() = ApplyUpdatesStateless(listOf { totalPlays++ }) 21 | 22 | fun userPlayedGameAndWon() = ApplyUpdatesStateless( 23 | listOf { 24 | totalWins++ 25 | }, 26 | sharedGamePlays() 27 | ) 28 | 29 | fun userPlayedGameAndLost() = ApplyUpdatesStateless( 30 | listOf { 31 | totalLosses++ 32 | }, 33 | sharedGamePlays() 34 | ) 35 | 36 | fun userPlayedGameAndLostUserDied() = ApplyUpdatesStateless( 37 | listOf({ 38 | totalLosses++ 39 | }, { 40 | totalDeaths++ 41 | }), 42 | sharedGamePlays() 43 | ) 44 | 45 | fun userKilledOpponent() = ApplyUpdatesStateless( 46 | listOf { 47 | totalKills++ 48 | } 49 | ) 50 | 51 | fun userWasKilled() = ApplyUpdatesStateless( 52 | listOf { 53 | totalDeaths++ 54 | } 55 | ) 56 | 57 | fun userWonGameAndKilledOpponent() = ApplyUpdatesStateless( 58 | listOf({ 59 | totalWins++ 60 | }, { 61 | totalKills++ 62 | }), 63 | sharedGamePlays() 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /services/statistics/src/main/kotlin/gg/tropic/practice/statistics/KitStatistics.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/21/2023 6 | */ 7 | open class KitStatistics 8 | { 9 | var plays = 0 10 | var wins = 0 11 | var kills = 0 12 | var deaths = 0 13 | 14 | var longestStreak = 0 15 | private set 16 | 17 | var streak = 0 18 | private set 19 | var dailyStreak = SingleDayLifetime(defaultValue = 0) 20 | 21 | fun streakUpdates() = ApplyUpdates(listOf({ 22 | dailyStreak /= if (it) dailyStreak() + 1 else 0 23 | }, { 24 | if (it) 25 | { 26 | streak += 1 27 | } else 28 | { 29 | streak = 0 30 | } 31 | }, { 32 | if (it) 33 | { 34 | if (streak > longestStreak) 35 | { 36 | longestStreak = streak 37 | } 38 | } 39 | })) 40 | } 41 | -------------------------------------------------------------------------------- /services/statistics/src/main/kotlin/gg/tropic/practice/statistics/Volatile.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics 2 | 3 | import org.joda.time.DateTime 4 | import org.joda.time.Period 5 | import org.joda.time.PeriodType 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 9/21/2023 10 | */ 11 | abstract class Volatile( 12 | private val defaultValue: T 13 | ) 14 | { 15 | abstract fun lifetime(): DateTime.() -> DateTime 16 | 17 | private var value = defaultValue 18 | private var lastValidation = DateTime 19 | .now() 20 | .millis 21 | 22 | operator fun divAssign(value: T) 23 | { 24 | this.value = value 25 | } 26 | 27 | operator fun invoke(): T 28 | { 29 | return get() 30 | } 31 | 32 | fun lastValidation() = lastValidation 33 | fun isExpired() = lifetime()(DateTime(lastValidation)).isBeforeNow 34 | fun getUnchecked() = value 35 | 36 | fun get(): T 37 | { 38 | if (isExpired()) 39 | { 40 | value = defaultValue 41 | lastValidation = DateTime.now().millis 42 | } 43 | 44 | return value 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /services/statistics/src/main/kotlin/gg/tropic/practice/statistics/VolatileImpls.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics 2 | 3 | import org.joda.time.DateTime 4 | import org.joda.time.DateTimeZone 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 9/21/2023 9 | */ 10 | private var nycTimeZone: DateTimeZone = DateTimeZone.forID("America/New_York") 11 | 12 | class SingleDayLifetime( 13 | defaultValue: T 14 | ) : Volatile(defaultValue) 15 | { 16 | override fun lifetime(): DateTime.() -> DateTime = { 17 | plusDays(1) 18 | .withZone(nycTimeZone) 19 | .withTimeAtStartOfDay() 20 | } 21 | } 22 | 23 | class SingleWeekLifetime( 24 | defaultValue: T 25 | ) : Volatile(defaultValue) 26 | { 27 | override fun lifetime(): DateTime.() -> DateTime = { 28 | weekOfWeekyear().roundFloorCopy() 29 | .withZone(nycTimeZone) 30 | .withTimeAtStartOfDay() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /services/statistics/src/main/kotlin/gg/tropic/practice/statistics/ranked/RankedKitStatistics.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.statistics.ranked 2 | 3 | import gg.tropic.practice.statistics.ApplyUpdates 4 | import gg.tropic.practice.statistics.KitStatistics 5 | import gg.tropic.practice.statistics.SingleDayLifetime 6 | import gg.tropic.practice.statistics.SingleWeekLifetime 7 | 8 | /** 9 | * @author GrowlyX 10 | * @since 9/21/2023 11 | */ 12 | class RankedKitStatistics : KitStatistics() 13 | { 14 | var elo = 1000 15 | private set 16 | 17 | var highestElo = elo 18 | private set 19 | 20 | val dailyEloChange = SingleDayLifetime(defaultValue = 0) 21 | val weeklyEloChange = SingleWeekLifetime(defaultValue = 0) 22 | 23 | fun updateELO(newElo: Int) 24 | { 25 | this.elo = newElo 26 | } 27 | 28 | fun eloUpdates() = ApplyUpdates(listOf({ 29 | this.elo = it 30 | }, { 31 | dailyEloChange /= dailyEloChange() + (it - elo) 32 | }, { 33 | weeklyEloChange /= weeklyEloChange() + (it - elo) 34 | }, { 35 | if (it > highestElo) 36 | { 37 | highestElo = it 38 | } 39 | })) 40 | } 41 | -------------------------------------------------------------------------------- /services/tournaments/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compileOnly(project(":services:games:game-manager")) 3 | compileOnly(project(":services:application:api")) 4 | compileOnly(project(":services:queue")) 5 | compileOnly(project(":shared")) 6 | 7 | api("com.tinder.statemachine:statemachine:0.2.0") 8 | } 9 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | plugins { 9 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" 10 | } 11 | 12 | rootProject.name = "tropic-practice" 13 | include( 14 | "shared", "game", "lobby", "devtools", 15 | "services:application", "services:queue", "services:statistics", "services:tournaments", 16 | "services:statistics:leaderboards", 17 | "services:application:api", 18 | "services:replications:replication-manager", 19 | "services:replications:replication-models", 20 | "services:games:game-manager" 21 | ) 22 | -------------------------------------------------------------------------------- /shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven("https://repo.glaremasters.me/repository/concuncan/") 3 | } 4 | 5 | dependencies { 6 | implementation(project(":services:replications:replication-models")) 7 | api(project(":services:statistics")) 8 | 9 | compileOnly("gg.tropic.game.extensions:tropic-core-game-extensions:1.2.8") 10 | 11 | api("joda-time:joda-time:2.12.5") 12 | api("xyz.xenondevs:particle:1.7.1") 13 | } 14 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/Environment.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 2/10/2024 6 | */ 7 | var devProvider: () -> Boolean = { false } 8 | 9 | fun isDev() = devProvider() 10 | fun isProd() = !devProvider() 11 | 12 | fun namespace() = "tropicpractice" 13 | 14 | fun practiceGroup() = "mip" 15 | fun gameGroup() = "mipgame" 16 | fun lobbyGroup() = "miplobby" 17 | 18 | fun String.suffixWhenDev() = (if (isDev()) "${if (this == "tropicpractice") 19 | "tropicprac" else this}DEV" else this) 20 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/PracticeShared.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice 2 | 3 | import com.google.gson.TypeAdapter 4 | import com.google.gson.stream.JsonReader 5 | import com.google.gson.stream.JsonWriter 6 | import gg.scala.commons.agnostic.sync.ServerSync 7 | import gg.tropic.practice.map.metadata.AbstractMapMetadata 8 | import net.evilblock.cubed.serializers.Serializers 9 | import net.evilblock.cubed.serializers.impl.AbstractTypeSerializer 10 | import org.bukkit.potion.PotionEffectType 11 | 12 | /** 13 | * @author GrowlyX 14 | * @since 8/5/2022 15 | */ 16 | object PracticeShared 17 | { 18 | // i don't like this, but we need to do it 19 | init 20 | { 21 | Serializers.create { 22 | registerTypeAdapter( 23 | AbstractMapMetadata::class.java, 24 | AbstractTypeSerializer() 25 | ) 26 | 27 | registerTypeAdapter( 28 | PotionEffectType::class.java, 29 | object : TypeAdapter() 30 | { 31 | override fun write(out: JsonWriter?, value: PotionEffectType?) 32 | { 33 | out?.value(value?.name) 34 | } 35 | 36 | override fun read(`in`: JsonReader?): PotionEffectType? 37 | { 38 | return PotionEffectType.getByName(`in`?.nextString()) 39 | } 40 | } 41 | ) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/commands/CommandExt.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.basics.plugin.profile.BasicsProfile 4 | import gg.scala.basics.plugin.profile.BasicsProfileService 5 | import gg.scala.commons.acf.ConditionFailedException 6 | import gg.scala.lemon.util.QuickAccess.username 7 | import gg.scala.store.controller.DataStoreObjectControllerCache 8 | import gg.scala.store.storage.type.DataStoreStorageType 9 | import gg.tropic.practice.profile.PracticeProfile 10 | import gg.tropic.practice.profile.PracticeProfileService 11 | import net.evilblock.cubed.util.CC 12 | import java.util.* 13 | 14 | /** 15 | * @author GrowlyX 16 | * @since 10/20/2023 17 | */ 18 | val UUID.offlineProfile: PracticeProfile 19 | get() = PracticeProfileService.find(this) 20 | ?: DataStoreObjectControllerCache 21 | .findNotNull() 22 | .load(this, DataStoreStorageType.MONGO) 23 | .join() 24 | ?: throw ConditionFailedException( 25 | "${CC.YELLOW}${username()}${CC.RED} has not logged onto our duels server." 26 | ) 27 | 28 | val UUID.basicsProfile: BasicsProfile 29 | get() = BasicsProfileService.find(this) 30 | ?: DataStoreObjectControllerCache 31 | .findNotNull() 32 | .load(this, DataStoreStorageType.MONGO) 33 | .join() 34 | ?: throw ConditionFailedException( 35 | "${CC.YELLOW}${username()}${CC.RED} has not logged onto our network." 36 | ) 37 | 38 | val unsignedNumberExtractor = "[0-9]\\d*".toRegex() 39 | fun extractNumbers(string: String) = unsignedNumberExtractor 40 | .findAll(string) 41 | .sumOf { it.value.toInt() } 42 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/commands/MatchInventoryCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.commons.acf.ConditionFailedException 4 | import gg.scala.commons.acf.annotation.CommandAlias 5 | import gg.scala.commons.acf.annotation.Optional 6 | import gg.scala.commons.annotations.commands.AutoRegister 7 | import gg.scala.commons.command.ScalaCommand 8 | import gg.scala.commons.issuer.ScalaPlayer 9 | import gg.scala.lemon.util.QuickAccess.username 10 | import gg.tropic.practice.reports.GameReportService 11 | import gg.tropic.practice.reports.menu.PlayerViewMenu 12 | import gg.tropic.practice.reports.menu.SelectPlayerMenu 13 | import net.evilblock.cubed.util.CC 14 | import java.util.* 15 | 16 | /** 17 | * @author GrowlyX 18 | * @since 10/22/2023 19 | */ 20 | @AutoRegister 21 | object MatchInventoryCommand : ScalaCommand() 22 | { 23 | @CommandAlias("matchinventory") 24 | fun onInventory( 25 | player: ScalaPlayer, 26 | matchId: UUID, 27 | @Optional playerId: UUID? 28 | ) = GameReportService.loadSnapshot(matchId) 29 | .thenAccept { 30 | if (it == null) 31 | { 32 | throw ConditionFailedException( 33 | "No match report exists with the ID ${CC.YELLOW}$matchId${CC.RED}." 34 | ) 35 | } 36 | 37 | if (playerId == null) 38 | { 39 | SelectPlayerMenu(it).openMenu(player.bukkit()) 40 | return@thenAccept 41 | } 42 | 43 | val snapshot = it.snapshots[playerId] 44 | ?: throw ConditionFailedException( 45 | "No match snapshot exists for the player ${CC.YELLOW}${ 46 | playerId.username() 47 | }${CC.RED}." 48 | ) 49 | 50 | PlayerViewMenu( 51 | gameReport = it, 52 | snapshot = snapshot 53 | ).openMenu( 54 | player.bukkit() 55 | ) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/commands/RegionCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands 2 | 3 | import gg.scala.commons.acf.ConditionFailedException 4 | import gg.scala.commons.acf.annotation.CommandAlias 5 | import gg.scala.commons.acf.annotation.CommandCompletion 6 | import gg.scala.commons.acf.annotation.CommandPermission 7 | import gg.scala.commons.annotations.commands.AutoRegister 8 | import gg.scala.commons.command.ScalaCommand 9 | import gg.scala.commons.issuer.ScalaPlayer 10 | import gg.scala.lemon.player.wrapper.AsyncLemonPlayer 11 | import gg.scala.lemon.util.QuickAccess 12 | import gg.tropic.practice.region.PlayerRegionFromRedisProxy 13 | import net.evilblock.cubed.util.CC 14 | 15 | /** 16 | * @author GrowlyX 17 | * @since 12/23/2023 18 | */ 19 | @AutoRegister 20 | object RegionCommand : ScalaCommand() 21 | { 22 | @CommandAlias("checkregion") 23 | @CommandCompletion("@mip-players") 24 | @CommandPermission("practice.command.checkregion") 25 | fun onCheckRegion( 26 | player: ScalaPlayer, 27 | target: AsyncLemonPlayer 28 | ) = target.validatePlayers(player.bukkit(), false) { 29 | val isPlayerOnline = QuickAccess 30 | .online(it.uniqueId) 31 | .join() 32 | 33 | if (!isPlayerOnline) 34 | { 35 | throw ConditionFailedException( 36 | "The player ${CC.YELLOW}${it.name}${CC.RED} is not logged onto the network." 37 | ) 38 | } 39 | 40 | val region = PlayerRegionFromRedisProxy 41 | .ofPlayerID(it.uniqueId) 42 | .join() 43 | 44 | player.sendMessage( 45 | "${CC.GREEN}The player ${it.name} is logged onto: ${CC.PRI}$region" 46 | ) 47 | } 48 | 49 | @CommandAlias("region|myregion") 50 | fun onRegion(player: ScalaPlayer) = PlayerRegionFromRedisProxy 51 | .of(player.bukkit()) 52 | .thenAccept { 53 | player.sendMessage( 54 | "${CC.GREEN}You are connected to the ${CC.PRI}${it.name}${CC.GREEN} region." 55 | ) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/commands/admin/matchlist/MatchListCommand.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands.admin.matchlist 2 | 3 | import gg.scala.commons.acf.annotation.CommandAlias 4 | import gg.scala.commons.acf.annotation.CommandCompletion 5 | import gg.scala.commons.acf.annotation.CommandPermission 6 | import gg.scala.commons.annotations.commands.AutoRegister 7 | import gg.scala.commons.command.ScalaCommand 8 | import gg.scala.commons.issuer.ScalaPlayer 9 | import gg.tropic.practice.services.GameManagerService 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 12/24/2023 14 | */ 15 | @AutoRegister 16 | object MatchListCommand : ScalaCommand() 17 | { 18 | @CommandAlias("matchlist") 19 | @CommandCompletion("@mip-players") 20 | @CommandPermission("practice.command.matchlist") 21 | fun onMatchList(player: ScalaPlayer) = GameManagerService 22 | .allGames() 23 | .thenAccept { references -> 24 | MatchListMenu(references).openMenu(player.bukkit()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/commands/customizers/KitGroupCommandCustomizers.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands.customizers 2 | 3 | import gg.scala.commons.acf.ConditionFailedException 4 | import gg.scala.commons.annotations.commands.customizer.CommandManagerCustomizer 5 | import gg.scala.commons.command.ScalaCommandManager 6 | import gg.tropic.practice.kit.group.KitGroup 7 | import gg.tropic.practice.kit.group.KitGroupService 8 | import gg.tropic.practice.map.Map 9 | import net.evilblock.cubed.util.CC 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 9/22/2023 14 | */ 15 | object KitGroupCommandCustomizers 16 | { 17 | @CommandManagerCustomizer 18 | fun customize(manager: ScalaCommandManager) 19 | { 20 | manager.commandContexts.registerContext(KitGroup::class.java) { 21 | val arg = it.popFirstArg() 22 | 23 | KitGroupService.cached().groups 24 | .firstOrNull { group -> 25 | group.id.equals(arg, true) 26 | } 27 | ?: throw ConditionFailedException( 28 | "No kit group with the ID ${CC.YELLOW}$arg${CC.RED} exists." 29 | ) 30 | } 31 | 32 | manager.commandCompletions 33 | .registerAsyncCompletion("kit-groups") { 34 | KitGroupService.cached() 35 | .groups.map(KitGroup::id) 36 | } 37 | 38 | manager.commandCompletions 39 | .registerAsyncCompletion("stranger-kit-groups") { 40 | val map = it.getContextValue(Map::class.java) 41 | 42 | KitGroupService.cached() 43 | .groups.map(KitGroup::id) 44 | .filterNot { group -> 45 | group in map.associatedKitGroups 46 | } 47 | } 48 | 49 | manager.commandCompletions 50 | .registerAsyncCompletion("associated-kit-groups") { 51 | val map = it.getContextValue(Map::class.java) 52 | 53 | KitGroupService.cached() 54 | .groups.map(KitGroup::id) 55 | .filter { group -> 56 | group in map.associatedKitGroups 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/commands/customizers/MapCommandCustomizers.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands.customizers 2 | 3 | import gg.scala.commons.acf.ConditionFailedException 4 | import gg.scala.commons.annotations.commands.customizer.CommandManagerCustomizer 5 | import gg.scala.commons.command.ScalaCommandManager 6 | import gg.tropic.practice.map.Map 7 | import gg.tropic.practice.map.MapService 8 | import net.evilblock.cubed.util.CC 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 9/22/2023 13 | */ 14 | object MapCommandCustomizers 15 | { 16 | @CommandManagerCustomizer 17 | fun customize(manager: ScalaCommandManager) 18 | { 19 | manager.commandContexts.registerContext(Map::class.java) { 20 | val arg = it.popFirstArg() 21 | 22 | MapService.cached().maps.values 23 | .firstOrNull { group -> 24 | group.name.equals(arg, true) 25 | } 26 | ?: throw ConditionFailedException( 27 | "No map with the ID ${CC.YELLOW}$arg${CC.RED} exists." 28 | ) 29 | } 30 | 31 | manager.commandCompletions 32 | .registerAsyncCompletion("maps") { 33 | MapService.cached().maps.keys 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/commands/menu/MapRatingOverviewMenu.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.commands.menu 2 | 3 | import gg.tropic.practice.map.MapService 4 | import gg.tropic.practice.map.rating.MapRatingService 5 | import net.evilblock.cubed.menu.Button 6 | import net.evilblock.cubed.menu.pagination.PaginatedMenu 7 | import net.evilblock.cubed.util.CC 8 | import net.evilblock.cubed.util.Color 9 | import net.evilblock.cubed.util.bukkit.ItemBuilder 10 | import org.bukkit.entity.Player 11 | 12 | /** 13 | * Class created on 1/12/2024 14 | 15 | * @author 98ping 16 | * @project tropic-practice 17 | * @website https://solo.to/redis 18 | */ 19 | class MapRatingOverviewMenu : PaginatedMenu() 20 | { 21 | override fun getAllPagesButtons(player: Player): Map 22 | { 23 | val buttons = mutableMapOf() 24 | 25 | MapService.cached().maps.values 26 | .forEach { 27 | buttons[buttons.size] = ItemBuilder.of(it.displayIcon.type) 28 | .name(Color.translate(it.displayName)) 29 | .addToLore( 30 | "${CC.GRAY}Average Rating: ${CC.WHITE}${MapRatingService.ratingMap[it.name] ?: 1}" 31 | ) 32 | .toButton() 33 | } 34 | 35 | return buttons 36 | } 37 | 38 | override fun getPrePaginatedTitle(player: Player) = "Viewing Map Ratings..." 39 | } 40 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/configuration/PracticeConfiguration.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.configuration 2 | 3 | import gg.tropic.practice.configuration.subconfig.DataSampleThresholds 4 | import gg.tropic.practice.map.metadata.anonymous.Position 5 | import net.evilblock.cubed.util.CC 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 10/13/2023 10 | */ 11 | data class PracticeConfiguration( 12 | var spawnLocation: Position = Position( 13 | 0.0, 100.0, 0.0, 180.0F, 0.0F 14 | ), 15 | val loginMOTD: MutableList = mutableListOf( 16 | "", 17 | "${CC.B_PRI}Welcome to Tropic Practice", 18 | "${CC.GRAY}We are currently in BETA! Report bugs in our Discord.", 19 | "" 20 | ), 21 | var rankedQueueEnabled: Boolean = true, 22 | var enableMIPTabHandler: Boolean? = true, 23 | var rankedMinimumWinRequirement: Int? = 5, 24 | var blockedHitCap: Int? = 10, 25 | var sampleThresholds: DataSampleThresholds? = DataSampleThresholds() 26 | ) 27 | { 28 | fun dataSampleThresholds(): DataSampleThresholds 29 | { 30 | if (sampleThresholds == null) 31 | { 32 | sampleThresholds = DataSampleThresholds() 33 | } 34 | 35 | return sampleThresholds!! 36 | } 37 | 38 | fun enableMIPTabHandler() = enableMIPTabHandler ?: true 39 | fun minimumWinRequirement() = rankedMinimumWinRequirement ?: 5 40 | } 41 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/configuration/PracticeConfigurationService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.configuration 2 | 3 | import gg.scala.commons.persist.datasync.DataSyncKeys 4 | import gg.scala.commons.persist.datasync.DataSyncService 5 | import gg.scala.commons.persist.datasync.DataSyncSource 6 | import gg.scala.flavor.service.Service 7 | import gg.tropic.practice.namespace 8 | import gg.tropic.practice.suffixWhenDev 9 | import net.kyori.adventure.key.Key 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 10/13/2023 14 | */ 15 | @Service 16 | object PracticeConfigurationService : DataSyncService() 17 | { 18 | object LobbyConfigurationKeys : DataSyncKeys 19 | { 20 | override fun newStore() = "mi-practice-lobbyconfig" 21 | 22 | override fun store() = Key.key(namespace(), "lobbyconf") 23 | override fun sync() = Key.key(namespace().suffixWhenDev(), "lcsync") 24 | } 25 | 26 | override fun locatedIn() = DataSyncSource.Mongo 27 | 28 | override fun keys() = LobbyConfigurationKeys 29 | override fun type() = PracticeConfiguration::class.java 30 | } 31 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/configuration/subconfig/DataSampleThresholds.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.configuration.subconfig 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 1/21/2024 6 | */ 7 | data class DataSampleThresholds( 8 | var autoClick: Int = 5, 9 | var doubleClick: Int = 3 10 | ) 11 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/expectation/GameExpectation.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.expectation 2 | 3 | import gg.tropic.practice.queue.QueueType 4 | import gg.tropic.practice.games.team.GameTeam 5 | import gg.tropic.practice.games.team.GameTeamSide 6 | import java.util.* 7 | 8 | /** 9 | * @author GrowlyX 10 | * @since 8/4/2022 11 | */ 12 | data class GameExpectation( 13 | val identifier: UUID, 14 | val players: List, 15 | val teams: Map, 16 | val kitId: String, 17 | val mapId: String, 18 | /** 19 | * Null queue types mean it is a private duel. 20 | */ 21 | val queueType: QueueType? = null, 22 | val queueId: String? = null 23 | ) 24 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/friendship/FriendshipRequirement.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.friendship 2 | 3 | import java.util.UUID 4 | import java.util.concurrent.CompletableFuture 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 1/19/2024 9 | */ 10 | interface FriendshipRequirement 11 | { 12 | fun existsBetween(playerOne: UUID, playerTwo: UUID): CompletableFuture 13 | } 14 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/friendship/FriendshipStateSetting.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.friendship 2 | 3 | import gg.scala.basics.plugin.settings.SettingValue 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 1/19/2024 9 | */ 10 | enum class FriendshipStateSetting(newDisplayName: String? = null) : SettingValue 11 | { 12 | Enabled, 13 | FriendsOnly("Friends Only"), 14 | Disabled; 15 | 16 | override val displayName = newDisplayName ?: name 17 | override fun display(player: Player) = true 18 | } 19 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/friendship/Friendships.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.friendship 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 1/19/2024 6 | */ 7 | object Friendships 8 | { 9 | var requirements: FriendshipRequirement = NoOpFriendshipRequirement 10 | } 11 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/friendship/NoOpFriendshipRequirement.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.friendship 2 | 3 | import java.util.* 4 | import java.util.concurrent.CompletableFuture 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 1/19/2024 9 | */ 10 | object NoOpFriendshipRequirement : FriendshipRequirement 11 | { 12 | override fun existsBetween(playerOne: UUID, playerTwo: UUID) = 13 | CompletableFuture.completedFuture(true) 14 | } 15 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/friendship/PluginFriendshipRequirement.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.friendship 2 | 3 | import gg.scala.commons.annotations.plugin.SoftDependency 4 | import gg.scala.flavor.service.Configure 5 | import gg.scala.flavor.service.Service 6 | import gg.scala.flavor.service.ignore.IgnoreAutoScan 7 | import gg.scala.friends.handler.KFriendHandler 8 | import java.util.* 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 1/19/2024 13 | */ 14 | @Service 15 | @IgnoreAutoScan 16 | @SoftDependency("Friends") 17 | object PluginFriendshipRequirement : FriendshipRequirement 18 | { 19 | @Configure 20 | fun configure() 21 | { 22 | Friendships.requirements = this 23 | } 24 | 25 | override fun existsBetween(playerOne: UUID, playerTwo: UUID) = KFriendHandler 26 | .getFriendshipBetween(playerOne, playerTwo) 27 | .thenApply { 28 | return@thenApply it != null 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/AbstractGame.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games 2 | 3 | import gg.tropic.practice.expectation.GameExpectation 4 | import gg.tropic.practice.games.team.GameTeam 5 | import gg.tropic.practice.games.team.GameTeamSide 6 | import gg.tropic.practice.kit.Kit 7 | import java.util.* 8 | 9 | /** 10 | * @author GrowlyX 11 | * @since 8/5/2022 12 | */ 13 | abstract class AbstractGame( 14 | val expectationModel: GameExpectation, 15 | val teams: Map, 16 | val kit: Kit, 17 | val expectation: UUID = expectationModel.identifier 18 | ) 19 | { 20 | val identifier: UUID 21 | get() = this.expectation 22 | 23 | var report: GameReport? = null 24 | var startTimestamp = -1L 25 | } 26 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/GameReference.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games 2 | 3 | import java.util.* 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 9/25/2023 8 | */ 9 | data class GameReference( 10 | val uniqueId: UUID, 11 | val mapID: String, 12 | val kitID: String, 13 | val state: GameState, 14 | val replicationID: String, 15 | val server: String, 16 | val players: List, 17 | val spectators: List, 18 | val majorityAllowsSpectators: Boolean, 19 | val queueId: String? = null 20 | ) 21 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/GameReport.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games 2 | 3 | import java.util.* 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 8/4/2022 8 | */ 9 | class GameReport( 10 | val identifier: UUID, 11 | val winners: List, 12 | val losers: List, 13 | val snapshots: Map, 14 | val duration: Long, 15 | val kit: String, 16 | val map: String, 17 | val status: GameReportStatus, 18 | val matchDate: Date = Date(), 19 | val extraInformation: Map>> = mutableMapOf() 20 | ) 21 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/GameReportSnapshot.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games 2 | 3 | import gg.tropic.practice.statistics.Counter 4 | import gg.tropic.practice.kit.Kit 5 | import org.bukkit.Material 6 | import org.bukkit.entity.Player 7 | import org.bukkit.inventory.ItemStack 8 | 9 | /** 10 | * @author GrowlyX 11 | * @since 8/9/2022 12 | */ 13 | class GameReportSnapshot(player: Player, counter: Counter, kit: Kit) 14 | { 15 | val playerUniqueId = player.uniqueId 16 | val inventoryContents: Array = player.inventory.contents 17 | 18 | val armorContents = player.inventory.armorContents 19 | val potionEffects = player.activePotionEffects 20 | 21 | val healthPotions = player.inventory.contents 22 | .filterNotNull() 23 | .count { 24 | it.type == Material.POTION && it.durability.toInt() == 16421 25 | } 26 | 27 | val containsHealthPotions = kit.contents 28 | .filterNotNull() 29 | .any { 30 | it.type == Material.POTION && it.durability.toInt() == 16421 31 | } 32 | 33 | val missedPotions = counter.valueOf("missedPots").toInt() 34 | val wastedHeals = counter.valueOf("wastedHeals") 35 | 36 | val hitPotions = counter.valueOf("hitPots").toInt() 37 | val totalPotionsUsed = counter.valueOf("totalPots").toInt() 38 | 39 | val mushroomStews = player.inventory.contents 40 | .filterNotNull() 41 | .count { 42 | it.type == Material.MUSHROOM_SOUP 43 | } 44 | 45 | val containsMushroomStews = kit.contents 46 | .filterNotNull() 47 | .any { 48 | it.type == Material.MUSHROOM_SOUP 49 | } 50 | 51 | val health = player.health 52 | val foodLevel = player.foodLevel 53 | } 54 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/GameReportStatus.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 8/5/2022 6 | */ 7 | enum class GameReportStatus 8 | { 9 | ForcefullyClosed, Completed 10 | } 11 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/GameState.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 8/4/2022 6 | */ 7 | enum class GameState 8 | { 9 | Waiting, Starting, Playing, Completed 10 | } 11 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/GameStatus.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/25/2023 6 | */ 7 | data class GameStatus( 8 | val games: List 9 | ) 10 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/GameStatusIndexes.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/25/2023 6 | */ 7 | data class GameStatusIndexes( 8 | val indexes: Map 9 | ) 10 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/duels/DuelRequest.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.duels 2 | 3 | import gg.tropic.practice.region.Region 4 | import java.util.UUID 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 10/21/2023 9 | */ 10 | data class DuelRequest( 11 | val requester: UUID, 12 | val requesterPing: Int, 13 | val requestee: UUID, 14 | val region: Region, 15 | val kitID: String, 16 | val mapID: String? = null 17 | ) 18 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/spectate/SpectateRequest.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.spectate 2 | 3 | import java.util.UUID 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 10/20/2023 8 | */ 9 | data class SpectateRequest( 10 | val player: UUID, 11 | val target: UUID, 12 | val bypassesSpectatorAllowanceChecks: Boolean = false 13 | ) 14 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/team/GameTeam.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.team 2 | 3 | import org.bukkit.Bukkit 4 | import java.util.UUID 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 8/9/2022 9 | */ 10 | class GameTeam( 11 | val side: GameTeamSide, 12 | val players: List 13 | ) 14 | { 15 | @Volatile 16 | var combinedHits = 0 17 | 18 | private var backingPlayerCombos: MutableMap? = null 19 | get() 20 | { 21 | if (field == null) 22 | { 23 | field = mutableMapOf() 24 | } 25 | return field 26 | } 27 | 28 | private var backingHighestPlayerCombos: MutableMap? = null 29 | get() 30 | { 31 | if (field == null) 32 | { 33 | field = mutableMapOf() 34 | } 35 | return field 36 | } 37 | 38 | val playerCombos: MutableMap 39 | get() = backingPlayerCombos!! 40 | 41 | val highestPlayerCombos: MutableMap 42 | get() = backingHighestPlayerCombos!! 43 | 44 | fun nonSpectators() = this.toBukkitPlayers() 45 | .filterNotNull() 46 | .filter { 47 | !it.hasMetadata("spectator") 48 | } 49 | 50 | fun toBukkitPlayers() = this.players 51 | .map { 52 | Bukkit.getPlayer(it) ?: null 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/games/team/GameTeamSide.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.games.team 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 8/9/2022 6 | */ 7 | enum class GameTeamSide 8 | { 9 | A, B 10 | } 11 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/guilds/GuildPluginGuildProvider.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.guilds 2 | 3 | import gg.scala.commons.annotations.plugin.SoftDependency 4 | import gg.scala.flavor.service.Configure 5 | import gg.scala.flavor.service.Service 6 | import gg.scala.flavor.service.ignore.IgnoreAutoScan 7 | import gg.tropic.game.extensions.guilds.GuildService 8 | import java.util.* 9 | import java.util.concurrent.CompletableFuture 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 2/3/2024 14 | */ 15 | @Service 16 | @IgnoreAutoScan 17 | @SoftDependency("CoreGameExtensions") 18 | object GuildPluginGuildProvider : GuildProvider 19 | { 20 | @Configure 21 | fun configure() 22 | { 23 | Guilds.guildProvider = this 24 | } 25 | 26 | override fun provideGuildNameFor(uniqueId: UUID) = GuildService 27 | .guildByUser(uniqueId) 28 | .thenApplyAsync { it?.name } 29 | } 30 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/guilds/GuildProvider.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.guilds 2 | 3 | import java.util.UUID 4 | import java.util.concurrent.CompletableFuture 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 2/3/2024 9 | */ 10 | interface GuildProvider 11 | { 12 | fun provideGuildNameFor(uniqueId: UUID): CompletableFuture 13 | } 14 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/guilds/Guilds.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.guilds 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 2/3/2024 6 | */ 7 | object Guilds 8 | { 9 | var guildProvider: GuildProvider = NoOpGuildProvider 10 | } 11 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/guilds/NoOpGuildProvider.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.guilds 2 | 3 | import java.util.* 4 | import java.util.concurrent.CompletableFuture 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 2/3/2024 9 | */ 10 | object NoOpGuildProvider : GuildProvider 11 | { 12 | override fun provideGuildNameFor(uniqueId: UUID): CompletableFuture = 13 | CompletableFuture.completedFuture(null) 14 | } 15 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/integration/StaffReportsService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.integration 2 | 3 | import gg.scala.commons.annotations.plugin.SoftDependency 4 | import gg.scala.flavor.service.Configure 5 | import gg.scala.flavor.service.Service 6 | import gg.scala.flavor.service.ignore.IgnoreAutoScan 7 | import gg.scala.lemon.util.QuickAccess.username 8 | import gg.scala.staff.reports.ReportProcessor 9 | import gg.scala.staff.reports.ResponseAction 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 1/16/2024 14 | */ 15 | @Service 16 | @IgnoreAutoScan 17 | @SoftDependency("ScStaff") 18 | object StaffReportsService 19 | { 20 | @Configure 21 | fun configure() 22 | { 23 | ReportProcessor.configureActionProvider { _, playerReport -> 24 | listOf( 25 | ResponseAction( 26 | description = "Check the player's region", 27 | command = "checkregion ${playerReport.to.username()}" 28 | ), 29 | ResponseAction( 30 | description = "Check the player's ranked ban status", 31 | command = "rankedbanstatus ${playerReport.to.username()}" 32 | ), 33 | ResponseAction( 34 | description = "Spectate the player's game", 35 | command = "spectate ${playerReport.to.username()}" 36 | ), 37 | ResponseAction( 38 | description = "Terminate the player's game", 39 | command = "terminatematch ${playerReport.to.username()}" 40 | ), 41 | ResponseAction( 42 | description = "View information on the player's game", 43 | command = "matchinfo ${playerReport.to.username()}" 44 | ) 45 | ) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/kit/KitContainer.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.kit 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/17/2023 6 | */ 7 | data class KitContainer( 8 | val kits: MutableMap = mutableMapOf() 9 | ) 10 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/kit/KitService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.kit 2 | 3 | import gg.scala.commons.persist.datasync.DataSyncKeys 4 | import gg.scala.commons.persist.datasync.DataSyncService 5 | import gg.scala.commons.persist.datasync.DataSyncSource 6 | import gg.scala.flavor.service.Service 7 | import gg.tropic.practice.PracticeShared 8 | import gg.tropic.practice.namespace 9 | import gg.tropic.practice.suffixWhenDev 10 | import net.kyori.adventure.key.Key 11 | 12 | /** 13 | * @author GrowlyX 14 | * @since 10/16/2022 15 | */ 16 | @Service 17 | object KitService : DataSyncService() 18 | { 19 | object KitKeys : DataSyncKeys 20 | { 21 | override fun newStore() = "mi-practice-kits" 22 | 23 | override fun store() = Key.key(namespace(), "kits") 24 | override fun sync() = Key.key(namespace().suffixWhenDev(), "ksync") 25 | } 26 | 27 | override fun locatedIn() = DataSyncSource.Mongo 28 | 29 | override fun keys() = KitKeys 30 | override fun type() = KitContainer::class.java 31 | } 32 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/kit/feature/FeatureFlag.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.kit.feature 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/17/2023 6 | */ 7 | enum class FeatureFlag( 8 | val schema: MutableMap = mutableMapOf(), 9 | val incompatibleWith: () -> Set = { emptySet() }, 10 | val requires: Set = setOf() 11 | ) 12 | { 13 | Ranked, 14 | QueueSizes( 15 | // another example: 3:Ranked,2:Casual,10:Ranked+Casual 16 | schema = mutableMapOf("sizes" to "1:Casual+Ranked") 17 | ), 18 | HeartsBelowNameTag, 19 | DoNotTakeDamage( 20 | schema = mutableMapOf("doDamageTick" to "false") 21 | ), 22 | DoNotTakeHunger, 23 | DoNotRemoveArmor, 24 | RequiresBuildMap, 25 | DeathBelowYAxis( 26 | schema = mutableMapOf( 27 | "level" to "95" 28 | ) 29 | ), 30 | BuildLimit( 31 | schema = mutableMapOf( 32 | "blocks" to "16" 33 | ) 34 | ), 35 | PlaceBlocks, 36 | BreakAllBlocks( 37 | incompatibleWith = { setOf(BreakPlacedBlocks, BreakSpecificBlockTypes) }, 38 | requires = setOf(RequiresBuildMap) 39 | ), 40 | BreakPlacedBlocks( 41 | incompatibleWith = { setOf(BreakAllBlocks) }, 42 | requires = setOf(RequiresBuildMap) 43 | ), 44 | BreakSpecificBlockTypes( 45 | incompatibleWith = { setOf(BreakAllBlocks) }, 46 | requires = setOf(RequiresBuildMap), 47 | schema = mutableMapOf( 48 | // Default for bridges LMAO 49 | "types" to "STAINED_CLAY:11,STAINDED_CLAY:14" 50 | ) 51 | ), 52 | EnderPearlCooldown( 53 | schema = mutableMapOf("duration" to "15") 54 | ), 55 | FrozenOnGameStart, 56 | NewlyCreated, 57 | MenuOrderWeight( 58 | schema = mutableMapOf("weight" to "0") 59 | ), 60 | ExpirePlacedBlocksAfterNSeconds( 61 | schema = mutableMapOf("time" to "10"), 62 | requires = setOf(PlaceBlocks) 63 | ), 64 | DeathOnLiquidInteraction, 65 | WinWhenNHitsReached( 66 | schema = mutableMapOf("hits" to "100") 67 | ), 68 | // multi-round 69 | MultiRound, 70 | ImmediateRespawnOnDeath( 71 | incompatibleWith = { setOf(StartNewRoundOnDeath) } 72 | ), 73 | StartNewRoundOnDeath( 74 | incompatibleWith = { setOf(ImmediateRespawnOnDeath) } 75 | ), 76 | RoundsRequiredToCompleteGame( 77 | schema = mutableMapOf("value" to "2") 78 | ), 79 | TimeUserSpectatesAfterDeath( 80 | schema = mutableMapOf("value" to "3") 81 | ), 82 | CountDownTimeBeforeRoundStart( 83 | schema = mutableMapOf("value" to "5") 84 | ), 85 | EntityDisguise( 86 | schema = mutableMapOf("type" to "IRON_GOLEM") 87 | ), 88 | FrozenOnRoundStart( 89 | // game start freeze is implied with round start 90 | incompatibleWith = { setOf(FrozenOnGameStart) } 91 | ), 92 | StartNewRoundOnPortalEnter( 93 | incompatibleWith = { setOf(StartNewRoundOnDeath) } 94 | ), 95 | RemovePlacedBlocksOnRoundStart 96 | } 97 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/kit/group/KitGroup.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.kit.group 2 | 3 | import gg.tropic.practice.kit.KitService 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 9/17/2023 8 | */ 9 | data class KitGroup( 10 | val id: String, 11 | val contains: MutableList = mutableListOf() 12 | ) 13 | { 14 | fun kits() = contains 15 | .mapNotNull { 16 | KitService.cached().kits[it] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/kit/group/KitGroupContainer.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.kit.group 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/17/2023 6 | */ 7 | class KitGroupContainer 8 | { 9 | companion object 10 | { 11 | const val DEFAULT = "__default__" 12 | } 13 | 14 | private val backingGroups: MutableList = 15 | mutableListOf( 16 | KitGroup(id = DEFAULT) 17 | ) 18 | 19 | val groups: List 20 | get() = backingGroups 21 | 22 | fun add(group: KitGroup) 23 | { 24 | if (group.id == DEFAULT) 25 | { 26 | throw IllegalStateException("stop") 27 | } 28 | 29 | backingGroups += group 30 | } 31 | 32 | fun remove(group: KitGroup) 33 | { 34 | if (group.id == DEFAULT) 35 | { 36 | throw IllegalStateException("stop") 37 | } 38 | 39 | backingGroups -= group 40 | } 41 | 42 | fun default() = groups.first { it.id == DEFAULT } 43 | } 44 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/kit/group/KitGroupService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.kit.group 2 | 3 | import gg.scala.commons.persist.datasync.DataSyncKeys 4 | import gg.scala.commons.persist.datasync.DataSyncService 5 | import gg.scala.commons.persist.datasync.DataSyncSource 6 | import gg.scala.flavor.service.Service 7 | import gg.tropic.practice.PracticeShared 8 | import gg.tropic.practice.kit.Kit 9 | import gg.tropic.practice.namespace 10 | import gg.tropic.practice.suffixWhenDev 11 | import net.kyori.adventure.key.Key 12 | 13 | /** 14 | * @author GrowlyX 15 | * @since 10/16/2022 16 | */ 17 | @Service 18 | object KitGroupService : DataSyncService() 19 | { 20 | object GroupKeys : DataSyncKeys 21 | { 22 | override fun newStore() = "mi-practice-kits-groups" 23 | 24 | override fun store() = Key.key(namespace(), "groups") 25 | override fun sync() = Key.key(namespace().suffixWhenDev(), "gsync") 26 | } 27 | 28 | override fun locatedIn() = DataSyncSource.Mongo 29 | 30 | override fun keys() = GroupKeys 31 | override fun type() = KitGroupContainer::class.java 32 | 33 | fun groupsOf(kit: Kit) = cached().groups 34 | .filter { 35 | kit.id in it.contains 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/leaderboards/Leaderboards.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.leaderboards 2 | 3 | import java.util.* 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 12/16/2023 8 | */ 9 | enum class ReferenceLeaderboardType( 10 | val displayName: String, 11 | val enforceRanked: Boolean = false 12 | ) 13 | { 14 | ELO("Ranked ELO", true), 15 | CasualWins("Casual Wins"), 16 | RankedWins("Ranked Wins", true), 17 | CasualWinStreak("Casual Daily Win Streak"), 18 | RankedWinStreak("Ranked Daily Win Streak", true); 19 | 20 | fun previous(): ReferenceLeaderboardType 21 | { 22 | return entries 23 | .getOrNull(ordinal - 1) 24 | ?: entries.last() 25 | } 26 | 27 | fun next(): ReferenceLeaderboardType 28 | { 29 | return entries 30 | .getOrNull(ordinal + 1) 31 | ?: entries.first() 32 | } 33 | } 34 | 35 | data class Reference( 36 | val leaderboardType: ReferenceLeaderboardType, 37 | val kitID: String? 38 | ) 39 | { 40 | fun id() = "${leaderboardType.name}:${kitID ?: "global"}" 41 | } 42 | 43 | data class LeaderboardReferences( 44 | val references: List 45 | ) 46 | 47 | data class LeaderboardEntry( 48 | val uniqueId: UUID, 49 | val value: Long 50 | ) 51 | 52 | data class Position( 53 | val uniqueId: UUID, 54 | val score: Long, 55 | val position: Long 56 | ) 57 | 58 | data class ScoreUpdates( 59 | val oldScore: Long, 60 | val oldPosition: Long, 61 | val newScore: Long, 62 | val newPosition: Long, 63 | val nextPosition: Position? 64 | ) 65 | { 66 | fun requiredScore() = nextPosition!!.score - newScore 67 | } 68 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/libraries/SharedLibraries.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.libraries 2 | 3 | import gg.scala.commons.annotations.LibraryLoader 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 9/21/2023 8 | */ 9 | @LibraryLoader 10 | object SharedLibraries 11 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/Map.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map 2 | 3 | import gg.tropic.practice.games.team.GameTeamSide 4 | import gg.tropic.practice.map.metadata.anonymous.Bounds 5 | import gg.tropic.practice.map.metadata.anonymous.toPosition 6 | import gg.tropic.practice.map.metadata.impl.MapLevelMetadata 7 | import gg.tropic.practice.map.metadata.impl.MapSpawnMetadata 8 | import gg.tropic.practice.map.metadata.impl.MapZoneMetadata 9 | import gg.tropic.practice.map.utilities.MapMetadata 10 | import net.evilblock.cubed.util.bukkit.ItemBuilder 11 | import net.evilblock.cubed.util.bukkit.Tasks 12 | import org.bukkit.Material 13 | import org.bukkit.World 14 | import org.bukkit.entity.Entity 15 | import org.bukkit.inventory.ItemStack 16 | import java.util.concurrent.CompletableFuture 17 | 18 | /** 19 | * Defines a map. Maps and available replications are decoupled, so 20 | * we don't have to worry about constantly synchronizing the [MapService] 21 | * when new replications are available or if a replication is deleted. 22 | * 23 | * @author GrowlyX 24 | * @since 9/21/2023 25 | */ 26 | data class Map( 27 | val name: String, 28 | val bounds: Bounds, 29 | val metadata: MapMetadata, 30 | var displayName: String, 31 | var displayIcon: ItemStack = ItemBuilder 32 | .of(Material.MAP) 33 | .build(), 34 | val associatedSlimeTemplate: String, 35 | val associatedKitGroups: MutableSet = 36 | mutableSetOf("__default__") 37 | ) 38 | { 39 | var locked = false 40 | 41 | fun findMapLevelRestrictions() = metadata 42 | .metadata 43 | .filterIsInstance() 44 | .firstOrNull() 45 | 46 | fun findZoneContainingEntity(entity: Entity) = metadata 47 | .metadata 48 | .filterIsInstance() 49 | .firstOrNull { 50 | it.bounds.contains(entity.location.toPosition()) 51 | } 52 | 53 | fun findSpawnLocationMatchingTeam(team: GameTeamSide) = metadata 54 | .metadata 55 | .filterIsInstance() 56 | .firstOrNull { 57 | it.id == team.name.lowercase() 58 | } 59 | ?.position 60 | } 61 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/MapContainer.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/21/2023 6 | */ 7 | data class MapContainer(val maps: MutableMap = mutableMapOf()) 8 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/MapService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map 2 | 3 | import gg.scala.commons.persist.datasync.DataSyncKeys 4 | import gg.scala.commons.persist.datasync.DataSyncService 5 | import gg.scala.commons.persist.datasync.DataSyncSource 6 | import gg.scala.flavor.service.Service 7 | import gg.tropic.practice.PracticeShared 8 | import gg.tropic.practice.namespace 9 | import gg.tropic.practice.suffixWhenDev 10 | import net.kyori.adventure.key.Key 11 | 12 | /** 13 | * @author GrowlyX 14 | * @since 9/21/2023 15 | */ 16 | @Service 17 | object MapService : DataSyncService() 18 | { 19 | object MapKeys : DataSyncKeys 20 | { 21 | override fun newStore() = "mi-practice-maps" 22 | 23 | override fun store() = Key.key(namespace(), "maps") 24 | override fun sync() = Key.key(namespace().suffixWhenDev(), "msync") 25 | } 26 | 27 | override fun locatedIn() = DataSyncSource.Mongo 28 | 29 | override fun keys() = MapKeys 30 | override fun type() = MapContainer::class.java 31 | 32 | fun maps() = cached().maps.values 33 | 34 | fun mapWithID(id: String) = cached().maps.values 35 | .firstOrNull { 36 | it.name.equals(id, true) 37 | } 38 | 39 | var onPostReload = {} 40 | override fun postReload() 41 | { 42 | onPostReload() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/AbstractMapMetadata.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata 2 | 3 | import net.evilblock.cubed.serializers.impl.AbstractTypeSerializable 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 11/10/2022 8 | */ 9 | abstract class AbstractMapMetadata : AbstractTypeSerializable 10 | { 11 | abstract val id: String 12 | } 13 | 14 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/anonymous/Bounds.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.anonymous 2 | 3 | import org.bukkit.Chunk 4 | import org.bukkit.World 5 | import org.bukkit.block.BlockState 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 9/23/2023 10 | */ 11 | data class Bounds( 12 | val lowerLeft: Position, 13 | val upperRight: Position 14 | ) 15 | { 16 | fun getTileEntities(world: World): List 17 | { 18 | return getChunks(world) 19 | .flatMap { 20 | it.tileEntities.toList() 21 | } 22 | } 23 | 24 | fun getChunks(world: World): List 25 | { 26 | val chunks = mutableListOf() 27 | val minX = lowerLeft.x.toInt() 28 | val maxX = upperRight.x.toInt() 29 | val minZ = lowerLeft.z.toInt() 30 | val maxZ = upperRight.z.toInt() 31 | 32 | for (x in (if (maxX > minX) minX..maxX else maxX..minX)) 33 | { 34 | for (z in (if (maxZ > minZ) minZ..maxZ else maxZ..minZ)) 35 | { 36 | if (!world.isChunkLoaded(x, z)) 37 | world.loadChunk(x, z, false) 38 | 39 | chunks.add( 40 | world.getChunkAt(x, z) 41 | ) 42 | } 43 | } 44 | 45 | return chunks 46 | } 47 | 48 | operator fun contains(position: Position): Boolean 49 | { 50 | return position.x >= lowerLeft.x && 51 | position.x <= upperRight.x && 52 | position.z >= lowerLeft.z && 53 | position.z <= upperRight.z 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/anonymous/BukkitExt.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.anonymous 2 | 3 | import org.bukkit.Location 4 | import org.bukkit.util.Vector 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 9/23/2023 9 | */ 10 | fun Location.toPosition() = Position( 11 | x = x, 12 | y = y, 13 | z = z, 14 | yaw = yaw, 15 | pitch = pitch 16 | ) 17 | 18 | fun Vector.toPosition() = Position( 19 | x = x, 20 | y = y, 21 | z = z, 22 | yaw = 0f, 23 | pitch = 0f 24 | ) 25 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/anonymous/Position.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.anonymous 2 | 3 | import org.bukkit.Location 4 | import org.bukkit.World 5 | import org.bukkit.util.Vector 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 9/23/2023 10 | */ 11 | data class Position( 12 | val x: Double, val y: Double, val z: Double, 13 | val yaw: Float, val pitch: Float 14 | ) 15 | { 16 | fun toVector() = Vector(x, y, z) 17 | fun toLocation(world: World) = Location(world, x, y, z, yaw, pitch) 18 | } 19 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/impl/MapLevelMetadata.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.impl 2 | 3 | import gg.tropic.practice.map.metadata.AbstractMapMetadata 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 11/10/2022 8 | */ 9 | data class MapLevelMetadata( 10 | val yAxis: Int, 11 | val below: Int = 2, 12 | val allowBuildOnBlockSideBlockFaces: Boolean = false, 13 | override val id: String = "level", 14 | ) : AbstractMapMetadata() 15 | { 16 | val range: IntRange 17 | get() = (yAxis - below)..yAxis 18 | 19 | override fun getAbstractType() = MapLevelMetadata::class.java 20 | } 21 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/impl/MapSpawnMetadata.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.impl 2 | 3 | import gg.tropic.practice.map.metadata.AbstractMapMetadata 4 | import gg.tropic.practice.map.metadata.anonymous.Position 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 11/10/2022 9 | */ 10 | data class MapSpawnMetadata( 11 | override val id: String, 12 | var position: Position 13 | ) : AbstractMapMetadata() 14 | { 15 | override fun getAbstractType() = MapSpawnMetadata::class.java 16 | } 17 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/impl/MapZoneMetadata.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.impl 2 | 3 | import gg.tropic.practice.map.metadata.AbstractMapMetadata 4 | import gg.tropic.practice.map.metadata.anonymous.Bounds 5 | import gg.tropic.practice.map.metadata.anonymous.Position 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 11/10/2022 10 | */ 11 | data class MapZoneMetadata( 12 | override val id: String, 13 | var lower: Position, 14 | var top: Position, 15 | val bounds: Bounds = Bounds(lower, top) 16 | ) : AbstractMapMetadata() 17 | { 18 | override fun getAbstractType() = MapZoneMetadata::class.java 19 | } 20 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/scanner/AbstractMapMetadataScanner.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.scanner 2 | 3 | import gg.tropic.practice.map.metadata.AbstractMapMetadata 4 | import gg.tropic.practice.map.metadata.sign.MapSignMetadataModel 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 11/10/2022 9 | */ 10 | abstract class AbstractMapMetadataScanner 11 | { 12 | abstract val type: String 13 | 14 | abstract fun scan(id: String, models: List): T 15 | } 16 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/scanner/MetadataScannerUtilities.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.scanner 2 | 3 | import gg.tropic.practice.map.metadata.scanner.impl.MapLevelMetadataScanner 4 | import gg.tropic.practice.map.metadata.scanner.impl.MapSpawnMetadataScanner 5 | import gg.tropic.practice.map.metadata.scanner.impl.MapZoneMetadataScanner 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 11/10/2022 10 | */ 11 | object MetadataScannerUtilities 12 | { 13 | private val scanners = mutableListOf>( 14 | // TODO: dynamic scan with @Service please 15 | MapZoneMetadataScanner, 16 | MapSpawnMetadataScanner, 17 | MapLevelMetadataScanner 18 | ) 19 | 20 | fun matches(type: String) = 21 | scanners.firstOrNull { 22 | it.type.equals(type, true) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/scanner/impl/MapLevelMetadataScanner.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.scanner.impl 2 | 3 | import gg.tropic.practice.map.metadata.impl.MapLevelMetadata 4 | import gg.tropic.practice.map.metadata.scanner.AbstractMapMetadataScanner 5 | import gg.tropic.practice.map.metadata.sign.MapSignMetadataModel 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 11/10/2022 10 | */ 11 | object MapLevelMetadataScanner : AbstractMapMetadataScanner() 12 | { 13 | override val type = "level" 14 | 15 | override fun scan( 16 | id: String, 17 | models: List 18 | ): MapLevelMetadata 19 | { 20 | val model = models.first() 21 | 22 | return MapLevelMetadata( 23 | model.location.blockY, 24 | model.valueOf("below")?.toInt() ?: 2, 25 | model.flags("allowBuildOnEx") 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/scanner/impl/MapSpawnMetadataScanner.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.scanner.impl 2 | 3 | import gg.tropic.practice.map.metadata.anonymous.toPosition 4 | import gg.tropic.practice.map.metadata.impl.MapSpawnMetadata 5 | import gg.tropic.practice.map.metadata.scanner.AbstractMapMetadataScanner 6 | import gg.tropic.practice.map.metadata.sign.MapSignMetadataModel 7 | import org.bukkit.block.BlockFace 8 | import org.bukkit.material.Sign 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 11/10/2022 13 | */ 14 | object MapSpawnMetadataScanner : AbstractMapMetadataScanner() 15 | { 16 | override val type = "spawn" 17 | 18 | private val manualMappings = mutableMapOf( 19 | BlockFace.NORTH to 180.0F, 20 | BlockFace.WEST to 90.0F, 21 | BlockFace.SOUTH to 0.0F, 22 | BlockFace.EAST to -90.0F, 23 | 24 | BlockFace.SOUTH_WEST to 45.0F, 25 | BlockFace.SOUTH_EAST to -45.0F, 26 | BlockFace.NORTH_WEST to 135.0F, 27 | BlockFace.NORTH_EAST to -135.0F 28 | ) 29 | 30 | override fun scan( 31 | id: String, models: List 32 | ): MapSpawnMetadata 33 | { 34 | val model = models.first() 35 | val location = model.location.clone() 36 | 37 | val sign = model.location.block.state.data as Sign 38 | location.yaw = manualMappings[sign.facing]!! 39 | 40 | location.z += 0.500F 41 | location.x += 0.500F 42 | 43 | return MapSpawnMetadata(id, location.toPosition()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/scanner/impl/MapZoneMetadataScanner.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.scanner.impl 2 | 3 | import gg.tropic.practice.map.metadata.anonymous.toPosition 4 | import gg.tropic.practice.map.metadata.impl.MapZoneMetadata 5 | import gg.tropic.practice.map.metadata.scanner.AbstractMapMetadataScanner 6 | import gg.tropic.practice.map.metadata.sign.MapSignMetadataModel 7 | 8 | /** 9 | * @author GrowlyX 10 | * @since 11/10/2022 11 | */ 12 | object MapZoneMetadataScanner : AbstractMapMetadataScanner() 13 | { 14 | override val type = "zone" 15 | 16 | override fun scan( 17 | id: String, 18 | models: List 19 | ): MapZoneMetadata 20 | { 21 | return MapZoneMetadata( 22 | id, 23 | models[0].location.toPosition(), 24 | models[1].location.toPosition() 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/sign/MapSignMetadataModel.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.sign 2 | 3 | import gg.tropic.practice.map.metadata.scanner.AbstractMapMetadataScanner 4 | import org.bukkit.Location 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 11/10/2022 9 | */ 10 | data class MapSignMetadataModel( 11 | val metaType: String, 12 | val location: Location, 13 | val id: String, 14 | val extraMetadata: List, 15 | val scanner: AbstractMapMetadataScanner<*> 16 | ) 17 | { 18 | fun flags(id: String) = extraMetadata 19 | .any { 20 | it == id 21 | } 22 | 23 | fun valueOf(id: String) = extraMetadata 24 | .firstOrNull { 25 | it.startsWith("$id=") 26 | } 27 | ?.split("=")?.first() 28 | } 29 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/metadata/sign/SignParserUtilities.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.metadata.sign 2 | 3 | import gg.tropic.practice.map.metadata.scanner.MetadataScannerUtilities 4 | import org.bukkit.Location 5 | import java.util.LinkedList 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 11/10/2022 10 | */ 11 | fun List.parseIntoMetadata(location: Location): MapSignMetadataModel? 12 | { 13 | if (size < 2) 14 | { 15 | return null 16 | } 17 | 18 | val type = first() 19 | 20 | if ( 21 | !type.startsWith("[") || 22 | !type.endsWith("]") 23 | ) 24 | { 25 | return null 26 | } 27 | 28 | val typeDelimited = type 29 | .removePrefix("[") 30 | .removeSuffix("]") 31 | 32 | val scanner = MetadataScannerUtilities 33 | .matches(typeDelimited) 34 | ?: return null 35 | 36 | val linked = LinkedList(this) 37 | linked.pop() 38 | 39 | return MapSignMetadataModel( 40 | metaType = typeDelimited, 41 | id = linked.pop(), 42 | extraMetadata = linked.toList(), 43 | scanner = scanner, 44 | location = location 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/rating/MapRating.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.rating 2 | 3 | import gg.scala.commons.annotations.Model 4 | import gg.scala.store.controller.annotations.Indexed 5 | import gg.scala.store.storage.storable.IDataStoreObject 6 | import java.util.* 7 | 8 | /** 9 | * Class created on 1/12/2024 10 | 11 | * @author 98ping 12 | * @project tropic-practice 13 | * @website https://solo.to/redis 14 | */ 15 | @Model 16 | data class MapRating( 17 | override val identifier: UUID, 18 | @Indexed val rating: Int, 19 | @Indexed val rater: UUID, 20 | @Indexed val mapID: String 21 | ) : IDataStoreObject 22 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/rating/MapRatingService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.rating 2 | 3 | import com.mongodb.client.model.Filters 4 | import gg.scala.flavor.service.Configure 5 | import gg.scala.flavor.service.Service 6 | import gg.scala.store.controller.DataStoreObjectControllerCache 7 | import gg.tropic.practice.map.Map 8 | import gg.tropic.practice.map.MapService 9 | import net.evilblock.cubed.util.bukkit.Tasks 10 | import org.bson.Document 11 | 12 | /** 13 | * Class created on 1/12/2024 14 | 15 | * @author 98ping 16 | * @project tropic-practice 17 | * @website https://solo.to/redis 18 | */ 19 | @Service 20 | object MapRatingService 21 | { 22 | val ratingMap: MutableMap = mutableMapOf() 23 | 24 | @Configure 25 | fun configure() 26 | { 27 | Tasks.asyncTimer(0L, 60 * 20L) { 28 | MapService.cached().maps.values.forEach { 29 | ratingMap[it.name] = loadAverageRating(it) 30 | } 31 | } 32 | } 33 | 34 | fun loadAverageRating(map: Map) = DataStoreObjectControllerCache 35 | .findNotNull() 36 | .mongo() 37 | .aggregate( 38 | listOf( 39 | Document( 40 | "\$match", 41 | Document( 42 | "mapID", map.name 43 | ) 44 | ), 45 | Document( 46 | "\$group", 47 | Document( 48 | mapOf( 49 | "_id" to "\$_id", 50 | "average" to Document( 51 | "\$avg", "\$rating" 52 | ) 53 | ) 54 | ) 55 | ) 56 | ) 57 | ) 58 | .first() 59 | ?.getDouble("average") 60 | ?: 0.0 61 | 62 | fun getRatingCount(map: Map) = DataStoreObjectControllerCache 63 | .findNotNull() 64 | .mongo() 65 | .loadWithFilter( 66 | Filters.eq("mapID", map.name) 67 | ) 68 | 69 | fun create(rating: MapRating) = DataStoreObjectControllerCache 70 | .findNotNull() 71 | .save(rating) 72 | } 73 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/utilities/MapMetadata.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.utilities 2 | 3 | import gg.tropic.practice.map.metadata.AbstractMapMetadata 4 | import net.evilblock.cubed.util.bukkit.Tasks 5 | import org.bukkit.Material 6 | import org.bukkit.World 7 | import org.bukkit.util.Vector 8 | 9 | /** 10 | * @author GrowlyX 11 | * @since 9/22/2023 12 | */ 13 | data class MapMetadata( 14 | val metadataSignLocations: List, 15 | val metadata: List 16 | ) 17 | { 18 | fun clearSignLocations(world: World) 19 | { 20 | val blocks = metadataSignLocations 21 | .mapNotNull { 22 | world.getBlockAt(it.blockX, it.blockY, it.blockZ) 23 | } 24 | 25 | Tasks.sync { 26 | blocks.forEach { 27 | it.type = Material.AIR 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/map/utilities/MapMetadataScanUtilities.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.map.utilities 2 | 3 | import gg.tropic.practice.map.metadata.AbstractMapMetadata 4 | import gg.tropic.practice.map.metadata.anonymous.Bounds 5 | import gg.tropic.practice.map.metadata.sign.parseIntoMetadata 6 | import org.bukkit.World 7 | import org.bukkit.block.Sign 8 | import org.bukkit.util.Vector 9 | 10 | /** 11 | * @author GrowlyX 12 | * @since 9/21/2023 13 | */ 14 | object MapMetadataScanUtilities 15 | { 16 | fun buildMetadataFor(bounds: Bounds, world: World): MapMetadata 17 | { 18 | val scheduledRemoval = mutableListOf() 19 | val blocks = bounds.getTileEntities(world) 20 | 21 | val modelMappings = blocks 22 | .filterIsInstance() 23 | .mapNotNull { 24 | val metadata = it.lines.toList() 25 | .parseIntoMetadata(it.location) 26 | 27 | metadata 28 | } 29 | .onEach { 30 | scheduledRemoval += it.location.block.location.toVector() 31 | } 32 | .groupBy { it.id } 33 | 34 | val metadata = mutableListOf() 35 | for (modelMapping in modelMappings) 36 | { 37 | val scanner = modelMapping 38 | .value.first().scanner 39 | 40 | metadata += scanner.scan( 41 | modelMapping.key, modelMapping.value 42 | ) 43 | } 44 | 45 | return MapMetadata( 46 | metadataSignLocations = scheduledRemoval, 47 | metadata = metadata 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/party/WParty.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.party 2 | 3 | import gg.scala.lemon.util.QuickAccess 4 | import gg.scala.parties.model.Party 5 | import gg.tropic.practice.lobbyGroup 6 | import gg.tropic.practice.suffixWhenDev 7 | import java.util.concurrent.CompletableFuture 8 | 9 | /** 10 | * @author GrowlyX 11 | * @since 2/9/2024 12 | */ 13 | data class WParty(var delegate: Party) 14 | { 15 | fun update(party: Party) 16 | { 17 | this.delegate = party 18 | } 19 | 20 | fun isInParty() = delegate 21 | fun onlinePracticePlayersInLobby() = CompletableFuture.supplyAsync { 22 | delegate.includedMembers() 23 | .associateWith { QuickAccess.server(it).join() } 24 | .filter { 25 | it.value?.groups 26 | ?.contains(lobbyGroup().suffixWhenDev()) == true 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/profile/PracticeProfile.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.profile 2 | 3 | import gg.scala.store.controller.DataStoreObjectControllerCache 4 | import gg.scala.store.storage.storable.IDataStoreObject 5 | import gg.tropic.practice.kit.Kit 6 | import gg.tropic.practice.profile.loadout.Loadout 7 | import gg.tropic.practice.profile.ranked.RankedBan 8 | import gg.tropic.practice.statistics.GlobalStatistics 9 | import gg.tropic.practice.statistics.KitStatistics 10 | import gg.tropic.practice.statistics.ranked.RankedKitStatistics 11 | import net.evilblock.cubed.util.time.Duration 12 | import org.bukkit.entity.Player 13 | import java.util.* 14 | import java.util.concurrent.ConcurrentHashMap 15 | 16 | /** 17 | * @author GrowlyX 18 | * @since 9/17/2023 19 | */ 20 | data class PracticeProfile( 21 | override val identifier: UUID 22 | ) : IDataStoreObject 23 | { 24 | private var rankedBan: RankedBan? = null 25 | 26 | var globalStatistics = GlobalStatistics() 27 | val casualStatistics = ConcurrentHashMap< 28 | String, 29 | KitStatistics 30 | >() 31 | 32 | val rankedStatistics = ConcurrentHashMap< 33 | String, 34 | RankedKitStatistics 35 | >() 36 | 37 | val customLoadouts = mutableMapOf< 38 | String, 39 | MutableList< 40 | Loadout 41 | > 42 | >() 43 | 44 | fun getCasualStatsFor(kit: Kit) = casualStatistics 45 | .putIfAbsent( 46 | kit.id, KitStatistics() 47 | ) 48 | ?: casualStatistics[kit.id]!! 49 | 50 | fun getRankedStatsFor(kit: Kit) = rankedStatistics 51 | .putIfAbsent( 52 | kit.id, RankedKitStatistics() 53 | ) 54 | ?: rankedStatistics[kit.id]!! 55 | 56 | fun getLoadoutsFromKit(kit: Kit) = customLoadouts[kit.id] ?: mutableListOf() 57 | 58 | fun hasActiveRankedBan() = rankedBan != null && rankedBan!!.isEffective() 59 | fun applyRankedBan(duration: Duration) 60 | { 61 | if (duration.isPermanent()) 62 | { 63 | rankedBan = RankedBan(effectiveUntil = null) 64 | return 65 | } 66 | 67 | rankedBan = RankedBan( 68 | effectiveUntil = System.currentTimeMillis() + duration.get() 69 | ) 70 | } 71 | 72 | fun deliverRankedBanMessage(player: Player) = rankedBan!!.deliverBanMessage(player) 73 | 74 | fun rankedBanEffectiveUntil() = rankedBan!!.effectiveUntil 75 | fun removeRankedBan() 76 | { 77 | rankedBan = null 78 | } 79 | 80 | fun save() = DataStoreObjectControllerCache 81 | .findNotNull() 82 | .save(this) 83 | 84 | fun saveAndPropagate() = save() 85 | .thenComposeAsync { 86 | PracticeProfileService.sendMessage( 87 | "propagate", 88 | "player" to identifier 89 | ) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/profile/loadout/Loadout.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.profile.loadout 2 | 3 | import org.bukkit.inventory.ItemStack 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 9/23/2023 8 | */ 9 | data class Loadout( 10 | var name: String, 11 | val pairedKit: String, 12 | var timestamp: Long, 13 | val inventoryContents: Array = arrayOfNulls(36) 14 | ) 15 | { 16 | override fun equals(other: Any?): Boolean 17 | { 18 | if (this === other) return true 19 | if (javaClass != other?.javaClass) return false 20 | 21 | other as Loadout 22 | 23 | if (name != other.name) return false 24 | if (pairedKit != other.pairedKit) return false 25 | if (timestamp != other.timestamp) return false 26 | if (!inventoryContents.contentEquals(other.inventoryContents)) return false 27 | 28 | return true 29 | } 30 | 31 | override fun hashCode(): Int 32 | { 33 | var result = name.hashCode() 34 | result = 31 * result + pairedKit.hashCode() 35 | result = 31 * result + timestamp.hashCode() 36 | result = 31 * result + inventoryContents.contentHashCode() 37 | return result 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/profile/ranked/RankedBan.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.profile.ranked 2 | 3 | import net.evilblock.cubed.util.CC 4 | import net.evilblock.cubed.util.time.TimeUtil 5 | import org.bukkit.entity.Player 6 | 7 | /** 8 | * @author GrowlyX 9 | * @since 1/1/2024 10 | */ 11 | data class RankedBan(var effectiveUntil: Long? = null) 12 | { 13 | fun isEffective() = effectiveUntil == null || 14 | System.currentTimeMillis() < effectiveUntil!! 15 | 16 | fun deliverBanMessage(player: Player) 17 | { 18 | player.sendMessage("${CC.RED}You currently have a ranked ban.") 19 | 20 | if (effectiveUntil != null) 21 | { 22 | player.sendMessage( 23 | CC.RED + "Effective for: " + CC.WHITE + TimeUtil.formatIntoAbbreviatedString( 24 | (effectiveUntil!! - System.currentTimeMillis()).toInt() / 1000 25 | ) 26 | ) 27 | } else 28 | { 29 | player.sendMessage(CC.RED + "This ranked-ban is effective forever.") 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/queue/MinMaxRangedNumber.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.queue 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 10/15/2023 6 | */ 7 | data class MinMaxRangedNumber( 8 | val med: Int, var diffsBy: Int 9 | ) 10 | { 11 | fun toIntRangeInclusive() = (med - diffsBy)..(med + diffsBy) 12 | } 13 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/queue/QueueEntry.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.queue 2 | 3 | import gg.tropic.practice.region.Region 4 | import java.util.UUID 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 9/24/2023 9 | */ 10 | data class QueueEntry( 11 | val leader: UUID, 12 | val leaderPing: Int, 13 | val queueRegion: Region, 14 | val maxPingDiff: Int, 15 | val leaderRangedPing: MinMaxRangedNumber = 16 | MinMaxRangedNumber( 17 | med = leaderPing, diffsBy = 10 18 | ), 19 | var lastPingRangeExpansion: Long = System.currentTimeMillis(), 20 | var lastRecordedDifferential: Int = 0, 21 | val leaderELO: Int, 22 | val leaderRangedELO: MinMaxRangedNumber = 23 | MinMaxRangedNumber( 24 | med = leaderELO, diffsBy = 10 25 | ), 26 | var lastELORangeExpansion: Long = System.currentTimeMillis(), 27 | val players: List, 28 | val joinQueueTimestamp: Long = System.currentTimeMillis(), 29 | var preferredQueueRegion: Region = queueRegion 30 | ) 31 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/queue/QueueState.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.queue 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/24/2023 6 | */ 7 | data class QueueState( 8 | val kitId: String, 9 | val queueType: QueueType, 10 | val teamSize: Int, 11 | val joined: Long 12 | ) 13 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/queue/QueueType.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.queue 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 9/21/2023 6 | */ 7 | enum class QueueType( 8 | val coinMultiplier: Double 9 | ) 10 | { 11 | Casual(1.0), Ranked(1.5) 12 | } 13 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/region/PlayerRegionFromRedisProxy.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.region 2 | 3 | import net.evilblock.cubed.ScalaCommonsSpigot 4 | import org.bukkit.entity.Player 5 | import java.util.UUID 6 | import java.util.concurrent.CompletableFuture 7 | 8 | /** 9 | * @author GrowlyX 10 | * @since 12/17/2023 11 | */ 12 | object PlayerRegionFromRedisProxy 13 | { 14 | fun of(player: Player): CompletableFuture = CompletableFuture 15 | .supplyAsync { 16 | ScalaCommonsSpigot.instance.kvConnection 17 | .sync() 18 | .hget("player:${player.uniqueId}", "instance") 19 | } 20 | .thenApply(Region::extractFrom) 21 | .exceptionally { Region.NA } 22 | 23 | fun ofPlayerID(player: UUID): CompletableFuture = CompletableFuture 24 | .supplyAsync { 25 | ScalaCommonsSpigot.instance.kvConnection 26 | .sync() 27 | .hget("player:$player", "instance") 28 | } 29 | .thenApply(Region::extractFrom) 30 | .exceptionally { Region.NA } 31 | } 32 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/region/Region.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.region 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 12/17/2023 6 | */ 7 | enum class Region 8 | { 9 | NA, EU, Both; 10 | 11 | fun withinScopeOf(region: Region) = region == this || region == Both 12 | 13 | companion object 14 | { 15 | @JvmStatic 16 | fun extractFrom(id: String) = when (true) 17 | { 18 | id.startsWith("na") -> NA 19 | id.startsWith("eu") -> EU 20 | else -> Both 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/reports/GameReportService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.reports 2 | 3 | import gg.tropic.practice.games.GameReport 4 | import gg.tropic.practice.namespace 5 | import gg.tropic.practice.suffixWhenDev 6 | import net.evilblock.cubed.ScalaCommonsSpigot 7 | import net.evilblock.cubed.serializers.Serializers 8 | import java.util.UUID 9 | import java.util.concurrent.CompletableFuture 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 1/2/2023 14 | */ 15 | object GameReportService 16 | { 17 | fun loadSnapshot(matchId: UUID): CompletableFuture 18 | { 19 | return CompletableFuture 20 | .supplyAsync { 21 | ScalaCommonsSpigot.instance.kvConnection.sync() 22 | .get("${namespace().suffixWhenDev()}:snapshots:matches:$matchId") 23 | } 24 | .thenApply { 25 | Serializers.gson.fromJson(it, GameReport::class.java) 26 | } 27 | } 28 | 29 | fun loadSnapshotsForParticipant(uniqueId: UUID): CompletableFuture> 30 | { 31 | return CompletableFuture 32 | .supplyAsync { 33 | ScalaCommonsSpigot.instance.kvConnection.sync() 34 | .keys("${namespace().suffixWhenDev()}:snapshots:players:$uniqueId:matches:*") 35 | } 36 | .thenApply { 37 | it.map { key -> 38 | // better than calling another GET 39 | key.split(":")[5] 40 | } 41 | } 42 | .thenApply { 43 | it 44 | .mapNotNull { uniqueId -> 45 | ScalaCommonsSpigot.instance.kvConnection.sync() 46 | .get("${namespace().suffixWhenDev()}:snapshots:matches:$uniqueId") 47 | } 48 | .mapNotNull { 49 | kotlin 50 | .runCatching { 51 | Serializers.gson.fromJson(it, GameReport::class.java) 52 | } 53 | .getOrNull() 54 | } 55 | } 56 | .exceptionally { 57 | it.printStackTrace() 58 | return@exceptionally listOf() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/reports/menu/SelectPlayerMenu.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.reports.menu 2 | 3 | import gg.tropic.practice.games.GameReport 4 | import gg.scala.lemon.util.QuickAccess.username 5 | import net.evilblock.cubed.menu.Button 6 | import net.evilblock.cubed.menu.Menu 7 | import net.evilblock.cubed.menu.pagination.PaginatedMenu 8 | import net.evilblock.cubed.util.CC 9 | import net.evilblock.cubed.util.bukkit.ItemBuilder 10 | import net.evilblock.cubed.util.bukkit.Tasks 11 | import org.bukkit.Material 12 | import org.bukkit.entity.Player 13 | 14 | /** 15 | * @author GrowlyX 16 | * @since 9/16/2022 17 | */ 18 | class SelectPlayerMenu( 19 | private val game: GameReport, 20 | private val gamesMenu: Menu? = null 21 | ) : PaginatedMenu() 22 | { 23 | override fun onClose(player: Player, manualClose: Boolean) 24 | { 25 | if (manualClose) 26 | { 27 | if (gamesMenu == null) 28 | { 29 | return 30 | } 31 | 32 | Tasks.sync { 33 | gamesMenu.openMenu(player) 34 | } 35 | } 36 | } 37 | 38 | override fun getAllPagesButtons(player: Player): Map 39 | { 40 | val buttons = mutableMapOf() 41 | 42 | game.snapshots 43 | .entries 44 | .sortedByDescending { it.key in game.losers } 45 | .onEach { 46 | buttons[buttons.size] = ItemBuilder 47 | .of(Material.SKULL_ITEM) 48 | .data(3) 49 | .owner(it.key.username()) 50 | .name("${CC.B_GREEN}${it.key.username()}") 51 | .addToLore( 52 | "${CC.WHITE}This player is on the:", 53 | "${CC.GREEN}${if (it.key in game.losers) "losing" else "winning"} team", 54 | "", 55 | "${CC.GREEN}Click to view inventory!" 56 | ) 57 | .toButton { _, _ -> 58 | Button.playNeutral(player) 59 | PlayerViewMenu(game, it.value, gamesMenu).openMenu(player) 60 | } 61 | } 62 | 63 | return buttons 64 | } 65 | 66 | override fun getPrePaginatedTitle(player: Player) = "Select a player..." 67 | } 68 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/reports/menu/utility/RomanNumerals.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.reports.menu.utility 2 | 3 | import java.util.* 4 | 5 | /** 6 | * https://stackoverflow.com/questions/12967896/converting-integers-to-roman-numerals-java 7 | */ 8 | object RomanNumerals 9 | { 10 | private val mappings = TreeMap() 11 | 12 | init 13 | { 14 | mappings[1000] = "M" 15 | mappings[900] = "CM" 16 | mappings[500] = "D" 17 | mappings[400] = "CD" 18 | mappings[100] = "C" 19 | mappings[90] = "XC" 20 | mappings[50] = "L" 21 | mappings[40] = "XL" 22 | mappings[10] = "X" 23 | mappings[9] = "IX" 24 | mappings[5] = "V" 25 | mappings[4] = "IV" 26 | mappings[1] = "I" 27 | } 28 | 29 | fun toRoman(number: Int): String 30 | { 31 | val key = mappings.floorKey(number) 32 | 33 | return if (number == key) 34 | { 35 | mappings[number]!! 36 | } else 37 | { 38 | mappings[key] + toRoman(number - key) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/services/ScoreboardTitleService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.services 2 | 3 | import gg.scala.commons.scoreboard.TextAnimator 4 | import gg.scala.commons.scoreboard.animations.TextFadeAnimation 5 | import gg.scala.flavor.service.Close 6 | import gg.scala.flavor.service.Configure 7 | import gg.scala.flavor.service.Service 8 | import net.evilblock.cubed.util.CC 9 | import org.bukkit.ChatColor 10 | 11 | /** 12 | * @author GrowlyX 13 | * @since 1/19/2024 14 | */ 15 | @Service 16 | object ScoreboardTitleService 17 | { 18 | private val titleAnimator = TextAnimator.of( 19 | TextFadeAnimation( 20 | "${CC.BOLD}Practice", 21 | ChatColor.AQUA, 22 | ChatColor.DARK_AQUA, 23 | ChatColor.DARK_GRAY 24 | ) 25 | ) 26 | 27 | fun getCurrentTitle() = titleAnimator.current().text 28 | 29 | @Configure 30 | fun configure() 31 | { 32 | titleAnimator.schedule() 33 | } 34 | 35 | @Close 36 | fun close() 37 | { 38 | titleAnimator.dispose() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/services/TournamentManagerService.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.services 2 | 3 | import gg.scala.aware.AwareBuilder 4 | import gg.scala.aware.codec.codecs.interpretation.AwareMessageCodec 5 | import gg.scala.aware.message.AwareMessage 6 | import gg.scala.aware.thread.AwareThreadContext 7 | import gg.scala.commons.ExtendedScalaPlugin 8 | import gg.scala.flavor.inject.Inject 9 | import gg.scala.flavor.service.Configure 10 | import gg.scala.flavor.service.Service 11 | import gg.tropic.practice.namespace 12 | import gg.tropic.practice.suffixWhenDev 13 | import gg.tropic.practice.tournaments.TournamentMemberList 14 | import me.lucko.helper.Schedulers 15 | import net.evilblock.cubed.ScalaCommonsSpigot 16 | import net.evilblock.cubed.serializers.Serializers 17 | import java.util.UUID 18 | import java.util.concurrent.CompletableFuture 19 | import java.util.logging.Logger 20 | 21 | /** 22 | * @author GrowlyX 23 | * @since 12/25/2023 24 | */ 25 | @Service 26 | object TournamentManagerService 27 | { 28 | @Inject 29 | lateinit var plugin: ExtendedScalaPlugin 30 | 31 | private val aware by lazy { 32 | AwareBuilder 33 | .of("practice:tournaments".suffixWhenDev()) 34 | .codec(AwareMessageCodec) 35 | .logger(Logger.getAnonymousLogger()) 36 | .build() 37 | } 38 | 39 | private var tournamentMembers = mutableListOf() 40 | 41 | fun isInTournament(uniqueId: UUID) = uniqueId in tournamentMembers 42 | 43 | @Configure 44 | fun configure() 45 | { 46 | aware.connect() 47 | 48 | Schedulers 49 | .async() 50 | .runRepeating({ _ -> 51 | val members = ScalaCommonsSpigot.instance 52 | .kvConnection 53 | .sync() 54 | .get("${namespace().suffixWhenDev()}:tournaments:members") 55 | 56 | tournamentMembers = if (members != null) 57 | { 58 | Serializers.gson 59 | .fromJson(members, TournamentMemberList::class.java) 60 | .members 61 | .toMutableList() 62 | } else 63 | { 64 | mutableListOf() 65 | } 66 | }, 0L, 1L) 67 | } 68 | 69 | fun publish( 70 | id: String, 71 | vararg data: Pair 72 | ) = CompletableFuture 73 | .runAsync { 74 | AwareMessage 75 | .of( 76 | packet = id, 77 | aware, 78 | *data 79 | ) 80 | .publish( 81 | AwareThreadContext.SYNC 82 | ) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/settings/ChatVisibility.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.settings 2 | 3 | import gg.scala.basics.plugin.settings.SettingValue 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 10/28/2023 9 | */ 10 | enum class ChatVisibility : SettingValue 11 | { 12 | Global, 13 | Match; 14 | 15 | override val displayName: String 16 | get() = name 17 | 18 | override fun display(player: Player) = true 19 | } 20 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/settings/SettingsExt.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.settings 2 | 3 | import gg.scala.basics.plugin.profile.BasicsProfileService 4 | import gg.scala.basics.plugin.settings.defaults.values.StateSettingValue 5 | import gg.tropic.practice.settings.scoreboard.ScoreboardStyle 6 | import org.bukkit.entity.Player 7 | 8 | /** 9 | * @author GrowlyX 10 | * @since 1/16/2024 11 | */ 12 | fun Player.isASilentSpectator(): Boolean 13 | { 14 | val basicsProfile = BasicsProfileService.find(this) 15 | ?: return false 16 | 17 | return basicsProfile 18 | .setting("${DuelsSettingCategory.DUEL_SETTING_PREFIX}:silent-spectator") == StateSettingValue.ENABLED 19 | && hasPermission("practice.silent-spectator") 20 | } 21 | 22 | fun layout(player: Player): ScoreboardStyle 23 | { 24 | return BasicsProfileService 25 | .find(player) 26 | ?.setting( 27 | "${DuelsSettingCategory.DUEL_SETTING_PREFIX}:scoreboard-style", 28 | ScoreboardStyle.Default 29 | ) 30 | ?: ScoreboardStyle.Default 31 | } 32 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/settings/restriction/RangeRestriction.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.settings.restriction 2 | 3 | import gg.scala.basics.plugin.settings.SettingValue 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 10/15/2023 9 | */ 10 | enum class RangeRestriction(private val diffsBy: Int) : SettingValue 11 | { 12 | _10(10), 13 | _50(50), 14 | _100(100), 15 | _150(150), 16 | _200(200), 17 | None(-1); 18 | 19 | override val displayName: String 20 | get() = if (this == None) name else diffsBy.toString() 21 | 22 | override fun display(player: Player) = true 23 | 24 | fun sanitizedDiffsBy() = diffsBy 25 | } 26 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/settings/scoreboard/LobbyScoreboardView.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.settings.scoreboard 2 | 3 | import gg.scala.basics.plugin.settings.SettingValue 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 10/14/2023 9 | */ 10 | enum class LobbyScoreboardView : SettingValue 11 | { 12 | Dev, Staff, None; 13 | 14 | override val displayName: String 15 | get() = name 16 | 17 | override fun display(player: Player) = if (this == None) 18 | { 19 | true 20 | } else 21 | { 22 | player 23 | .hasPermission( 24 | "practice.lobby.scoreboard.views.$name" 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/settings/scoreboard/ScoreboardStyle.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.settings.scoreboard 2 | 3 | import gg.scala.basics.plugin.settings.SettingValue 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * @author DripW 8 | * @since 2/29/2024 9 | */ 10 | enum class ScoreboardStyle : SettingValue { 11 | Default, Legacy, Disabled; 12 | 13 | override val displayName: String 14 | get() = name.lowercase().capitalize() 15 | 16 | override fun display(player: Player) = true 17 | } 18 | 19 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/tournaments/ScheduledMatchList.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.tournaments 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 12/25/2023 6 | */ 7 | data class ScheduledMatchList( 8 | val playerGroups: List>, 9 | val stray: List 10 | ) 11 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/tournaments/TournamentConfig.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.tournaments 2 | 3 | import gg.tropic.practice.region.Region 4 | import java.util.* 5 | 6 | /** 7 | * @author GrowlyX 8 | * @since 12/17/2023 9 | */ 10 | data class TournamentConfig( 11 | val creator: UUID, 12 | val teamSize: Int, 13 | val maxPlayers: Int, 14 | val kitID: String, 15 | val region: Region, 16 | val creatorBypassesCreationRequirements: Boolean 17 | ) 18 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/tournaments/TournamentMember.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.tournaments 2 | 3 | import java.util.UUID 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 12/18/2023 8 | */ 9 | data class TournamentMember( 10 | val leader: UUID, 11 | val players: Set 12 | ) 13 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/tournaments/TournamentMemberList.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.tournaments 2 | 3 | import java.util.UUID 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 12/25/2023 8 | */ 9 | data class TournamentMemberList( 10 | val members: List 11 | ) 12 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/utilities/Items.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.utilities 2 | 3 | import org.bukkit.inventory.ItemStack 4 | 5 | /** 6 | * @author GrowlyX 7 | * @since 3/2/2024 8 | */ 9 | fun Array.deepClone(): Array 10 | { 11 | val copy = arrayOfNulls(size = size) 12 | forEachIndexed { index, itemStack -> 13 | copy[index] = itemStack?.clone() 14 | } 15 | 16 | return copy 17 | } 18 | -------------------------------------------------------------------------------- /shared/src/main/kotlin/gg/tropic/practice/utilities/Ping.kt: -------------------------------------------------------------------------------- 1 | package gg.tropic.practice.utilities 2 | 3 | /** 4 | * @author GrowlyX 5 | * @since 12/24/2023 6 | */ 7 | fun formatPlayerPing(ping: Int) = when (true) 8 | { 9 | (ping > 110) -> "&c" 10 | (ping > 60) -> "&e" 11 | else -> "&a" 12 | } 13 | --------------------------------------------------------------------------------