Compare commits

...

184 Commits

Author SHA1 Message Date
Konicai 9edc25f5dc
Merge c98d090774 into 8addcadb71 2024-05-05 09:02:19 +02:00
AJ Ferguson 8addcadb71
Bump MCPL to increase NBT max depth (#4639) 2024-05-05 02:24:28 -04:00
Konicai c98d090774
Merge remote-tracking branch 'refs/remotes/upstream/master' into feature/cloud
# Conflicts:
#	bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java
#	bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java
#	bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
#	bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java
#	core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java
#	gradle.properties
#	gradle/libs.versions.toml
2024-05-05 01:57:16 -04:00
Camotoy 5770c96f48
Indicate support for 1.20.81 2024-05-05 01:29:37 -04:00
AJ Ferguson b8fe71a8bc
Bump MCPL to fix ClientboundExplodePacket (#4635) 2024-05-04 01:06:59 -04:00
basaigh 9d299ee83b
Fix particle reading issues (#4631) 2024-05-03 07:29:15 -07:00
basaigh a39cd65537
Fix velocity ping passthrough (#4626) 2024-05-03 13:53:47 +02:00
Camotoy b39ed5de53
Panda eating particles are not necessarily bamboo 2024-05-02 20:33:28 -04:00
Camotoy cab1a20034
Set mappings commit to master 2024-05-02 13:08:09 -04:00
Camotoy 6a214f235c
Remove duplicate method 2024-05-02 13:07:18 -04:00
Camotoy efda13421f
Merge branch 'master' into feature/1.20.5 2024-05-02 12:58:46 -04:00
onebeastchris f7c65f38d1 properly annotate methods in the ServerTransferEvent 2024-05-02 12:51:32 +02:00
AJ Ferguson 60f8532be3 Fix attribute display text 2024-05-02 06:12:16 -04:00
AJ Ferguson 29a613b85c Use java default area effect cloud radius 2024-05-02 05:04:19 -04:00
AJ Ferguson d003818e73 Potion fixes 2024-05-02 03:47:30 -04:00
AJ Ferguson fdae333351 Add data components hash code to translated NBT 2024-05-01 19:06:22 -04:00
chris 1291b89e64
Ensure proper Geyser starting/disabling when Geyser is used on a client (#4621)
* Ensure proper Geyser starting/disabling when Geyser is used on a client

* also set correct remote port

* only use direct connection on server, not client, actually override remote port
2024-05-02 00:29:33 +02:00
Camotoy cc635d4447
This would probably end up being an issue... 2024-05-01 15:41:27 -04:00
Camotoy 1e8d6b23cf
Different registry implementation; fix banner blocks with ViaVersion 2024-05-01 15:35:30 -04:00
Camotoy abb1d7d9e9
Indicate Java 1.20.6 support 2024-04-30 18:50:41 -04:00
Camotoy 59a2c0dc02
Use item tags for all animal loved food 2024-04-30 18:35:52 -04:00
AJ Ferguson aff7d2cf35 Fix potential NPE 2024-04-30 18:05:46 -04:00
onebeastchris 255835438d viaversion 4.10.0 compat, indicate 1.20.6 support on modrinth 2024-04-30 21:59:38 +02:00
onebeastchris 51cbbba47c Merge remote-tracking branch 'upstream/feature/1.20.5' into feature/1.20.5 2024-04-30 21:48:41 +02:00
onebeastchris 4a0a694eb9 revert bad diff 2024-04-30 21:38:25 +02:00
onebeastchris d99f498901 translate ominous banners 2024-04-30 21:35:21 +02:00
onebeastchris dd745b901f move to paper-adapters 2024-04-30 20:48:10 +02:00
AJ Ferguson ff9965f559 Translate item repair cost component 2024-04-30 05:49:22 -04:00
AJ Ferguson 74d6a37261 Fix bug when adding enchantments in anvil 2024-04-30 05:49:03 -04:00
AJ Ferguson dacacc6df8 Anvil renaming 2024-04-30 04:48:15 -04:00
Camotoy c963503fef
Entity scale attribute is now applied 2024-04-30 00:33:49 -04:00
onebeastchris 28d5db622b revert bad change 2024-04-29 23:41:14 +02:00
onebeastchris 5d3630cf23 ominous banners - this really isn't ideal 2024-04-29 23:19:18 +02:00
onebeastchris 8b7b8cdffd Properly shutdown LocalSession's, ensure transferring works properly regardless if we're injected or not 2024-04-29 16:08:14 +02:00
AJ Ferguson 4ff746e48a Fix translateTag NPE 2024-04-29 04:22:51 -04:00
Camotoy a82a156419
Firework shapes 2024-04-29 01:03:18 -04:00
Camotoy 88ae447fc6 Fix banner block entity base colors with no patterns 2024-04-29 00:47:52 -04:00
Camotoy e8c1c2218f Fix banners on shields 2024-04-29 00:35:44 -04:00
onebeastchris 82123aecf1 Fix: Modded platform injector not being used 2024-04-28 23:52:23 +02:00
onebeastchris 8ab0921448 Merge remote-tracking branch 'upstream/feature/1.20.5' into feature/1.20.5 2024-04-28 23:41:36 +02:00
onebeastchris 9b1e45007a Fix injectors, should work with Spigot/Paper 1.20.5 now 2024-04-28 23:41:13 +02:00
basaigh 420f67752c Fix suspicious stew NPEs 2024-04-28 18:54:34 +01:00
AJ Ferguson e97bbcc483 Potion effect colors 2024-04-28 02:10:20 -04:00
Camotoy 6d5ac233d6
Dyeable items work. 2024-04-27 21:00:10 -04:00
Camotoy f47754be03
Goat horns 2024-04-27 15:49:19 -04:00
Camotoy 9217414c8c Merge remote-tracking branch 'origin/feature/1.20.5' into feature/1.20.5 2024-04-26 21:46:38 -04:00
Camotoy 2fa7585db3 Switch to Cloudburst NBT only 2024-04-26 21:44:59 -04:00
onebeastchris 5015a2ef89 bump project version to 2.3.0 2024-04-26 15:41:05 +02:00
onebeastchris 68534f386c Merge remote-tracking branch 'upstream/feature/1.20.5' into feature/1.20.5 2024-04-26 15:36:41 +02:00
onebeastchris f67c131b8d Forcibly disconnect players even if no server target was set in the JavaTransferEvent 2024-04-26 15:36:26 +02:00
onebeastchris 91a74603c7 idea: deal with cookies and transfer 2024-04-26 14:50:48 +02:00
AJ Ferguson 3656395ce1 Armadillo states 2024-04-25 21:08:38 -04:00
onebeastchris 8e3a3ea453 implement curse of binding check for wolf armor removal 2024-04-26 01:00:14 +02:00
onebeastchris 16cb76f523 neoforge 1.20.5 boots 2024-04-25 17:38:03 +02:00
Joshua Castle 99e6a2981d
Entity properties
Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com>
2024-04-25 01:33:18 -07:00
Joshua Castle 652f6af784
Fix custom skulls 1.20.5
Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com>
2024-04-24 18:13:07 -07:00
Camotoy 9d540fe672
Shulker box NPE fix 2024-04-24 16:41:02 -04:00
Camotoy b29e0d9d10
Move MCProtocolLib to dev - package ID change 2024-04-24 16:39:35 -04:00
AJ Ferguson 9490a091b5
Calculate horse inventory size 2024-04-24 16:26:59 -04:00
Camotoy a1534e4535
Basic Armadillo structure 2024-04-24 16:26:59 -04:00
AJ Ferguson bbaffb2ab3
Wolf interactions 2024-04-24 16:26:59 -04:00
onebeastchris 687d299ff5
target 1.20.5 release, build neoforge again 2024-04-24 16:26:59 -04:00
Camotoy c54624fb26
Fix some cases of empty tags being needed 2024-04-24 16:26:59 -04:00
Camotoy abea0131e4
Fix llama carpets 2024-04-24 16:26:59 -04:00
Camotoy d105dadf62
Update for release. STILL NOT READY YET THOUGH 2024-04-24 16:26:59 -04:00
Camotoy b81408820b
Refactor TagCache to be extensible
Previously, for any new tag, we would have to add a field, add the line to load it, add the line to clear it, and make a method for that tag. Now, you just add an enum.
2024-04-24 16:26:58 -04:00
onebeastchris c48428daf0
init: pick item component change 2024-04-24 16:26:58 -04:00
AJ Ferguson 9d8edad9fc
Fix horse inventory 2024-04-24 16:26:58 -04:00
Joshua Castle 3f499e3ec0
Start on custom skulls
Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com>
2024-04-24 16:26:58 -04:00
AJ Ferguson 1bdbcab4e8
Wolf variants 2024-04-24 16:26:58 -04:00
AJ Ferguson d3f902ae34
Banners 2024-04-24 16:26:58 -04:00
Camotoy 11f79d4e2c
Refactor Java registry storage; implement trim support 2024-04-24 16:26:58 -04:00
onebeastchris 6a5efa3c9d
Start on 1.20.5 mod platform support - NeoForge (temporarily) excluded
Also fixes lecterns, and block break speed calculations
2024-04-24 16:26:58 -04:00
Camotoy c34f0f2c3b
Update for latest MCProtocolLib 2024-04-24 16:26:58 -04:00
basaigh dac5f69d47
Bump mcpl 2024-04-24 16:26:58 -04:00
AJ Ferguson 8381a148fc
Fix book signing 2024-04-24 16:26:58 -04:00
onebeastchris c5e02d28e6
ensure geyser builds 2024-04-24 16:26:58 -04:00
basaigh 48164e93c7
Bump mcpl to fix item deserialization 2024-04-24 16:26:58 -04:00
onebeastchris b73f23de0f
remove global palette bits, fix nullable block entity tags 2024-04-24 16:26:58 -04:00
basaigh 57ce5706ee
Update mappings submodule 2024-04-24 16:26:58 -04:00
Camotoy 61907b1851
Little more work 2024-04-24 16:26:57 -04:00
Camotoy 687ddbb520
Tiny fixes 2024-04-24 16:26:57 -04:00
AJ Ferguson ab8832b771
Compiles 2024-04-24 16:26:57 -04:00
Camotoy 8bd2df0828
Trying to get more compiled but brain is officially fried for the day! 2024-04-24 16:26:57 -04:00
basaigh 94e533ea7c
Fix tags and attributes 2024-04-24 16:26:57 -04:00
Camotoy aed7f1bed7
Update the item parts 2024-04-24 16:26:57 -04:00
basaigh 6d8021f155
Update the non-item parts (#4586)
* Update the non-item parts

* Add MaceItem

* Fix registry data loading
2024-04-24 16:26:57 -04:00
Camotoy 909139326d
Keep chugging away 2024-04-24 16:26:57 -04:00
Camotoy c1edf20734
Here's the idea so far 2024-04-24 16:26:57 -04:00
Camotoy 099e968bde
Initial, incomplete pass at Java 1.20.5 2024-04-24 16:26:57 -04:00
onebeastchris 7456ed3c1f - add default handlers for neoforge
- dont register blank permissions
- handle neoforge's picky hasPermission
- register all Geyser non-command permissions as unset permissions
2024-04-21 04:34:26 +02:00
onebeastchris 27a086ec72 - add permission (+default) for base `/geyser` command
- update languages
- remove permission from the spigot plugin.yml
- catch RuntimeExceptions
- properly send command.failed lang string
2024-04-21 02:19:10 +02:00
onebeastchris 55ebb95270 Revert add entity translator shenanigans, re-add viaproxy console command event handler to deal with console commands 2024-04-20 13:21:41 +02:00
onebeastchris 8245b0f8a5 don't jij cloud stuff, cloud-platform does that already 2024-04-19 15:53:28 +02:00
onebeastchris aa3d875471 Merge remote-tracking branch 'upstream/master' into feature/cloud-updated 2024-04-19 15:20:05 +02:00
onebeastchris 9aa6553901 help command changes to ensure root command does require the help command permission 2024-04-15 15:23:52 +02:00
onebeastchris f2611376c3 - yeet application plugin from mod/plugin build scripts, set main class manifest attribute directly
- fix viaproxy plugin plugin warnings
- update languages module
2024-04-15 14:39:36 +02:00
onebeastchris 834148007f add comment on paper command manager 2024-04-14 17:09:41 +02:00
onebeastchris 542fb88263 Merge remote-tracking branch 'konica/feature/cloud' into feature/cloud-updated 2024-04-14 16:51:04 +02:00
onebeastchris e0175320aa - Don't throw when a throwable has been handled
- Warn when a geyser dump arg isn't valid
- don't register the `geyser` root literal with the geyser help permission
2024-04-14 16:49:57 +02:00
onebeastchris ad546b64fc it builds! 2024-04-14 15:55:34 +02:00
onebeastchris dea6c2fec6 Merge remote-tracking branch 'upstream/master' into feature/cloud-updated
# Conflicts:
#	gradle/libs.versions.toml
#	gradle/wrapper/gradle-wrapper.jar
#	gradlew
2024-04-14 14:54:33 +02:00
onebeastchris 5079ce574c maybe the dump command works? 2024-04-14 00:49:29 +02:00
onebeastchris 69111054d9 yeet paper mojang api depend 2024-04-13 22:15:09 +02:00
onebeastchris 98856a9c54 Merge remote-tracking branch 'upstream/master' into feature/cloud-updated
# Conflicts:
#	bootstrap/bungeecord/build.gradle.kts
#	bootstrap/mod/fabric/build.gradle.kts
#	bootstrap/mod/neoforge/build.gradle.kts
#	bootstrap/spigot/build.gradle.kts
#	bootstrap/velocity/build.gradle.kts
#	gradle.properties
2024-04-13 21:05:23 +02:00
onebeastchris ba3bd43e6e revert attempts related to exception handling 2024-03-08 19:48:55 +01:00
onebeastchris 677f79dbd7 - removed fabric permission api, done by cloud now
- updated GeyserPermission to 2.0
- removed mod platform specific permission checkers

TODO:
- test neoforge permission registration event
- fix: exception handling
2024-03-08 19:42:05 +01:00
onebeastchris f2a3feac05 2.0 command arguments 2024-03-07 20:11:04 +01:00
onebeastchris dc1826c5f9 Start on cloud 2.0 changes 2024-03-07 19:55:17 +01:00
onebeastchris 666c23f558 some more merge issues 2024-03-07 13:59:38 +01:00
onebeastchris c867b54141 attempt at updating the cloud pr; todo neoforge or even general testing 2024-03-07 12:33:17 +01:00
onebeastchris f6b0f36441 Merge remote-tracking branch 'upstream/master' into feature/cloud-updated 2024-03-05 15:11:49 +01:00
Konicai f1906f560b
javadocs fixes
Co-authored-by: chris <github@onechris.mozmail.com>
2024-02-28 13:57:41 -05:00
onebeastchris c7aa3fa580 Merge remote-tracking branch 'upstream/master' into feature/cloud-updated
# Conflicts:
#	api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java
#	bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java
#	bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java
#	bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java
#	bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java
#	bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
#	bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java
#	bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java
#	bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java
#	bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java
#	bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java
#	bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java
#	core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java
#	core/src/main/java/org/geysermc/geyser/GeyserLogger.java
#	core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java
#	core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java
#	core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java
#	core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java
#	core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java
#	core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java
#	core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java
#	gradle/libs.versions.toml
2024-01-09 17:13:30 +01:00
onebeastchris 4bd9bfc6c5 Merge remote-tracking branch 'upstream/master' into feature/cloud
# Conflicts:
#	core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java
2023-10-18 21:02:00 +02:00
Konicai aeca04a2c1
update gradle, lombok, loom 2023-10-16 23:46:34 -04:00
Konicai bfc5b7f10c Revert "loom is the bane of my existence"
This reverts commit f1ad466c75.
2023-10-06 17:34:49 -04:00
Konicai f1ad466c75 loom is the bane of my existence 2023-10-06 17:28:49 -04:00
Konicai b8b30d37e6 Merge remote-tracking branch 'upstream/master' into feature/cloud
# Conflicts:
#	core/src/main/java/org/geysermc/geyser/GeyserImpl.java
2023-10-06 17:16:52 -04:00
Konicai 35e08cc088 Improve permission messages, deprecate executableOnConsole for playerOnly 2023-10-06 17:13:29 -04:00
Konicai 37807a5115 hnnnng 2023-10-02 18:20:17 -04:00
Konicai 956d60721c Don't use Optional for CommandSource#playerUuid/connection 2023-10-02 18:04:50 -04:00
Konicai 0cfee52e48
don't let (unexpected) exceptions thrown while handling command exceptions be swallowed 2023-10-01 23:22:29 -04:00
Konicai f5b245718e
don't block standalone terminal with commands 2023-10-01 20:06:09 -04:00
Konicai 1cf6d97c09
remove some outdated todos 2023-10-01 18:58:54 -04:00
Konicai 7c083f0541
better javadocs on Command.Builder#permission 2023-10-01 17:17:43 -04:00
Konicai 8d708b6212
Update languages 2023-10-01 17:05:47 -04:00
Konicai 3a1797f852
Update lang keys for root command descriptions 2023-10-01 17:03:36 -04:00
Konicai c73b9de121
Merge remote-tracking branch 'upstream/master' into feature/cloud
# Conflicts:
#	bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
#	gradle/libs.versions.toml
2023-10-01 16:52:14 -04:00
Konicai 0fedd8b086 Simplify alias setting for extension commands 2023-09-29 20:16:26 -04:00
Konicai 215f1fea94 some misc changes 2023-09-29 20:12:45 -04:00
Konicai 24323d4e82 Cleanup nullabilities relating to extension commands 2023-09-29 20:00:18 -04:00
Konicai 799972d5a9 Reverse nullability changes for `Command.Builder#description/permission/aliases` 2023-09-29 19:24:48 -04:00
Konicai c715c8689b Move warning to debug 2023-09-29 19:01:16 -04:00
Konicai d97ed142f4 Don't explicitly clear the CommandRegistry
Discarding old registries is fine, and clearing one doesn't remove the registrations with cloud anyway.
2023-09-29 18:51:17 -04:00
Konicai 6f06ff627b Merge remote-tracking branch 'upstream/master' into feature/cloud 2023-09-29 18:30:06 -04:00
Konicai e9d4802a47 2.2.0 -> 2.3.0 2023-09-29 18:28:14 -04:00
Konicai 7580b8b3a7
address some simple reviews 2023-09-18 23:35:17 -04:00
Konicai df15625b0f
Fix usage of HelpCommand for extensions 2023-09-18 23:23:56 -04:00
Konicai 53140047ec
Strip trailing whitespace for cloud on Standalone as well 2023-09-18 23:02:35 -04:00
Konicai 6b22cf707d
cloud 1.8.4 2023-09-15 07:43:35 -04:00
Konicai 2aa2621adc javadocs 2023-09-14 13:24:42 -04:00
Konicai c40cfd6806
Merge branch 'master' into feature/cloud 2023-09-12 20:24:46 -04:00
Konicai e9140c559e Reimplement command descriptions 2023-09-12 20:23:37 -04:00
Konicai 6e4f718069 Merge remote-tracking branch 'upstream/master' into feature/cloud
# Conflicts:
#	bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java
#	bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java
#	bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java
#	bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java
#	core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java
2023-09-11 14:02:05 -04:00
Konicai 3ec2125d3f
Stop Bedrock commands from appearing in Java players' brig suggestions
move bedrock-only and player-only checks to the permission check, instead of being a postprocessor
2023-09-08 01:45:06 -04:00
Konicai 55e50fecb9
Remove trailing whitespace on Standalone GUI 2023-09-06 23:28:50 -04:00
Konicai f1e9869343
Merge upstream 2023-09-06 23:15:09 -04:00
Konicai 14e1c046e5
Target languages PR 2023-09-06 22:27:36 -04:00
Konicai 821ce46909 Update translations 2023-09-06 17:15:44 -04:00
Konicai cdf06ce350
Don't suggest opts that have already been added for dump command 2023-09-05 04:24:18 -04:00
Konicai bdbef04e8e
Customize exception handlers on all platforms 2023-09-05 00:08:23 -04:00
Konicai 12928f103b
Missing Override annotation for version command 2023-09-04 21:18:25 -04:00
Konicai acd53a4519
Refactor command abstraction and implement `/geyser` as help 2023-09-04 21:02:40 -04:00
Konicai 065abb0550 Bootstrap consistency: register commands after PreInitEvent, before PostInitEvent 2023-09-04 15:59:22 -04:00
Konicai 26f53d2254 Make extension command builder an inner class of ExtensionCommand 2023-09-04 15:08:13 -04:00
Konicai 1c45bb4420
Temporary warning if command source converter receives atypical argument 2023-09-03 23:23:01 -04:00
Konicai c7826b5e73
Move command registry creation to mod init on fabric 2023-09-03 23:16:16 -04:00
Konicai 1eba87d4e2
fix commands on fabric
- create cloud earlier
- trigger resource reload after geyser registers commands to cloud so that cloud registers them to the server
2023-09-02 23:06:22 -04:00
Konicai a640e1ac78
Merge remote-tracking branch 'origin/feature/cloud' into feature/cloud 2023-09-02 03:19:36 -04:00
Konicai 887509bde7
Don't force perm registration of perms used by commands
feeling kinda icky but idk
2023-09-02 03:16:54 -04:00
Konicai 2ab03e7415
Allow commands without aliases to have their permissions registered 2023-09-02 03:04:29 -04:00
Konicai 54b2597783
Merge branch 'master' into feature/cloud 2023-09-01 23:18:41 -04:00
Konicai 4b893e6475
Undo some changes, some legacy compat for suggestedOpOnly 2023-09-01 23:17:56 -04:00
Konicai 94d3f6229c
Define default permissions of builtin commands 2023-09-01 21:51:46 -04:00
Konicai 7ee26fb2bd
Attempt at permission defaults for spigot 2023-09-01 21:40:14 -04:00
Konicai aea2a18995
CommandBuilder -> ExtensionCommandBuilder 2023-09-01 20:10:51 -04:00
Konicai cddc34aeb7
Cleanup builtin and extension command implementations 2023-09-01 20:06:37 -04:00
Konicai 47031ce283
misc changes for reviews 2023-08-31 23:17:31 -04:00
Konicai 6c932ecc62 Handle exceptions on standalone, class renaming and moving 2023-08-30 21:24:16 -04:00
Konicai 42ada0bd87 Merge remote-tracking branch 'upstream/master' into feature/cloud
# Conflicts:
#	bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java
#	bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java
#	bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java
#	bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
#	core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java
#	core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java
#	core/src/main/resources/languages
#	gradle/libs.versions.toml
2023-08-30 18:40:03 -04:00
Konicai 049e29ed42
Make commands fully use cloud, command api deprecations 2023-06-27 04:39:00 -04:00
Konicai a830fb76fb
permissions.yml and compiling 2023-06-26 16:38:11 -04:00
Konicai f553e832e9
Merge remote-tracking branch 'upstream/master' into feature/cloud
# Conflicts:
#	bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java
2023-06-26 15:37:05 -04:00
Konicai 5b3226b0bf
Fix GeyserSession#hasPermission, remove WorldManager#hasPermission, docs 2023-06-25 03:12:03 -04:00
Konicai e3532fa4f9
Initial start on permissions 2023-06-20 15:35:01 -04:00
Konicai 2435a33414
Merge remote-tracking branch 'upstream/master' into feature/cloud
# Conflicts:
#	bootstrap/bungeecord/build.gradle.kts
#	bootstrap/spigot/build.gradle.kts
#	core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java
#	core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java
2023-06-20 01:32:02 -04:00
Konicai 411c289fa7
Refactor CommandSourceConverter 2023-06-20 01:26:16 -04:00
Konicai e2f71af6e7
Fix small mistakes 2023-06-02 20:39:15 -04:00
Konicai 8e6d7d3cc0
Cleanup the last commit 2023-06-02 20:27:29 -04:00
Konicai 37797d7ae3
Be more lenient with backwardsCommandSenderMappers
any GeyserCommandSource should be valid to use in any CommandManager as long as one of the following is satisfied
1. it is a platform implementation
2. isConsole() returns true
2. playerUuid() returns a valid uuid and the player lookup succeeds
2023-06-02 18:52:37 -04:00
Konicai 9da3345982
paper-mojangapi is no longer required
at least right now lol
2023-06-01 22:56:14 -04:00
Konicai 8d7e789338
Compiling and working on spigot 2023-06-01 21:14:49 -04:00
Konicai db5ccff9e5
Initial start on cloud 2023-06-01 00:28:38 -04:00
517 changed files with 7429 additions and 5815 deletions

View File

@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.80 and Minecraft Java 1.20.4
### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.80/81 and Minecraft Java 1.20.5/1.20.6
## Setting Up
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.

View File

@ -28,7 +28,9 @@ package org.geysermc.geyser.api.command;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.TriState;
import java.util.Collections;
import java.util.List;
@ -58,15 +60,15 @@ public interface Command {
* Gets the permission node associated with
* this command.
*
* @return the permission node for this command
* @return the permission node for this command if defined, otherwise an empty string
*/
@NonNull
String permission();
/**
* Gets the aliases for this command.
* Gets the aliases for this command, as an unmodifiable list
*
* @return the aliases for this command
* @return the aliases for this command as an unmodifiable list
*/
@NonNull
List<String> aliases();
@ -75,35 +77,39 @@ public interface Command {
* Gets if this command is designed to be used only by server operators.
*
* @return if this command is designated to be used only by server operators.
* @deprecated this method is not guaranteed to provide meaningful or expected results.
*/
boolean isSuggestedOpOnly();
/**
* Gets if this command is executable on console.
*
* @return if this command is executable on console
*/
boolean isExecutableOnConsole();
/**
* Gets the subcommands associated with this
* command. Mainly used within the Geyser Standalone
* GUI to know what subcommands are supported.
*
* @return the subcommands associated with this command
*/
@NonNull
default List<String> subCommands() {
return Collections.emptyList();
@Deprecated(forRemoval = true)
default boolean isSuggestedOpOnly() {
return false;
}
/**
* Used to send a deny message to Java players if this command can only be used by Bedrock players.
*
* @return true if this command can only be used by Bedrock players.
* @return true if this command is executable on console
* @deprecated use {@link #isPlayerOnly()} instead (inverted)
*/
default boolean isBedrockOnly() {
return false;
@Deprecated(forRemoval = true)
default boolean isExecutableOnConsole() {
return !isPlayerOnly();
}
/**
* @return true if this command can only be used by players
*/
boolean isPlayerOnly();
/**
* @return true if this command can only be used by Bedrock players
*/
boolean isBedrockOnly();
/**
* @deprecated this method will always return an empty immutable list
*/
@Deprecated(forRemoval = true)
@NonNull
default List<String> subCommands() {
return Collections.emptyList();
}
/**
@ -128,7 +134,7 @@ public interface Command {
* is an instance of this source.
*
* @param sourceType the source type
* @return the builder
* @return this builder
*/
Builder<T> source(@NonNull Class<? extends T> sourceType);
@ -136,7 +142,7 @@ public interface Command {
* Sets the command name.
*
* @param name the command name
* @return the builder
* @return this builder
*/
Builder<T> name(@NonNull String name);
@ -144,23 +150,38 @@ public interface Command {
* Sets the command description.
*
* @param description the command description
* @return the builder
* @return this builder
*/
Builder<T> description(@NonNull String description);
/**
* Sets the permission node.
* Sets the permission node required to run this command. <br>
* It will not be registered with any permission registries, such as an underlying server,
* or a permissions Extension (unlike {@link #permission(String, TriState)}).
*
* @param permission the permission node
* @return the builder
* @return this builder
*/
Builder<T> permission(@NonNull String permission);
/**
* Sets the permission node and its default value. The usage of the default value is platform dependant
* and may or may not be used. For example, it may be registered to an underlying server.
<p>
* Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} to register permissions, which
* should be used if the same permission is required by multiple commands.
*
* @param permission the permission node
* @param defaultValue the node's default value
* @return this builder
*/
Builder<T> permission(@NonNull String permission, @NonNull TriState defaultValue);
/**
* Sets the aliases.
*
* @param aliases the aliases
* @return the builder
* @return this builder
*/
Builder<T> aliases(@NonNull List<String> aliases);
@ -168,46 +189,62 @@ public interface Command {
* Sets if this command is designed to be used only by server operators.
*
* @param suggestedOpOnly if this command is designed to be used only by server operators
* @return the builder
* @return this builder
* @deprecated this method is not guaranteed to produce meaningful or expected results
*/
@Deprecated(forRemoval = true)
Builder<T> suggestedOpOnly(boolean suggestedOpOnly);
/**
* Sets if this command is executable on console.
*
* @param executableOnConsole if this command is executable on console
* @return the builder
* @return this builder
* @deprecated use {@link #isPlayerOnly()} instead (inverted)
*/
@Deprecated(forRemoval = true)
Builder<T> executableOnConsole(boolean executableOnConsole);
/**
* Sets if this command can only be executed by players.
*
* @param playerOnly if this command is player only
* @return this builder
*/
Builder<T> playerOnly(boolean playerOnly);
/**
* Sets if this command can only be executed by bedrock players.
*
* @param bedrockOnly if this command is bedrock only
* @return this builder
*/
Builder<T> bedrockOnly(boolean bedrockOnly);
/**
* Sets the subcommands.
*
* @param subCommands the subcommands
* @return the builder
* @return this builder
* @deprecated this method has no effect
*/
Builder<T> subCommands(@NonNull List<String> subCommands);
/**
* Sets if this command is bedrock only.
*
* @param bedrockOnly if this command is bedrock only
* @return the builder
*/
Builder<T> bedrockOnly(boolean bedrockOnly);
@Deprecated(forRemoval = true)
default Builder<T> subCommands(@NonNull List<String> subCommands) {
return this;
}
/**
* Sets the {@link CommandExecutor} for this command.
*
* @param executor the command executor
* @return the builder
* @return this builder
*/
Builder<T> executor(@NonNull CommandExecutor<T> executor);
/**
* Builds the command.
*
* @return the command
* @return a new command from this builder
*/
@NonNull
Command build();

View File

@ -26,6 +26,10 @@
package org.geysermc.geyser.api.command;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.connection.Connection;
import java.util.UUID;
/**
* Represents an instance capable of sending commands.
@ -64,6 +68,17 @@ public interface CommandSource {
*/
boolean isConsole();
/**
* @return a Java UUID if this source represents a player, otherwise null
*/
@Nullable UUID playerUuid();
/**
* @return a Connection if this source represents a Bedrock player that is connected
* to this Geyser instance, otherwise null
*/
@Nullable Connection connection();
/**
* Returns the locale of the command source.
*

View File

@ -32,6 +32,9 @@ import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import org.geysermc.geyser.api.network.RemoteServer;
import java.util.Map;
import java.util.Objects;
/**
* Called when a session has logged in, and is about to connect to a remote java server.
* This event is cancellable, and can be used to prevent the player from connecting to the remote server.
@ -40,10 +43,16 @@ public final class SessionLoginEvent extends ConnectionEvent implements Cancella
private RemoteServer remoteServer;
private boolean cancelled;
private String disconnectReason;
private Map<String, byte[]> cookies;
private boolean transferring;
public SessionLoginEvent(@NonNull GeyserConnection connection, @NonNull RemoteServer remoteServer) {
public SessionLoginEvent(@NonNull GeyserConnection connection,
@NonNull RemoteServer remoteServer,
@NonNull Map<String, byte[]> cookies) {
super(connection);
this.remoteServer = remoteServer;
this.cookies = cookies;
this.transferring = false;
}
/**
@ -106,4 +115,36 @@ public final class SessionLoginEvent extends ConnectionEvent implements Cancella
public void remoteServer(@NonNull RemoteServer remoteServer) {
this.remoteServer = remoteServer;
}
/**
* Sets a map of cookies from a possible previous session. The Java server can send and request these
* to store information on the client across server transfers.
*/
public void cookies(@NonNull Map<String, byte[]> cookies) {
Objects.requireNonNull(cookies);
this.cookies = cookies;
}
/**
* Gets a map of the sessions cookies, if set.
* @return the connections cookies
*/
public @NonNull Map<String, byte[]> cookies() {
return cookies;
}
/**
* Determines the connection intent of the connection
*/
public void transferring(boolean transferring) {
this.transferring = transferring;
}
/**
* Gets whether this login attempt to the Java server
* has the transfer intent
*/
public boolean transferring() {
return this.transferring;
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.event.java;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.qual.IntRange;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import java.util.Map;
/**
* Fired when the Java server sends a transfer request to a different Java server.
* Geyser Extensions can listen to this event and set a target server ip/port for Bedrock players to be transferred to.
*/
public class ServerTransferEvent extends ConnectionEvent {
private final String host;
private final int port;
private String bedrockHost;
private int bedrockPort;
private final Map<String, byte[]> cookies;
public ServerTransferEvent(@NonNull GeyserConnection connection,
@NonNull String host, int port, @NonNull Map<String, byte[]> cookies) {
super(connection);
this.host = host;
this.port = port;
this.cookies = cookies;
this.bedrockHost = null;
this.bedrockPort = -1;
}
/**
* The host that the Java server requests a transfer to.
*
* @return the host
*/
public @NonNull String host() {
return this.host;
}
/**
* The port that the Java server requests a transfer to.
*
* @return the port
*/
public int port() {
return this.port;
}
/**
* The host that the Bedrock player should try and connect to.
* If this is not set, the Bedrock player will just be disconnected.
*
* @return the host where the Bedrock client will be transferred to, or null if not set.
*/
public @Nullable String bedrockHost() {
return this.bedrockHost;
}
/**
* The port that the Bedrock player should try and connect to.
* If this is not set, the Bedrock player will just be disconnected.
*
* @return the port where the Bedrock client will be transferred to, or -1 if not set.
*/
public int bedrockPort() {
return this.bedrockPort;
}
/**
* Sets the host for the Bedrock player to be transferred to
*/
public void bedrockHost(@NonNull String host) {
if (host == null || host.isBlank()) {
throw new IllegalArgumentException("Server address cannot be null or blank");
}
this.bedrockHost = host;
}
/**
* Sets the port for the Bedrock player to be transferred to
*/
public void bedrockPort(@IntRange(from = 0, to = 65535) int port) {
if (port < 0 || port > 65535) {
throw new IllegalArgumentException("Server port must be between 0 and 65535, was " + port);
}
this.bedrockPort = port;
}
/**
* Gets a map of the sessions current cookies.
*
* @return the connections cookies
*/
public @NonNull Map<String, byte[]> cookies() {
return cookies;
}
}

View File

@ -50,7 +50,7 @@ public interface GeyserDefineCommandsEvent extends Event {
/**
* Gets all the registered built-in {@link Command}s.
*
* @return all the registered built-in commands
* @return all the registered built-in commands as an unmodifiable map
*/
@NonNull
Map<String, Command> commands();

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.event.lifecycle;
import org.geysermc.event.Event;
import org.geysermc.event.PostOrder;
import org.geysermc.geyser.api.permission.PermissionChecker;
/**
* Fired by any permission manager implementations that wish to add support for custom permission checking.
* This event is not guaranteed to be fired - it is currently only fired on Geyser-Standalone.
* <p>
* Subscribing to this event with an earlier {@link PostOrder} and registering a {@link PermissionChecker}
* will result in that checker having a higher priority than others.
*/
public interface GeyserRegisterPermissionCheckersEvent extends Event {
void register(PermissionChecker checker);
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.event.lifecycle;
import org.geysermc.event.Event;
import org.geysermc.geyser.api.util.TriState;
/**
* Fired by anything that wishes to gather permission nodes and defaults.
* <p>
* This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system.
* It can be expected to fire on Geyser-Spigot, Geyser-NeoForge and Geyser-Standalone.
* It may still be fired on other platforms due to a 3rd party.
*/
public interface GeyserRegisterPermissionsEvent extends Event {
/**
* Registers a permission node and its default value with the firer.
*
* @param permission the permission node to register
* @param defaultValue the default value of the node
*/
void register(String permission, TriState defaultValue);
}

View File

@ -107,6 +107,15 @@ public interface Extension extends EventRegistrar {
return this.extensionLoader().description(this);
}
/**
* @return the root command that all of this extension's commands will stem from.
* By default, this is the extension's id.
*/
@NonNull
default String rootCommand() {
return this.description().id();
}
/**
* Gets the extension's logger
*

View File

@ -23,30 +23,27 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.item.type;
package org.geysermc.geyser.api.permission;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.item.DyeableLeatherItem;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.util.TriState;
public class DyeableHorseArmorItem extends Item implements DyeableLeatherItem {
public DyeableHorseArmorItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
/**
* Something capable of checking if a {@link CommandSource} has a permission
*/
@FunctionalInterface
public interface PermissionChecker {
@Override
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
super.translateNbtToBedrock(session, tag);
DyeableLeatherItem.translateNbtToBedrock(tag);
}
@Override
public void translateNbtToJava(@NonNull CompoundTag tag, @NonNull ItemMapping mapping) {
super.translateNbtToJava(tag, mapping);
DyeableLeatherItem.translateNbtToJava(tag);
}
/**
* Checks if the given source has a permission
*
* @param source the {@link CommandSource} whose permissions should be queried
* @param permission the permission node to check
* @return a {@link TriState} as the value of the node. {@link TriState#NOT_SET} generally means that the permission
* node itself was not found, and the source does not have such permission.
* {@link TriState#TRUE} and {@link TriState#FALSE} represent explicitly set values.
*/
@NonNull
TriState hasPermission(@NonNull CommandSource source, @NonNull String permission);
}

View File

@ -1,5 +1,7 @@
dependencies {
api(projects.core)
implementation(libs.cloud.bungee)
implementation(libs.adventure.text.serializer.bungeecord)
compileOnlyApi(libs.bungeecord.proxy)
}
@ -8,13 +10,14 @@ platformRelocate("net.md_5.bungee.jni")
platformRelocate("com.fasterxml.jackson")
platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound
platformRelocate("net.kyori")
platformRelocate("org.incendo")
platformRelocate("org.yaml") // Broken as of 1.20
// These dependencies are already present on the platform
provided(libs.bungeecord.proxy)
application {
mainClass.set("org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain")
tasks.withType<Jar> {
manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain"
}
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {

View File

@ -27,6 +27,7 @@ package org.geysermc.geyser.platform.bungeecord;
import io.netty.channel.Channel;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.ProtocolConstants;
@ -34,17 +35,20 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor;
import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.bungee.BungeeCommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator;
import java.io.File;
import java.io.IOException;
@ -54,7 +58,6 @@ import java.net.SocketAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -62,14 +65,16 @@ import java.util.logging.Level;
public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
private GeyserCommandManager geyserCommandManager;
private CommandRegistry commandRegistry;
private GeyserBungeeConfiguration geyserConfig;
private GeyserBungeeInjector geyserInjector;
private GeyserBungeeLogger geyserLogger;
private IGeyserPingPassthrough geyserBungeePingPassthrough;
private GeyserImpl geyser;
// We can't disable the plugin; hence we need to keep track of it manually
private boolean disabled;
@Override
public void onLoad() {
onGeyserInitialize();
@ -94,16 +99,23 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
if (!this.loadConfig()) {
disabled = true;
return;
}
this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this);
this.geyserInjector = new GeyserBungeeInjector(this);
// Registration of listeners occurs only once
this.getProxy().getPluginManager().registerListener(this, new GeyserBungeeUpdateListener());
}
@Override
public void onEnable() {
if (disabled) {
return; // Config did not load properly!
}
// Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
// task that waits for a field to be filled which is set after the plugin enable
// process is complete
@ -144,10 +156,19 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
} else {
// For consistency with other platforms - create command manager before GeyserImpl#start()
// This ensures the command events are called before the item/block ones are
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
var sourceConverter = new CommandSourceConverter<>(
CommandSender.class,
id -> getProxy().getPlayer(id),
() -> getProxy().getConsole(),
BungeeCommandSource::new
);
CommandManager<GeyserCommandSource> cloud = new BungeeCommandManager<>(
this,
ExecutionCoordinator.simpleCoordinator(),
sourceConverter
);
this.commandRegistry = new CommandRegistry(geyser, cloud);
}
// Force-disable query if enabled, or else Geyser won't enable
@ -182,16 +203,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
this.geyserInjector.initializeLocalChannel(this);
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands()));
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
Map<String, Command> commands = entry.getValue();
if (commands.isEmpty()) {
continue;
}
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands));
}
}
@Override
@ -227,8 +238,8 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return this.geyserCommandManager;
public CommandRegistry getCommandRegistry() {
return this.commandRegistry;
}
@Override

View File

@ -27,19 +27,22 @@ package org.geysermc.geyser.platform.bungeecord.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Locale;
import java.util.UUID;
public class BungeeCommandSource implements GeyserCommandSource {
private final net.md_5.bungee.api.CommandSender handle;
private final CommandSender handle;
public BungeeCommandSource(net.md_5.bungee.api.CommandSender handle) {
public BungeeCommandSource(CommandSender handle) {
this.handle = handle;
// Ensure even Java players' languages are loaded
GeyserLocale.loadGeyserLocale(this.locale());
@ -72,12 +75,20 @@ public class BungeeCommandSource implements GeyserCommandSource {
return !(handle instanceof ProxiedPlayer);
}
@Override
public @Nullable UUID playerUuid() {
if (handle instanceof ProxiedPlayer player) {
return player.getUniqueId();
}
return null;
}
@Override
public String locale() {
if (handle instanceof ProxiedPlayer player) {
Locale locale = player.getLocale();
if (locale != null) {
// Locale can be null early on in the conneciton
// Locale can be null early on in the connection
return GeyserLocale.formatLocale(locale.getLanguage() + "_" + locale.getCountry());
}
}
@ -88,4 +99,9 @@ public class BungeeCommandSource implements GeyserCommandSource {
public boolean hasPermission(String permission) {
return handle.hasPermission(permission);
}
@Override
public Object handle() {
return handle;
}
}

View File

@ -1,89 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.bungeecord.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
public class GeyserBungeeCommandExecutor extends Command implements TabExecutor {
private final GeyserCommandExecutor commandExecutor;
public GeyserBungeeCommandExecutor(String name, GeyserImpl geyser, Map<String, org.geysermc.geyser.api.command.Command> commands) {
super(name);
this.commandExecutor = new GeyserCommandExecutor(geyser, commands);
}
@Override
public void execute(CommandSender sender, String[] args) {
BungeeCommandSource commandSender = new BungeeCommandSource(sender);
GeyserSession session = this.commandExecutor.getGeyserSession(commandSender);
if (args.length > 0) {
GeyserCommand command = this.commandExecutor.getCommand(args[0]);
if (command != null) {
if (!sender.hasPermission(command.permission())) {
String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale());
commandSender.sendMessage(ChatColor.RED + message);
return;
}
if (command.isBedrockOnly() && session == null) {
String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale());
commandSender.sendMessage(ChatColor.RED + message);
return;
}
command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
} else {
String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale());
commandSender.sendMessage(ChatColor.RED + message);
}
} else {
this.commandExecutor.getCommand("help").execute(session, commandSender, new String[0]);
}
}
@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
if (args.length == 1) {
return commandExecutor.tabComplete(new BungeeCommandSource(sender));
} else {
return Collections.emptyList();
}
}
}

View File

@ -1,7 +1,3 @@
plugins {
application
}
architectury {
platformSetupLoomIde()
fabric()
@ -38,20 +34,17 @@ dependencies {
shadow(projects.api) { isTransitive = false }
shadow(projects.common) { isTransitive = false }
// Permissions
modImplementation(libs.fabric.permissions)
include(libs.fabric.permissions)
modImplementation(libs.cloud.fabric)
include(libs.cloud.fabric)
}
application {
mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain")
tasks.withType<Jar> {
manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.fabric.GeyserFabricMain"
}
relocate("org.cloudburstmc.netty")
relocate("org.cloudburstmc.protocol")
relocate("com.github.steveice10.mc.protocol")
relocate("com.github.steveice10.mc.auth")
relocate("com.github.steveice10.packetlib")
tasks {
remapJar {

View File

@ -25,17 +25,24 @@
package org.geysermc.geyser.platform.fabric;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.fabric.FabricServerCommandManager;
public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer {
@ -45,28 +52,47 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit
@Override
public void onInitialize() {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) {
if (isServer()) {
// Set as an event, so we can get the proper IP and port if needed
ServerLifecycleEvents.SERVER_STARTED.register((server) -> {
this.setServer(server);
onGeyserEnable();
});
} else {
ClientLifecycleEvents.CLIENT_STOPPING.register(($)-> {
onGeyserShutdown();
});
}
// These are only registered once
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onGeyserShutdown());
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> {
if (isServer()) {
onGeyserShutdown();
} else {
onGeyserDisable();
}
});
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer()));
this.onGeyserInitialize();
var sourceConverter = CommandSourceConverter.layered(
CommandSourceStack.class,
id -> getServer().getPlayerList().getPlayer(id),
Player::createCommandSourceStack,
() -> getServer().createCommandSourceStack(), // NPE if method reference is used, since server is not available yet
ModCommandSource::new
);
CommandManager<GeyserCommandSource> cloud = new FabricServerCommandManager<>(
ExecutionCoordinator.simpleCoordinator(),
sourceConverter
);
this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud));
}
@Override
public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
return Permissions.check(source, permissionNode);
}
@Override
public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
return Permissions.check(source, permissionNode, permissionLevel);
public boolean isServer() {
return FabricLoader.getInstance().getEnvironmentType().equals(EnvType.SERVER);
}
}

View File

@ -23,9 +23,8 @@
"geyser.mixins.json"
],
"depends": {
"fabricloader": ">=0.15.2",
"fabricloader": ">=0.15.10",
"fabric": "*",
"minecraft": ">=1.20.4",
"fabric-permissions-api-v0": "*"
"minecraft": ">=1.20.5"
}
}

View File

@ -1,7 +1,3 @@
plugins {
application
}
// This is provided by "org.cloudburstmc.math.mutable" too, so yeet.
// NeoForge's class loader is *really* annoying.
provided("org.cloudburstmc.math", "api")
@ -35,10 +31,13 @@ dependencies {
// Include all transitive deps of core via JiJ
includeTransitive(projects.core)
modImplementation(libs.cloud.neoforge)
include(libs.cloud.neoforge)
}
application {
mainClass.set("org.geysermc.geyser.platform.forge.GeyserNeoForgeMain")
tasks.withType<Jar> {
manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.neoforge.GeyserNeoForgeMain"
}
tasks {

View File

@ -27,35 +27,57 @@ package org.geysermc.geyser.platform.neoforge;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.EventPriority;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.GameShuttingDownEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.event.server.ServerStartedEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.neoforge.NeoForgeServerCommandManager;
@Mod(ModConstants.MOD_ID)
public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
private final GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler();
public GeyserNeoForgeBootstrap() {
super(new GeyserNeoForgePlatform());
if (FMLLoader.getDist() == Dist.DEDICATED_SERVER) {
if (isServer()) {
// Set as an event so we can get the proper IP and port if needed
NeoForge.EVENT_BUS.addListener(this::onServerStarted);
} else {
NeoForge.EVENT_BUS.addListener(this::onClientStopping);
}
NeoForge.EVENT_BUS.addListener(this::onServerStopping);
NeoForge.EVENT_BUS.addListener(this::onPlayerJoin);
NeoForge.EVENT_BUS.addListener(this.permissionHandler::onPermissionGather);
GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler();
NeoForge.EVENT_BUS.addListener(EventPriority.HIGHEST, permissionHandler::onPermissionGather);
this.onGeyserInitialize();
var sourceConverter = CommandSourceConverter.layered(
CommandSourceStack.class,
id -> getServer().getPlayerList().getPlayer(id),
Player::createCommandSourceStack,
() -> getServer().createCommandSourceStack(),
ModCommandSource::new
);
CommandManager<GeyserCommandSource> cloud = new NeoForgeServerCommandManager<>(
ExecutionCoordinator.simpleCoordinator(),
sourceConverter
);
this.setCommandRegistry(new GeyserNeoForgeCommandRegistry(GeyserImpl.getInstance(), cloud));
}
private void onServerStarted(ServerStartedEvent event) {
@ -64,6 +86,14 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
}
private void onServerStopping(ServerStoppingEvent event) {
if (isServer()) {
this.onGeyserShutdown();
} else {
this.onGeyserDisable();
}
}
private void onClientStopping(GameShuttingDownEvent ignored) {
this.onGeyserShutdown();
}
@ -72,12 +102,7 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
}
@Override
public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
return this.permissionHandler.hasPermission(source, permissionNode);
}
@Override
public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
return this.permissionHandler.hasPermission(source, permissionNode, permissionLevel);
public boolean isServer() {
return FMLLoader.getDist().isDedicatedServer();
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.neoforge;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.incendo.cloud.CommandManager;
public class GeyserNeoForgeCommandRegistry extends CommandRegistry {
public GeyserNeoForgeCommandRegistry(GeyserImpl geyser, CommandManager<GeyserCommandSource> cloud) {
super(geyser, cloud);
}
// todo yeet once cloud enforced method contract here:
// https://github.com/Incendo/cloud/blob/master/cloud-core/src/main/java/org/incendo/cloud/CommandManager.java#L441-L449
@Override
public boolean hasPermission(GeyserCommandSource source, String permission) {
try {
return super.hasPermission(source, permission);
} catch (UncheckedExecutionException e) {
return false;
}
}
}

View File

@ -25,24 +25,16 @@
package org.geysermc.geyser.platform.neoforge;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.neoforged.neoforge.server.permission.PermissionAPI;
import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey;
import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
import net.neoforged.neoforge.server.permission.nodes.PermissionType;
import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.api.util.TriState;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class GeyserNeoForgePermissionHandler {
@ -64,72 +56,28 @@ public class GeyserNeoForgePermissionHandler {
}
}
private final Map<String, PermissionNode<Boolean>> permissionNodes = new HashMap<>();
public void onPermissionGather(PermissionGatherEvent.Nodes event) {
this.registerNode(Constants.UPDATE_PERMISSION, event);
GeyserCommandManager commandManager = GeyserImpl.getInstance().commandManager();
for (Map.Entry<String, Command> entry : commandManager.commands().entrySet()) {
Command command = entry.getValue();
// Don't register aliases
if (!command.name().equals(entry.getKey())) {
continue;
}
this.registerNode(command.permission(), event);
}
for (Map<String, Command> commands : commandManager.extensionCommands().values()) {
for (Map.Entry<String, Command> entry : commands.entrySet()) {
Command command = entry.getValue();
// Don't register aliases
if (!command.name().equals(entry.getKey())) {
continue;
GeyserImpl.getInstance().eventBus().fire(
(GeyserRegisterPermissionsEvent) (permission, defaultValue) -> {
if (permission.isBlank()) {
return;
}
this.registerNode(command.permission(), event);
this.registerNode(permission, defaultValue, event);
}
}
);
}
public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
PermissionNode<Boolean> node = this.permissionNodes.get(permissionNode);
if (node == null) {
GeyserImpl.getInstance().getLogger().warning("Unable to find permission node " + permissionNode);
return false;
}
return PermissionAPI.getPermission((ServerPlayer) source, node);
}
public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
if (!source.isPlayer()) {
return true;
}
assert source.getPlayer() != null;
boolean permission = this.hasPermission(source.getPlayer(), permissionNode);
if (!permission) {
return source.getPlayer().hasPermissions(permissionLevel);
}
return true;
}
private void registerNode(String node, PermissionGatherEvent.Nodes event) {
PermissionNode<Boolean> permissionNode = this.createNode(node);
private void registerNode(String node, TriState permissionDefault, PermissionGatherEvent.Nodes event) {
PermissionNode<Boolean> permissionNode = this.createNode(node, permissionDefault);
// NeoForge likes to crash if you try and register a duplicate node
if (!event.getNodes().contains(permissionNode)) {
if (event.getNodes().stream().noneMatch(eventNode -> eventNode.getNodeName().equals(node))) {
event.addNodes(permissionNode);
this.permissionNodes.put(node, permissionNode);
}
}
@SuppressWarnings("unchecked")
private PermissionNode<Boolean> createNode(String node) {
private PermissionNode<Boolean> createNode(String node, TriState permissionDefault) {
// The typical constructors in PermissionNode require a
// mod id, which means our permission nodes end up becoming
// geyser_neoforge.<node> instead of just <node>. We work around
@ -139,7 +87,16 @@ public class GeyserNeoForgePermissionHandler {
return (PermissionNode<Boolean>) PERMISSION_NODE_CONSTRUCTOR.newInstance(
node,
PermissionTypes.BOOLEAN,
(PermissionNode.PermissionResolver<Boolean>) (player, playerUUID, context) -> false,
(PermissionNode.PermissionResolver<Boolean>) (player, playerUUID, context) -> switch (permissionDefault) {
case TRUE -> true;
case FALSE -> false;
case NOT_SET -> {
if (player != null) {
yield player.createCommandSourceStack().hasPermission(player.server.getOperatorUserPermissionLevel());
}
yield false;
}
},
new PermissionDynamicContextKey[0]
);
} catch (Exception e) {

View File

@ -14,12 +14,12 @@ config = "geyser.mixins.json"
[[dependencies.geyser_neoforge]]
modId="neoforge"
type="required"
versionRange="[20.4.48-beta,)"
versionRange="[20.5.0-beta,)"
ordering="NONE"
side="BOTH"
[[dependencies.geyser_neoforge]]
modId="minecraft"
type="required"
versionRange="[1.20,1.21)"
versionRange="[1.20.5,1.21)"
ordering="NONE"
side="BOTH"

View File

@ -25,31 +25,22 @@
package org.geysermc.geyser.platform.mod;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.player.Player;
import org.apache.logging.log4j.LogManager;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.mod.command.GeyserModCommandExecutor;
import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager;
import org.geysermc.geyser.text.GeyserLocale;
@ -58,8 +49,8 @@ import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketAddress;
import java.nio.file.Path;
import java.util.Map;
import java.util.UUID;
@RequiredArgsConstructor
@ -69,14 +60,14 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
private static GeyserModBootstrap instance;
private final GeyserModPlatform platform;
private GeyserImpl geyser;
private Path dataFolder;
@Setter
@Setter @Getter
private MinecraftServer server;
private GeyserCommandManager geyserCommandManager;
@Setter
private CommandRegistry commandRegistry;
private GeyserModConfiguration geyserConfig;
private GeyserModInjector geyserInjector;
private GeyserModLogger geyserLogger;
@ -94,10 +85,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
this.geyserLogger = new GeyserModLogger(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(this.platform.platformType(), this);
// Create command manager here, since the permission handler on neo needs it
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
}
public void onGeyserEnable() {
@ -127,50 +114,8 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
// We want to do this late in the server startup process to allow other mods
// To do their job injecting, then connect into *that*
this.geyserInjector = new GeyserModInjector(server, this.platform);
this.geyserInjector.initializeLocalChannel(this);
// Start command building
// Set just "geyser" as the help command
GeyserModCommandExecutor helpExecutor = new GeyserModCommandExecutor(geyser,
(GeyserCommand) geyser.commandManager().getCommands().get("help"));
LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal("geyser").executes(helpExecutor);
// Register all subcommands as valid
for (Map.Entry<String, Command> command : geyser.commandManager().getCommands().entrySet()) {
GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
builder.then(Commands.literal(command.getKey())
.executes(executor)
// Could also test for Bedrock but depending on when this is called it may backfire
.requires(executor::testPermission)
// Allows parsing of arguments; e.g. for /geyser dump logs or the connectiontest command
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
.requires(executor::testPermission)));
}
server.getCommands().getDispatcher().register(builder);
// Register extension commands
for (Map.Entry<Extension, Map<String, Command>> extensionMapEntry : geyser.commandManager().extensionCommands().entrySet()) {
Map<String, Command> extensionCommands = extensionMapEntry.getValue();
if (extensionCommands.isEmpty()) {
continue;
}
// Register help command for just "/<extensionId>"
GeyserModCommandExecutor extensionHelpExecutor = new GeyserModCommandExecutor(geyser,
(GeyserCommand) extensionCommands.get("help"));
LiteralArgumentBuilder<CommandSourceStack> extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor);
for (Map.Entry<String, Command> command : extensionCommands.entrySet()) {
GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
extCmdBuilder.then(Commands.literal(command.getKey())
.executes(executor)
.requires(executor::testPermission)
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
.requires(executor::testPermission)));
}
server.getCommands().getDispatcher().register(extCmdBuilder);
if (isServer()) {
this.geyserInjector.initializeLocalChannel(this);
}
}
@ -204,8 +149,8 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return geyserCommandManager;
public CommandRegistry getCommandRegistry() {
return commandRegistry;
}
@Override
@ -233,6 +178,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
return this.server.getServerVersion();
}
@SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null
@NonNull
@Override
public String getServerBindAddress() {
@ -241,10 +187,22 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
}
@Override
public int getServerPort() {
return ((GeyserServerPortGetter) server).geyser$getServerPort();
public SocketAddress getSocketAddress() {
return this.geyserInjector.getServerSocketAddress();
}
@Override
public int getServerPort() {
if (isServer()) {
return ((GeyserServerPortGetter) server).geyser$getServerPort();
} else {
// Set in the IntegratedServerMixin
return geyserConfig.getRemote().port();
}
}
public abstract boolean isServer();
@Override
public boolean testFloodgatePluginPresent() {
return this.platform.testFloodgatePluginPresent(this);
@ -256,10 +214,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
return this.platform.resolveResource(resource);
}
public abstract boolean hasPermission(@NonNull Player source, @NonNull String permissionNode);
public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel);
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
try {

View File

@ -93,8 +93,11 @@ public class GeyserModInjector extends GeyserInjector {
protected void initChannel(@NonNull Channel ch) throws Exception {
initChannel.invoke(childHandler, ch);
int index = ch.pipeline().names().indexOf("encoder");
String baseName = index != -1 ? "encoder" : "outbound_config";
if (bootstrap.getGeyserConfig().isDisableCompression()) {
ch.pipeline().addAfter("encoder", "geyser-compression-disabler", new GeyserModCompressionDisabler());
ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserModCompressionDisabler());
}
}
})

View File

@ -25,17 +25,16 @@
package org.geysermc.geyser.platform.mod;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.platform.mod.command.ModCommandSender;
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.geysermc.geyser.util.VersionCheckUtils;
public final class GeyserModUpdateListener {
public static void onPlayReady(Player player) {
CommandSourceStack stack = player.createCommandSourceStack();
if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION, 2)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSender(stack));
ModCommandSource source = new ModCommandSource(player.createCommandSourceStack());
if (source.hasPermission(Constants.UPDATE_PERMISSION)) {
VersionCheckUtils.checkForGeyserUpdate(() -> source);
}
}

View File

@ -29,6 +29,7 @@ import lombok.AllArgsConstructor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.Connection;
import net.minecraft.network.PacketSendListener;
import net.minecraft.network.protocol.Packet;
@ -69,7 +70,7 @@ public class ModPingPassthrough implements IGeyserPingPassthrough {
StatusInterceptor connection = new StatusInterceptor();
ServerStatusPacketListener statusPacketListener = new ServerStatusPacketListenerImpl(status, connection);
statusPacketListener.handleStatusRequest(new ServerboundStatusRequestPacket());
statusPacketListener.handleStatusRequest(ServerboundStatusRequestPacket.INSTANCE);
// mods like MiniMOTD (that inject into the above method) have now processed the response
status = Objects.requireNonNull(connection.status, "status response");
} catch (Exception e) {
@ -79,7 +80,7 @@ public class ModPingPassthrough implements IGeyserPingPassthrough {
}
}
String jsonDescription = net.minecraft.network.chat.Component.Serializer.toJson(status.description());
String jsonDescription = net.minecraft.network.chat.Component.Serializer.toJson(status.description(), RegistryAccess.EMPTY);
String legacyDescription = LEGACY_SERIALIZER.serialize(GSON_SERIALIZER.deserializeOr(jsonDescription, Component.empty()));
return new GeyserPingInfo(

View File

@ -1,75 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.mod.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Collections;
public class GeyserModCommandExecutor extends GeyserCommandExecutor implements Command<CommandSourceStack> {
private final GeyserCommand command;
public GeyserModCommandExecutor(GeyserImpl geyser, GeyserCommand command) {
super(geyser, Collections.singletonMap(command.name(), command));
this.command = command;
}
public boolean testPermission(CommandSourceStack source) {
return GeyserModBootstrap.getInstance().hasPermission(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0);
}
@Override
public int run(CommandContext<CommandSourceStack> context) {
return runWithArgs(context, "");
}
public int runWithArgs(CommandContext<CommandSourceStack> context, String args) {
CommandSourceStack source = context.getSource();
ModCommandSender sender = new ModCommandSender(source);
GeyserSession session = getGeyserSession(sender);
if (!testPermission(source)) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return 0;
}
if (command.isBedrockOnly() && session == null) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
return 0;
}
command.execute(session, sender, args.split(" "));
return 0;
}
}

View File

@ -27,21 +27,23 @@ package org.geysermc.geyser.platform.mod.command;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.text.ChatColor;
import java.util.Objects;
import java.util.UUID;
public class ModCommandSender implements GeyserCommandSource {
public class ModCommandSource implements GeyserCommandSource {
private final CommandSourceStack source;
public ModCommandSender(CommandSourceStack source) {
public ModCommandSource(CommandSourceStack source) {
this.source = source;
}
@ -63,7 +65,7 @@ public class ModCommandSender implements GeyserCommandSource {
public void sendMessage(net.kyori.adventure.text.Component message) {
if (source.getEntity() instanceof ServerPlayer player) {
String decoded = GsonComponentSerializer.gson().serialize(message);
player.displayClientMessage(Objects.requireNonNull(Component.Serializer.fromJson(decoded)), false);
player.displayClientMessage(Objects.requireNonNull(Component.Serializer.fromJson(decoded, RegistryAccess.EMPTY)), false);
return;
}
GeyserCommandSource.super.sendMessage(message);
@ -75,7 +77,23 @@ public class ModCommandSender implements GeyserCommandSource {
}
@Override
public boolean hasPermission(String permission) {
return GeyserModBootstrap.getInstance().hasPermission(source, permission, source.getServer().getOperatorUserPermissionLevel());
public @Nullable UUID playerUuid() {
if (source.getEntity() instanceof ServerPlayer player) {
return player.getUUID();
}
return null;
}
}
@Override
public boolean hasPermission(String permission) {
// Unlike other bootstraps; we delegate to cloud here too:
// On NeoForge; we'd have to keep track of all PermissionNodes - cloud already does that
// For Fabric, we won't need to include the Fabric Permissions API anymore - cloud already does that too :p
return GeyserImpl.getInstance().commandRegistry().hasPermission(this, permission);
}
@Override
public Object handle() {
return source;
}
}

View File

@ -54,8 +54,10 @@ public class IntegratedServerMixin implements GeyserServerPortGetter {
private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable<Boolean> cir) {
if (cir.getReturnValueZ()) {
// If the LAN is opened, starts Geyser.
GeyserModBootstrap.getInstance().setServer((MinecraftServer) (Object) this);
GeyserModBootstrap.getInstance().onGeyserEnable();
GeyserModBootstrap instance = GeyserModBootstrap.getInstance();
instance.setServer((MinecraftServer) (Object) this);
instance.getGeyserConfig().getRemote().setPort(port);
instance.onGeyserEnable();
// Ensure player locale has been loaded, in case it's different from Java system language
GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode);
// Give indication that Geyser is loaded

View File

@ -25,39 +25,34 @@
package org.geysermc.geyser.platform.mod.world;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityInfo;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.ByteArrayTag;
import net.minecraft.nbt.ByteTag;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.DoubleTag;
import net.minecraft.nbt.EndTag;
import net.minecraft.nbt.FloatTag;
import net.minecraft.nbt.IntArrayTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.LongArrayTag;
import net.minecraft.nbt.LongTag;
import net.minecraft.nbt.ShortTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.TagVisitor;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.Filterable;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.WritableBookItem;
import net.minecraft.world.item.WrittenBookItem;
import net.minecraft.world.item.component.WritableBookContent;
import net.minecraft.world.item.component.WrittenBookContent;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BannerBlockEntity;
import net.minecraft.world.level.block.entity.BannerPatternLayers;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.LecternBlockEntity;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
@ -71,11 +66,14 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.BlockEntityUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public class GeyserModWorldManager extends GeyserWorldManager {
private static final GsonComponentSerializer GSON_SERIALIZER = GsonComponentSerializer.gson();
private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.legacySection();
private final MinecraftServer server;
public GeyserModWorldManager(MinecraftServer server) {
@ -180,7 +178,7 @@ public class GeyserModWorldManager extends GeyserWorldManager {
}
ItemStack book = lectern.getBook();
int pageCount = WrittenBookItem.getPageCount(book);
int pageCount = getPageCount(book);
boolean hasBookPages = pageCount > 0;
NbtMapBuilder lecternTag = LecternUtils.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1);
lecternTag.putInt("page", lectern.getPage() / 2);
@ -189,11 +187,9 @@ public class GeyserModWorldManager extends GeyserWorldManager {
.putShort("Damage", (short) 0)
.putString("Name", "minecraft:writable_book");
List<NbtMap> pages = new ArrayList<>(hasBookPages ? pageCount : 1);
if (hasBookPages && WritableBookItem.makeSureTagIsValid(book.getTag())) {
ListTag listTag = book.getTag().getList("pages", 8);
for (int i = 0; i < listTag.size(); i++) {
String page = listTag.getString(i);
if (hasBookPages) {
List<String> bookPages = getPages(book);
for (String page : bookPages) {
NbtMapBuilder pageBuilder = NbtMap.builder()
.putString("photoname", "")
.putString("text", page);
@ -213,12 +209,6 @@ public class GeyserModWorldManager extends GeyserWorldManager {
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z));
}
@Override
public boolean hasPermission(GeyserSession session, String permission) {
ServerPlayer player = getPlayer(session);
return GeyserModBootstrap.getInstance().hasPermission(player, permission);
}
@Override
public GameMode getDefaultGameMode(GeyserSession session) {
return GameMode.byId(server.getDefaultGameType().getId());
@ -226,8 +216,8 @@ public class GeyserModWorldManager extends GeyserWorldManager {
@NonNull
@Override
public CompletableFuture<com.github.steveice10.opennbt.tag.builtin.CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
CompletableFuture<com.github.steveice10.opennbt.tag.builtin.CompoundTag> future = new CompletableFuture<>();
public CompletableFuture<org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
CompletableFuture<org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents> future = new CompletableFuture<>();
server.execute(() -> {
ServerPlayer player = getPlayer(session);
if (player == null) {
@ -243,9 +233,23 @@ public class GeyserModWorldManager extends GeyserWorldManager {
// Potentially exposes other NBT data? But we need to get the NBT data for the banner patterns *and*
// the banner might have a custom name, both of which a Java client knows and caches
ItemStack itemStack = banner.getItem();
var tag = OpenNbtTagVisitor.convert("", itemStack.getOrCreateTag());
future.complete(tag);
org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents components =
new org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents(new HashMap<>());
components.put(DataComponentType.DAMAGE, itemStack.getDamageValue());
Component customName = itemStack.getComponents().get(DataComponents.CUSTOM_NAME);
if (customName != null) {
components.put(DataComponentType.CUSTOM_NAME, toKyoriComponent(customName));
}
BannerPatternLayers pattern = itemStack.get(DataComponents.BANNER_PATTERNS);
if (pattern != null) {
components.put(DataComponentType.BANNER_PATTERNS, toPatternList(pattern));
}
future.complete(components);
return;
}
future.complete(null);
@ -257,95 +261,52 @@ public class GeyserModWorldManager extends GeyserWorldManager {
return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid());
}
// Future considerations: option to clone; would affect arrays
private static class OpenNbtTagVisitor implements TagVisitor {
private String currentKey;
private final com.github.steveice10.opennbt.tag.builtin.CompoundTag root;
private com.github.steveice10.opennbt.tag.builtin.Tag currentTag;
OpenNbtTagVisitor(String key) {
root = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(key);
}
@Override
public void visitString(StringTag stringTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.StringTag(currentKey, stringTag.getAsString());
}
@Override
public void visitByte(ByteTag byteTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.ByteTag(currentKey, byteTag.getAsByte());
}
@Override
public void visitShort(ShortTag shortTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.ShortTag(currentKey, shortTag.getAsShort());
}
@Override
public void visitInt(IntTag intTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.IntTag(currentKey, intTag.getAsInt());
}
@Override
public void visitLong(LongTag longTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.LongTag(currentKey, longTag.getAsLong());
}
@Override
public void visitFloat(FloatTag floatTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.FloatTag(currentKey, floatTag.getAsFloat());
}
@Override
public void visitDouble(DoubleTag doubleTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.DoubleTag(currentKey, doubleTag.getAsDouble());
}
@Override
public void visitByteArray(ByteArrayTag byteArrayTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.ByteArrayTag(currentKey, byteArrayTag.getAsByteArray());
}
@Override
public void visitIntArray(IntArrayTag intArrayTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.IntArrayTag(currentKey, intArrayTag.getAsIntArray());
}
@Override
public void visitLongArray(LongArrayTag longArrayTag) {
currentTag = new com.github.steveice10.opennbt.tag.builtin.LongArrayTag(currentKey, longArrayTag.getAsLongArray());
}
@Override
public void visitList(ListTag listTag) {
var newList = new com.github.steveice10.opennbt.tag.builtin.ListTag(currentKey);
for (Tag tag : listTag) {
currentKey = "";
tag.accept(this);
newList.add(currentTag);
}
currentTag = newList;
}
@Override
public void visitCompound(@NonNull CompoundTag compoundTag) {
currentTag = convert(currentKey, compoundTag);
}
private static com.github.steveice10.opennbt.tag.builtin.CompoundTag convert(String name, CompoundTag compoundTag) {
OpenNbtTagVisitor visitor = new OpenNbtTagVisitor(name);
for (String key : compoundTag.getAllKeys()) {
visitor.currentKey = key;
Tag tag = Objects.requireNonNull(compoundTag.get(key));
tag.accept(visitor);
visitor.root.put(visitor.currentTag);
}
return visitor.root;
}
@Override
public void visitEnd(@NonNull EndTag endTag) {
private static int getPageCount(ItemStack itemStack) {
WrittenBookContent writtenBookContent = itemStack.get(DataComponents.WRITTEN_BOOK_CONTENT);
if (writtenBookContent != null) {
return writtenBookContent.pages().size();
} else {
WritableBookContent writableBookContent = itemStack.get(DataComponents.WRITABLE_BOOK_CONTENT);
return writableBookContent != null ? writableBookContent.pages().size() : 0;
}
}
private static List<String> getPages(ItemStack itemStack) {
WrittenBookContent writtenBookContent = itemStack.get(DataComponents.WRITTEN_BOOK_CONTENT);
if (writtenBookContent != null) {
return writtenBookContent.pages().stream()
.map(Filterable::raw)
.map(GeyserModWorldManager::fromComponent)
.toList();
} else {
WritableBookContent writableBookContent = itemStack.get(DataComponents.WRITABLE_BOOK_CONTENT);
if (writableBookContent == null) {
return List.of();
}
return writableBookContent.pages().stream()
.map(Filterable::raw)
.toList();
}
}
private static String fromComponent(Component component) {
String json = Component.Serializer.toJson(component, RegistryAccess.EMPTY);
return LEGACY_SERIALIZER.serialize(GSON_SERIALIZER.deserializeOr(json, net.kyori.adventure.text.Component.empty()));
}
private static net.kyori.adventure.text.Component toKyoriComponent(Component component) {
String json = Component.Serializer.toJson(component, RegistryAccess.EMPTY);
return GSON_SERIALIZER.deserializeOr(json, net.kyori.adventure.text.Component.empty());
}
private static List<BannerPatternLayer> toPatternList(BannerPatternLayers patternLayers) {
return patternLayers.layers().stream()
.map(layer -> {
BannerPatternLayer.BannerPattern pattern = new BannerPatternLayer.BannerPattern(
layer.pattern().value().assetId().toString(), layer.pattern().value().translationKey()
);
return new BannerPatternLayer(Holder.ofCustom(pattern), layer.color().getId());
})
.toList();
}
}

View File

@ -7,13 +7,16 @@ dependencies {
implementation(variantOf(libs.adapters.spigot) {
classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
})
implementation(variantOf(libs.adapters.paper) {
classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
})
implementation(libs.cloud.paper)
implementation(libs.commodore)
implementation(libs.adventure.text.serializer.bungeecord)
compileOnly(libs.folia.api)
compileOnly(libs.paper.mojangapi)
compileOnlyApi(libs.viaversion)
}
@ -24,16 +27,23 @@ platformRelocate("com.fasterxml.jackson")
platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger")
platformRelocate("org.objectweb.asm")
platformRelocate("me.lucko.commodore")
platformRelocate("org.incendo")
platformRelocate("org.yaml") // Broken as of 1.20
// These dependencies are already present on the platform
provided(libs.viaversion)
application {
mainClass.set("org.geysermc.geyser.platform.spigot.GeyserSpigotMain")
tasks.withType<Jar> {
manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.spigot.GeyserSpigotMain"
}
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
// Prevents Paper 1.20.5+ from remapping Geyser
manifest {
attributes["paperweight-mappings-namespace"] = "mojang"
}
archiveBaseName.set("Geyser-Spigot")
dependencies {

View File

@ -25,7 +25,7 @@
package org.geysermc.geyser.platform.spigot;
import com.github.steveice10.mc.protocol.MinecraftProtocol;
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
@ -119,8 +119,11 @@ public class GeyserSpigotInjector extends GeyserInjector {
protected void initChannel(@NonNull Channel ch) throws Exception {
initChannel.invoke(childHandler, ch);
int index = ch.pipeline().names().indexOf("encoder");
String baseName = index != -1 ? "encoder" : "outbound_config";
if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserSpigotCompressionDisabler.ENABLED) {
ch.pipeline().addAfter("encoder", "geyser-compression-disabler", new GeyserSpigotCompressionDisabler());
ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserSpigotCompressionDisabler());
}
}
})
@ -177,6 +180,7 @@ public class GeyserSpigotInjector extends GeyserInjector {
bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress,
InetAddress.getLoopbackAddress().getHostAddress(), protocol, protocol.createHelper());
session.connect();
session.disconnect("");
}
@Override

View File

@ -30,36 +30,34 @@ import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import io.netty.buffer.ByteBuf;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.block.data.BlockData;
import org.bukkit.command.CommandMap;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.CommandSender;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerLoadEvent;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.adapters.paper.PaperAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport;
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor;
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager;
import org.geysermc.geyser.platform.spigot.command.SpigotCommandRegistry;
import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource;
import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener;
import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativeWorldManager;
@ -67,22 +65,22 @@ import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorld
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.incendo.cloud.bukkit.BukkitCommandManager;
import org.incendo.cloud.bukkit.CloudBukkitCapabilities;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.paper.PaperCommandManager;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.SocketAddress;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.logging.Level;
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
private GeyserSpigotCommandManager geyserCommandManager;
private CommandRegistry commandRegistry;
private GeyserSpigotConfiguration geyserConfig;
private GeyserSpigotInjector geyserInjector;
private GeyserSpigotLogger geyserLogger;
@ -165,31 +163,39 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
@Override
public void onEnable() {
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
this.geyserCommandManager.init();
// Create command manager early so we can add Geyser extension commands
var sourceConverter = new CommandSourceConverter<>(
CommandSender.class,
Bukkit::getPlayer,
Bukkit::getConsoleSender,
SpigotCommandSource::new
);
PaperCommandManager<GeyserCommandSource> cloud;
try {
// PaperCommandManager is a cloud impl for all Bukkit based platforms
// https://github.com/Incendo/cloud-minecraft/blob/master/cloud-paper/src/main/java/org/incendo/cloud/paper/PaperCommandManager.java#L47-L49
cloud = new PaperCommandManager<>(
this,
ExecutionCoordinator.simpleCoordinator(),
sourceConverter
);
} catch (Exception e) {
throw new RuntimeException(e);
}
// Because Bukkit locks its command map upon startup, we need to
// add our plugin commands in onEnable, but populating the executor
// can happen at any time (later in #onGeyserEnable())
CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
// Thanks again, Bukkit
if (cloud.hasCapability(CloudBukkitCapabilities.BRIGADIER)) {
try {
Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructor.setAccessible(true);
PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
commandMap.register(extension.description().id(), "geyserext", pluginCommand);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
// Should always be available on 1.13 and up
cloud.registerBrigadier();
} catch (BukkitCommandManager.BrigadierInitializationException e) {
geyserLogger.debug("Failed to initialize Brigadier support: " + e.getMessage());
}
}
this.commandRegistry = new SpigotCommandRegistry(geyser, cloud);
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
Bukkit.getPluginManager().registerEvents(new Listener() {
@EventHandler
public void onServerLoaded(ServerLoadEvent event) {
if (event.getType() == ServerLoadEvent.LoadType.RELOAD) {
@ -213,6 +219,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
GeyserImpl.start();
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else {
@ -227,7 +234,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
}
geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
// Don't need to re-create the world manager/re-register commands/reinject when reloading
// Don't need to re-create the world manager/reinject when reloading
if (GeyserImpl.getInstance().isReloading()) {
return;
}
@ -244,16 +251,27 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
if (Boolean.parseBoolean(System.getProperty("Geyser.UseDirectAdapters", "true"))) {
try {
String name = Bukkit.getServer().getClass().getPackage().getName();
String nmsVersion = name.substring(name.lastIndexOf('.') + 1);
SpigotAdapters.registerWorldAdapter(nmsVersion);
boolean isPaper = false;
try {
String name = Bukkit.getServer().getClass().getPackage().getName();
String nmsVersion = name.substring(name.lastIndexOf('.') + 1);
SpigotAdapters.registerWorldAdapter(nmsVersion);
geyserLogger.debug("Using spigot NMS adapter for nms version: " + nmsVersion);
} catch (Exception e) { // Likely running on Paper 1.20.5+
//noinspection deprecation
int protocolVersion = Bukkit.getUnsafe().getProtocolVersion();
PaperAdapters.registerClosestWorldAdapter(protocolVersion);
isPaper = true;
geyserLogger.debug("Using paper world adapter for protocol version: " + protocolVersion);
}
if (isViaVersion && isViaVersionNeeded()) {
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this);
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, isPaper);
} else {
// No ViaVersion
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this);
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this, isPaper);
}
geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion);
geyserLogger.debug("Using world manager of type: " + this.geyserWorldManager.getClass().getSimpleName());
} catch (Exception e) {
if (geyserConfig.isDebugMode()) {
geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)");
@ -270,79 +288,37 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
geyserLogger.debug("Using default world manager.");
}
PluginCommand geyserCommand = this.getCommand("geyser");
Objects.requireNonNull(geyserCommand, "base command cannot be null");
geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands()));
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
Map<String, Command> commands = entry.getValue();
if (commands.isEmpty()) {
continue;
}
PluginCommand command = this.getCommand(entry.getKey().description().id());
if (command == null) {
continue;
}
command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands));
}
// Register permissions so they appear in, for example, LuckPerms' UI
// Re-registering permissions throws an error
for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) {
Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) {
// Don't register aliases
continue;
// Re-registering permissions without removing it throws an error
PluginManager pluginManager = Bukkit.getPluginManager();
geyser.eventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> {
if (permission.isBlank()) {
return;
}
PermissionDefault permissionDefault = switch (def) {
case TRUE -> PermissionDefault.TRUE;
case FALSE -> PermissionDefault.FALSE;
case NOT_SET -> PermissionDefault.OP;
};
Permission existingPermission = pluginManager.getPermission(permission);
if (existingPermission != null) {
geyserLogger.debug("permission " + permission + " with a default of " +
existingPermission.getDefault() + " is being overriden by " + permissionDefault);
pluginManager.removePermission(permission);
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
// Register permissions for extension commands
for (Map.Entry<Extension, Map<String, Command>> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
for (Map.Entry<String, Command> entry : commandEntry.getValue().entrySet()) {
Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) {
// Don't register aliases
continue;
}
if (command.permission().isBlank()) {
continue;
}
// Avoid registering the same permission twice, e.g. for the extension help commands
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
}
Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
"Whether update notifications can be seen", PermissionDefault.OP));
pluginManager.addPermission(new Permission(permission, permissionDefault));
});
// Events cannot be unregistered - re-registering results in duplicate firings
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
pluginManager.registerEvents(blockPlaceListener, this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
pluginManager.registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
boolean brigadierSupported = CommodoreProvider.isSupported();
geyserLogger.debug("Brigadier supported? " + brigadierSupported);
if (brigadierSupported) {
GeyserBrigadierSupport.loadBrigadier(this, geyserCommand);
}
pluginManager.registerEvents(new GeyserSpigotUpdateListener(), this);
}
@Override
@ -378,8 +354,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return this.geyserCommandManager;
public CommandRegistry getCommandRegistry() {
return this.commandRegistry;
}
@Override

View File

@ -25,7 +25,7 @@
package org.geysermc.geyser.platform.spigot;
import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer;
import org.geysermc.mcprotocollib.protocol.data.DefaultComponentSerializer;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.Nullable;

View File

@ -1,61 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.spigot.command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin;
/**
* Needs to be a separate class so pre-1.13 loads correctly.
*/
public final class GeyserBrigadierSupport {
public static void loadBrigadier(GeyserSpigotPlugin plugin, PluginCommand pluginCommand) {
// Enable command completions if supported
// This is beneficial because this is sent over the network and Bedrock can see it
Commodore commodore = CommodoreProvider.getCommodore(plugin);
LiteralArgumentBuilder<?> builder = LiteralArgumentBuilder.literal("geyser");
for (String command : plugin.getGeyserCommandManager().getCommands().keySet()) {
builder.then(LiteralArgumentBuilder.literal(command));
}
commodore.register(pluginCommand, builder);
try {
Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent");
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), plugin);
plugin.getGeyserLogger().debug("Successfully registered AsyncPlayerSendCommandsEvent listener.");
} catch (ClassNotFoundException e) {
plugin.getGeyserLogger().debug("Not registering AsyncPlayerSendCommandsEvent listener.");
}
}
private GeyserBrigadierSupport() {
}
}

View File

@ -1,87 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.spigot.command;
import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent;
import com.mojang.brigadier.tree.CommandNode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.Map;
public final class GeyserPaperCommandListener implements Listener {
@SuppressWarnings("UnstableApiUsage")
@EventHandler
public void onCommandSend(AsyncPlayerSendCommandsEvent<?> event) {
// Documentation says to check (event.isAsynchronous() || !event.hasFiredAsync()), but as of Paper 1.18.2
// event.hasFiredAsync is never true
if (event.isAsynchronous()) {
CommandNode<?> geyserBrigadier = event.getCommandNode().getChild("geyser");
if (geyserBrigadier != null) {
Player player = event.getPlayer();
boolean isJavaPlayer = isProbablyJavaPlayer(player);
Map<String, Command> commands = GeyserImpl.getInstance().commandManager().getCommands();
Iterator<? extends CommandNode<?>> it = geyserBrigadier.getChildren().iterator();
while (it.hasNext()) {
CommandNode<?> subnode = it.next();
Command command = commands.get(subnode.getName());
if (command != null) {
if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.permission())) {
// Remove this from the node as we don't have permission to use it
it.remove();
}
}
}
}
}
}
/**
* This early on, there is a rare chance that Geyser has yet to process the connection. We'll try to minimize that
* chance, though.
*/
private boolean isProbablyJavaPlayer(Player player) {
if (GeyserImpl.getInstance().connectionByUuid(player.getUniqueId()) != null) {
// For sure this is a Bedrock player
return false;
}
if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) {
InetSocketAddress address = player.getAddress();
if (address != null) {
return address.getPort() != 0;
}
}
return true;
}
}

View File

@ -1,88 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.spigot.command;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class GeyserSpigotCommandExecutor extends GeyserCommandExecutor implements TabExecutor {
public GeyserSpigotCommandExecutor(GeyserImpl geyser, Map<String, org.geysermc.geyser.api.command.Command> commands) {
super(geyser, commands);
}
@Override
public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, String[] args) {
SpigotCommandSource commandSender = new SpigotCommandSource(sender);
GeyserSession session = getGeyserSession(commandSender);
if (args.length > 0) {
GeyserCommand geyserCommand = getCommand(args[0]);
if (geyserCommand != null) {
if (!sender.hasPermission(geyserCommand.permission())) {
String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale());
commandSender.sendMessage(ChatColor.RED + message);
return true;
}
if (geyserCommand.isBedrockOnly() && session == null) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale()));
return true;
}
geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
return true;
} else {
String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale());
commandSender.sendMessage(ChatColor.RED + message);
}
} else {
getCommand("help").execute(session, commandSender, new String[0]);
return true;
}
return true;
}
@Override
public List<String> onTabComplete(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, String[] args) {
if (args.length == 1) {
return tabComplete(new SpigotCommandSource(sender));
}
return Collections.emptyList();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -29,16 +29,21 @@ import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.incendo.cloud.CommandManager;
import java.lang.reflect.Field;
public class GeyserSpigotCommandManager extends GeyserCommandManager {
public class SpigotCommandRegistry extends CommandRegistry {
private static final CommandMap COMMAND_MAP;
private final CommandMap commandMap;
public SpigotCommandRegistry(GeyserImpl geyser, CommandManager<GeyserCommandSource> cloud) {
super(geyser, cloud);
static {
CommandMap commandMap = null;
try {
// Paper-only
@ -49,24 +54,28 @@ public class GeyserSpigotCommandManager extends GeyserCommandManager {
Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
cmdMapField.setAccessible(true);
commandMap = (CommandMap) cmdMapField.get(Bukkit.getServer());
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
} catch (Exception ex) {
geyser.getLogger().error("Failed to get Spigot's CommandMap", ex);
}
}
COMMAND_MAP = commandMap;
}
public GeyserSpigotCommandManager(GeyserImpl geyser) {
super(geyser);
this.commandMap = commandMap;
}
@NonNull
@Override
public String description(String command) {
Command cmd = COMMAND_MAP.getCommand(command.replace("/", ""));
return cmd != null ? cmd.getDescription() : "";
}
public String description(@NonNull String command, @NonNull String locale) {
// check if the command is /geyser or an extension command so that we can localize the description
String description = super.description(command, locale);
if (!description.isBlank()) {
return description;
}
public static CommandMap getCommandMap() {
return COMMAND_MAP;
if (commandMap != null) {
Command cmd = commandMap.getCommand(command);
if (cmd != null) {
return cmd.getDescription();
}
}
return "";
}
}

View File

@ -27,17 +27,22 @@ package org.geysermc.geyser.platform.spigot.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.spigot.PaperAdventure;
import org.geysermc.geyser.text.GeyserLocale;
public class SpigotCommandSource implements GeyserCommandSource {
private final org.bukkit.command.CommandSender handle;
import java.util.Optional;
import java.util.UUID;
public SpigotCommandSource(org.bukkit.command.CommandSender handle) {
public class SpigotCommandSource implements GeyserCommandSource {
private final CommandSender handle;
public SpigotCommandSource(CommandSender handle) {
this.handle = handle;
// Ensure even Java players' languages are loaded
GeyserLocale.loadGeyserLocale(locale());
@ -65,11 +70,24 @@ public class SpigotCommandSource implements GeyserCommandSource {
handle.spigot().sendMessage(BungeeComponentSerializer.get().serialize(message));
}
@Override
public Object handle() {
return handle;
}
@Override
public boolean isConsole() {
return handle instanceof ConsoleCommandSender;
}
@Override
public @Nullable UUID playerUuid() {
if (handle instanceof Player player) {
return player.getUniqueId();
}
return null;
}
@SuppressWarnings("deprecation")
@Override
public String locale() {

View File

@ -25,7 +25,7 @@
package org.geysermc.geyser.platform.spigot.world;
import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
import org.cloudburstmc.math.vector.Vector3i;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;

View File

@ -46,8 +46,8 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl
private final Int2IntMap oldToNewBlockId;
public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin) {
super(plugin);
public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean isPaper) {
super(plugin, isPaper);
IntList allBlockStates = adapter.getAllBlockStates();
oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size());
ProtocolVersion serverVersion = plugin.getServerProtocolVersion();

View File

@ -26,20 +26,26 @@
package org.geysermc.geyser.platform.spigot.world.manager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.adapters.WorldAdapter;
import org.geysermc.geyser.adapters.paper.PaperAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession;
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
protected final SpigotWorldAdapter adapter;
protected final WorldAdapter<World> adapter;
public GeyserSpigotNativeWorldManager(Plugin plugin) {
public GeyserSpigotNativeWorldManager(Plugin plugin, boolean isPaper) {
super(plugin);
adapter = SpigotAdapters.getWorldAdapter();
if (isPaper) {
adapter = PaperAdapters.getWorldAdapter();
} else {
adapter = SpigotAdapters.getWorldAdapter();
}
}
@Override

View File

@ -25,9 +25,9 @@
package org.geysermc.geyser.platform.spigot.world.manager;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityInfo;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World;
@ -39,7 +39,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtMap;
import org.geysermc.erosion.bukkit.BukkitLecterns;
import org.geysermc.erosion.bukkit.BukkitUtils;
import org.geysermc.erosion.bukkit.PickBlockUtils;
import org.geysermc.erosion.bukkit.SchedulerUtils;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.GameRule;
@ -195,18 +194,10 @@ public class GeyserSpigotWorldManager extends WorldManager {
return GameMode.byId(Bukkit.getDefaultGameMode().ordinal());
}
@NonNull
@Override
public boolean hasPermission(GeyserSession session, String permission) {
Player player = Bukkit.getPlayer(session.javaUuid());
if (player != null) {
return player.hasPermission(permission);
}
return false;
}
@Override
public @NonNull CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
CompletableFuture<@Nullable CompoundTag> future = new CompletableFuture<>();
public @NonNull CompletableFuture<@Nullable DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
CompletableFuture<@Nullable DataComponents> future = new CompletableFuture<>();
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
future.complete(null);
@ -215,7 +206,7 @@ public class GeyserSpigotWorldManager extends WorldManager {
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
// Paper 1.19.3 complains about async access otherwise.
// java.lang.IllegalStateException: Tile is null, asynchronous access?
SchedulerUtils.runTask(this.plugin, () -> future.complete(PickBlockUtils.pickBlock(block)), block);
SchedulerUtils.runTask(this.plugin, () -> future.complete(/*PickBlockUtils.pickBlock(block)*/ null), block); // TODO fix erosion once clear how to handle this
return future;
}

View File

@ -6,11 +6,3 @@ version: ${version}
softdepend: ["ViaVersion", "floodgate"]
api-version: 1.13
folia-supported: true
commands:
geyser:
description: The main command for Geyser.
usage: /geyser <subcommand>
permission: geyser.command
permissions:
geyser.command:
default: true

View File

@ -1,5 +1,9 @@
import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer
plugins {
application
}
val terminalConsoleVersion = "1.2.0"
val jlineVersion = "3.21.0"

View File

@ -42,7 +42,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
@ -69,7 +70,7 @@ import java.util.stream.Collectors;
public class GeyserStandaloneBootstrap implements GeyserBootstrap {
private GeyserCommandManager geyserCommandManager;
private CommandRegistry commandRegistry;
private GeyserStandaloneConfiguration geyserConfig;
private GeyserStandaloneLogger geyserLogger;
private IGeyserPingPassthrough geyserPingPassthrough;
@ -224,13 +225,16 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
geyser = GeyserImpl.load(PlatformType.STANDALONE, this);
geyserCommandManager = new GeyserCommandManager(geyser);
geyserCommandManager.init();
// fire GeyserDefineCommandsEvent after PreInitEvent, before PostInitEvent, for consistency with other bootstraps
StandaloneCloudCommandManager cloud = new StandaloneCloudCommandManager(geyser);
commandRegistry = new CommandRegistry(geyser, cloud);
GeyserImpl.start();
cloud.gatherPermissions(); // event must be fired after CommandRegistry has subscribed its listener
if (gui != null) {
gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager);
gui.enableCommands(geyser.getScheduledThread(), commandRegistry);
}
geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
@ -258,7 +262,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
@Override
public void onGeyserDisable() {
// We can re-register commands on standalone, so why not
GeyserImpl.getInstance().commandManager().getCommands().clear();
commandRegistry.commands().clear();
geyser.disable();
}
@ -279,8 +283,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return geyserCommandManager;
public CommandRegistry getCommandRegistry() {
return commandRegistry;
}
@Override

View File

@ -44,7 +44,9 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
@Override
protected void runCommand(String line) {
GeyserImpl.getInstance().commandManager().runCommand(this, line);
// don't block the terminal!
GeyserImpl geyser = GeyserImpl.getInstance();
geyser.getScheduledThread().execute(() -> geyser.commandRegistry().runCommand(this, line));
}
@Override

View File

@ -28,7 +28,7 @@ package org.geysermc.geyser.platform.standalone.gui;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
@ -271,15 +271,14 @@ public class GeyserStandaloneGUI {
}
/**
* Enable the command input box.
* Enables the command input box.
*
* @param executor the executor for running commands off the GUI thread
* @param commandManager the command manager to delegate commands to
* @param executor the executor that commands will be run on
* @param registry the command registry containing all current commands
*/
public void enableCommands(ScheduledExecutorService executor, GeyserCommandManager commandManager) {
public void enableCommands(ScheduledExecutorService executor, CommandRegistry registry) {
// we don't want to block the GUI thread with the command execution
// todo: once cloud is used, an AsynchronousCommandExecutionCoordinator can be used to avoid this scheduler
commandListener.handler = cmd -> executor.schedule(() -> commandManager.runCommand(logger, cmd), 0, TimeUnit.SECONDS);
commandListener.dispatcher = cmd -> executor.execute(() -> registry.runCommand(logger, cmd));
commandInput.setEnabled(true);
commandInput.requestFocusInWindow();
}
@ -344,13 +343,14 @@ public class GeyserStandaloneGUI {
private class CommandListener implements ActionListener {
private Consumer<String> handler;
private Consumer<String> dispatcher;
@Override
public void actionPerformed(ActionEvent e) {
String command = commandInput.getText();
// the headless variant of Standalone strips trailing whitespace for us - we need to manually
String command = commandInput.getText().stripTrailing();
appendConsole(command + "\n"); // show what was run in the console
handler.accept(command); // run the command
dispatcher.accept(command); // run the command
commandInput.setText(""); // clear the input
}
}

View File

@ -0,0 +1,9 @@
# Add any permissions here that all players should have.
# Permissions for builtin Geyser commands do not have to be listed here.
# If an extension/plugin registers their permissions with default values, entries here are typically unnecessary.
# If extensions don't register their permissions, permissions that everyone should have must be added here manually.
default-permissions:
- geyser.command.help # this is unnecessary

View File

@ -3,12 +3,14 @@ dependencies {
api(projects.core)
compileOnlyApi(libs.velocity.api)
api(libs.cloud.velocity)
}
platformRelocate("com.fasterxml.jackson")
platformRelocate("it.unimi.dsi.fastutil")
platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl")
platformRelocate("org.yaml")
platformRelocate("org.incendo")
exclude("com.google.*:*")
@ -38,8 +40,8 @@ exclude("net.kyori:adventure-nbt:*")
// These dependencies are already present on the platform
provided(libs.velocity.api)
application {
mainClass.set("org.geysermc.geyser.platform.velocity.GeyserVelocityMain")
tasks.withType<Jar> {
manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.velocity.GeyserVelocityMain"
}
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {

View File

@ -26,6 +26,7 @@
package org.geysermc.geyser.platform.velocity;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.network.ProtocolState;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.ProxyServer;
@ -88,6 +89,11 @@ public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough {
public ProtocolVersion getProtocolVersion() {
return ProtocolVersion.MAXIMUM_VERSION;
}
@Override
public ProtocolState getProtocolState() {
return ProtocolState.STATUS;
}
}
}

View File

@ -26,7 +26,7 @@
package org.geysermc.geyser.platform.velocity;
import com.google.inject.Inject;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ListenerBoundEvent;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
@ -34,24 +34,28 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.network.ListenerType;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecutor;
import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.velocity.VelocityCommandManager;
import org.slf4j.Logger;
import java.io.File;
@ -59,7 +63,6 @@ import java.io.IOException;
import java.net.SocketAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.UUID;
@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
@ -71,9 +74,9 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
private ProxyServer proxyServer;
@Inject
private CommandManager commandManager;
private PluginContainer container;
private GeyserCommandManager geyserCommandManager;
private CommandRegistry commandRegistry;
private GeyserVelocityConfiguration geyserConfig;
private GeyserVelocityInjector geyserInjector;
private GeyserVelocityLogger geyserLogger;
@ -117,8 +120,19 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
} else {
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
var sourceConverter = new CommandSourceConverter<>(
CommandSource.class,
id -> proxyServer.getPlayer(id).orElse(null),
proxyServer::getConsoleCommandSource,
VelocityCommandSource::new
);
CommandManager<GeyserCommandSource> cloud = new VelocityCommandManager<>(
container,
proxyServer,
ExecutionCoordinator.simpleCoordinator(),
sourceConverter
);
this.commandRegistry = new CommandRegistry(geyser, cloud);
}
GeyserImpl.start();
@ -129,22 +143,10 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
}
// No need to re-register commands when reloading
if (GeyserImpl.getInstance().isReloading()) {
return;
// No need to re-register events
if (!GeyserImpl.getInstance().isReloading()) {
proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener());
}
this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands()));
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
Map<String, Command> commands = entry.getValue();
if (commands.isEmpty()) {
continue;
}
this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands));
}
proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener());
}
@Override
@ -175,8 +177,8 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return this.geyserCommandManager;
public CommandRegistry getCommandRegistry() {
return this.commandRegistry;
}
@Override

View File

@ -1,83 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.velocity.command;
import com.velocitypowered.api.command.SimpleCommand;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class GeyserVelocityCommandExecutor extends GeyserCommandExecutor implements SimpleCommand {
public GeyserVelocityCommandExecutor(GeyserImpl geyser, Map<String, Command> commands) {
super(geyser, commands);
}
@Override
public void execute(Invocation invocation) {
GeyserCommandSource sender = new VelocityCommandSource(invocation.source());
GeyserSession session = getGeyserSession(sender);
if (invocation.arguments().length > 0) {
GeyserCommand command = getCommand(invocation.arguments()[0]);
if (command != null) {
if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).permission())) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return;
}
if (command.isBedrockOnly() && session == null) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
return;
}
command.execute(session, sender, invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]);
} else {
String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", sender.locale());
sender.sendMessage(ChatColor.RED + message);
}
} else {
getCommand("help").execute(session, sender, new String[0]);
}
}
@Override
public List<String> suggest(Invocation invocation) {
// Velocity seems to do the splitting a bit differently. This results in the same behaviour in bungeecord/spigot.
if (invocation.arguments().length == 0 || invocation.arguments().length == 1) {
return tabComplete(new VelocityCommandSource(invocation.source()));
}
return Collections.emptyList();
}
}

