101 Commits
Author SHA1 Message Date
NotAlexNoyle 95c40e93dc Point submodules at canonical Gitea repositories 2026-11-01 12:52:44 -05:00
NotAlexNoyle aa9fe100f5 Update canonical Gitea links 2026-10-31 14:18:18 -04:00
NotAlexNoyle 1b6bb67f4f Update canonical Gitea links 2026-10-31 11:41:05 -04:00
NotAlexNoyle 1df7c95094 Exclude CODEOWNERS from language statistics 2026-10-31 09:55:00 -04:00
NotAlexNoyle 5cc2e4ff41 Add CODEOWNERS (one staff approval) 2026-10-31 09:54:59 -04:00
NotAlexNoyle 21363e10c3 Update Chat-OG 2026-10-18 15:51:30 -04:00
NotAlexNoyle efa75a993d Use temp Chat-OG 2026-10-18 15:28:50 -04:00
NotAlexNoyle f52fb4dde2 Import dependencies as projects 2026-10-18 15:06:58 -04:00
NotAlexNoyle b2dea2cbb7 Update MyWorlds API, Utilities-OG API, DiamondBank-OG API 2026-10-17 15:19:26 -04:00
NotAlexNoyle c98c13fb1c Update dependencies 2026-10-14 13:32:50 -04:00
NotAlexNoyle 20bfd44329 Detach stale game worlds from their MyWorlds inventory group before deletion 2026-10-02 11:53:32 -04:00
NotAlexNoyle ec0d480eb8 Fix lobby crash on 1.21.2+ clients 2026-09-16 13:15:38 -04:00
NotAlexNoyle e77e1bfd80 New scoreboards 2026-09-14 18:27:21 -04:00
NotAlexNoyle b8219adb3b Improve GameModeInventories-OG guard 2026-09-14 11:13:09 -04:00
NotAlexNoyle 351b6b9b51 Fix cut off item on scoreboard 2026-09-14 10:48:58 -04:00
NotAlexNoyle 7cbceb2b5c Update Utilities-OG API 2026-09-14 02:06:20 -04:00
NotAlexNoyle 435a9589ef Fix GameModeInventories compatibility 2026-09-14 00:12:42 -04:00
NotAlexNoyle c446fd93b7 Harmonize hub commands 2026-09-12 19:06:57 -04:00
NotAlexNoyle 313be2da3c v0.9.7 2026-09-12 09:45:24 -04:00
NotAlexNoyle 629d2f008c Add MiniPlaceholders 2026-08-24 11:19:30 -04:00
NotAlexNoyle f547f5a146 Update MyWorlds API 2026-08-19 10:04:01 -04:00
NotAlexNoyle 09bb04e554 Update README.md 2026-08-19 07:47:05 -04:00
NotAlexNoyle d578cfb916 Fix bugs 2026-08-17 13:41:18 -04:00
NotAlexNoyle 009dbe137c Override Votifier /vote, fix world loading 2026-08-11 14:07:20 -04:00
NotAlexNoyle 22dfa1a737 Fix /addfloor 2026-08-09 11:55:03 -04:00
NotAlexNoyle f20802f71d Fix config 2026-08-08 14:33:47 -04:00
NotAlexNoyle 1a6a9ecefb Add Loss 2026-08-08 11:19:59 -04:00
NotAlexNoyle 1d1e142e4f Implement Chat-OG API 2026-08-05 17:08:43 -04:00
NotAlexNoyle 19ea51fea0 Improved MyWorlds support by copying TheHerobrine-OG 2026-07-31 12:32:33 -04:00
NotAlexNoyle fc03c84be3 Update README 2026-07-30 15:25:45 -04:00
NotAlexNoyle 345f836e6b Update DiamondBank-OG API 2026-07-29 11:44:38 -04:00
NotAlexNoyle c8bcb7e196 Update DiamondBank-OG API 2026-07-27 08:02:57 -04:00
NotAlexNoyle 5ac81cc514 Update DiamondBank-OG API 2026-07-22 07:10:12 -04:00
NotAlexNoyle 55e4de2f0d Update Utilities-OG API 2026-07-20 00:53:27 -04:00
NotAlexNoyle 5acf7c1652 Update GxUI-OG 2026-07-17 12:51:41 -04:00
NotAlexNoyle 550bb2be8d Update config.yml 2026-06-24 10:12:31 -04:00
NotAlexNoyle 6385fffa24 Update maps 2026-06-17 04:58:52 -04:00
NotAlexNoyle a643ec88bc Update GxUI-OG 2026-05-21 11:38:46 -04:00
NotAlexNoyle 080efbd25e Update GxUI-OG and Utilities-OG API 2026-05-20 11:20:52 -04:00
NotAlexNoyle ef9816de55 Add support for static worlds under maps/ 2026-05-20 08:41:19 -04:00
NotAlexNoyle 8db6544dad Make join signs behave like TheHerobrine-OG 2026-05-20 05:52:38 -04:00
NotAlexNoyle c80d56b88f v0.9.3 release 2026-05-20 04:49:24 -04:00
NotAlexNoyle 6eaf9e3f4f v0.9.2 update 2026-05-17 22:45:46 -04:00
NotAlexNoyle 729c0e8ed5 v0.9.1 2026-05-11 10:29:23 -04:00
NotAlexNoyle af2c7f7820 Update Utilities-OG 2026-05-11 00:55:42 -04:00
NotAlexNoyle e5768f3961 Fix maps.yml creation 2026-04-23 13:42:24 -04:00
NotAlexNoyle 78451bbf72 Add custom MyWorlds fork 2026-04-23 13:24:27 -04:00
NotAlexNoyle c07b90b919 first beta 2026-04-23 09:54:00 -04:00
NotAlexNoyle 8ecb02ac4b v0.8.9 update 2026-04-23 09:33:29 -04:00
NotAlexNoyle 9d18a32b16 v0.8.8 Release 2026-04-22 16:18:16 -04:00
NotAlexNoyle 1b12aa8c1e V0.8.7 2026-04-22 15:20:50 -04:00
NotAlexNoyle f193e62b64 Update Splegg-OG 2026-04-22 11:51:38 -04:00
NotAlexNoyle c1a1caa34d v0.8.5 release 2026-04-22 07:41:35 -04:00
NotAlexNoyle b47452dea3 v0.8 update 2026-04-21 17:24:09 -04:00
NotAlexNoyle 81d9d80298 Update GxUI-OG and Utilities-OG APIs 2026-04-20 02:15:38 -04:00
NotAlexNoyle 2ee68a045d Update DiamondBank-OG API 2026-04-07 09:33:45 -04:00
NotAlexNoyle 75c17167e4 Update Splegg-OG API 2026-04-06 00:20:29 -04:00
NotAlexNoyle 42191e2463 Update DiamondBank-OG API 2026-04-05 13:39:10 -04:00
NotAlexNoyle 3b7cbf4cb1 Update TrueOG Network APIs 2026-04-05 13:14:05 -04:00
NotAlexNoyle 3bd7dea577 Update DiamondBank-OG API 2026-04-05 11:13:41 -04:00
NotAlexNoyle e392d1aa09 Update DiamondBank-OG API 2026-04-04 14:10:40 -04:00
NotAlexNoyle 8b197c03a9 Update DiamondBank-OG API 2026-04-01 15:30:24 -04:00
NotAlexNoyle e0b6171464 Ensure MyWorlds compatibility 2026-04-01 14:51:26 -04:00
NotAlexNoyle 545113f504 Update DiamondBank-OG API 2026-04-01 05:58:29 -04:00
NotAlexNoyle 5ca6c16e25 Update DiamondBank-OG API 2026-04-01 05:03:26 -04:00
NotAlexNoyle 7d1651d02b Update DiamondBank-OG API 2026-03-30 12:28:14 -04:00
NotAlexNoyle 7c9b9db223 Use MyWorlds 2026-03-30 12:09:53 -04:00
NotAlexNoyle 646c509322 Fix teleporting to wrong world 2026-03-30 06:20:07 -04:00
NotAlexNoyle b1d92b36d9 Update Utilities-OG 2026-03-30 05:06:33 -04:00
NotAlexNoyle 5648700dd4 Update Splegg-OG 2026-03-30 00:40:48 -04:00
NotAlexNoyle bb06bb6c7b Update Utilities-OG 2026-03-25 11:57:59 -04:00
NotAlexNoyle c8947b9aa1 Don't impact main worlds 2026-03-25 06:39:26 -04:00
NotAlexNoyle 8e32d6a8a8 Update essentialsx api 2026-03-12 12:50:00 -04:00
NotAlexNoyle fe5813448a Update DiamondBank-OG 2026-03-11 00:28:06 -04:00
NotAlexNoyle e4b0ee67a1 Update GxUI-OG 2026-03-09 06:46:35 -04:00
NotAlexNoyle 71579b7353 Update GxUI-OG 2026-03-09 05:21:49 -04:00
NotAlexNoyle 05593b6b2d Update code for new DiamondBank-OG API 2026-03-02 06:38:46 -05:00
NotAlexNoyle df7422213f Add new DiamondBank-OG 2026-03-02 06:02:45 -05:00
NotAlexNoyle 104e9fd638 Remove old DiamondBank 2026-03-02 06:01:29 -05:00
NotAlexNoyle d8c52c7040 Update DiamondBank-OG API 2026-01-24 10:56:42 -05:00
NotAlexNoyle 6e58778326 Update DiamondBank-OG API 2026-01-19 13:31:12 -05:00
NotAlexNoyle f91fefa3d6 Update TrueOG libraries 2025-12-11 07:40:07 -05:00
NotAlexNoyle cf3347aead Update GxUI-OG 2025-12-07 21:02:40 -05:00
NotAlexNoyle e318023c10 Update dependencies 2025-12-07 19:10:23 -05:00
NotAlexNoyle 8bed1cb2dd Update DiamondBank-OG API 2025-11-12 00:52:12 -05:00
NotAlexNoyle 772292d0c4 Update DB-OG API 2025-09-27 17:21:13 -04:00
NotAlexNoyle 169031f1c5 Update DiamondBank-OG dependency 2025-08-27 12:30:42 -04:00
NotAlexNoyle bd1851e523 Remove bad DiamondBank-OG repo 2025-08-27 12:27:14 -04:00
NotAlexNoyle 24882ca79b Update gradle to 8.14.3, add formatting 2025-08-17 21:52:27 -04:00
NotAlexNoyle 0a18ab3c82 Update gradle to 8.14.1 2025-06-06 08:37:31 -04:00
NotAlexNoyle e878320ebd Update README.md 2024-08-09 07:56:08 +00:00
NotAlexNoyle 7ebc24fea1 1.19.4 Update 2024-08-09 07:53:38 +00:00
NotAlexNoyle 7d622b3efa Fix typo in changelog 2023-04-12 22:04:53 -04:00
NotAlexNoyle 9eb7c879b2 1.4 Update 2023-04-12 22:04:31 -04:00
NotAlexNoyle 5b5faa04b8 Sync local and remote README.md 2023-04-12 22:02:47 -04:00
NotAlexNoyle 88f5b8e064 Update README.md
Wontfix: useful for 1.8 compatibility
2023-04-11 15:38:47 -04:00
NotAlexNoyle 0c6eccdfca 1.3.1 Update 2023-04-11 15:19:32 -04:00
NotAlexNoyle 2077f3b8ef 1.3 Update 2023-04-03 02:36:50 -04:00
NotAlexNoyle 540bb74db1 1.4 Broken Prerelease 2023-03-25 15:23:05 -04:00
NotAlexNoyle fe35af4388 Fix EssentialsX API Import 2023-02-28 06:10:30 -05:00
NotAlexNoyle ac41d4edbf 1.3 Pre-Release Development Milestone 2023-02-28 05:20:31 -05:00
73 changed files with 9521 additions and 2450 deletions

No files matched your search