View File

@ -31,10 +31,12 @@ import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Locale;
import java.util.UUID;
public class VelocityCommandSource implements GeyserCommandSource {
@ -72,6 +74,14 @@ public class VelocityCommandSource implements GeyserCommandSource {
return handle instanceof ConsoleCommandSource;
}
@Override
public @Nullable UUID playerUuid() {
if (handle instanceof Player player) {
return player.getUniqueId();
}
return null;
}
@Override
public String locale() {
if (handle instanceof Player) {
@ -85,4 +95,9 @@ public class VelocityCommandSource implements GeyserCommandSource {
public boolean hasPermission(String permission) {
return handle.hasPermission(permission);
}
@Override
public Object handle() {
return handle;
}
}

View File

@ -8,12 +8,13 @@ platformRelocate("net.kyori")
platformRelocate("org.yaml")
platformRelocate("it.unimi.dsi.fastutil")
platformRelocate("org.cloudburstmc.netty")
platformRelocate("org.incendo")
// These dependencies are already present on the platform
provided(libs.viaproxy)
application {
mainClass.set("org.geysermc.geyser.platform.viaproxy.GeyserViaProxyMain")
tasks.withType<Jar> {
manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.viaproxy.GeyserViaProxyMain"
}
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {

View File

@ -34,12 +34,14 @@ import net.raphimc.viaproxy.plugins.events.ProxyStartEvent;
import net.raphimc.viaproxy.plugins.events.ProxyStopEvent;
import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent;
import org.apache.logging.log4j.LogManager;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
@ -48,7 +50,6 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.util.LoopbackUtil;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
@ -64,7 +65,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
private final GeyserViaProxyLogger logger = new GeyserViaProxyLogger(LogManager.getLogger("Geyser"));
private GeyserViaProxyConfiguration config;
private GeyserImpl geyser;
private GeyserCommandManager commandManager;
private CommandRegistry commandRegistry;
private IGeyserPingPassthrough pingPassthrough;
@Override
@ -85,7 +86,9 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
@EventHandler
private void onConsoleCommand(final ConsoleCommandEvent event) {
final String command = event.getCommand().startsWith("/") ? event.getCommand().substring(1) : event.getCommand();
if (this.getGeyserCommandManager().runCommand(this.getGeyserLogger(), command + " " + String.join(" ", event.getArgs()))) {
CommandRegistry registry = this.getCommandRegistry();
if (registry.cloud().rootCommands().contains(command)) {
registry.runCommand(this.getGeyserLogger(), command + " " + String.join(" ", event.getArgs()));
event.setCancelled(true);
}
}
@ -131,8 +134,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
}
}
this.commandManager = new GeyserCommandManager(this.geyser);
this.commandManager.init();
StandaloneCloudCommandManager cloud = new StandaloneCloudCommandManager(geyser);
this.commandRegistry = new CommandRegistry(geyser, cloud);
GeyserImpl.start();
@ -163,8 +166,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return this.commandManager;
public CommandRegistry getCommandRegistry() {
return this.commandRegistry;
}
@Override
@ -182,7 +185,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
return new GeyserViaProxyDumpInfo();
}
@NotNull
@NonNull
@Override
public String getServerBindAddress() {
if (ViaProxy.getConfig().getBindAddress() instanceof InetSocketAddress socketAddress) {
@ -206,6 +209,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
return false;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
try {
final File configFile = FileUtils.fileOrCopiedFromResource(new File(ROOT_FOLDER, "config.yml"), "config.yml", s -> s.replaceAll("generateduuid", UUID.randomUUID().toString()), this);

View File

@ -0,0 +1,9 @@
# Add any permissions here that all players should have.
# Permissions for builtin Geyser commands do not have to be listed here.
# If an extension/plugin registers their permissions with default values, entries here are typically unnecessary.
# If extensions don't register their permissions, permissions that everyone should have must be added here manually.
default-permissions:
- geyser.command.help # this is unnecessary

View File

@ -23,7 +23,7 @@ indra {
tasks {
processResources {
// Spigot, BungeeCord, Velocity, Fabric, ViaProxy, NeoForge
filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json", "viaproxy.yml", "META-INF/mods.toml")) {
filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json", "viaproxy.yml", "META-INF/neoforge.mods.toml")) {
expand(
"id" to "geyser",
"name" to "Geyser",

View File

@ -36,16 +36,23 @@ provided("io.netty", "netty-transport")
provided("io.netty", "netty-codec")
provided("io.netty", "netty-resolver-dns")
provided("io.netty", "netty-resolver-dns-native-macos")
provided("org.incendo", ".*") // fabric-cloud/neoforge-cloud jij's all cloud depends already
provided("org.ow2.asm", "asm")
architectury {
minecraft = "1.20.4"
minecraft = "1.20.5"
}
loom {
silentMojangMappingsLicense()
}
indra {
javaVersions {
target(21)
}
}
configurations {
create("includeTransitive").isTransitive = true
}
@ -104,7 +111,7 @@ afterEvaluate {
}
dependencies {
minecraft("com.mojang:minecraft:1.20.4")
minecraft("com.mojang:minecraft:1.20.5")
mappings(loom.officialMojangMappings())
}
@ -128,6 +135,6 @@ modrinth {
syncBodyFrom.set(rootProject.file("README.md").readText())
uploadFile.set(tasks.getByPath("remapModrinthJar"))
gameVersions.addAll("1.20.4")
gameVersions.addAll("1.20.5", "1.20.6")
failSilently.set(true)
}

View File

@ -1,4 +1,3 @@
plugins {
application
id("geyser.publish-conventions")
}

View File

@ -53,6 +53,9 @@ dependencies {
// Adventure text serialization
api(libs.bundles.adventure)
// command library
api(libs.cloud.core)
api(libs.erosion.common) {
isTransitive = false
}

View File

@ -38,6 +38,8 @@ public final class Constants {
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
public static final String UPDATE_PERMISSION = "geyser.update";
public static final String SERVER_SETTINGS_PERMISSION = "geyser.settings.server";
public static final String SETTINGS_GAMERULES_PERMISSION = "geyser.settings.gamerules";
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";

View File

@ -27,7 +27,7 @@ package org.geysermc.geyser;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.GeyserWorldManager;
@ -82,11 +82,11 @@ public interface GeyserBootstrap {
GeyserLogger getGeyserLogger();
/**
* Returns the current CommandManager
* Returns the current CommandRegistry
*
* @return The current CommandManager
* @return The current CommandRegistry
*/
GeyserCommandManager getGeyserCommandManager();
CommandRegistry getCommandRegistry();
/**
* Returns the current PingPassthrough manager

View File

@ -29,7 +29,6 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.steveice10.packetlib.tcp.TcpSession;
import io.netty.channel.epoll.Epoll;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.DefaultThreadFactory;
@ -46,7 +45,6 @@ import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.geysermc.api.Geyser;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.erosion.packet.Packets;
import org.geysermc.floodgate.crypto.AesCipher;
import org.geysermc.floodgate.crypto.AesKeyProducer;
import org.geysermc.floodgate.crypto.Base64Topping;
@ -62,7 +60,7 @@ import org.geysermc.geyser.api.network.BedrockListener;
import org.geysermc.geyser.api.network.RemoteServer;
import org.geysermc.geyser.api.util.MinecraftVersion;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.erosion.UnixSocketClientListener;
@ -86,6 +84,7 @@ import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.*;
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
import java.io.File;
import java.io.FileWriter;
@ -384,7 +383,7 @@ public class GeyserImpl implements GeyserApi {
this.newsHandler = new NewsHandler(BRANCH, this.buildNumber());
Packets.initGeyser();
//Packets.initGeyser();
if (Epoll.isAvailable()) {
this.erosionUnixListener = new UnixSocketClientListener();
@ -656,7 +655,6 @@ public class GeyserImpl implements GeyserApi {
if (isEnabled) {
this.disable();
}
this.commandManager().getCommands().clear();
// Disable extensions, fire the shutdown event
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
@ -693,9 +691,12 @@ public class GeyserImpl implements GeyserApi {
return this.extensionManager;
}
/**
* @return the current CommandRegistry in use. The instance may change over the lifecycle of the Geyser runtime.
*/
@NonNull
public GeyserCommandManager commandManager() {
return this.bootstrap.getGeyserCommandManager();
public CommandRegistry commandRegistry() {
return this.bootstrap.getCommandRegistry();
}
@Override

View File

@ -30,6 +30,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.command.GeyserCommandSource;
import java.util.UUID;
public interface GeyserLogger extends GeyserCommandSource {
@ -129,6 +130,11 @@ public interface GeyserLogger extends GeyserCommandSource {
return true;
}
@Override
default @Nullable UUID playerUuid() {
return null;
}
@Override
default boolean hasPermission(String permission) {
return true;

View File

@ -0,0 +1,305 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.command;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.AllArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand;
import org.geysermc.geyser.command.defaults.AdvancementsCommand;
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
import org.geysermc.geyser.command.defaults.DumpCommand;
import org.geysermc.geyser.command.defaults.ExtensionsCommand;
import org.geysermc.geyser.command.defaults.HelpCommand;
import org.geysermc.geyser.command.defaults.ListCommand;
import org.geysermc.geyser.command.defaults.OffhandCommand;
import org.geysermc.geyser.command.defaults.ReloadCommand;
import org.geysermc.geyser.command.defaults.SettingsCommand;
import org.geysermc.geyser.command.defaults.StatisticsCommand;
import org.geysermc.geyser.command.defaults.StopCommand;
import org.geysermc.geyser.command.defaults.VersionCommand;
import org.geysermc.geyser.event.GeyserEventRegistrar;
import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl;
import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.text.MinecraftLocale;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.exception.ArgumentParseException;
import org.incendo.cloud.exception.CommandExecutionException;
import org.incendo.cloud.exception.InvalidCommandSenderException;
import org.incendo.cloud.exception.InvalidSyntaxException;
import org.incendo.cloud.exception.NoPermissionException;
import org.incendo.cloud.exception.NoSuchCommandException;
import org.incendo.cloud.exception.handling.ExceptionContext;
import org.incendo.cloud.exception.handling.ExceptionController;
import org.incendo.cloud.exception.handling.ExceptionHandler;
import org.incendo.cloud.execution.ExecutionCoordinator;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
public class CommandRegistry {
private final GeyserImpl geyser;
private final CommandManager<GeyserCommandSource> cloud;
/**
* Map of Geyser subcommands to their Commands
*/
private final Map<String, Command> commands = new Object2ObjectOpenHashMap<>(13);
/**
* Map of Extensions to maps of their subcommands
*/
private final Map<Extension, Map<String, Command>> extensionCommands = new Object2ObjectOpenHashMap<>(0);
/**
* Map of root commands (that are for extensions) to Extensions
*/
private final Map<String, Extension> extensionRootCommands = new Object2ObjectOpenHashMap<>(0);
/**
* Map containing only permissions that have been registered with a default value
*/
private final Map<String, TriState> permissionDefaults = new Object2ObjectOpenHashMap<>(13);
public CommandRegistry(GeyserImpl geyser, CommandManager<GeyserCommandSource> cloud) {
this.geyser = geyser;
this.cloud = cloud;
// Yeet the default exception handlers that the typical cloud implementations provide so that we can perform localization.
// This is kind of meaningless for our Geyser-Standalone implementation since these handlers are the default exception handlers in that case.
cloud.exceptionController().clearHandlers();
List<GeyserExceptionHandler<?>> exceptionHandlers = List.of(
new GeyserExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.correctSyntax())),
new GeyserExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.commandSender().getClass().getSimpleName(), e.requiredSender())),
new GeyserExceptionHandler<>(NoPermissionException.class, this::handleNoPermission),
new GeyserExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found")),
new GeyserExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())),
new GeyserExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())),
new GeyserExceptionHandler<>(Throwable.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause()))
);
for (GeyserExceptionHandler<?> handler : exceptionHandlers) {
handler.register(cloud);
}
// begin command registration
registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", "geyser.command", this.commands));
registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list"));
registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload"));
registerBuiltInCommand(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand"));
registerBuiltInCommand(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump"));
registerBuiltInCommand(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version"));
registerBuiltInCommand(new SettingsCommand(geyser, "settings", "geyser.commands.settings.desc", "geyser.command.settings"));
registerBuiltInCommand(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
registerBuiltInCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
registerBuiltInCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips"));
registerBuiltInCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest"));
if (this.geyser.getPlatformType() == PlatformType.STANDALONE) {
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
}
if (!this.geyser.extensionManager().extensions().isEmpty()) {
registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions"));
}
GeyserDefineCommandsEvent defineCommandsEvent = new GeyserDefineCommandsEventImpl(this.commands) {
@Override
public void register(@NonNull Command command) {
if (!(command instanceof GeyserExtensionCommand extensionCommand)) {
throw new IllegalArgumentException("Expected GeyserExtensionCommand as part of command registration but got " + command + "! Did you use the Command builder properly?");
}
registerExtensionCommand(extensionCommand.extension(), extensionCommand);
}
};
this.geyser.eventBus().fire(defineCommandsEvent);
for (Map.Entry<Extension, Map<String, Command>> entry : this.extensionCommands.entrySet()) {
Extension extension = entry.getKey();
// Register this extension's root command
extensionRootCommands.put(extension.rootCommand(), extension);
// Register help commands for all extensions with commands
String id = extension.description().id();
registerExtensionCommand(extension, new HelpCommand(
this.geyser,
"help",
"geyser.commands.exthelp.desc",
"geyser.command.exthelp." + id,
extension.rootCommand(),
extension.description().id() + ".command",
entry.getValue()));
}
// wait for the right moment (depends on the platform) to register permissions
geyser.eventBus().subscribe(new GeyserEventRegistrar(this), GeyserRegisterPermissionsEvent.class, this::onRegisterPermissions);
}
@NonNull
public CommandManager<GeyserCommandSource> cloud() {
return cloud;
}
@NonNull
public Map<String, Command> commands() {
return Collections.unmodifiableMap(this.commands);
}
/**
* For internal Geyser commands
*/
public void registerBuiltInCommand(GeyserCommand command) {
register(command, this.commands);
}
public void registerExtensionCommand(@NonNull Extension extension, @NonNull GeyserCommand command) {
register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>()));
}
public boolean hasPermission(GeyserCommandSource source, String permission) {
return cloud.hasPermission(source, permission);
}
private void register(GeyserCommand command, Map<String, Command> commands) {
command.register(cloud);
commands.put(command.name(), command);
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.name()));
for (String alias : command.aliases()) {
commands.put(alias, command);
}
if (!command.permission().isBlank() && command.permissionDefault() != null) {
permissionDefaults.put(command.permission(), command.permissionDefault());
}
if (command instanceof HelpCommand helpCommand) {
permissionDefaults.put(helpCommand.rootCommand(), helpCommand.permissionDefault());
}
}
private void onRegisterPermissions(GeyserRegisterPermissionsEvent event) {
for (Map.Entry<String, TriState> permission : permissionDefaults.entrySet()) {
event.register(permission.getKey(), permission.getValue());
}
// Register other various Geyser permissions
event.register(Constants.UPDATE_PERMISSION, TriState.NOT_SET);
event.register(Constants.SERVER_SETTINGS_PERMISSION, TriState.NOT_SET);
event.register(Constants.SETTINGS_GAMERULES_PERMISSION, TriState.NOT_SET);
}
/**
* Returns the description of the given command
*
* @param command the root command node
* @param locale the ideal locale that the description should be in
* @return a description if found, otherwise an empty string. The locale is not guaranteed.
*/
@NonNull
public String description(@NonNull String command, @NonNull String locale) {
if (command.equals(GeyserCommand.DEFAULT_ROOT_COMMAND)) {
return GeyserLocale.getPlayerLocaleString("geyser.command.root.geyser", locale);
}
Extension extension = extensionRootCommands.get(command);
if (extension != null) {
return GeyserLocale.getPlayerLocaleString("geyser.command.root.extension", locale, extension.name());
}
return "";
}
/**
* Dispatches a command into cloud and handles any thrown exceptions.
* This method may or may not be blocking, depending on the {@link ExecutionCoordinator} in use by cloud.
*/
public void runCommand(@NonNull GeyserCommandSource source, @NonNull String command) {
cloud.commandExecutor().executeCommand(source, command);
}
private void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) {
// we basically recheck bedrock-only and player-only to see if they were the cause of this
if (exception.missingPermission() instanceof GeyserPermission permission) {
GeyserPermission.Result result = permission.check(source);
if (result == GeyserPermission.Result.NOT_BEDROCK) {
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale()));
return;
}
if (result == GeyserPermission.Result.NOT_PLAYER) {
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale()));
return;
}
} else {
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
if (logger.isDebug()) {
logger.debug("Expected a GeyserPermission for %s but instead got %s".formatted(exception.currentChain(), exception.missingPermission()));
}
}
// Result.NO_PERMISSION, or we're unable to recheck
source.sendLocaleString("geyser.command.permission_fail");
}
private void handleUnexpectedThrowable(GeyserCommandSource source, Throwable throwable) {
source.sendMessage(MinecraftLocale.getLocaleString("command.failed", source.locale())); // java edition translation key
GeyserImpl.getInstance().getLogger().error("Exception while executing command handler", throwable);
}
@AllArgsConstructor
private static class GeyserExceptionHandler<E extends Throwable> implements ExceptionHandler<GeyserCommandSource, E> {
final Class<E> type;
final BiConsumer<GeyserCommandSource, E> handler;
void register(CommandManager<GeyserCommandSource> manager) {
manager.exceptionController().registerHandler(type, this);
}
@Override
public void handle(@NonNull ExceptionContext context) throws Throwable {
Throwable unwrapped = ExceptionController.unwrapCompletionException(context.exception());
handler.accept((GeyserCommandSource) context.context().sender(), type.cast(unwrapped));
}
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.command;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.session.GeyserSession;
import org.incendo.cloud.SenderMapper;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Converts {@link GeyserCommandSource}s to the server's command sender type (and back) in a lenient manner.
*
* @param senderType class of the server command sender type
* @param playerLookup function for looking up a player command sender by UUID
* @param consoleProvider supplier of the console command sender
* @param commandSourceLookup supplier of the platform implementation of the {@link GeyserCommandSource}
* @param <S> server command sender type
*/
public record CommandSourceConverter<S>(Class<S> senderType,
Function<UUID, S> playerLookup,
Supplier<S> consoleProvider,
Function<S, GeyserCommandSource> commandSourceLookup
) implements SenderMapper<S, GeyserCommandSource> {
/**
* Creates a new CommandSourceConverter for a server platform
* in which the player type is not a command sender type, and must be mapped.
*
* @param senderType class of the command sender type
* @param playerLookup function for looking up a player by UUID
* @param senderLookup function for converting a player to a command sender
* @param consoleProvider supplier of the console command sender
* @param commandSourceLookup supplier of the platform implementation of {@link GeyserCommandSource}
* @return a new CommandSourceConverter
* @param <P> server player type
* @param <S> server command sender type
*/
public static <P, S> CommandSourceConverter<S> layered(Class<S> senderType,
Function<UUID, P> playerLookup,
Function<P, S> senderLookup,
Supplier<S> consoleProvider,
Function<S, GeyserCommandSource> commandSourceLookup) {
Function<UUID, S> lookup = uuid -> {
P player = playerLookup.apply(uuid);
if (player == null) {
return null;
}
return senderLookup.apply(player);
};
return new CommandSourceConverter<>(senderType, lookup, consoleProvider, commandSourceLookup);
}
@Override
public @NonNull GeyserCommandSource map(@NonNull S base) {
return commandSourceLookup.apply(base);
}
@SuppressWarnings("unchecked")
@Override
public @NonNull S reverse(GeyserCommandSource source) throws IllegalArgumentException {
Object handle = source.handle();
if (senderType.isInstance(handle)) {
return (S) handle; // one of the server platform implementations
}
if (source.isConsole()) {
return consoleProvider.get(); // one of the loggers
}
if (!(source instanceof GeyserSession)) {
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
if (logger.isDebug()) {
logger.debug("Falling back to UUID for command sender lookup for a command source that is not a GeyserSession: " + source);
Thread.dumpStack();
}
}
// Ideally lookup should only be necessary for GeyserSession
UUID uuid = source.playerUuid();
if (uuid != null) {
return playerLookup.apply(uuid);
}
throw new IllegalArgumentException("failed to find sender for name=%s, uuid=%s".formatted(source.name(), source.playerUuid()));
}
}

View File

@ -25,65 +25,182 @@
package org.geysermc.geyser.command;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.text.GeyserLocale;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.description.CommandDescription;
import org.jetbrains.annotations.Contract;
import java.util.Collections;
import java.util.List;
@Accessors(fluent = true)
@Getter
@RequiredArgsConstructor
public abstract class GeyserCommand implements Command {
public abstract class GeyserCommand implements org.geysermc.geyser.api.command.Command {
public static final String DEFAULT_ROOT_COMMAND = "geyser";
/**
* The second literal of the command. Note: the first literal is {@link #rootCommand()}.
*/
@NonNull
private final String name;
protected final String name;
/**
* The description of the command - will attempt to be translated.
*/
protected final String description;
protected final String permission;
private List<String> aliases = Collections.emptyList();
public abstract void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args);
@NonNull
private final String description;
/**
* If false, hides the command from being shown on the Geyser Standalone GUI.
*
* @return true if the command can be run on the server console
*/
@Override
public boolean isExecutableOnConsole() {
return true;
}
/**
* Used in the GUI to know what subcommands can be run
*
* @return a list of all possible subcommands, or empty if none.
* The permission node required to run the command, or blank if not required.
*/
@NonNull
@Override
public List<String> subCommands() {
return Collections.emptyList();
private final String permission;
/**
* The default value of the permission node.
* A null value indicates that the permission node should not be registered whatsoever.
*/
@Nullable
private final TriState permissionDefault;
/**
* True if this command can be executed by players
*/
private final boolean playerOnly;
/**
* True if this command can only be run by bedrock players
*/
private final boolean bedrockOnly;
/**
* The aliases of the command {@link #name}. This should not be modified after construction.
*/
protected List<String> aliases = Collections.emptyList();
public GeyserCommand(@NonNull String name, @NonNull String description,
@NonNull String permission, @Nullable TriState permissionDefault,
boolean playerOnly, boolean bedrockOnly) {
if (name.isBlank()) {
throw new IllegalArgumentException("Command cannot be null or blank!");
}
if (permission.isBlank()) {
permissionDefault = null;
}
this.name = name;
this.description = description;
this.permission = permission;
this.permissionDefault = permissionDefault;
if (bedrockOnly && !playerOnly) {
throw new IllegalArgumentException("Command cannot be bedrockOnly if it is not playerOnly");
}
this.playerOnly = playerOnly;
this.bedrockOnly = bedrockOnly;
}
public void setAliases(List<String> aliases) {
this.aliases = aliases;
public GeyserCommand(@NonNull String name, @NonNull String description, @NonNull String permission, @Nullable TriState permissionDefault) {
this(name, description, permission, permissionDefault, false, false);
}
@NonNull
@Override
public final String name() {
return name;
}
@NonNull
@Override
public final String description() {
return description;
}
@NonNull
@Override
public final String permission() {
return permission;
}
@Nullable
public final TriState permissionDefault() {
return permissionDefault;
}
@Override
public final boolean isPlayerOnly() {
return playerOnly;
}
@Override
public final boolean isBedrockOnly() {
return bedrockOnly;
}
@NonNull
@Override
public final List<String> aliases() {
return Collections.unmodifiableList(aliases);
}
/**
* Used for permission defaults on server implementations.
*
* @return if this command is designated to be used only by server operators.
* @return the first (literal) argument of this command, which comes before {@link #name()}.
*/
@Override
public boolean isSuggestedOpOnly() {
return false;
public String rootCommand() {
return DEFAULT_ROOT_COMMAND;
}
}
/**
* Returns a {@link org.incendo.cloud.permission.Permission} that handles {@link #isBedrockOnly()}, {@link #isPlayerOnly()}, and {@link #permission()}.
*
* @param manager the manager to be used for permission node checking
* @return a permission that will properly restrict usage of this command
*/
public final GeyserPermission commandPermission(CommandManager<GeyserCommandSource> manager) {
return new GeyserPermission(bedrockOnly, playerOnly, permission, manager);
}
/**
* Creates a new command builder with {@link #rootCommand()}, {@link #name()}, and {@link #aliases()} built on it.
* A permission predicate that takes into account {@link #permission()}, {@link #isBedrockOnly()}, and {@link #isPlayerOnly()}
* is applied. The Applicable from {@link #meta()} is also applied to the builder.
*/
@Contract(value = "_ -> new", pure = true)
public final Command.Builder<GeyserCommandSource> baseBuilder(CommandManager<GeyserCommandSource> manager) {
return manager.commandBuilder(rootCommand())
.literal(name, aliases.toArray(new String[0]))
.permission(commandPermission(manager))
.apply(meta());
}
/**
* @return an Applicable that applies this command's description
*/
protected Command.Builder.Applicable<GeyserCommandSource> meta() {
return builder -> builder
.commandDescription(CommandDescription.commandDescription(GeyserLocale.getLocaleStringLog(description))); // used in cloud-bukkit impl
}
/**
* Registers this command to the given command manager.
* This method may be overridden to register more than one command.
* <p>
* The default implementation is that {@link #baseBuilder(CommandManager)} with {@link #execute(CommandContext)}
* applied as the handler is registered to the manager.
*/
public void register(CommandManager<GeyserCommandSource> manager) {
manager.command(baseBuilder(manager).handler(this::execute));
}
/**
* Executes this command
* @param context the context with which this command should be executed
*/
public abstract void execute(CommandContext<GeyserCommandSource> context);
}

View File

@ -1,98 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.command;
import lombok.AllArgsConstructor;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.session.GeyserSession;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Represents helper functions for listening to {@code /geyser} or {@code /geyserext} commands.
*/
@AllArgsConstructor
public class GeyserCommandExecutor {
protected final GeyserImpl geyser;
private final Map<String, Command> commands;
public GeyserCommand getCommand(String label) {
return (GeyserCommand) commands.get(label);
}
@Nullable
public GeyserSession getGeyserSession(GeyserCommandSource sender) {
if (sender.isConsole()) {
return null;
}
for (GeyserSession session : geyser.getSessionManager().getSessions().values()) {
if (sender.name().equals(session.getPlayerEntity().getUsername())) {
return session;
}
}
return null;
}
/**
* Determine which subcommands to suggest in the tab complete for the main /geyser command by a given command sender.
*
* @param sender The command sender to receive the tab complete suggestions.
* If the command sender is a bedrock player, an empty list will be returned as bedrock players do not get command argument suggestions.
* If the command sender is not a bedrock player, bedrock commands will not be shown.
* If the command sender does not have the permission for a given command, the command will not be shown.
* @return A list of command names to include in the tab complete
*/
public List<String> tabComplete(GeyserCommandSource sender) {
if (getGeyserSession(sender) != null) {
// Bedrock doesn't get tab completions or argument suggestions
return Collections.emptyList();
}
List<String> availableCommands = new ArrayList<>();
// Only show commands they have permission to use
for (Map.Entry<String, Command> entry : commands.entrySet()) {
Command geyserCommand = entry.getValue();
if (sender.hasPermission(geyserCommand.permission())) {
if (geyserCommand.isBedrockOnly()) {
// Don't show commands the JE player can't run
continue;
}
availableCommands.add(entry.getKey());
}
}
return availableCommands;
}
}

View File

@ -1,330 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.command;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.command.CommandExecutor;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand;
import org.geysermc.geyser.command.defaults.AdvancementsCommand;
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
import org.geysermc.geyser.command.defaults.DumpCommand;
import org.geysermc.geyser.command.defaults.ExtensionsCommand;
import org.geysermc.geyser.command.defaults.HelpCommand;
import org.geysermc.geyser.command.defaults.ListCommand;
import org.geysermc.geyser.command.defaults.OffhandCommand;
import org.geysermc.geyser.command.defaults.ReloadCommand;
import org.geysermc.geyser.command.defaults.SettingsCommand;
import org.geysermc.geyser.command.defaults.StatisticsCommand;
import org.geysermc.geyser.command.defaults.StopCommand;
import org.geysermc.geyser.command.defaults.VersionCommand;
import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl;
import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@RequiredArgsConstructor
public class GeyserCommandManager {
@Getter
private final Map<String, Command> commands = new Object2ObjectOpenHashMap<>(12);
private final Map<Extension, Map<String, Command>> extensionCommands = new Object2ObjectOpenHashMap<>(0);
private final GeyserImpl geyser;
public void init() {
registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", this.commands));
registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list"));
registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload"));
registerBuiltInCommand(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand"));
registerBuiltInCommand(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump"));
registerBuiltInCommand(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version"));
registerBuiltInCommand(new SettingsCommand(geyser, "settings", "geyser.commands.settings.desc", "geyser.command.settings"));
registerBuiltInCommand(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
registerBuiltInCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
registerBuiltInCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips"));
registerBuiltInCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest"));
if (this.geyser.getPlatformType() == PlatformType.STANDALONE) {
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
}
if (!this.geyser.extensionManager().extensions().isEmpty()) {
registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions"));
}
GeyserDefineCommandsEvent defineCommandsEvent = new GeyserDefineCommandsEventImpl(this.commands) {
@Override
public void register(@NonNull Command command) {
if (!(command instanceof GeyserExtensionCommand extensionCommand)) {
throw new IllegalArgumentException("Expected GeyserExtensionCommand as part of command registration but got " + command + "! Did you use the Command builder properly?");
}
registerExtensionCommand(extensionCommand.extension(), extensionCommand);
}
};
this.geyser.eventBus().fire(defineCommandsEvent);
// Register help commands for all extensions with commands
for (Map.Entry<Extension, Map<String, Command>> entry : this.extensionCommands.entrySet()) {
String id = entry.getKey().description().id();
registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp." + id, id, entry.getValue()));
}
}
/**
* For internal Geyser commands
*/
public void registerBuiltInCommand(GeyserCommand command) {
register(command, this.commands);
}
public void registerExtensionCommand(@NonNull Extension extension, @NonNull Command command) {
register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>()));
}
private void register(Command command, Map<String, Command> commands) {
commands.put(command.name(), command);
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.name()));
if (command.aliases().isEmpty()) {
return;
}
for (String alias : command.aliases()) {
commands.put(alias, command);
}
}
@NonNull
public Map<String, Command> commands() {
return Collections.unmodifiableMap(this.commands);
}
@NonNull
public Map<Extension, Map<String, Command>> extensionCommands() {
return Collections.unmodifiableMap(this.extensionCommands);
}
public boolean runCommand(GeyserCommandSource sender, String command) {
Extension extension = null;
for (Extension loopedExtension : this.extensionCommands.keySet()) {
if (command.startsWith(loopedExtension.description().id() + " ")) {
extension = loopedExtension;
break;
}
}
if (!command.startsWith("geyser ") && extension == null) {
return false;
}
command = command.trim().replace(extension != null ? extension.description().id() + " " : "geyser ", "");
String label;
String[] args;
if (!command.contains(" ")) {
label = command.toLowerCase(Locale.ROOT);
args = new String[0];
} else {
label = command.substring(0, command.indexOf(" ")).toLowerCase(Locale.ROOT);
String argLine = command.substring(command.indexOf(" ") + 1);
args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine };
}
Command cmd = (extension != null ? this.extensionCommands.getOrDefault(extension, Collections.emptyMap()) : this.commands).get(label);
if (cmd == null) {
sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.commands.invalid"));
return false;
}
if (cmd instanceof GeyserCommand) {
if (sender instanceof GeyserSession) {
((GeyserCommand) cmd).execute((GeyserSession) sender, sender, args);
} else {
if (!cmd.isBedrockOnly()) {
((GeyserCommand) cmd).execute(null, sender, args);
} else {
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only"));
}
}
}
return true;
}
/**
* Returns the description of the given command
*
* @param command Command to get the description for
* @return Command description
*/
public String description(String command) {
return "";
}
@RequiredArgsConstructor
public static class CommandBuilder<T extends CommandSource> implements Command.Builder<T> {
private final Extension extension;
private Class<? extends T> sourceType;
private String name;
private String description = "";
private String permission = "";
private List<String> aliases;
private boolean suggestedOpOnly = false;
private boolean executableOnConsole = true;
private List<String> subCommands;
private boolean bedrockOnly;
private CommandExecutor<T> executor;
@Override
public Command.Builder<T> source(@NonNull Class<? extends T> sourceType) {
this.sourceType = sourceType;
return this;
}
public CommandBuilder<T> name(@NonNull String name) {
this.name = name;
return this;
}
public CommandBuilder<T> description(@NonNull String description) {
this.description = description;
return this;
}
public CommandBuilder<T> permission(@NonNull String permission) {
this.permission = permission;
return this;
}
public CommandBuilder<T> aliases(@NonNull List<String> aliases) {
this.aliases = aliases;
return this;
}
@Override
public Command.Builder<T> suggestedOpOnly(boolean suggestedOpOnly) {
this.suggestedOpOnly = suggestedOpOnly;
return this;
}
public CommandBuilder<T> executableOnConsole(boolean executableOnConsole) {
this.executableOnConsole = executableOnConsole;
return this;
}
public CommandBuilder<T> subCommands(@NonNull List<String> subCommands) {
this.subCommands = subCommands;
return this;
}
public CommandBuilder<T> bedrockOnly(boolean bedrockOnly) {
this.bedrockOnly = bedrockOnly;
return this;
}
public CommandBuilder<T> executor(@NonNull CommandExecutor<T> executor) {
this.executor = executor;
return this;
}
@NonNull
public GeyserExtensionCommand build() {
if (this.name == null || this.name.isBlank()) {
throw new IllegalArgumentException("Command cannot be null or blank!");
}
if (this.sourceType == null) {
throw new IllegalArgumentException("Source type was not defined for command " + this.name + " in extension " + this.extension.name());
}
return new GeyserExtensionCommand(this.extension, this.name, this.description, this.permission) {
@SuppressWarnings("unchecked")
@Override
public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) {
Class<? extends T> sourceType = CommandBuilder.this.sourceType;
CommandExecutor<T> executor = CommandBuilder.this.executor;
if (sourceType.isInstance(session)) {
executor.execute((T) session, this, args);
return;
}
if (sourceType.isInstance(sender)) {
executor.execute((T) sender, this, args);
return;
}
GeyserImpl.getInstance().getLogger().debug("Ignoring command " + this.name + " due to no suitable sender.");
}
@NonNull
@Override
public List<String> aliases() {
return CommandBuilder.this.aliases == null ? Collections.emptyList() : CommandBuilder.this.aliases;
}
@Override
public boolean isSuggestedOpOnly() {
return CommandBuilder.this.suggestedOpOnly;
}
@NonNull
@Override
public List<String> subCommands() {
return CommandBuilder.this.subCommands == null ? Collections.emptyList() : CommandBuilder.this.subCommands;
}
@Override
public boolean isBedrockOnly() {
return CommandBuilder.this.bedrockOnly;
}
@Override
public boolean isExecutableOnConsole() {
return CommandBuilder.this.executableOnConsole;
}
};
}
}
}

View File

@ -25,11 +25,16 @@
package org.geysermc.geyser.command;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.UUID;
/**
* Implemented on top of any class that can send a command.
* For example, it wraps around Spigot's CommandSender class.
@ -46,4 +51,29 @@ public interface GeyserCommandSource extends CommandSource {
default void sendMessage(Component message) {
sendMessage(LegacyComponentSerializer.legacySection().serialize(message));
}
default void sendLocaleString(String key, Object... values) {
sendMessage(GeyserLocale.getPlayerLocaleString(key, locale(), values));
}
default void sendLocaleString(String key) {
sendMessage(GeyserLocale.getPlayerLocaleString(key, locale()));
}
@Override
default @Nullable GeyserSession connection() {
UUID uuid = playerUuid();
if (uuid == null) {
return null;
}
return GeyserImpl.getInstance().connectionByUuid(uuid);
}
/**
* @return the underlying platform handle that this source represents.
* If such handle doesn't exist, this itself is returned.
*/
default Object handle() {
return this;
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.command;
import lombok.AllArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.key.CloudKey;
import org.incendo.cloud.permission.Permission;
import org.incendo.cloud.permission.PermissionResult;
import org.incendo.cloud.permission.PredicatePermission;
@AllArgsConstructor
public class GeyserPermission implements PredicatePermission<GeyserCommandSource> {
private final boolean bedrockOnly;
private final boolean playerOnly;
private final String permission;
private final CommandManager<GeyserCommandSource> manager;
public Result check(GeyserCommandSource source) {
if (permission.isBlank()) {
return Result.ALLOWED;
}
if (bedrockOnly) {
if (source.connection() == null) {
return Result.NOT_BEDROCK;
}
// connection is present -> it is a player -> playerOnly is irrelevant
} else if (playerOnly) {
if (source.isConsole()) {
return Result.NOT_PLAYER; // must be a player but is console
}
}
if (manager.hasPermission(source, permission)) {
return Result.ALLOWED;
}
return Result.NO_PERMISSION;
}
@Override
public @NonNull CloudKey<Void> key() {
return CloudKey.cloudKey(permission);
}
@Override
public @NonNull PermissionResult testPermission(@NonNull GeyserCommandSource sender) {
return check(sender).toPermission(permission);
}
public enum Result {
/**
* The source must be a bedrock player, but is not.
*/
NOT_BEDROCK,
/**
* The source must be a player, but is not.
*/
NOT_PLAYER,
/**
* The source does not have a required permission node.
*/
NO_PERMISSION,
/**
* The source meets all requirements.
*/
ALLOWED;
public PermissionResult toPermission(String permission) {
return PermissionResult.of(this == ALLOWED, Permission.of(permission));
}
}
}

View File

@ -25,33 +25,32 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.MinecraftLocale;
import org.incendo.cloud.context.CommandContext;
import java.util.Objects;
public class AdvancedTooltipsCommand extends GeyserCommand {
public AdvancedTooltipsCommand(String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.TRUE, true, true);
}
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
if (session != null) {
String onOrOff = session.isAdvancedTooltips() ? "off" : "on";
session.setAdvancedTooltips(!session.isAdvancedTooltips());
session.sendMessage("§l§e" + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) + " §r" + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale()));
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
}
}
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserSession session = Objects.requireNonNull(context.sender().connection());
@Override
public boolean isExecutableOnConsole() {
return false;
}
@Override
public boolean isBedrockOnly() {
return true;
String onOrOff = session.isAdvancedTooltips() ? "off" : "on";
session.setAdvancedTooltips(!session.isAdvancedTooltips());
session.sendMessage(ChatColor.BOLD + ChatColor.YELLOW
+ MinecraftLocale.getLocaleString("debug.prefix", session.locale())
+ " " + ChatColor.RESET
+ MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale()));
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
}
}

View File

@ -25,29 +25,23 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.incendo.cloud.context.CommandContext;
import java.util.Objects;
public class AdvancementsCommand extends GeyserCommand {
public AdvancementsCommand(String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.TRUE, true, true);
}
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
if (session != null) {
session.getAdvancementsCache().buildAndShowMenuForm();
}
}
@Override
public boolean isExecutableOnConsole() {
return false;
}
@Override
public boolean isBedrockOnly() {
return true;
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserSession session = Objects.requireNonNull(context.sender().connection());
session.getAdvancementsCache().buildAndShowMenuForm();
}
}

View File

@ -26,90 +26,82 @@
package org.geysermc.geyser.command.defaults;
import com.fasterxml.jackson.databind.JsonNode;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.LoopbackUtil;
import org.geysermc.geyser.util.WebUtils;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import static org.incendo.cloud.parser.standard.IntegerParser.integerParser;
import static org.incendo.cloud.parser.standard.StringParser.stringParser;
public class ConnectionTestCommand extends GeyserCommand {
/*
* The MOTD is temporarily changed during the connection test.
* This allows us to check if we are pinging the correct Geyser instance
*/
public static String CONNECTION_TEST_MOTD = null;
private final GeyserImpl geyser;
private static final String ADDRESS = "address";
private static final String PORT = "port";
private final GeyserImpl geyser;
private final Random random = new Random();
public ConnectionTestCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.NOT_SET);
this.geyser = geyser;
}
@Override
public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) {
// Only allow the console to create dumps on Geyser Standalone
if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return;
}
public void register(CommandManager<GeyserCommandSource> manager) {
manager.command(baseBuilder(manager)
.required(ADDRESS, stringParser())
.optional(PORT, integerParser(0, 65535))
.handler(this::execute));
}
if (args.length == 0) {
sender.sendMessage("Provide the server IP and port you are trying to test Bedrock connections for. Example: `test.geysermc.org:19132`");
return;
}
@Override
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.sender();
String ipArgument = context.get(ADDRESS);
Integer portArgument = context.getOrDefault(PORT, null); // null if port was not specified
// Replace "<" and ">" symbols if they are present to avoid the common issue of people including them
String[] fullAddress = args[0].replace("<", "").replace(">", "").split(":", 2);
// Still allow people to not supply a port and fallback to 19132
int port;
if (fullAddress.length == 2) {
try {
port = Integer.parseInt(fullAddress[1]);
} catch (NumberFormatException e) {
// can occur if e.g. "/geyser connectiontest <ip>:<port> is ran
sender.sendMessage("Not a valid port! Specify a valid numeric port.");
return;
}
} else {
port = geyser.getConfig().getBedrock().broadcastPort();
}
String ip = fullAddress[0];
final String ip = ipArgument.replace("<", "").replace(">", "");
final int port = portArgument != null ? portArgument : geyser.getConfig().getBedrock().broadcastPort(); // default bedrock port
// Issue: people commonly checking placeholders
if (ip.equals("ip")) {
sender.sendMessage(ip + " is not a valid IP, and instead a placeholder. Please specify the IP to check.");
source.sendMessage(ip + " is not a valid IP, and instead a placeholder. Please specify the IP to check.");
return;
}
// Issue: checking 0.0.0.0 won't work
if (ip.equals("0.0.0.0")) {
sender.sendMessage("Please specify the IP that you would connect with. 0.0.0.0 in the config tells Geyser to the listen on the server's IPv4.");
source.sendMessage("Please specify the IP that you would connect with. 0.0.0.0 in the config tells Geyser to the listen on the server's IPv4.");
return;
}
// Issue: people testing local ip
if (ip.equals("localhost") || ip.startsWith("127.") || ip.startsWith("10.") || ip.startsWith("192.168.")) {
sender.sendMessage("This tool checks if connections from other networks are possible, so you cannot check a local IP.");
source.sendMessage("This tool checks if connections from other networks are possible, so you cannot check a local IP.");
return;
}
// Issue: port out of bounds
if (port <= 0 || port >= 65535) {
sender.sendMessage("The port you specified is invalid! Please specify a valid port.");
source.sendMessage("The port you specified is invalid! Please specify a valid port.");
return;
}
@ -118,37 +110,37 @@ public class ConnectionTestCommand extends GeyserCommand {
// Issue: do the ports not line up? We only check this if players don't override the broadcast port - if they do, they (hopefully) know what they're doing
if (config.getBedrock().broadcastPort() == config.getBedrock().port()) {
if (port != config.getBedrock().port()) {
if (fullAddress.length == 2) {
sender.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration ("
if (portArgument != null) {
source.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration ("
+ config.getBedrock().port() + ")");
sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
source.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
if (config.getBedrock().isCloneRemotePort()) {
sender.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead.");
source.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead.");
}
} else {
sender.sendMessage("You did not specify the port to check (add it with \":<port>\"), " +
source.sendMessage("You did not specify the port to check (add it with \":<port>\"), " +
"and the default port 19132 does not match the port in your Geyser configuration ("
+ config.getBedrock().port() + ")!");
sender.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`.");
source.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`.");
}
}
} else {
if (config.getBedrock().broadcastPort() != port) {
sender.sendMessage("The port you are testing with (" + port + ") is not the same as the broadcast port set in your Geyser configuration ("
source.sendMessage("The port you are testing with (" + port + ") is not the same as the broadcast port set in your Geyser configuration ("
+ config.getBedrock().broadcastPort() + "). ");
sender.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on.");
sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `broadcast-port` in the config.");
source.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on.");
source.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `broadcast-port` in the config.");
}
}
// Issue: is the `bedrock` `address` in the config different?
if (!config.getBedrock().address().equals("0.0.0.0")) {
sender.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional.");
source.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional.");
}
// Issue: did someone turn on enable-proxy-protocol, and they didn't mean it?
if (config.getBedrock().isEnableProxyProtocol()) {
sender.sendMessage("You have the `enable-proxy-protocol` setting enabled. " +
source.sendMessage("You have the `enable-proxy-protocol` setting enabled. " +
"Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled.");
}
@ -157,14 +149,14 @@ public class ConnectionTestCommand extends GeyserCommand {
// Issue: SRV record?
String[] record = WebUtils.findSrvRecord(geyser, ip);
if (record != null && !ip.equals(record[3]) && !record[2].equals(String.valueOf(port))) {
sender.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2]
source.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2]
+ ". If that fails, re-run this command with that address and port.");
return;
}
// Issue: does Loopback need applying?
if (LoopbackUtil.needsLoopback(GeyserImpl.getInstance().getLogger())) {
sender.sendMessage("Loopback is not applied on this computer! You will have issues connecting from the same computer. " +
source.sendMessage("Loopback is not applied on this computer! You will have issues connecting from the same computer. " +
"See here for steps on how to resolve: " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/#using-geyser-on-the-same-computer");
}
@ -178,7 +170,7 @@ public class ConnectionTestCommand extends GeyserCommand {
String connectionTestMotd = "Geyser Connection Test " + randomStr;
CONNECTION_TEST_MOTD = connectionTestMotd;
sender.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait...");
source.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait...");
JsonNode output;
try {
String hostname = URLEncoder.encode(ip, StandardCharsets.UTF_8);
@ -200,31 +192,31 @@ public class ConnectionTestCommand extends GeyserCommand {
JsonNode pong = ping.get("pong");
String remoteMotd = pong.get("motd").asText();
if (!connectionTestMotd.equals(remoteMotd)) {
sender.sendMessage("The MOTD did not match when we pinged the server (we got '" + remoteMotd + "'). " +
source.sendMessage("The MOTD did not match when we pinged the server (we got '" + remoteMotd + "'). " +
"Did you supply the correct IP and port of your server?");
sendLinks(sender);
sendLinks(source);
return;
}
if (ping.get("tcpFirst").asBoolean()) {
sender.sendMessage("Your server hardware likely has some sort of firewall preventing people from joining easily. See https://geysermc.link/ovh-firewall for more information.");
sendLinks(sender);
source.sendMessage("Your server hardware likely has some sort of firewall preventing people from joining easily. See https://geysermc.link/ovh-firewall for more information.");
sendLinks(source);
return;
}
sender.sendMessage("Your server is likely online and working as of " + when + "!");
sendLinks(sender);
source.sendMessage("Your server is likely online and working as of " + when + "!");
sendLinks(source);
return;
}
sender.sendMessage("Your server is likely unreachable from outside the network!");
source.sendMessage("Your server is likely unreachable from outside the network!");
JsonNode message = output.get("message");
if (message != null && !message.asText().isEmpty()) {
sender.sendMessage("Got the error message: " + message.asText());
source.sendMessage("Got the error message: " + message.asText());
}
sendLinks(sender);
sendLinks(source);
} catch (Exception e) {
sender.sendMessage("An error occurred while trying to check your connection! Check the console for more information.");
source.sendMessage("An error occurred while trying to check your connection! Check the console for more information.");
geyser.getLogger().error("Error while trying to check your connection!", e);
}
});
@ -235,9 +227,4 @@ public class ConnectionTestCommand extends GeyserCommand {
"https://wiki.geysermc.org/geyser/setup/");
sender.sendMessage("If that does not work, see " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/" + ", or contact us on Discord: " + "https://discord.gg/geysermc");
}
@Override
public boolean isSuggestedOpOnly() {
return true;
}
}

View File

@ -29,43 +29,71 @@ import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.dump.DumpInfo;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.AsteriskSerializer;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.WebUtils;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import static org.incendo.cloud.parser.standard.StringArrayParser.stringArrayParser;
public class DumpCommand extends GeyserCommand {
private static final String ARGUMENTS = "args";
private static final Iterable<String> SUGGESTIONS = List.of("full", "offline", "logs");
private final GeyserImpl geyser;
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String DUMP_URL = "https://dump.geysermc.org/";
public DumpCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.NOT_SET);
this.geyser = geyser;
}
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
// Only allow the console to create dumps on Geyser Standalone
if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return;
@Override
public void register(CommandManager<GeyserCommandSource> manager) {
manager.command(baseBuilder(manager)
.optional(ARGUMENTS, stringArrayParser(), SuggestionProvider.blockingStrings((ctx, input) -> {
// parse suggestions here
List<String> inputs = new ArrayList<>();
while (input.hasRemainingInput()) {
inputs.add(input.readStringSkipWhitespace());
}
if (inputs.size() <= 2) {
return SUGGESTIONS; // only `geyser dump` was typed (2 literals)
}
// the rest of the input after `geyser dump` is for this argument
inputs = inputs.subList(2, inputs.size());
// don't suggest any words they have already typed
List<String> suggestions = new ArrayList<>();
SUGGESTIONS.forEach(suggestions::add);
suggestions.removeAll(inputs);
return suggestions;
}))
.handler(this::execute));
}
@Override
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.sender();
String[] args = context.getOrDefault(ARGUMENTS, new String[0]);
boolean showSensitive = false;
boolean offlineDump = false;
boolean addLog = false;
@ -75,13 +103,14 @@ public class DumpCommand extends GeyserCommand {
case "full" -> showSensitive = true;
case "offline" -> offlineDump = true;
case "logs" -> addLog = true;
default -> context.sender().sendMessage("Invalid geyser dump option " + arg + "! Fallback to no arguments.");
}
}
}
AsteriskSerializer.showSensitive = showSensitive;
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", sender.locale()));
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", source.locale()));
String dumpData;
try {
if (offlineDump) {
@ -93,7 +122,7 @@ public class DumpCommand extends GeyserCommand {
dumpData = MAPPER.writeValueAsString(new DumpInfo(addLog));
}
} catch (IOException e) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.locale()));
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", source.locale()));
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e);
return;
}
@ -101,21 +130,21 @@ public class DumpCommand extends GeyserCommand {
String uploadedDumpUrl;
if (offlineDump) {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", sender.locale()));
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", source.locale()));
try {
FileOutputStream outputStream = new FileOutputStream(GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile());
outputStream.write(dumpData.getBytes());
outputStream.close();
} catch (IOException e) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", sender.locale()));
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", source.locale()));
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.write_error_short"), e);
return;
}
uploadedDumpUrl = "dump.json";
} else {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", sender.locale()));
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", source.locale()));
String response;
JsonNode responseNode;
@ -123,33 +152,22 @@ public class DumpCommand extends GeyserCommand {
response = WebUtils.post(DUMP_URL + "documents", dumpData);
responseNode = MAPPER.readTree(response);
} catch (IOException e) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.locale()));
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", source.locale()));
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e);
return;
}
if (!responseNode.has("key")) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", source.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
return;
}
uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText();
}
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", sender.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl);
if (!sender.isConsole()) {
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", sender.name(), uploadedDumpUrl));
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", source.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl);
if (!source.isConsole()) {
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", source.name(), uploadedDumpUrl));
}
}
@NonNull
@Override
public List<String> subCommands() {
return Arrays.asList("offline", "full", "logs");
}
@Override
public boolean isSuggestedOpOnly() {
return true;
}
}