+1
View File
@@ -4,3 +4,4 @@
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+30 -37
View File
@@ -1,47 +1,40 @@
# Eclipse stuff
.classpath
.project
.settings/
# Compiled class file
*.class
# netbeans
nbproject
# Log file
*.log
# maven
/*/target/
target/
dependency-reduced-pom.xml
# BlueJ files
*.ctxt
# gradle
.gradle/
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# vim
.*.sw[a-p]
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# various other potential build files
/*/build/
/buildassets/map_*.txt
/*/bin/
/*/dist/
/*/manifest.mf
/release
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# Mac filesystem dust
# macOS system files
.DS_Store
# intellij
*.iml
*.ipr
*.iws
.idea/
# gradle build output files
build/
.gradle/
.checkstyle
# eclipse project files
.settings/
.project
.classpath
bin/
# Testfiles
test.sh
/bin/
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build
# Vendored compile-only APIs under libs/ are tracked on purpose.
!libs/*/*.jar
+15
View File
@@ -0,0 +1,15 @@
[submodule "libs/Utilities-OG"]
path = libs/Utilities-OG
url = https://git.trueog.net/trueog/Utilities-OG
[submodule "libs/GxUI-OG"]
path = libs/GxUI-OG
url = https://git.trueog.net/trueog/GxUI-OG
[submodule "libs/DiamondBank-OG"]
path = libs/DiamondBank-OG
url = https://git.trueog.net/trueog/DiamondBank-OG
[submodule "libs/MyWorlds"]
path = libs/MyWorlds
url = https://git.trueog.net/trueog/MyWorlds
[submodule "libs/Scoreboard-OG"]
path = libs/Scoreboard-OG
url = https://git.trueog.net/trueog/Scoreboard-OG
+261 -4
View File
@@ -1,4 +1,261 @@
**1.2:**
**0.9.9:**
- Fixed 1.21.2 and newer clients being kicked with `Network Protocol Error` on joining a lobby. The lobby inventory setup touched slot 41, one past the last player-inventory slot, and the server relayed that as a set-slot packet for container slot 46; older clients logged and ignored it, newer clients treat a bad packet as fatal. Present since the lobby items were added.
- The sidebar now follows the Scoreboard-OG network board's look: a `♥ Splegg-OG ♥` title, one blank line between labelled blocks and a `true-og.net` footer. The queue card shows the map, the players in the lobby and the countdown; the match card shows the map, the players still alive, the player's own blocks broken and the time left as `mm:ss`. Every line is at most 16 legacy characters, so 1.8 clients on ViaBackwards see it uncut, and the layout is built in code rather than the removed `Scoreboard.*` keys in `config.yml`.
- Stale game-world copies left by a reload are detached from their MyWorlds inventory group before deletion, matching the normal cleanup path. A copy still attached to its template's group can be that group's storage world, which MyWorlds refuses to delete, so without detaching first the leftover world folder would be left behind.
**0.9.8:**
- Lobbies now work like TheHerobrine-OG's. Every hub world under `Worlds.Lobby` is one persistent lobby whose id is the world name up to the dash (`SP1-hub` is `SP1`). Players join with `/splegg join SP1`, `/splegg join 1`, `/sp join 1` or `/spjoin 1`, wait and vote in the hub, and the match runs in a fresh `SP1-<map>` copy that is deleted afterwards. `/splegg join` with no argument picks the best open lobby. Joining by map name and `/splegg random` are gone.
- Map voting no longer keeps the joined map as a fixed choice; the lobby draws `Options.VotingMaps` playable maps at random. The arena is copied when the vote closes while players stay in the hub, a countdown abandoned for lack of players discards it and reopens the vote, and a template that fails to load falls through to the next map instead of blocking the start.
- Per-map match lobbies (`/splegg setlobby <map>`) were removed; `/splegg setlobby` now sets the waiting spot used in every hub and must be run inside a hub world. Any `Spawns.lobby` entry in a map file is ignored.
- Added `Options.MaxPlayers` (default 10) as the lobby size, since the map is unknown while the vote is open. Spawn points beyond it go unused and a map with fewer spawns cycles them.
- `/sp` is claimed in the server command map once every plugin has enabled, so it reaches Splegg even when another plugin (WorldGuard) registered `/sp` first. That plugin's command stays reachable through its own namespace.
- Join signs bind to a lobby id (`SP1`) or, with a blank second line, to whichever lobby is best to join. Existing signs that name a map keep working as any-lobby signs. Sign formats gained a `%lobby%` placeholder, and `%map%` shows the lobby id until a map is voted.
- `/splegg join` and `/spjoin` with no lobby list every lobby with a clickable join line, like `/hbjoin`. Join signs with a blank second line still pick the best open lobby.
- `/hub`, `/lobby` and `/spawn` are claimed inside Splegg territory before any other plugin sees them. Splegg-OG, TheHerobrine-OG and BuildBattle-OG all register `/hub`, and Bukkit hands the bare label to whichever loads first, so a Splegg player's `/hub` could run another minigame's command and teleport them out while Splegg still counted them as playing; Spawn-OG's `/spawn` did the same. `/lobby` is also an alias of `/hub` outside Splegg territory.
- `/splegg list` lists lobbies with clickable join lines; `/splegg maps` lists maps. `/splegg start` and `/splegg stop` take a lobby id instead of a map name.
- The lobby scoreboard shows `Voting...` until a map is chosen, and `Messages.IndividualLeaveGame` gained a `%lobby%` placeholder.
**0.9.7:**
- Added an in-game scoreboard: map, players alive, own blocks broken and time left, with new `Scoreboard.Map`, `Scoreboard.Alive` and `Scoreboard.TimeLeft` labels in config.yml. With Scoreboard-OG 1.2.0 or newer installed, both the queue and match sidebars are drawn through Scoreboard-OG's sidebar API, so the network board returns by itself on leave and `/togglescoreboard` is honoured; without it a plain Bukkit sidebar is used as before.
- Fixed floor detection: floor cuboids and the knockout height were read from config.yml instead of the map's own file, so eggs never vaporised blocks. Present since 0.9.2.
- Stopped logging "ready" once per map per second; the line now prints only when a map becomes playable.
- Removed `vote` and `v` from plugin.yml. The preprocess listener still claims `/vote` and `/v` inside Splegg territory, and VotingPlugin keeps both labels everywhere else.
- Return locations are now kept on disk in `prejoin-locations.yaml` with an `Options.PreJoinLocationExpiryDays` expiry, matching TheHerobrine-OG and BuildBattle-OG. A relog or a restart still returns the player to where they came from, and any route into a Splegg world records the spot.
- Dropped the Essentials-OG dependency; nothing used it.
- The shop resolves balances off the main thread before it opens and no longer pre-checks funds on click, so DiamondBank calls stop stalling the server.
- The startup purge only removes per-match copies of known templates, so an `SP2-...` world that is not in config yet survives a restart.
- Player tracking is keyed by UUID and pruned on quit.
- `/splegg` subcommands no longer print a stray usage line, map names are limited to one path segment, and sign locations parse world names that contain dots.
**0.9.6:**
- Added persistent per-player points and the Hive Java Splegg rank ladder (Farmer through Oliver, with The Eggspert reserved for the top scorer once they also hold Oliver). Points are awarded per block an egg destroys and to the match winner, tunable under the new `Points` config section, and stored in `plugins/Splegg-OG/stats.yml`.
- Registered `<sp_score>` and `<sp_rank>` MiniPlaceholders through Utilities-OG. Both read the live in-memory totals, so they update the moment a block breaks or a match is won.
- Declared `Utilities-OG` as a hard dependency; the plugin already needed it at runtime for chat and message formatting.
**0.9.5:**
- Join signs now look and behave like TheHerobrine-OG's: a `&4Splegg` title, the map name, a bold aggregate status (`JOIN`, `STARTING`, `LIVE`, `FULL`, `ENDING`, `DISABLED`), and black-on-sign player counts, redrawn once a second by a repeating task that never force-loads chunks. The flashing "Restarting" sign state was removed along with the `Sings.Restarting` config section.
- Sign clicks are now recognized by registered location instead of by the sign's rendered header, so reformatting a sign (or changing `Sings.Format.1`) can no longer orphan it, and all sign types are matched through block tags.
- Leaving a Splegg lobby or game now hands the player back to the server's main scoreboard instead of a fresh empty one, so Scoreboard-OG can restore its sidebar after the return teleport.
- Splegg chat now renders the standard TrueOG name segment (union bracket tag, display name, LuckPerms suffix) like TheHerobrine-OG, with a caret colored by the sender's Chat-OG message color, while keeping the lobby queue count and in-game broken-block counters.
- Fixed a crash in the legacy color serializer when a string ends with a trailing `&`.
**0.9.4:**
- Splegg now loads its configured lobby and in-game worlds itself instead of asking an admin to run `/mw load` first, and per-match world copies fall back to loading their template on demand.
- World provisioning happens on the first tick after startup rather than during plugin enable, so loading a world no longer fires chunk events at plugins that are still starting.
- Copied and refreshed worlds keep a void chunk generator (`Worlds.VoidGenerator`, on by default), because a directory copy carries no MyWorlds generator setting and would otherwise grow vanilla terrain outside the arena.
- World and MapBase template folders now match case-insensitively and ignoring `-`/`_`, and a missing template is reported with the list of folders that do exist.
- MapBase refresh covers `Worlds.Lobby` as well as `Worlds.InGame`.
- Splegg worlds are detached from their previous MyWorlds inventory bundle before being grouped, so a Splegg world can no longer share the SMP inventory.
- MyWorlds is also recognized under its `My_Worlds` fork name.
- Stale per-game worlds are now unloaded and dropped through MyWorlds before falling back to deleting the directory, and the purge reports how many it removed.
- Downgraded the BKCommonLib compile dependency to the locally built 1.19.4-v2 jar, matching the MyWorlds build Splegg runs against.
**0.9.3:**
- Added `/hub` so players can leave an active Splegg game or return to a protected main-world spawn.
- Added TheHerobrine-OG-style in-lobby map voting with `/vote`, `/v`, clickable vote messages, vote reminders, configurable vote choices, and configurable voting cutoff.
- Players who reconnect inside configured Splegg worlds or per-game world copies are now moved back to the overworld.
- Updated the default lobby sign layout to show header, map, status, and player count.
- Stopped forcing health and fall-distance resets during final game-leave cleanup.
**0.9.2:**
- Players on the same map in different game instances no longer share an arena, scoreboard, or block state.
- Games are now created on demand when a player joins a lobby, instead of being statically generated at startup.
- Replaced block-level floor rollback with whole-world recycling.
- Added join signs.
- Added `PlayerKickEvent` handling so kicked players are removed from their match after its conclusion.
- Added `WorldUnloadEvent` handling so any game whose template or per-game world is unloaded out from under it is shut down cleanly.
- Refused to copy, load, or purge any world whose name matches a protected main world.
**0.9.1:**
- Improve MyWorlds support
- Blacklist main 3 worlds
**0.9.0:**
- Leaving Splegg now returns players to the exact main-SMP location they were at before joining, preserving overworld/nether/end context while still crossing back out of Splegg inventory isolation.
**0.8.9:**
- Re-keyed all per-player runtime state (match roster, shovel selections, money tracking, egg-launch flags) from player names to UUIDs so name changes, duplicate names, and offline-player lookups no longer corrupt match state.
- Fixed a NullPointerException in the egg-landing handler that could fire when the ray trace produced no candidate block.
- Fixed the lobby-entry and match-exit flows that reset `FallDistance` to 1 instead of 0, leaving players with a one-block fall penalty after Splegg transitions.
- Replaced the try/catch NPE swallow in the plugin shutdown path with a direct null-check so unrelated bugs no longer silently print stack traces.
- Removed the noisy try/catch around `PlayerInteractEvent.getHand()` and the misleading `HandlerList.unregisterAll(new X())` no-ops from `onDisable`.
- Removed the legacy `My_Worlds` softdepend and plugin-id fallback; `MyWorlds` is the only recognized world-manager plugin name.
**0.8.8:**
- Tightened playable-map validation so a map now requires at least 2 spawn points, at least 1 floor, and valid loaded world references before it can enter a playable lobby state.
- Fixed join flow so players can now join Splegg lobbies from outside Splegg worlds, including via `/splegg join`, `/splegg random`, and registered join signs.
- Fixed lobby countdown recovery so a failed countdown now returns to a waiting state instead of immediately restarting itself.
- Fixed kill reward payouts so eliminations only reward surviving players in the same match instead of players from other matches.
- Reworked the Splegg shop onto the local GxUI-OG GUI layer and added live affordability feedback: affordable shovels glow, unaffordable ones do not, and shop lore now reflects the current affordability state.
**0.8.7:**
- Fixed the in-game round timer so matches now count down correctly and end on time limit instead of stopping immediately.
- Added proper last-player-standing match completion, including server-wide winner chat, loser messaging, and winner payout handling.
- Added a winner title that is shown only to the winning player, while the winner chat announcement remains server-wide.
- Added broken-block tracking to match exits so players now see their block total when they leave a round.
- Fixed leave messaging to name the player who actually left and cleaned up shovel-selection state more completely on exit.
**0.8.6:**
- Added `/splegg random` to join a random playable map and a `getRandomMap()` utility that lays the groundwork for in-lobby map voting.
- Added `/splegg help`, `/splegg list`, and `/splegg info <map>` to surface configured maps, their status, and setup next-steps.
- Added tab completion for every `/splegg` subcommand and map-name argument.
- Added a per-player lobby scoreboard driven by the existing `Scoreboard.*` config keys, refreshed on join, leave, and each countdown tick.
- Fixed lobby signs rendering raw `&` color codes by serializing line content through the Adventure legacy serializer.
- Fixed right-click-to-join and sign-break handling, which previously compared Adventure Component `toString()` output to raw config strings and never matched.
- Added a distinct `[Starting]` status for lobby signs during the start countdown (`Sings.Status.Starting`) so operators can see at a glance that a match is about to begin.
- Improved the sign player count line with color accents.
**0.8.5:**
- Fixed plugin startup with players already online by initializing tracked player storage before repopulating it.
- Stopped using Essentials kits to save and restore Splegg inventories, so MyWorlds now remains the only inventory boundary between main and Splegg worlds.
- Fixed in-match protection listeners to use the tracked `UtilPlayer` state instead of creating fresh session objects.
- Fixed the knockout floor height calculation so eliminations happen below the actual arena floor.
- Fixed admin `/splegg start <map>` and `/splegg stop <map>` targeting so they operate on the specified game instead of the command sender's current game.
- Tightened Splegg chat isolation so joined players only talk to players in the same match, while non-participants in Splegg worlds stay scoped to their current world.
**0.8:**
- Added permission metadata for `splegg.join`, `splegg.joinfull`, and `splegg.admin`.
- Expanded the shop to include every shovel available in Minecraft 1.19.4.
- Added support for selecting wood, stone, iron, gold, diamond, and netherite shovels before a match.
- Updated the README and plugin dependency metadata to reflect the current DiamondBank-OG, Essentials-OG, and MyWorlds requirements.
- Fixed the incorrect startup error message shown when Essentials-OG is missing.
**0.7:**
- 1.19.4 Support.
**0.6:**
- The splegg shop is fixed.
- The winner is now removed from the game.
- The floor now resets correctly after a match.
**0.5:**
- Fixed dust particles coming off of egg instead of the block that was hit.
- Fixed match not ending when players get below the minimum level.
**0.4:**
- It is no longer possible to start a match with only 1 player in the lobby.
- Match startup is fixed.
- The formatting of the shop items is fixed.
- Right clicking items in the lobby inventory is fixed.
- The shovel is now given only after the grace period is over.
- Players pre-game inventory is now saved and restored using EssentialsX kits.
- Inventory saving now still works upon leaving the server during a match.
- The game leave message now displays slightly differently to the individual who left.
- The game leave message is now only displayed to players that are in the game.
- The game leave message now displays when someone quits the server completely.
- Leaving the server in the lobby now properly removes players from the game.
- Restored lobby inventory layout to be like it was on Hive Java.
- The lobby items now properly go away when a game starts.
**0.3**
- Fixed the shovel being given in the lobby.
@@ -14,7 +271,7 @@
- Fixed a few NullPointerException errors.
**1.1:**
**0.2**
- In-game players can no longer be hit with eggs.
@@ -34,6 +291,6 @@
- Fixed an infinite loop upon a player dying.
**1.0:**
**0.1**
- Initial Release of Splegg-OG.
- Initial Release of Splegg-OG.
+180 -34
View File
@@ -1,65 +1,211 @@
# Splegg-OG
# Splegg-OG 0.9.9
![Icon](https://raw.githubusercontent.com/NotAlexNoyle/Splegg-OG/master/assets/splegg-logo.png)
![Icon](https://git.trueog.net/trueog/Splegg-OG/raw/branch/main/assets/splegg-logo.png)
**Splegg-OG** is a Splegg plugin originally made by MrLuangamer, updated for Worldedit 7.2 and Spigot 1.16.4 by Hraponssi, then updated for Purpur 1.18.2 for use by [TrueOG](https://true-og.net/) by [NotAlexNoyle](https://github.com/NotAlexNoyle/).
**Splegg-OG** is a Splegg plugin originally made by MrLuangamer, updated for Worldedit 7.2 and Spigot 1.16.4 by Hraponssi, then updated for Purpur 1.19.4 for use at [TrueOG Network](https://true-og.net/) by [NotAlexNoyle](https://github.com/NotAlexNoyle/).
Dependencies: WorldEdit, Vault.
Runtime dependencies: WorldEdit, DiamondBank-OG, Utilities-OG, and MyWorlds. Chat-OG and Scoreboard-OG are optional and integrated when present.
**To Set Up:**:
Current feature state:
`/splegg create my-map` First, create a Splegg map with a name of your choosing.
- Lobbies work like TheHerobrine-OG's. Each hub world listed under `Worlds.Lobby` is one lobby: `SP1-hub` is lobby `SP1`, joined with `/splegg join SP1` or `/splegg join 1`. Players wait and vote for the map in the hub; when the vote ends the winning map's template is copied to `SP1-<map>` and the match runs there. When it ends the copy is deleted and the lobby goes back to waiting. Add `SP2-hub` for a second concurrent lobby.
- Matches run in per-lobby MyWorlds copies of the map's template world, so two lobbies can play the same map without sharing arena blocks, scoreboards, or player state. The template copied is the world the map's **terrain** is in, taken from its spawns and then its floors.
- Splegg creates isolated MyWorlds inventory groups for configured lobby and in-game worlds. It does not manage the vanilla main-world inventory group.
- Players join a lobby with `/splegg join <lobby>` (`/sp join 1`, `/spjoin 1`), or through registered join signs, then vote for the map while waiting in the hub. `/splegg join` on its own lists every lobby with a clickable join line, like `/hbjoin`.
- Players can leave with `/splegg leave`, `/hub`, `/lobby`, `/spawn`, or the lobby slimeball. Inside Splegg territory all three commands are claimed by Splegg before any other plugin sees them, so another minigame's `/hub` or Spawn-OG's `/spawn` can never teleport a player out while Splegg still counts them as playing.
- Players who leave, relog, or reconnect inside a Splegg world are returned to where they were before they entered it. Return locations are kept on disk in `plugins/Splegg-OG/prejoin-locations.yaml` and expire after `Options.PreJoinLocationExpiryDays` days; the protected main-world spawn is the fallback.
`/splegg setspawn my-map` The amount of spawn points you set is the amount of players that will be able to join the map.
**Protected vanilla worlds:** Splegg will *never* read, write, or configure the vanilla overworld dimensions (`world`, `world_nether`, `world_the_end`). This guard is hard-coded -- listing those names under `Worlds.Lobby` or `Worlds.InGame` in `config.yml` is rejected with startup warnings, and `/splegg create`, `/splegg setspawn`, `/splegg setlobby`, and `/splegg addfloor` all refuse to run while you are standing in one of them. Run Splegg only inside dedicated worlds that you create yourself.
`/splegg setspawn my-map next` You can define new spawn points for a map with or without the "next" keyword.
## Step-by-Step Setup Guide
`/splegg setspawn my-map 3` At any time, you can modify existing spawn points by using the number of the order in which you created them.
Follow these steps in order to get your first map running.
`/splegg setlobby my-map` Set a lobby area for the map during the voting/warm-up period.
### 1. Choose Your Map Source
Decide how you want to create your arena:
- **Option 1A (Manual):** Create a fresh void world and build your map from scratch.
- **Option 1B (Import):** Use a pre-built world folder (e.g., from PlanetMinecraft).
`//wand` Summon a wand with WorldEdit and then use it to select two points. These represent the corners of your floor.
#### 1A. Create Base Worlds (Manual Path)
Splegg requires dedicated worlds for lobby hubs and maps.
1. Install [MyWorlds](https://git.trueog.net/trueog/MyWorlds).
2. Create a hub world for lobby 1: `/mw create SP1-hub void`. The name must be `<GamePrefix><number>-<name>`; the number is the lobby id.
3. Create an in-game world: `/mw create splegg_ingame void`
4. Load them: `/mw load SP1-hub` and `/mw load splegg_ingame`
`/splegg addfloor my-map` Add the area you just selected with WorldEdit as a floor. You can add as many as you like.
#### 1B. Import Pre-built Maps (Import Path)
If you have a downloaded world folder:
1. In `config.yml`, set `Worlds.MapBase` to `maps`.
2. Drop your world folder (e.g., `MyAwesomeMap`) into the `/maps/` directory at your server root, and a hub world folder named `SP1-hub`.
3. Add `MyAwesomeMap` to the `Worlds.InGame` list and `SP1-hub` to the `Worlds.Lobby` list in `config.yml`.
4. Restart the server. Splegg copies both into place on every boot.
`/splegg join my-map` You can now join the map you just created, and so can anyone else with splegg.join permission.
### 2. Configure the Plugin
1. Open `plugins/Splegg-OG/config.yml`.
2. Ensure `SP1-hub` is under `Worlds.Lobby` and your in-game world (e.g., `splegg_ingame` or `MyAwesomeMap`) is under `Worlds.InGame`. Every `Worlds.Lobby` entry becomes one lobby; one that is not named `<GamePrefix><number>-<name>` is skipped with a startup warning.
3. Restart your server or run `/reload confirm`.
`/splegg start` Anyone with splegg.admin permissions can start the game even if less players join than there are spawn points.
### 3. Initialize the Splegg Map
Teleport to your in-game world (`/mw tp <world_name>`) and run:
- `/splegg create <map-name>` (e.g., `/splegg create my-first-map`)
### 4. Define the Arena
While standing in the map:
1. **Set Spawns:** Run `/splegg setspawn <map-name>` at every player starting position. You need at least two. Use `next` or a number to modify them.
2. **Define Floors:**
- Use the WorldEdit wand (`//wand`) to select two parallel corners of a floor.
- Run `/splegg addfloor <map-name>`. Repeat for as many floors as needed.
### 5. Set the Waiting Spot (optional)
Stand in `SP1-hub` where players should wait and run `/splegg setlobby`. The coordinates are reused in every lobby hub, so all hubs should be copies of the same template. Without it players land at the hub world's spawn.
### 6. Test
Run `/splegg join 1` (or `/sp join SP1`; `/splegg join` alone lists the lobbies). You are put in the hub of lobby `SP1` and asked to vote. Once enough players are waiting (`Options.AutoStartPlayers`) the countdown runs, the vote closes at `Options.EndVotingAt` seconds, and the match starts in a fresh copy of the winning map. Use `/spforcestart` to skip the player minimum while testing.
## Join Signs
Admins can place physical signs that players right-click to join a lobby. A repeating task redraws every sign each second with the lobby, its voted map once there is one, live status, and player counts, using the `Sings.Format` and `Sings.Status` templates in `config.yml`. Placeholders are `%lobby%`, `%map%` (the lobby id while the vote is open), `%status%`, `%count%` and `%maxcount%`.
To set one up:
1. Place a sign in a persistent world such as the server hub — never inside a per-match world copy, which is deleted after each game.
2. Write `[Splegg]` on the first line and a lobby id (`SP1` or `1`) on the second line, or leave it blank for "whichever lobby is best to join". Requires the `splegg.admin` permission.
3. The sign reformats itself immediately; right-clicking it joins that lobby.
Signs from older versions that name a map keep working as "any lobby" signs.
Signs are registered by location, not by their text, so reformatting cannot orphan them. Breaking a registered sign requires `splegg.admin` and unregisters it; everyone else is blocked from breaking it.
## Points, Ranks & Placeholders
Every block an egg destroys and every match win awards points (`Points.BlockBroken`
and `Points.Win` in `config.yml`). Totals persist in `plugins/Splegg-OG/stats.yml`
and map onto the Hive Java Splegg rank ladder:
| Rank | Points |
|------|--------|
| Farmer | 0 |
| Cook | 5,000 |
| Chef | 10,000 |
| Master | 100,000 |
| Humpty Dumpty | 250,000 |
| Ramsay | 500,000 |
| Hot'n'Spicy | 1,000,000 |
| Splegg Head | 1,200,000 |
| Bacon | 1,500,000 |
| Sunnyside | 2,000,000 |
| Delicious | 3,000,000 |
| Easter Bunny | 4,000,000 |
| Scrambled | 5,000,000 |
| Oliver | 7,500,000 |
| The Eggspert | Top player, once they also reach Oliver |
Registered through Utilities-OG as MiniPlaceholders, resolved for the viewing
player, and updated the moment the underlying value changes:
| Placeholder | Value |
|-------------|-------|
| `<sp_score>` | Total points |
| `<sp_rank>` | Colored rank name for that score |
## 🛠 Command Reference
### Management & Setup
- `/splegg create <map>` - Initialize a new map.
- `/splegg setspawn <map> [next|#]` - Define/modify player start points.
- `/splegg setlobby` - Set where players wait in every lobby hub. Run inside a hub world.
- `/splegg addfloor <map>` - Add selected WorldEdit region as a floor.
- `/splegg info <map>` - Check setup status (spawns, floors, etc.).
### Gameplay
- `/splegg join [lobby]` - Join a lobby by id (`SP1`, or just `1`). With no argument, lists the lobbies with clickable join lines. `/spjoin [lobby]` is the same command.
- `/splegg list` - List every lobby with a clickable join line.
- `/splegg maps` - List every map and which lobby is on it.
- `/vote [#]` or `/v [#]` - View or cast votes for the current lobby's maps.
- `/splegg leave` - Exit a lobby or match.
- `/hub`, `/lobby`, `/spawn` - Leave the lobby or match and return to where you came from, or to the main world spawn.
### Administration
- `/spforcestart [time]` - Start the match ignoring `Options.AutoStartPlayers`.
- `/splegg start [lobby]` - Start a lobby's match early. Defaults to your own lobby.
- `/splegg stop [lobby]` - End a lobby's match immediately.
- `/splegg help` - Full command reference.
*Alias: `/sp` can be used instead of `/splegg`. Splegg claims the bare `/sp` label once every plugin has enabled, so it wins over any other plugin (such as WorldGuard) that registers `/sp` first; that plugin's command stays reachable through its own namespace, for example `/worldguard:sp`.*
**Map Voting:**
When the first player joins a lobby, it picks up to `Options.VotingMaps` playable maps at random. Players get the same chat voting flow as TheHerobrine-OG: `/v #`, clickable vote lines, one active vote per player, vote changes that move the count, and reminder messages every `Options.VotingReminder` seconds.
When the start countdown reaches `Options.EndVotingAt`, voting ends and the highest-voted map wins. Splegg copies that map's template to `<GamePrefix><lobby>-<map>` while everyone keeps waiting in the hub, then teleports them onto the map's spawns when the countdown hits zero. If the countdown is abandoned for lack of players, the copy is thrown away and a fresh vote opens. A lobby holds `Options.MaxPlayers` players; extra spawn points go unused and a map with fewer spawns cycles them.
`/v` and `/vote` are claimed inside Splegg territory before any other plugin sees them, so VotingPlugin cannot take the `/vote` label away from map voting. The claim covers a player who is in a Splegg game, and anyone standing in a configured lobby or in-game world or a per-match copy. Everywhere else `/vote` behaves normally: neither label is declared in plugin.yml, so Splegg never competes for them.
**Force Starting:**
`/spforcestart [time]` restarts the lobby countdown ignoring `Options.AutoStartPlayers`, so a match can begin below the configured minimum. The countdown floor is `Options.EndVotingAt` so map voting still resolves before the match starts, and `[time]` raises it. Requires `splegg.admin`.
**Chat:**
When Chat-OG is installed, Splegg registers a chat formatter for its `Worlds.GamePrefix` key (`SP` by default), which covers both the configured lobby worlds and every per-match copy. Lobby chat shows the player count, in-game chat shows blocks broken, and eliminated players are greyed out. Chat-OG's `discord.games` must contain a matching `SP` key for Splegg worlds to get their own chat; without it they stay in global chat and the formatter never runs.
Chat-OG routes worlds named `<letters><number>-<name>`, which is also the shape every lobby hub must have, so `SP1-hub` and the match copy `SP1-<map>` land in the same lobby chat. `Worlds.InGame` entries are match templates, nobody queues or plays in one, and each per-game copy routes whatever the template is called.
**Scoreboard:**
Queued players see a sidebar with the map, the players in the lobby and the countdown; during the match it shows the map, the players still alive, the player's own blocks broken and the time left. It uses the Scoreboard-OG network board's look (`♥ Splegg-OG ♥` title, blank-separated blocks, `true-og.net` footer), is built in code, and keeps every line within 16 legacy characters so 1.8 clients see it whole. With Scoreboard-OG `1.2.0` or newer installed, the sidebar is drawn through Scoreboard-OG's sidebar API, so the network board returns on its own when the player leaves and `/togglescoreboard` is honoured. Without it, a plain Bukkit sidebar is used and the main scoreboard is restored on leave.
**Lobbies:**
A lobby is a persistent object created at startup for every hub world under `Worlds.Lobby`. Its id is the world name up to the dash: `SP1-hub` is `SP1`. A lobby is either waiting in its hub, starting (countdown running), live (match running in `SP1-<map>`), or ending. Live lobbies cannot be joined. When the match ends the arena copy is deleted and the same lobby id is ready again, so `/splegg join 1` always means the same place.
If you are upgrading from 0.9.7 or older: per-map match lobbies (`/splegg setlobby <map>`) are gone and any `Spawns.lobby` entry in a map file is ignored. Rename your lobby world to `SP1-hub` if it is not already, and set the waiting spot with `/splegg setlobby` inside it.
**Lobby Signs:**
Admins can create a join sign by placing a sign with these lines:
`[splegg]`
`SP1`
The second line is a lobby id, or blank for the best open lobby. Splegg rewrites the sign using `Sings.Format` from `config.yml`. The default layout is header, lobby (or map once voted), status, then player count. Sign clicks resolve the lobby from the saved sign location in `maps.yml`, not from the visible line text, so operators can change the displayed format without breaking joins. Admins can break registered Splegg signs to remove them; non-admin players cannot break them.
**Permissions:**
- `splegg.join` - Join Splegg lobbies with `/splegg join`, `/spjoin`, or lobby signs.
- `splegg.joinfull` - Join a full lobby.
- `splegg.admin` - Manage maps, signs, and matches. Includes the join permissions.
- `splegg.use` - Metadata permission that includes `splegg.join`.
[Original Bukkit Page](https://dev.bukkit.org/projects/splegg-minigame).
*The current Gradle build target is Purpur 1.18.2*
*The current Gradle build target is Purpur 1.19.4 on GraalVM CE 17 / Java 17.*
**To Build:**
`./bootstrap.sh`
`export JAVA_HOME=/path/to/java-17`
`./gradlew build`
Gradle must run on Java 17 or newer, and the git submodules under `libs/` must be initialized before the build.
The resulting .jar file will be in build/libs/
**Known Bugs:**
- Floors below 0 y-level do not function.
- Slimeball does not work and is not labeled.
- The game does not end when 1 player is remaining.
- It is possible to start a game with only 1 player.
- Pre-game inventory is not preserved.
**Planned Features:**
- Better lobby inventory
- Database to remember shop purchases across sessions (currently in-memory only).
- Better splegg shop
- Functional lobby menu items: wire up the Guide book and Cosmetics menu. The shop item and leave slimeball are functional.
- Diamond shard integration with DiamondBank-OG
- Power ups.
- Multiverse Integration
- Spectator Mode (partial TODOs exist for spectator compass, spectator inventory, and join-in-progress).
- Spectator Mode
- Leaderboards.
- Leaderboards
- PlaceholderAPI support.
**Licensed under the GPLv3.**
**Licensed under the GPLv3.**
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/bash
# Fetch all submodule content.
git submodule update --force --recursive --init
-93
View File
@@ -1,93 +0,0 @@
plugins {
id "java" // Tell gradle this is a java project.
id "com.github.johnrengelman.shadow" version "7.1.2" // Import utility to package libraries into .jar file.
}
group = "net.trueog.splegg-og"
version = "1.2"
def apiVersion = "1.18.2" // Declare minecraft server target version.
def targetJavaVersion = 17 // Declare java compatibility minimum.
apply plugin: "eclipse" // Enable integration with Eclipse IDE for development.
processResources {
def props = [
version: version,
apiVersion: apiVersion,
]
inputs.properties props // Indicates to rerun if version changes.
filesMatching("plugin.yml") {
expand props
}
}
repositories {
mavenCentral()
maven {
url "https://repo.purpurmc.org/snapshots" // Get purpur API from purpur maven repository.
}
maven {
url "https://jitpack.io" // Get Vault API from jitpack repository.
}
maven {
url "https://maven.enginehub.org/repo" // Get WorldEdit API from enginehub repository.
}
}
dependencies {
compileOnly "org.purpurmc.purpur:purpur-api:1.18.2-R0.1-SNAPSHOT" // Declare purpur API version to be packaged.
compileOnly "com.github.MilkBowl:VaultAPI:1.7" // Declare Vault API to be packaged.
compileOnly "com.sk89q.worldedit:worldedit-bukkit:7.3.0-SNAPSHOT" // Declare WorldEdit API to be packaged.
}
shadowJar {
minimize()
}
jar.dependsOn shadowJar
tasks.jar.configure {
classifier = "part"
}
tasks.shadowJar.configure {
classifier = null
}
compileJava {
options.compilerArgs += ["-parameters"]
options.fork = true
options.forkOptions.executable = 'javac'
}
+143
View File
@@ -0,0 +1,143 @@
/* ------------------------------ Plugins ------------------------------ */
plugins {
id("java") // Import Java plugin.
id("java-library") // Import Java Library plugin.
id("com.diffplug.spotless") version "8.1.0" // Import Spotless plugin.
id("com.gradleup.shadow") version "8.3.9" // Import Shadow plugin.
id("checkstyle") // Import Checkstyle plugin.
eclipse // Import Eclipse plugin.
kotlin("jvm") version "2.1.21" // Import Kotlin JVM plugin.
}
extra["kotlinAttribute"] = Attribute.of("kotlin-tag", Boolean::class.javaObjectType)
val kotlinAttribute: Attribute<Boolean> by rootProject.extra
/* --------------------------- JDK / Kotlin ---------------------------- */
java {
sourceCompatibility = JavaVersion.VERSION_17 // Compile with JDK 17 compatibility.
toolchain { // Select Java toolchain.
languageVersion.set(JavaLanguageVersion.of(17)) // Use JDK 17.
vendor.set(JvmVendorSpec.GRAAL_VM) // Use GraalVM CE.
}
}
kotlin { jvmToolchain(17) }
/* ----------------------------- Metadata ------------------------------ */
group = "net.trueog.splegg-og" // Declare bundle identifier.
version = "0.9.9" // Declare plugin version (will be in .jar).
val apiVersion = "1.19" // Declare minecraft server target version.
/* ----------------------------- Resources ----------------------------- */
tasks.named<ProcessResources>("processResources") {
val props = mapOf("version" to version, "apiVersion" to apiVersion)
inputs.properties(props) // Indicates to rerun if version changes.
filesMatching("plugin.yml") { expand(props) }
from("LICENSE") { into("/") } // Bundle licenses into jarfiles.
}
/* ---------------------------- Repos ---------------------------------- */
repositories {
mavenCentral() // Import the Maven Central Maven Repository.
maven("https://ci.mg-dev.eu/plugin/repository/everything/") // BKCommonLib.
gradlePluginPortal() // Import the Gradle Plugin Portal Maven Repository.
maven { url = uri("https://repo.purpurmc.org/snapshots") } // Import the PurpurMC Maven Repository.
maven { url = uri("https://jitpack.io") } // Import the Jitpack Maven Repository.
maven { url = uri("https://papermc.io/repo/repository/maven-public/") } // Import the Paper Maven Repository.
maven { url = uri("https://maven.enginehub.org/repo") } // Import the WorldEdit Maven Repository.
}
/* ---------------------- Java project deps ---------------------------- */
dependencies {
compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare Purpur API version to be packaged.
compileOnly("io.github.miniplaceholders:miniplaceholders-api:2.2.3") // Import MiniPlaceholders API.
compileOnly(
"com.sk89q.worldedit:worldedit-bukkit:7.2.15"
) // Import WorldEdit API (7.2.15 matches the server's WorldEdit build).
compileOnly(project(":libs:MyWorlds")) // Import MyWorlds API (git submodule, built from source).
compileOnly(
"com.bergerkiller.bukkit:BKCommonLib:1.19.4-v2"
) // Import BKCommonLib API from its Maven repository (required by MyWorlds at compile time).
compileOnly(
files("libs/Chat-OG/Chat-OG.jar")
) // Import Chat-OG API for world chat formatting (vendored jar until the API lands in trueog/Chat-OG).
compileOnly(project(":libs:Scoreboard-OG")) // Import TrueOG Network Scoreboard-OG Java API (from source).
implementation(project(":libs:GxUI-OG")) // Import TrueOG Network GxUI-OG Java API (from source).
compileOnlyApi(project(":libs:Utilities-OG")) // Import TrueOG Network Utilities-OG Java API (from source).
compileOnlyApi(project(":libs:DiamondBank-OG")) {
attributes { attribute(kotlinAttribute, true) }
} // Import TrueOG network DiamondBank-OG Kotlin API (from source).
}
apply(from = "eclipse.gradle.kts") // Import eclipse classpath support script.
/* ---------------------- Reproducible jars ---------------------------- */
tasks.withType<AbstractArchiveTask>().configureEach { // Ensure reproducible .jars
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
/* ----------------------------- Shadow -------------------------------- */
tasks.shadowJar {
exclude("io.github.miniplaceholders.*") // Exclude the MiniPlaceholders package from being shadowed.
isEnableRelocation = true
relocationPrefix = "${project.group}.shadow"
archiveClassifier.set("") // Use empty string instead of null.
minimize()
from("libs/Chat-OG/LICENSE") {
into("META-INF/licenses/libs/Chat-OG")
} // Bundle the vendored Chat-OG jar's license.
}
tasks.jar { archiveClassifier.set("part") } // Applies to root jarfile only.
tasks.build { dependsOn(tasks.spotlessApply, tasks.shadowJar) } // Build depends on spotless and shadow.
/* --------------------------- Javac opts ------------------------------- */
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("-parameters") // Enable reflection for java code.
options.isFork = true // Run javac in its own process.
options.compilerArgs.add("-Xlint:deprecation") // Trigger deprecation warning messages.
options.encoding = "UTF-8" // Use UTF-8 file encoding.
}
/* ----------------------------- Auto Formatting ------------------------ */
spotless {
java {
eclipse().configFile("config/formatter/eclipse-java-formatter.xml") // Eclipse java formatting.
leadingTabsToSpaces() // Convert leftover leading tabs to spaces.
removeUnusedImports() // Remove imports that aren't being called.
}
kotlinGradle {
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } // JetBrains Kotlin formatting.
target("build.gradle.kts", "settings.gradle.kts") // Gradle files to format.
}
}
checkstyle {
toolVersion = "10.18.1" // Declare checkstyle version to use.
configFile = file("config/checkstyle/checkstyle.xml") // Point checkstyle to config file.
isIgnoreFailures = true // Don't fail the build if checkstyle does not pass.
isShowViolations = true // Show the violations in any IDE with the checkstyle plugin.
}
tasks.named("compileJava") {
dependsOn("spotlessApply") // Run spotless before compiling with the JDK.
}
tasks.named("spotlessCheck") {
dependsOn("spotlessApply") // Run spotless before checking if spotless ran.
}
/* ------------------------------ Eclipse SHIM ------------------------- */
// This can't be put in eclipse.gradle.kts because Gradle is weird.
subprojects {
apply(plugin = "java-library")
apply(plugin = "eclipse")
eclipse.project.name = "${project.name}-${rootProject.name}"
tasks.withType<Jar>().configureEach { archiveBaseName.set("${project.name}-${rootProject.name}") }
}
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="TreeWalker">
<module name="FinalLocalVariable"/>
<module name="AvoidStarImport"/>
<module name="Indentation">
<property name="severity" value="ignore"/>
</module>
</module>
<module name="RegexpSingleline">
<property name="format" value="\borg\.junit\.jupiter\.api\.Assertions\.fail\s*\(\s*&quot;Not yet implemented&quot;\s*\)\s*;"/>
<property name="message" value="Remove placeholder fail() and implement the test."/>
<property name="ignoreCase" value="true"/>
<property name="severity" value="warning"/>
</module>
<module name="RegexpSingleline">
<property name="format" value="(?&lt;!\w)fail\s*\(\s*&quot;Not yet implemented&quot;\s*\)\s*;"/>
<property name="message" value="Remove placeholder fail() and implement the test."/>
<property name="severity" value="warning"/>
</module>
</module>
+404
View File
@@ -0,0 +1,404 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="23">
<profile kind="CodeFormatterProfile" name="TrueOG Network Formatting" version="23">
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case_after_arrow" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.align_arrows_in_switch_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow" value="20"/>
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile>
</profiles>
+151
View File
@@ -0,0 +1,151 @@
/* This is free and unencumbered software released into the public domain */
import org.gradle.plugins.ide.eclipse.model.EclipseModel
import java.nio.file.Files
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
val kotlinAttribute: Attribute<Boolean> by rootProject.extra
val kotlinPluginProjects = configurations
.getByName("compileClasspath")
.allDependencies
.filterIsInstance<ProjectDependency>()
.filter { it -> it.attributes.getAttribute(kotlinAttribute) != null }
.map { it.path }
kotlinPluginProjects.forEach { evaluationDependsOn(it) }
val ideLibs: Configuration by
configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
}
configure<EclipseModel> {
project {
name = "Splegg-OG-Plugin"
}
classpath {
plusConfigurations.add(ideLibs)
}
}
val ideLibDir = layout.buildDirectory.dir("ide-libs")
val hashRegex = Regex("-[0-9a-fA-F]{10}(?=\\.jar$)") // Black magic.
val copyTasks = mutableListOf<TaskProvider<Copy>>()
kotlinPluginProjects.forEach { path ->
val sub = project(path)
val shadowJarProv = sub.tasks.named("shadowJar")
val copyTask =
tasks.register<Copy>("ideCopy${sub.name.replaceFirstChar(Char::titlecase)}") {
dependsOn(shadowJarProv)
from(shadowJarProv)
into(ideLibDir)
rename { it.replace(hashRegex, "") } // Remove git commit hash from jarfile.
}
copyTasks += copyTask
}
/* Ensure the jars exist before .classpath is generated */
tasks.named("eclipseClasspath").configure { dependsOn(copyTasks) }
/* Supply those jars to the ideLibs configuration (after evaluation) */
afterEvaluate { dependencies { add("ideLibs", fileTree(ideLibDir) { include("*.jar") }) } }
val injectIdeLibs =
tasks.register("injectIdeLibs") {
dependsOn("eclipse") // run after all eclipse files are generated
doLast {
val cpFile = file(".classpath")
if (!cpFile.exists()) return@doLast
val jars = fileTree(ideLibDir.get()) { include("*.jar") }.files.sortedBy { it.name }
if (jars.isEmpty()) return@doLast
// Parse DOM
val dbf = DocumentBuilderFactory.newInstance()
val doc = dbf.newDocumentBuilder().parse(cpFile)
val root = doc.documentElement
// Helper to see if an entry already exists
fun exists(path: String): Boolean =
root.getElementsByTagName("classpathentry").let { list ->
(0 until list.length).any { i ->
val n = list.item(i)
val kind = n.attributes?.getNamedItem("kind")?.nodeValue
val p = n.attributes?.getNamedItem("path")?.nodeValue
kind == "lib" && p == path
}
}
// Remove any entry that points to ide-libs dir (folder or jars) to avoid dupes
val toRemove = mutableListOf<org.w3c.dom.Node>()
val list = root.getElementsByTagName("classpathentry")
val dirPath = ideLibDir.get().asFile.absolutePath
for (i in 0 until list.length) {
val n = list.item(i)
val kind = n.attributes?.getNamedItem("kind")?.nodeValue
val p = n.attributes?.getNamedItem("path")?.nodeValue ?: ""
if (kind == "lib" && (p == dirPath || p.startsWith("$dirPath/"))) {
toRemove += n
}
}
toRemove.forEach { root.removeChild(it) }
// Append our jar entries LAST
jars.forEach { f ->
val abs = f.absolutePath
if (!exists(abs)) {
val entry = doc.createElement("classpathentry")
entry.setAttribute("kind", "lib")
entry.setAttribute("path", abs)
root.appendChild(entry)
}
}
// Write back pretty
val tf =
TransformerFactory.newInstance().newTransformer().apply {
setOutputProperty(OutputKeys.INDENT, "yes")
setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1")
setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no")
setOutputProperty(OutputKeys.ENCODING, "UTF-8")
}
Files.newBufferedWriter(cpFile.toPath()).use { w -> tf.transform(DOMSource(doc), StreamResult(w)) }
}
}
tasks.named("eclipse").configure { finalizedBy(injectIdeLibs) }
val eclipseModel = extensions.getByType<EclipseModel>()
fun Project.addResolvableEclipseConfigs() {
val jarAttr =
objects.named(org.gradle.api.attributes.LibraryElements::class, org.gradle.api.attributes.LibraryElements.JAR)
val apiAttr = objects.named(org.gradle.api.attributes.Usage::class, org.gradle.api.attributes.Usage.JAVA_API)
val compileOnlyRes =
configurations.create("eclipseCompileOnly") {
extendsFrom(configurations.getByName("compileOnly"))
isCanBeResolved = true
isCanBeConsumed = false
attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr)
attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr)
}
val compileOnlyApiRes =
configurations.create("eclipseCompileOnlyApi") {
extendsFrom(configurations.getByName("compileOnlyApi"))
isCanBeResolved = true
isCanBeConsumed = false
attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr)
attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr)
}
eclipseModel.classpath.plusConfigurations.addAll(listOf(compileOnlyRes, compileOnlyApiRes))
}
addResolvableEclipseConfigs()
Binary file not shown.
+3 -1
View File
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+32 -15
View File
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -133,22 +133,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,18 +200,28 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Vendored
+23 -18
View File
@@ -13,8 +13,10 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -56,32 +59,34 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>
Submodule libs/DiamondBank-OG added at 39d5a2b936.
Submodule
+1
Submodule libs/GxUI-OG added at 287d0eb3ee.
Submodule
+1
Submodule libs/MyWorlds added at 659627d65e.
Submodule libs/Scoreboard-OG added at 3c2e10c103.
+1
Submodule libs/Utilities-OG added at 8b5c86cc9c.
-10
View File
@@ -1,10 +0,0 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/7.4.2/userguide/multi_project_builds.html
*/
rootProject.name = 'Splegg-OG'
+15
View File
@@ -0,0 +1,15 @@
rootProject.name = "Splegg-OG"
// Fetch submodule content before the libraries below are wired in as subprojects.
ProcessBuilder("sh", "bootstrap.sh").directory(rootDir).inheritIO().start().let {
if (it.waitFor() != 0) throw GradleException("bootstrap.sh failed")
}
// Every git submodule under libs/ with a Gradle build becomes :libs:<name>, like Template-OG.
file("libs")
.listFiles()
?.filter { it.isDirectory && !it.name.startsWith(".") && file("${it.path}/build.gradle.kts").exists() }
?.forEach { dir ->
include(":libs:${dir.name}")
project(":libs:${dir.name}").projectDir = dir
}
+124
View File
@@ -0,0 +1,124 @@
package chat;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import nl.skbotnl.chatog.api.WorldChatFormatter;
import org.bukkit.entity.Player;
import main.SpleggOG;
import managers.Game;
import managers.Status;
import net.trueog.utilitiesog.UtilitiesOG;
import utils.SpleggPlayer;
import utils.UtilPlayer;
// Renders Splegg chat now that Chat-OG owns delivery and the Discord relay.
//
// The game is resolved from the sender rather than from the lobby id Chat-OG passes: a Splegg game
// is a live object keyed by game id, and its worlds are per-match copies, so the player is the only
// reliable handle. The lobby id is still the right thing for Chat-OG to route on, it just is not
// what identifies a Game here.
//
// The name segment mirrors TheHerobrine-OG's formatter: the standard TrueOG
// union bracket tag, display name, and LuckPerms suffix, expanded through
// MiniPlaceholders with the sender as the audience so the same content reaches
// the in-game view and the Discord relay.
public class SpleggChatFormatter implements WorldChatFormatter {
private static final String NAME_SEGMENT = "<simpleclans_union_bracket_tag><player_display_name><luckperms_suffix> ";
@Override
public Component format(Player sender, Component message, String worldName, String lobbyId) {
final SpleggOG plugin = SpleggOG.getPlugin();
if (plugin == null || plugin.pm == null) {
return null;
}
final UtilPlayer trackedPlayer = plugin.pm.getPlayer(sender);
final Game game = trackedPlayer == null ? null : trackedPlayer.getGame();
// Standing in a Splegg world without being in a game (staff, a stale copy):
// let Chat-OG use its default format rather than inventing a game prefix.
if (game == null) {
return null;
}
final Component prefix = UtilitiesOG.trueogExpand(buildPrefix(sender, trackedPlayer, game), sender);
// The caret never inherits bold or colors from the prefix.
final Component caret = UtilitiesOG.trueogExpand(getCaretColor(sender) + "> &r", sender)
.decoration(TextDecoration.BOLD, false);
// The message is already sanitised by Chat-OG, so it is composed with rather
// than re-parsed.
return Component.join(JoinConfiguration.noSeparators(), prefix, caret, message);
}
private String buildPrefix(Player player, UtilPlayer trackedPlayer, Game game) {
if (game.getStatus() == Status.LOBBY) {
final int max = game.getMaxPlayers();
return "&e" + game.getPlayers().size() + "&7/&e" + max + "&8 ▏ " + NAME_SEGMENT;
}
if (game.getStatus() != Status.INGAME) {
return NAME_SEGMENT;
}
final SpleggPlayer spleggPlayer = game.getPlayers().get(player.getUniqueId());
final int broken = spleggPlayer == null ? 0 : spleggPlayer.getBroken();
if (!trackedPlayer.isAlive()) {
return "&e" + broken + "&8 ▍ &4OUT &8▏ " + NAME_SEGMENT;
}
return "&e" + broken + "&8 ▏ " + NAME_SEGMENT;
}
// TODO: Expose PlayerUtils.getMessageColor in Chat-OG as a supported API.
private String getCaretColor(Player player) {
try {
final net.kyori.adventure.text.format.TextColor color = nl.skbotnl.chatog.util.PlayerUtils.INSTANCE
.getMessageColor(player.getUniqueId());
if (color != null) {
if (color.equals(NamedTextColor.WHITE)) {
return "&f";
} else if (color.equals(NamedTextColor.GRAY)) {
return "&7";
}
}
} catch (Throwable ignored) {
// Chat-OG internals moved or are absent -- fall back to dark gray.
}
return "&8";
}
}
@@ -0,0 +1,86 @@
package commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import main.SpleggOG;
import managers.Game;
import managers.Status;
import utils.UtilPlayer;
import utils.Utils;
// Starts the lobby countdown regardless of Options.AutoStartPlayers.
public class ForceStartCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("You are unable to use this command.");
return true;
}
if (!player.hasPermission("splegg.admin")) {
Utils.spleggOGMessage(player, "&cYou do not have permission to use this command.");
return true;
}
final UtilPlayer trackedPlayer = SpleggOG.getPlugin().pm.getPlayer(player);
final Game game = trackedPlayer != null ? trackedPlayer.getGame() : null;
if (game == null) {
Utils.spleggOGMessage(player, "&cYou must be in a lobby to do this.");
return true;
}
if (game.getStatus() != Status.LOBBY) {
Utils.spleggOGMessage(player, "&cYou cannot run this command right now.");
return true;
}
if (game.getPlayers().isEmpty()) {
Utils.spleggOGMessage(player, "&cThere is nobody in the lobby to start a game for.");
return true;
}
int startTime = 0;
if (args.length > 0) {
try {
startTime = Integer.parseInt(args[0]);
} catch (NumberFormatException error) {
Utils.spleggOGMessage(player, "&cCorrect Usage: /spforcestart [time]");
return true;
}
}
final int countdown = game.forceStartCountdown(startTime);
if (countdown < 0) {
Utils.spleggOGMessage(player, "&cYou cannot run this command right now.");
return true;
}
SpleggOG.getPlugin().chat.bc("&6The game will start in &b" + countdown + "&6 seconds.", game);
return true;
}
}
+82
View File
@@ -0,0 +1,82 @@
package commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import main.SpleggOG;
import managers.Game;
import utils.UtilPlayer;
import utils.Utils;
// /hub and /lobby. Leaves the Splegg lobby or match the player is in and sends
// them back where they came from, or to main spawn. HubCommandListener routes
// /hub, /lobby and /spawn here for anyone inside Splegg territory, whichever
// plugin owns the bare label.
public class HubCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players can use this command.");
return true;
}
handle(player);
return true;
}
public static void handle(Player player) {
final SpleggOG plugin = SpleggOG.getPlugin();
final UtilPlayer trackedPlayer = plugin.pm.track(player);
// leaveGame already returns the player to their pre-join spot.
final Game game = trackedPlayer.getGame();
if (game != null) {
game.leaveGame(trackedPlayer);
return;
}
if (plugin.hasPreJoinLocation(player.getUniqueId())) {
if (!plugin.returnPlayer(player, false)) {
Utils.spleggOGMessage(player, "&cUnable to return you to your previous location.");
return;
}
Utils.spleggOGMessage(player, "&aReturned to your previous location.");
return;
}
if (plugin.findMainWorld() == null) {
Utils.spleggOGMessage(player, "&cNo main world is available.");
return;
}
// MyWorlds' main world spawn is what Spawn-OG's /setspawn writes, so this
// lands on the server spawn.
if (!plugin.returnPlayer(player, true)) {
Utils.spleggOGMessage(player, "&cUnable to return you to the hub.");
return;
}
Utils.spleggOGMessage(player, "&aReturned to the hub.");
}
}
@@ -0,0 +1,97 @@
package commands;
import java.util.Locale;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import main.SpleggOG;
import utils.UtilPlayer;
// Claims /hub, /lobby and /spawn for anyone in a Splegg game or standing in
// Splegg territory. Splegg-OG, TheHerobrine-OG and BuildBattle-OG all declare
// /hub, and Bukkit hands the bare label to whichever registered first, so
// without this a Splegg player's /hub could run another minigame's command and
// teleport them out while Splegg still counts them as playing. /spawn from
// Spawn-OG would do the same. Everywhere else the event is left alone.
public class HubCommandListener implements Listener {
@EventHandler(priority = EventPriority.LOWEST)
public void onCommandPreprocess(PlayerCommandPreprocessEvent event) {
final String label = commandLabel(event.getMessage());
if (!label.equals("hub") && !label.equals("lobby") && !label.equals("spawn")) {
return;
}
final Player player = event.getPlayer();
if (!isSpleggTerritory(player)) {
return;
}
event.setCancelled(true);
HubCommand.handle(player);
}
// The bare command name in lower case, without the leading slash or a
// plugin namespace such as splegg-og:hub.
static String commandLabel(String message) {
if (message == null || message.length() < 2 || message.charAt(0) != '/') {
return "";
}
final String[] parts = message.substring(1).trim().split("\\s+");
if (parts.length == 0) {
return "";
}
String label = parts[0].toLowerCase(Locale.ROOT);
final int colon = label.indexOf(':');
if (colon >= 0) {
label = label.substring(colon + 1);
}
return label;
}
private static boolean isSpleggTerritory(Player player) {
final SpleggOG plugin = SpleggOG.getPlugin();
if (plugin == null) {
return false;
}
if (plugin.pm != null) {
final UtilPlayer trackedPlayer = plugin.pm.getPlayer(player);
if (trackedPlayer != null && trackedPlayer.getGame() != null) {
return true;
}
}
return plugin.isSpleggTerritory(player.getWorld().getName());
}
}
@@ -0,0 +1,61 @@
package commands;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import main.SpleggOG;
import utils.UtilPlayer;
// /spjoin [lobby]: the same as /splegg join. Accepts SP1, sp1 or a bare 1; with
// no argument the lobby list is shown.
public class JoinLobbyCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("You are unable to use this command.");
return true;
}
final UtilPlayer trackedPlayer = SpleggOG.getPlugin().pm.track(player);
SpleggCommand.join(player, trackedPlayer, args.length > 0 ? args[0] : null);
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length != 1) {
return null;
}
final String prefix = args[0].toLowerCase(Locale.ROOT);
final List<String> completions = new ArrayList<>();
for (String id : SpleggOG.getPlugin().games.getLobbyIds()) {
if (id.toLowerCase(Locale.ROOT).startsWith(prefix)) {
completions.add(id);
}
}
return completions;
}
}
+399 -319
View File
@@ -1,9 +1,17 @@
package commands;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import com.sk89q.worldedit.IncompleteRegionException;
@@ -16,551 +24,623 @@ import main.SpleggOG;
import managers.Game;
import managers.Status;
import utils.UtilPlayer;
import utils.Utils;
public class SpleggCommand implements CommandExecutor {
public class SpleggCommand implements CommandExecutor, TabCompleter {
public boolean onCommand(CommandSender sender, Command cmd, String tag, String[] args) {
private static final List<String> PLAYER_SUBS = Arrays.asList("join", "leave", "help", "list", "maps");
private static final List<String> ADMIN_SUBS = Arrays.asList("create", "delete", "setspawn", "setlobby", "addfloor",
"start", "stop", "info");
private static final List<String> MAP_ARG_SUBS = Arrays.asList("create", "delete", "setspawn", "addfloor", "info");
private static final List<String> LOBBY_ARG_SUBS = Arrays.asList("join", "start", "stop");
// Map names become file names under the data folder, so they stay on one path
// segment.
private static final Pattern MAP_NAME = Pattern.compile("[A-Za-z0-9_-]{1,48}");
if (sender instanceof Player) {
private boolean ensureSpleggWorld(Player player) {
Player player = (Player) sender;
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
if (args.length == 0) {
return true;
SpleggOG.getPlugin().chat.sendMessage(player, "&aPlugin created by MrLuangamer, updated by Hraponssi, now maintained by NotAlexNoyle for true-og.net. &6For more information: /splegg help");
}
}
else if (args.length == 1) {
if (SpleggOG.isProtectedMainWorld(player.getWorld())) {
if (args[0].equalsIgnoreCase("join")) {
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.InProtectedMainWorld",
"&cERROR: Splegg cannot modify the vanilla overworld, nether, or end. Move to a Splegg lobby or in-game world first."));
return false;
if (player.hasPermission("splegg.join")) {
}
if (u.getGame() != null && u.isAlive() && u.getGame().getLobbyCount() > 0) {
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.NotInSpleggWorld"));
return false;
player.teleport(u.getGame().getMap().getLobby());
}
}
else {
// Joins the named lobby (SP1 or 1); with no name, lists the lobbies. Shared
// with /spjoin.
public static void join(Player player, UtilPlayer u, String lobbyArgument) {
// Test to see if the user entered any text or not. If they didn't, an ArrayIndexOutOfBoundsException is thrown.
try {
if (!player.hasPermission("splegg.join")) {
String gameThatWasNotFound = args[1];
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.NoPermission"));
return;
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: Failed to start game: &6" + gameThatWasNotFound + "&c! &eSyntax: /splegg join &6mapname.");
}
}
catch(ArrayIndexOutOfBoundsException error) {
if (u.getGame() != null) {
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: The game to join was unspecified! &6Syntax: &e/splegg join mapname.");
Utils.spleggOGMessage(player, "&cERROR: You are already in lobby &e" + u.getGame().getLobbyId() + "&c.");
return;
}
}
// No lobby named: show the list, TheHerobrine-OG style. Each line is
// clickable, so picking one is a single click.
if (lobbyArgument == null || lobbyArgument.isBlank()) {
}
SpleggOG.getPlugin().games.sendLobbyMessage(player);
return;
}
else {
}
permissionMessage(player);
final Game game = SpleggOG.getPlugin().games.resolveLobby(lobbyArgument);
if (game == null) {
}
Utils.spleggOGMessage(player, "&cLobby &e" + lobbyArgument + " &cdoes not exist.");
SpleggOG.getPlugin().games.sendLobbyMessage(player);
return;
}
else if (args[0].equalsIgnoreCase("leave")) {
}
if (u.getGame() != null && u.isAlive()) {
game.joinGame(u);
Game game = u.getGame();
game.leaveGame(u);
}
SpleggOG.getPlugin().chat.sendMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.LeaveGame").replaceAll("&", "§").replaceAll("%map%", game.getMap().getName()));
public boolean onCommand(CommandSender sender, Command cmd, String tag, String[] args) {
}
else {
if (!(sender instanceof Player player)) {
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: You are not in a game!");
sender.sendMessage("Only players can use this command.");
return true;
}
}
}
else if (args[0].equalsIgnoreCase("start")) {
final UtilPlayer u = SpleggOG.getPlugin().pm.track(player);
if (player.hasPermission("splegg.admin")) {
if (args.length == 0) {
if (u.getGame() == null) {
Utils.spleggOGMessage(player,
"&aPlugin created by MrLuangamer, updated by Hraponssi, now maintained by NotAlexNoyle for true-og.net. &6For more information: /"
+ tag + " help");
return true;
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: You are not in a game!");
}
}
else if (u.getGame().getStatus() == Status.LOBBY) {
final String sub = args[0].toLowerCase(Locale.ROOT);
final String arg = args.length > 1 ? args[1] : null;
SpleggOG.getPlugin().game.startGame(u.getGame());
switch (sub) {
SpleggOG.getPlugin().chat.sendMessage(player, "&aGame started!");
case "help" -> sendHelp(player, tag);
case "list", "lobbies" -> SpleggOG.getPlugin().games.sendLobbyMessage(player);
case "maps" -> sendMapList(player);
case "join", "random" -> join(player, u, arg);
case "leave" -> {
}
else if (u.getGame().getStatus() == Status.INGAME) {
if (u.getGame() != null && u.isAlive()) {
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: The game has already begun!");
u.getGame().leaveGame(u);
}
} else {
}
else {
Utils.spleggOGMessage(player, "&cERROR: You are not in a game!");
permissionMessage(player);
}
}
}
case "start" -> {
}
else if (args[0].equalsIgnoreCase("stop")) {
if (!player.hasPermission("splegg.admin")) {
if (player.hasPermission("splegg.admin")) {
permissionMessage(player);
return true;
if (u.getGame() == null) {
}
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: You are not in a game!");
final Game game = arg == null ? u.getGame() : SpleggOG.getPlugin().games.resolveLobby(arg);
if (game == null) {
}
else if (u.getGame().getStatus() == Status.LOBBY) {
Utils.spleggOGMessage(player, arg == null ? "&cERROR: You are not in a lobby!"
: "&cLobby &e" + arg + " &cdoes not exist.");
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: The game has not begun yet!");
} else if (game.getStatus() == Status.INGAME) {
}
else if (u.getGame().getStatus() == Status.INGAME) {
Utils.spleggOGMessage(player, "&cERROR: The game has already begun!");
SpleggOG.getPlugin().chat.bc("&5" + player.getName() + "&6 has stopped the game.", u.getGame());
} else if (game.getStatus() != Status.LOBBY) {
SpleggOG.getPlugin().game.stopGame(u.getGame(), 1);
Utils.spleggOGMessage(player,
"&cERROR: Lobby &e" + game.getLobbyId() + " &ccannot start right now.");
SpleggOG.getPlugin().chat.sendMessage(player, "&6You have stopped the game.");
} else if (game.getPlayers().size() < 2) {
}
Utils.spleggOGMessage(player,
"&cERROR: There are not enough players in the lobby to start the game. &6Players required: &e2&6.");
}
else {
} else {
permissionMessage(player);
Utils.spleggOGMessage(player, "&eStarting lobby " + game.getLobbyId() + "...");
if (SpleggOG.getPlugin().game.startGame(game)) {
}
Utils.spleggOGMessage(player, "&aGame started!");
}
else if (args[0].equalsIgnoreCase("setlobby")) {
} else {
if (player.hasPermission("splegg.admin")) {
Utils.spleggOGMessage(player, "&cERROR: No playable map could be loaded.");
SpleggOG.getPlugin().config.setLobby(player.getLocation());
}
SpleggOG.getPlugin().chat.sendMessage(player, "&aThe lobby has been set.");
}
}
else {
}
case "stop" -> {
permissionMessage(player);
if (!player.hasPermission("splegg.admin")) {
}
permissionMessage(player);
return true;
}
else if (!args[0].equalsIgnoreCase("")) {
}
SpleggOG.getPlugin().chat.sendMessage(player, "&cIncorrect Usage! &6Applicable commands are: &e/" + tag + " &6<&ejoin&6, &eleave&6, &ehelp&6>");
final Game game = arg == null ? u.getGame() : SpleggOG.getPlugin().games.resolveLobby(arg);
if (game == null) {
}
Utils.spleggOGMessage(player, arg == null ? "&cERROR: You are not in a lobby!"
: "&cLobby &e" + arg + " &cdoes not exist.");
}
else {
} else if (game.getStatus() == Status.LOBBY) {
Map map;
String firstUserCommandArgument;
Game game;
if (args.length == 2 || args.length == 3) {
Utils.spleggOGMessage(player, "&cERROR: The game has not begun yet!");
if (args[0].equalsIgnoreCase("setspawn")) {
} else if (game.getStatus() == Status.INGAME) {
if (player.hasPermission("splegg.admin")) {
SpleggOG.getPlugin().chat.bc("&5" + player.getName() + "&6 has stopped the game.", game);
SpleggOG.getPlugin().game.stopGame(game, game.getPlayers().size());
Utils.spleggOGMessage(player, "&6You have stopped the game.");
firstUserCommandArgument = args[1];
if (SpleggOG.getPlugin().maps.mapExists(firstUserCommandArgument)) {
}
map = SpleggOG.getPlugin().maps.getMap(firstUserCommandArgument);
String secondUserCommandArgumentIsText;
try {
}
case "setlobby" -> {
secondUserCommandArgumentIsText = args[2];
if (secondUserCommandArgumentIsText.equalsIgnoreCase("next")) {
if (!player.hasPermission("splegg.admin")) {
map.addSpawn(player.getLocation(), SpleggOG.getPlugin().games.getGame(map.getName()));
SpleggOG.getPlugin().chat.sendMessage(player, "&aSpawn &6" + map.getSpawnCount() + " &aset for map: &e" + map.getName() + "&a.");
permissionMessage(player);
return true;
}
else {
}
int secondUserCommandArgumentIsInteger;
try {
if (arg != null) {
secondUserCommandArgumentIsInteger = Integer.parseInt(args[2]);
if (this.spawnset(secondUserCommandArgumentIsInteger, map)) {
Utils.spleggOGMessage(player,
"&cERROR: Match lobbies are no longer per map. Stand in a hub world and run &e/" + tag
+ " setlobby &cwith no map name.");
return true;
map.setSpawn(map, secondUserCommandArgumentIsInteger, player.getLocation());
}
SpleggOG.getPlugin().chat.sendMessage(player, "&aThe spawn point &6" + secondUserCommandArgumentIsInteger + " &afor map: &e" + firstUserCommandArgument + "&a has been re-set.");
if (!SpleggOG.getPlugin().isSpleggLobbyWorld(player.getWorld())) {
}
else {
Utils.spleggOGMessage(player,
"&cERROR: The queue lobby must be set inside a lobby hub world (one of Worlds.Lobby).");
return true;
SpleggOG.getPlugin().chat.sendMessage(player, "&ERROR: The spawn point: &6" + secondUserCommandArgumentIsInteger + " &cdoes not yet exist for map: &e" + map.getName() + "&c.");
usageMessage(player, tag);
}
}
SpleggOG.getPlugin().config.setLobby(player.getLocation());
Utils.spleggOGMessage(player,
"&aThe Splegg queue lobby has been set. Every lobby hub uses these coordinates.");
}
catch(Exception error) {
}
case "setspawn", "create", "delete", "addfloor", "info" -> handleMapCommand(player, tag, sub, args);
default -> Utils.spleggOGMessage(player, "&cIncorrect Usage! &6Applicable commands are: &e/" + tag
+ " &6<&ejoin&6, &eleave&6, &elist&6, &ehelp&6>");
usageMessage(player, tag);
}
}
return true;
}
}
}
catch(ArrayIndexOutOfBoundsException error) {
private void handleMapCommand(Player player, String tag, String sub, String[] args) {
map.addSpawn(player.getLocation(), SpleggOG.getPlugin().games.getGame(map.getName()));
SpleggOG.getPlugin().chat.sendMessage(player, "&aThe spawn: &6" + map.getSpawnCount() + " &ahas been set for the map: &e" + map.getName() + "&a.");
if (!player.hasPermission("splegg.admin")) {
}
permissionMessage(player);
return;
}
else {
}
noMapMessage(player, firstUserCommandArgument);
if (args.length < 2) {
}
usageMessage(player, tag);
return;
}
else {
}
permissionMessage(player);
final String mapName = args[1];
}
switch (sub) {
}
else if (args[0].equalsIgnoreCase("create")) {
case "create" -> {
if (player.hasPermission("splegg.admin")) {
if (!ensureSpleggWorld(player)) {
firstUserCommandArgument = args[1];
if (SpleggOG.getPlugin().maps.mapExists(firstUserCommandArgument)) {
return;
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: The map: &e" + firstUserCommandArgument + " &calready exists.");
}
}
else {
if (!MAP_NAME.matcher(mapName).matches()) {
SpleggOG.getPlugin().maps.c.addMap(firstUserCommandArgument);
SpleggOG.getPlugin().maps.addMap(firstUserCommandArgument);
Utils.spleggOGMessage(player, "&cERROR: Map names may only contain letters, digits, '-' and '_'.");
map = SpleggOG.getPlugin().maps.getMap(firstUserCommandArgument);
game = new Game(SpleggOG.getPlugin(), map);
game.setStatus(Status.DISABLED);
SpleggOG.getPlugin().games.addGame(map.getName(), game);
} else if (SpleggOG.getPlugin().maps.mapExists(mapName)) {
SpleggOG.getPlugin().chat.sendMessage(player, "&aThe map: &e" + firstUserCommandArgument + " &ahas been created. It will be &cDISABLED &auntil at least one spawn point and one floor have been added.");
Utils.spleggOGMessage(player, "&cERROR: The map: &e" + mapName + " &calready exists.");
}
} else {
}
else {
SpleggOG.getPlugin().maps.c.addMap(mapName);
SpleggOG.getPlugin().maps.addMap(mapName);
permissionMessage(player);
Utils.spleggOGMessage(player, "&aThe map: &e" + mapName
+ " &ahas been created. It will be &cDISABLED &auntil at least two spawn points and one floor have been added.");
}
}
}
else if (args[0].equalsIgnoreCase("delete")) {
}
case "delete" -> {
if (player.hasPermission("splegg.admin")) {
if (SpleggOG.getPlugin().maps.mapExists(mapName)) {
firstUserCommandArgument = args[1];
if (SpleggOG.getPlugin().maps.mapExists(firstUserCommandArgument)) {
SpleggOG.getPlugin().maps.deleteMap(mapName);
Utils.spleggOGMessage(player, "&aThe map: &e" + mapName + " &ahas been deleted.");
SpleggOG.getPlugin().maps.deleteMap(firstUserCommandArgument);
} else {
SpleggOG.getPlugin().chat.sendMessage(player, "&aThe map: &e" + firstUserCommandArgument + " has been deleted.");
noMapMessage(player, mapName);
}
else {
}
noMapMessage(player, firstUserCommandArgument);
}
case "info" -> {
}
if (SpleggOG.getPlugin().maps.mapExists(mapName)) {
}
else {
sendMapInfo(player, mapName);
permissionMessage(player);
} else {
}
noMapMessage(player, mapName);
}
else if(args[0].equalsIgnoreCase("start")) {
}
if (player.hasPermission("splegg.admin")) {
}
case "setspawn" -> {
firstUserCommandArgument = args[1];
if (SpleggOG.getPlugin().maps.mapExists(firstUserCommandArgument)) {
if (!ensureSpleggWorld(player)) {
game = SpleggOG.getPlugin().games.getGame(firstUserCommandArgument);
if (game == null) {
return;
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: You are not in a game!");
}
}
else if (game.getStatus() == Status.LOBBY) {
if (!SpleggOG.getPlugin().maps.mapExists(mapName)) {
SpleggOG.getPlugin().chat.sendMessage(player, "&eStarting Game " + firstUserCommandArgument + "...");
noMapMessage(player, mapName);
return;
SpleggOG.getPlugin().game.startGame(game);
}
}
else if (game.getStatus() == Status.INGAME) {
final Map map = SpleggOG.getPlugin().maps.getMap(mapName);
if (args.length < 3 || args[2].equalsIgnoreCase("next") || args[2].equalsIgnoreCase("append")) {
// TODO: Change this for spectator mode when it is implemented.
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: The game has already begun.");
map.addSpawn(player.getLocation());
Utils.spleggOGMessage(player,
"&aSpawn &6" + map.getSpawnCount() + " &aset for map: &e" + map.getName() + "&a.");
return;
}
}
}
else {
final int spawnId;
try {
noMapMessage(player, firstUserCommandArgument);
spawnId = Integer.parseInt(args[2]);
}
} catch (NumberFormatException error) {
}
else {
usageMessage(player, tag);
return;
permissionMessage(player);
}
}
}
else if (args[0].equalsIgnoreCase("stop")) {
if (this.spawnset(spawnId, map)) {
if (player.hasPermission("splegg.admin")) {
map.setSpawn(map, spawnId, player.getLocation());
Utils.spleggOGMessage(player,
"&aThe spawn point &6" + spawnId + " &afor map: &e" + mapName + "&a has been re-set.");
firstUserCommandArgument = args[1];
if (SpleggOG.getPlugin().maps.mapExists(firstUserCommandArgument)) {
} else {
game = SpleggOG.getPlugin().games.getGame(firstUserCommandArgument);
if (game == null) {
Utils.spleggOGMessage(player, "&cERROR: The spawn point: &6" + spawnId
+ " &cdoes not yet exist for map: &e" + map.getName() + "&c.");
usageMessage(player, tag);
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: You are not in a game!");
}
}
else if (game.getStatus() == Status.LOBBY) {
}
case "addfloor" -> {
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: The game has not begun yet!");
if (!ensureSpleggWorld(player)) {
}
else if (game.getStatus() == Status.INGAME) {
return;
SpleggOG.getPlugin().chat.bc("&5" + player.getName() + "&6 has stopped the game.", game);
}
SpleggOG.getPlugin().game.stopGame(game, 1);
if (!SpleggOG.getPlugin().maps.mapExists(mapName)) {
SpleggOG.getPlugin().chat.sendMessage(player, "&6You have stopped the game.");
noMapMessage(player, mapName);
return;
}
}
}
else {
final Map map = SpleggOG.getPlugin().maps.getMap(mapName);
final WorldEditPlugin we = SpleggOG.getPlugin().getWorldEdit();
Region sel = null;
try {
noMapMessage(player, firstUserCommandArgument);
sel = we.getSession(player).getSelection(new BukkitWorld(player.getWorld()));
}
} catch (IncompleteRegionException error) {
}
else {
Utils.spleggOGMessage(player, "&cERROR: The area you have selected is incomplete.");
permissionMessage(player);
}
}
if (sel == null) {
}
else if (args[0].equalsIgnoreCase("setlobby")) {
Utils.spleggOGMessage(player, "&4Please select an area with worldedit.");
return;
if (player.hasPermission("splegg.admin")) {
}
firstUserCommandArgument = args[1];
if (SpleggOG.getPlugin().maps.mapExists(firstUserCommandArgument)) {
map.addFloor(
new Location(player.getWorld(), sel.getMinimumPoint().getX(), sel.getMinimumPoint().getY(),
sel.getMinimumPoint().getZ()),
new Location(player.getWorld(), sel.getMaximumPoint().getX(), sel.getMaximumPoint().getY(),
sel.getMaximumPoint().getZ()));
SpleggOG.getPlugin().maps.getMap(firstUserCommandArgument).setLobby(player.getLocation());
SpleggOG.getPlugin().chat.sendMessage(player, "&aThe lobby for map: &e" + firstUserCommandArgument + "&a has been set to: &2" + player.getLocation() + "&a.");
Utils.spleggOGMessage(player,
"&aFloor &6" + map.getFloors() + " &aadded to map: &e" + map.getName() + "&a.");
}
else {
}
default -> usageMessage(player, tag);
noMapMessage(player, firstUserCommandArgument);
}
}
}
}
else {
void permissionMessage(Player player) {
permissionMessage(player);
Utils.spleggOGMessage(player, "&cERROR: You do not have permission to do that.");
}
}
}
else if (args[0].equalsIgnoreCase("addfloor")) {
void usageMessage(Player player, String tag) {
if (player.hasPermission("splegg.admin")) {
Utils.spleggOGMessage(player,
"&cERROR: Incorrect syntax. &6Use the command: &e/" + tag + " help &6for the command list.");
firstUserCommandArgument = args[1];
if (SpleggOG.getPlugin().maps.mapExists(firstUserCommandArgument)) {
}
map = SpleggOG.getPlugin().maps.getMap(firstUserCommandArgument);
WorldEditPlugin we = SpleggOG.getPlugin().getWorldEdit();
Region sel = null;
try {
void noMapMessage(Player player, String mapUserTriedToReferTo) {
sel = we.getSession(player).getSelection(new BukkitWorld(player.getWorld()));
Utils.spleggOGMessage(player, "&cERROR: The map: &e" + mapUserTriedToReferTo + " &cdoes not exist.");
}
catch (IncompleteRegionException error) {
}
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: The area you have selected is incomplete.");
boolean spawnset(int i, Map map) {
}
return map.getConfig().isString("Spawns." + i + ".world");
if (sel == null) {
}
SpleggOG.getPlugin().chat.sendMessage(player, "&4Please select an area with worldedit.");
public void sendUsage(Player player, String tag, String usage, String def) {
}
else {
Utils.spleggOGMessage(player, "&c/" + tag + " &d" + usage + " &5- &b" + def);
map.addFloor(new Location(player.getWorld(), sel.getMinimumPoint().getBlockX(), sel.getMinimumPoint().getBlockY(), sel.getMinimumPoint().getBlockZ()), new Location(player.getWorld(), sel.getMaximumPoint().getBlockX(), sel.getMaximumPoint().getBlockY(), sel.getMaximumPoint().getBlockZ()), SpleggOG.getPlugin().games.getGame(map.getName()));
}
SpleggOG.getPlugin().chat.sendMessage(player, "&aFloor &6" + map.getFloors() + " &aadded to map: &e" + map.getName() + "&a.");
private void sendHelp(Player player, String tag) {
}
final boolean admin = player.hasPermission("splegg.admin");
Utils.spleggOGMessage(player, "&6Splegg-OG commands:");
sendUsage(player, tag, "join [lobby]", "Join a lobby by id (SP1 or 1); no id lists the lobbies.");
sendUsage(player, tag, "leave", "Leave your current match or lobby.");
sendUsage(player, tag, "list", "List every lobby and its status.");
sendUsage(player, tag, "maps", "List every configured map and its status.");
sendUsage(player, tag, "help", "Show this help message.");
Utils.spleggOGMessage(player, "&c/vote &d<map number> &5- &bVote for a lobby map. Alias: &e/v");
}
else {
if (!admin) {
noMapMessage(player, firstUserCommandArgument);
return;
}
}
}
else {
Utils.spleggOGMessage(player, "&cAdmin commands:");
sendUsage(player, tag, "create <map>", "Create a new map (disabled until floor + spawns exist).");
sendUsage(player, tag, "delete <map>", "Delete a map.");
sendUsage(player, tag, "info <map>", "Show map setup status (spawns, floors).");
sendUsage(player, tag, "setlobby", "Set where players wait in every lobby hub. Run inside a hub world.");
sendUsage(player, tag, "setspawn <map> [next|append|#]", "Add or update a spawn point.");
sendUsage(player, tag, "addfloor <map>", "Add a WorldEdit selection as a floor.");
sendUsage(player, tag, "start [lobby]", "Start a lobby's match early.");
sendUsage(player, tag, "stop [lobby]", "Stop a lobby's match.");
Utils.spleggOGMessage(player, "&c/spforcestart &d[time] &5- &bStart your lobby ignoring the player minimum.");
permissionMessage(player);
}
}
private void sendMapList(Player player) {
}
else if (args[0].equalsIgnoreCase("join")) {
if (SpleggOG.getPlugin().maps.getMaps().isEmpty()) {
if (player.hasPermission("splegg.join")) {
Utils.spleggOGMessage(player, "&6No maps have been configured yet.");
return;
if (u.getGame() != null) {
}
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: You are already playing.");
Utils.spleggOGMessage(player, "&6Splegg maps:");
for (Map map : SpleggOG.getPlugin().maps.getMaps()) {
}
else {
final List<Game> games = SpleggOG.getPlugin().games.gamesForMap(map.getName());
final String statusLabel;
if (!map.isUsable(map)) {
firstUserCommandArgument = args[1];
if (SpleggOG.getPlugin().maps.mapExists(firstUserCommandArgument)) {
statusLabel = "&cDISABLED";
game = SpleggOG.getPlugin().games.getGame(firstUserCommandArgument);
if (game != null && SpleggOG.getPlugin().maps.getMap(firstUserCommandArgument).isUsable(game.getMap())) {
} else if (games.isEmpty()) {
game.joinGame(u);
statusLabel = "&aREADY";
}
else {
} else {
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: The map: &e" + firstUserCommandArgument + " &cis incorrectly set up. &6Please read the README.md file for instructions.");
final List<String> lobbies = new ArrayList<>();
for (Game g : games) {
}
lobbies.add(g.getLobbyId() + (g.getStatus() == Status.INGAME ? " &3LIVE" : " &5VOTED"));
}
else {
}
noMapMessage(player, firstUserCommandArgument);
statusLabel = "&6" + String.join("&7, &6", lobbies);
}
}
}
Utils.spleggOGMessage(player,
"&e" + map.getName() + " &7- " + statusLabel + " &7(&f" + map.getSpawnCount() + " spawns&7)");
}
else {
}
permissionMessage(player);
}
}
private void sendMapInfo(Player player, String mapName) {
}
else if (args[0].equalsIgnoreCase("leave")) {
final Map map = SpleggOG.getPlugin().maps.getMap(mapName);
final List<Game> games = SpleggOG.getPlugin().games.gamesForMap(mapName);
SpleggOG.getPlugin().chat.sendMessage(player, "&4Please use &e/" + tag + " leave");
Utils.spleggOGMessage(player, "&6Map info: &e" + map.getName());
Utils.spleggOGMessage(player, "&6Template world: &f"
+ (map.getWorldName() != null ? map.getWorldName() : "&cunset (set a spawn first)"));
Utils.spleggOGMessage(player, "&6Spawn points: &f" + map.getSpawnCount());
Utils.spleggOGMessage(player, "&6Floor regions: &f" + map.getFloors());
Utils.spleggOGMessage(player, "&6Lobbies on this map: &f" + games.size());
}
else {
final boolean playable = map.isUsable(map);
Utils.spleggOGMessage(player, "&6Playable: " + (playable ? "&ayes" : "&cno"));
usageMessage(player, tag);
if (!playable) {
}
final List<String> missing = new ArrayList<>();
if (map.getSpawnCount() < 2) {
}
else {
missing.add("/splegg setspawn " + map.getName() + " (at least two)");
usageMessage(player, tag);
}
}
if (map.getFloors() <= 0) {
}
missing.add("/splegg addfloor " + map.getName());
}
}
return false;
if (!missing.isEmpty()) {
}
Utils.spleggOGMessage(player, "&6Next steps:");
for (String step : missing) {
void permissionMessage(Player player) {
Utils.spleggOGMessage(player, " &e" + step);
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: You do not have permission to do that.");
}
}
}
void usageMessage(Player player, String tag) {
}
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: Incorrect syntax. &6Use the command: &e/splegg setspawn [MAPNAME] &6to create spawn points.");
}
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
void noMapMessage(Player player, String mapUserTriedToReferTo) {
if (args.length == 1) {
SpleggOG.getPlugin().chat.sendMessage(player, "&cERROR: The map: &e" + mapUserTriedToReferTo + " &cdoes not exist.");
final List<String> options = new ArrayList<>(PLAYER_SUBS);
if (sender.hasPermission("splegg.admin")) {
}
options.addAll(ADMIN_SUBS);
boolean spawnset(int i, Map map) {
}
return map.getConfig().isString("Spawns." + i + ".world");
return filterPrefix(options, args[0]);
}
}
public void sendUsage(Player player, String tag, String usage, String def) {
if (args.length == 2 && LOBBY_ARG_SUBS.contains(args[0].toLowerCase(Locale.ROOT))) {
SpleggOG.getPlugin().chat.sendMessage(player, "&c/" + tag + " &d" + usage + " &5- &b" + def);
return filterPrefix(SpleggOG.getPlugin().games.getLobbyIds(), args[1]);
}
}
}
if (args.length == 2 && MAP_ARG_SUBS.contains(args[0].toLowerCase(Locale.ROOT))) {
final List<String> mapNames = new ArrayList<>();
for (Map map : SpleggOG.getPlugin().maps.getMaps()) {
mapNames.add(map.getName());
}
return filterPrefix(mapNames, args[1]);
}
if (args.length == 3 && args[0].equalsIgnoreCase("setspawn")) {
return filterPrefix(Arrays.asList("next", "append"), args[2]);
}
return Collections.emptyList();
}
private List<String> filterPrefix(List<String> source, String prefix) {
final String needle = prefix == null ? "" : prefix.toLowerCase(Locale.ROOT);
final List<String> matched = new ArrayList<>();
for (String candidate : source) {
if (candidate.toLowerCase(Locale.ROOT).startsWith(needle)) {
matched.add(candidate);
}
}
Collections.sort(matched);
return matched;
}
}
+66
View File
@@ -0,0 +1,66 @@
package commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import main.SpleggOG;
import managers.Game;
import utils.UtilPlayer;
import utils.Utils;
public class VoteCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("You are unable to use this command.");
return true;
}
final UtilPlayer trackedPlayer = SpleggOG.getPlugin().pm.getPlayer(player);
final Game game = trackedPlayer != null ? trackedPlayer.getGame() : null;
if (game == null) {
Utils.spleggOGMessage(player, "&cYou must be in a lobby to do this.");
return true;
}
if (!game.isVotingRunning()) {
Utils.spleggOGMessage(player, "&cYou cannot run this command right now.");
return true;
}
if (args.length == 0) {
game.sendVotingMessage(player);
return true;
}
final int map;
try {
map = Integer.parseInt(args[0]);
} catch (Exception error) {
Utils.spleggOGMessage(player, "&cCorrect Usage: /vote <map number>");
game.sendVotingMessage(player);
return true;
}
game.vote(player, map);
return true;
}
}
@@ -0,0 +1,108 @@
package commands;
import java.util.Arrays;
import java.util.Locale;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import main.SpleggOG;
import managers.GameWorldManager;
import utils.UtilPlayer;
// Routes /v and /vote to Splegg map voting inside Splegg games and worlds only.
// Without this, VotingPlugin can own the /vote label and swallow map votes.
public class VoteCommandListener implements Listener {
private final VoteCommand voteCommand;
public VoteCommandListener(VoteCommand voteCommand) {
this.voteCommand = voteCommand;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onCommandPreprocess(PlayerCommandPreprocessEvent event) {
final String message = event.getMessage();
if (message == null || message.length() < 2 || message.charAt(0) != '/') {
return;
}
final String[] parts = message.substring(1).trim().split("\\s+");
if (parts.length == 0 || parts[0].isEmpty()) {
return;
}
// Drop any namespace prefix (e.g. votingplugin:v) before matching.
String label = parts[0].toLowerCase(Locale.ROOT);
final int colon = label.indexOf(':');
if (colon >= 0) {
label = label.substring(colon + 1);
}
if (!label.equals("v") && !label.equals("vote")) {
return;
}
final Player player = event.getPlayer();
if (!isSpleggTerritory(player)) {
return;
}
final String[] args = parts.length > 1 ? Arrays.copyOfRange(parts, 1, parts.length) : new String[0];
voteCommand.onCommand(player, null, label, args);
event.setCancelled(true);
}
// Claims the command for a tracked Splegg player, or for anyone standing in a
// Splegg lobby, in-game, or per-match world. Defers everywhere else.
private boolean isSpleggTerritory(Player player) {
final SpleggOG plugin = SpleggOG.getPlugin();
if (plugin == null) {
return false;
}
if (plugin.pm != null) {
final UtilPlayer trackedPlayer = plugin.pm.getPlayer(player);
if (trackedPlayer != null && trackedPlayer.getGame() != null) {
return true;
}
}
final String worldName = player.getWorld().getName();
if (plugin.isSpleggWorld(worldName)) {
return true;
}
final GameWorldManager gameWorldManager = plugin.getGameWorldManager();
return gameWorldManager != null && !SpleggOG.isProtectedMainWorld(worldName)
&& gameWorldManager.isGameCopyName(worldName);
}
}
+285 -190
View File
@@ -11,314 +11,409 @@ import org.bukkit.configuration.file.YamlConfiguration;
import main.SpleggOG;
import managers.Game;
import managers.Status;
public class Map {
SpleggOG splegg;
String name;
File file;
int spawncount;
int floorcount;
boolean usable;
// Enable the conversion of text from config.yml to objects.
public FileConfiguration config = SpleggOG.getPlugin().getConfig();
public Map(SpleggOG splegg, String name) {
SpleggOG splegg;
String name;
File file;
int spawncount;
int floorcount;
boolean usable;
// Enable the conversion of text from config.yml to objects.
public FileConfiguration config = SpleggOG.getPlugin().getConfig();
this.splegg = splegg;
this.name = name;
this.floorcount = 0;
this.spawncount = 0;
public Map(SpleggOG splegg, String name) {
}
this.splegg = splegg;
this.name = name;
this.floorcount = 0;
this.spawncount = 0;
public void load() {
}
SpleggOG.getPlugin().chat.log("Loading map " + this.name + "...");
public void load() {
this.file = new File(SpleggOG.getPlugin().getDataFolder(), this.name + ".yml");
try {
SpleggOG.getPlugin().getLogger().info("Loading map " + this.name + "...");
if (! this.file.exists()) {
this.file = new File(SpleggOG.getPlugin().getDataFolder(), this.name + ".yml");
try {
this.file.createNewFile();
if (!this.file.exists()) {
}
this.file.createNewFile();
}
catch (IOException error) {
}
SpleggOG.getPlugin().getLogger().severe("ERROR: Issue creating " + this.name + ".yml: " + error.getMessage());
} catch (IOException error) {
}
SpleggOG.getPlugin().getLogger()
.severe("ERROR: Issue creating " + this.name + ".yml: " + error.getMessage());
this.setConfig(YamlConfiguration.loadConfiguration(this.file));
this.save();
}
this.loadSpawns();
this.loadFloors();
this.setConfig(YamlConfiguration.loadConfiguration(this.file));
this.save();
SpleggOG.getPlugin().getLogger().info("File set: " + this.file.getName() + ".");
this.loadSpawns();
this.loadFloors();
}
SpleggOG.getPlugin().getLogger().info("File set: " + this.file.getName() + ".");
public void usableDecider(Map map) {
}
if (this.spawncount > 0 && this.floorcount > 0) {
public void usableDecider(Map map) {
SpleggOG.getPlugin().getLogger().info("Floor and spawn point(s) detected. The map is ready to go!");
this.usable = true;
SpleggOG.getPlugin().games.getGame(this.getName()).setStatus(Status.LOBBY);
final boolean valid = this.spawncount >= 2 && this.floorcount > 0 && hasValidConfiguredWorlds();
// Logged once per transition: the sign updater re-evaluates every map every
// second.
if (valid && !this.usable) {
}
else {
SpleggOG.getPlugin().getLogger().info("Map " + this.name + " has floors and spawn points and is ready.");
this.usable = false;
}
}
this.usable = valid;
}
}
public boolean isUsable(Map map) {
public boolean isUsable(Map map) {
usableDecider(map);
return this.usable;
usableDecider(map);
return this.usable;
}
}
public void delete() {
private boolean hasValidConfiguredWorlds() {
this.file.delete();
for (int i = 1; i <= this.spawncount; i++) {
}
final String spawnWorld = this.config.getString("Spawns." + i + ".world");
if (!isWorldEligible(spawnWorld, "spawn " + i)) {
public void savenumbers() {
return false;
config.set("Spawns.count", this.spawncount);
config.set("Floors.count", this.floorcount);
}
}
}
public void save() {
for (int i = 1; i <= this.floorcount; i++) {
try {
final String floorOneWorld = this.config.getString("Floors." + i + ".p1.world");
final String floorTwoWorld = this.config.getString("Floors." + i + ".p2.world");
if (!isWorldEligible(floorOneWorld, "floor " + i + " p1")
|| !isWorldEligible(floorTwoWorld, "floor " + i + " p2") || !floorOneWorld.equals(floorTwoWorld))
{
config.save(this.file);
return false;
}
catch (IOException error) {
}
SpleggOG.getPlugin().getLogger().severe("An error occured while saving " + this.name + ".yml.");
}
}
return true;
}
}
public void setSpawn(Map map, int id, Location l) {
// A configured world is eligible for splegg use only when it (1) is loaded
// by the server, and (2) is NOT in the SMP-protected list. Log loudly when
// an admin has somehow saved a protected world into a map config -- that is
// the bug the rest of the safety net is designed to prevent ever happening.
private boolean isWorldEligible(String worldName, String label) {
int x = l.getBlockX();
int y = l.getBlockY();
int z = l.getBlockZ();
if (worldName == null) {
float pitch = l.getPitch();
float yaw = l.getYaw();
return false;
String worldname = l.getWorld().getName();
}
config.set("Spawns." + id + ".world", worldname);
config.set("Spawns." + id + ".x", x);
config.set("Spawns." + id + ".y", y);
config.set("Spawns." + id + ".z", z);
config.set("Spawns." + id + ".pitch", pitch);
config.set("Spawns." + id + ".yaw", yaw);
if (SpleggOG.isProtectedMainWorld(worldName)) {
this.save();
usableDecider(map);
SpleggOG.getPlugin().getLogger()
.warning("Map '" + this.name + "' has " + label + " configured in protected SMP world '" + worldName
+ "'. Map will stay DISABLED. Reconfigure it inside a Splegg lobby or in-game world.");
return false;
}
}
public Location getSpawn(int id) {
return Bukkit.getWorld(worldName) != null;
int x = config.getInt("Spawns." + id + ".x");
int y = config.getInt("Spawns." + id + ".y");
int z = config.getInt("Spawns." + id + ".z");
}
float yaw = (float) config.getInt("Spawns." + id + ".yaw");
float pitch = (float) config.getInt("Spawns." + id + ".pitch");
public void delete() {
World world = Bukkit.getWorld(config.getString("Spawns." + id + ".world"));
this.file.delete();
return new Location(world, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, yaw, pitch);
}
}
public void savenumbers() {
public void setSpec(Location l) {
config.set("Spawns.count", this.spawncount);
config.set("Floors.count", this.floorcount);
int x = l.getBlockX();
int y = l.getBlockY();
int z = l.getBlockZ();
}
float pitch = l.getPitch();
float yaw = l.getYaw();
public void save() {
String worldname = l.getWorld().getName();
try {
config.set("Spec.world", worldname);
config.set("Spec.x", x);
config.set("Spec.y", y);
config.set("Spec.z", z);
config.set("Spec.pitch", pitch);
config.set("Spec.yaw", yaw);
config.save(this.file);
}
} catch (IOException error) {
public Location getSpawnSpec() {
SpleggOG.getPlugin().getLogger().severe("An error occured while saving " + this.name + ".yml.");
int x = config.getInt("Spec.x");
int y = config.getInt("Spec.y");
int z = config.getInt("Spec.z");
}
float yaw = (float) config.getInt("Spec.yaw");
float pitch = (float) config.getInt("Spec.pitch");
}
World world = Bukkit.getWorld(config.getString("Spec.world"));
public void setSpawn(Map map, int id, Location l) {
return new Location(world, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, yaw, pitch);
int x = l.getBlockX();
int y = l.getBlockY();
int z = l.getBlockZ();
}
float pitch = l.getPitch();
float yaw = l.getYaw();
public String getName() {
String worldname = l.getWorld().getName();
return this.name;
config.set("Spawns." + id + ".world", worldname);
config.set("Spawns." + id + ".x", x);
config.set("Spawns." + id + ".y", y);
config.set("Spawns." + id + ".z", z);
config.set("Spawns." + id + ".pitch", pitch);
config.set("Spawns." + id + ".yaw", yaw);
}
this.save();
public void loadSpawns() {
usableDecider(map);
this.spawncount = getCount();
}
}
public Location getSpawn(int id) {
public void loadFloors() {
int x = config.getInt("Spawns." + id + ".x");
int y = config.getInt("Spawns." + id + ".y");
int z = config.getInt("Spawns." + id + ".z");
this.floorcount = getFloors();
float yaw = (float) config.getInt("Spawns." + id + ".yaw");
float pitch = (float) config.getInt("Spawns." + id + ".pitch");
}
World world = Bukkit.getWorld(config.getString("Spawns." + id + ".world"));
public void addSpawn(Location l, Game game) {
return new Location(world, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, yaw, pitch);
this.spawncount++;
this.savenumbers();
this.setSpawn(game.getMap(), this.spawncount, l);
}
}
public void setSpec(Location l) {
public void addFloor(Location p1, Location p2, Game game) {
int x = l.getBlockX();
int y = l.getBlockY();
int z = l.getBlockZ();
this.floorcount++;
this.savenumbers();
float pitch = l.getPitch();
float yaw = l.getYaw();
this.config.set("Floors." + this.floorcount + ".p1.x", p1.getBlockX());
this.config.set("Floors." + this.floorcount + ".p1.y", p1.getBlockY());
this.config.set("Floors." + this.floorcount + ".p1.z", p1.getBlockZ());
this.config.set("Floors." + this.floorcount + ".p1.world", p1.getWorld().getName());
this.config.set("Floors." + this.floorcount + ".p2.x", p2.getBlockX());
this.config.set("Floors." + this.floorcount + ".p2.y", p2.getBlockY());
this.config.set("Floors." + this.floorcount + ".p2.z", p2.getBlockZ());
this.config.set("Floors." + this.floorcount + ".p2.world", p2.getWorld().getName());
String worldname = l.getWorld().getName();
this.save();
config.set("Spec.world", worldname);
config.set("Spec.x", x);
config.set("Spec.y", y);
config.set("Spec.z", z);
config.set("Spec.pitch", pitch);
config.set("Spec.yaw", yaw);
usableDecider(game.getMap());
}
}
public Location getSpawnSpec() {
public Location getFloor(int id, String pos) {
int x = config.getInt("Spec.x");
int y = config.getInt("Spec.y");
int z = config.getInt("Spec.z");
int x = this.config.getInt("Floors." + id + ".p" + pos + ".x");
int y = this.config.getInt("Floors." + id + ".p" + pos + ".y");
int z = this.config.getInt("Floors." + id + ".p" + pos + ".z");
float yaw = (float) config.getInt("Spec.yaw");
float pitch = (float) config.getInt("Spec.pitch");
String world = this.config.getString("Floors." + id + ".p" + pos + ".world");
World world = Bukkit.getWorld(config.getString("Spec.world"));
return new Location(Bukkit.getWorld(world), (double) x, (double) y, (double) z);
return new Location(world, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, yaw, pitch);
}
}
public int getCount() {
public String getName() {
return config.getInt("Spawns.count");
return this.name;
}
}
public int getFloors() {
public void loadSpawns() {
return config.getInt("Floors.count");
this.spawncount = getCount();
}
}
public int getSpawnCount() {
public void loadFloors() {
return this.spawncount;
this.floorcount = getFloors();
}
}
public FileConfiguration getConfig() {
public void addSpawn(Location l, Game game) {
return this.config;
addSpawn(l);
}
}
public void setConfig(FileConfiguration config) {
public void addSpawn(Location l) {
this.config = config;
this.spawncount++;
this.savenumbers();
this.setSpawn(this, this.spawncount, l);
}
}
public boolean lobbySet() {
public void addFloor(Location p1, Location p2, Game game) {
return config.isString("Spawns.lobby.world");
addFloor(p1, p2);
}
}
public void setLobby(Location l) {
public void addFloor(Location p1, Location p2) {
int x = l.getBlockX();
int y = l.getBlockY();
int z = l.getBlockZ();
this.floorcount++;
this.savenumbers();
float pitch = l.getPitch();
float yaw = l.getYaw();
this.config.set("Floors." + this.floorcount + ".p1.x", p1.getBlockX());
this.config.set("Floors." + this.floorcount + ".p1.y", p1.getBlockY());
this.config.set("Floors." + this.floorcount + ".p1.z", p1.getBlockZ());
this.config.set("Floors." + this.floorcount + ".p1.world", p1.getWorld().getName());
this.config.set("Floors." + this.floorcount + ".p2.x", p2.getBlockX());
this.config.set("Floors." + this.floorcount + ".p2.y", p2.getBlockY());
this.config.set("Floors." + this.floorcount + ".p2.z", p2.getBlockZ());
this.config.set("Floors." + this.floorcount + ".p2.world", p2.getWorld().getName());
String worldname = l.getWorld().getName();
this.save();
config.set("Spawns.lobby.world", worldname);
config.set("Spawns.lobby.x", x);
config.set("Spawns.lobby.y", y);
config.set("Spawns.lobby.z", z);
config.set("Spawns.lobby.pitch", pitch);
config.set("Spawns.lobby.yaw", yaw);
usableDecider(this);
this.save();
}
}
public Location getFloor(int id, String pos) {
public Location getLobby() {
int x = this.config.getInt("Floors." + id + ".p" + pos + ".x");
int y = this.config.getInt("Floors." + id + ".p" + pos + ".y");
int z = this.config.getInt("Floors." + id + ".p" + pos + ".z");
int x = config.getInt("Spawns.lobby.x");
int y = config.getInt("Spawns.lobby.y");
int z = config.getInt("Spawns.lobby.z");
String world = this.config.getString("Floors." + id + ".p" + pos + ".world");
float yaw = (float) config.getInt("Spawns.lobby.yaw");
float pitch = (float) config.getInt("Spawns.lobby.pitch");
return new Location(Bukkit.getWorld(world), (double) x, (double) y, (double) z);
World world = Bukkit.getWorld(config.getString("Spawns.lobby.world"));
}
return new Location(world, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, yaw, pitch);
public int getCount() {
}
return config.getInt("Spawns.count");
}
}
public int getFloors() {
return config.getInt("Floors.count");
}
public int getSpawnCount() {
return this.spawncount;
}
public FileConfiguration getConfig() {
return this.config;
}
public void setConfig(FileConfiguration config) {
this.config = config;
}
// The world the map's terrain lives in, and therefore the template
// GameWorldManager copies per match. Spawns define it, then floors.
public String getTerrainWorldName() {
if (this.config.isString("Spawns.1.world")) {
return this.config.getString("Spawns.1.world");
}
if (this.config.isString("Floors.1.p1.world")) {
return this.config.getString("Floors.1.p1.world");
}
return null;
}
public String getWorldName() {
return this.getTerrainWorldName();
}
// Rebase helpers: return template-configured coordinates anchored to the
// supplied per-game world. Used by Game/GameManager so map locations point
// at the live per-game copy, not the template world.
public Location getSpawnIn(World world, int id) {
if (world == null)
return getSpawn(id);
int x = config.getInt("Spawns." + id + ".x");
int y = config.getInt("Spawns." + id + ".y");
int z = config.getInt("Spawns." + id + ".z");
float yaw = (float) config.getInt("Spawns." + id + ".yaw");
float pitch = (float) config.getInt("Spawns." + id + ".pitch");
return new Location(world, x + 0.5D, y + 0.5D, z + 0.5D, yaw, pitch);
}
public Location getFloorIn(World world, int id, String pos) {
if (world == null)
return getFloor(id, pos);
int x = config.getInt("Floors." + id + ".p" + pos + ".x");
int y = config.getInt("Floors." + id + ".p" + pos + ".y");
int z = config.getInt("Floors." + id + ".p" + pos + ".z");
return new Location(world, x, y, z);
}
public Location getSpecIn(World world) {
if (world == null)
return getSpawnSpec();
int x = config.getInt("Spec.x");
int y = config.getInt("Spec.y");
int z = config.getInt("Spec.z");
float yaw = (float) config.getInt("Spec.yaw");
float pitch = (float) config.getInt("Spec.pitch");
return new Location(world, x + 0.5D, y + 0.5D, z + 0.5D, yaw, pitch);
}
@SuppressWarnings("unused")
private static Game gameTypeReference() {
// Anchors the Game import for addSpawn(Game)/addFloor(Game) callers.
return null;
}
}
+91 -66
View File
@@ -2,126 +2,151 @@ package config;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import main.SpleggOG;
import managers.Game;
import managers.Status;
public class MapConfig {
public FileConfiguration maps;
public File f;
public FileConfiguration maps;
public File f;
public void setup() {
public void setup() {
this.f = new File(SpleggOG.getPlugin().getDataFolder(), "maps.yml");
try {
final File dataFolder = SpleggOG.getPlugin().getDataFolder();
if (!dataFolder.exists()) {
this.f.createNewFile();
dataFolder.mkdirs();
}
catch (IOException error) {
}
SpleggOG.getPlugin().getLogger().severe("An error occured while creating maps.yml.");
this.f = new File(dataFolder, "maps.yml");
if (!this.f.exists()) {
}
try {
this.loadMaps();
this.saveMaps();
this.f.createNewFile();
SpleggOG.getPlugin().maps.MAPS.clear();
} catch (IOException error) {
Iterator<?> enabledMapIterator = this.getEnabledMaps().iterator();
while(enabledMapIterator.hasNext()) {
SpleggOG.getPlugin().getLogger()
.severe("An error occured while creating maps.yml: " + error.getMessage());
String maps = (String) enabledMapIterator.next();
SpleggOG.getPlugin().maps.addMap(maps);
}
Map map = SpleggOG.getPlugin().maps.getMap(maps);
Game game = new Game(SpleggOG.getPlugin(), map);
SpleggOG.getPlugin().games.addGame(map.getName(), game);
}
if (! map.isUsable(map)) {
this.loadMaps();
this.saveMaps();
game.setStatus(Status.DISABLED);
SpleggOG.getPlugin().maps.MAPS.clear();
}
Iterator<?> enabledMapIterator = this.getEnabledMaps().iterator();
while (enabledMapIterator.hasNext()) {
}
String maps = (String) enabledMapIterator.next();
SpleggOG.getPlugin().maps.addMap(maps);
// Maps are only templates now: a lobby copies one per match after the
// vote, so no Game is tied to a map at boot.
Map map = SpleggOG.getPlugin().maps.getMap(maps);
map.isUsable(map); // populates the usable flag for status displays
}
}
private void loadMaps() {
}
this.maps = YamlConfiguration.loadConfiguration(this.f);
private void loadMaps() {
}
this.maps = YamlConfiguration.loadConfiguration(this.f);
public void saveMaps() {
}
try {
public void saveMaps() {
this.maps.save(this.f);
try {
}
catch (IOException error) {
this.maps.save(this.f);
SpleggOG.getPlugin().getLogger().severe("An error occured while saving maps.yml.");
} catch (IOException error) {
}
SpleggOG.getPlugin().getLogger().severe("An error occured while saving maps.yml.");
}
}
public List<String> getEnabledMaps() {
}
return this.maps.getStringList("maps");
public List<String> getEnabledMaps() {
}
return this.maps.getStringList("maps");
public void addSign(String map, String loc) {
}
List<String> signs = this.maps.getStringList("Signs." + map + ".lobby");
signs.add(loc);
// Every key signs are stored under: lobby ids, "any", and map names left by
// older versions (which now behave as "any").
public List<String> getSignKeys() {
this.maps.set("Signs." + map + ".lobby", signs);
this.saveMaps();
final ConfigurationSection section = this.maps.getConfigurationSection("Signs");
return section == null ? new ArrayList<>() : new ArrayList<>(section.getKeys(false));
}
}
public void delSign(String map, String loc) {
public List<String> getSigns(String key) {
List<String> signs = this.maps.getStringList("Signs." + map + ".lobby");
signs.remove(loc);
return this.maps.getStringList("Signs." + key + ".lobby");
this.maps.set("Signs." + map + ".lobby", signs);
this.saveMaps();
}
}
public void addSign(String key, String loc) {
public void addMap(String name) {
List<String> signs = this.maps.getStringList("Signs." + key + ".lobby");
signs.add(loc);
List<String> maps = this.maps.getStringList("maps");
maps.add(name);
this.maps.set("Signs." + key + ".lobby", signs);
this.saveMaps();
this.maps.set("maps", maps);
this.saveMaps();
}
}
public void delSign(String key, String loc) {
public void removeMap(String name) {
List<String> signs = this.maps.getStringList("Signs." + key + ".lobby");
signs.remove(loc);
List<String> maps = this.maps.getStringList("maps");
maps.remove(name);
this.maps.set("Signs." + key + ".lobby", signs.isEmpty() ? null : signs);
if (signs.isEmpty()) {
this.maps.set("maps", maps);
this.saveMaps();
this.maps.set("Signs." + key, null);
}
}
}
this.saveMaps();
}
public void addMap(String name) {
List<String> maps = this.maps.getStringList("maps");
maps.add(name);
this.maps.set("maps", maps);
this.saveMaps();
}
public void removeMap(String name) {
List<String> maps = this.maps.getStringList("maps");
maps.remove(name);
this.maps.set("maps", maps);
this.saveMaps();
}
}
+46 -47
View File
@@ -1,84 +1,83 @@
package config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import main.SpleggOG;
import managers.Game;
import managers.Status;
public class MapUtilities {
public MapConfig c = new MapConfig();
public HashMap<String, Map> MAPS = new HashMap<String, Map>();
public MapConfig c = new MapConfig();
public HashMap<String, Map> MAPS = new HashMap<String, Map>();
public void addMap(String name) {
public void addMap(String name) {
Map map = new Map(SpleggOG.getPlugin(), name);
this.MAPS.put(name, map);
map.load();
Map map = new Map(SpleggOG.getPlugin(), name);
this.MAPS.put(name, map);
map.load();
}
}
public void deleteMap(String name) {
public void deleteMap(String name) {
Map m = this.getMap(name);
Game game = SpleggOG.getPlugin().games.getGame(name);
if (game.getStatus() == Status.INGAME) {
Map m = this.getMap(name);
// Stop every active game for this map -- N concurrent games allowed.
for (Game game : new ArrayList<>(SpleggOG.getPlugin().games.gamesForMap(name)))
SpleggOG.getPlugin().game.stopGame(game, 0);
SpleggOG.getPlugin().game.stopGame(game, 0);
this.MAPS.remove(name, m);
this.c.removeMap(name);
m.delete();
}
}
SpleggOG.getPlugin().games.GAMES.remove(m.getName());
public boolean mapExists(String name) {
this.MAPS.remove(name, m);
this.c.removeMap(name);
m.delete();
return this.MAPS.containsKey(name);
}
}
public boolean mapExists(String name) {
public Collection<Map> getMaps() {
return this.MAPS.containsKey(name);
return this.MAPS.values();
}
}
public Collection<Map> getMaps() {
public Map getMap(String name) {
return this.MAPS.values();
return (Map) this.MAPS.get(name);
}
}
public Map getMap(String name) {
// Returns a random usable (playable) map in LOBBY status, or null when none
// qualify.
// Used by /splegg random and will back in-lobby map voting once that ships.
public Map getRandomMap() {
return (Map) this.MAPS.get(name);
// With on-demand games, any usable map is a valid candidate -- a new
// game can always be spun up when none are joinable.
final ArrayList<Map> candidates = new ArrayList<>();
final Iterator<Map> mapIterator = this.MAPS.values().iterator();
while (mapIterator.hasNext()) {
}
final Map map = mapIterator.next();
if (map.isUsable(map))
candidates.add(map);
// TODO: Use getRandomMap in map voting as an option.
/*private Map getRandomMap() {
}
ArrayList<Map> all = new ArrayList<Map>();
Iterator<?> var3 = this.MAPS.values().iterator();
if (candidates.isEmpty()) {
while(var3.hasNext()) {
return null;
Map map = (Map)var3.next();
}
if (map.isUsable()) {
return candidates.get(new Random().nextInt(candidates.size()));
all.add(map);
}
}
Object[] mapsa = all.toArray();
Map randomMap = (Map) mapsa[(new Random()).nextInt(mapsa.length)];
return randomMap;
}*/
}
}
+413 -132
View File
@@ -1,7 +1,12 @@
package events;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
@@ -10,217 +15,493 @@ import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import com.destroystokyo.paper.event.player.PlayerAdvancementCriterionGrantEvent;
import io.papermc.paper.event.player.AsyncChatEvent;
import gui.SpleggShopGUI;
import main.SpleggOG;
import net.milkbowl.vault.economy.EconomyResponse;
import managers.Game;
import net.trueog.diamondbankog.DiamondBankException.EconomyDisabledException;
import net.trueog.diamondbankog.DiamondBankException.InsufficientFundsException;
import net.trueog.diamondbankog.DiamondBankException.InvalidPlayerException;
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
import net.trueog.utilitiesog.UtilitiesOG;
import utils.UtilPlayer;
import utils.Utils;
public class Listeners implements Listener {
public static ArrayList<String> manager = new ArrayList<String>();
public static ArrayList<String> moneymanager = new ArrayList<String>();
public static ArrayList<String> shopmanager = new ArrayList<String>();
public static ArrayList<String> diamondspade = new ArrayList<String>();
public static ArrayList<String> goldspade = new ArrayList<String>();
public static ArrayList<String> launchEggs = new ArrayList<String>();
public static final Set<UUID> manager = ConcurrentHashMap.newKeySet();
public static final Set<UUID> moneymanager = ConcurrentHashMap.newKeySet();
public static final Set<UUID> shopmanager = ConcurrentHashMap.newKeySet();
public static final Set<UUID> woodspade = ConcurrentHashMap.newKeySet();
public static final Set<UUID> stonespade = ConcurrentHashMap.newKeySet();
public static final Set<UUID> diamondspade = ConcurrentHashMap.newKeySet();
public static final Set<UUID> goldspade = ConcurrentHashMap.newKeySet();
public static final Set<UUID> netheritespade = ConcurrentHashMap.newKeySet();
public static final Set<UUID> launchEggs = ConcurrentHashMap.newKeySet();
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
public Listeners(DiamondBankAPIJava diamondBankAPI) {
Player player = (Player) event.getWhoClicked();
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
ItemStack stack = event.getCurrentItem();
if (event.getView().title().toString().equals(SpleggOG.getPlugin().getConfig().getString("GUI.Shop.Title"))) {
// Constructor retained for compatibility with existing registration call sites.
if (shopmanager.contains(player.getName())) {
}
EconomyResponse r;
Material selectedType;
try {
public static void clearAll() {
selectedType = stack.getType();
manager.clear();
moneymanager.clear();
shopmanager.clear();
woodspade.clear();
stonespade.clear();
goldspade.clear();
diamondspade.clear();
netheritespade.clear();
launchEggs.clear();
}
catch(NullPointerException error) {
}
selectedType = null;
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
}
if (!(event.getWhoClicked() instanceof Player player)) {
if (selectedType == Material.GOLDEN_SHOVEL) {
return;
PlayerInventory inventory = player.getInventory();
inventory.close();
}
if (SpleggOG.getPlugin().econ.getBalance(player) >= (double) SpleggOG.getPlugin().getConfig().getInt("GUI.Shop.GoldShovel.Price")) {
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
r = SpleggOG.getPlugin().econ.withdrawPlayer(player, (double) SpleggOG.getPlugin().getConfig().getInt("GUI.Shop.GoldShovel.Price"));
if (r.transactionSuccess()) {
return;
goldspade.add(player.getName());
diamondspade.remove(player.getName());
shopmanager.remove(player.getName());
manager.remove(player.getName());
}
SpleggOG.getPlugin().chat.sendMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.BuyGoldShovel").replaceAll("&", "§"));
final UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u != null && u.getGame() != null && u.isAlive()) {
}
else {
event.setCancelled(true);
player.sendMessage(String.format("An error occured: %s", r.errorMessage));
}
shopmanager.add(player.getName());
manager.add(player.getName());
goldspade.remove(player.getName());
diamondspade.remove(player.getName());
}
}
public static boolean handleShovelSelection(Player player, String configPath, String successMessagePath,
Set<UUID> selectedShovelList)
{
}
else {
final UUID playerId = player.getUniqueId();
if (!shopmanager.contains(playerId)) {
inventory.close();
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.Haveyoueverbought"));
SpleggOG.getPlugin().chat.sendMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.NoEnoughMoney").replaceAll("&", "§"));
return false;
}
}
}
final int priceInDiamonds = SpleggOG.getPlugin().getConfig().getInt(configPath + ".Price");
if (priceInDiamonds <= 0) {
if (selectedType == Material.DIAMOND_SHOVEL) {
selectPurchasedShovel(playerId, selectedShovelList);
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString(successMessagePath));
player.closeInventory();
PlayerInventory inventory = player.getInventory();
inventory.close();
return true;
if (SpleggOG.getPlugin().econ.getBalance(player) >= (double) SpleggOG.getPlugin().getConfig().getInt("GUI.Shop.DiamondShovel.Price")) {
}
r = SpleggOG.getPlugin().econ.withdrawPlayer(player, (double) SpleggOG.getPlugin().getConfig().getInt("GUI.Shop.DiamondShovel.Price"));
if (r.transactionSuccess()) {
// No pre-check: consumeFromPlayer refuses an unaffordable purchase itself,
// and every DiamondBank call blocks the main thread.
final DiamondBankAPIJava diamondBankAPI = SpleggOG.getPlugin().getDiamondBankAPI();
if (diamondBankAPI == null) {
goldspade.remove(player.getName());
diamondspade.add(player.getName());
shopmanager.remove(player.getName());
manager.remove(player.getName());
UtilitiesOG.trueogMessage(player, "&cERROR: The Diamond economy is currently unavailable.");
SpleggOG.getPlugin().chat.sendMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.BuyDiamondShovel").replaceAll("&", "§"));
return false;
}
else {
}
player.sendMessage(String.format("An error occured: %s", r.errorMessage));
final long priceInShards = diamondBankAPI.diamondsToShards((float) priceInDiamonds);
shopmanager.add(player.getName());
manager.add(player.getName());
goldspade.remove(player.getName());
diamondspade.remove(player.getName());
try {
}
diamondBankAPI.consumeFromPlayer(playerId, priceInShards,
"Player " + player.getName() + " purchased a " + currentShovelName(configPath) + " in Splegg.",
"Plugin: Splegg-OG");
}
else {
selectPurchasedShovel(playerId, selectedShovelList);
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString(successMessagePath));
player.closeInventory();
return true;
inventory.close();
} catch (InsufficientFundsException insufficientFundsException) {
SpleggOG.getPlugin().chat.sendMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.NoEnoughMoney").replaceAll("&", "§"));
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.NoEnoughMoney"));
return false;
}
} catch (EconomyDisabledException economyDisabledException) {
}
restoreDefaultLobbyState(playerId);
UtilitiesOG.trueogMessage(player, "&cERROR: The Diamond economy is currently unavailable.");
return false;
}
else {
} catch (InvalidPlayerException playerException) {
PlayerInventory inventory = player.getInventory();
inventory.close();
restoreDefaultLobbyState(playerId);
UtilitiesOG.trueogMessage(player,
"&cERROR: Your player account could not be found. Contact an administrator.");
return false;
SpleggOG.getPlugin().chat.sendMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.Haveyoueverbought").replaceAll("&", "§"));
}
}
}
}
public static boolean handleIronSelection(Player player) {
if (u.getGame() != null && u.isAlive()) {
restoreDefaultLobbyState(player.getUniqueId());
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.BuyIronShovel"));
player.closeInventory();
event.setCancelled(true);
return true;
}
}
}
public static boolean isShovelAffordable(Player player, String configPath) {
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if(event.getHand().equals(EquipmentSlot.HAND)) {
Player player = (Player) event.getPlayer();
if (launchEggs.contains(player.getName())) {
final int priceInDiamonds = SpleggOG.getPlugin().getConfig().getInt(configPath + ".Price");
if (priceInDiamonds <= 0) {
// Fire an egg.
Utils.fireEgg(event);
return true;
}
}
}
}
final DiamondBankAPIJava diamondBankAPI = SpleggOG.getPlugin().getDiamondBankAPI();
if (diamondBankAPI == null) {
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
return false;
Player player = event.getPlayer();
}
manager.remove(player.getName());
shopmanager.remove(player.getName());
goldspade.remove(player.getName());
diamondspade.remove(player.getName());
launchEggs.remove(player.getName());
moneymanager.remove(player.getName());
try {
}
final long priceInShards = diamondBankAPI.diamondsToShards((float) priceInDiamonds);
final long availableShards = diamondBankAPI.getBankShards(player.getUniqueId())
+ diamondBankAPI.getInventoryShards(player.getUniqueId());
@EventHandler
public void onPlayerPickupItem(EntityPickupItemEvent event) {
return availableShards >= priceInShards;
if(event.getEntityType() == EntityType.PLAYER) {
} catch (EconomyDisabledException economyDisabledException) {
Player player = (Player) event.getEntity();
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
return false;
event.setCancelled(true);
}
}
}
}
public static boolean canPurchasePremiumShovel(UUID playerId) {
}
return shopmanager.contains(playerId);
@EventHandler
public void onPlayerAdvancement(PlayerAdvancementCriterionGrantEvent event) {
}
Player player = event.getPlayer();
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if(u.getGame() != null && u.isAlive()) {
// Balance lookups block, so they run off the main thread and the GUI opens once
// they are in.
public static void openShop(Player player) {
event.setCancelled(true);
Bukkit.getScheduler().runTaskAsynchronously(SpleggOG.getPlugin(), () -> {
}
final Map<String, Boolean> affordable = new HashMap<>();
for (String configPath : SpleggShopGUI.SHOVEL_CONFIG_PATHS) {
}
affordable.put(configPath, isShovelAffordable(player, configPath));
@EventHandler
public void onAsyncPlayerChat(AsyncChatEvent event) {
}
// TODO: Direct chat to world-specific channel here.
Bukkit.getScheduler().runTask(SpleggOG.getPlugin(), () -> {
}
final UtilPlayer tracked = SpleggOG.getPlugin().pm.getPlayer(player);
if (!player.isOnline() || tracked == null || tracked.getGame() == null) {
}
return;
}
new SpleggShopGUI(player, affordable).open(true);
});
});
}
private static void selectPurchasedShovel(UUID playerId, Set<UUID> selectedShovelList) {
clearSelectedShovels(playerId);
manager.remove(playerId);
shopmanager.remove(playerId);
selectedShovelList.add(playerId);
}
private static void clearSelectedShovels(UUID playerId) {
woodspade.remove(playerId);
stonespade.remove(playerId);
goldspade.remove(playerId);
diamondspade.remove(playerId);
netheritespade.remove(playerId);
}
private static void restoreDefaultLobbyState(UUID playerId) {
clearSelectedShovels(playerId);
manager.add(playerId);
shopmanager.add(playerId);
}
private static String currentShovelName(String configPath) {
return SpleggOG.getPlugin().getConfig().getString(configPath + ".Name").replaceAll("&[0-9A-FK-ORa-fk-or]", "");
}
public static Material getSelectedShovelMaterial(UUID playerId) {
if (woodspade.contains(playerId)) {
return Material.WOODEN_SHOVEL;
}
if (stonespade.contains(playerId)) {
return Material.STONE_SHOVEL;
}
if (goldspade.contains(playerId)) {
return Material.GOLDEN_SHOVEL;
}
if (diamondspade.contains(playerId)) {
return Material.DIAMOND_SHOVEL;
}
if (netheritespade.contains(playerId)) {
return Material.NETHERITE_SHOVEL;
}
return Material.IRON_SHOVEL;
}
public static String getSelectedShovelConfigPath(UUID playerId) {
if (woodspade.contains(playerId)) {
return "Shovels.Wood";
}
if (stonespade.contains(playerId)) {
return "Shovels.Stone";
}
if (goldspade.contains(playerId)) {
return "Shovels.Gold";
}
if (diamondspade.contains(playerId)) {
return "Shovels.Diamond";
}
if (netheritespade.contains(playerId)) {
return "Shovels.Netherite";
}
return "Shovels.Iron";
}
public static void finalizePreGameShovelState(UUID playerId) {
manager.remove(playerId);
shopmanager.remove(playerId);
clearSelectedShovels(playerId);
moneymanager.add(playerId);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent playerQuitEvent) {
handlePlayerExit(playerQuitEvent.getPlayer());
}
// A cancelled kick (anti-cheat setback, staff tooling) must not eject the
// player from their lobby.
@EventHandler(ignoreCancelled = true)
public void onPlayerKick(PlayerKickEvent playerKickEvent) {
handlePlayerExit(playerKickEvent.getPlayer());
}
@EventHandler(ignoreCancelled = true)
public void onWorldUnload(WorldUnloadEvent worldUnloadEvent) {
final String worldName = worldUnloadEvent.getWorld().getName();
if (!SpleggOG.getPlugin().isSpleggWorld(worldName))
return;
final java.util.List<Game> affected = new java.util.ArrayList<>();
for (Game g : SpleggOG.getPlugin().games.GAMES.values()) {
// Skip lobbies already shutting down -- our own cleanup is what
// triggered this unload.
if (g.getStatus() == managers.Status.ENDING)
continue;
final String mapWorld = g.getMap() == null ? null : g.getMap().getWorldName();
if ((mapWorld != null && mapWorld.equals(worldName)) || g.ownsWorld(worldName))
affected.add(g);
}
for (Game g : affected)
SpleggOG.getPlugin().game.stopGame(g, g.getPlayers().size());
}
private void handlePlayerExit(Player player) {
if (player == null)
return;
final UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u != null && u.getGame() != null)
u.getGame().leaveGame(u);
// The wrapper holds a dead Player reference from here on; a relog creates a
// fresh one.
SpleggOG.getPlugin().pm.untrack(player);
}
@EventHandler
public void onPlayerPickupItem(EntityPickupItemEvent entityPickupItemEvent) {
if (entityPickupItemEvent.getEntityType() != EntityType.PLAYER) {
return;
}
final Player player = (Player) entityPickupItemEvent.getEntity();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
return;
}
final UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
entityPickupItemEvent.setCancelled(true);
}
}
@EventHandler
public void onPlayerAdvancement(PlayerAdvancementCriterionGrantEvent playerAdvancementCriterionGrantEvent) {
final Player player = playerAdvancementCriterionGrantEvent.getPlayer();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
return;
}
final UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
playerAdvancementCriterionGrantEvent.setCancelled(true);
}
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent playerInteractEvent) {
final Player player = playerInteractEvent.getPlayer();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
return;
}
final UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
final Game game = u.getGame();
if (!(game != null && Listeners.launchEggs.contains(player.getUniqueId()))) {
return;
}
final EquipmentSlot playerHand = playerInteractEvent.getHand();
if (playerHand != EquipmentSlot.HAND) {
return;
}
final ItemStack itemInHand = player.getInventory().getItemInMainHand();
switch (itemInHand.getType()) {
case WOODEN_SHOVEL, STONE_SHOVEL, IRON_SHOVEL, GOLDEN_SHOVEL, DIAMOND_SHOVEL, NETHERITE_SHOVEL ->
Utils.fireEgg(playerInteractEvent, u, player, itemInHand);
case SLIME_BALL -> game.leaveGame(u);
default -> {
if (itemInHand.getType() == Material
.getMaterial(SpleggOG.getPlugin().getConfig().getString("Shop.Item")))
{
openShop(player);
}
}
}
}
// Chat scoping now belongs to Chat-OG, which routes per world and mirrors to
// the Splegg Discord
// channel. Filtering viewers() here never worked anyway, because Chat-OG
// cancels AsyncChatEvent
// and builds its own recipient set.
}
+49 -25
View File
@@ -12,43 +12,67 @@ import utils.UtilPlayer;
public class MapListener implements Listener {
@EventHandler
public void blockBreak(BlockBreakEvent e) {
@EventHandler
public void blockBreak(BlockBreakEvent event) {
Player player = e.getPlayer();
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
Player player = event.getPlayer();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
e.setCancelled(true);
return;
}
}
}
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
@EventHandler
public void blockPlace(BlockPlaceEvent e) {
event.setCancelled(true);
Player player = e.getPlayer();
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
}
e.setCancelled(true);
}
}
@EventHandler
public void blockPlace(BlockPlaceEvent event) {
}
Player player = event.getPlayer();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
@EventHandler
public void hangingEntityBreak(HangingBreakByEntityEvent e) {
return;
Player player = (Player)e.getRemover();
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
}
e.setCancelled(true);
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
}
event.setCancelled(true);
}
}
}
}
@EventHandler
public void hangingEntityBreak(HangingBreakByEntityEvent event) {
if (!(event.getRemover() instanceof Player)) {
return;
}
Player player = (Player) event.getRemover();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
return;
}
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
event.setCancelled(true);
}
}
}
+99 -62
View File
@@ -1,6 +1,6 @@
package events;
import org.bukkit.entity.Entity;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@@ -9,108 +9,145 @@ import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import main.SpleggOG;
import managers.Game;
import utils.UtilPlayer;
import utils.Utils;
public class PlayerListener implements Listener {
String[] cmds = new String[]{""};
String[] cmds = new String[] { "" };
@EventHandler
public void onFood(FoodLevelChangeEvent e) {
private UtilPlayer getTrackedPlayer(Player player) {
if (e.getEntity() instanceof Player) {
return SpleggOG.getPlugin().pm.track(player);
Player player = (Player) e.getEntity();
UtilPlayer u = new UtilPlayer(player);
if (u.getGame() != null && u.isAlive()) {
}
e.setCancelled(true);
e.setFoodLevel(20);
@EventHandler
public void onFood(FoodLevelChangeEvent event) {
}
if (!(event.getEntity() instanceof Player)) {
}
return;
}
}
@EventHandler
public void entityDamage(EntityDamageEvent e) {
final Player player = (Player) event.getEntity();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
Entity ent = e.getEntity();
if (ent instanceof Player) {
return;
Player player = (Player) ent;
UtilPlayer u = new UtilPlayer(player);
if (u.getGame() != null && u.isAlive()) {
}
e.setCancelled(true);
final UtilPlayer u = getTrackedPlayer(player);
if (!(u.getGame() != null && u.isAlive())) {
}
return;
}
}
}
event.setCancelled(true);
event.setFoodLevel(20);
@EventHandler
public void onJoin(PlayerJoinEvent e) {
}
Player player = e.getPlayer();
UtilPlayer u = new UtilPlayer(player);
@EventHandler
public void entityDamage(EntityDamageEvent event) {
SpleggOG.getPlugin().pm.PLAYERS.put(player.getName(), u);
if (!(event.getEntity() instanceof Player)) {
}
return;
@EventHandler
public void onQuit(PlayerQuitEvent e) {
}
Player player = e.getPlayer();
UtilPlayer u = new UtilPlayer(player);
if (u.getGame() != null && u.isAlive()) {
final Player player = (Player) event.getEntity();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
// TODO: Remove dev logger
SpleggOG.getPlugin().getLogger().info("Player leaving game onQuit: " + u.getGame() + " with map " + u.getGame().getMap().getName());
return;
Game game = u.getGame();
game.leaveGame(u);
}
}
final UtilPlayer u = getTrackedPlayer(player);
if (u.getGame() != null && u.isAlive()) {
SpleggOG.getPlugin().pm.PLAYERS.remove(player.getName());
event.setCancelled(true);
}
}
@EventHandler
public void dropItem(PlayerDropItemEvent e) {
}
Player player = e.getPlayer();
UtilPlayer u = new UtilPlayer(player);
// A fresh wrapper per login; the reconnect teleport lives in
// PreJoinLocationListener.
@EventHandler
public void onJoin(PlayerJoinEvent event) {
if (u.getGame() != null && u.isAlive()) {
SpleggOG.getPlugin().pm.untrack(event.getPlayer());
SpleggOG.getPlugin().pm.track(event.getPlayer());
e.setCancelled(true);
}
}
@EventHandler
public void dropItem(PlayerDropItemEvent event) {
}
final Player player = event.getPlayer();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent e) {
return;
Player player = e.getPlayer();
UtilPlayer u = new UtilPlayer(player);
if (u.getGame() != null && u.isAlive() && ! e.getMessage().startsWith("/splegg") && ! player.hasPermission("splegg.admin")) {
}
e.setCancelled(true);
final UtilPlayer u = getTrackedPlayer(player);
SpleggOG.getPlugin().chat.sendMessage(player, "&6You cannot use that command in &3Splegg&6!");
if (u.getGame() != null && u.isAlive()) {
}
event.setCancelled(true);
}
}
}
}
// ignoreCancelled: HubCommandListener and VoteCommandListener claim their
// commands at LOWEST, and a claimed command must not also be reported as
// blocked.
@EventHandler(ignoreCancelled = true)
public void onCommand(PlayerCommandPreprocessEvent event) {
final Player player = event.getPlayer();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
return;
}
final UtilPlayer u = getTrackedPlayer(player);
if (!(u.getGame() != null && u.isAlive() && !isAllowedGameCommand(event.getMessage())
&& !player.hasPermission("splegg.admin")))
{
return;
}
event.setCancelled(true);
Utils.spleggOGMessage(player, "&6You cannot use that command in &3Splegg&6!");
}
private boolean isAllowedGameCommand(String message) {
return isCommand(message, "/splegg") || isCommand(message, "/sp") || isCommand(message, "/spjoin")
|| isCommand(message, "/hub") || isCommand(message, "/lobby") || isCommand(message, "/spawn")
|| isCommand(message, "/vote") || isCommand(message, "/v");
}
private boolean isCommand(String message, String command) {
return StringUtils.equalsIgnoreCase(message, command)
|| StringUtils.startsWithIgnoreCase(message, command + " ");
}
}
@@ -0,0 +1,160 @@
package events;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import main.SpleggOG;
// Players reach Splegg territory by /mw tp, portals and other plugins too. Every
// route records a return spot so nobody lands at main spawn afterwards.
public class PreJoinLocationListener implements Listener {
private final SpleggOG plugin;
public PreJoinLocationListener(SpleggOG plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTeleport(PlayerTeleportEvent event) {
Location from = event.getFrom();
Location to = event.getTo();
if (from == null || to == null || from.getWorld() == null || to.getWorld() == null)
return;
if (from.getWorld().equals(to.getWorld()))
return;
boolean fromManaged = plugin.isSpleggTerritory(from.getWorld().getName());
boolean toManaged = plugin.isSpleggTerritory(to.getWorld().getName());
if (toManaged && !fromManaged) {
plugin.savePreJoinLocation(event.getPlayer().getUniqueId(), from);
// Before the world change: MyWorlds' gamemode restore on entry and every
// adventure flip Splegg makes must run without GameModeInventories.
plugin.getGmiGuard().suspend(event.getPlayer());
return;
}
// Left Splegg territory for a real world by any route, so the recorded spot
// has served its purpose.
if (fromManaged && !toManaged) {
plugin.removePreJoinLocation(event.getPlayer().getUniqueId());
// Stays attached through this teleport's world change (MyWorlds restores
// the real inventory and then the saved survival gamemode there).
plugin.getGmiGuard().suspend(event.getPlayer());
plugin.getGmiGuard().releaseAfterLeaving(event.getPlayer());
}
}
// A respawn is a world change without a teleport event, so the suspension
// is handled here for both directions.
@EventHandler(priority = EventPriority.MONITOR)
public void onRespawnMonitor(PlayerRespawnEvent event) {
Player player = event.getPlayer();
Location respawn = event.getRespawnLocation();
if (respawn == null || respawn.getWorld() == null)
return;
boolean fromManaged = player.getWorld() != null && plugin.isSpleggTerritory(player.getWorld().getName());
boolean toManaged = plugin.isSpleggTerritory(respawn.getWorld().getName());
if (toManaged) {
plugin.getGmiGuard().suspend(player);
return;
}
if (fromManaged) {
plugin.getGmiGuard().suspend(player);
plugin.getGmiGuard().releaseAfterLeaving(player);
}
}
// A login inside Splegg territory: MyWorlds forces the world's gamemode at
// join, and the return teleport follows a tick later.
@EventHandler(priority = EventPriority.MONITOR)
public void onLogin(PlayerLoginEvent event) {
if (event.getResult() != PlayerLoginEvent.Result.ALLOWED)
return;
Player player = event.getPlayer();
if (player.getWorld() != null && plugin.isSpleggTerritory(player.getWorld().getName()))
plugin.getGmiGuard().suspend(player);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onQuit(PlayerQuitEvent event) {
plugin.getGmiGuard().release(event.getPlayer());
}
// A player still on the death screen when their match world is deleted would
// otherwise respawn at main world spawn.
@EventHandler(priority = EventPriority.HIGHEST)
public void onRespawn(PlayerRespawnEvent event) {
Player player = event.getPlayer();
if (!plugin.hasPreJoinLocation(player.getUniqueId()))
return;
Location respawn = event.getRespawnLocation();
if (respawn != null && respawn.getWorld() != null && plugin.isSpleggTerritory(respawn.getWorld().getName()))
return;
Location saved = plugin.resolvePreJoinLocation(player.getUniqueId());
if (saved == null)
return;
event.setRespawnLocation(saved);
plugin.removePreJoinLocation(player.getUniqueId());
}
// A relog inside Splegg territory, or after the world the player quit in was
// deleted, goes back to the recorded spot; the main spawn is the fallback.
@EventHandler(priority = EventPriority.MONITOR)
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
boolean inTerritory = player.getWorld() != null && plugin.isSpleggTerritory(player.getWorld().getName());
if (!inTerritory && !plugin.hasPreJoinLocation(player.getUniqueId()))
return;
Bukkit.getScheduler().runTaskLater(plugin, () -> {
if (!player.isOnline())
return;
if (!plugin.returnPlayer(player, inTerritory))
plugin.getLogger().warning("Could not return " + player.getName() + " after a relog.");
}, 1L);
}
}
+134 -76
View File
@@ -1,11 +1,8 @@
package events;
import java.util.ArrayList;
import java.util.Collection;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Sign;
import org.bukkit.Tag;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
@@ -16,117 +13,178 @@ import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import main.SpleggOG;
import managers.Game;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import signs.LobbySign;
import signs.LobbySignUtils;
import utils.Utils;
import utils.UtilPlayer;
public class SignListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST)
public void signPlace(SignChangeEvent e) {
private static boolean isSign(Material material) {
Player player = e.getPlayer();
if (e.line(0).toString().equalsIgnoreCase("[splegg]") && e.line(1).toString().equalsIgnoreCase("join") && player.hasPermission("splegg.admin")) {
return Tag.SIGNS.isTagged(material) || Tag.WALL_SIGNS.isTagged(material);
String map = e.line(2).toString();
if (SpleggOG.getPlugin().maps.mapExists(map)) {
}
LobbySign ls = new LobbySign(SpleggOG.getPlugin().maps.getMap(map), SpleggOG.getPlugin());
ls.create(e.getBlock().getLocation(), SpleggOG.getPlugin().maps.getMap(map));
// Resolves the stored key a registered join sign was saved under, or null when
// the location is not a registered sign. Signs are recognized by location,
// not by their rendered text, so reformatting a sign cannot orphan it.
private static String storedKey(Location location) {
SpleggOG.getPlugin().chat.sendMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.CreateSign").replaceAll("&", "§").replaceAll("%map%", map));
for (String key : SpleggOG.getPlugin().maps.c.getSignKeys()) {
}
if (LobbySignUtils.get().isLobbySign(location, key)) {
}
return key;
}
}
private static Collection<Material> signMaterials = new ArrayList<>();
static {
}
signMaterials.add(Material.ACACIA_SIGN);
signMaterials.add(Material.DARK_OAK_SIGN);
signMaterials.add(Material.OAK_SIGN);
signMaterials.add(Material.BIRCH_SIGN);
signMaterials.add(Material.SPRUCE_SIGN);
signMaterials.add(Material.JUNGLE_SIGN);
signMaterials.add(Material.CRIMSON_SIGN);
signMaterials.add(Material.WARPED_SIGN);
signMaterials.add(Material.ACACIA_WALL_SIGN);
signMaterials.add(Material.DARK_OAK_WALL_SIGN);
signMaterials.add(Material.OAK_WALL_SIGN);
signMaterials.add(Material.BIRCH_WALL_SIGN);
signMaterials.add(Material.SPRUCE_WALL_SIGN);
signMaterials.add(Material.JUNGLE_WALL_SIGN);
signMaterials.add(Material.CRIMSON_WALL_SIGN);
signMaterials.add(Material.WARPED_WALL_SIGN);
return null;
}
}
@EventHandler
public void onInteract(PlayerInteractEvent e) {
@EventHandler(priority = EventPriority.HIGHEST)
public void signPlace(SignChangeEvent event) {
if (e.hasBlock() && signMaterials.contains(e.getClickedBlock().getType()) && e.getAction() == Action.RIGHT_CLICK_BLOCK) {
Player player = event.getPlayer();
final String header = PlainTextComponentSerializer.plainText().serialize(event.line(0)).trim();
Sign s = (Sign)e.getClickedBlock().getState();
Player player = e.getPlayer();
if (s.line(0).toString().equalsIgnoreCase(SpleggOG.getPlugin().getConfig().getString("Sings.Format.1").replaceAll("&", "§"))) {
if (!header.equalsIgnoreCase("[Splegg]"))
return;
String map = ChatColor.stripColor(s.line(2).toString());
if (!player.hasPermission("splegg.admin")) {
if (SpleggOG.getPlugin().maps.mapExists(map)) {
Utils.spleggOGMessage(player, "&cYou don't have permission to create Splegg signs.");
event.setCancelled(true);
return;
player.chat("/splegg join " + map);
player.updateInventory();
}
e.setCancelled(true);
final String target = PlainTextComponentSerializer.plainText().serialize(event.line(1)).trim();
final String key;
if (target.isEmpty() || target.equalsIgnoreCase(LobbySign.ANY) || target.equalsIgnoreCase("join")) {
}
else {
key = LobbySign.ANY;
SpleggOG.getPlugin().chat.sendMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.Mapnotexist").replaceAll("&", "§"));
} else {
e.setCancelled(true);
final Game game = SpleggOG.getPlugin().games.resolveLobby(target);
if (game == null) {
}
Utils.spleggOGMessage(player, "&cLobby '" + target
+ "' does not exist. Use a lobby id such as SP1, or leave line 2 blank for any lobby.");
event.setCancelled(true);
return;
}
}
}
key = game.getLobbyId();
}
}
@EventHandler
public void signBreak(BlockBreakEvent e) {
new LobbySign(key, SpleggOG.getPlugin()).create(event.getBlock().getLocation());
Player player = e.getPlayer();
if(signMaterials.contains(e.getBlock().getType())) {
// Provisional lines; the sign updater redraws with live data within a
// second.
event.line(0, Component.text("§4Splegg"));
event.line(1, Component.text("§6" + (LobbySign.ANY.equals(key) ? "Any" : key)));
event.line(2, Component.text("§7Loading..."));
event.line(3, Component.text(""));
Sign s = (Sign) e.getBlock().getState();
String[] lines = (String[]) s.lines().toArray();
String map = ChatColor.stripColor(lines[1]);
if (LobbySignUtils.get().isLobbySign(e.getBlock().getLocation(), map)) {
Utils.spleggOGMessage(player,
SpleggOG.getPlugin().getConfig().getString("Messages.CreateSign").replaceAll("%map%", key));
if (player.hasPermission("splegg.admin")) {
}
LobbySign sign = new LobbySign(SpleggOG.getPlugin().maps.getMap(map), SpleggOG.getPlugin());
sign.delete(e.getBlock().getLocation());
@EventHandler(ignoreCancelled = true)
public void onInteract(PlayerInteractEvent e) {
SpleggOG.getPlugin().chat.sendMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.RemovedSign").replaceAll("&", "§").replaceAll("%map%", map));
if (!e.hasBlock() || e.getAction() != Action.RIGHT_CLICK_BLOCK || !isSign(e.getClickedBlock().getType())) {
}
else {
return;
e.setCancelled(true);
}
SpleggOG.getPlugin().chat.sendMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.NotBreakSign").replaceAll("&", "§"));
final String key = storedKey(e.getClickedBlock().getLocation());
if (key == null) {
}
return;
}
}
}
e.setCancelled(true);
}
final Player player = e.getPlayer();
if (!player.hasPermission("splegg.join")) {
}
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.NoPermission"));
return;
}
final UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u == null) {
return;
}
if (u.getGame() != null) {
Utils.spleggOGMessage(player, "&cERROR: You are already playing.");
return;
}
final Game game = new LobbySign(key, SpleggOG.getPlugin()).resolveTarget(player);
if (game == null) {
Utils.spleggOGMessage(player, "&cNo Splegg lobby is open to join right now.");
return;
}
game.joinGame(u);
player.updateInventory();
}
@EventHandler
public void signBreak(BlockBreakEvent e) {
if (!isSign(e.getBlock().getType())) {
return;
}
final String key = storedKey(e.getBlock().getLocation());
if (key == null) {
return;
}
final Player player = e.getPlayer();
if (player.hasPermission("splegg.admin")) {
new LobbySign(key, SpleggOG.getPlugin()).delete(key, e.getBlock().getLocation());
Utils.spleggOGMessage(player,
SpleggOG.getPlugin().getConfig().getString("Messages.RemovedSign").replaceAll("%map%", key));
} else {
e.setCancelled(true);
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.NotBreakSign"));
}
}
}
+149 -73
View File
@@ -2,6 +2,7 @@ package events;
import java.util.Iterator;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
@@ -12,6 +13,7 @@ import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.ProjectileHitEvent;
import org.bukkit.event.player.PlayerEggThrowEvent;
import org.bukkit.event.player.PlayerMoveEvent;
@@ -21,125 +23,199 @@ import org.bukkit.util.BlockIterator;
import main.SpleggOG;
import managers.Game;
import managers.Status;
import stats.SpleggStats;
import utils.SpleggPlayer;
import utils.UtilPlayer;
import utils.Utils;
public class SpleggEvents implements Listener {
@EventHandler
public void eggLand(ProjectileHitEvent e) {
@EventHandler
public void eggLand(ProjectileHitEvent event) {
ProjectileSource shooter = e.getEntity().getShooter();
if (shooter instanceof Player && e.getEntity() instanceof Egg) {
ProjectileSource shooter = event.getEntity().getShooter();
if (shooter instanceof Player && event.getEntity() instanceof Egg) {
Player player = (Player) shooter;
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
Player player = (Player) shooter;
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
// If the egg hit an entity, do this.
if(e.getHitEntity() != null) {
return;
// Cancel the hit.
e.setCancelled(true);
}
}
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
Block hitBlock = event.getHitBlock();
if (hitBlock != null) {
BlockIterator bi = new BlockIterator(e.getEntity().getWorld(), e.getEntity().getLocation().toVector(), e.getEntity().getVelocity().normalize(), 0.0D, 4);
Block hit = null;
while(bi.hasNext()) {
Location hitLocation = null;
BlockIterator bi = new BlockIterator(hitBlock.getWorld(), event.getEntity().getLocation().toVector(),
event.getEntity().getVelocity().normalize(), 0.0D, 4);
if (u.getGame() != null && u.isAlive()) {
hit = bi.next();
Block hit = null;
while (bi.hasNext()) {
if (hit.getType() != Material.AIR) {
hit = bi.next();
break;
if (hit.getType() != Material.AIR) {
}
break;
}
} else {
if (hit.getType() == Material.TNT) {
// Get location if it is a block.
hitLocation = hitBlock.getLocation();
e.getEntity().getWorld().createExplosion(e.getEntity().getLocation(), 3.0F);
// Cancel the collision.
event.setCancelled(true);
Iterator<?> var7 = e.getEntity().getWorld().getEntities().iterator();
while(var7.hasNext()) {
// Deletes the egg entity.
event.getEntity().remove();
Entity drop = (Entity) var7.next();
}
if (drop.getType() == EntityType.DROPPED_ITEM) {
}
drop.remove();
drop.remove();
drop.remove();
drop.remove();
drop.remove();
drop.remove();
if (hit == null) {
}
return;
}
}
}
if (hit.getType() == Material.TNT) {
if (u.getGame().getFloor().contains(hit.getLocation())) {
hitBlock.getWorld().createExplosion(hitLocation, 3.0F);
Game game = u.getGame();
// If the game is in-progress, do this.
if (game.getStatus() == Status.INGAME) {
Iterator<?> entityIterator = hitBlock.getWorld().getEntities().iterator();
while (entityIterator.hasNext()) {
// Play chicken egg sound upon block hit for every player nearby the event.
player.getWorld().playSound(hit.getLocation(), Sound.ENTITY_CHICKEN_EGG, 1.0F, 1.0F);
Entity drop = (Entity) entityIterator.next();
// Spawn spell particles when a block is destroyed to indicate vaporization.
player.getWorld().spawnParticle(Particle.SPELL, hit.getLocation(), 10);
if (drop.getType() == EntityType.DROPPED_ITEM) {
// Destroy the block that was hit by an egg.
hit.setType(Material.AIR);
drop.remove();
}
}
}
}
}
}
}
Game game = u.getGame();
if (game.isInsideFloor(hitLocation)) {
}
// If the game is in-progress, do this.
if (game.getStatus() == Status.INGAME) {
@EventHandler
public void onKnockout(PlayerMoveEvent e) {
// Play chicken egg sound upon block hit for every player nearby the event.
player.getWorld().playSound(hitLocation, Sound.ENTITY_CHICKEN_EGG, 1.0F, 1.0F);
Player player = e.getPlayer();
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive() && ((double) player.getLocation().getBlockY() < -5.0D || player.getLocation().getBlockY() < u.getGame().getLowestPossible()) && u.getGame().getStatus() == Status.INGAME) {
// Spawn spell particles when a block is destroyed to indicate vaporization.
player.getWorld().spawnParticle(Particle.SPELL, hitLocation, 12);
player.getWorld().spawnParticle(Particle.BLOCK_DUST, hitLocation, 6, hit.getBlockData());
SpleggOG.getPlugin().chat.bc(SpleggOG.getPlugin().getConfig().getString("Messages.PlayersRemaining").replaceAll("&", "§").replaceAll("%count%", String.valueOf(u.getGame().getPlayers().size() - 1)), u.getGame());
// Destroy the block that was hit by an egg.
hit.setType(Material.AIR);
Listeners.launchEggs.remove(player.getName());
final SpleggPlayer spleggPlayer = game.getPlayer(player);
if (spleggPlayer != null) {
// TODO: Remove dev logger
SpleggOG.getPlugin().getLogger().info("Player leaving game onKnockout: " + u.getGame() + " with map " + u.getGame().getMap().getName());
spleggPlayer.addBrokenBlock();
SpleggStats.get().addPoints(player, SpleggStats.blockPoints());
Game game = u.getGame();
game.leaveGame(u);
}
player.setFallDistance(1.0F);
}
}
}
}
}
@EventHandler
public void eggHatch(PlayerEggThrowEvent e) {
}
Player player = e.getPlayer();
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
}
if (u.getGame() != null && u.isAlive()) {
}
e.setHatching(false);
// TODO: Implement spectator compass here.
@EventHandler
public void eggHatch(PlayerEggThrowEvent event) {
}
Player player = event.getPlayer();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
}
}
return;
}
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
event.setHatching(false);
}
}
@EventHandler
public void onFall(EntityDamageEvent event) {
if (event.getEntity() instanceof Player) {
Player player = (Player) event.getEntity();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
return;
}
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
if (u.getGame() != null && u.isAlive()) {
event.setCancelled(true);
}
}
}
@EventHandler
public void onKnockout(PlayerMoveEvent event) {
Player player = event.getPlayer();
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
return;
}
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
Game game = u.getGame();
if (game != null && u.isAlive()
&& ((double) player.getLocation().getBlockY() < -5.0D
|| player.getLocation().getBlockY() < game.getLowestPossible())
&& game.getStatus() == Status.INGAME)
{
SpleggOG.getPlugin().chat.bc(SpleggOG.getPlugin().getConfig().getString("Messages.KnockoutPlayer")
.replaceAll("%player%", player.getName()));
SpleggOG.getPlugin().chat.bc(SpleggOG.getPlugin().getConfig().getString("Messages.PlayersRemaining")
.replaceAll("%count%", String.valueOf(game.getPlayers().size() - 1)), game);
Utils.spleggOGMessage(player,
SpleggOG.getPlugin().getConfig()
.getString("Messages.LoseGame", "&cYou were eliminated from the match on &e%map%&c.")
.replaceAll("%map%", game.getMapDisplayName()));
player.setFallDistance(0.0F);
game.leaveGame(u);
SpleggOG.getPlugin().games.checkWinner(game);
}
}
}
+155
View File
@@ -0,0 +1,155 @@
package gui;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import events.Listeners;
import main.SpleggOG;
import net.kyori.adventure.text.TextComponent;
import net.trueog.gxui.GUIButton;
import net.trueog.gxui.GUIBase;
import net.trueog.gxui.GUIItem;
import net.trueog.utilitiesog.UtilitiesOG;
public class SpleggShopGUI extends GUIBase {
public static final List<String> SHOVEL_CONFIG_PATHS = List.of("GUI.Shop.WoodShovel", "GUI.Shop.StoneShovel",
"GUI.Shop.IronShovel", "GUI.Shop.GoldShovel", "GUI.Shop.DiamondShovel", "GUI.Shop.NetheriteShovel");
// Affordability is resolved off the main thread before the GUI is built.
private final Map<String, Boolean> affordable;
public SpleggShopGUI(Player player, Map<String, Boolean> affordable) {
super(SpleggOG.getPlugin(), player, SpleggOG.getPlugin().getConfig().getString("GUI.Shop.Title"),
normalizeSize(SpleggOG.getPlugin().getConfig().getInt("GUI.Shop.Size")), true);
this.affordable = affordable;
}
private static int normalizeSize(int configuredSize) {
if (configuredSize <= 0) {
return 9;
}
final int clamped = Math.min(54, configuredSize);
final int remainder = clamped % 9;
return remainder == 0 ? clamped : clamped + (9 - remainder);
}
@Override
public void setupItems() {
addShovel(0, Material.WOODEN_SHOVEL, "GUI.Shop.WoodShovel", "Messages.BuyWoodShovel", Listeners.woodspade);
addShovel(1, Material.STONE_SHOVEL, "GUI.Shop.StoneShovel", "Messages.BuyStoneShovel", Listeners.stonespade);
addShovel(2, Material.IRON_SHOVEL, "GUI.Shop.IronShovel", "Messages.BuyIronShovel", null);
addShovel(3, Material.GOLDEN_SHOVEL, "GUI.Shop.GoldShovel", "Messages.BuyGoldShovel", Listeners.goldspade);
addShovel(4, Material.DIAMOND_SHOVEL, "GUI.Shop.DiamondShovel", "Messages.BuyDiamondShovel",
Listeners.diamondspade);
addShovel(5, Material.NETHERITE_SHOVEL, "GUI.Shop.NetheriteShovel", "Messages.BuyNetheriteShovel",
Listeners.netheritespade);
}
private void addShovel(int slot, Material material, String configPath, String successMessagePath,
Set<UUID> selectedShovelList)
{
final Player player = getPlayer();
final boolean affordable = this.affordable.getOrDefault(configPath, false);
final GUIItem item = new GUIItem(material, 1, SpleggOG.getPlugin().getConfig().getString(configPath + ".Name"));
item.lore(buildLore(player, configPath, affordable));
item.button(buildButton(player, configPath, successMessagePath, selectedShovelList));
if (affordable) {
item.enchantment(Enchantment.DURABILITY, 1);
}
addItem(slot, item);
}
private GUIButton buildButton(Player player, String configPath, String successMessagePath,
Set<UUID> selectedShovelList)
{
return new GUIButton() {
@Override
public boolean leftClick() {
return handleClick();
}
@Override
public boolean leftClickShift() {
return handleClick();
}
@Override
public boolean rightClick() {
return handleClick();
}
@Override
public boolean rightClickShift() {
return handleClick();
}
private boolean handleClick() {
if (selectedShovelList == null) {
return Listeners.handleIronSelection(player);
}
return Listeners.handleShovelSelection(player, configPath, successMessagePath, selectedShovelList);
}
};
}
private List<TextComponent> buildLore(Player player, String configPath, boolean affordable) {
final List<TextComponent> lore = new ArrayList<>();
final String description = SpleggOG.getPlugin().getConfig().getString(configPath + ".Description")
.replaceAll("%price%", "&B" + SpleggOG.getPlugin().getConfig().getInt(configPath + ".Price"));
lore.add(UtilitiesOG.trueogExpand(description));
lore.add(UtilitiesOG.trueogExpand(affordable ? "&aAffordable right now." : "&cUnaffordable right now."));
if (!Listeners.canPurchasePremiumShovel(player.getUniqueId())) {
lore.add(UtilitiesOG.trueogExpand("&6Premium shovel already chosen for this round."));
}
return lore;
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+175 -102
View File
@@ -1,163 +1,236 @@
package managers;
import java.util.ArrayList;
import java.time.Duration;
import java.util.HashMap;
import java.util.Iterator;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import config.Map;
import events.Listeners;
import main.SpleggOG;
import net.kyori.adventure.title.Title;
import net.trueog.diamondbankog.DiamondBankException.EconomyDisabledException;
import net.trueog.diamondbankog.DiamondBankException.InvalidPlayerException;
import net.trueog.utilitiesog.UtilitiesOG;
import stats.SpleggStats;
import utils.SpleggPlayer;
import utils.UtilPlayer;
import utils.Utils;
public class GameManager {
SpleggOG splegg;
SpleggOG splegg;
public GameManager() {
public GameManager() {
splegg = SpleggOG.getPlugin();
splegg = SpleggOG.getPlugin();
}
}
public void startGame(Game game) {
// Moves the lobby into its arena. Returns false, with the lobby left waiting,
// when no map could be loaded.
public boolean startGame(Game game) {
splegg.chat.log("New game commencing..");
game.startGameTimer();
Bukkit.getScheduler().cancelTask(game.counter);
game.status = Status.INGAME;
game.time = 901;
game.setLobbyCount(31);
int c = 1;
game.loadFloors();
Map map = game.getMap();
if (game.getStatus() != Status.LOBBY) {
Iterator<?> playersInGame = game.players.values().iterator();
SpleggPlayer sp;
while(playersInGame.hasNext()) {
return false;
sp = (SpleggPlayer) playersInGame.next();
sp.getPlayer().setLevel(0);
sp.getUtilPlayer().setAlive(true);
}
if (c > map.getSpawnCount()) {
if (!game.prepareArena()) {
c = 1;
splegg.chat.bc("&cNo playable map could be loaded, so the match cannot start.", game);
return false;
}
}
sp.getPlayer().teleport(map.getSpawn(c));
c++;
sp.getPlayer().setLevel(0);
sp.getPlayer().setExp(0.0F);
sp.getPlayer().setGameMode(GameMode.ADVENTURE);
final Map map = game.getMap();
SpleggOG.getPlugin().getLogger()
.info("Lobby " + game.getLobbyId() + " is starting a match on map " + map.getName() + ".");
if (game.counter != -1) {
// TODO: Add all available shovels here.
if (! Listeners.manager.contains(sp.getPlayer().getName())) {
org.bukkit.Bukkit.getScheduler().cancelTask(game.counter);
game.counter = -1;
if (Listeners.goldspade.contains(sp.getPlayer().getName())) {
}
sp.getPlayer().getInventory().clear();
sp.getPlayer().getInventory().setItem(0, Utils.getItem(Material.GOLDEN_SHOVEL, splegg.getConfig().getString("Shovels.Gold.Name").replaceAll("&", "§"), splegg.getConfig().getString("Shovels.Gold.Lore").replaceAll("&", "§")));
sp.getPlayer().updateInventory();
game.stopVoting();
game.status = Status.INGAME;
game.startGameTimer();
game.time = 901;
game.setLobbyCount(31);
int c = 1;
game.loadFloors();
Listeners.manager.remove(sp.getPlayer().getName());
Listeners.goldspade.remove(sp.getPlayer().getName());
Listeners.diamondspade.remove(sp.getPlayer().getName());
Listeners.shopmanager.remove(sp.getPlayer().getName());
Listeners.moneymanager.add(sp.getPlayer().getName());
final Iterator<?> playersInGame = game.players.values().iterator();
SpleggPlayer sp;
while (playersInGame.hasNext()) {
}
sp = (SpleggPlayer) playersInGame.next();
if (Listeners.diamondspade.contains(sp.getPlayer().getName())) {
sp.getPlayer().setLevel(0);
sp.getUtilPlayer().setAlive(true);
sp.getPlayer().getInventory().clear();
sp.getPlayer().getInventory().setItem(0, Utils.getItem(Material.DIAMOND_SHOVEL, splegg.getConfig().getString("Shovels.Diamond.Name").replaceAll("&", "§"), splegg.getConfig().getString("Shovels.Diamond.Lore").replaceAll("&", "§")));
sp.getPlayer().updateInventory();
if (c > map.getSpawnCount()) {
Listeners.manager.remove(sp.getPlayer().getName());
Listeners.goldspade.remove(sp.getPlayer().getName());
Listeners.diamondspade.remove(sp.getPlayer().getName());
Listeners.shopmanager.remove(sp.getPlayer().getName());
Listeners.moneymanager.add(sp.getPlayer().getName());
c = 1;
}
}
}
else {
sp.getPlayer().teleport(map.getSpawnIn(game.getGameWorld(), c));
c++;
sp.getPlayer().setLevel(0);
sp.getPlayer().setExp(0.0F);
sp.getPlayer().setGameMode(GameMode.ADVENTURE);
sp.getPlayer().getInventory().clear();
sp.getPlayer().getInventory().clear();
sp.getPlayer().getInventory().setItem(0, Utils.getItem(Material.IRON_SHOVEL, splegg.getConfig().getString("Shovels.Iron.Name").replaceAll("&", "§"), splegg.getConfig().getString("Shovels.Iron.Lore").replaceAll("&", "§")));
sp.getPlayer().updateInventory();
}
Listeners.manager.remove(sp.getPlayer().getName());
Listeners.goldspade.remove(sp.getPlayer().getName());
Listeners.diamondspade.remove(sp.getPlayer().getName());
Listeners.shopmanager.remove(sp.getPlayer().getName());
Listeners.moneymanager.add(sp.getPlayer().getName());
game.updateSigns();
LobbyScoreboard.refreshGame(game);
}
splegg.chat.bc(splegg.getConfig().getString("Messages.InstructionsGame"), game);
return true;
}
}
game.getSign().update(map, false);
// Ends whatever the lobby is doing, sends everyone home, deletes the arena
// copy and puts the lobby back to waiting in its hub.
public void stopGame(Game game, int r) {
splegg.chat.bc(splegg.getConfig().getString("Messages.InstructionsGame").replaceAll("&", "§"), game);
SpleggOG.getPlugin().getLogger().info("Lobby " + game.getLobbyId() + " is ending"
+ (game.getMap() != null ? " its match on map " + game.getMap().getName() : "") + ".");
}
game.status = Status.ENDING;
game.stopVoting();
game.stopGameTimer();
game.setStarting(false);
public void stopGame(Game game, int r) {
final Iterator<?> playersInGame = new ArrayList<>(game.players.values()).iterator();
while (playersInGame.hasNext()) {
SpleggOG.getPlugin().getLogger().info("Commencing shutdown of " + game.getMap().getName() + ".");
final SpleggPlayer sp = (SpleggPlayer) playersInGame.next();
final UtilPlayer u = sp.getUtilPlayer();
game.leaveGame(u);
game.status = Status.ENDING;
game.stopGameTimer();
game.setLobbyCount(31);
game.time = 601;
game.setStatus(Status.LOBBY);
game.resetArena();
game.data.clear();
game.floor.clear();
game.setStarting(false);
game.players.clear();
// game.leaveGame(game.getPlayers().);
}
if (! splegg.disabling) {
game.players.clear();
game.getSign().update(game.map, true);
// Delete the per-game world copy. The lobby itself stays registered and
// waits in its hub for the next match.
SpleggOG.getPlugin().getGameWorldManager().cleanupWorld(game);
game.resetToLobby();
}
if (!splegg.disabling) {
splegg.chat.log("Map " + game.map.getName() + " was reset.");
game.updateSigns();
}
}
public String getDigitTime(int count) {
SpleggOG.getPlugin().getLogger().info("Lobby " + game.getLobbyId() + " is back to waiting.");
int minutes = count / 60;
int seconds = count % 60;
}
String disMinu = (minutes < 10 ? "0" : "") + minutes;
String disSec = (seconds < 10 ? "0" : "") + seconds;
String formattedTime = disMinu + ":" + disSec;
public void finishGame(Game game, SpleggPlayer winner) {
return formattedTime;
if (winner == null) {
}
stopGame(game, 0);
return;
public void ingameTimer(int count, HashMap<String, SpleggPlayer> players) {
}
Iterator<?> playersInGame = players.values().iterator();
while(playersInGame.hasNext()) {
final Player player = winner.getPlayer();
final String mapName = game.getMapDisplayName();
final String winnerBroadcast = splegg.getConfig().getString("Messages.WinnerGame",
"&a%player% won Splegg on map &e%map%&a!");
final String winnerTitle = splegg.getConfig().getString("Messages.WinnerTitle", "&a&lVICTORY!");
final String winnerSubtitle = splegg.getConfig().getString("Messages.WinnerSubtitle",
"&eYou won on &6%map%&e!");
final String youWonMessage = splegg.getConfig().getString("Messages.YouWonGame",
"&aYou won the match on &e%map%&a!");
SpleggPlayer sp = (SpleggPlayer) playersInGame.next();
splegg.chat.sendMessage(sp.getPlayer(), "&6Splegg is ending in §5§l" + splegg.game.getDigitTime(count));
splegg.chat.bc(winnerBroadcast.replaceAll("%player%", player.getName()).replaceAll("%map%", mapName));
player.showTitle(Title.title(
Utils.legacySerializerAnyCase(
winnerTitle.replaceAll("%player%", player.getName()).replaceAll("%map%", mapName)),
Utils.legacySerializerAnyCase(
winnerSubtitle.replaceAll("%player%", player.getName()).replaceAll("%map%", mapName)),
Title.Times.times(Duration.ofMillis(300), Duration.ofSeconds(3), Duration.ofMillis(600))));
Utils.spleggOGMessage(player, youWonMessage.replaceAll("%map%", mapName));
rewardWinner(player);
SpleggStats.get().addPoints(player, SpleggStats.winPoints());
}
stopGame(game, 1);
}
}
}
public String getDigitTime(int count) {
final int minutes = count / 60;
final int seconds = count % 60;
final String disMinu = (minutes < 10 ? "0" : "") + minutes;
final String disSec = (seconds < 10 ? "0" : "") + seconds;
return disMinu + ":" + disSec;
}
public void ingameTimer(int count, HashMap<java.util.UUID, SpleggPlayer> players) {
final Iterator<?> playersInGame = players.values().iterator();
while (playersInGame.hasNext()) {
final SpleggPlayer sp = (SpleggPlayer) playersInGame.next();
Utils.spleggOGMessage(sp.getPlayer(), ("&6Splegg is ending in... " + splegg.game.getDigitTime(count)));
}
}
private void rewardWinner(Player winner) {
if (splegg.getDiamondBankAPI() == null) {
return;
}
final int rewardInDiamonds = splegg.getConfig().getInt("Money.FinishGame");
if (rewardInDiamonds <= 0) {
return;
}
final long rewardInShards = splegg.getDiamondBankAPI().diamondsToShards((float) rewardInDiamonds);
try {
splegg.getDiamondBankAPI().addToPlayerBankShards(winner.getUniqueId(), rewardInShards,
"Player " + winner.getName() + " earned Diamonds for winning Splegg.", "Plugin: Splegg-OG");
final String rewardMessage = splegg.getConfig().getString("Messages.FinishReward",
"&BYou received %reward% &BDiamonds for winning!");
winner.sendMessage(Utils
.legacySerializerAnyCase(rewardMessage.replaceAll("%reward%", String.valueOf(rewardInDiamonds)))
.content());
} catch (EconomyDisabledException economyDisabledException) {
UtilitiesOG.trueogMessage(winner,
"&cERROR: The Diamond economy is currently unavailable. Your win reward could not be paid out.");
} catch (InvalidPlayerException invalidPlayerException) {
UtilitiesOG.trueogMessage(winner,
"&cERROR: Your player account could not be found. Your win reward could not be paid out.");
}
}
}
+329 -58
View File
@@ -1,112 +1,383 @@
package managers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player;
import main.SpleggOG;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import utils.SpleggPlayer;
import utils.Utils;
// Registry of every Splegg lobby, keyed by lobby number. Lobbies are created
// once per configured hub world (SP1-hub is lobby SP1) and live for the whole
// server session; a finished match resets its lobby instead of removing it.
public class GameUtilities {
public HashMap<String, Game> GAMES = new HashMap<String, Game>();
public java.util.Map<String, Game> GAMES = new ConcurrentHashMap<>();
public Game getGame(String map) {
// Lobby ids are <Worlds.GamePrefix><number>, the same shape as the hub
// world name without its -hub suffix. Returns the number, or null when the
// world name is not <prefix><digits>-<name>.
public static String lobbyNumberFromWorldName(String worldName) {
return (Game) this.GAMES.get(map);
if (worldName == null) {
}
return null;
public void addGame(String map, Game game) {
}
this.GAMES.put(map, game);
final String prefix = SpleggOG.getPlugin().getGameWorldPrefix();
if (prefix == null || prefix.isEmpty() || !worldName.regionMatches(true, 0, prefix, 0, prefix.length())) {
}
return null;
public SpleggPlayer getPlayer(Player player) {
}
if (player == null) {
int index = prefix.length();
final int digitStart = index;
while (index < worldName.length() && Character.isDigit(worldName.charAt(index))) {
return null;
index++;
}
else {
}
SpleggPlayer sp = null;
Iterator<?> var4 = this.GAMES.values().iterator();
while(var4.hasNext()) {
if (index == digitStart || index >= worldName.length() || worldName.charAt(index) != '-'
|| index + 1 >= worldName.length())
{
Game games = (Game) var4.next();
return null;
Iterator<?> var6 = games.players.values().iterator();
while(var6.hasNext()) {
}
SpleggPlayer sps = (SpleggPlayer) var6.next();
return String.valueOf(Integer.parseInt(worldName.substring(digitStart, index)));
if (sps.getPlayer().getName().equalsIgnoreCase(player.getName())) {
}
sp = sps;
// Brings a lobby online for a loaded hub world, or returns the one that
// already owns that number.
public Game createLobby(String hubWorldName) {
}
final String number = lobbyNumberFromWorldName(hubWorldName);
if (number == null) {
}
SpleggOG.getPlugin().getLogger()
.warning("Lobby world '" + hubWorldName + "' is not named <"
+ SpleggOG.getPlugin().getGameWorldPrefix()
+ "><number>-<name> (for example SP1-hub), so no lobby was created for it.");
return null;
}
}
return sp;
final Game existing = GAMES.get(number);
if (existing != null) {
}
if (!existing.getHubWorldName().equalsIgnoreCase(hubWorldName)) {
}
SpleggOG.getPlugin().getLogger().warning("Lobby world '" + hubWorldName + "' clashes with '"
+ existing.getHubWorldName() + "': both claim lobby " + existing.getLobbyId() + ".");
public Game getMatchedGame(Player player) {
}
Game game = null;
Iterator<?> var4 = this.GAMES.values().iterator();
return existing;
while(var4.hasNext()) {
}
Game g = (Game) var4.next();
if (g.players.containsKey(player.getName())) {
final Game game = new Game(SpleggOG.getPlugin(), number, hubWorldName);
GAMES.put(number, game);
SpleggOG.getPlugin().getLogger()
.info("Lobby " + game.getLobbyId() + " is ready in hub world '" + hubWorldName + "'.");
return game;
game = g;
}
}
public void clear() {
}
GAMES.clear();
return game;
}
}
public Game getGame(String lobbyIdOrNumber) {
public int howManyOpenGames() {
return resolveLobby(lobbyIdOrNumber);
ArrayList<Game> all = new ArrayList<Game>();
Iterator<?> var3 = this.GAMES.values().iterator();
while(var3.hasNext()) {
}
Game games = (Game) var3.next();
if (games.getStatus() == Status.LOBBY) {
public void registerGame(Game game) {
all.add(games);
}
GAMES.put(game.getGameId(), game);
}
}
return all.size();
public void removeGame(Game game) {
}
if (game == null)
return;
GAMES.remove(game.getGameId(), game);
GAMES.entrySet().removeIf(e -> e.getValue() == game);
public void checkWinner(Game game) {
}
if (game.players.size() < 2) {
// Lobbies whose chosen or running map is the given one.
public List<Game> gamesForMap(String mapName) {
// TODO: Assign winner here.
game.splegg.game.stopGame(game, game.players.size());
List<Game> out = new ArrayList<>();
if (mapName == null)
return out;
for (Game g : GAMES.values())
if (g.getMap() != null && mapName.equals(g.getMap().getName()))
out.add(g);
return out;
}
}
}
// Accepts SP1, sp1 or a bare 1. Map names are deliberately not resolvable.
public Game resolveLobby(String input) {
}
if (input == null)
return null;
String query = input.trim();
if (query.isEmpty())
return null;
final String prefix = SpleggOG.getPlugin().getGameWorldPrefix();
if (!prefix.isEmpty() && query.regionMatches(true, 0, prefix, 0, prefix.length()))
query = query.substring(prefix.length());
if (query.isEmpty() || !query.chars().allMatch(Character::isDigit))
return null;
final String number = String.valueOf(Integer.parseInt(query));
return GAMES.get(number);
}
// The best lobby for a player who did not name one: a starting lobby with
// room and the most players, else a waiting lobby with room and the most
// players. Live lobbies cannot be joined.
public Game findBestLobby(Player player) {
Game bestStarting = null;
Game bestWaiting = null;
int bestStartingFill = -1;
int bestWaitingFill = -1;
for (Game g : all()) {
if (g.getStatus() != Status.LOBBY || g.getHubWorld() == null)
continue;
final int fill = g.getPlayers().size();
if (fill >= g.getMaxPlayers() && !player.hasPermission("splegg.joinfull"))
continue;
if (g.isStarting()) {
if (fill > bestStartingFill) {
bestStartingFill = fill;
bestStarting = g;
}
} else if (fill > bestWaitingFill) {
bestWaitingFill = fill;
bestWaiting = g;
}
}
return bestStarting != null ? bestStarting : bestWaiting;
}
public String getLobbyId(Game game) {
return game == null ? null : game.getLobbyId();
}
public List<String> getLobbyIds() {
final List<String> ids = new ArrayList<>();
for (Game g : all())
ids.add(g.getLobbyId());
return ids;
}
// Every lobby, ordered by number.
public List<Game> all() {
final List<Game> games = new ArrayList<>(GAMES.values());
games.sort(Comparator.comparingInt(g -> Integer.parseInt(g.getGameId())));
return Collections.unmodifiableList(games);
}
// The lobby list players see, TheHerobrine-OG style, with clickable joins.
public void sendLobbyMessage(Player player) {
final List<Game> games = all();
if (games.isEmpty()) {
Utils.spleggOGMessage(player, "&cThere are no Splegg lobbies. Check Worlds.Lobby in config.yml.");
return;
}
Utils.spleggOGMessage(player, "&6Join a lobby with /splegg join <id>.");
Utils.spleggOGMessage(player, "&6Lobbies available to join:");
final boolean overfill = player.hasPermission("splegg.joinfull");
for (Game game : games) {
final String id = game.getLobbyId();
final int fill = game.getPlayers().size();
final int max = game.getMaxPlayers();
final String line;
final boolean joinable;
switch (game.getStatus()) {
case LOBBY -> {
final String state = game.isStarting() ? "&d&lSTARTING " : "&e&lWAITING ";
joinable = fill < max || overfill;
line = "&b" + id + ": &e" + fill + "/" + max + "&7 - &r" + state + "&r"
+ (fill < max ? "&a(JOIN)" : "&c&lFULL &r" + (overfill ? "&a(JOIN)" : ""));
}
case INGAME -> {
joinable = false;
line = "&b" + id + ": &e" + fill + " remaining&8 - &b&lLIVE &r&7(" + game.getMapDisplayName() + ")";
}
case ENDING -> {
joinable = false;
line = "&b" + id + ": &8&lENDING";
}
default -> {
joinable = false;
line = "&b" + id + ": &c&lDISABLED";
}
}
TextComponent component = Utils.legacySerializerAnyCase(Utils.prefix + line);
if (joinable) {
component = component
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,
Utils.legacySerializerAnyCase("&6Click here to join &b" + id)))
.clickEvent(ClickEvent.runCommand("/splegg join " + id));
}
player.sendMessage(component);
}
}
public int howManyOpenGames() {
int n = 0;
for (Game g : GAMES.values())
if (g.getStatus() == Status.LOBBY)
n++;
return n;
}
public SpleggPlayer getPlayer(Player player) {
if (player == null)
return null;
final UUID playerId = player.getUniqueId();
for (Game g : GAMES.values()) {
final SpleggPlayer sp = g.players.get(playerId);
if (sp != null)
return sp;
}
return null;
}
public Game getMatchedGame(Player player) {
if (player == null)
return null;
final UUID playerId = player.getUniqueId();
for (Game g : GAMES.values())
if (g.players.containsKey(playerId))
return g;
return null;
}
public boolean checkWinner(Game game) {
if (game == null || game.getStatus() != Status.INGAME)
return false;
int amountOfPlayersInGame = game.players.size();
if (amountOfPlayersInGame <= 1) {
if (amountOfPlayersInGame == 0)
game.splegg.game.stopGame(game, 0);
else {
SpleggPlayer winner = game.players.values().iterator().next();
SpleggOG.getPlugin().getLogger().info("Winner detected: " + winner.getPlayer().getName());
game.splegg.game.finishGame(game, winner);
}
return true;
}
return false;
}
/**
* Whether the given world name belongs to an active per-game world copy. Used
* by listeners so events inside ephemeral game worlds resolve as "splegg world"
* even though the copy is not configured in Worlds.InGame.
*/
public boolean isActiveGameWorld(String worldName) {
if (worldName == null)
return false;
for (Game g : GAMES.values()) {
org.bukkit.World w = g.getGameWorld();
if (w != null && worldName.equals(w.getName()))
return true;
}
return false;
}
}
@@ -0,0 +1,811 @@
package managers;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import com.bergerkiller.bukkit.mw.WorldConfig;
import com.bergerkiller.bukkit.mw.WorldInventory;
import config.Map;
import main.SpleggOG;
// Owns the per-game world lifecycle for Splegg matches.
// Each game is bound to a copy of its template world; the copy lives at
// <worldContainer>/<prefix><gameId>-<templateName>/ and is deleted when the game
// ends. The original template world remains loaded for admin editing and is
// never modified by gameplay.
public class GameWorldManager {
private final SpleggOG plugin;
public GameWorldManager(SpleggOG plugin) {
this.plugin = plugin;
}
// Matches <prefix><digits>-<rest>, the shape prepareWorld builds. Walks the
// name instead of a regex.
public boolean isGameCopyName(String name) {
String prefix = plugin.getGameWorldPrefix();
if (prefix.isEmpty() || !name.regionMatches(true, 0, prefix, 0, prefix.length()))
return false;
int index = prefix.length();
int digitStart = index;
while (index < name.length() && Character.isDigit(name.charAt(index)))
index++;
if (index == digitStart || index >= name.length() || name.charAt(index) != '-')
return false;
if (index + 1 >= name.length())
return false;
// A configured lobby or template world is the admin's, never a match copy.
return !isConfiguredWorld(name);
}
// Only copies of known templates are purged, so an admin's SP2-something
// world that is not in config yet survives a restart.
private boolean isKnownTemplateCopy(String name) {
int dash = name.indexOf('-');
if (dash < 0 || dash + 1 >= name.length())
return false;
String suffix = name.substring(dash + 1);
for (String template : plugin.getInGameWorlds())
if (template != null && template.equalsIgnoreCase(suffix))
return true;
if (plugin.maps == null)
return false;
for (Map map : plugin.maps.getMaps()) {
String world = map.getWorldName();
if (world != null && world.equalsIgnoreCase(suffix))
return true;
}
return false;
}
private boolean isConfiguredWorld(String name) {
for (String configured : plugin.getLobbyWorlds())
if (configured.equalsIgnoreCase(name))
return true;
for (String configured : plugin.getInGameWorlds())
if (configured.equalsIgnoreCase(name))
return true;
return false;
}
// Removes stale per-game world directories left behind from a previous run
// (e.g. server crash). Safe to call at plugin enable before any games register.
// Returns how many worlds were removed.
public int purgeStaleCopies() {
File container = plugin.getServer().getWorldContainer();
File[] children = container.listFiles(File::isDirectory);
if (children == null)
return 0;
int purged = 0;
for (File child : children) {
String name = child.getName();
if (!isGameCopyName(name) || !isKnownTemplateCopy(name))
continue;
if (SpleggOG.isProtectedMainWorld(name)) {
plugin.getLogger().warning("Refusing to purge protected main world named '" + name + "'.");
continue;
}
if (deleteStaleCopy(name, child))
purged++;
}
if (purged > 0)
plugin.getLogger().info("Purged " + purged + " stale Splegg game world(s) from a previous run.");
return purged;
}
// MyWorlds owns the world registration, so ask it to drop the world first and
// only fall back to deleting the directory when it cannot.
private boolean deleteStaleCopy(String name, File worldDir) {
// A reload can leave players standing in a copy no game claims yet.
// Pulling it out from under them is worse than keeping the directory.
World live = Bukkit.getWorld(name);
if (live != null && !live.getPlayers().isEmpty()) {
plugin.getLogger().warning("Not purging stale game world '" + name + "': it still has players in it.");
return false;
}
WorldConfig worldConfig = WorldConfig.getIfExists(name);
if (worldConfig != null) {
if (worldConfig.isLoaded() && !worldConfig.unloadWorld()) {
plugin.getLogger().warning("Could not unload stale game world '" + name + "'; leaving it in place.");
return false;
}
// Same as cleanupWorld: a copy still in its template's bundle could be the
// bundle's storage world, which MyWorlds refuses to delete
WorldInventory.detach(Collections.singletonList(name));
if (worldConfig.deleteWorld())
return true;
}
try {
deleteRecursive(worldDir.toPath());
return true;
} catch (Exception ex) {
plugin.getLogger().warning("Failed to purge stale game world '" + name + "': " + ex.getMessage());
return false;
}
}
// World folder lookup that tolerates the spelling an admin actually typed:
// exact name first, then case-insensitive, then ignoring separators as well so
// 'shake-it' still finds 'Shake_It' on a case-sensitive filesystem. Returns
// null when nothing matches.
public static File resolveWorldDir(File baseDir, String name) {
if (baseDir == null || name == null || name.isBlank())
return null;
File exact = new File(baseDir, name);
if (exact.isDirectory())
return exact;
File[] candidates = baseDir.listFiles(File::isDirectory);
if (candidates == null)
return null;
for (File candidate : candidates)
if (candidate.getName().equalsIgnoreCase(name))
return candidate;
String flattened = flattenName(name);
for (File candidate : candidates)
if (flattenName(candidate.getName()).equals(flattened))
return candidate;
return null;
}
private static String flattenName(String name) {
return name.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", "");
}
// Lists the world folders that do exist under a directory, for error messages.
public static String describeWorldDirs(File baseDir) {
File[] candidates = baseDir == null ? null : baseDir.listFiles(File::isDirectory);
if (candidates == null || candidates.length == 0)
return "No world folders found in " + (baseDir == null ? "(unset)" : baseDir.getPath()) + ".";
StringBuilder sb = new StringBuilder("Available world folders in " + baseDir.getPath() + ": ");
for (int i = 0; i < candidates.length; i++) {
if (i > 0)
sb.append(", ");
sb.append(candidates[i].getName());
}
return sb.append(".").toString();
}
// Pins the void chunk generator onto a world about to be loaded. A copied
// world directory carries no MyWorlds generator setting, so without this the
// copy generates vanilla terrain outside the saved region even though the
// template was created with /mw create <world> void.
private void applyVoidGenerator(WorldConfig wc) {
if (!plugin.isVoidGeneratorEnabled())
return;
// An admin who already picked a generator for this world keeps it; only a
// world with no generator of its own (every fresh copy) gets the void one.
String existing = wc.getChunkGeneratorName();
if (existing != null && !existing.isBlank())
return;
wc.setChunkGeneratorName(plugin.getName() + ":void");
}
// Loads a configured Splegg world through MyWorlds when the server does not
// have it loaded already. Returns the live world, or null when it could not be
// loaded. The returned name is the one the server actually uses, which is the
// folder name and may differ in case from the configured one.
public World ensureWorldLoaded(String name) {
if (name == null || name.isBlank())
return null;
if (SpleggOG.isProtectedMainWorld(name)) {
plugin.getLogger().warning("Refusing to load protected main world '" + name + "' as a Splegg world.");
return null;
}
World existing = Bukkit.getWorld(name);
if (existing != null)
return existing;
File container = plugin.getServer().getWorldContainer();
File worldDir = resolveWorldDir(container, name);
if (worldDir == null) {
plugin.getLogger()
.warning("Configured Splegg world '" + name + "' has no world folder in " + container.getPath()
+ ". Create it with MyWorlds (/mw create " + name + " void) or add it to "
+ "the MapBase directory.");
return null;
}
if (!worldDir.getName().equals(name))
plugin.getLogger().info("Configured Splegg world '" + name + "' resolved to folder '" + worldDir.getName()
+ "'; rename it or fix config.yml to match exactly.");
WorldConfig wc = WorldConfig.get(worldDir.getName());
applyVoidGenerator(wc);
World loaded;
try {
loaded = wc.loadWorld();
} catch (Exception ex) {
plugin.getLogger().severe("Exception loading Splegg world '" + name + "': " + ex.getMessage());
return null;
}
if (loaded == null) {
plugin.getLogger().severe("MyWorlds returned null when loading Splegg world '" + name + "'.");
return null;
}
plugin.getLogger().info("Loaded Splegg world '" + loaded.getName() + "' via MyWorlds.");
return loaded;
}
// Copy template -> game world and load via MyWorlds. The template world must
// be loaded by the server (so its chunks can be flushed to disk before they
// are read). Returns null on failure; caller must abort game creation.
public World prepareWorld(Game game) {
Map map = game.getMap();
String templateName = map.getWorldName();
if (templateName == null) {
plugin.getLogger().warning("Cannot prepare world for game '" + game.getGameId() + "': map '" + map.getName()
+ "' has no configured world.");
return null;
}
if (SpleggOG.isProtectedMainWorld(templateName)) {
plugin.getLogger().severe("Refusing to copy protected main world '" + templateName + "' for game '"
+ game.getGameId() + "'.");
return null;
}
World template = Bukkit.getWorld(templateName);
if (template == null) {
// Bring the template up rather than refusing the match: a template only
// has to exist on disk, not be loaded by an admin ahead of time.
template = ensureWorldLoaded(templateName);
}
if (template == null) {
plugin.getLogger().warning("Cannot prepare world for game '" + game.getGameId() + "': template world '"
+ templateName + "' is not loaded and could not be loaded.");
return null;
}
// From here on use the name the server knows the world by, which is the
// folder name and may differ in case from the configured one.
templateName = template.getName();
String copyName = plugin.getGameWorldPrefix() + game.getGameId() + "-" + templateName;
if (SpleggOG.isProtectedMainWorld(copyName)) {
plugin.getLogger()
.severe("Refusing to use protected world name '" + copyName + "' for a Splegg game copy.");
return null;
}
// Make sure no stale copy with the same name lingers.
if (Bukkit.getWorld(copyName) != null && !unloadCopy(copyName)) {
plugin.getLogger().warning("Could not unload existing world '" + copyName + "' before recreating.");
return null;
}
File container = plugin.getServer().getWorldContainer();
File templateDir = resolveWorldDir(container, templateName);
File copyDir = new File(container, copyName);
if (templateDir == null) {
plugin.getLogger().warning("Template world directory missing: "
+ new File(container, templateName).getPath() + ". " + describeWorldDirs(container));
return null;
}
try {
// Flush template chunks so the on-disk dir is consistent before copy.
template.save();
if (copyDir.exists())
deleteRecursive(copyDir.toPath());
copyRecursive(templateDir.toPath(), copyDir.toPath());
// Force a fresh world UUID + drop any held session lock so MC treats
// the copy as a brand new world rather than re-attaching to the
// template.
File uid = new File(copyDir, "uid.dat");
if (uid.exists())
Files.deleteIfExists(uid.toPath());
File lock = new File(copyDir, "session.lock");
if (lock.exists())
Files.deleteIfExists(lock.toPath());
} catch (Exception ex) {
plugin.getLogger()
.severe("Failed to copy template '" + templateName + "' -> '" + copyName + "': " + ex.getMessage());
try {
if (copyDir.exists())
deleteRecursive(copyDir.toPath());
} catch (Exception ignored) {
}
return null;
}
WorldConfig wc = WorldConfig.get(copyName);
applyVoidGenerator(wc);
World copy;
try {
copy = wc.loadWorld();
} catch (Exception ex) {
plugin.getLogger().severe("Exception loading game world '" + copyName + "': " + ex.getMessage());
try {
deleteRecursive(copyDir.toPath());
} catch (Exception ignored) {
}
return null;
}
if (copy == null) {
plugin.getLogger().severe("MyWorlds returned null when loading game world '" + copyName + "'.");
return null;
}
// Per-game world joins the in-game inventory bundle so players keep the
// splegg in-game inventory across template -> per-game world teleports.
// Detach first to clear any stray bundle membership, then merge with
// the template. The merge must carry the template's existing bundle
// members: WorldInventory.merge builds a fresh bundle from exactly the
// listed worlds, so passing only template+copy would silently strip the
// copies of other games running concurrently on the same map.
try {
WorldInventory.detach(Collections.singletonList(copyName));
final java.util.LinkedHashSet<String> bundle = new java.util.LinkedHashSet<>();
bundle.add(templateName);
bundle.addAll(WorldConfig.get(templateName).inventory.getWorlds());
bundle.add(copyName);
WorldInventory.merge(new ArrayList<>(bundle));
} catch (Throwable t) {
plugin.getLogger().warning(
"Failed to attach MyWorlds inventory bundle for game world '" + copyName + "': " + t.getMessage());
}
plugin.getLogger().info("Prepared Splegg game world '" + copyName + "' for game " + game.getGameId());
return copy;
}
// Evict players, unload via MyWorlds, and delete the game world directory.
// No-op when the game has no world (game never started its world copy).
public void cleanupWorld(Game game) {
World gameWorld = game.getGameWorld();
if (gameWorld == null)
return;
cleanupWorld(gameWorld);
game.setGameWorld(null);
}
public void cleanupWorld(World gameWorld) {
if (gameWorld == null)
return;
String name = gameWorld.getName();
if (SpleggOG.isProtectedMainWorld(name)) {
plugin.getLogger().severe("Refusing to clean protected main world '" + name + "'.");
return;
}
evictPlayers(gameWorld);
try {
WorldInventory.detach(Collections.singletonList(name));
} catch (Throwable ignored) {
}
WorldConfig wc = WorldConfig.get(name);
if (wc.isLoaded() && !wc.unloadWorld())
plugin.getLogger().warning("Failed to unload Splegg game world '" + name + "' before deletion.");
boolean deleted = wc.deleteWorld();
if (!deleted)
plugin.getLogger()
.warning("WorldConfig.deleteWorld() returned false for '" + name + "'; directory may persist.");
}
private boolean unloadCopy(String name) {
WorldConfig wc = WorldConfig.get(name);
if (!wc.isLoaded())
return true;
World w = Bukkit.getWorld(name);
if (w != null)
evictPlayers(w);
return wc.unloadWorld();
}
// Refreshes each configured Splegg world from its cold-storage template under
// MapBase. For every name in the list this evacuates any loaded world at the
// server root, deletes it, copies <mapBase>/<name>/ into place, sanitizes
// stale uid.dat / session.lock, then re-loads via MyWorlds. Per-match world
// copies (splegg-<id>-<name>) are untouched and continue to be sourced from
// the freshly refreshed live world.
// Silently no-ops when mapBaseDir is null. Logs warnings (does not throw) for
// individual world failures so one bad template cannot prevent others from
// refreshing.
public void refreshTemplatesFromMapBase(List<String> worlds, File mapBaseDir, String configPath) {
if (mapBaseDir == null)
return;
if (!mapBaseDir.isDirectory()) {
plugin.getLogger().info("MapBase directory '" + mapBaseDir.getPath()
+ "' does not exist; skipping template refresh. Create it to enable map-folder workflow.");
return;
}
if (worlds == null || worlds.isEmpty())
return;
File container = plugin.getServer().getWorldContainer();
for (String name : worlds) {
if (name == null || name.isBlank())
continue;
if (SpleggOG.isProtectedMainWorld(name)) {
plugin.getLogger().warning("Refusing to refresh protected world name '" + name + "' from MapBase.");
continue;
}
File source = resolveWorldDir(mapBaseDir, name);
if (source == null) {
plugin.getLogger().warning("MapBase template missing for '" + name + "' from " + configPath + ": "
+ new File(mapBaseDir, name).getPath() + ". " + describeWorldDirs(mapBaseDir));
continue;
}
File levelDat = new File(source, "level.dat");
if (!levelDat.isFile()) {
plugin.getLogger().warning("MapBase template is not a world: " + source.getPath()
+ " (expected level.dat). Skipping refresh for '" + name + "'.");
continue;
}
File dest = new File(container, name);
// Source under MapBase must not collide with the destination -- otherwise
// we would delete the source before copying it back.
try {
if (dest.exists() && source.getCanonicalFile().equals(dest.getCanonicalFile())) {
plugin.getLogger().warning("MapBase entry '" + name + "' resolves to the server world container"
+ "; refusing to wipe and re-copy onto itself. Move templates outside the world container.");
continue;
}
} catch (IOException ex) {
plugin.getLogger().warning("Could not canonicalize paths for template '" + name + "': "
+ ex.getMessage() + ". Skipping refresh to be safe.");
continue;
}
if (!forceUnloadIfPresent(name)) {
plugin.getLogger().warning(
"Could not unload existing world '" + name + "' before refresh; skipping to avoid data loss.");
continue;
}
try {
if (dest.exists())
deleteRecursive(dest.toPath());
copyRecursive(source.toPath(), dest.toPath());
sanitizeWorldFolder(dest);
} catch (IOException ex) {
plugin.getLogger().severe("Failed to refresh template '" + name + "' from MapBase: " + ex.getMessage());
continue;
}
WorldConfig wc = WorldConfig.get(name);
applyVoidGenerator(wc);
World loaded;
try {
loaded = wc.loadWorld();
} catch (Exception ex) {
plugin.getLogger().severe("Exception loading refreshed template '" + name + "': " + ex.getMessage());
continue;
}
if (loaded == null) {
plugin.getLogger().severe("MyWorlds returned null when loading refreshed template '" + name + "'.");
continue;
}
plugin.getLogger().info("Refreshed " + configPath + " world '" + name + "' from " + source.getPath() + ".");
}
}
private boolean forceUnloadIfPresent(String name) {
WorldConfig wc = WorldConfig.get(name);
if (!wc.isLoaded())
return true;
World existing = Bukkit.getWorld(name);
if (existing != null)
evictPlayers(existing);
boolean unloaded = wc.unloadWorld();
if (!unloaded)
plugin.getLogger().warning("Failed to unload world '" + name + "' before template refresh.");
return unloaded;
}
private static void sanitizeWorldFolder(File worldDir) {
if (worldDir == null)
return;
File uid = new File(worldDir, "uid.dat");
if (uid.exists()) {
try {
Files.deleteIfExists(uid.toPath());
} catch (IOException ignored) {
}
}
File lock = new File(worldDir, "session.lock");
if (lock.exists()) {
try {
Files.deleteIfExists(lock.toPath());
} catch (IOException ignored) {
}
}
}
private static void deleteRecursive(Path root) throws IOException {
if (!Files.exists(root))
return;
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (exc != null)
throw exc;
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
private static void copyRecursive(Path source, Path target) throws IOException {
Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path rel = source.relativize(dir);
Path dest = target.resolve(rel);
if (!Files.exists(dest))
Files.createDirectories(dest);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Path rel = source.relativize(file);
Path dest = target.resolve(rel);
Files.copy(file, dest, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
return FileVisitResult.CONTINUE;
}
});
}
private void evictPlayers(World world) {
if (world.getPlayers().isEmpty())
return;
List<String> mainNames = plugin.getMainWorlds();
World fallback = null;
for (String name : mainNames) {
fallback = Bukkit.getWorld(name);
if (fallback != null)
break;
}
if (fallback == null && !Bukkit.getWorlds().isEmpty())
fallback = Bukkit.getWorlds().get(0);
if (fallback == null) {
plugin.getLogger().severe("Cannot evict players from '" + world.getName() + "': no fallback world.");
return;
}
Location dest = fallback.getSpawnLocation();
for (Player p : new ArrayList<>(world.getPlayers()))
p.teleport(dest);
}
}
+247
View File
@@ -0,0 +1,247 @@
package managers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.Criteria;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.ScoreboardManager;
import main.SpleggOG;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import utils.ScoreboardOGBridge;
import utils.SpleggPlayer;
import utils.TrueOGBoard;
import utils.Utils;
// The sidebar a Splegg player sees while queued and while playing. With Scoreboard-OG
// present it renders through that plugin's sidebar; otherwise a Bukkit board is used.
public class LobbyScoreboard {
private static final HashMap<UUID, Scoreboard> BOARDS = new HashMap<>();
private static final HashMap<UUID, Game> GAMES = new HashMap<>();
private LobbyScoreboard() {
// Static utility.
}
public static void attach(Player player, Game game) {
GAMES.put(player.getUniqueId(), game);
if (ScoreboardOGBridge.isAvailable()) {
ScoreboardOGBridge.claim(player, LobbyScoreboard::title, LobbyScoreboard::lines);
return;
}
final ScoreboardManager manager = Bukkit.getScoreboardManager();
if (manager == null) {
return;
}
final Scoreboard board = manager.getNewScoreboard();
final Objective objective = board.registerNewObjective("splegg_lobby", Criteria.DUMMY, title(player));
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
BOARDS.put(player.getUniqueId(), board);
player.setScoreboard(board);
render(player, game);
}
public static void detach(Player player) {
GAMES.remove(player.getUniqueId());
if (ScoreboardOGBridge.isAvailable()) {
ScoreboardOGBridge.release(player);
return;
}
// Hand the player back to the server's main scoreboard rather than a
// fresh empty one: Scoreboard-OG's world-change hook only reclaims its
// sidebar when the player's Bukkit board is the main scoreboard.
final ScoreboardManager manager = Bukkit.getScoreboardManager();
if (manager != null) {
player.setScoreboard(manager.getMainScoreboard());
}
BOARDS.remove(player.getUniqueId());
}
public static void detachAll() {
for (UUID playerId : new ArrayList<>(GAMES.keySet())) {
final Player player = Bukkit.getPlayer(playerId);
if (player != null) {
detach(player);
}
}
GAMES.clear();
BOARDS.clear();
}
// Scoreboard-OG polls the provider itself; only the Bukkit boards need
// redrawing.
public static void refreshGame(Game game) {
if (game == null || ScoreboardOGBridge.isAvailable()) {
return;
}
final Iterator<SpleggPlayer> it = game.getPlayers().values().iterator();
while (it.hasNext()) {
final SpleggPlayer sp = it.next();
render(sp.getPlayer(), game);
}
}
private static TextComponent title(Player player) {
return Utils.legacySerializerAnyCase(TrueOGBoard.TITLE);
}
private static List<Component> lines(Player player) {
final List<Component> out = new ArrayList<>();
final Game game = GAMES.get(player.getUniqueId());
if (game == null) {
return out;
}
for (String line : rawLines(player, game)) {
out.add(Utils.legacySerializerAnyCase(line));
}
return out;
}
// Top to bottom in the network board's layout: a blank, labelled blocks split
// by blanks, then the site footer. Queue card counts down; match card scores.
private static List<String> rawLines(Player player, Game game) {
final List<String> lines = new ArrayList<>();
lines.add("");
lines.add("&eMap:");
lines.add(game.getMap() != null ? "&f" + TrueOGBoard.fit(game.getMap().getName(), TrueOGBoard.VALUE_WIDTH)
: "&7Voting...");
lines.add("");
if (game.getStatus() == Status.INGAME) {
final SpleggPlayer self = game.getPlayers().get(player.getUniqueId());
final int broken = self == null ? 0 : self.getBroken();
lines.add("&aAlive: &f" + game.getPlayers().size());
lines.add("");
lines.add("&eBlocks: &f" + TrueOGBoard.compact(broken));
lines.add("");
lines.add("&6Time: &f" + TrueOGBoard.clock(game.getCount()));
} else {
final int currentPlayers = game.getPlayers().size();
lines.add("&6Players:");
lines.add("&f" + currentPlayers + "&7/&f" + game.getMaxPlayers());
lines.add("");
lines.add("&6Starting in:");
if (game.isStarting()) {
lines.add("&f" + TrueOGBoard.clock(game.getLobbyCount()));
} else {
final int required = Math.max(2, SpleggOG.getPlugin().getConfig().getInt("Options.AutoStartPlayers"));
final int needed = Math.max(0, required - currentPlayers);
lines.add(needed == 0 ? "&aReady" : "&7Need " + needed + " more");
}
}
lines.add("");
lines.add(TrueOGBoard.FOOTER);
return lines;
}
private static void render(Player player, Game game) {
final Scoreboard board = BOARDS.get(player.getUniqueId());
if (board == null) {
return;
}
final Objective objective = board.getObjective("splegg_lobby");
if (objective == null) {
return;
}
for (String entry : board.getEntries()) {
board.resetScores(entry);
}
// Bukkit boards score from the bottom, so the top line gets the highest score.
// Entries must be unique, so repeated blanks get a different width each.
final List<String> lines = rawLines(player, game);
final StringBuilder blank = new StringBuilder();
int score = lines.size();
for (String line : lines) {
String entry = line;
if (line.isEmpty()) {
blank.append(' ');
entry = blank.toString();
}
objective.getScore(Utils.legacySectionize(entry)).setScore(score--);
}
}
}
-78
View File
@@ -1,78 +0,0 @@
package managers;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
public class Rollback {
private String world;
private Material previd;
private Material newid;
private BlockData prevdata;
private BlockData newdata;
private int x;
private int y;
private int z;
public Rollback(String world, Material previd, BlockData prevdata, int x, int y, int z) {
this.world = world;
this.previd = previd;
this.prevdata = prevdata;
this.newid = Material.AIR;
this.newdata = null;
this.x = x;
this.y = y;
this.z = z;
}
public String getWorld() {
return this.world;
}
public BlockData getPrevdata() {
return this.prevdata;
}
public BlockData getNewdata() {
return this.newdata;
}
public Material getPrevid() {
return this.previd;
}
public Material getNewid() {
return this.newid;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public int getZ() {
return this.z;
}
}
+1 -4
View File
@@ -2,9 +2,6 @@ package managers;
public enum Status {
DISABLED,
LOBBY,
INGAME,
ENDING;
DISABLED, LOBBY, INGAME, ENDING;
}
@@ -0,0 +1,66 @@
package managers;
import java.util.Collections;
import java.util.List;
import org.bukkit.World;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator;
// Pure-void generator for Splegg lobby and arena worlds.
// Saved chunks (from the .mca region files of the copied world) still load from
// disk. Anything outside the saved area generates as void instead of the
// level.dat default, which is what admins asked MyWorlds for when they ran
// /mw create <world> void -- a plain directory copy loses that setting.
public class VoidChunkGenerator extends ChunkGenerator {
@Override
public boolean shouldGenerateNoise() {
return false;
}
@Override
public boolean shouldGenerateSurface() {
return false;
}
@Override
public boolean shouldGenerateCaves() {
return false;
}
@Override
public boolean shouldGenerateDecorations() {
return false;
}
@Override
public boolean shouldGenerateMobs() {
return false;
}
@Override
public boolean shouldGenerateStructures() {
return false;
}
@Override
public List<BlockPopulator> getDefaultPopulators(World world) {
return Collections.emptyList();
}
}
+53
View File
@@ -0,0 +1,53 @@
package managers;
import config.Map;
public class VotingMap {
private final int id;
private final Map map;
private int votes;
public VotingMap(int id, Map map) {
this.id = id;
this.map = map;
this.votes = 0;
}
public int getId() {
return this.id;
}
public Map getMap() {
return this.map;
}
public int getVotes() {
return this.votes;
}
public void incrementVotes() {
this.votes++;
}
public void decrementVotes() {
if (this.votes > 0) {
this.votes--;
}
}
}
+32 -26
View File
@@ -2,53 +2,59 @@ package runnables;
import main.SpleggOG;
import managers.Game;
import managers.LobbyScoreboard;
import managers.Status;
public class GameTime implements Runnable {
SpleggOG splegg;
Game game;
SpleggOG splegg;
Game game;
public GameTime(SpleggOG splegg, Game game) {
public GameTime(SpleggOG splegg, Game game) {
this.splegg = splegg;
this.game = game;
this.splegg = splegg;
this.game = game;
}
}
public void run() {
public void run() {
if (this.game.getCount() > 0) {
if (game.getStatus() != Status.INGAME) {
this.splegg.games.checkWinner(this.game);
game.stopGameTimer();
return;
if (this.game.getCount() % 300 == 0) {
}
this.splegg.game.ingameTimer(this.game.getCount(), this.game.getPlayers());
if (splegg.games.checkWinner(game)) {
}
return;
if (this.game.getCount() % 30 == 0 && this.game.getCount() < 60) {
}
this.splegg.game.ingameTimer(this.game.getCount(), this.game.getPlayers());
final int remaining = game.tickTime();
LobbyScoreboard.refreshGame(game);
if (remaining <= 0) {
}
splegg.chat.bc(splegg.getConfig().getString("Messages.Timelimitreached"), game);
splegg.game.stopGame(game, game.getPlayers().size());
return;
if (this.game.getCount() <= 5 && this.game.getCount() >= 1) {
}
this.splegg.chat.bc(splegg.getConfig().getString("Messages.EndingTimer").replaceAll("&", "§").replaceAll("%timer%", String.valueOf(this.game.getCount())), this.game);
if (remaining % 300 == 0 || remaining % 30 == 0 && remaining < 60) {
}
splegg.game.ingameTimer(remaining, game.getPlayers());
}
else {
}
this.game.stopGameTimer();
if (remaining <= 5) {
this.splegg.chat.bc(splegg.getConfig().getString("Messages.Timelimitreached"), this.game);
this.splegg.game.stopGame(this.game, 1);
splegg.chat.bc(splegg.getConfig().getString("Messages.EndingTimer").replaceAll("%timer%",
String.valueOf(remaining)), game);
}
}
}
}
}
}
+64 -50
View File
@@ -7,89 +7,103 @@ import org.bukkit.Sound;
import main.SpleggOG;
import managers.Game;
import managers.LobbyScoreboard;
import utils.SpleggPlayer;
public class LobbyCountdown implements Runnable {
int lobbycount;
Game game;
int lobbycount;
Game game;
// Set by /spforcestart so the countdown ignores Options.AutoStartPlayers.
boolean ignorePlayerCount;
public LobbyCountdown(SpleggOG splegg, Game game, int lobbycount) {
public LobbyCountdown(SpleggOG splegg, Game game, int lobbycount) {
this.game = game;
this.lobbycount = lobbycount;
this(splegg, game, lobbycount, false);
}
}
public void run() {
public LobbyCountdown(SpleggOG splegg, Game game, int lobbycount, boolean ignorePlayerCount) {
if (this.lobbycount >= 1) {
this.game = game;
this.lobbycount = lobbycount;
this.ignorePlayerCount = ignorePlayerCount;
this.lobbycount--;
this.game.setLobbyCount(this.lobbycount);
}
SpleggPlayer sp;
Iterator<?> var2 = this.game.getPlayers().values().iterator();
while(var2.hasNext()) {
public void run() {
sp = (SpleggPlayer) var2.next();
sp.getPlayer().setLevel(this.lobbycount);
sp.getPlayer().setExp((float)((double) this.game.getLobbyCount() * 0.008D));
if (this.lobbycount >= 1) {
}
this.lobbycount--;
this.game.setLobbyCount(this.lobbycount);
this.game.selectMapFromVoteIfDue(this.lobbycount);
this.game.getSign().update(this.game.getMap(), false);
SpleggPlayer sp;
Iterator<?> playersInGameIterator = this.game.getPlayers().values().iterator();
while (playersInGameIterator.hasNext()) {
if (this.lobbycount % 25 == 0) {
sp = (SpleggPlayer) playersInGameIterator.next();
sp.getPlayer().setLevel(this.lobbycount);
sp.getPlayer().setExp((float) ((double) this.game.getLobbyCount() * 0.008D));
SpleggOG.getPlugin().chat.bc(SpleggOG.getPlugin().getConfig().getString("Messages.LobbyTimer").replaceAll("&", "§").replaceAll("%timer%", String.valueOf(this.lobbycount)), this.game);
}
}
this.game.updateSigns();
LobbyScoreboard.refreshGame(this.game);
if (this.lobbycount <= 10 && this.lobbycount >= 1) {
if (this.lobbycount % 25 == 0) {
SpleggOG.getPlugin().chat.bc(SpleggOG.getPlugin().getConfig().getString("Messages.LobbyTimer").replaceAll("&", "§").replaceAll("%timer%", String.valueOf(this.lobbycount)), this.game);
SpleggOG.getPlugin().chat.bc(SpleggOG.getPlugin().getConfig().getString("Messages.LobbyTimer")
.replaceAll("%timer%", String.valueOf(this.lobbycount)), this.game);
var2 = this.game.getPlayers().values().iterator();
while(var2.hasNext()) {
}
sp = (SpleggPlayer) var2.next();
sp.getPlayer().playSound(sp.getPlayer().getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1.0F, 1.0F);
if (this.lobbycount <= 10 && this.lobbycount >= 1) {
}
SpleggOG.getPlugin().chat.bc(SpleggOG.getPlugin().getConfig().getString("Messages.LobbyTimer")
.replaceAll("%timer%", String.valueOf(this.lobbycount)), this.game);
}
playersInGameIterator = this.game.getPlayers().values().iterator();
while (playersInGameIterator.hasNext()) {
if (this.lobbycount <= 0) {
sp = (SpleggPlayer) playersInGameIterator.next();
sp.getPlayer().playSound(sp.getPlayer().getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1.0F,
1.0F);
var2 = this.game.getPlayers().values().iterator();
while(var2.hasNext()) {
}
sp = (SpleggPlayer) var2.next();
}
}
return;
}
}
}
else if (this.game.getPlayers().size() > 1) {
Bukkit.getScheduler().cancelTask(this.game.getCounterID());
Bukkit.getScheduler().cancelTask(this.game.getCounterID());
SpleggOG.getPlugin().game.startGame(this.game);
if ((this.game.getPlayers().size()) >= (this.ignorePlayerCount ? 1 : 2)
&& SpleggOG.getPlugin().game.startGame(this.game))
{
}
else {
return;
SpleggOG.getPlugin().chat.bc(SpleggOG.getPlugin().getConfig().getString("Messages.NoEnoughPlayers").replaceAll("&", "§"), this.game);
Bukkit.getScheduler().cancelTask(this.game.getCounterID());
}
this.game.setLobbyCount(31);
this.game.setStarting(false);
this.game.getSign().update(this.game.getMap(), false);
this.game.startCountdown();
if (this.game.getPlayers().size() < (this.ignorePlayerCount ? 1 : 2)) {
}
SpleggOG.getPlugin().chat.bc(SpleggOG.getPlugin().getConfig().getString("Messages.NoEnoughPlayers"),
this.game);
}
}
}
// Not enough players, or no map could be loaded: back to waiting with a
// fresh vote.
this.game.setStarting(false);
this.game.setLobbyCount(SpleggOG.getPlugin().getConfig().getInt("Options.Timer"));
this.game.resetVoting();
this.game.updateSigns();
LobbyScoreboard.refreshGame(this.game);
}
}
@@ -0,0 +1,41 @@
package runnables;
import org.bukkit.Bukkit;
import managers.Game;
import managers.Status;
public class MapVotingRunnable implements Runnable {
private int time;
private final Game game;
public MapVotingRunnable(Game game) {
this.game = game;
this.time = 0;
}
@Override
public void run() {
if (this.game.getStatus() != Status.LOBBY || !this.game.isVotingRunning()) {
Bukkit.getScheduler().cancelTask(this.game.getVotingReminderTaskId());
this.game.clearVotingReminderTaskId();
return;
}
this.time++;
if (this.time == this.game.getVotingReminderSeconds()) {
this.game.sendVotingMessage(null);
this.time = 0;
}
}
}
+37
View File
@@ -0,0 +1,37 @@
package signs;
import org.bukkit.scheduler.BukkitRunnable;
import main.SpleggOG;
/**
* Redraws every registered join sign once a second, mirroring TheHerobrine-OG's
* sign updater. Event-driven updates still happen for instant feedback; this
* pass catches everything they miss (worlds loading late, aggregate status
* drift, signs edited externally).
*/
public class JoinSignUpdater extends BukkitRunnable {
private final SpleggOG splegg;
public JoinSignUpdater(SpleggOG splegg) {
this.splegg = splegg;
}
// Immediate redraw, used once the lobbies come online.
public static void redrawNow(SpleggOG splegg) {
LobbySign.updateAll(splegg);
}
@Override
public void run() {
LobbySign.updateAll(this.splegg);
}
}
+274 -130
View File
@@ -1,214 +1,358 @@
package signs;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import config.Map;
import main.SpleggOG;
import managers.Game;
import managers.Status;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import utils.Utils;
/**
* Renders join signs. A sign is bound to one lobby (SP1) or to any lobby, in
* which case it shows the lobby a click would put the player in,
* TheHerobrine-OG style. A {@link JoinSignUpdater} redraws every sign once a
* second, so event-driven update calls only shorten the latency.
*/
public class LobbySign {
SpleggOG splegg;
Map map;
// The sign key that means "whichever lobby is best to join".
public static final String ANY = "any";
public LobbySign(Map map, SpleggOG s) {
// Sign lines hold 15 visible characters; longer names would clip
// mid-color-code, so they are truncated before rendering.
private static final int MAX_LINE_LENGTH = 15;
this.splegg = s;
this.map = map;
private final SpleggOG splegg;
private final String key;
}
public LobbySign(String key, SpleggOG splegg) {
private static Collection<Material> signMaterials = new ArrayList<>();
static {
this.splegg = splegg;
this.key = normalizeKey(key);
signMaterials.add(Material.ACACIA_SIGN);
signMaterials.add(Material.DARK_OAK_SIGN);
signMaterials.add(Material.OAK_SIGN);
signMaterials.add(Material.BIRCH_SIGN);
signMaterials.add(Material.SPRUCE_SIGN);
signMaterials.add(Material.JUNGLE_SIGN);
signMaterials.add(Material.CRIMSON_SIGN);
signMaterials.add(Material.WARPED_SIGN);
signMaterials.add(Material.ACACIA_WALL_SIGN);
signMaterials.add(Material.DARK_OAK_WALL_SIGN);
signMaterials.add(Material.OAK_WALL_SIGN);
signMaterials.add(Material.BIRCH_WALL_SIGN);
signMaterials.add(Material.SPRUCE_WALL_SIGN);
signMaterials.add(Material.JUNGLE_WALL_SIGN);
signMaterials.add(Material.CRIMSON_WALL_SIGN);
signMaterials.add(Material.WARPED_WALL_SIGN);
}
}
// A key that names a lobby stays a lobby id; anything else (including a map
// name from an older version) means any lobby.
public static String normalizeKey(String key) {
public void create(Location location, final Map map) {
if (key == null || key.isBlank()) {
String loc = LobbySignUtils.get().locationToString(location);
this.splegg.maps.c.addSign(map.getName(), loc);
return ANY;
if (this.map == null) {
}
this.map = map;
final Game game = SpleggOG.getPlugin().games == null ? null : SpleggOG.getPlugin().games.resolveLobby(key);
return game != null ? game.getLobbyId() : ANY;
}
}
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(this.splegg, new Runnable() {
public String getKey() {
public void run() {
return this.key;
LobbySign.this.update(map, true);
}
}
public void create(Location location) {
}, 5L);
this.splegg.maps.c.addSign(this.key, LobbySignUtils.get().locationToString(location));
this.splegg.getServer().getScheduler().runTaskLater(this.splegg, () -> updateAll(this.splegg), 5L);
}
}
public void delete(Location location) {
public void delete(String storedKey, Location location) {
String loc = LobbySignUtils.get().locationToString(location);
this.splegg.maps.c.delSign(this.map.getName(), loc);
this.splegg.maps.c.delSign(storedKey, LobbySignUtils.get().locationToString(location));
this.splegg.maps.c.saveMaps();
this.map = null;
}
}
// Redraws every registered sign under every stored key.
public static void updateAll(SpleggOG splegg) {
public void update(Map map, boolean force) {
if (splegg == null || splegg.maps == null || splegg.maps.c == null || splegg.games == null) {
Iterator<?> var4 = this.splegg.maps.c.maps.getStringList("Signs." + map.getName() + ".lobby").iterator();
while(var4.hasNext()) {
return;
String loc = (String)var4.next();
Location location = LobbySignUtils.get().stringToLocation(loc);
if (! signMaterials.contains(location.getBlock().getType())) {
}
location.getBlock().setType(Material.OAK_WALL_SIGN);
for (String storedKey : splegg.maps.c.getSignKeys()) {
}
new LobbySign(storedKey, splegg).update(storedKey);
Sign s = (Sign) location.getBlock().getState();
String[] sign;
if (force) {
}
String[] array = new String[]{splegg.getConfig().getString("Sings.Restarting.1").replaceAll("&", "§").replaceAll("#", "§"), splegg.getConfig().getString("Sings.Restarting.2").replaceAll("&", "§"), splegg.getConfig().getString("Sings.Restarting.3").replaceAll("&", "§").replaceAll("%map%", map.getName()), splegg.getConfig().getString("Sings.Restarting.4").replaceAll("&", "§").replaceAll("#", "§")};
this.setSign(array, s);
sign = new String[4];
Game game = this.splegg.games.getGame(map.getName());
if (game == null) {
}
sign[0] = "";
sign[1] = ChatColor.DARK_RED + "Please remove";
sign[2] = ChatColor.DARK_RED + "this sign";
sign[3] = "";
public void update(String storedKey) {
}
else {
final String[] lines = renderLines();
sign[0] = splegg.getConfig().getString("Sings.Format.1").replaceAll("&", "§").replaceAll("%status%", this.getFancyStatus(game)).replaceAll("%map%", map.getName()).replaceAll("%count%/%maxcount%", this.getPlayers(game));
sign[1] = splegg.getConfig().getString("Sings.Format.2").replaceAll("&", "§").replaceAll("%status%", this.getFancyStatus(game)).replaceAll("%map%", map.getName()).replaceAll("%count%/%maxcount%", this.getPlayers(game));
sign[2] = splegg.getConfig().getString("Sings.Format.3").replaceAll("&", "§").replaceAll("%map%", map.getName()).replaceAll("%status%", this.getFancyStatus(game)).replaceAll("%count%/%maxcount%", this.getPlayers(game));
sign[3] = splegg.getConfig().getString("Sings.Format.4").replaceAll("&", "§").replaceAll("%count%/%maxcount%", this.getPlayers(game)).replaceAll("%status%", this.getFancyStatus(game)).replaceAll("%map%", map.getName());
for (String loc : this.splegg.maps.c.getSigns(storedKey)) {
}
final Location location = LobbySignUtils.get().stringToLocation(loc);
final World world = location.getWorld();
if (world == null) {
Bukkit.getScheduler().scheduleSyncDelayedTask(this.splegg, new SignDelay(sign, s), 40L);
continue;
}
else {
}
Game game = this.splegg.games.getGame(map.getName());
sign = new String[]{splegg.getConfig().getString("Sings.Format.1").replaceAll("&", "§").replaceAll("%status%", this.getFancyStatus(game)).replaceAll("%map%", map.getName()).replaceAll("%count%/%maxcount%", this.getPlayers(game)), splegg.getConfig().getString("Sings.Format.2").replaceAll("&", "§").replaceAll("%status%", this.getFancyStatus(game)).replaceAll("%map%", map.getName()).replaceAll("%count%/%maxcount%", this.getPlayers(game)), splegg.getConfig().getString("Sings.Format.3").replaceAll("&", "§").replaceAll("%map%", map.getName()).replaceAll("%status%", this.getFancyStatus(game)).replaceAll("%count%/%maxcount%", this.getPlayers(game)), splegg.getConfig().getString("Sings.Format.4").replaceAll("&", "§").replaceAll("%count%/%maxcount%", this.getPlayers(game)).replaceAll("%status%", this.getFancyStatus(game)).replaceAll("%map%", map.getName())};
this.setSign(sign, s);
// Never force a chunk load just to redraw a sign.
if (!world.isChunkLoaded(location.getBlockX() >> 4, location.getBlockZ() >> 4)) {
}
continue;
}
}
}
final BlockState state = location.getBlock().getState();
if (!(state instanceof Sign)) {
private String getPlayers(Game game) {
continue;
String players = "";
if (game.getStatus() == Status.DISABLED) {
}
players = "";
this.setSign(lines, (Sign) state);
}
else if (game.getMap().getSpawnCount() <= 1) {
}
players = "Players: " + game.getPlayers().size();
}
}
else {
// The lobby a click on this sign would join, or null.
public Game resolveTarget(org.bukkit.entity.Player player) {
players = game.getPlayers().size() + "/" + game.getMap().getSpawnCount();
if (ANY.equals(this.key)) {
}
return this.splegg.games.findBestLobby(player);
return players;
}
}
return this.splegg.games.resolveLobby(this.key);
private void setSign(String[] lines, Sign s) {
}
for(int i = 0; i < lines.length; ++i) {
private String[] renderLines() {
TextComponent signLineContainer = Component.text(lines[i]);
s.line(i, signLineContainer);
final String lobby;
final String map;
final String status;
final int count;
final int maxCount;
}
if (ANY.equals(this.key)) {
s.update();
final List<Game> games = this.splegg.games.all();
Game display = null;
int displayPriority = Integer.MAX_VALUE;
int players = 0;
for (Game game : games) {
}
players += game.getPlayers().size();
final int priority = priority(game);
if (priority < displayPriority) {
private String getFancyStatus(Game game) {
displayPriority = priority;
display = game;
String status = new String();
Status st = game.getStatus();
if (st == Status.LOBBY) {
}
if (game.isStarting()) {
}
status = splegg.getConfig().getString("Sings.Status.Join").replaceAll("&", "§");
count = players;
maxCount = games.isEmpty() ? 0 : games.get(0).getMaxPlayers() * games.size();
lobby = display == null ? "Any" : display.getLobbyId();
map = display == null ? "Splegg" : display.getMapDisplayName();
status = aggregateStatus(games);
}
else {
} else {
status = splegg.getConfig().getString("Sings.Status.Join").replaceAll("&", "§");
final Game game = this.splegg.games.resolveLobby(this.key);
if (game == null) {
}
lobby = this.key;
map = this.key;
count = 0;
maxCount = 0;
status = configStatus("Disabled", "&cDISABLED");
}
else if (st == Status.DISABLED) {
} else {
status = splegg.getConfig().getString("Sings.Status.Disabled").replaceAll("&", "§");
lobby = game.getLobbyId();
map = game.getMapDisplayName();
count = game.getPlayers().size();
maxCount = game.getMaxPlayers();
status = statusFor(game);
}
else if (st == Status.INGAME) {
}
status = splegg.getConfig().getString("Sings.Status.Started").replaceAll("&", "§");
}
}
else {
final String[] lines = new String[4];
final String[] defaults = { "&4Splegg", "&6%map%", "%status%", "&0%count%&8/&0%maxcount%" };
for (int i = 0; i < 4; i++) {
status = ChatColor.DARK_GREEN + st.toString().toLowerCase();
final String raw = this.splegg.getConfig().getString("Sings.Format." + (i + 1), defaults[i]);
lines[i] = raw.replace("%status%", status).replace("%map%", truncate(map))
.replace("%lobby%", truncate(lobby)).replace("%count%", String.valueOf(count))
.replace("%maxcount%", String.valueOf(maxCount));
}
}
return status;
return lines;
}
}
}
private static String truncate(String subject) {
return subject.length() > MAX_LINE_LENGTH ? subject.substring(0, MAX_LINE_LENGTH) : subject;
}
private String configStatus(String key, String fallback) {
return this.splegg.getConfig().getString("Sings.Status." + key, fallback);
}
private String statusFor(Game game) {
switch (game.getStatus()) {
case LOBBY -> {
if (game.getPlayers().size() >= game.getMaxPlayers()) {
return configStatus("Full", "&4&lFULL");
}
return game.isStarting() ? configStatus("Starting", "&5&lSTARTING") : configStatus("Join", "&2&lJOIN");
}
case INGAME -> {
return configStatus("Started", "&3&lLIVE");
}
case ENDING -> {
return configStatus("Ending", "&8&lENDING");
}
default -> {
return configStatus("Disabled", "&cDISABLED");
}
}
}
// Mirrors TheHerobrine-OG's join-sign status table: STARTING and JOIN win
// over LIVE, an all-live set reads LIVE, an all-ending set reads ENDING, and
// anything else that cannot be joined reads FULL.
private String aggregateStatus(List<Game> games) {
if (games.isEmpty()) {
return configStatus("Disabled", "&cDISABLED");
}
boolean anyJoinable = false;
boolean anyStarting = false;
int liveCount = 0;
int endingCount = 0;
for (Game g : games) {
final Status st = g.getStatus();
if (st == Status.LOBBY) {
if (g.getPlayers().size() < g.getMaxPlayers()) {
anyJoinable = true;
if (g.isStarting()) {
anyStarting = true;
}
}
} else if (st == Status.INGAME) {
liveCount++;
} else if (st == Status.ENDING) {
endingCount++;
}
}
if (anyJoinable && anyStarting) {
return configStatus("Starting", "&5&lSTARTING");
}
if (anyJoinable) {
return configStatus("Join", "&2&lJOIN");
}
if (liveCount == games.size()) {
return configStatus("Started", "&3&lLIVE");
}
if (endingCount == games.size()) {
return configStatus("Ending", "&8&lENDING");
}
return configStatus("Full", "&4&lFULL");
}
private static int priority(Game game) {
final boolean room = game.getPlayers().size() < game.getMaxPlayers();
if (game.getStatus() == Status.LOBBY && game.isStarting() && room)
return 0;
if (game.getStatus() == Status.LOBBY && room)
return 1;
if (game.getStatus() == Status.INGAME)
return 2;
if (game.getStatus() == Status.LOBBY)
return 3;
if (game.getStatus() == Status.ENDING)
return 4;
return 5;
}
private void setSign(String[] lines, Sign s) {
for (int i = 0; i < lines.length; ++i) {
final TextComponent signLineContainer = Utils.legacySerializerAnyCase(lines[i] != null ? lines[i] : "");
s.line(i, signLineContainer);
}
s.update(false, false);
}
}
+42 -35
View File
@@ -10,65 +10,72 @@ import main.SpleggOG;
public class LobbySignUtils {
public static LobbySignUtils lsu = new LobbySignUtils();
public static LobbySignUtils lsu = new LobbySignUtils();
public static LobbySignUtils get() {
public static LobbySignUtils get() {
return lsu;
return lsu;
}
}
public boolean isLobbySign(Location location, String name) {
public boolean isLobbySign(Location location, String name) {
String locString = this.locationToString(location);
List<String> signLocs = SpleggOG.getPlugin().maps.c.maps.getStringList("Signs." + name + ".lobby");
String locString = this.locationToString(location);
List<String> signLocs = SpleggOG.getPlugin().maps.c.maps.getStringList("Signs." + name + ".lobby");
return signLocs.contains(locString);
return signLocs.contains(locString);
}
}
public String locationToString(Location l) {
public String locationToString(Location l) {
String w = l.getWorld().getName();
String w = l.getWorld().getName();
int x = l.getBlockX();
int y = l.getBlockY();
int z = l.getBlockZ();
int x = l.getBlockX();
int y = l.getBlockY();
int z = l.getBlockZ();
return w + "." + x + "." + y + "." + z;
return w + "." + x + "." + y + "." + z;
}
}
public Location stringToLocation(String s) {
// Parsed from the right so a world name containing dots still resolves.
public Location stringToLocation(String s) {
String[] str = s.split("\\.");
String[] str = s.split("\\.");
if (str.length < 4) {
World w = Bukkit.getServer().getWorld(str[0]);
return new Location(null, 0, 0, 0);
int x = Integer.parseInt(str[1]);
int y = Integer.parseInt(str[2]);
int z = Integer.parseInt(str[3]);
}
return new Location(w, (double) x, (double) y, (double) z);
int z = Integer.parseInt(str[str.length - 1]);
int y = Integer.parseInt(str[str.length - 2]);
int x = Integer.parseInt(str[str.length - 3]);
String worldName = String.join(".", java.util.Arrays.copyOf(str, str.length - 3));
}
World w = Bukkit.getServer().getWorld(worldName);
public boolean compareLocations(Location one, Location two) {
return new Location(w, (double) x, (double) y, (double) z);
String w = one.getWorld().getName();
}
int x = one.getBlockX();
int y = one.getBlockY();
int z = one.getBlockZ();
public boolean compareLocations(Location one, Location two) {
String checkw = two.getWorld().getName();
String w = one.getWorld().getName();
int checkx = two.getBlockX();
int checky = two.getBlockY();
int checkz = two.getBlockZ();
int x = one.getBlockX();
int y = one.getBlockY();
int z = one.getBlockZ();
return w.equalsIgnoreCase(checkw) && x == checkx && y == checky && z == checkz;
String checkw = two.getWorld().getName();
}
int checkx = two.getBlockX();
int checky = two.getBlockY();
int checkz = two.getBlockZ();
return w.equalsIgnoreCase(checkw) && x == checkx && y == checky && z == checkz;
}
}
-33
View File
@@ -1,33 +0,0 @@
package signs;
import org.bukkit.block.Sign;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
public class SignDelay implements Runnable {
String[] data;
Sign sign;
public SignDelay(String[] data, Sign sign) {
this.data = data;
this.sign = sign;
}
public void run() {
for(int i = 0; i < this.data.length; i++) {
TextComponent signData = Component.text(this.data[i]);
this.sign.line(i, signData);
}
this.sign.update();
}
}
@@ -0,0 +1,23 @@
package stats;
import org.bukkit.entity.Player;
import net.trueog.utilitiesog.UtilitiesOG;
// Registers the sp_score and sp_rank MiniPlaceholders through Utilities-OG.
public final class SpleggPlaceholders {
private SpleggPlaceholders() {
}
public static void register() {
UtilitiesOG.registerAudiencePlaceholder("sp_score",
(Player player) -> String.valueOf(SpleggStats.get().getPoints(player.getUniqueId())));
UtilitiesOG.registerAudiencePlaceholder("sp_rank",
(Player player) -> SpleggStats.get().getRank(player.getUniqueId()).getDisplay());
}
}
+77
View File
@@ -0,0 +1,77 @@
package stats;
// Hive Java Splegg rank ladder; The Eggspert is reserved for the top scorer.
public enum SpleggRank {
FARMER("&7Farmer", 0), COOK("&fCook", 5000), CHEF("&eChef", 10000), MASTER("&6Master", 100000),
HUMPTY_DUMPTY("&bHumpty Dumpty", 250000), RAMSAY("&cRamsay", 500000), HOT_N_SPICY("&4Hot'n'Spicy", 1000000),
SPLEGG_HEAD("&aSplegg Head", 1200000), BACON("&dBacon", 1500000), SUNNYSIDE("&eSunnyside", 2000000),
DELICIOUS("&2Delicious", 3000000), EASTER_BUNNY("&5Easter Bunny", 4000000), SCRAMBLED("&3Scrambled", 5000000),
OLIVER("&6&lOliver", 7500000), EGGSPERT("&4&k# &r&c&lThe Eggspert", Integer.MAX_VALUE);
private final String display;
private final int lowBound;
private SpleggRank(String display, int lowBound) {
this.display = display;
this.lowBound = lowBound;
}
public String getDisplay() {
return display;
}
public int getLowBound() {
return lowBound;
}
// The Eggspert needs the top score and the highest regular rank, so #1 still
// climbs the ladder.
public static int topPlayerGate() {
int gate = 0;
for (SpleggRank rank : values()) {
if (rank != EGGSPERT) {
gate = Math.max(gate, rank.getLowBound());
}
}
return gate;
}
// Highest ladder rank whose threshold the points meet; never the reserved one.
public static SpleggRank findRank(int points) {
final SpleggRank[] ranks = values();
for (int i = ranks.length - 1; i >= 0; i--) {
if (ranks[i] == EGGSPERT) {
continue;
}
if (points >= ranks[i].getLowBound()) {
return ranks[i];
}
}
return FARMER;
}
}
+238
View File
@@ -0,0 +1,238 @@
package stats;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;
import main.SpleggOG;
// Persistent per-player Splegg points behind the sp_score and sp_rank placeholders.
// Kept in memory and flushed to stats.yml off the main thread.
public class SpleggStats {
private static final long AUTOSAVE_TICKS = 20L * 30L;
private static SpleggStats instance;
private final SpleggOG plugin;
private final File file;
private final Map<UUID, Integer> points = new ConcurrentHashMap<>();
private final Map<UUID, String> names = new ConcurrentHashMap<>();
private final AtomicBoolean dirty = new AtomicBoolean(false);
private volatile UUID topPlayer;
private volatile int topPoints;
private BukkitTask autosaveTask;
public SpleggStats(SpleggOG plugin) {
this.plugin = plugin;
this.file = new File(plugin.getDataFolder(), "stats.yml");
instance = this;
}
public static SpleggStats get() {
return instance;
}
// Points awarded per block an egg destroys.
public static int blockPoints() {
return SpleggOG.getPlugin().getConfig().getInt("Points.BlockBroken", 1);
}
// Points awarded to the match winner.
public static int winPoints() {
return SpleggOG.getPlugin().getConfig().getInt("Points.Win", 50);
}
public void load() {
points.clear();
names.clear();
topPlayer = null;
topPoints = 0;
if (!file.exists()) {
return;
}
final YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
final ConfigurationSection players = config.getConfigurationSection("players");
if (players == null) {
return;
}
for (String key : players.getKeys(false)) {
final UUID uuid;
try {
uuid = UUID.fromString(key);
} catch (IllegalArgumentException invalid) {
plugin.getLogger().warning("Skipping stats.yml entry with an invalid UUID: " + key);
continue;
}
points.put(uuid, Math.max(players.getInt(key + ".points", 0), 0));
final String name = players.getString(key + ".name");
if (name != null) {
names.put(uuid, name);
}
bumpTop(uuid);
}
plugin.getLogger().info("Loaded Splegg points for " + points.size() + " players.");
}
public void startAutosave() {
autosaveTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> {
if (dirty.compareAndSet(true, false)) {
save();
}
}, AUTOSAVE_TICKS, AUTOSAVE_TICKS);
}
// Final synchronous flush; safe to call from onDisable.
public void shutdown() {
if (autosaveTask != null) {
autosaveTask.cancel();
autosaveTask = null;
}
if (dirty.compareAndSet(true, false)) {
save();
}
if (instance == this) {
instance = null;
}
}
public int getPoints(UUID uuid) {
return points.getOrDefault(uuid, 0);
}
public void addPoints(Player player, int amount) {
if (player == null || amount <= 0) {
return;
}
final UUID uuid = player.getUniqueId();
points.merge(uuid, amount, Integer::sum);
names.put(uuid, player.getName());
bumpTop(uuid);
dirty.set(true);
}
// Same rule as TheHerobrine-OG's Death Bringer: the reserved rank belongs to
// the top scorer.
public SpleggRank getRank(UUID uuid) {
final int score = getPoints(uuid);
if (uuid.equals(topPlayer) && score >= SpleggRank.topPlayerGate()) {
return SpleggRank.EGGSPERT;
}
return SpleggRank.findRank(score);
}
public UUID getTopPlayer() {
return topPlayer;
}
private void bumpTop(UUID uuid) {
final int score = getPoints(uuid);
if (uuid.equals(topPlayer) || score > topPoints) {
topPlayer = uuid;
topPoints = score;
}
}
private synchronized void save() {
final YamlConfiguration config = new YamlConfiguration();
for (Map.Entry<UUID, Integer> entry : new HashMap<>(points).entrySet()) {
final String base = "players." + entry.getKey();
config.set(base + ".points", entry.getValue());
final String name = names.get(entry.getKey());
if (name != null) {
config.set(base + ".name", name);
}
}
try {
config.save(file);
} catch (IOException error) {
plugin.getLogger().severe("An error occured while saving stats.yml: " + error.getMessage());
dirty.set(true);
}
}
}
@@ -0,0 +1,158 @@
package utils;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.plugin.Plugin;
// Keeps GameModeInventories-OG out of the gamemode changes a minigame stay causes.
//
// GameModeInventories-OG swaps inventory, armour, XP and ender chest on every
// gamemode change, keyed by player and gamemode only, never by world. MyWorlds
// swaps the same data per world bundle and, on entering a world, re-applies the
// gamemode saved with that bundle. A player who was put in adventure or spectator
// inside the minigame therefore flips back to survival in the overworld right
// after MyWorlds restored their real inventory; GMI answers that flip by filing
// the real inventory and ender chest under the minigame gamemode and loading the
// empty SURVIVAL snapshot it took inside the minigame. That is a full wipe.
//
// Negating gamemodeinventories.use through a permission attachment for as long
// as the player is inside minigame territory, and for one tick after leaving it,
// keeps GMI out of every flip on both ends. gamemodeinventories.death is negated
// alongside it: a staff death inside the minigame would otherwise file the kit
// under their survival row and re-apply it on a respawn that lands outside.
// gamemodeinventories.spectator is granted so GMI's restrict_spectator cannot
// cancel the spectator mode a minigame puts eliminated players in.
// Same class in Splegg-OG, TheHerobrine-OG and BuildBattle-OG.
public final class GameModeInventoriesGuard {
private static final String GMI_USE_PERMISSION = "gamemodeinventories.use";
private static final String GMI_DEATH_PERMISSION = "gamemodeinventories.death";
private static final String GMI_SPECTATOR_PERMISSION = "gamemodeinventories.spectator";
private final Plugin plugin;
private final Predicate<Player> insideTerritory;
private final Map<UUID, PermissionAttachment> attachments = new HashMap<>();
public GameModeInventoriesGuard(Plugin plugin, Predicate<Player> insideTerritory) {
this.plugin = plugin;
this.insideTerritory = insideTerritory;
}
// Idempotent. Must run before the first teleport into, or gamemode change
// inside, minigame territory.
public void suspend(Player player) {
if (player == null || attachments.containsKey(player.getUniqueId()))
return;
try {
final PermissionAttachment attachment = player.addAttachment(plugin);
attachment.setPermission(GMI_USE_PERMISSION, false);
attachment.setPermission(GMI_DEATH_PERMISSION, false);
attachment.setPermission(GMI_SPECTATOR_PERMISSION, true);
attachments.put(player.getUniqueId(), attachment);
} catch (IllegalArgumentException | IllegalStateException ex) {
plugin.getLogger().warning("Could not suspend GameModeInventories for " + player.getName() + ": "
+ ex.getMessage() + ". Their inventory is at risk on the way out of the minigame.");
}
}
public boolean isSuspended(Player player) {
return player != null && attachments.containsKey(player.getUniqueId());
}
public void release(Player player) {
if (player != null)
release(player.getUniqueId());
}
public void release(UUID playerId) {
final PermissionAttachment attachment = attachments.remove(playerId);
if (attachment == null)
return;
try {
attachment.remove();
} catch (IllegalArgumentException ignored) {
// The player instance is already gone; the attachment died with it.
}
}
// The world change that follows a teleport event, and MyWorlds' gamemode
// restore inside it, must finish under the suspension, so the release is
// deferred a tick. A player who is back inside territory by then (another
// plugin bounced them straight back) keeps it.
public void releaseAfterLeaving(Player player) {
if (player == null)
return;
if (!plugin.isEnabled()) {
release(player);
return;
}
Bukkit.getScheduler().runTask(plugin, () -> {
if (!player.isOnline()) {
release(player.getUniqueId());
return;
}
if (insideTerritory.test(player))
return;
release(player);
});
}
// Suspends everyone already standing in territory, for /reload and for
// players who were inside before the plugin came up.
public void sweepOnlinePlayers() {
for (Player player : Bukkit.getOnlinePlayers()) {
if (insideTerritory.test(player))
suspend(player);
}
}
public void releaseAll() {
for (UUID playerId : new java.util.ArrayList<>(attachments.keySet()))
release(playerId);
}
}
+67 -56
View File
@@ -1,75 +1,86 @@
package utils;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import java.util.ArrayList;
import java.util.Collection;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
public class InvStore {
public Player player;
public ItemStack[] inv;
public ItemStack[] armour;
public double health;
public int fire;
public int food;
public int level;
public float exp;
public GameMode gamemode;
public Player player;
public double health;
public int fire;
public int food;
public int level;
public float exp;
public Collection<PotionEffect> activePotionEffects;
public InvStore(Player player) {
public InvStore(Player player) {
this.player = player;
this.exp = 0.0F;
this.level = 0;
this.health = 0.0D;
this.food = 0;
this.fire = 0;
this.armour = null;
this.inv = null;
this.player = player;
this.exp = 0.0F;
this.level = 0;
this.health = 0.0D;
this.food = 0;
this.fire = 0;
this.activePotionEffects = new ArrayList<>();
}
}
public void load() {
public void load() {
this.player.getInventory().setContents(this.inv);
this.player.getInventory().setArmorContents(this.armour);
this.player.setExp(this.exp);
this.player.setLevel(this.level);
this.player.setHealthScale(this.health);
this.player.setFoodLevel(this.food);
this.player.setFireTicks(this.fire);
this.player.setGameMode(this.gamemode);
this.player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard());
this.player.updateInventory();
for (PotionEffect effect : this.player.getActivePotionEffects()) {
}
this.player.removePotionEffect(effect.getType());
public void save() {
}
this.inv = this.player.getInventory().getContents();
this.armour = this.player.getInventory().getArmorContents();
this.exp = this.player.getExp();
this.level = this.player.getLevel();
this.food = this.player.getFoodLevel();
this.fire = this.player.getFireTicks();
this.health = this.player.getHealthScale();
this.gamemode = this.player.getGameMode();
this.player.updateInventory();
this.player.addPotionEffects(this.activePotionEffects);
}
this.player.setExp(this.exp);
this.player.setLevel(this.level);
this.player.setFoodLevel(this.food);
this.player.setFireTicks(this.fire);
public void reset() {
final Attribute maxHealthAttribute = Attribute.GENERIC_MAX_HEALTH;
final double maxHealth = this.player.getAttribute(maxHealthAttribute) != null
? this.player.getAttribute(maxHealthAttribute).getValue()
: 20.0D;
final double restoredHealth = this.health > 0.0D ? this.health : maxHealth;
this.player.setHealth(Math.min(restoredHealth, maxHealth));
this.exp = 0.0F;
this.level = 0;
this.health = 0.0D;
this.food = 0;
this.fire = 0;
this.gamemode = null;
this.armour = null;
this.inv = null;
// No gamemode restore here: load() runs after the player is back in the
// overworld, where MyWorlds already re-applied the saved gamemode, and a
// second change there is exactly the flip GameModeInventories-OG answers
// by swapping the real inventory out. No scoreboard reset either:
// LobbyScoreboard.detach() has already handed the board back.
this.player.updateInventory();
}
}
}
public void save() {
this.exp = this.player.getExp();
this.level = this.player.getLevel();
this.food = this.player.getFoodLevel();
this.fire = this.player.getFireTicks();
this.health = this.player.getHealth();
this.activePotionEffects = new ArrayList<>(this.player.getActivePotionEffects());
this.player.updateInventory();
}
public void reset() {
this.exp = 0.0F;
this.level = 0;
this.health = 0.0D;
this.food = 0;
this.fire = 0;
this.activePotionEffects = new ArrayList<>();
}
}
@@ -0,0 +1,245 @@
package utils;
import java.io.File;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import com.bergerkiller.bukkit.mw.WorldConfig;
// Disk-backed record of where each player stood before they entered Splegg territory.
// Same layout as TheHerobrine-OG and BuildBattle-OG so a relog or restart still returns them.
public class PreJoinLocationStore {
private static final long DAY_MILLIS = 86400000L;
private static final long FLUSH_INTERVAL_TICKS = 100L;
private record Entry(String world, double x, double y, double z, float yaw, float pitch, long savedAt) {
static Entry of(Location location) {
return new Entry(location.getWorld().getName(), location.getX(), location.getY(), location.getZ(),
location.getYaw(), location.getPitch(), System.currentTimeMillis());
}
}
private final JavaPlugin plugin;
private final File file;
private final Map<UUID, Entry> entries = new ConcurrentHashMap<>();
private volatile boolean dirty;
private BukkitTask flushTask;
public PreJoinLocationStore(JavaPlugin plugin) {
this.plugin = plugin;
this.file = new File(plugin.getDataFolder(), "prejoin-locations.yaml");
load();
flushTask = Bukkit.getScheduler().runTaskTimer(plugin, this::save, FLUSH_INTERVAL_TICKS, FLUSH_INTERVAL_TICKS);
}
public synchronized void load() {
entries.clear();
if (!file.exists()) {
return;
}
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
ConfigurationSection section = yaml.getConfigurationSection("locations");
if (section == null)
return;
long expiryDays = Math.max(0, plugin.getConfig().getInt("Options.PreJoinLocationExpiryDays", 30));
long oldestAllowed = expiryDays == 0 ? 0 : System.currentTimeMillis() - (expiryDays * DAY_MILLIS);
int expired = 0;
for (String key : section.getKeys(false)) {
ConfigurationSection s = section.getConfigurationSection(key);
if (s == null)
continue;
String world = s.getString("world");
if (world == null) {
plugin.getLogger().warning("Skipping malformed return location entry '" + key + "'.");
continue;
}
UUID id;
try {
id = UUID.fromString(key);
} catch (IllegalArgumentException e) {
plugin.getLogger().warning("Skipping return location entry with invalid UUID '" + key + "'.");
continue;
}
long savedAt = s.getLong("saved", System.currentTimeMillis());
if (savedAt < oldestAllowed) {
expired++;
continue;
}
entries.put(id, new Entry(world, s.getDouble("x"), s.getDouble("y"), s.getDouble("z"),
(float) s.getDouble("yaw"), (float) s.getDouble("pitch"), savedAt));
}
if (expired > 0)
dirty = true;
plugin.getLogger().info("Loaded " + entries.size() + " stored return location(s)"
+ (expired > 0 ? ", dropped " + expired + " older than " + expiryDays + " day(s)." : "."));
}
// Only writes when something actually changed, so the repeating flush is free
// on an idle server.
public synchronized void save() {
if (!dirty)
return;
YamlConfiguration yaml = new YamlConfiguration();
for (Map.Entry<UUID, Entry> e : entries.entrySet()) {
String path = "locations." + e.getKey();
Entry entry = e.getValue();
yaml.set(path + ".world", entry.world());
yaml.set(path + ".x", entry.x());
yaml.set(path + ".y", entry.y());
yaml.set(path + ".z", entry.z());
yaml.set(path + ".yaw", (double) entry.yaw());
yaml.set(path + ".pitch", (double) entry.pitch());
yaml.set(path + ".saved", entry.savedAt());
}
try {
if (!plugin.getDataFolder().exists())
plugin.getDataFolder().mkdirs();
yaml.save(file);
dirty = false;
} catch (Exception e) {
plugin.getLogger().warning("Failed to save return locations: " + e.getMessage());
}
}
public void put(UUID playerId, Location location) {
if (playerId == null || location == null || location.getWorld() == null)
return;
entries.put(playerId, Entry.of(location));
dirty = true;
}
public void remove(UUID playerId) {
if (playerId != null && entries.remove(playerId) != null)
dirty = true;
}
public boolean has(UUID playerId) {
return playerId != null && entries.containsKey(playerId);
}
public String getWorldName(UUID playerId) {
Entry entry = playerId != null ? entries.get(playerId) : null;
return entry != null ? entry.world() : null;
}
// Resolves the stored spot against a loaded world. Null when the world is not
// loaded right now -- the entry is kept so a later attempt can still use it.
public Location get(UUID playerId) {
Entry entry = playerId != null ? entries.get(playerId) : null;
if (entry == null)
return null;
World world = Bukkit.getWorld(entry.world());
if (world == null)
return null;
return new Location(world, entry.x(), entry.y(), entry.z(), entry.yaw(), entry.pitch());
}
// Same as get(), but pulls the world back in through MyWorlds when it is
// unloaded -- a player who came from the End belongs in the End.
public Location getOrLoadWorld(UUID playerId) {
Location resolved = get(playerId);
if (resolved != null)
return resolved;
String worldName = getWorldName(playerId);
if (worldName == null)
return null;
WorldConfig wc = WorldConfig.getIfExists(worldName);
if (wc == null) {
plugin.getLogger().warning("Stored return world '" + worldName + "' is unknown to MyWorlds.");
return null;
}
plugin.getLogger().info("Loading world '" + worldName + "' to return a player to their pre-join location.");
if (wc.loadWorld() == null)
return null;
return get(playerId);
}
public void shutdown() {
if (flushTask != null) {
try {
flushTask.cancel();
} catch (IllegalStateException ignored) {
}
flushTask = null;
}
save();
}
}
+113
View File
@@ -0,0 +1,113 @@
package utils;
import java.util.List;
import java.util.function.Function;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import net.kyori.adventure.text.Component;
import plugin.api.ScoreboardOGAPI;
import plugin.api.SidebarProvider;
// Soft bridge to Scoreboard-OG. The nested Hook is the only class that touches the
// API, and it is loaded only after the plugin is confirmed present and enabled.
public final class ScoreboardOGBridge {
private static final String OWNER = "Splegg-OG";
private static Boolean available;
private ScoreboardOGBridge() {
}
public static boolean isAvailable() {
if (available == null) {
boolean present = Bukkit.getPluginManager().isPluginEnabled("Scoreboard-OG");
available = present && Hook.ready();
}
return available;
}
public static void claim(Player player, Function<Player, Component> title,
Function<Player, List<Component>> lines)
{
if (isAvailable())
Hook.claim(player, title, lines);
}
public static void release(Player player) {
if (isAvailable())
Hook.release(player);
}
public static void releaseAll() {
if (isAvailable())
Hook.releaseAll();
}
private static final class Hook {
static boolean ready() {
try {
return ScoreboardOGAPI.isAvailable();
} catch (NoClassDefFoundError error) {
// An older Scoreboard-OG without the API; fall back to the local boards.
return false;
}
}
static void claim(Player player, Function<Player, Component> title, Function<Player, List<Component>> lines) {
ScoreboardOGAPI.claim(player, OWNER, new SidebarProvider() {
@Override
public Component title(Player viewer) {
return title.apply(viewer);
}
@Override
public List<Component> lines(Player viewer) {
return lines.apply(viewer);
}
});
}
static void release(Player player) {
ScoreboardOGAPI.release(player, OWNER);
}
static void releaseAll() {
ScoreboardOGAPI.releaseAll(OWNER);
}
}
}
+30 -24
View File
@@ -4,46 +4,52 @@ import org.bukkit.entity.Player;
public class SpleggPlayer {
UtilPlayer u;
int kills;
int broken;
UtilPlayer u;
int kills;
int broken;
public SpleggPlayer(UtilPlayer u) {
public SpleggPlayer(UtilPlayer u) {
this.u = u;
this.kills = 0;
this.broken = 0;
this.u = u;
this.kills = 0;
this.broken = 0;
}
}
public UtilPlayer getUtilPlayer() {
public UtilPlayer getUtilPlayer() {
return this.u;
return this.u;
}
}
public Player getPlayer() {
public Player getPlayer() {
return this.u.getPlayer();
return this.u.getPlayer();
}
}
public int getKills() {
public int getKills() {
return this.kills;
return this.kills;
}
}
public void setKills(int i) {
public void setKills(int i) {
this.kills = i;
this.kills = i;
}
}
public int getBroken() {
public int getBroken() {
return this.broken;
return this.broken;
}
}
}
public void addBrokenBlock() {
this.broken++;
}
}
+56
View File
@@ -0,0 +1,56 @@
package utils;
// Text helpers for the Splegg sidebar in the Scoreboard-OG house style.
// Every line stays within 16 legacy characters so 1.8 clients see it whole.
public final class TrueOGBoard {
public static final String TITLE = "&4♥ &a&lSplegg&c&l-OG &4♥";
public static final String FOOTER = "&etrue-og.net";
// Visible characters left on a line after one leading color code.
public static final int VALUE_WIDTH = 14;
private TrueOGBoard() {
}
// Cuts plain text to the given visible width.
public static String fit(String text, int width) {
if (text == null) {
return "";
}
return text.length() <= width ? text : text.substring(0, width);
}
// Keeps counters at four characters for a shared line: 12345 becomes 12k.
public static String compact(int value) {
if (value < 10000) {
return String.valueOf(value);
}
if (value < 1000000) {
return (value / 1000) + "k";
}
return (value / 1000000) + "m";
}
// Formats seconds as mm:ss, clamped at zero.
public static String clock(int seconds) {
final int clamped = Math.max(0, seconds);
return String.format("%02d:%02d", clamped / 60, clamped % 60);
}
}
+34 -34
View File
@@ -6,62 +6,62 @@ import managers.Game;
public class UtilPlayer {
Player player;
String name;
boolean alive;
Game game;
InvStore store;
Player player;
String name;
boolean alive;
Game game;
InvStore store;
public UtilPlayer(Player player) {
public UtilPlayer(Player player) {
this.player = player;
this.game = null;
this.name = player.getName();
this.alive = false;
this.store = new InvStore(player);
this.player = player;
this.game = null;
this.name = player.getName();
this.alive = false;
this.store = new InvStore(player);
}
}
public InvStore getStore() {
public InvStore getStore() {
return this.store;
return this.store;
}
}
public Player getPlayer() {
public Player getPlayer() {
return this.player;
return this.player;
}
}
public String getName() {
public String getName() {
return this.name;
return this.name;
}
}
public boolean isAlive() {
public boolean isAlive() {
return this.alive;
return this.alive;
}
}
public void setAlive(boolean a) {
public void setAlive(boolean a) {
this.alive = a;
this.alive = a;
}
}
public Game getGame() {
public Game getGame() {
return this.game;
return this.game;
}
}
public void setGame(Game game) {
public void setGame(Game game) {
this.game = game;
this.game = game;
}
}
}
}
+262 -180
View File
@@ -5,9 +5,9 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
@@ -17,305 +17,387 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Egg;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.potion.PotionEffect;
import main.SpleggOG;
import managers.Game;
import managers.Status;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
public class Utils {
// Enable the conversion of text from config.yml to objects.
public FileConfiguration config = SpleggOG.getPlugin().getConfig();
// Enable the conversion of text from config.yml to objects.
public FileConfiguration config = SpleggOG.getPlugin().getConfig();
// Keyed by UUID so a name change or relog never resolves to a stale wrapper.
public HashMap<UUID, UtilPlayer> PLAYERS = new HashMap<>();
private File f;
public static String prefix = "&7[&eSplegg&f-&4OG&7] ";
public HashMap<String, UtilPlayer> PLAYERS = new HashMap<String, UtilPlayer>();
private File f;
private String prefix = config.getString("Messages.Prefix").replaceAll("&", "§");
public void bc(String s) {
public String getPrefix() {
final TextComponent prefixContainer = legacySerializerAnyCase(prefix + s);
Bukkit.broadcast(prefixContainer);
return this.prefix;
}
}
public void bc(String string, Game game) {
public void log(String s) {
final Iterator<?> playerIterator = SpleggOG.getPlugin().pm.PLAYERS.values().iterator();
while (playerIterator.hasNext()) {
Bukkit.getConsoleSender().sendMessage(this.prefix + s);
final UtilPlayer u = (UtilPlayer) playerIterator.next();
if (u.getGame() == game && u.isAlive()) {
}
spleggOGMessage(u.getPlayer(), string);
public void sendMessage(Player player, String s) {
}
player.sendMessage(this.prefix + ChatColor.translateAlternateColorCodes('&', s));
}
}
}
public void bc(String s) {
public void setup() {
TextComponent prefixContainer = Component.text(this.prefix + ChatColor.translateAlternateColorCodes('&', s));
Bukkit.broadcast(prefixContainer);
this.f = new File(SpleggOG.getPlugin().getDataFolder(), "spawns.yml");
}
try {
public void bc(String string, Game game) {
if (!this.f.exists()) {
Iterator<?> playerIterator = SpleggOG.getPlugin().pm.PLAYERS.values().iterator();
while(playerIterator.hasNext()) {
this.f.createNewFile();
UtilPlayer u = (UtilPlayer) playerIterator.next();
if (u.getGame() == game && u.isAlive()) {
}
sendMessage(u.getPlayer(), string);
} catch (IOException error) {
}
SpleggOG.getPlugin().getLogger().severe("An error occured while creating spawns.yml.");
}
}
}
reloadSpawns();
saveSpawns();
reloadSpawns();
public void setup() {
}
this.f = new File(SpleggOG.getPlugin().getDataFolder(), "spawns.yml");
private void reloadSpawns() {
try {
this.config = YamlConfiguration.loadConfiguration(f);
if (! this.f.exists()) {
}
this.f.createNewFile();
private void saveSpawns() {
}
try {
}
catch (IOException error) {
this.config.save(f);
SpleggOG.getPlugin().getLogger().severe("An error occured while creating spawns.yml.");
} catch (IOException error) {
}
SpleggOG.getPlugin().getLogger().severe("An error occured while saving spawns.yml.");
reloadSpawns();
saveSpawns();
reloadSpawns();
}
}
}
private void reloadSpawns() {
public void setLobby(Location l) {
this.config = YamlConfiguration.loadConfiguration(f);
setSpawn("Spawns.lobby", l);
}
}
private void saveSpawns() {
public Location getLobby(Player player) {
try {
return getSpawn("Spawns.lobby");
this.config.save(f);
}
}
catch (IOException error) {
private void setSpawn(String path, Location location) {
SpleggOG.getPlugin().getLogger().severe("An error occured while saving spawns.yml.");
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
float yaw = location.getYaw();
float pitch = location.getPitch();
String worldName = location.getWorld().getName();
}
this.config.set(path + ".world", worldName);
this.config.set(path + ".x", x);
this.config.set(path + ".y", y);
this.config.set(path + ".z", z);
this.config.set(path + ".pitch", pitch);
this.config.set(path + ".yaw", yaw);
}
saveSpawns();
public void setLobby(Location l) {
}
int x = l.getBlockX();
int y = l.getBlockY();
int z = l.getBlockZ();
float yaw = l.getYaw();
float pitch = l.getPitch();
String worldName = l.getWorld().getName();
private boolean hasSpawn(String path) {
this.config.set("Spawns.lobby.world", worldName);
this.config.set("Spawns.lobby.x", x);
this.config.set("Spawns.lobby.y", y);
this.config.set("Spawns.lobby.z", z);
this.config.set("Spawns.lobby.pitch", pitch);
this.config.set("Spawns.lobby.yaw", yaw);
return this.config.isString(path + ".world");
saveSpawns();
}
}
private Location getSpawn(String path) {
public Location getLobby(Player player) {
if (!hasSpawn(path)) {
int x = this.config.getInt("Spawns.lobby.x");
int y = this.config.getInt("Spawns.lobby.y");
int z = this.config.getInt("Spawns.lobby.z");
return null;
float yaw = (float) this.config.getInt("Spawns.lobby.yaw");
float pitch = (float) this.config.getInt("Spawns.lobby.pitch");
}
World worldName = player.getWorld();
int x = this.config.getInt(path + ".x");
int y = this.config.getInt(path + ".y");
int z = this.config.getInt(path + ".z");
return new Location(worldName, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, yaw, pitch);
float yaw = (float) this.config.getInt(path + ".yaw");
float pitch = (float) this.config.getInt(path + ".pitch");
}
World worldName = Bukkit.getWorld(this.config.getString(path + ".world"));
if (worldName == null) {
public UtilPlayer getPlayer(String name) {
return null;
return (UtilPlayer) this.PLAYERS.get(name);
}
}
return new Location(worldName, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, yaw, pitch);
public UtilPlayer getPlayer(Player player) {
}
return (UtilPlayer) this.PLAYERS.get(player.getName());
public UtilPlayer getPlayer(Player player) {
}
return this.PLAYERS.get(player.getUniqueId());
public void clearInventory(Player player) {
}
// TODO: Save this inventory and give it back to the player after the game.
PlayerInventory pInv = player.getInventory();
pInv.setArmorContents((ItemStack[]) null);
pInv.clear();
// Returns the tracked wrapper, creating one for a player who joined before the
// plugin enabled.
public UtilPlayer track(Player player) {
player.setFireTicks(0);
clearPotions(player);
return this.PLAYERS.computeIfAbsent(player.getUniqueId(), id -> new UtilPlayer(player));
}
}
public void clearPotions(Player player) {
public void untrack(Player player) {
Iterator<?> activePotionEffects = player.getActivePotionEffects().iterator();
while(activePotionEffects.hasNext()) {
this.PLAYERS.remove(player.getUniqueId());
PotionEffect effect = (PotionEffect) activePotionEffects.next();
}
player.removePotionEffect(effect.getType());
// Sends a formatted message to the player (including name replacement).
public static void spleggOGMessage(Player p, String message) {
}
p.sendMessage(legacySerializerAnyCase(prefix + message));
}
}
public static ItemStack getItem(Material material, String name, String lore) {
// Convert "&"-prefixed legacy colors into the "§" section form Bukkit APIs
// that still take raw Strings (sign lines, scoreboard entries) understand.
public static String legacySectionize(String subject) {
ItemStack stack = new ItemStack(material, 1);
ItemMeta meta = stack.getItemMeta();
return LegacyComponentSerializer.legacySection().serialize(legacySerializerAnyCase(subject));
TextComponent nameContainer = Component.text(name);
TextComponent loreContainer = Component.text(lore);
}
meta.displayName(nameContainer);
meta.lore(Arrays.asList(loreContainer));
stack.setItemMeta(meta);
public static TextComponent legacySerializerAnyCase(String subject) {
return stack;
int count = 0;
// Count the number of '&' characters to determine the size of the array
for (char c : subject.toCharArray()) {
}
if (c == '&') {
public static void fireEgg(PlayerInteractEvent event) {
count++;
Player player = event.getPlayer();
ItemStack hand = player.getInventory().getItemInMainHand();
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
}
if(event.getAction() == Action.RIGHT_CLICK_BLOCK || event.getAction() == Action.RIGHT_CLICK_AIR) {
}
if(u.getGame().getStatus().equals(Status.INGAME) && u.isAlive()) {
// Create an array to store the positions of '&' characters
int[] positions = new int[count];
int index = 0;
// Find the positions of '&' characters and store in the array
for (int i = 0; i < subject.length(); i++) {
switch(hand.getType()) {
case WOODEN_SHOVEL:
player.launchProjectile(Egg.class);
eggSound(player);
break;
case STONE_SHOVEL:
player.launchProjectile(Egg.class);
eggSound(player);
break;
case IRON_SHOVEL:
player.launchProjectile(Egg.class);
eggSound(player);
break;
case GOLDEN_SHOVEL:
player.launchProjectile(Egg.class);
eggSound(player);
break;
case DIAMOND_SHOVEL:
for(int i = 0; i < 2; i = i + 1) {
if (subject.charAt(i) == '&') {
player.launchProjectile(Egg.class);
if (i + 1 < subject.length() && isUpperBukkitCode(subject.charAt(i + 1))) {
}
eggSound(player);
break;
case NETHERITE_SHOVEL:
for(int i = 0; i < 3; i = i + 1) {
subject = replaceCharAtIndex(subject, (i + 1), Character.toLowerCase(subject.charAt(i + 1)));
player.launchProjectile(Egg.class);
}
}
eggSound(player);
break;
default:
break;
}
positions[index++] = i;
}
}
}
}
if (hand.getType() == Material.COMPASS) {
return LegacyComponentSerializer.legacyAmpersand().deserialize(subject);
player.openInventory(getSpecInventory());
}
}
private static boolean isUpperBukkitCode(char input) {
if (hand.getType() == Material.SLIME_BALL) {
char[] bukkitColorCodes = { 'A', 'B', 'C', 'D', 'E', 'F', 'K', 'L', 'M', 'N', 'O', 'R' };
boolean match = false;
// TODO: Remove dev logger
SpleggOG.getPlugin().getLogger().info("Player leaving game slimeball: " + u.getGame() + " with map " + u.getGame().getMap().getName());
// Loop through each character in the array.
for (char c : bukkitColorCodes) {
Game game = u.getGame();
game.leaveGame(u);
// Check if the current character in the array is equal to the input character.
if (c == input) {
}
match = true;
if (hand.getType() == Material.getMaterial(SpleggOG.getPlugin().getConfig().getString("Shop.Item"))) {
}
player.openInventory(getShopInventory());
}
}
return match;
}
}
private static void eggSound(Player player) {
private static String replaceCharAtIndex(String original, int index, char newChar) {
player.playSound(player.getLocation(), Sound.ENTITY_GHAST_SHOOT, 0.10F, 2.0F);
// Check if the index is valid
if (index >= 0 && index < original.length()) {
}
// Create a new string with the replaced character
return original.substring(0, index) + newChar + original.substring(index + 1);
private static Inventory getShopInventory() {
}
TextComponent shopTitle = Component.text(SpleggOG.getPlugin().getConfig().getString("GUI.Shop.Title").replaceAll("&", "§"));
Inventory shop = Bukkit.createInventory((InventoryHolder) null, InventoryType.CHEST, shopTitle);
// If the index is invalid, return the original string
return original;
shop.setItem(0, Utils.getItem(Material.GOLDEN_SHOVEL, SpleggOG.getPlugin().getConfig().getString("GUI.Shop.GoldShovel.Name").replaceAll("&", "§"), SpleggOG.getPlugin().getConfig().getString("GUI.Shop.GoldShovel.Description").replaceAll("&", "§").replaceAll("%price%", String.valueOf(SpleggOG.getPlugin().getConfig().getInt("GUI.Shop.GoldShovel.Price")))));
shop.setItem(1, Utils.getItem(Material.DIAMOND_SHOVEL, SpleggOG.getPlugin().getConfig().getString("GUI.Shop.DiamondShovel.Name").replaceAll("&", "§"), SpleggOG.getPlugin().getConfig().getString("GUI.Shop.DiamondShovel.Description").replaceAll("&", "§").replaceAll("%price%", String.valueOf(SpleggOG.getPlugin().getConfig().getInt("GUI.Shop.DiamondShovel.Price")))));
}
return shop;
public static ItemStack getItem(Material material, String name, String lore) {
}
ItemStack stack = new ItemStack(material, 1);
ItemMeta meta = stack.getItemMeta();
TextComponent nameContainer = null;
TextComponent loreContainer = null;
try {
private static Inventory getSpecInventory() {
nameContainer = Component.text(name);
loreContainer = Component.text(lore);
TextComponent spectatorTitle = Component.text("Splegg - Spectators");
Inventory spec = Bukkit.createInventory((InventoryHolder) null, InventoryType.CHEST, spectatorTitle);
} catch (NullPointerException error) {
return spec;
SpleggOG.getPlugin().getLogger().severe("Failed to get Item information for: " + material + ".");
}
}
}
meta.displayName(nameContainer);
meta.lore(Arrays.asList(loreContainer));
stack.setItemMeta(meta);
return stack;
}
public static void fireEgg(PlayerInteractEvent event, UtilPlayer u, Player player, ItemStack itemInHand) {
if (u.getGame() != null) {
if (u.getGame().getStatus().equals(Status.INGAME) && u.isAlive()) {
if (event.getAction() == Action.RIGHT_CLICK_BLOCK || event.getAction() == Action.RIGHT_CLICK_AIR) {
switch (itemInHand.getType()) {
case WOODEN_SHOVEL, STONE_SHOVEL, IRON_SHOVEL, GOLDEN_SHOVEL:
player.launchProjectile(Egg.class);
eggSound(player);
break;
case DIAMOND_SHOVEL:
for (int i = 0; i < 2; i = i + 1) {
player.launchProjectile(Egg.class);
}
eggSound(player);
break;
case NETHERITE_SHOVEL:
for (int i = 0; i < 3; i = i + 1) {
player.launchProjectile(Egg.class);
}
eggSound(player);
break;
default:
break;
}
}
}
}
}
private static void eggSound(Player player) {
player.playSound(player.getLocation(), Sound.ENTITY_GHAST_SHOOT, 0.10F, 2.0F);
}
public static Inventory getShopInventory() {
// Create the GUI title.
TextComponent shopTitleComponent = legacySerializerAnyCase(
SpleggOG.getPlugin().getConfig().getString("GUI.Shop.Title"));
String shopTitle = LegacyComponentSerializer.legacyAmpersand().serialize(shopTitleComponent);
// Create the inventory.
Inventory shop = Bukkit.createInventory((InventoryHolder) null, 9, Component.text(shopTitle));
shop.setItem(0, getShopItem(Material.WOODEN_SHOVEL, "GUI.Shop.WoodShovel"));
shop.setItem(1, getShopItem(Material.STONE_SHOVEL, "GUI.Shop.StoneShovel"));
shop.setItem(2, getShopItem(Material.IRON_SHOVEL, "GUI.Shop.IronShovel"));
shop.setItem(3, getShopItem(Material.GOLDEN_SHOVEL, "GUI.Shop.GoldShovel"));
shop.setItem(4, getShopItem(Material.DIAMOND_SHOVEL, "GUI.Shop.DiamondShovel"));
shop.setItem(5, getShopItem(Material.NETHERITE_SHOVEL, "GUI.Shop.NetheriteShovel"));
// Pass the shop to the player.
return shop;
}
private static ItemStack getShopItem(Material material, String configPath) {
final String name = LegacyComponentSerializer.legacyAmpersand()
.serialize(legacySerializerAnyCase(SpleggOG.getPlugin().getConfig().getString(configPath + ".Name")));
final String description = LegacyComponentSerializer.legacyAmpersand().serialize(
legacySerializerAnyCase(SpleggOG.getPlugin().getConfig().getString(configPath + ".Description")
.replaceAll("%price%", "&B" + SpleggOG.getPlugin().getConfig().getInt(configPath + ".Price"))));
return Utils.getItem(material, name, description);
}
// TODO: Spectator mode in listener.
/*
* private static Inventory getSpecInventory() {
*
* TextComponent spectatorTitle = Component.text("Splegg - Spectators");
* Inventory spec = Bukkit.createInventory((InventoryHolder) null,
* InventoryType.CHEST, spectatorTitle);
*
* return spec;
*
* }
*/
}
+135 -30
View File
@@ -1,8 +1,12 @@
Messages:
Prefix: '&8[&2Splegg&8-&4OG&8] &3'
NoPermission: '&cYou do not have permission!'
NotInSpleggWorld: '&cERROR: Splegg can only be used in configured splegg worlds.'
InProtectedMainWorld: '&cERROR: Splegg cannot modify the vanilla overworld, nether,
or end. Move to a Splegg lobby or in-game world first.'
JoinGame: '&6%player% has joined the game. &7[&e%count%/%maxcount%&7]'
LeaveGame: '&6You have left the lobby for map &a%map%&6.'
LeaveGame: '&6%player% has left the game. &7[&e%count%/%maxcount%&7]'
IndividualLeaveGame: '&6You have left lobby &a%lobby%&6.'
LobbyTimer: '&6Match starting &f%timer% &6seconds.'
InstructionsGame: '&3Use your shovel to throw eggs and make the other players fall!'
GraceTimeStart: '&aGrace Period (&6%grace% &aseconds)'
@@ -22,38 +26,107 @@ Messages:
Mapnotexist: '&cERROR: Map does not exist!'
Mapdisabled: '&cERROR: Map is disabled.'
Haveyoueverbought: '&cERROR: You already own this shovel.'
BuyWoodShovel: '&BYou bought a Wooden Shovel!'
BuyStoneShovel: '&BYou bought a Stone Shovel!'
BuyIronShovel: '&BYou selected the Iron Shovel!'
BuyGoldShovel: '&BYou bought a Gold Shovel!'
BuyDiamondShovel: '&BYou bought a Diamond Shovel!'
BuyNetheriteShovel: '&BYou bought a Netherite Shovel!'
VIPPrivilege: '&6Only &fOG &6players and above can enter the full game!'
VIPJoinGame: '&6You have joined a full game thanks to your donor rank. Thank you for supporting &2True&4OG!'
Scoreboard:
Title: '&2Splegg&r-&4OG'
Queue: '&6Players Waiting:'
Starting: '&6Starting in:'
BrokenBlocks: '&eBlocks Broken:'
VIPJoinGame: '&6You have joined a full game thanks to your premium rank. Thank you
for supporting &aTrue&cOG &eNetwork!'
# Winner/loser flow
WinnerGame: '&a%player% won Splegg on map &e%map%&a!'
WinnerTitle: '&a&lVICTORY!'
WinnerSubtitle: '&eYou won on &6%map%&e!'
YouWonGame: '&aYou won the match on &e%map%&a!'
LoseGame: '&cYou were eliminated from the match on &e%map%&c.'
FinishReward: '&BYou received %reward% &BDiamonds for winning!'
Money:
KillPlayer: 0
# Diamonds awarded to the match winner.
FinishGame: 1
Points:
# Points awarded per block an egg destroys.
BlockBroken: 1
# Points awarded to the match winner.
Win: 50
Options:
Timer: 120
# Players needed in a lobby before its countdown starts.
AutoStartPlayers: 4
# Players a lobby holds. Extra spawn points on a map go unused; a map with
# fewer spawns than this cycles them.
MaxPlayers: 10
GraceTime: 10
VotingMaps: 2
EndVotingAt: 10
VotingReminder: 30
# Days a stored pre-game return location survives restarts. 0 keeps them forever.
PreJoinLocationExpiryDays: 30
# Splegg only manages dedicated lobby and in-game worlds. The vanilla overworld
# (world, world_nether, world_the_end) is hard-blacklisted in code and cannot
# be added here -- splegg will refuse to put a map, inventory group, or any
# listener side effect into those worlds even if their names appear below.
# Worlds listed here are loaded by Splegg itself on the first tick after startup;
# create them with MyWorlds first (e.g. /mw create splegg_lobby void) or place
# them under MapBase. Folder names match case-insensitively and ignoring - and _.
#
# MapBase (optional): cold-storage directory holding world templates. Each
# Worlds.Lobby and Worlds.InGame name is resolved to <MapBase>/<Name>/ on
# startup; the world is copied to the server root and (re-)loaded from that fresh
# copy on every boot. Spawns/floors/match-lobby coords stored in
# plugins/Splegg-OG/<map>.yml carry over across reboots. In-game terrain edits to
# a template world are wiped on the next restart -- to make terrain changes stick,
# replace the files under <MapBase>/<Name>/ externally. Leave empty to keep the
# legacy behavior, where worlds live directly at the server root.
# Relative paths resolve against the server's world container (server root on
# a default install). Absolute paths are accepted.
#
# VoidGenerator: pin the bundled void chunk generator onto every world Splegg
# loads or copies. A copied world folder carries no MyWorlds generator setting,
# so without this chunks outside the built arena generate vanilla terrain.
#
# GamePrefix: lobby ids and world names share it. Every world listed under
# Worlds.Lobby must be named <GamePrefix><number>-<name>, for example SP1-hub:
# that world is the hub of lobby SP1, which players join with /splegg join SP1 or
# /splegg join 1. Players wait and vote for the map in the hub; when the vote
# ends the winning template from Worlds.InGame is copied to
# <GamePrefix><number>-<map> (SP1-Shake_It) and the match runs there. Add a
# second hub (SP2-hub) for a second concurrent lobby.
# Chat-OG keys its per-game Discord channel off this prefix, so keep the two in sync.
Worlds:
MapBase: 'maps'
VoidGenerator: true
GamePrefix: SP
Lobby:
- SP1-hub
InGame:
- Loss
# Join signs render like TheHerobrine-OG's: title, lobby or map, bold status,
# then black-on-sign player counts. A repeating task redraws every sign each
# second. Placeholders: %lobby% (SP1), %map% (the voted map, or the lobby id
# while the vote is open), %status%, %count%, %maxcount%.
Sings:
Format:
1: '&8[&2Splegg&r-&4OG&8]'
2: '%status%'
3: '%map%'
4: '&5%count%/%maxcount%'
Restarting:
1: '###########################'
2: '&8[&6Restarting &2Splegg&4OG&8]'
3: '%map%'
4: '###########################'
1: '&4Splegg'
2: '&6%map%'
3: '%status%'
4: '&0%count%&8/&0%maxcount%'
Status:
Join: '&4[Join]'
Started: '&6[Started]'
Disabled: '&4[Disabled]'
Join: '&2&lJOIN'
Starting: '&5&lSTARTING'
Started: '&3&lLIVE'
Full: '&4&lFULL'
Ending: '&8&lENDING'
Disabled: '&cDISABLED'
Shovels:
Wood:
Name: '&6&lWooden Shovel'
Lore: '&e&lRight-Click to launch eggs!'
Stone:
Name: '&7&lStone Shovel'
Lore: '&e&lRight-Click to launch eggs!'
Iron:
Name: '&f&lIron Shovel'
Lore: '&e&lRight-Click to launch eggs!'
@@ -63,28 +136,60 @@ Shovels:
Diamond:
Name: '&b&lDiamond Shovel'
Lore: '&e&lRight-Click to launch eggs!'
Netherite:
Name: '&8&lNetherite Shovel'
Lore: '&e&lRight-Click to launch eggs!'
GUI:
Shop:
Title: '&lSplegg Shop'
Size: 10
Title: '&3&lSplegg Shop'
Size: 9
WoodShovel:
Name: '&6&lWooden Shovel'
Description: '&6A classic &6Wooden Shovel&6 for style points. Price: &B%price%
Diamonds&6.'
Price: 100
StoneShovel:
Name: '&7&lStone Shovel'
Description: '&6Upgrade your look with a &7Stone Shovel&6. Price: &B%price%
Diamonds&6.'
Price: 250
IronShovel:
Name: '&f&lIron Shovel'
Description: '&6The standard &fIron Shovel &6comes with every match. Price:
&B%price% Diamonds&6.'
Price: 0
GoldShovel:
Name: '&e&lGolden Shovel'
Description: '&6Impress your SO with this fancy &eGolden &6shovel! Price: &B%price% Diamonds&6.'
Description: '&6Impress your SO with this fancy &eGolden Shovel&6! Price: &B%price%
Diamonds&6.'
Price: 500
DiamondShovel:
Name: '&b&lDiamond Shovel'
Description: '&6The Diamond Shovel shoots 2 eggs at a time! Price: &B%price% Diamonds'
Description: '&6The illustrious &BDiamond Shovel &6shoots TWO eggs at a time!
Price: &B%price% Diamonds&6.'
Price: 1000
Shop:
NetheriteShovel:
Name: '&8&lNetherite Shovel'
Description: '&6The fearsome &8Netherite Shovel &6shoots THREE eggs at a time!
Price: &B%price% Diamonds&6.'
Price: 1500
Guide:
Item: ENCHANTED_BOOK
Slot: 0
Name: 'Splegg Guide'
Lore: '&eLearn how to play!'
Name: '&eSplegg Guide'
Lore: '&6Learn how to play!'
Shop:
Item: DIAMOND
Slot: 1
Name: '&BSplegg Shop'
Lore: '&BBuy fancy new shovels!'
Cosmetics:
Item: SUNFLOWER
Slot: 4
Name: 'Splegg Shop'
Lore: '&eBuy fancy new shovels!'
Name: '&3Cosmetics Menu'
Lore: '&5Upgrade your look!'
Leave:
Item: SLIME_BALL
Slot: 8
Name: 'Exit Game'
Lore: '&eReturn to &2True&4OG &2SMP&e!'
Name: '&4Leave Game'
Lore: '&eReturn to &aTrue&cOG &2SMP&e!'
+44 -3
View File
@@ -1,8 +1,49 @@
name: Splegg-OG
main: main.SpleggOG
version: 1.2
version: '${version}'
authors: [Hraponssi, MrLuangamer, Theluangamer9416, NotAlexNoyle]
api-version: 1.18
depend: [Vault, WorldEdit]
api-version: '${apiVersion}'
depend: [DiamondBank-OG, WorldEdit, MyWorlds, Utilities-OG]
softdepend: [My_Worlds, Chat-OG, Scoreboard-OG]
# /vote and /v are not declared here on purpose: VoteCommandListener claims them inside Splegg
# territory only, so VotingPlugin keeps the labels everywhere else.
commands:
splegg:
description: The command prefix for all Splegg-OG commands.
usage: /<command> join [lobby]
# /sp is also claimed at startup in the command map, so it wins over any other plugin's /sp.
aliases: [sp]
# Inside Splegg territory HubCommandListener claims /hub, /lobby and /spawn before any
# other plugin sees them; this registration only matters outside it.
hub:
description: Leave the Splegg lobby or game and return to the main world.
aliases: [lobby]
spjoin:
description: Join a Splegg lobby by its id (SP1 or 1); lists the lobbies with no argument.
usage: /<command> [lobby]
spforcestart:
description: Start the Splegg game even if the player minimum is not met.
permission: splegg.admin
usage: /<command> [time]
permissions:
splegg.use:
description: Permission to run Splegg-OG commands.
default: op
children:
splegg.join: true
splegg.join:
description: Permission to join Splegg games.
default: op
splegg.joinfull:
description: Permission to join full Splegg games.
default: op
splegg.admin:
description: Permission to manage Splegg maps, signs, and matches.
default: op
children:
splegg.use: true
splegg.join: true
splegg.joinfull: true