View File

@ -25,14 +25,14 @@
package org.geysermc.geyser.command.defaults;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.incendo.cloud.context.CommandContext;
import java.util.Comparator;
import java.util.List;
@ -41,22 +41,23 @@ public class ExtensionsCommand extends GeyserCommand {
private final GeyserImpl geyser;
public ExtensionsCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.TRUE);
this.geyser = geyser;
}
@Override
public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) {
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.sender();
// TODO: Pagination
int page = 1;
int maxPage = 1;
String header = GeyserLocale.getPlayerLocaleString("geyser.commands.extensions.header", sender.locale(), page, maxPage);
sender.sendMessage(header);
String header = GeyserLocale.getPlayerLocaleString("geyser.commands.extensions.header", source.locale(), page, maxPage);
source.sendMessage(header);
this.geyser.extensionManager().extensions().stream().sorted(Comparator.comparing(Extension::name)).forEach(extension -> {
String extensionName = (extension.isEnabled() ? ChatColor.GREEN : ChatColor.RED) + extension.name();
sender.sendMessage("- " + extensionName + ChatColor.RESET + " v" + extension.description().version() + formatAuthors(extension.description().authors()));
source.sendMessage("- " + extensionName + ChatColor.RESET + " v" + extension.description().version() + formatAuthors(extension.description().authors()));
});
}

View File

@ -25,61 +25,73 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
public class HelpCommand extends GeyserCommand {
private final GeyserImpl geyser;
private final String baseCommand;
private final Map<String, Command> commands;
private final String rootCommand;
private final String rootCommandPermission;
private final Collection<Command> commands;
public HelpCommand(GeyserImpl geyser, String name, String description, String permission,
String baseCommand, Map<String, Command> commands) {
super(name, description, permission);
this.geyser = geyser;
this.baseCommand = baseCommand;
this.commands = commands;
this.setAliases(Collections.singletonList("?"));
String rootCommand, String rootCommandPermission, Map<String, Command> commands) {
super(name, description, permission, TriState.TRUE);
this.rootCommand = rootCommand;
this.rootCommandPermission = rootCommandPermission;
this.commands = commands.values();
this.aliases = Collections.singletonList("?");
}
/**
* Sends the help menu to a command sender. Will not show certain commands depending on the command sender and session.
*
* @param session The Geyser session of the command sender, if it is a bedrock player. If null, bedrock-only commands will be hidden.
* @param sender The CommandSender to send the help message to.
* @param args Not used.
*/
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
public String rootCommand() {
return rootCommand;
}
@Override
public void register(CommandManager<GeyserCommandSource> manager) {
super.register(manager);
// Also register just the root (ie `/geyser` or `/extensionId`)
// note: this doesn't do the other permission checks that GeyserCommand#baseBuilder does,
// but it's fine because the help command can be executed by non-bedrock players and by the console.
manager.command(manager.commandBuilder(rootCommand)
.apply(meta()) // shouldn't be necessary - just for consistency
.permission(rootCommandPermission)
.handler((this::execute)));
}
@Override
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.sender();
boolean bedrockPlayer = source.connection() != null;
// todo: pagination
int page = 1;
int maxPage = 1;
String translationKey = this.baseCommand.equals("geyser") ? "geyser.commands.help.header" : "geyser.commands.extensions.header";
String header = GeyserLocale.getPlayerLocaleString(translationKey, sender.locale(), page, maxPage);
sender.sendMessage(header);
String translationKey = this.rootCommand.equals(GeyserCommand.DEFAULT_ROOT_COMMAND) ? "geyser.commands.help.header" : "geyser.commands.extensions.header";
String header = GeyserLocale.getPlayerLocaleString(translationKey, source.locale(), page, maxPage);
source.sendMessage(header);
this.commands.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> {
Command cmd = entry.getValue();
// Standalone hack-in since it doesn't have a concept of permissions
if (geyser.getPlatformType() == PlatformType.STANDALONE || sender.hasPermission(cmd.permission())) {
// Only list commands the player can actually run
if (cmd.isBedrockOnly() && session == null) {
return;
}
sender.sendMessage(ChatColor.YELLOW + "/" + baseCommand + " " + entry.getKey() + ChatColor.WHITE + ": " +
GeyserLocale.getPlayerLocaleString(cmd.description(), sender.locale()));
}
});
this.commands.stream()
.distinct() // remove aliases
.filter(cmd -> !cmd.isBedrockOnly() || bedrockPlayer) // remove bedrock only commands if not a bedrock player
.filter(cmd -> source.hasPermission(cmd.permission()))
.sorted(Comparator.comparing(Command::name))
.forEach(cmd -> {
String description = GeyserLocale.getPlayerLocaleString(cmd.description(), source.locale());
source.sendMessage(ChatColor.YELLOW + "/" + rootCommand + " " + cmd.name() + ChatColor.WHITE + ": " + description);
});
}
}

View File

@ -26,10 +26,12 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.incendo.cloud.context.CommandContext;
import java.util.stream.Collectors;
@ -38,22 +40,18 @@ public class ListCommand extends GeyserCommand {
private final GeyserImpl geyser;
public ListCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.NOT_SET);
this.geyser = geyser;
}
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", sender.locale(),
geyser.getSessionManager().size(),
geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" ")));
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.sender();
sender.sendMessage(message);
}
String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", source.locale(),
geyser.getSessionManager().size(),
geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" ")));
@Override
public boolean isSuggestedOpOnly() {
return true;
source.sendMessage(message);
}
}

View File

@ -26,32 +26,23 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.incendo.cloud.context.CommandContext;
import java.util.Objects;
public class OffhandCommand extends GeyserCommand {
public OffhandCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.TRUE, true, true);
}
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
if (session == null) {
return;
}
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserSession session = Objects.requireNonNull(context.sender().connection());
session.requestOffhandSwap();
}
@Override
public boolean isExecutableOnConsole() {
return false;
}
@Override
public boolean isBedrockOnly() {
return true;
}
}

View File

@ -25,12 +25,12 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.incendo.cloud.context.CommandContext;
import java.util.concurrent.TimeUnit;
@ -39,27 +39,17 @@ public class ReloadCommand extends GeyserCommand {
private final GeyserImpl geyser;
public ReloadCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.NOT_SET);
this.geyser = geyser;
}
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) {
return;
}
String message = GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", sender.locale());
sender.sendMessage(message);
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.sender();
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", source.locale()));
geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick");
//FIXME Without the tiny wait, players do not get kicked - same happens when Geyser tries to disconnect all sessions on shutdown
geyser.getScheduledThread().schedule(geyser::reloadGeyser, 10, TimeUnit.MILLISECONDS);
}
@Override
public boolean isSuggestedOpOnly() {
return true;
}
}

View File

@ -26,30 +26,24 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.SettingsUtils;
import org.incendo.cloud.context.CommandContext;
import java.util.Objects;
public class SettingsCommand extends GeyserCommand {
public SettingsCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.TRUE, true, true);
}
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
if (session != null) {
session.sendForm(SettingsUtils.buildForm(session));
}
}
@Override
public boolean isExecutableOnConsole() {
return false;
}
@Override
public boolean isBedrockOnly() {
return true;
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserSession session = Objects.requireNonNull(context.sender().connection());
session.sendForm(SettingsUtils.buildForm(session));
}
}

View File

@ -25,35 +25,29 @@
package org.geysermc.geyser.command.defaults;
import com.github.steveice10.mc.protocol.data.game.ClientCommand;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.ClientCommand;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket;
import org.incendo.cloud.context.CommandContext;
import java.util.Objects;
public class StatisticsCommand extends GeyserCommand {
public StatisticsCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.TRUE, true, true);
}
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
if (session == null) return;
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserSession session = Objects.requireNonNull(context.sender().connection());
session.setWaitingForStatistics(true);
ServerboundClientCommandPacket ServerboundClientCommandPacket = new ServerboundClientCommandPacket(ClientCommand.STATS);
session.sendDownstreamGamePacket(ServerboundClientCommandPacket);
}
@Override
public boolean isExecutableOnConsole() {
return false;
}
@Override
public boolean isBedrockOnly() {
return true;
ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(ClientCommand.STATS);
session.sendDownstreamGamePacket(packet);
}
}

View File

@ -25,12 +25,11 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.incendo.cloud.context.CommandContext;
import java.util.Collections;
@ -39,24 +38,13 @@ public class StopCommand extends GeyserCommand {
private final GeyserImpl geyser;
public StopCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.NOT_SET);
this.geyser = geyser;
this.setAliases(Collections.singletonList("shutdown"));
this.aliases = Collections.singletonList("shutdown");
}
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return;
}
public void execute(CommandContext<GeyserCommandSource> context) {
geyser.getBootstrap().onGeyserShutdown();
}
@Override
public boolean isSuggestedOpOnly() {
return true;
}
}

View File

@ -29,13 +29,14 @@ import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.WebUtils;
import org.incendo.cloud.context.CommandContext;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@ -46,13 +47,14 @@ public class VersionCommand extends GeyserCommand {
private final GeyserImpl geyser;
public VersionCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
super(name, description, permission, TriState.NOT_SET);
this.geyser = geyser;
}
@Override
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.sender();
String bedrockVersions;
List<BedrockCodec> supportedCodecs = GameProtocol.SUPPORTED_BEDROCK_CODECS;
if (supportedCodecs.size() > 1) {
@ -68,12 +70,12 @@ public class VersionCommand extends GeyserCommand {
javaVersions = supportedJavaVersions.get(0);
}
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", sender.locale(),
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", source.locale(),
GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions));
// Disable update checking in dev mode and for players in Geyser Standalone
if (GeyserImpl.getInstance().isProductionEnvironment() && !(!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale()));
if (GeyserImpl.getInstance().isProductionEnvironment() && !(!source.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) {
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", source.locale()));
try {
String buildXML = WebUtils.getBody("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" +
URLEncoder.encode(GeyserImpl.BRANCH, StandardCharsets.UTF_8) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber");
@ -81,23 +83,18 @@ public class VersionCommand extends GeyserCommand {
int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim());
int buildNum = this.geyser.buildNumber();
if (latestBuildNum == buildNum) {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale()));
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", source.locale()));
} else {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated",
sender.locale(), (latestBuildNum - buildNum), Constants.GEYSER_DOWNLOAD_LOCATION));
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated",
source.locale(), (latestBuildNum - buildNum), Constants.GEYSER_DOWNLOAD_LOCATION));
}
} else {
throw new AssertionError("buildNumber missing");
}
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.version.failed"), e);
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.locale()));
source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", source.locale()));
}
}
}
@Override
public boolean isSuggestedOpOnly() {
return true;
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.command.standalone;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import java.util.Collections;
import java.util.Set;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
public class PermissionConfiguration {
@JsonProperty("default-permissions")
private Set<String> defaultPermissions = Collections.emptySet();
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.command.standalone;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionCheckersEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.api.permission.PermissionChecker;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.util.FileUtils;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.internal.CommandRegistrationHandler;
import org.incendo.cloud.meta.CommandMeta;
import org.incendo.cloud.meta.SimpleCommandMeta;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class StandaloneCloudCommandManager extends CommandManager<GeyserCommandSource> {
private final GeyserImpl geyser;
/**
* The checkers we use to test if a command source has a permission
*/
private final List<PermissionChecker> permissionCheckers = new ArrayList<>();
/**
* Any permissions that all connections have
*/
private final Set<String> basePermissions = new ObjectOpenHashSet<>();
public StandaloneCloudCommandManager(GeyserImpl geyser) {
super(ExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler());
// simpleCoordinator: execute commands immediately on the calling thread.
// nullCommandRegistrationHandler: cloud is not responsible for handling our CommandRegistry, which is fairly decoupled.
this.geyser = geyser;
// allow any extensions to customize permissions
geyser.getEventBus().fire((GeyserRegisterPermissionCheckersEvent) permissionCheckers::add);
// must still implement a basic permission system
try {
File permissionsFile = geyser.getBootstrap().getConfigFolder().resolve("permissions.yml").toFile();
FileUtils.fileOrCopiedFromResource(permissionsFile, "permissions.yml", geyser.getBootstrap());
PermissionConfiguration config = FileUtils.loadConfig(permissionsFile, PermissionConfiguration.class);
basePermissions.addAll(config.getDefaultPermissions());
} catch (Exception e) {
geyser.getLogger().error("Failed to load permissions.yml - proceeding without it", e);
}
}
/**
* Fire a {@link GeyserRegisterPermissionsEvent} to determine any additions or removals to the base list of
* permissions. This should be called after any event listeners have been registered, such as that of {@link CommandRegistry}.
*/
public void gatherPermissions() {
geyser.getEventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> {
if (permission.isBlank()) {
return;
}
if (def == TriState.TRUE) {
basePermissions.add(permission);
}
});
}
@Override
public boolean hasPermission(@NonNull GeyserCommandSource sender, @NonNull String permission) {
// Note: the two GeyserCommandSources on Geyser-Standalone are GeyserLogger and GeyserSession
// GeyserLogger#hasPermission always returns true
// GeyserSession#hasPermission delegates to this method,
// which is why this method doesn't just call GeyserCommandSource#hasPermission
if (sender.isConsole()) {
return true;
}
for (PermissionChecker checker : permissionCheckers) {
Boolean result = checker.hasPermission(sender, permission).toBoolean();
if (result != null) {
return result;
}
// undefined - try the next checker to see if it has a defined value
}
// fallback to our list of default permissions
// note that a PermissionChecker may in fact override any values set here by return FALSE
return basePermissions.contains(permission);
}
@NonNull
@Override
public CommandMeta createDefaultCommandMeta() {
return SimpleCommandMeta.empty();
}
}

View File

@ -27,8 +27,8 @@ package org.geysermc.geyser.dump;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.text.AsteriskSerializer;
import java.util.List;

View File

@ -56,12 +56,7 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.*;
import java.util.stream.Collectors;
@Getter

View File

@ -25,14 +25,15 @@
package org.geysermc.geyser.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.entity.factory.EntityFactory;
import org.geysermc.geyser.entity.properties.GeyserEntityProperties;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.translator.entity.EntityMetadataTranslator;
@ -49,10 +50,10 @@ import java.util.function.BiConsumer;
* @param <T> the entity type this definition represents
*/
public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, EntityType entityType, String identifier,
float width, float height, float offset, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
float width, float height, float offset, GeyserEntityProperties registeredProperties, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
public static <T extends Entity> Builder<T> inherited(EntityFactory<T> factory, EntityDefinition<? super T> parent) {
return new Builder<>(factory, parent.entityType, parent.identifier, parent.width, parent.height, parent.offset, new ObjectArrayList<>(parent.translators));
return new Builder<>(factory, parent.entityType, parent.identifier, parent.width, parent.height, parent.offset, parent.registeredProperties, new ObjectArrayList<>(parent.translators));
}
public static <T extends Entity> Builder<T> builder(EntityFactory<T> factory) {
@ -87,6 +88,7 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
private float width;
private float height;
private float offset = 0.00001f;
private GeyserEntityProperties registeredProperties;
private final List<EntityMetadataTranslator<? super T, ?, ?>> translators;
private Builder(EntityFactory<T> factory) {
@ -94,13 +96,14 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
translators = new ObjectArrayList<>();
}
public Builder(EntityFactory<T> factory, EntityType type, String identifier, float width, float height, float offset, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
public Builder(EntityFactory<T> factory, EntityType type, String identifier, float width, float height, float offset, GeyserEntityProperties registeredProperties, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
this.factory = factory;
this.type = type;
this.identifier = identifier;
this.width = width;
this.height = height;
this.offset = offset;
this.registeredProperties = registeredProperties;
this.translators = translators;
}
@ -127,6 +130,11 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
return this;
}
public Builder<T> properties(GeyserEntityProperties registeredProperties) {
this.registeredProperties = registeredProperties;
return this;
}
public <U, EM extends EntityMetadata<U, ? extends MetadataType<U>>> Builder<T> addTranslator(MetadataType<U> type, BiConsumer<T, EM> translateFunction) {
translators.add(new EntityMetadataTranslator<>(type, translateFunction));
return this;
@ -149,10 +157,13 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
if (identifier == null && type != null) {
identifier = "minecraft:" + type.name().toLowerCase(Locale.ROOT);
}
EntityDefinition<T> definition = new EntityDefinition<>(factory, type, identifier, width, height, offset, translators);
EntityDefinition<T> definition = new EntityDefinition<>(factory, type, identifier, width, height, offset, registeredProperties, translators);
if (register && definition.entityType() != null) {
Registries.ENTITY_DEFINITIONS.get().putIfAbsent(definition.entityType(), definition);
Registries.JAVA_ENTITY_IDENTIFIERS.get().putIfAbsent("minecraft:" + type.name().toLowerCase(Locale.ROOT), definition);
if (definition.registeredProperties() != null) {
Registries.BEDROCK_ENTITY_PROPERTIES.get().add(definition.registeredProperties().toNbtMap(identifier));
}
}
return definition;
}

View File

@ -25,12 +25,13 @@
package org.geysermc.geyser.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.properties.GeyserEntityProperties;
import org.geysermc.geyser.entity.type.*;
import org.geysermc.geyser.entity.type.living.*;
import org.geysermc.geyser.entity.type.living.animal.*;
@ -53,8 +54,9 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
public final class EntityDefinitions {
public static final EntityDefinition<AllayEntity> ALLAY;
public static final EntityDefinition<AreaEffectCloudEntity> AREA_EFFECT_CLOUD;
public static final EntityDefinition<ArmadilloEntity> ARMADILLO;
public static final EntityDefinition<ArmorStandEntity> ARMOR_STAND;
public static final EntityDefinition<TippedArrowEntity> ARROW;
public static final EntityDefinition<ArrowEntity> ARROW;
public static final EntityDefinition<AxolotlEntity> AXOLOTL;
public static final EntityDefinition<BatEntity> BAT;
public static final EntityDefinition<BeeEntity> BEE;
@ -200,7 +202,6 @@ public final class EntityDefinitions {
.type(EntityType.AREA_EFFECT_CLOUD)
.height(0.5f).width(1.0f)
.addTranslator(MetadataType.FLOAT, AreaEffectCloudEntity::setRadius)
.addTranslator(MetadataType.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.EFFECT_COLOR, entityMetadata.getValue()))
.addTranslator(null) // Waiting
.addTranslator(MetadataType.PARTICLE, AreaEffectCloudEntity::setParticle)
.build();
@ -376,10 +377,10 @@ public final class EntityDefinitions {
.addTranslator(MetadataType.BYTE, AbstractArrowEntity::setArrowFlags)
.addTranslator(null) // "Piercing level"
.build();
ARROW = EntityDefinition.inherited(TippedArrowEntity::new, abstractArrowBase)
ARROW = EntityDefinition.inherited(ArrowEntity::new, abstractArrowBase)
.type(EntityType.ARROW)
.heightAndWidth(0.25f)
.addTranslator(MetadataType.INT, TippedArrowEntity::setPotionEffectColor)
.addTranslator(MetadataType.INT, ArrowEntity::setPotionEffectColor)
.build();
SPECTRAL_ARROW = EntityDefinition.inherited(abstractArrowBase.factory(), abstractArrowBase)
.type(EntityType.SPECTRAL_ARROW)
@ -452,8 +453,7 @@ public final class EntityDefinitions {
EntityDefinition<LivingEntity> livingEntityBase = EntityDefinition.inherited(LivingEntity::new, entityBase)
.addTranslator(MetadataType.BYTE, LivingEntity::setLivingEntityFlags)
.addTranslator(MetadataType.FLOAT, LivingEntity::setHealth)
.addTranslator(MetadataType.INT,
(livingEntity, entityMetadata) -> livingEntity.getDirtyMetadata().put(EntityDataTypes.EFFECT_COLOR, entityMetadata.getValue()))
.addTranslator(MetadataType.PARTICLES, LivingEntity::setParticles)
.addTranslator(MetadataType.BOOLEAN,
(livingEntity, entityMetadata) -> livingEntity.getDirtyMetadata().put(EntityDataTypes.EFFECT_AMBIENCE, (byte) (((BooleanEntityMetadata) entityMetadata).getPrimitiveValue() ? 1 : 0)))
.addTranslator(null) // Arrow count
@ -672,7 +672,7 @@ public final class EntityDefinitions {
SLIME = EntityDefinition.inherited(SlimeEntity::new, mobEntityBase)
.type(EntityType.SLIME)
.heightAndWidth(0.51f)
.addTranslator(MetadataType.INT, SlimeEntity::setScale)
.addTranslator(MetadataType.INT, SlimeEntity::setSlimeScale)
.build();
MAGMA_CUBE = EntityDefinition.inherited(MagmaCubeEntity::new, SLIME)
.type(EntityType.MAGMA_CUBE)
@ -770,6 +770,20 @@ public final class EntityDefinitions {
// Extends ageable
{
ARMADILLO = EntityDefinition.inherited(ArmadilloEntity::new, ageableEntityBase)
.type(EntityType.ARMADILLO)
.height(0.65f).width(0.7f)
.properties(new GeyserEntityProperties.Builder()
.addEnum(
"minecraft:armadillo_state",
"unrolled",
"rolled_up",
"rolled_up_peeking",
"rolled_up_relaxing",
"rolled_up_unrolling")
.build())
.addTranslator(MetadataType.ARMADILLO_STATE, ArmadilloEntity::setArmadilloState)
.build();
AXOLOTL = EntityDefinition.inherited(AxolotlEntity::new, ageableEntityBase)
.type(EntityType.AXOLOTL)
.height(0.42f).width(0.7f)
@ -780,6 +794,9 @@ public final class EntityDefinitions {
BEE = EntityDefinition.inherited(BeeEntity::new, ageableEntityBase)
.type(EntityType.BEE)
.heightAndWidth(0.6f)
.properties(new GeyserEntityProperties.Builder()
.addBoolean("minecraft:has_nectar")
.build())
.addTranslator(MetadataType.BYTE, BeeEntity::setBeeFlags)
.addTranslator(MetadataType.INT, BeeEntity::setAngerTime)
.build();
@ -937,8 +954,7 @@ public final class EntityDefinitions {
LLAMA = EntityDefinition.inherited(LlamaEntity::new, chestedHorseEntityBase)
.type(EntityType.LLAMA)
.height(1.87f).width(0.9f)
.addTranslator(MetadataType.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.STRENGTH, entityMetadata.getValue()))
.addTranslator(MetadataType.INT, LlamaEntity::setCarpetedColor)
.addTranslator(MetadataType.INT, LlamaEntity::setStrength)
.addTranslator(MetadataType.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.VARIANT, entityMetadata.getValue()))
.build();
TRADER_LLAMA = EntityDefinition.inherited(TraderLlamaEntity::new, LLAMA)
@ -947,7 +963,7 @@ public final class EntityDefinitions {
.build();
}
EntityDefinition<TameableEntity> tameableEntityBase = EntityDefinition.inherited(TameableEntity::new, ageableEntityBase)
EntityDefinition<TameableEntity> tameableEntityBase = EntityDefinition.<TameableEntity>inherited(null, ageableEntityBase) // No factory, is abstract
.addTranslator(MetadataType.BYTE, TameableEntity::setTameableFlags)
.addTranslator(MetadataType.OPTIONAL_UUID, TameableEntity::setOwner)
.build();
@ -971,6 +987,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataType.BOOLEAN, (wolfEntity, entityMetadata) -> wolfEntity.setFlag(EntityFlag.INTERESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataType.INT, WolfEntity::setCollarColor)
.addTranslator(MetadataType.INT, WolfEntity::setWolfAngerTime)
.addTranslator(MetadataType.WOLF_VARIANT, WolfEntity::setWolfVariant)
.build();
// As of 1.18 these don't track entity data at all

View File

@ -49,6 +49,7 @@ public enum GeyserAttributeType {
ATTACK_KNOCKBACK("minecraft:generic.attack_knockback", null, 1.5f, Float.MAX_VALUE, 0f),
ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f),
MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f),
SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f), // Unused. Do we need this?
// Bedrock Attributes
ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f),

View File

@ -0,0 +1,165 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.entity.properties;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import org.geysermc.geyser.entity.properties.type.BooleanProperty;
import org.geysermc.geyser.entity.properties.type.EnumProperty;
import org.geysermc.geyser.entity.properties.type.FloatProperty;
import org.geysermc.geyser.entity.properties.type.IntProperty;
import org.geysermc.geyser.entity.properties.type.PropertyType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@EqualsAndHashCode
@ToString
public class GeyserEntityProperties {
private final ObjectArrayList<PropertyType> properties;
private final Object2IntMap<String> propertyIndices;
private GeyserEntityProperties(ObjectArrayList<PropertyType> properties,
Object2IntMap<String> propertyIndices) {
this.properties = properties;
this.propertyIndices = propertyIndices;
}
public NbtMap toNbtMap(String entityType) {
NbtMapBuilder mapBuilder = NbtMap.builder();
List<NbtMap> nbtProperties = new ArrayList<>();
for (PropertyType property : properties) {
nbtProperties.add(property.nbtMap());
}
mapBuilder.putList("properties", NbtType.COMPOUND, nbtProperties);
return mapBuilder.putString("type", entityType).build();
}
public @NonNull List<PropertyType> getProperties() {
return properties;
}
public int getPropertyIndex(String name) {
return propertyIndices.getOrDefault(name, -1);
}
public static class Builder {
private final ObjectArrayList<PropertyType> properties = new ObjectArrayList<>();
private final Object2IntMap<String> propertyIndices = new Object2IntOpenHashMap<>();
public Builder addInt(@NonNull String name, int min, int max) {
if (propertyIndices.containsKey(name)) {
throw new IllegalArgumentException(
"Property with name " + name + " already exists on builder!");
}
PropertyType property = new IntProperty(name, min, max);
this.properties.add(property);
propertyIndices.put(name, properties.size() - 1);
return this;
}
public Builder addInt(@NonNull String name) {
if (propertyIndices.containsKey(name)) {
throw new IllegalArgumentException(
"Property with name " + name + " already exists on builder!");
}
PropertyType property = new IntProperty(name, Integer.MIN_VALUE, Integer.MAX_VALUE);
this.properties.add(property);
propertyIndices.put(name, properties.size() - 1);
return this;
}
public Builder addFloat(@NonNull String name, float min, float max) {
if (propertyIndices.containsKey(name)) {
throw new IllegalArgumentException(
"Property with name " + name + " already exists on builder!");
}
PropertyType property = new FloatProperty(name, min, max);
this.properties.add(property);
propertyIndices.put(name, properties.size() - 1);
return this;
}
public Builder addFloat(@NonNull String name) {
if (propertyIndices.containsKey(name)) {
throw new IllegalArgumentException(
"Property with name " + name + " already exists on builder!");
}
PropertyType property = new FloatProperty(name, Float.MIN_NORMAL, Float.MAX_VALUE);
this.properties.add(property);
propertyIndices.put(name, properties.size() - 1);
return this;
}
public Builder addBoolean(@NonNull String name) {
if (propertyIndices.containsKey(name)) {
throw new IllegalArgumentException(
"Property with name " + name + " already exists on builder!");
}
PropertyType property = new BooleanProperty(name);
this.properties.add(property);
propertyIndices.put(name, properties.size() - 1);
return this;
}
public Builder addEnum(@NonNull String name, List<String> values) {
if (propertyIndices.containsKey(name)) {
throw new IllegalArgumentException(
"Property with name " + name + " already exists on builder!");
}
PropertyType property = new EnumProperty(name, values);
this.properties.add(property);
propertyIndices.put(name, properties.size() - 1);
return this;
}
public Builder addEnum(@NonNull String name, String... values) {
if (propertyIndices.containsKey(name)) {
throw new IllegalArgumentException(
"Property with name " + name + " already exists on builder!");
}
List<String> valuesList = Arrays.asList(values); // Convert array to list
PropertyType property = new EnumProperty(name, valuesList);
this.properties.add(property);
propertyIndices.put(name, properties.size() - 1);
return this;
}
public GeyserEntityProperties build() {
return new GeyserEntityProperties(properties, propertyIndices);
}
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.entity.properties;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.cloudburstmc.protocol.bedrock.data.entity.FloatEntityProperty;
import org.cloudburstmc.protocol.bedrock.data.entity.IntEntityProperty;
import org.geysermc.geyser.entity.properties.type.EnumProperty;
import org.geysermc.geyser.entity.properties.type.PropertyType;
import java.util.List;
public class GeyserEntityPropertyManager {
private final GeyserEntityProperties properties;
private final ObjectArrayList<IntEntityProperty> intEntityProperties = new ObjectArrayList<>();
private final ObjectArrayList<FloatEntityProperty> floatEntityProperties = new ObjectArrayList<>();
public GeyserEntityPropertyManager(GeyserEntityProperties properties) {
this.properties = properties;
}
public void add(String propertyName, int value) {
int index = properties.getPropertyIndex(propertyName);
intEntityProperties.add(new IntEntityProperty(index, value));
}
public void add(String propertyName, boolean value) {
int index = properties.getPropertyIndex(propertyName);
intEntityProperties.add(new IntEntityProperty(index, value ? 1 : 0));
}
public void add(String propertyName, String value) {
int index = properties.getPropertyIndex(propertyName);
PropertyType property = properties.getProperties().get(index);
int enumIndex = ((EnumProperty) property).getIndex(value);
intEntityProperties.add(new IntEntityProperty(index, enumIndex));
}
public void add(String propertyName, float value) {
int index = properties.getPropertyIndex(propertyName);
floatEntityProperties.add(new FloatEntityProperty(index, value));
}
public boolean hasFloatProperties() {
return !this.floatEntityProperties.isEmpty();
}
public boolean hasIntProperties() {
return !this.intEntityProperties.isEmpty();
}
public boolean hasProperties() {
return hasFloatProperties() || hasIntProperties();
}
public ObjectArrayList<IntEntityProperty> intProperties() {
return this.intEntityProperties;
}
public void applyIntProperties(List<IntEntityProperty> properties) {
properties.addAll(intEntityProperties);
intEntityProperties.clear();
}
public ObjectArrayList<FloatEntityProperty> floatProperties() {
return this.floatEntityProperties;
}
public void applyFloatProperties(List<FloatEntityProperty> properties) {
properties.addAll(floatEntityProperties);
floatEntityProperties.clear();
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.entity.properties.type;
import org.cloudburstmc.nbt.NbtMap;
public class BooleanProperty implements PropertyType {
private final String name;
public BooleanProperty(String name) {
this.name = name;
}
@Override
public NbtMap nbtMap() {
return NbtMap.builder()
.putString("name", name)
.putInt("type", 2)
.build();
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.entity.properties.type;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtType;
import java.util.List;
public class EnumProperty implements PropertyType {
private final String name;
private final List<String> values;
private final Object2IntMap<String> valueIndexMap;
public EnumProperty(String name, List<String> values) {
this.name = name;
this.values = values;
this.valueIndexMap = new Object2IntOpenHashMap<>(values.size());
for (int i = 0; i < values.size(); i++) {
valueIndexMap.put(values.get(i), i);
}
}
@Override
public NbtMap nbtMap() {
return NbtMap.builder()
.putString("name", name)
.putList("enum", NbtType.STRING, values)
.putInt("type", 3)
.build();
}
public int getIndex(String value) {
return valueIndexMap.getOrDefault(value, -1);
}
}

View File

@ -23,34 +23,28 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.item;
package org.geysermc.geyser.entity.properties.type;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import org.cloudburstmc.nbt.NbtMap;
public interface DyeableLeatherItem {
public class FloatProperty implements PropertyType {
private final String name;
private final float max;
private final float min;
static void translateNbtToBedrock(CompoundTag tag) {
CompoundTag displayTag = tag.get("display");
if (displayTag == null) {
return;
}
IntTag color = displayTag.remove("color");
if (color != null) {
tag.put(new IntTag("customColor", color.getValue()));
}
public FloatProperty(String name, float min, float max) {
this.name = name;
this.max = max;
this.min = min;
}
static void translateNbtToJava(CompoundTag tag) {
IntTag color = tag.get("customColor");
if (color == null) {
return;
}
CompoundTag displayTag = tag.get("display");
if (displayTag == null) {
displayTag = new CompoundTag("display");
}
displayTag.put(color);
tag.remove("customColor");
@Override
public NbtMap nbtMap() {
return NbtMap.builder()
.putString("name", name)
.putFloat("max", max)
.putFloat("min", min)
.putInt("type", 1)
.build();
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.entity.properties.type;
import org.cloudburstmc.nbt.NbtMap;
public class IntProperty implements PropertyType {
private final String name;
private final int max;
private final int min;
public IntProperty(String name, int min, int max) {
this.name = name;
this.max = max;
this.min = min;
}
@Override
public NbtMap nbtMap() {
return NbtMap.builder()
.putString("name", name)
.putInt("max", max)
.putInt("min", min)
.putInt("type", 0)
.build();
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.entity.properties.type;
import org.cloudburstmc.nbt.NbtMap;
public interface PropertyType {
NbtMap nbtMap();
}

View File

@ -25,8 +25,8 @@
package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;

Some files were not shown because too many files have changed in this diff Show More