Compare commits

..
Author SHA1 Message Date
Hut512 df52ed2f37 Make SteamEditWand event handlers public 2026-10-01 19:57:17 +02:00
Hut512 230bfca3ca Rewrite SteamEditWand without packet filters 2026-10-01 19:57:17 +02:00
Hut512 c83ec07516 Simplify NoClipCommand 2026-10-01 19:57:17 +02:00
Hut512 4682166e00 Remove netty Cursor render 2026-10-01 19:57:17 +02:00
Hut512 38bca8e875 Remove obsolete AntiNocom 2026-10-01 19:57:17 +02:00
Hut512 e1f581eac8 Make Smart Place thread safe 2026-10-01 19:57:17 +02:00
YoyoNow 2b42f0d13c Remove YAPION from BauSystem_Main module 2026-09-29 14:26:57 +02:00
YoyoNow 4306fe7bc9 Merge pull request 'Fix SteamwarUser.setLocale' (#489) from Hut512/SteamWar:fix/setlocale-typo into main
Reviewed-on: SteamWar/SteamWar#489
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-09-14 12:18:40 +02:00
YoyoNow 58df6087f0 Merge pull request 'Fix NoClip' (#491) from Hut512/SteamWar:fix/swplayer-invalid-reference into main
Reviewed-on: SteamWar/SteamWar#491
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-09-14 12:18:16 +02:00
YoyoNow b9b7869124 Merge pull request 'Fix region water level' (#494) from Hut512/SteamWar:fix/testblock-water-level into main
Reviewed-on: SteamWar/SteamWar#494
2026-09-14 12:17:30 +02:00
YoyoNow 1e85b14667 Improve SteamEditWand 2026-09-14 10:02:39 +02:00
Hut512 35c27923aa Fix region water level 2026-09-12 20:07:07 +02:00
Hut512 c3b3e3e60f Fix NoClip instant block break 2026-09-07 22:21:53 +02:00
Hut512 3aa1bf7ffd Fix scoreboard cleanup 2026-09-07 21:15:01 +02:00
Hut512 5a02a58437 Fix SteamwarUser.setLocale 2026-09-07 15:24:32 +02:00
YoyoNow 6209b86149 Fix TeamCommand.info 2026-09-07 14:03:06 +02:00
YoyoNow 6f1403df9c Cleanup SWPlayer 2026-09-05 14:52:59 +02:00
YoyoNow 0113788da2 Fix SteamEditWand 2026-09-05 09:50:47 +02:00
YoyoNow 8a7f65e000 Merge pull request 'Fix CRIU subserver stop' (#485) from Hut512/SteamWar:main into main
Reviewed-on: SteamWar/SteamWar#485
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-09-03 21:39:15 +02:00
Hut512 8401f67b24 Fix CRIU subserver stop 2026-09-03 02:24:49 +02:00
YoyoNow 362468e756 Fix SteamEditWand 2026-09-01 11:14:30 +02:00
YoyoNow ece12df0bc Add SteamEdit 2026-09-01 10:32:56 +02:00
YoyoNow f35c07ab5b Fix EventStarter 2026-08-30 16:12:32 +02:00
YoyoNow 549f7e555c Remove NoTeamServer punishment 2026-08-30 13:04:52 +02:00
YoyoNow 859238d7fa Disable 'showtable' in EventCommand 2026-08-30 12:25:34 +02:00
YoyoNow a79b00f78f Fix EventCommand 2026-08-30 12:23:23 +02:00
YoyoNow 105eda32e6 Update CustomMap 2026-08-30 12:15:22 +02:00
YoyoNow b4487b8079 Update doc comment 2026-08-30 12:00:40 +02:00
YoyoNow ea3aad3bd2 Initial FightSWCommand 2026-08-29 12:52:09 +02:00
YoyoNow d307f890e2 Remove debug output 2026-08-26 22:35:39 +02:00
YoyoNow 97c1da1b2a Add debug output 2026-08-26 22:31:30 +02:00
YoyoNow 8a53aecfac Add debug output 2026-08-26 22:26:43 +02:00
YoyoNow 5236afba17 Add debug output 2026-08-26 22:17:33 +02:00
YoyoNow d3c8b6e1c6 Add debug output 2026-08-26 22:12:53 +02:00
YoyoNow 00ea792eee Merge pull request 'Improve AutoChecker and GameModeConfig again' (#478) from AutoChecker into main
Reviewed-on: SteamWar/SteamWar#478
2026-08-26 22:05:31 +02:00
YoyoNow 38e845b0e4 Improve AutoChecker and GameModeConfig again 2026-08-26 21:46:45 +02:00
YoyoNow c60a8caed8 Merge pull request 'Improve items for GameModeConfig' (#476) from AutoChecker into main
Reviewed-on: SteamWar/SteamWar#476
Reviewed-by: sakziea <59+sakziea@noreply.localhost>
2026-08-25 17:07:11 +02:00
YoyoNow 2e390286b2 Merge pull request 'VelocityCore - Server Links' (#477) from VelocityCore/ServerLinks into main
Reviewed-on: SteamWar/SteamWar#477
Reviewed-by: sakziea <59+sakziea@noreply.localhost>
2026-08-25 16:57:04 +02:00
YoyoNow 6476125a3f Add ServerLinks to login 2026-08-24 20:22:48 +02:00
YoyoNow 311437d451 Add ServerLinks to login 2026-08-24 20:22:02 +02:00
YoyoNow 5a52771ec1 Improve items for GameModeConfig 2026-08-24 20:05:58 +02:00
YoyoNow 72b70a62e1 Fix CustomItem 2026-08-19 12:05:09 +02:00
YoyoNow 8575dcac33 Add WindCharge option to LaunchScript 2026-08-19 11:06:34 +02:00
YoyoNow cb744f6aa0 Add AntiPlayerCollision 2026-08-18 10:48:16 +02:00
YoyoNow 227f63c9ff Add AntiPlayerCollision 2026-08-18 10:39:58 +02:00
YoyoNow 397b10c319 Merge pull request 'Velocity core /event command' (#465) from VelocityCore/EventCommand into main
Reviewed-on: SteamWar/SteamWar#465
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-08-14 07:53:43 +02:00
YoyoNow 32569f19d9 Closes: #468 2026-08-13 10:09:13 +02:00
YoyoNow f19e4158e7 Fix Check 2026-08-13 10:01:55 +02:00
YoyoNow 6c46b5b2a2 Fix Check 2026-08-13 09:51:56 +02:00
YoyoNow 58516448d9 Merge pull request 'Add deletion of PlayerData on /bau delmember' (#454) from VelocityCore/DeleteMemberOfBauDeletingPlayerData into main
Reviewed-on: SteamWar/SteamWar#454
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2026-08-13 09:50:14 +02:00
YoyoNow 683236bb59 Fix TexturePackSystem for newer versions 2026-08-13 09:39:42 +02:00
YoyoNow 8dc101639d Add alias of /tick rate to /tick rate 20 2026-08-11 21:48:28 +02:00
YoyoNow 792620804a Improve TPSSystem 2026-08-11 21:46:38 +02:00
YoyoNow 4aa7c99a6e Fix Check and CheckCommand permission 2026-08-11 21:05:36 +02:00
Sakziea cce18124f2 Remove unused translation 2026-08-05 20:30:36 +02:00
Sakziea 18ad0ad90c Add Clickevents to EventPage 2026-08-05 20:30:28 +02:00
Sakziea a58de898bb Add Clickevents to EventPage 2026-08-05 20:30:25 +02:00
Sakziea 507b7c2569 Add event showtable 2026-08-05 20:30:20 +02:00
Sakziea a18098b8f4 Fix Pull request comments 2026-08-05 20:30:15 +02:00
Sakziea 97773f292d Remove unused import 2026-08-05 20:30:09 +02:00
Sakziea 5fdfc03587 Fix /event [Team] move to /event team [Team] 2026-08-05 20:30:00 +02:00
Sakziea 493e66cc98 Add current event uses eventpage 2026-08-05 20:29:37 +02:00
Sakziea 2be3a136ae Add funktion to show next events and formatet the fights in pages 2026-08-05 20:29:28 +02:00
Sakziea c62a566447 Fix pull Request comment 2026-08-05 20:29:18 +02:00
Sakziea 0684765a76 Remove unused imports 2026-08-05 20:29:09 +02:00
Sakziea c40f29c3c1 Add Wind charge config 2026-08-05 20:28:53 +02:00
Sakziea 72dc4eee9e Fix Wind charges cross middle and interact with Blocks 2026-08-05 20:28:24 +02:00
Sakziea c7a949322e Fix Windcharge remove Water 2026-08-05 20:27:39 +02:00
Chaoscaot 9d5b7893f1 Merge pull request 'Replace Custom Observer with kotlinx StateFlows' (#444) from swui/flows into main
Reviewed-on: SteamWar/SteamWar#444
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-07-26 10:22:42 +02:00
Chaoscaot ae898e14c2 Merge branch 'main' into swui/flows 2026-07-26 10:20:35 +02:00
Chaoscaot 726f73cc45 Merge branch 'main' into VelocityCore/DeleteMemberOfBauDeletingPlayerData 2026-07-26 03:04:45 +02:00
YoyoNow badff1c70f Merge pull request 'Fix Wind Charges remove Water, interact with Blocks and cross middle' (#455) from FightSystem/Windcharge into main
Reviewed-on: SteamWar/SteamWar#455
2026-07-18 15:00:18 +02:00
Sakziea 1d2ac4f705 Fix pull Request comment 2026-07-18 14:58:24 +02:00
Sakziea b48c75ef74 Remove unused imports 2026-07-18 12:49:23 +02:00
Sakziea 8b6ea12a08 Add Wind charge config 2026-07-18 12:43:46 +02:00
Sakziea 801678ddd3 Fix Wind charges cross middle and interact with Blocks 2026-07-18 12:43:23 +02:00
Sakziea e5cc874834 Fix Windcharge remove Water 2026-07-18 11:08:51 +02:00
YoyoNow 95119fc93f Fix FightServer.kt and VelocityServer.kt @get:Optional annotations complaining about primitive types of field 2026-07-17 16:40:38 +02:00
YoyoNow d84ca3547a Merge pull request 'Fix AutoChecker only counts filled Dispenser' (#453) from SchematicSystem/AutoPrüfer into main
Reviewed-on: SteamWar/SteamWar#453
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-07-17 14:27:01 +02:00
Sakziea 25ab79abc0 Fix PullRequest comment 2026-07-17 14:23:55 +02:00
Sakziea 582d6a62e3 Fix PullRequest comment 2026-07-17 14:22:36 +02:00
Chaoscaot 5eec792aff Merge branch 'main' of https://git.steamwar.de/SteamWar/SteamWar 2026-07-17 12:05:00 +02:00
Chaoscaot e0f26c3e8d Delete event relations when fights and groups are removed 2026-07-17 12:03:15 +02:00
YoyoNow a59e9b4343 Closes: #323 2026-07-17 10:06:48 +02:00
YoyoNow 64e627514d Add deletion of PlayerData on /bau delmember 2026-07-17 09:47:40 +02:00
Sakziea a8c68864d8 Fix AutoChecker only counts filled Dispenser 2026-07-16 17:07:37 +02:00
YoyoNow c0668723d7 Merge pull request 'Add GameModeConfig.KitsConfig.MaxBlastResistance for disabling all Blocks with more than 9.0 blast resistance' (#451) from UpdateGameModeConfig into main
Reviewed-on: SteamWar/SteamWar#451
2026-07-16 16:42:16 +02:00
YoyoNow 5bc2daf37a Add GameModeConfig.KitsConfig.MaxBlastResistance for disabling all Blocks with more than 9.0 blast resistance 2026-07-16 16:39:56 +02:00
YoyoNow e6d39da36b Add GameModeConfig.KitsConfig.MaxBlastResistance for disabling all Blocks with more than 9.0 blast resistance 2026-07-16 16:39:27 +02:00
YoyoNow 390d388692 Closes: #442 2026-07-16 16:32:35 +02:00
YoyoNow 9a60ac228c Merge pull request 'Making /trace isolate toggle the tnt on and off to normal Trace show' (#445) from BauSystem/Trace into main
Reviewed-on: SteamWar/SteamWar#445
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-07-16 16:24:16 +02:00
Sakziea dab443b989 Fix pullRequest comments 2026-07-16 16:23:52 +02:00
Sakziea bf3c65c630 Fix when no tnt isolated show normal 2026-07-14 19:47:08 +02:00
Sakziea b488c4ed7c Fix /trace isolate {id} showing tnt from other traces 2026-07-14 19:29:12 +02:00
Sakziea 2e795aaf8e Fix /trace 1 still showes all the tnt of that trace 2026-07-06 17:49:09 +02:00
Sakziea 7a601235f5 Fix Trace isolating the same Tnt shows all Tnts 2026-07-06 17:27:47 +02:00
Chaoscaot 3a247de43d Replace Custom Observer with kotlinx StateFlows
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-07-06 01:13:05 +02:00
Chaoscaot 62e855ec1b Fix Reset
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-07-05 14:39:20 +02:00
YoyoNow 9e7311934f Convert buildSrc from gradle/groovy to kts/kotlin 2026-07-05 14:14:04 +02:00
Chaoscaot 6fd3e6f690 Fix Reset
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-07-05 13:51:45 +02:00
Chaoscaot 7acd51fd61 Fix Reset
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-07-05 13:45:59 +02:00
Chaoscaot b278ca15f3 Fix Devserver
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-07-05 13:41:01 +02:00
Chaoscaot 029249c2b0 Fix Database Reset
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-07-05 13:23:52 +02:00
Chaoscaot 1ceb10439b Fix Database Reset
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-07-05 13:18:38 +02:00
Chaoscaot a5dfe6ab7e Fix Database Reset
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-07-05 12:59:29 +02:00
YoyoNow c8a5f567eb Add StreamingCommand for UserPerm.PREFIX_YOUTUBER 2026-07-05 11:54:07 +02:00
Chaoscaot 47fb4da85b Fix Inventory
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-20 21:43:05 +02:00
YoyoNow 0eef22aab0 Fix ServerStarter.tempWorld 2026-06-20 17:42:16 +02:00
D4rkr34lm 013d18614f Fix player nametags showing 2026-06-20 17:20:25 +02:00
YoyoNow c7c1ab64e0 Fix BoatRace 2026-06-18 15:30:07 +02:00
Chaoscaot b68a27fbb6 Merge pull request 'SWUI V2' (#418) from swui-v2 into main
Reviewed-on: SteamWar/SteamWar#418
Reviewed-by: D4rkr34lm <dark@steamwar.de>
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-06-18 14:10:59 +02:00
Chaoscaot 7038dc2aa5 Merge branch 'main' into swui-v2 2026-06-17 21:49:52 +02:00
Chaoscaot 7af586ec16 Fix Observer
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-17 21:48:27 +02:00
YoyoNow b04e99ce54 Merge pull request 'fix(FightSystem): prevent driedGhasts from spawing & plants from growing' (#434) from FightSystem/fix-dried-ghast-spawning into main
Reviewed-on: SteamWar/SteamWar#434
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-06-17 21:24:00 +02:00
YoyoNow 524702fbb9 Merge pull request 'Implement proper stop and not sleep on BauCommand.stop' (#435) from VelocityCore/BauCommand#stop into main
Reviewed-on: SteamWar/SteamWar#435
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-06-17 21:23:33 +02:00
YoyoNow e1df793e9f Merge pull request 'VelocityCore /check command' (#430) from VelocityCore/CheckCommand into main
Reviewed-on: SteamWar/SteamWar#430
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2026-06-17 21:17:54 +02:00
YoyoNow 806a1affea Merge pull request 'fix(BauSystem): advanced flag entities being misplaced' (#433) from wip/fix-advanced-flag into main
Reviewed-on: SteamWar/SteamWar#433
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-06-17 21:14:27 +02:00
YoyoNow c538cdbbd2 Implement proper stop and not sleep on BauCommand.stop 2026-06-17 21:13:57 +02:00
YoyoNow 48846a698f Fix TestblockCommand tab complete order 2026-06-17 21:04:34 +02:00
D4rkr34lm b771c95b99 Fix advanced flag and add lsp files to gitignore 2026-06-17 20:28:33 +02:00
YoyoNow d0aada2b00 Deactivate AdvancementsManager 2026-06-17 16:16:39 +02:00
YoyoNow 6522fc77f2 Improve color gradient 2026-06-17 11:14:53 +02:00
YoyoNow 27e01a56ae Improve CheckCommand 2026-06-17 11:09:32 +02:00
YoyoNow ed89770c0f Fix ConcurrentModificationException 2026-06-17 11:04:07 +02:00
YoyoNow 3ec3c90f04 Fix message on join 2026-06-17 10:57:04 +02:00
YoyoNow 73d4ed26c8 Implement CheckCommand for Checkers not having Check perm 2026-06-17 10:52:32 +02:00
YoyoNow 256bcfb1bf Update CheckCommand 2026-06-17 10:52:32 +02:00
YoyoNow 28ce288815 Merge pull request 'Velocity core/ advancements' (#429) from VelocityCore/Advancements into main
Reviewed-on: SteamWar/SteamWar#429
2026-06-17 10:24:21 +02:00
YoyoNow 6599dd026a Update CustomMap 2026-06-15 09:24:46 +02:00
YoyoNow 03052b677f Update CustomMap 2026-06-15 09:20:21 +02:00
YoyoNow f76a6fa79d Merge pull request 'fix(VelocityCore): server starter copping world to wrong path' (#427) from fix-event-server-naming into main
Reviewed-on: SteamWar/SteamWar#427
Reviewed-by: YoyoNow <4+yoyonow@noreply.localhost>
2026-06-14 21:47:31 +02:00
D4rkr34lm 7fb7f46ca7 Fix path issues 2026-06-14 00:58:41 +02:00
YoyoNow 1269b7f00a Fix NoClipCommand 2026-06-12 22:16:00 +02:00
YoyoNow 91a0685724 Fix Missile spawning 2026-06-12 21:41:20 +02:00
YoyoNow 72dd2ef59a Fix Missile spawning 2026-06-12 20:43:43 +02:00
YoyoNow 54a1549973 Fix Missile spawning 2026-06-12 20:30:44 +02:00
D4rkr34lm fd5f5b92b4 Merge pull request 'fix(SchematicSystem): mixup of coordinates in size check' (#426) from fix-autochecker into main
Reviewed-on: SteamWar/SteamWar#426
2026-06-12 19:59:48 +02:00
D4rkr34lm 60dc5e4442 Fix formatiing 2026-06-12 19:56:12 +02:00
D4rkr34lm 4cdaf759af Fix mixup of dimensions in autochecker 2026-06-12 19:54:06 +02:00
YoyoNow 6e919d9148 Fix AdvancementsManager
(cherry picked from commit c221989f9c)
2026-06-12 16:29:28 +02:00
YoyoNow a20ec8c93d Add comment in Items 2026-06-12 15:36:21 +02:00
YoyoNow 18b65a2984 Remove item.json
Improve AdvancementsManager
2026-06-12 15:35:13 +02:00
YoyoNow 7adb1e8b4a Remove automatic update for now 2026-06-12 14:22:44 +02:00
YoyoNow b88e592e79 Move item.json 2026-06-12 14:21:45 +02:00
YoyoNow 914134600e Improve Advancements performance 2026-06-12 14:19:47 +02:00
Chaoscaot 70c8678c60 Refactor rendering system: Introduced RenderObject abstraction, replaced RenderBoundary with ObserverListener, and updated slot calculation for inventory types.
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-12 12:00:04 +02:00
Chaoscaot 997292d58e Merge branch 'main' into swui-v2 2026-06-10 21:43:25 +02:00
Chaoscaot 8050f51424 Cleanup for Merge
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-10 21:43:02 +02:00
Chaoscaot a6681ffaf6 Add Top-Level State and more Utils
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-10 16:07:37 +02:00
Chaoscaot a23530074c Merge remote-tracking branch 'origin/swui-v2' into swui-v2 2026-06-10 09:28:24 +02:00
Chaoscaot e88a4c624b Merge branch 'main' into swui-v2 2026-06-10 09:28:16 +02:00
Chaoscaot 105beaf7a6 Merge branch 'main' into swui-v2 2026-06-10 09:27:59 +02:00
Chaoscaot 5f7ab3cd7b Updates
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-10 09:26:04 +02:00
Chaoscaot 5ce69e6b7a SWUI V2
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-10 00:36:18 +02:00
YoyoNow 9e43d5cd1b Add more Advancements in progression 2026-06-01 14:03:50 +02:00
YoyoNow 2ad9ae3c7f Fix Advancements Placements 2026-05-31 23:04:08 +02:00
YoyoNow 847eb3fe5d Add even more Advancements 2026-05-31 22:31:02 +02:00
YoyoNow 8fa9f7b0d3 Add even more Advancements 2026-05-31 21:56:38 +02:00
YoyoNow 559cae2b6d Add playtime update 2026-05-31 20:20:14 +02:00
YoyoNow 5dbf3638d0 Add Advancement
Add Advancements
Add AdvancementsManager
Add SelectAdvancementTabPacket
Add item.json
Update ConnectionListener
2026-05-31 18:09:44 +02:00
191 changed files with 6703 additions and 2339 deletions
+5
View File
@@ -15,6 +15,11 @@ bin/
.vscode .vscode
.settings .settings
# Language Server
**/.project
**/.factorypath
**/.classpath
# Other # Other
lib lib
/WebsiteBackend/data /WebsiteBackend/data
+2 -1
View File
@@ -35,6 +35,7 @@ dependencies {
compileOnly(libs.classindex) compileOnly(libs.classindex)
annotationProcessor(libs.classindex) annotationProcessor(libs.classindex)
compileOnly(project(":SpigotCore", "default")) compileOnly(project(":SpigotCore", "default"))
compileOnly(project(":KotlinCore", "default"))
compileOnly(libs.axiom) compileOnly(libs.axiom)
compileOnly(libs.authlib) compileOnly(libs.authlib)
@@ -46,7 +47,7 @@ dependencies {
compileOnly(libs.netty) compileOnly(libs.netty)
implementation(libs.luaj) implementation(libs.luaj)
implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar")) // implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar"))
} }
widener { widener {
@@ -20,8 +20,7 @@
package de.steamwar.bausystem; package de.steamwar.bausystem;
import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.configplayer.Config; import de.steamwar.bausystem.config.ConfigStore;
import de.steamwar.bausystem.configplayer.ConfigConverter;
import de.steamwar.bausystem.features.gui.BauGUI; import de.steamwar.bausystem.features.gui.BauGUI;
import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin; import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin;
import de.steamwar.bausystem.features.script.lua.libs.LuaLib; import de.steamwar.bausystem.features.script.lua.libs.LuaLib;
@@ -32,16 +31,17 @@ import de.steamwar.bausystem.features.tracer.TraceManager;
import de.steamwar.bausystem.features.tracer.TraceRecorder; import de.steamwar.bausystem.features.tracer.TraceRecorder;
import de.steamwar.bausystem.features.world.BauScoreboard; import de.steamwar.bausystem.features.world.BauScoreboard;
import de.steamwar.bausystem.linkage.BauGuiItem; import de.steamwar.bausystem.linkage.BauGuiItem;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionSystem; import de.steamwar.bausystem.region.RegionSystem;
import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.bausystem.utils.TickManager; import de.steamwar.bausystem.utils.TickManager;
import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.command.AbstractValidator; import de.steamwar.command.AbstractValidator;
import de.steamwar.command.SWCommandUtils; import de.steamwar.command.SWCommandUtils;
import de.steamwar.core.CRIUSleepEvent; import de.steamwar.core.CRIUSleepEvent;
import de.steamwar.core.Core; import de.steamwar.core.Core;
import de.steamwar.core.WorldEditRendererCUIEditor;
import de.steamwar.core.WorldIdentifier; import de.steamwar.core.WorldIdentifier;
import de.steamwar.core.worldedit.SteamEdit;
import de.steamwar.core.worldedit.WorldEditRendererCUIEditor;
import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.AbstractLinker;
import de.steamwar.linkage.SpigotLinker; import de.steamwar.linkage.SpigotLinker;
import de.steamwar.message.Message; import de.steamwar.message.Message;
@@ -49,6 +49,7 @@ import de.steamwar.providers.BauServerInfo;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameRule; import org.bukkit.GameRule;
import org.bukkit.Location;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@@ -109,9 +110,6 @@ public class BauSystem extends JavaPlugin implements Listener {
if (any instanceof PanzernAlgorithm) { if (any instanceof PanzernAlgorithm) {
Panzern.add((PanzernAlgorithm) any); Panzern.add((PanzernAlgorithm) any);
} }
if (any instanceof ConfigConverter) {
Config.addConfigConverter((ConfigConverter) any);
}
if (any instanceof BoundingBoxLoader) { if (any instanceof BoundingBoxLoader) {
((BoundingBoxLoader) any).load(); ((BoundingBoxLoader) any).load();
} }
@@ -129,7 +127,19 @@ public class BauSystem extends JavaPlugin implements Listener {
TraceManager.instance.init(); TraceManager.instance.init();
TraceRecorder.instance.init(); TraceRecorder.instance.init();
new WorldEditRendererCUIEditor(); SteamEdit.init();
SteamEdit.ADJACENT_FILTERS = location -> {
Region region = Region.getRegion(location);
Region.Area area;
if (region.getBuildArea().inRegion(location, true)) {
area = region.getBuildArea();
} else if (region.getTestblockArea().inRegion(location, true)) {
area = region.getTestblockArea();
} else {
return SteamEdit.AdjacentFilter.ALWAYS;
}
return (x, y, z) -> area.inRegion(new Location(null, x, y, z), true);
};
Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false); Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false);
@@ -145,9 +155,8 @@ public class BauSystem extends JavaPlugin implements Listener {
@Override @Override
public void onDisable() { public void onDisable() {
linker.unlink(); linker.unlink();
WorldData.write();
RegionSystem.INSTANCE.save(); RegionSystem.INSTANCE.save();
Config.getInstance().saveAll(); ConfigStore.saveAll();
} }
private <T extends CommandSender> AbstractValidator<T, ?> validator(Permission permission) { private <T extends CommandSender> AbstractValidator<T, ?> validator(Permission permission) {
@@ -0,0 +1,47 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config;
/**
* Converts a config of one version into the next version, e.g. ConfigV1 to
* ConfigV2. The converter for the second newest version converts into the
* current version. Converters are registered in the {@link ConfigStore}
* of the config.
*
* @param <F> the config version to convert from
* @param <T> the config version to convert to
*/
public interface ConfigConverter<F extends VersionedConfig, T extends VersionedConfig> {
/**
* @return the version this converter converts from
*/
int version();
/**
* Converts the config to the next version. If anything goes wrong do not
* silently exit this method, throw an Exception. The {@link ConfigStore}
* will deal with it.
*
* @param config the config to convert
* @return the converted config
*/
T convert(F config);
}
@@ -0,0 +1,364 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import de.steamwar.sql.UserConfig;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Level;
/**
* Loads, converts and saves a versioned config stored as JSON. Older versions
* are converted with the registered {@link ConfigConverter}s, legacy formats
* that are not JSON can be read with {@link #legacy(Predicate, Function)}.
* <p>
* A store can be bound to where the config is stored:
* <ul>
* <li>{@link #userConfig(String)}: one config per player in the {@link UserConfig}, use {@link #get(Player)}</li>
* <li>{@link #file(File)}: one config in a file, use {@link #get()}</li>
* <li>unbound: use {@link #load(File)} and {@link #save(File, VersionedConfig)}</li>
* </ul>
*
* @param <T> the current config version
*/
public class ConfigStore<T extends VersionedConfig> {
private static final List<ConfigStore<?>> STORES = new ArrayList<>();
private final Class<T> type;
private final int version;
private final Supplier<T> defaultConfig;
private final Map<Integer, Class<? extends VersionedConfig>> jsonVersions = new HashMap<>();
private final Map<Integer, ConfigConverter<?, ?>> converters = new HashMap<>();
private final List<LegacyFormat> legacyFormats = new ArrayList<>();
private Gson gson = new Gson();
private Integer unversionedVersion = null;
private String userConfigKey = null;
private Predicate<T> removeIf = config -> false;
private final Map<UUID, T> playerConfigs = new ConcurrentHashMap<>();
private File file = null;
private File legacyFile = null;
private Function<String, ? extends VersionedConfig> legacyFileReader = null;
private T fileConfig = null;
private record LegacyFormat(Predicate<String> matches, Function<String, ? extends VersionedConfig> reader) {
}
public ConfigStore(Class<T> type, int version, Supplier<T> defaultConfig) {
this.type = type;
this.version = version;
this.defaultConfig = defaultConfig;
jsonVersions.put(version, type);
STORES.add(this);
}
public ConfigStore<T> gson(Gson gson) {
this.gson = gson;
return this;
}
/**
* Registers an older config version that is stored as JSON.
*/
public ConfigStore<T> jsonVersion(int version, Class<? extends VersionedConfig> type) {
jsonVersions.put(version, type);
return this;
}
public ConfigStore<T> converter(ConfigConverter<?, ?> converter) {
if (converters.putIfAbsent(converter.version(), converter) != null) {
throw new IllegalStateException("Duplicate converter for version " + converter.version() + " of " + type.getSimpleName());
}
return this;
}
/**
* Registers a legacy format that is not JSON, stored strings matching the predicate are read by the reader.
*/
public ConfigStore<T> legacy(Predicate<String> matches, Function<String, ? extends VersionedConfig> reader) {
legacyFormats.add(new LegacyFormat(matches, reader));
return this;
}
/**
* The version of stored JSON without a version field.
*/
public ConfigStore<T> unversioned(int version) {
this.unversionedVersion = version;
return this;
}
/**
* Binds this store to the {@link UserConfig} with the given key, one config per player.
*/
public ConfigStore<T> userConfig(String key) {
this.userConfigKey = key;
return this;
}
/**
* Removes the config from the {@link UserConfig} instead of saving it, if the predicate matches.
*/
public ConfigStore<T> removeIf(Predicate<T> removeIf) {
this.removeIf = removeIf;
return this;
}
/**
* Binds this store to a single file.
*/
public ConfigStore<T> file(File file) {
this.file = file;
return this;
}
/**
* Legacy file to read, if the file of this store does not exist yet.
*/
public ConfigStore<T> legacyFile(File legacyFile, Function<String, ? extends VersionedConfig> reader) {
this.legacyFile = legacyFile;
this.legacyFileReader = reader;
return this;
}
/**
* Reads a stored config and converts it to the current version.
*
* @throws RuntimeException if the config could not be read or converted
*/
public T read(String stored) {
for (LegacyFormat legacyFormat : legacyFormats) {
if (legacyFormat.matches().test(stored)) {
return update(legacyFormat.reader().apply(stored));
}
}
JsonObject json = JsonParser.parseString(stored).getAsJsonObject();
int storedVersion;
if (json.has("version")) {
storedVersion = json.get("version").getAsInt();
} else if (unversionedVersion != null) {
storedVersion = unversionedVersion;
json.addProperty("version", storedVersion);
} else {
throw new IllegalStateException("Missing version of " + type.getSimpleName());
}
Class<? extends VersionedConfig> storedType = jsonVersions.get(storedVersion);
if (storedType == null) {
throw new IllegalStateException("Unknown version " + storedVersion + " of " + type.getSimpleName());
}
return update(gson.fromJson(json, storedType));
}
public String write(T config) {
return gson.toJson(config);
}
/**
* Converts a config to the current version.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public T update(VersionedConfig config) {
while (config.getVersion() < version) {
ConfigConverter converter = converters.get(config.getVersion());
if (converter == null) {
throw new IllegalStateException("No converter found for version " + config.getVersion() + " of " + type.getSimpleName());
}
VersionedConfig converted = converter.convert(config);
if (converted.getVersion() <= config.getVersion()) {
throw new IllegalStateException("Version did not increase after conversion of version " + config.getVersion() + " of " + type.getSimpleName());
}
config = converted;
}
if (!type.isInstance(config)) {
throw new IllegalStateException("Version " + config.getVersion() + " is not the current version of " + type.getSimpleName());
}
return type.cast(config);
}
/**
* @return the config stored in the file, or the default config if the file is missing or empty
* @throws RuntimeException if the config could not be read or converted
*/
public T load(File file) throws IOException {
if (file.length() == 0) {
return defaultConfig.get();
}
return read(Files.readString(file.toPath(), StandardCharsets.UTF_8));
}
public void save(File file, T config) throws IOException {
Files.writeString(file.toPath(), write(config), StandardCharsets.UTF_8);
}
/**
* Get the config of a player, loads it from the {@link UserConfig} and converts it if necessary.
*/
public T get(Player player) {
requireUserConfig();
return playerConfigs.computeIfAbsent(player.getUniqueId(), this::loadPlayer);
}
/**
* Save the config of a player, this does not unload the config.
*/
public void save(Player player) {
requireUserConfig();
T config = playerConfigs.get(player.getUniqueId());
if (config != null) {
savePlayer(player.getUniqueId(), config);
}
}
/**
* Save and unload the config of a player.
*/
public void unload(Player player) {
if (userConfigKey == null) return;
T config = playerConfigs.remove(player.getUniqueId());
if (config != null) {
savePlayer(player.getUniqueId(), config);
}
}
private T loadPlayer(UUID uuid) {
String stored = UserConfig.getConfig(uuid, userConfigKey);
if (stored == null) {
return defaultConfig.get();
}
try {
return read(stored);
} catch (Exception e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not load " + type.getSimpleName() + " of " + uuid, e);
return defaultConfig.get();
}
}
private void savePlayer(UUID uuid, T config) {
if (removeIf.test(config)) {
UserConfig.removePlayerConfig(uuid, userConfigKey);
} else {
UserConfig.updatePlayerConfig(uuid, userConfigKey, write(config));
}
}
private void requireUserConfig() {
if (userConfigKey == null) {
throw new IllegalStateException(type.getSimpleName() + " is not stored in the UserConfig");
}
}
/**
* Get the config of the file of this store, loads it and converts it if necessary.
*/
public T get() {
requireFile();
if (fileConfig == null) {
fileConfig = loadFile();
save();
}
return fileConfig;
}
/**
* Save the config to the file of this store, if it was loaded.
*/
public void save() {
requireFile();
if (fileConfig == null) return;
try {
save(file, fileConfig);
} catch (IOException e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not write " + file, e);
}
}
private T loadFile() {
try {
if (file.length() != 0) {
return load(file);
}
if (legacyFile != null && legacyFile.length() != 0) {
return update(legacyFileReader.apply(Files.readString(legacyFile.toPath(), StandardCharsets.UTF_8)));
}
return defaultConfig.get();
} catch (Exception e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not load " + file + ", using default " + type.getSimpleName(), e);
backupBrokenFile();
return defaultConfig.get();
}
}
private void backupBrokenFile() {
if (file.length() == 0) return;
try {
Files.copy(file.toPath(), new File(file.getParentFile(), file.getName() + ".broken").toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not backup broken " + file, e);
}
}
private void requireFile() {
if (file == null) {
throw new IllegalStateException(type.getSimpleName() + " is not bound to a file");
}
}
static void unloadAll(Player player) {
STORES.forEach(store -> store.unload(player));
}
/**
* Saves every loaded config and unloads the configs of the players.
*/
public static void saveAll() {
for (ConfigStore<?> store : STORES) {
if (store.userConfigKey != null) {
store.playerConfigs.forEach((uuid, config) -> saveUnchecked(store, uuid, config));
store.playerConfigs.clear();
}
if (store.file != null) {
store.save();
}
}
}
@SuppressWarnings("unchecked")
private static <T extends VersionedConfig> void saveUnchecked(ConfigStore<T> store, UUID uuid, Object config) {
store.savePlayer(uuid, (T) config);
}
}
@@ -0,0 +1,45 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config;
import de.steamwar.core.CRIUSleepEvent;
import de.steamwar.linkage.Linked;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
/**
* Saves and unloads the configs of the {@link ConfigStore}s bound to the UserConfig
* when a player quits, configs are loaded on first use.
*/
@Linked
public class ConfigStoreListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) {
ConfigStore.unloadAll(event.getPlayer());
}
@EventHandler
public void onCRIUSleep(CRIUSleepEvent event) {
ConfigStore.saveAll();
}
}
@@ -0,0 +1,29 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config;
/**
* Common interface of every version of a config stored by a {@link ConfigStore},
* the current version (e.g. Config) and every older version (ConfigV1, ConfigV2, ...).
*/
public interface VersionedConfig {
int getVersion();
}
@@ -0,0 +1,95 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config.legacy;
import lombok.experimental.UtilityClass;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Deserializes {@link ItemStack}s that were serialized by YAPION, read with {@link YapionReader}.
*/
@UtilityClass
@Deprecated
public class YapionItemDeserializer {
private static final String TYPE_IDENTIFIER = "@type";
public ItemStack deserializeItem(Object yapion) {
if (yapion == null) return null;
Object result = deserialize(yapion);
if (result instanceof ItemStack itemStack) return itemStack;
throw new IllegalArgumentException("Not an ItemStack: " + result);
}
private Object deserialize(Object yapion) {
if (yapion instanceof List<?> list) {
List<Object> result = new ArrayList<>();
list.forEach(o -> result.add(deserialize(o)));
return result;
}
if (yapion instanceof YapionReader.YapionMap map) {
Map<Object, Object> result = new LinkedHashMap<>();
map.entries().forEach((key, value) -> result.put(deserialize(key), deserialize(value)));
return result;
}
if (yapion instanceof Map<?, ?> object) {
return deserializeObject(object);
}
return yapion;
}
private Object deserializeObject(Map<?, ?> object) {
Map<String, Object> data = new LinkedHashMap<>();
object.forEach((key, value) -> {
if (!TYPE_IDENTIFIER.equals(key)) data.put((String) key, deserialize(value));
});
if (!(object.get(TYPE_IDENTIFIER) instanceof String type)) {
return data;
}
Class<?> javaType = javaUtilClass(type);
if (javaType != null && Collection.class.isAssignableFrom(javaType)) {
return data.getOrDefault("values", new ArrayList<>());
}
if (javaType != null && Map.class.isAssignableFrom(javaType)) {
return data.containsKey("values") ? data.get("values") : data;
}
data.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, type.equals("org.bukkit.inventory.meta.ItemMeta") ? "ItemMeta" : type);
return ConfigurationSerialization.deserializeObject(data);
}
private Class<?> javaUtilClass(String type) {
if (!type.startsWith("java.util.")) return null;
try {
return Class.forName(type);
} catch (ClassNotFoundException e) {
return null;
}
}
}
@@ -0,0 +1,323 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config.legacy;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Minimal reader for the legacy YAPION format, only used to migrate old data.
* <p>
* The result is built from plain java objects:
* <ul>
* <li>YAPION objects {@code {key(value)}} become a {@link LinkedHashMap} of String to value</li>
* <li>YAPION arrays {@code [a,b]} become a {@link List}</li>
* <li>YAPION maps {@code <(a):(b)>} become a {@link YapionMap}</li>
* <li>YAPION values {@code (value)} become null, Boolean, Number or String</li>
* </ul>
*/
@Deprecated
public class YapionReader {
private static final Pattern INTEGER = Pattern.compile("[-+]?\\d+");
private static final Pattern DECIMAL = Pattern.compile("[-+]?(\\d+\\.\\d*|\\.\\d+|\\d+)([eE][-+]?\\d+)?");
public record YapionMap(Map<Object, Object> entries) {
}
public static Map<String, Object> parse(String yapion) {
YapionReader reader = new YapionReader(yapion);
reader.skipWhitespace();
Map<String, Object> result = reader.readObject();
reader.skipWhitespace();
if (reader.index != yapion.length()) {
throw reader.error("Unexpected trailing content");
}
return result;
}
public static double getDouble(Map<?, ?> object, String key, double defaultValue) {
return object.get(key) instanceof Number value ? value.doubleValue() : defaultValue;
}
public static int getInt(Map<?, ?> object, String key, int defaultValue) {
return object.get(key) instanceof Number value ? value.intValue() : defaultValue;
}
public static boolean getBoolean(Map<?, ?> object, String key, boolean defaultValue) {
return object.get(key) instanceof Boolean value ? value : defaultValue;
}
public static String getString(Map<?, ?> object, String key, String defaultValue) {
return object.get(key) instanceof String value ? value : defaultValue;
}
/**
* @return the entries of the array that are YAPION objects
*/
public static List<Map<?, ?>> getObjects(Map<?, ?> object, String key) {
List<Map<?, ?>> objects = new ArrayList<>();
if (object.get(key) instanceof List<?> array) {
array.forEach(o -> {
if (o instanceof Map<?, ?> map) objects.add(map);
});
}
return objects;
}
private final String input;
private int index = 0;
private YapionReader(String input) {
this.input = input;
}
private Object readAny() {
return switch (peek()) {
case '{' -> readObject();
case '[' -> readArray();
case '<' -> readMap();
case '(' -> readValue();
default -> throw error("Expected '{', '[', '<' or '('");
};
}
private Map<String, Object> readObject() {
expect('{');
Map<String, Object> object = new LinkedHashMap<>();
while (true) {
skipWhitespace();
if (peek() == '}') {
index++;
return object;
}
StringBuilder key = new StringBuilder();
while (true) {
char c = peek();
if (c == '(' || c == '{' || c == '[' || c == '<') break;
if (c == '}') throw error("Key without value");
key.append(readChar());
}
object.put(key.toString(), readAny());
}
}
private List<Object> readArray() {
expect('[');
List<Object> array = new ArrayList<>();
skipWhitespace();
if (peek() == ']') {
index++;
return array;
}
while (true) {
int start = index;
skipWhitespace();
char c = peek();
if (c == '{' || c == '[' || c == '<' || c == '(') {
array.add(readAny());
skipWhitespace();
} else {
// Whitespace is part of plain values
index = start;
StringBuilder value = new StringBuilder();
while (peek() != ',' && peek() != ']') {
value.append(readChar());
}
array.add(typed(value.toString()));
}
char separator = input.charAt(index++);
if (separator == ']') return array;
if (separator != ',') throw error("Expected ',' or ']'");
}
}
private YapionMap readMap() {
expect('<');
Map<Object, Object> map = new LinkedHashMap<>();
while (true) {
skipWhitespace();
if (peek() == '>') {
index++;
return new YapionMap(map);
}
Object key = readAny();
skipWhitespace();
expect(':');
skipWhitespace();
map.put(key, readAny());
}
}
/**
* YAPION does not escape parentheses in values, balanced parentheses are part of the value.
* A backslash only escapes a backslash, a unicode sequence or a control character, otherwise it is kept.
*/
private Object readValue() {
expect('(');
StringBuilder value = new StringBuilder();
int depth = 0;
while (true) {
char c = peek();
if (c == '\\') {
value.append(readValueEscape());
continue;
}
index++;
if (c == '(') {
depth++;
} else if (c == ')') {
if (depth == 0) break;
depth--;
}
value.append(c);
}
return typed(value.toString());
}
private String readValueEscape() {
index++;
if (index >= input.length()) return "\\";
char escaped = input.charAt(index);
switch (escaped) {
case '\\', '(', ')':
index++;
return String.valueOf(escaped);
case 'n':
index++;
return "\n";
case 't':
index++;
return "\t";
case 'r':
index++;
return "\r";
case 'u':
if (index + 5 <= input.length() && input.substring(index + 1, index + 5).matches("[0-9a-fA-F]{4}")) {
char unicode = (char) Integer.parseInt(input.substring(index + 1, index + 5), 16);
index += 5;
return String.valueOf(unicode);
}
return "\\";
default:
return "\\";
}
}
/**
* Reads one character of a key or plain array value, resolving backslash escapes.
* A backslash that does not escape anything is kept, as the old player config
* collapsed escaped backslashes into a single one.
*/
private char readChar() {
char c = input.charAt(index++);
if (c != '\\') return c;
if (index >= input.length()) throw error("Unterminated escape");
char escaped = input.charAt(index);
switch (escaped) {
case 'n':
index++;
return '\n';
case 't':
index++;
return '\t';
case 'r':
index++;
return '\r';
case 'u':
if (index + 5 <= input.length() && input.substring(index + 1, index + 5).matches("[0-9a-fA-F]{4}")) {
char unicode = (char) Integer.parseInt(input.substring(index + 1, index + 5), 16);
index += 5;
return unicode;
}
return '\\';
case '\\', ',', ' ', '(', ')', '{', '}', '[', ']', '<', '>', '"', ':', '@':
index++;
return escaped;
default:
return '\\';
}
}
private static Object typed(String value) {
if (value.equals("null")) return null;
if (value.equals("true")) return true;
if (value.equals("false")) return false;
if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
return value.substring(1, value.length() - 1);
}
try {
if (INTEGER.matcher(value).matches()) {
long l = Long.parseLong(value);
if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) return (int) l;
return l;
}
if (value.length() > 1) {
String number = value.substring(0, value.length() - 1);
switch (value.charAt(value.length() - 1)) {
case 'L':
if (INTEGER.matcher(number).matches()) return Long.parseLong(number);
break;
case 'S':
if (INTEGER.matcher(number).matches()) return Short.parseShort(number);
break;
case 'B':
if (INTEGER.matcher(number).matches()) return Byte.parseByte(number);
break;
case 'F':
if (DECIMAL.matcher(number).matches()) return Float.parseFloat(number);
break;
case 'D':
if (DECIMAL.matcher(number).matches()) return Double.parseDouble(number);
break;
default:
break;
}
}
if (DECIMAL.matcher(value).matches()) return Double.parseDouble(value);
} catch (NumberFormatException e) {
// Not a number, fall through to String
}
return value;
}
private void skipWhitespace() {
while (index < input.length() && Character.isWhitespace(input.charAt(index))) {
index++;
}
}
private char peek() {
if (index >= input.length()) throw error("Unexpected end of input");
return input.charAt(index);
}
private void expect(char c) {
if (peek() != c) throw error("Expected '" + c + "'");
index++;
}
private IllegalArgumentException error(String message) {
return new IllegalArgumentException(message + " at index " + index);
}
}
@@ -0,0 +1,63 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.config.serializer;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack;
import java.io.IOException;
import java.util.Base64;
import java.util.logging.Level;
/**
* Stores an {@link ItemStack} as Base64 of {@link ItemStack#serializeAsBytes()}, which
* contains the data version and gets upgraded on newer Minecraft versions.
* Empty items are stored as null, items that can't be read are deserialized as null.
*/
public class ItemStackTypeAdapter extends TypeAdapter<ItemStack> {
@Override
public void write(JsonWriter out, ItemStack itemStack) throws IOException {
if (itemStack == null || itemStack.isEmpty()) {
out.nullValue();
return;
}
out.value(Base64.getEncoder().encodeToString(itemStack.serializeAsBytes()));
}
@Override
public ItemStack read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
String value = in.nextString();
try {
return ItemStack.deserializeBytes(Base64.getDecoder().decode(value));
} catch (Exception e) {
Bukkit.getLogger().log(Level.WARNING, "Could not deserialize ItemStack", e);
return null;
}
}
}
@@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * This file is a part of the SteamWar software.
* *
* Copyright (C) 2025 SteamWar.de-Serverteam * Copyright (C) 2026 SteamWar.de-Serverteam
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by * it under the terms of the GNU Affero General Public License as published by
@@ -19,129 +19,77 @@
package de.steamwar.bausystem.configplayer; package de.steamwar.bausystem.configplayer;
import de.steamwar.bausystem.configplayer.serializer.ConfigurationSerializableSerializer; import com.google.gson.GsonBuilder;
import de.steamwar.linkage.Linked; import de.steamwar.bausystem.config.ConfigConverter;
import de.steamwar.sql.UserConfig; import de.steamwar.bausystem.config.ConfigStore;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.config.serializer.ItemStackTypeAdapter;
import de.steamwar.bausystem.configplayer.converter.ConfigV1ToV2Converter;
import de.steamwar.bausystem.features.hotbar.DefaultHotbar;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import lombok.Setter;
import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import yapion.hierarchy.output.StringOutput;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;
import yapion.serializing.SerializeManager;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
@Linked /**
public class Config implements Listener { * The current player config. It is stored as JSON, fields missing in the
* stored JSON keep the default value of their initializer, so adding a new
* field does not need a new version.
* <p>
* When a field gets removed, renamed or its meaning changes, copy this class
* to ConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION},
* write a {@link ConfigConverter} from the copy to this class and register it in the {@link #STORE}.
*/
@Getter
@Setter
public class Config implements VersionedConfig {
static { public static final int VERSION = 2;
SerializeManager.add(new ConfigurationSerializableSerializer());
}
@Getter public static final ConfigStore<Config> STORE = new ConfigStore<>(Config.class, VERSION, Config::new)
private static Config instance; .gson(new GsonBuilder().registerTypeHierarchyAdapter(ItemStack.class, new ItemStackTypeAdapter()).create())
.legacy(s -> s.startsWith("{@version(1)"), ConfigV1::parse)
.converter(new ConfigV1ToV2Converter())
.userConfig("bausystem");
{ @Setter(AccessLevel.NONE)
instance = this; protected int version = VERSION;
}
private final Map<UUID, YAPIONObject> playerConfigurations = new HashMap<>();
private static final Map<Integer, ConfigConverter> CONFIG_CONVERTER_MAP = new HashMap<>();
public static void addConfigConverter(ConfigConverter configConverter) {
CONFIG_CONVERTER_MAP.putIfAbsent(configConverter.version(), configConverter);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
get(event.getPlayer());
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
save(event.getPlayer());
playerConfigurations.remove(event.getPlayer().getUniqueId());
}
/** /**
* Get a PlayerConfig, optionally loads it from the DataBase and migrates it if necessary. * BauGui item id to inventory slot
*
* @param player the player from whom to get the config.
* @return the config object
*/ */
public YAPIONObject get(Player player) { protected Map<Integer, Integer> bauGui = defaultBauGui();
UUID uuid = player.getUniqueId(); protected int bauGuiSize = 45;
if (!playerConfigurations.containsKey(uuid)) {
String s = UserConfig.getConfig(uuid, "bausystem");
YAPIONObject yapionObject;
if (s == null) {
yapionObject = ConfigCreator.createDefaultConfig();
} else {
yapionObject = YAPIONParser.parse(s);
}
yapionObject = update(yapionObject);
playerConfigurations.put(uuid, yapionObject);
return yapionObject;
}
return playerConfigurations.get(uuid);
}
public void saveAll() {
playerConfigurations.forEach((uuid, yapionObject) -> {
String string = yapionObject.toYAPION(new StringOutput()).getResult().replaceAll("\\+", "\\");
UserConfig.updatePlayerConfig(uuid, "bausystem", string);
});
playerConfigurations.clear();
}
/** /**
* Save a PlayerConfig, this does not remove the key value mapping from the map. * Slots 0-8 are the hotbar, slots 9-12 the armor contents
*
* @param player the player to save the config.
*/ */
public void save(Player player) { protected ItemStack[] hotbar = DefaultHotbar.defaultHotbar();
UUID uuid = player.getUniqueId();
if (playerConfigurations.containsKey(uuid)) {
YAPIONObject yapionObject = playerConfigurations.get(uuid);
String string = yapionObject.toYAPION(new StringOutput()).getResult().replaceAll("\\\\+", "\\\\");
UserConfig.updatePlayerConfig(uuid, "bausystem", string);
}
}
private YAPIONObject update(YAPIONObject yapionObject) { protected boolean nightVision = false;
int version = yapionObject.getPlainValue("@version"); protected boolean tntClickDetails = true;
while (version < ConfigCreator.currentVersion) { protected boolean detonatorAutostart = false;
ConfigConverter configConverter = CONFIG_CONVERTER_MAP.getOrDefault(version, null); protected boolean inventoryFill = false;
if (configConverter == null) { protected boolean rayVisualizer = false;
Bukkit.getLogger().log(Level.SEVERE, "No updater found for version " + version); protected boolean smartPlace = false;
return ConfigCreator.createDefaultConfig(); protected boolean blockCounter = false;
} protected boolean depthMessage = true;
try {
configConverter.update(yapionObject); public static Map<Integer, Integer> defaultBauGui() {
} catch (Exception e) { // 0: ? | 1: ? | 2: 10 | 3: 3 | 4: 7 | 5: 17 | 6: 15 | 7: ? | 8: ?
Bukkit.getLogger().log(Level.SEVERE, e.getMessage(), e); // 9: 5 | 10: ? | 11: ? | 12: ? | 13: ? | 14: ? | 15: ? | 16: ? | 17: 16
return ConfigCreator.createDefaultConfig(); // 18: 4 | 19: ? | 20: 19 | 21: 21 | 22: 9 | 23: 0 | 24: 1 | 25: ? | 26: 11
} // 27: 6 | 28: ? | 29: ? | 30: ? | 31: ? | 32: ? | 33: ? | 34: ? | 35: 18
int newVersion = yapionObject.getPlainValue("@version"); // 36: ? | 37: 23 | 38: 20 | 39: 8 | 40: 22 | 41: 26 | 42: 12 | 43: 14 | 44: ?
if (version == newVersion) { Map<Integer, Integer> bauGui = new HashMap<>();
Bukkit.getLogger().log(Level.SEVERE, "Version Tag was the same after conversion"); bauGui.put(10, 2); bauGui.put(3, 3); bauGui.put(7, 4); bauGui.put(17, 5); bauGui.put(15, 6);
return ConfigCreator.createDefaultConfig(); bauGui.put(5, 9); bauGui.put(4, 18); bauGui.put(6, 27);
} bauGui.put(16, 17); bauGui.put(11, 26); bauGui.put(18, 35);
if (newVersion < version) { bauGui.put(19, 20); bauGui.put(21, 21); bauGui.put(9, 22); bauGui.put(0, 23); bauGui.put(1, 24);
Bukkit.getLogger().log(Level.SEVERE, "Version Tag was earlier after conversion"); bauGui.put(23, 37); bauGui.put(20, 38); bauGui.put(8, 39); bauGui.put(22, 40); bauGui.put(26, 41); bauGui.put(12, 42); bauGui.put(14, 43);
return ConfigCreator.createDefaultConfig(); return bauGui;
}
version = newVersion;
}
return yapionObject;
} }
} }
@@ -1,56 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.configplayer;
import yapion.hierarchy.types.YAPIONObject;
/**
* A new {@link ConfigConverter} should be written when you remove anything
* from the Config or modify any mayor part. When you move anything from
* any key to any other key you should write a new {@link ConfigConverter}.
* For adding any new key you should be able to get the default without
* having it in the Config. Anything you need to change you should also
* change the {@link ConfigCreator} accordingly, to produce the new Config.
*/
public interface ConfigConverter {
/**
* This describes the version this Converter can convert from. The version
* it should convert to is the version 1 above this number. But this is not
* a necessity. In the config Object as parameter given in {@link #update(YAPIONObject)}
* you should update the <b>@version</b> variable in the root object to the
* new version this converter produced.
*
* @return the version number
*/
int version();
/**
* This method should update everything needed to go from a lower config
* version to a higher. It should update the <b>@version</b> variable
* accordingly. Anything else is up the implementation. If anything goes wrong
* do not silently exit this method, throw an Exception. The updater Code will
* deal with it. Never leave the inputted object in a corrupted state.
*
* @param config the config object to update
*/
void update(YAPIONObject config);
}
@@ -1,61 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.configplayer;
import de.steamwar.bausystem.features.hotbar.DefaultHotbar;
import lombok.experimental.UtilityClass;
import yapion.hierarchy.types.YAPIONObject;
@UtilityClass
public class ConfigCreator {
public static final int currentVersion = 1;
public YAPIONObject createDefaultConfig() {
YAPIONObject yapionObject = new YAPIONObject();
// This call should never be touched
yapionObject.add("@version", currentVersion);
// Any initialising goes into here
yapionObject.add("baugui", defaultBauGui());
// Default Hotbar Gui
yapionObject.add("hotbar", DefaultHotbar.defaultHotbar());
return yapionObject;
}
private YAPIONObject defaultBauGui() {
YAPIONObject yapionObject = new YAPIONObject();
// 0: ? | 1: ? | 2: 10 | 3: 3 | 4: 7 | 5: 17 | 6: 15 | 7: ? | 8: ?
// 9: 5 | 10: ? | 11: ? | 12: ? | 13: ? | 14: ? | 15: ? | 16: ? | 17: 16
// 18: 4 | 19: ? | 20: 19 | 21: 21 | 22: 9 | 23: 0 | 24: 1 | 25: ? | 26: 11
// 27: 6 | 28: ? | 29: ? | 30: ? | 31: ? | 32: ? | 33: ? | 34: ? | 35: 18
// 36: ? | 37: 23 | 38: 20 | 39: 8 | 40: 22 | 41: 26 | 42: 12 | 43: 14 | 44: ?
yapionObject.add("10", 2).add("3", 3).add("7", 4).add("17", 5).add("15", 6);
yapionObject.add("5", 9).add("4", 18).add("6", 27);
yapionObject.add("16", 17).add("11", 26).add("18", 35);
yapionObject.add("19", 20).add("21", 21).add("9", 22).add("0", 23).add("1", 24);
yapionObject.add("23", 37).add("20", 38).add("8", 39).add("22", 40).add("26", 41).add("12", 42).add("14", 43);
yapionObject.add("size", 45);
return yapionObject;
}
}
@@ -0,0 +1,104 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.configplayer;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.config.legacy.YapionReader;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The legacy config that was stored in the YAPION format.
*/
@Getter
@Setter
public class ConfigV1 implements VersionedConfig {
public static final int VERSION = 1;
@Setter(AccessLevel.NONE)
protected int version = VERSION;
/**
* BauGui item id to inventory slot
*/
protected Map<Integer, Integer> bauGui = new HashMap<>();
protected int bauGuiSize = 45;
/**
* Minecraft version to the hotbar serialized by YAPION, read with {@link YapionReader}.
* Use {@link de.steamwar.bausystem.config.legacy.YapionItemDeserializer} to get the items.
*/
protected Map<Integer, List<?>> hotbars = new HashMap<>();
protected boolean nightVision = false;
protected boolean tntClickDetails = true;
protected boolean detonatorAutostart = false;
protected boolean inventoryFill = false;
protected boolean rayVisualizer = false;
protected boolean smartPlace = false;
protected boolean blockCounter = false;
protected boolean depthMessage = true;
public static ConfigV1 parse(String yapion) {
Map<String, Object> object = YapionReader.parse(yapion);
ConfigV1 config = new ConfigV1();
config.version = (Integer) object.get("@version");
if (object.get("baugui") instanceof Map<?, ?> bauGui) {
bauGui.forEach((key, value) -> {
if (key.equals("size")) {
config.bauGuiSize = (Integer) value;
} else {
config.bauGui.put(Integer.parseInt((String) key), (Integer) value);
}
});
}
object.forEach((key, value) -> {
if (!(value instanceof List<?> hotbar)) return;
if (key.equals("hotbar")) {
// Hotbars were stored without version prior to 1.19
config.hotbars.putIfAbsent(19, hotbar);
} else if (key.startsWith("hotbar-")) {
config.hotbars.put(Integer.parseInt(key.substring("hotbar-".length())), hotbar);
}
});
config.nightVision = getBoolean(object, "nightvision", config.nightVision);
config.tntClickDetails = getBoolean(object, "tnt_click_details", config.tntClickDetails);
config.detonatorAutostart = getBoolean(object, "detonator-autostart", config.detonatorAutostart);
config.inventoryFill = getBoolean(object, "inventoryfill", config.inventoryFill);
config.rayVisualizer = getBoolean(object, "rayvisualizer", config.rayVisualizer);
config.smartPlace = getBoolean(object, "smartPlace", config.smartPlace);
config.blockCounter = getBoolean(object, "blockCounter", config.blockCounter);
config.depthMessage = getBoolean(object, "depth_message", config.depthMessage);
return config;
}
private static boolean getBoolean(Map<String, Object> object, String key, boolean defaultValue) {
return object.get(key) instanceof Boolean value ? value : defaultValue;
}
}
@@ -0,0 +1,75 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.configplayer.converter;
import de.steamwar.bausystem.config.ConfigConverter;
import de.steamwar.bausystem.config.legacy.YapionItemDeserializer;
import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.bausystem.configplayer.ConfigV1;
import de.steamwar.core.Core;
import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
@Deprecated
public class ConfigV1ToV2Converter implements ConfigConverter<ConfigV1, Config> {
@Override
public int version() {
return ConfigV1.VERSION;
}
@Override
public Config convert(ConfigV1 old) {
Config config = new Config();
config.setBauGui(new HashMap<>(old.getBauGui()));
config.setBauGuiSize(old.getBauGuiSize());
List<?> hotbar = old.getHotbars().get(Core.getVersion());
if (hotbar != null) {
config.setHotbar(convertHotbar(hotbar));
}
config.setNightVision(old.isNightVision());
config.setTntClickDetails(old.isTntClickDetails());
config.setDetonatorAutostart(old.isDetonatorAutostart());
config.setInventoryFill(old.isInventoryFill());
config.setRayVisualizer(old.isRayVisualizer());
config.setSmartPlace(old.isSmartPlace());
config.setBlockCounter(old.isBlockCounter());
config.setDepthMessage(old.isDepthMessage());
return config;
}
private ItemStack[] convertHotbar(List<?> hotbar) {
ItemStack[] items = new ItemStack[13];
for (int i = 0; i < Math.min(hotbar.size(), items.length); i++) {
try {
items[i] = YapionItemDeserializer.deserializeItem(hotbar.get(i));
} catch (Exception e) {
Bukkit.getLogger().log(Level.WARNING, "Could not convert hotbar item " + i, e);
}
}
return items;
}
}
@@ -1,90 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.configplayer.serializer;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import yapion.hierarchy.api.groups.YAPIONAnyType;
import yapion.hierarchy.types.YAPIONObject;
import yapion.serializing.api.SerializerObject;
import yapion.serializing.data.DeserializeData;
import yapion.serializing.data.SerializeData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import static yapion.utils.IdentifierUtils.TYPE_IDENTIFIER;
public class ConfigurationSerializableSerializer extends SerializerObject<ConfigurationSerializable> {
@Override
public Class<ConfigurationSerializable> type() {
return ConfigurationSerializable.class;
}
@Override
public boolean isInterface() {
return true;
}
@Override
public YAPIONObject serialize(SerializeData<ConfigurationSerializable> serializeData) {
YAPIONObject yapionObject = new YAPIONObject();
yapionObject.add(TYPE_IDENTIFIER, serializeData.object.getClass().getTypeName());
if (serializeData.object instanceof ItemStack) {
yapionObject.add(TYPE_IDENTIFIER, ItemStack.class.getTypeName());
}
if (serializeData.object instanceof ItemMeta) {
yapionObject.add(TYPE_IDENTIFIER, ItemMeta.class.getTypeName());
}
Map<String, Object> serializeDataMap = serializeData.object.serialize();
serializeDataMap.forEach((s, o) -> {
YAPIONAnyType yapionAnyType = serializeData.serialize(o);
if (yapionAnyType instanceof YAPIONObject) {
YAPIONObject object = (YAPIONObject) yapionAnyType;
if (object.containsKey(TYPE_IDENTIFIER) && object.getPlainValue(TYPE_IDENTIFIER).equals("com.google.common.collect.RegularImmutableList")) {
object.put(TYPE_IDENTIFIER, ArrayList.class.getTypeName());
}
}
yapionObject.add(s, yapionAnyType);
});
return yapionObject;
}
@Override
public ConfigurationSerializable deserialize(DeserializeData<YAPIONObject> deserializeData) {
Map<String, Object> deserializeDataMap = new HashMap<>();
deserializeData.object.forEach((s, yapionAnyType) -> {
if (s.equals(TYPE_IDENTIFIER)) {
if (yapionAnyType.toString().equals("(org.bukkit.inventory.meta.ItemMeta)")) {
deserializeDataMap.put("==", "ItemMeta");
} else {
deserializeDataMap.put("==", deserializeData.deserialize(yapionAnyType));
}
return;
}
deserializeDataMap.put(s, deserializeData.deserialize(yapionAnyType));
});
return ConfigurationSerialization.deserializeObject(deserializeDataMap);
}
}
@@ -64,7 +64,7 @@ public class Depth {
dimensions.setZ(Math.abs(dimensions.getZ())); dimensions.setZ(Math.abs(dimensions.getZ()));
RegionUtils.message(region, player -> { RegionUtils.message(region, player -> {
if (Config.getInstance().get(player).getPlainValueOrDefault("depth_message", true)) { if (Config.STORE.get(player).isDepthMessage()) {
player.spigot().sendMessage(getMessage(player, dimensions.getBlockX() + 1, dimensions.getBlockY() + 1, dimensions.getBlockZ() + 1, tntCount)); player.spigot().sendMessage(getMessage(player, dimensions.getBlockX() + 1, dimensions.getBlockY() + 1, dimensions.getBlockZ() + 1, tntCount));
} }
}); });
@@ -34,11 +34,11 @@ public class DepthCommand extends SWCommand {
@Register @Register
public void toggle(Player player) { public void toggle(Player player) {
if (Config.getInstance().get(player).getPlainValueOrDefault("depth_message", true)) { if (Config.STORE.get(player).isDepthMessage()) {
Config.getInstance().get(player).put("depth_message", false); Config.STORE.get(player).setDepthMessage(false);
BauSystem.MESSAGE.send("DEPTH_COUNTER_DISABLE", player); BauSystem.MESSAGE.send("DEPTH_COUNTER_DISABLE", player);
} else { } else {
Config.getInstance().get(player).put("depth_message", true); Config.STORE.get(player).setDepthMessage(true);
BauSystem.MESSAGE.send("DEPTH_COUNTER_ENABLE", player); BauSystem.MESSAGE.send("DEPTH_COUNTER_ENABLE", player);
} }
} }
@@ -95,7 +95,7 @@ public class Detonator {
public static void activateDetonator(DetonatorStorage detonator) { public static void activateDetonator(DetonatorStorage detonator) {
Player p = detonator.getPlayer(); Player p = detonator.getPlayer();
if (Config.getInstance().get(p).getPlainValueOrDefault("detonator-autostart", false)) { if (Config.STORE.get(p).isDetonatorAutostart()) {
AutostartListener.instance.activate(p); AutostartListener.instance.activate(p);
} }
@@ -76,8 +76,8 @@ public class DetonatorCommand extends SWCommand {
@Register(value = "autostart", description = "DETONATOR_HELP_AUTOSTART") @Register(value = "autostart", description = "DETONATOR_HELP_AUTOSTART")
public void toggleAutostartTimer(Player p) { public void toggleAutostartTimer(Player p) {
boolean current = Config.getInstance().get(p).getPlainValueOrDefault("detonator-autostart", false); boolean current = Config.STORE.get(p).isDetonatorAutostart();
Config.getInstance().get(p).put("detonator-autostart", !current); Config.STORE.get(p).setDetonatorAutostart(!current);
if (!current) { if (!current) {
BauSystem.MESSAGE.send("DETONATOR_AUTOSTART_ENABLE", p); BauSystem.MESSAGE.send("DETONATOR_AUTOSTART_ENABLE", p);
} else { } else {
@@ -28,7 +28,6 @@ import lombok.Setter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
import yapion.hierarchy.types.YAPIONObject;
import java.util.*; import java.util.*;
@@ -52,15 +51,15 @@ public class BauGuiMapping {
} }
@Getter @Getter
private final YAPIONObject object; private final Config config;
@Getter @Getter
private final Player owner; private final Player owner;
@Getter @Getter
@Setter @Setter
private boolean saved; private boolean saved;
protected BauGuiMapping(YAPIONObject object, Player p) { protected BauGuiMapping(Config config, Player p) {
this.object = object; this.config = config;
this.owner = p; this.owner = p;
fromUUID.put(p.getUniqueId(), this); fromUUID.put(p.getUniqueId(), this);
mappings.add(this); mappings.add(this);
@@ -70,19 +69,18 @@ public class BauGuiMapping {
BauGuiMapping mapping = fromUUID.get(p.getUniqueId()); BauGuiMapping mapping = fromUUID.get(p.getUniqueId());
if (mapping == null) { if (mapping == null) {
YAPIONObject yapionObject = Config.getInstance().get(p); mapping = new BauGuiMapping(Config.STORE.get(p), p);
mapping = new BauGuiMapping(yapionObject.getObject("baugui"), p);
} }
return mapping; return mapping;
} }
public boolean isShown(int id) { public boolean isShown(int id) {
return object.getPlainValueOrDefault(Integer.toString(id), -1) >= 0; return config.getBauGui().getOrDefault(id, -1) >= 0;
} }
public int getSlot(int id) { public int getSlot(int id) {
return object.getPlainValue(Integer.toString(id)); return config.getBauGui().get(id);
} }
public Map<Integer, Integer> getMapping() { public Map<Integer, Integer> getMapping() {
@@ -94,7 +92,7 @@ public class BauGuiMapping {
} }
public int getSize() { public int getSize() {
return object.getPlainValueOrDefault("size", 45); return config.getBauGuiSize();
} }
public void setSize(int size) { public void setSize(int size) {
@@ -102,7 +100,7 @@ public class BauGuiMapping {
return; return;
} }
object.add("size", size); config.setBauGuiSize(size);
} }
private void tick() { private void tick() {
@@ -111,16 +109,17 @@ public class BauGuiMapping {
public void save() { public void save() {
if (saved) return; if (saved) return;
YAPIONObject config = Config.getInstance().get(owner); Config current = Config.STORE.get(owner);
config.add("baugui", object); current.setBauGui(config.getBauGui());
Config.getInstance().save(owner); current.setBauGuiSize(config.getBauGuiSize());
Config.STORE.save(owner);
saved = true; saved = true;
} }
private Map<Integer, Integer> internalReadMap() { private Map<Integer, Integer> internalReadMap() {
Map<Integer, Integer> map = new HashMap<>(); Map<Integer, Integer> map = new HashMap<>();
for (Map.Entry<Integer, BauGuiItem> e : BauGUI.getITEMS().entrySet()) { for (Map.Entry<Integer, BauGuiItem> e : BauGUI.getITEMS().entrySet()) {
Integer value = object.getPlainValueOrDefault(e.getKey().toString(), -1); Integer value = config.getBauGui().getOrDefault(e.getKey(), -1);
if (value == -1) continue; if (value == -1) continue;
map.put(e.getKey(), value); map.put(e.getKey(), value);
} }
@@ -129,10 +128,10 @@ public class BauGuiMapping {
private void internalWriteMap(Map<Integer, Integer> map) { private void internalWriteMap(Map<Integer, Integer> map) {
for (Map.Entry<Integer, BauGuiItem> e : BauGUI.getITEMS().entrySet()) { for (Map.Entry<Integer, BauGuiItem> e : BauGUI.getITEMS().entrySet()) {
object.remove(e.getKey().toString()); config.getBauGui().remove(e.getKey());
Integer value = map.getOrDefault(e.getKey(), -1); Integer value = map.getOrDefault(e.getKey(), -1);
if (value == -1) continue; if (value == -1) continue;
object.add(e.getKey().toString(), value); config.getBauGui().put(e.getKey(), value);
} }
} }
} }
@@ -20,39 +20,23 @@
package de.steamwar.bausystem.features.hotbar; package de.steamwar.bausystem.features.hotbar;
import de.steamwar.bausystem.configplayer.Config; import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.core.Core;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import yapion.hierarchy.types.YAPIONArray; import org.bukkit.inventory.meta.ItemMeta;
import yapion.hierarchy.types.YAPIONObject;
import yapion.hierarchy.types.YAPIONValue;
import yapion.parser.YAPIONParser;
import yapion.serializing.YAPIONDeserializer;
import yapion.serializing.YAPIONSerializer;
import java.util.HashSet; import java.util.List;
import java.util.Set;
@UtilityClass @UtilityClass
public class DefaultHotbar { public class DefaultHotbar {
private final YAPIONArray DEFAULT_HOTBAR = YAPIONParser.parse("{[{@type(org.bukkit.inventory.ItemStack)v(2230)type(WOODEN_AXE)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(WorldEdit Wand)lore{@type(java.util.ArrayList)values[Left click: select pos #1,Right click: select pos #2]}}},{@type(org.bukkit.inventory.ItemStack)v(2230)type(COMPASS)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(Navigation Wand)lore{@type(java.util.ArrayList)values[Left click: jump to location,Right click: pass through walls]}}},null,null,null,null,null,null,{@type(org.bukkit.inventory.ItemStack)v(2230)type(NETHER_STAR)meta{@type(org.bukkit.inventory.meta.ItemMeta)meta-type(UNSPECIFIC)display-name(§eBau GUI)}},null,null,{@type(org.bukkit.inventory.ItemStack)v(3117)type(ELYTRA)},null]}").getArray("");
public void updateHotbar(Player p) { public void updateHotbar(Player p) {
ItemStack[] hotbar = new ItemStack[13]; ItemStack[] hotbar = new ItemStack[13];
System.arraycopy(p.getInventory().getContents(), 0, hotbar, 0, 9); System.arraycopy(p.getInventory().getContents(), 0, hotbar, 0, 9);
System.arraycopy(p.getInventory().getArmorContents(), 0, hotbar, 9, 4); System.arraycopy(p.getInventory().getArmorContents(), 0, hotbar, 9, 4);
YAPIONArray yapionArray = new YAPIONArray(); Config.STORE.get(p).setHotbar(copy(hotbar));
for (ItemStack itemStack : hotbar) { Config.STORE.save(p);
if (itemStack != null) {
yapionArray.add(YAPIONSerializer.serialize(itemStack));
} else {
yapionArray.add(new YAPIONValue<>(null));
}
}
Config.getInstance().get(p).add("hotbar-" + Core.getVersion(), yapionArray);
Config.getInstance().save(p);
} }
public void setHotbar(Player p) { public void setHotbar(Player p) {
@@ -66,28 +50,34 @@ public class DefaultHotbar {
} }
public ItemStack[] getItems(Player p) { public ItemStack[] getItems(Player p) {
Config.getInstance().get(p).remap("hotbar", "hotbar-19"); return copy(Config.STORE.get(p).getHotbar());
YAPIONArray yapionArray = Config.getInstance().get(p).getYAPIONArrayOrSetDefault("hotbar-" + Core.getVersion(), defaultHotbar()); }
public ItemStack[] defaultHotbar() {
ItemStack[] hotbar = new ItemStack[13]; ItemStack[] hotbar = new ItemStack[13];
Set<Integer> invalid = new HashSet<>(); hotbar[0] = item(Material.WOODEN_AXE, "WorldEdit Wand", "Left click: select pos #1", "Right click: select pos #2");
yapionArray.forEach((integer, yapionAnyType) -> { hotbar[1] = item(Material.COMPASS, "Navigation Wand", "Left click: jump to location", "Right click: pass through walls");
if (yapionAnyType instanceof YAPIONValue) { hotbar[8] = item(Material.NETHER_STAR, "§eBau GUI");
hotbar[integer] = null; hotbar[11] = new ItemStack(Material.ELYTRA);
} else {
try {
hotbar[integer] = YAPIONDeserializer.deserialize((YAPIONObject) yapionAnyType);
} catch (Exception e) {
invalid.add(integer);
hotbar[integer] = null;
}
}
});
invalid.forEach(i -> yapionArray.set(i, new YAPIONValue<>(null)));
if (!invalid.isEmpty()) Config.getInstance().save(p);
return hotbar; return hotbar;
} }
public YAPIONArray defaultHotbar() { private ItemStack item(Material material, String name, String... lore) {
return DEFAULT_HOTBAR; ItemStack itemStack = new ItemStack(material);
ItemMeta meta = itemStack.getItemMeta();
meta.setDisplayName(name);
if (lore.length > 0) {
meta.setLore(List.of(lore));
}
itemStack.setItemMeta(meta);
return itemStack;
}
private ItemStack[] copy(ItemStack[] hotbar) {
ItemStack[] copy = new ItemStack[13];
for (int i = 0; i < Math.min(hotbar.length, copy.length); i++) {
copy[i] = hotbar[i] == null ? null : hotbar[i].clone();
}
return copy;
} }
} }
@@ -49,7 +49,7 @@ public class InventoryFillBauGuiItem extends BauGuiItem {
@Override @Override
public ItemStack getItem(Player player) { public ItemStack getItem(Player player) {
String loreKey = Config.getInstance().get(player).getPlainValueOrDefault("inventoryfill", false) ? "OTHER_ITEMS_INVENTORY_FILL_LORE_ACTIVE" : "OTHER_ITEMS_INVENTORY_FILL_LORE_INACTIVE"; String loreKey = Config.STORE.get(player).isInventoryFill() ? "OTHER_ITEMS_INVENTORY_FILL_LORE_ACTIVE" : "OTHER_ITEMS_INVENTORY_FILL_LORE_INACTIVE";
return new SWItem(Material.HOPPER, BauSystem.MESSAGE.parse("OTHER_ITEMS_INVENTORY_FILL_NAME", player), Collections.singletonList(BauSystem.MESSAGE.parse(loreKey, player)), false, clickType -> { return new SWItem(Material.HOPPER, BauSystem.MESSAGE.parse("OTHER_ITEMS_INVENTORY_FILL_NAME", player), Collections.singletonList(BauSystem.MESSAGE.parse(loreKey, player)), false, clickType -> {
}).getItemStack(); }).getItemStack();
} }
@@ -40,7 +40,7 @@ public class InventoryFiller implements Listener {
@EventHandler @EventHandler
public void onPlayerDropItem(PlayerDropItemEvent event) { public void onPlayerDropItem(PlayerDropItemEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("inventoryfill", false)) return; if (!Config.STORE.get(event.getPlayer()).isInventoryFill()) return;
if (!event.getPlayer().isSneaking()) return; if (!event.getPlayer().isSneaking()) return;
Block block = event.getPlayer().getTargetBlockExact(5); Block block = event.getPlayer().getTargetBlockExact(5);
if (block == null) return; if (block == null) return;
@@ -62,7 +62,7 @@ public class InventoryFiller implements Listener {
@EventHandler @EventHandler
public void onPlayerItemHeld(PlayerItemHeldEvent event) { public void onPlayerItemHeld(PlayerItemHeldEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("inventoryfill", false)) return; if (!Config.STORE.get(event.getPlayer()).isInventoryFill()) return;
if (!event.getPlayer().isSneaking()) return; if (!event.getPlayer().isSneaking()) return;
ItemStack itemStack = event.getPlayer().getInventory().getItemInMainHand(); ItemStack itemStack = event.getPlayer().getInventory().getItemInMainHand();
if (itemStack.getType() == Material.AIR) return; if (itemStack.getType() == Material.AIR) return;
@@ -68,8 +68,8 @@ public class InventoryFillerCommand extends SWCommand {
@Register(description = {"INVENTORY_FILL_HELP", "INVENTORY_FILL_INFO"}) @Register(description = {"INVENTORY_FILL_HELP", "INVENTORY_FILL_INFO"})
public void toggle(Player player) { public void toggle(Player player) {
boolean inventoryFill = Config.getInstance().get(player).getPlainValueOrDefault("inventoryfill", false); boolean inventoryFill = Config.STORE.get(player).isInventoryFill();
Config.getInstance().get(player).put("inventoryfill", !inventoryFill); Config.STORE.get(player).setInventoryFill(!inventoryFill);
if (!inventoryFill) { if (!inventoryFill) {
SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("INVENTORY_FILL_ENABLE", player)); SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("INVENTORY_FILL_ENABLE", player));
BauSystem.MESSAGE.send("INVENTORY_FILL_INFO", player); BauSystem.MESSAGE.send("INVENTORY_FILL_INFO", player);
@@ -290,7 +290,7 @@ public class KillcheckerVisualizer {
} }
rEntities.get(point).die(); rEntities.get(point).die();
} }
RBlockDisplay entity = new RBlockDisplay(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0.5, 0, 0.5)); RBlockDisplay entity = new RBlockDisplay(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0, 0, 0));
entity.setBlock(MATERIALS[Math.min(count - 1, MATERIALS.length) - 1].createBlockData()); entity.setBlock(MATERIALS[Math.min(count - 1, MATERIALS.length) - 1].createBlockData());
rEntities.put(point, entity); rEntities.put(point, entity);
if (outlinePoints.contains(point)) outlinePointsCache.add(point); if (outlinePoints.contains(point)) outlinePointsCache.add(point);
@@ -46,7 +46,7 @@ public class ObserverTracerListener implements Listener {
public ObserverTracerListener() { public ObserverTracerListener() {
Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> { Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> { SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> {
if (pair.getPlayer().getGameMode() != GameMode.SPECTATOR) return; if (pair.getSwPlayer().getPlayer().getGameMode() != GameMode.SPECTATOR) return;
pair.getComponent().show(); pair.getComponent().show();
}); });
}, 15L, 15L); }, 15L, 15L);
@@ -167,7 +167,7 @@ public class RayVisualizerCommand extends SWCommand implements Listener {
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
boolean rayvisualizer = Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("rayvisualizer", false); boolean rayvisualizer = Config.STORE.get(event.getPlayer()).isRayVisualizer();
if (rayvisualizer) server.addPlayer(event.getPlayer()); if (rayvisualizer) server.addPlayer(event.getPlayer());
} }
@@ -181,7 +181,7 @@ public class RayVisualizerCommand extends SWCommand implements Listener {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
for (Player player : Bukkit.getOnlinePlayers()) { for (Player player : Bukkit.getOnlinePlayers()) {
if (Permission.BUILD.hasPermission(player)) { if (Permission.BUILD.hasPermission(player)) {
boolean rayvisualizer = Config.getInstance().get(player).getPlainValueOrDefault("rayvisualizer", false); boolean rayvisualizer = Config.STORE.get(player).isRayVisualizer();
if (rayvisualizer) server.addPlayer(player); if (rayvisualizer) server.addPlayer(player);
} else { } else {
server.removePlayer(player); server.removePlayer(player);
@@ -192,8 +192,8 @@ public class RayVisualizerCommand extends SWCommand implements Listener {
@Register @Register
public void toggle(@Validator Player player) { public void toggle(@Validator Player player) {
boolean rayvisualizer = Config.getInstance().get(player).getPlainValueOrDefault("rayvisualizer", false); boolean rayvisualizer = Config.STORE.get(player).isRayVisualizer();
Config.getInstance().get(player).put("rayvisualizer", !rayvisualizer); Config.STORE.get(player).setRayVisualizer(!rayvisualizer);
if (!rayvisualizer) { if (!rayvisualizer) {
SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("RAY_VISUALIZER_ENABLE", player)); SWUtils.sendToActionbar(player, BauSystem.MESSAGE.parse("RAY_VISUALIZER_ENABLE", player));
server.addPlayer(player); server.addPlayer(player);
@@ -185,17 +185,16 @@ public class TestblockCommand extends SWCommand {
return new TypeMapper<SchematicNode>() { return new TypeMapper<SchematicNode>() {
@Override @Override
public List<String> tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) { public List<String> tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) {
List<String> stringList = new ArrayList<>(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s)); List<String> stringList = new ArrayList<>();
stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.byId(0), s)); stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.byId(0), s));
stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s));
return stringList; return stringList;
} }
@Override @Override
public SchematicNode map(CommandSender commandSender, PreviousArguments previousArguments, String s) { public SchematicNode map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s);
if (node == null) { if (node == null) node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s);
}
return node; return node;
} }
}; };
@@ -19,6 +19,7 @@
package de.steamwar.bausystem.features.simulator; package de.steamwar.bausystem.features.simulator;
import com.destroystokyo.paper.event.server.ServerTickEndEvent;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission; import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils; import de.steamwar.bausystem.SWUtils;
@@ -77,8 +78,16 @@ public class SimulatorCursor implements Listener {
return isSimulatorItem(player.getInventory().getItemInMainHand()) || isSimulatorItem(player.getInventory().getItemInOffHand()); return isSimulatorItem(player.getInventory().getItemInMainHand()) || isSimulatorItem(player.getInventory().getItemInOffHand());
} }
private static Set<Player> scheduledUpdates = new HashSet<>();
private static void scheduleCursorUpdate(Player player) { private static void scheduleCursorUpdate(Player player) {
BauSystem.runTaskLater(BauSystem.getInstance(), () -> calcCursor(player), 1); scheduledUpdates.add(player);
}
@EventHandler
public void onServerTickEnd(ServerTickEndEvent event) {
scheduledUpdates.forEach(SimulatorCursor::calcCursor);
scheduledUpdates.clear();
} }
@EventHandler @EventHandler
@@ -122,6 +131,7 @@ public class SimulatorCursor implements Listener {
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
cursorType.remove(event.getPlayer()); cursorType.remove(event.getPlayer());
removeCursor(event.getPlayer()); removeCursor(event.getPlayer());
scheduledUpdates.remove(event.getPlayer());
} }
private static final Map<Player, Long> LAST_SNEAKS = new HashMap<>(); private static final Map<Player, Long> LAST_SNEAKS = new HashMap<>();
@@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data;
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui; import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
import de.steamwar.inventory.InvCallback; import de.steamwar.inventory.InvCallback;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import lombok.Getter; import lombok.Getter;
@@ -28,7 +29,6 @@ import lombok.Setter;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.hierarchy.types.YAPIONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
@@ -121,9 +121,9 @@ public abstract class SimulatorElement<T extends SimulatorPhase> {
public abstract String getType(); public abstract String getType();
public void saveExtra(YAPIONObject elementObject) { public void saveExtra(SimulatorConfig.Element element) {
} }
public void loadExtra(YAPIONObject elementObject) { public void loadExtra(SimulatorConfig.Element element) {
} }
} }
@@ -20,10 +20,10 @@
package de.steamwar.bausystem.features.simulator.data; package de.steamwar.bausystem.features.simulator.data;
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.hierarchy.types.YAPIONObject;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@@ -38,9 +38,9 @@ public abstract class SimulatorPhase {
public abstract void toSimulatorActions(Vector position, BiConsumer<Integer, SimulatorAction> tickStart, BiConsumer<Integer, SimulatorAction> tickEnd); public abstract void toSimulatorActions(Vector position, BiConsumer<Integer, SimulatorAction> tickStart, BiConsumer<Integer, SimulatorAction> tickEnd);
public void saveExtra(YAPIONObject phaseObject) { public void saveExtra(SimulatorConfig.Phase phase) {
} }
public void loadExtra(YAPIONObject phaseObject) { public void loadExtra(SimulatorConfig.Phase phase) {
} }
} }
@@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data.observer;
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase; import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
@@ -31,7 +32,6 @@ import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.block.data.type.Observer; import org.bukkit.block.data.type.Observer;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.hierarchy.types.YAPIONObject;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@@ -70,12 +70,12 @@ public final class ObserverPhase extends SimulatorPhase {
} }
@Override @Override
public void saveExtra(YAPIONObject phaseObject) { public void saveExtra(SimulatorConfig.Phase phase) {
phaseObject.add("orientation", orientation.name()); phase.setOrientation(orientation);
} }
@Override @Override
public void loadExtra(YAPIONObject phaseObject) { public void loadExtra(SimulatorConfig.Phase phase) {
orientation = BlockFace.valueOf(phaseObject.getPlainValue("orientation")); if (phase.getOrientation() != null) orientation = phase.getOrientation();
} }
} }
@@ -24,13 +24,13 @@ import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.gui.SimulatorTNTGui; import de.steamwar.bausystem.features.simulator.gui.SimulatorTNTGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui; import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
import de.steamwar.inventory.InvCallback; import de.steamwar.inventory.InvCallback;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.hierarchy.types.YAPIONObject;
public final class TNTElement extends SimulatorElement<TNTPhase> { public final class TNTElement extends SimulatorElement<TNTPhase> {
@@ -100,14 +100,14 @@ public final class TNTElement extends SimulatorElement<TNTPhase> {
} }
@Override @Override
public void saveExtra(YAPIONObject elementObject) { public void saveExtra(SimulatorConfig.Element element) {
elementObject.add("alignmentX", alignment.getX()); element.setAlignmentX(alignment.getX());
elementObject.add("alignmentZ", alignment.getZ()); element.setAlignmentZ(alignment.getZ());
} }
@Override @Override
public void loadExtra(YAPIONObject elementObject) { public void loadExtra(SimulatorConfig.Element element) {
alignment.setX(elementObject.getDoubleOrSetDefault("alignmentX", 0)); alignment.setX(element.getAlignmentX() != null ? element.getAlignmentX() : 0);
alignment.setZ(elementObject.getDoubleOrSetDefault("alignmentZ", 0)); alignment.setZ(element.getAlignmentZ() != null ? element.getAlignmentZ() : 0);
} }
} }
@@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.simulator.data.tnt;
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase; import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction; import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
import de.steamwar.bausystem.features.simulator.storage.SimulatorConfig;
import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.FreezeMode; import de.steamwar.bausystem.region.flags.FreezeMode;
@@ -31,7 +32,6 @@ import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.TNTPrimed; import org.bukkit.entity.TNTPrimed;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.hierarchy.types.YAPIONObject;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@@ -83,18 +83,18 @@ public final class TNTPhase extends SimulatorPhase {
} }
@Override @Override
public void saveExtra(YAPIONObject phaseObject) { public void saveExtra(SimulatorConfig.Phase phase) {
phaseObject.add("count", count); phase.setCount(count);
phaseObject.add("xJump", xJump); phase.setXJump(xJump);
phaseObject.add("yJump", yJump); phase.setYJump(yJump);
phaseObject.add("zJump", zJump); phase.setZJump(zJump);
} }
@Override @Override
public void loadExtra(YAPIONObject phaseObject) { public void loadExtra(SimulatorConfig.Phase phase) {
count = phaseObject.getPlainValue("count"); if (phase.getCount() != null) count = phase.getCount();
xJump = phaseObject.getPlainValue("xJump"); if (phase.getXJump() != null) xJump = phase.getXJump();
yJump = phaseObject.getPlainValue("yJump"); if (phase.getYJump() != null) yJump = phase.getYJump();
zJump = phaseObject.getPlainValue("zJump"); if (phase.getZJump() != null) zJump = phase.getZJump();
} }
} }
@@ -19,6 +19,7 @@
package de.steamwar.bausystem.features.simulator.storage; package de.steamwar.bausystem.features.simulator.storage;
import com.google.gson.JsonParseException;
import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement; import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
@@ -29,12 +30,7 @@ import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase; import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement; import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase; import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import org.bukkit.Material;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.exceptions.YAPIONException;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -51,44 +47,41 @@ public class SimFormatSimulatorLoader implements SimulatorLoader {
return Optional.empty(); return Optional.empty();
} }
YAPIONObject yapionObject; SimulatorConfig config;
try { try {
yapionObject = YAPIONParser.parse(file); config = SimulatorConfig.STORE.load(file);
} catch (YAPIONException | IOException e) { } catch (JsonParseException | IllegalStateException | IOException e) {
return Optional.empty(); return Optional.empty();
} }
String name = file.getName().substring(0, file.getName().length() - ".sim".length()); String name = file.getName().substring(0, file.getName().length() - ".sim".length());
Simulator simulator = new Simulator(name); Simulator simulator = new Simulator(name);
loadSimulator(yapionObject, simulator); loadSimulator(config, simulator);
return Optional.of(Collections.singletonList(simulator)); return Optional.of(Collections.singletonList(simulator));
} }
private void loadSimulator(YAPIONObject simulatorObject, Simulator simulator) { private void loadSimulator(SimulatorConfig config, Simulator simulator) {
simulator.setMaterial(Material.valueOf(simulatorObject.getPlainValue("material"))); if (config.getMaterial() != null) simulator.setMaterial(config.getMaterial());
simulator.setAutoTrace(simulatorObject.getPlainValue("autoTrace")); simulator.setAutoTrace(config.isAutoTrace());
simulator.setAutoTestblock(simulatorObject.getPlainValueOrDefault("autoTestblock", false)); simulator.setAutoTestblock(config.isAutoTestblock());
YAPIONArray groups = simulatorObject.getArray("groups"); config.getGroups().forEach(groupConfig -> {
groups.streamObject().forEach(groupObject -> {
SimulatorGroup simulatorGroup = new SimulatorGroup(); SimulatorGroup simulatorGroup = new SimulatorGroup();
loadGroup(groupObject, simulatorGroup); loadGroup(groupConfig, simulatorGroup);
simulator.add(simulatorGroup); simulator.add(simulatorGroup);
}); });
} }
private void loadGroup(YAPIONObject groupObject, SimulatorGroup group) { private void loadGroup(SimulatorConfig.Group groupConfig, SimulatorGroup group) {
group.setMaterial(Material.valueOf(groupObject.getPlainValue("material"))); if (groupConfig.getMaterial() != null) group.setMaterial(groupConfig.getMaterial());
group.setDisabled(groupObject.getPlainValue("disabled")); group.setDisabled(groupConfig.isDisabled());
YAPIONArray elements = groupObject.getArray("elements"); groupConfig.getElements().forEach(elementConfig -> {
elements.streamObject().forEach(elementObject -> {
SimulatorElement<?> element; SimulatorElement<?> element;
Supplier<SimulatorPhase> phaseConstructor; Supplier<SimulatorPhase> phaseConstructor;
Vector position = new Vector(elementObject.getDouble("x"), elementObject.getDouble("y"), elementObject.getDouble("z")); Vector position = new Vector(elementConfig.getX(), elementConfig.getY(), elementConfig.getZ());
String type = elementObject.getPlainValue("type"); switch (String.valueOf(elementConfig.getType())) {
switch (type) {
case "TNT": case "TNT":
element = new TNTElement(position); element = new TNTElement(position);
phaseConstructor = TNTPhase::new; phaseConstructor = TNTPhase::new;
@@ -109,28 +102,27 @@ public class SimFormatSimulatorLoader implements SimulatorLoader {
if (element == null) { if (element == null) {
return; return;
} }
loadElement(elementObject, element, phaseConstructor); loadElement(elementConfig, element, phaseConstructor);
group.add(element); group.add(element);
}); });
} }
private void loadElement(YAPIONObject elementObject, SimulatorElement<?> element, Supplier<SimulatorPhase> phaseConstructor) { private void loadElement(SimulatorConfig.Element elementConfig, SimulatorElement<?> element, Supplier<SimulatorPhase> phaseConstructor) {
element.setMaterial(Material.valueOf(elementObject.getPlainValue("material"))); if (elementConfig.getMaterial() != null) element.setMaterial(elementConfig.getMaterial());
element.setDisabled(elementObject.getPlainValue("disabled")); element.setDisabled(elementConfig.isDisabled());
element.loadExtra(elementObject); element.loadExtra(elementConfig);
YAPIONArray phases = elementObject.getArray("phases"); elementConfig.getPhases().forEach(phaseConfig -> {
phases.streamObject().forEach(phaseObject -> {
SimulatorPhase phase = phaseConstructor.get(); SimulatorPhase phase = phaseConstructor.get();
loadPhase(phaseObject, phase); loadPhase(phaseConfig, phase);
((SimulatorElement) element).add(phase); ((SimulatorElement) element).add(phase);
}); });
} }
private void loadPhase(YAPIONObject phaseObject, SimulatorPhase phase) { private void loadPhase(SimulatorConfig.Phase phaseConfig, SimulatorPhase phase) {
phase.setTickOffset(phaseObject.getPlainValue("tickOffset")); phase.setTickOffset(phaseConfig.getTickOffset());
phase.setLifetime(phaseObject.getPlainValue("lifetime")); phase.setLifetime(phaseConfig.getLifetime());
phase.setOrder(phaseObject.getPlainValue("order")); phase.setOrder(phaseConfig.getOrder());
phase.loadExtra(phaseObject); phase.loadExtra(phaseConfig);
} }
} }
@@ -0,0 +1,117 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.storage;
import de.steamwar.bausystem.config.ConfigStore;
import de.steamwar.bausystem.config.VersionedConfig;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import java.util.ArrayList;
import java.util.List;
/**
* The current stored form of a simulator, saved as JSON in a .sim file. Files
* saved before the version was stored have no version and are version 1.
* Fields missing in the stored JSON keep the default value of their initializer,
* so adding a new field does not need a new version.
* <p>
* When a field gets removed, renamed or its meaning changes, copy this class
* to SimulatorConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a
* {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to
* this class and register it in the {@link #STORE}.
*/
@Getter
@Setter
public class SimulatorConfig implements VersionedConfig {
public static final int VERSION = 1;
/**
* Files saved before the version was stored are version 1
*/
public static final ConfigStore<SimulatorConfig> STORE = new ConfigStore<>(SimulatorConfig.class, VERSION, SimulatorConfig::new)
.unversioned(1);
@Setter(AccessLevel.NONE)
protected int version = VERSION;
protected Material material = Material.BARREL;
protected boolean autoTrace = false;
protected boolean autoTestblock = false;
protected List<Group> groups = new ArrayList<>();
@Getter
@Setter
public static class Group {
protected Material material = Material.CHEST;
protected boolean disabled = false;
protected List<Element> elements = new ArrayList<>();
}
@Getter
@Setter
public static class Element {
/**
* One of "TNT", "Redstone" or "Observer"
*/
protected String type;
protected Material material;
protected boolean disabled = false;
protected double x;
protected double y;
protected double z;
/**
* Only used by TNT
*/
protected Double alignmentX;
protected Double alignmentZ;
protected List<Phase> phases = new ArrayList<>();
}
@Getter
@Setter
public static class Phase {
protected int tickOffset;
protected int lifetime;
protected int order;
/**
* Only used by TNT
*/
protected Integer count;
protected Boolean xJump;
protected Boolean yJump;
protected Boolean zJump;
/**
* Only used by Observer
*/
protected BlockFace orientation;
}
}
@@ -19,24 +19,30 @@
package de.steamwar.bausystem.features.simulator.storage; package de.steamwar.bausystem.features.simulator.storage;
import de.steamwar.bausystem.config.legacy.YapionReader;
import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement; import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase; import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.exceptions.YAPIONException;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import static de.steamwar.bausystem.config.legacy.YapionReader.*;
/**
* Loads the legacy .simulator files stored as YAPION, they are deleted after loading
* and saved in the current format by the {@link SimulatorSaver}.
*/
@Deprecated
public class SimulatorFormatSimulatorLoader implements SimulatorLoader { public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
@Override @Override
@@ -45,20 +51,19 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
return Optional.empty(); return Optional.empty();
} }
YAPIONObject yapionObject; Map<String, Object> yapionObject;
try { try {
yapionObject = YAPIONParser.parse(file); yapionObject = YapionReader.parse(Files.readString(file.toPath(), StandardCharsets.UTF_8));
} catch (YAPIONException | IOException e) { } catch (IllegalArgumentException | IOException e) {
return Optional.empty(); return Optional.empty();
} }
String name = file.getName().substring(0, file.getName().length() - ".simulator".length()); String name = file.getName().substring(0, file.getName().length() - ".simulator".length());
Simulator simulator = new Simulator(name); Simulator simulator = new Simulator(name);
simulator.setMaterial(Material.valueOf(yapionObject.getStringOrDefault("material", "BARREL"))); simulator.setMaterial(Material.valueOf(getString(yapionObject, "material", "BARREL")));
YAPIONArray tntElements = yapionObject.getArrayOrDefault("tntElements", new YAPIONArray()); for (Map<?, ?> element : getObjects(yapionObject, "tntElements")) {
for (YAPIONObject element : tntElements.streamObject().collect(Collectors.toList())) {
if (element.containsKey("elements")) { if (element.containsKey("elements")) {
simulator.add(loadGroup(element)); simulator.add(loadGroup(element));
} else { } else {
@@ -70,27 +75,26 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
return Optional.of(Collections.singletonList(simulator)); return Optional.of(Collections.singletonList(simulator));
} }
private SimulatorGroup loadGroup(YAPIONObject element) { private SimulatorGroup loadGroup(Map<?, ?> element) {
double x = element.getDoubleOrDefault("x", 0); double x = getDouble(element, "x", 0);
double y = element.getDoubleOrDefault("y", 0); double y = getDouble(element, "y", 0);
double z = element.getDoubleOrDefault("z", 0); double z = getDouble(element, "z", 0);
int tickOffset = element.getIntOrDefault("tickOffset", 0); int tickOffset = getInt(element, "tickOffset", 0);
SimulatorGroup simulatorGroup = new SimulatorGroup(); SimulatorGroup simulatorGroup = new SimulatorGroup();
simulatorGroup.setDisabled(element.getBooleanOrDefault("disabled", false)); simulatorGroup.setDisabled(getBoolean(element, "disabled", false));
YAPIONArray elements = element.getArrayOrDefault("elements", new YAPIONArray()); for (Map<?, ?> e : getObjects(element, "elements")) {
for (YAPIONObject e : elements.streamObject().collect(Collectors.toList())) {
simulatorGroup.add(loadElement(e, x, y, z, tickOffset)); simulatorGroup.add(loadElement(e, x, y, z, tickOffset));
} }
return simulatorGroup; return simulatorGroup;
} }
private TNTElement loadElement(YAPIONObject element, double dx, double dy, double dz, int dTickOffset) { private TNTElement loadElement(Map<?, ?> element, double dx, double dy, double dz, int dTickOffset) {
TNTElement tntElement = new TNTElement(new Vector(element.getDouble("x") + dx, element.getDouble("y") + dy, element.getDouble("z") + dz)); TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "x") + dx, requireDouble(element, "y") + dy, requireDouble(element, "z") + dz));
tntElement.setDisabled(element.getBooleanOrDefault("disabled", false)); tntElement.setDisabled(getBoolean(element, "disabled", false));
tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT"))); tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT")));
TNTPhase tntPhase = loadPhase(element); TNTPhase tntPhase = loadPhase(element);
tntPhase.setTickOffset(tntPhase.getTickOffset() + dTickOffset); tntPhase.setTickOffset(tntPhase.getTickOffset() + dTickOffset);
@@ -99,12 +103,12 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
return tntElement; return tntElement;
} }
private SimulatorGroup loadElement(YAPIONObject element) { private SimulatorGroup loadElement(Map<?, ?> element) {
SimulatorGroup simulatorGroup = new SimulatorGroup(); SimulatorGroup simulatorGroup = new SimulatorGroup();
TNTElement tntElement = new TNTElement(new Vector(element.getDouble("x"), element.getDouble("y"), element.getDouble("z"))); TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "x"), requireDouble(element, "y"), requireDouble(element, "z")));
tntElement.setDisabled(element.getBooleanOrDefault("disabled", false)); tntElement.setDisabled(getBoolean(element, "disabled", false));
tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT"))); tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT")));
simulatorGroup.add(tntElement); simulatorGroup.add(tntElement);
tntElement.add(loadPhase(element)); tntElement.add(loadPhase(element));
@@ -112,15 +116,15 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
return simulatorGroup; return simulatorGroup;
} }
private TNTPhase loadPhase(YAPIONObject element) { private TNTPhase loadPhase(Map<?, ?> element) {
TNTPhase tntPhase = new TNTPhase(); TNTPhase tntPhase = new TNTPhase();
tntPhase.setLifetime(element.getIntOrDefault("fuseTicks", 80)); tntPhase.setLifetime(getInt(element, "fuseTicks", 80));
tntPhase.setCount(element.getIntOrDefault("count", 1)); tntPhase.setCount(getInt(element, "count", 1));
tntPhase.setTickOffset(element.getIntOrDefault("tickOffset", 0)); tntPhase.setTickOffset(getInt(element, "tickOffset", 0));
tntPhase.setXJump(element.getBooleanOrDefault("xVelocity", false)); tntPhase.setXJump(getBoolean(element, "xVelocity", false));
tntPhase.setYJump(element.getBooleanOrDefault("yVelocity", false)); tntPhase.setYJump(getBoolean(element, "yVelocity", false));
tntPhase.setZJump(element.getBooleanOrDefault("zVelocity", false)); tntPhase.setZJump(getBoolean(element, "zVelocity", false));
switch (element.getStringOrDefault("order", "REPEATER")) { switch (getString(element, "order", "REPEATER")) {
case "REPEATER": case "REPEATER":
tntPhase.setOrder(0); tntPhase.setOrder(0);
break; break;
@@ -133,4 +137,8 @@ public class SimulatorFormatSimulatorLoader implements SimulatorLoader {
} }
return tntPhase; return tntPhase;
} }
private double requireDouble(Map<?, ?> element, String key) {
return ((Number) element.get(key)).doubleValue();
}
} }
@@ -21,9 +21,6 @@ package de.steamwar.bausystem.features.simulator.storage;
import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.Simulator;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import yapion.hierarchy.output.FileOutput;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -36,48 +33,42 @@ public class SimulatorSaver {
directory.mkdirs(); directory.mkdirs();
} }
YAPIONObject simulatorObject = new YAPIONObject(); SimulatorConfig config = new SimulatorConfig();
simulatorObject.add("material", simulator.getMaterial().name()); config.setMaterial(simulator.getMaterial());
simulatorObject.add("autoTrace", simulator.isAutoTrace()); config.setAutoTrace(simulator.isAutoTrace());
simulatorObject.add("autoTestblock", simulator.isAutoTestblock()); config.setAutoTestblock(simulator.isAutoTestblock());
YAPIONArray groups = new YAPIONArray();
simulator.getGroups().forEach(group -> { simulator.getGroups().forEach(group -> {
YAPIONObject groupObject = new YAPIONObject(); SimulatorConfig.Group groupConfig = new SimulatorConfig.Group();
groupObject.add("material", group.getMaterial().name()); groupConfig.setMaterial(group.getMaterial());
groupObject.add("disabled", group.isDisabled()); groupConfig.setDisabled(group.isDisabled());
YAPIONArray elements = new YAPIONArray();
group.getElements().forEach(element -> { group.getElements().forEach(element -> {
YAPIONObject elementObject = new YAPIONObject(); SimulatorConfig.Element elementConfig = new SimulatorConfig.Element();
elementObject.add("type", element.getType()); elementConfig.setType(element.getType());
elementObject.add("material", element.getMaterial().name()); elementConfig.setMaterial(element.getMaterial());
elementObject.add("disabled", element.isDisabled()); elementConfig.setDisabled(element.isDisabled());
elementObject.add("x", element.getPosition().getX()); elementConfig.setX(element.getPosition().getX());
elementObject.add("y", element.getPosition().getY()); elementConfig.setY(element.getPosition().getY());
elementObject.add("z", element.getPosition().getZ()); elementConfig.setZ(element.getPosition().getZ());
element.saveExtra(elementObject); element.saveExtra(elementConfig);
YAPIONArray phases = new YAPIONArray();
element.getPhases().forEach(phase -> { element.getPhases().forEach(phase -> {
YAPIONObject phaseObject = new YAPIONObject(); SimulatorConfig.Phase phaseConfig = new SimulatorConfig.Phase();
phaseObject.add("tickOffset", phase.getTickOffset()); phaseConfig.setTickOffset(phase.getTickOffset());
phaseObject.add("lifetime", phase.getLifetime()); phaseConfig.setLifetime(phase.getLifetime());
phaseObject.add("order", phase.getOrder()); phaseConfig.setOrder(phase.getOrder());
phase.saveExtra(phaseObject); phase.saveExtra(phaseConfig);
phases.add(phaseObject); elementConfig.getPhases().add(phaseConfig);
}); });
elementObject.add("phases", phases); groupConfig.getElements().add(elementConfig);
elements.add(elementObject);
}); });
groupObject.add("elements", elements); config.getGroups().add(groupConfig);
groups.add(groupObject);
}); });
simulatorObject.add("groups", groups);
File file = new File(directory, simulator.getName() + ".sim"); File file = new File(directory, simulator.getName() + ".sim");
try { try {
simulatorObject.toJSONLossy(new FileOutput(file)).close(); SimulatorConfig.STORE.save(file, config);
} catch (IOException e) { } catch (IOException e) {
// Ignore // Ignore
} }
@@ -19,6 +19,7 @@
package de.steamwar.bausystem.features.simulator.storage; package de.steamwar.bausystem.features.simulator.storage;
import de.steamwar.bausystem.config.legacy.YapionReader;
import de.steamwar.bausystem.features.simulator.data.Simulator; import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup; import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement; import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
@@ -26,18 +27,23 @@ import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import yapion.exceptions.YAPIONException;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import static de.steamwar.bausystem.config.legacy.YapionReader.*;
/**
* Loads the legacy per player .yapion files containing multiple simulators, they are
* deleted after loading and saved in the current format by the {@link SimulatorSaver}.
*/
@Deprecated
public class YAPIONFormatSimulatorLoader implements SimulatorLoader { public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
@Override @Override
@@ -46,10 +52,10 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
return Optional.empty(); return Optional.empty();
} }
YAPIONObject yapionObject; Map<String, Object> yapionObject;
try { try {
yapionObject = YAPIONParser.parse(file); yapionObject = YapionReader.parse(Files.readString(file.toPath(), StandardCharsets.UTF_8));
} catch (YAPIONException | IOException e) { } catch (IllegalArgumentException | IOException e) {
return Optional.empty(); return Optional.empty();
} }
@@ -57,28 +63,28 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
SteamwarUser steamwarUser = SteamwarUser.byId(Integer.parseInt(name)); SteamwarUser steamwarUser = SteamwarUser.byId(Integer.parseInt(name));
List<Simulator> simulators = new ArrayList<>(); List<Simulator> simulators = new ArrayList<>();
for (String s : yapionObject.getKeys()) { for (String s : yapionObject.keySet()) {
String newName = steamwarUser.getUserName() + (s.isEmpty() ? "" : "_" + s); String newName = steamwarUser.getUserName() + (s.isEmpty() ? "" : "_" + s);
YAPIONArray content = yapionObject.getArray(s); List<Map<?, ?>> content = getObjects(yapionObject, s);
if (content.isEmpty()) continue; if (content.isEmpty()) continue;
Simulator simulator = new Simulator(newName); Simulator simulator = new Simulator(newName);
for (YAPIONObject element : content.streamObject().collect(Collectors.toList())) { for (Map<?, ?> element : content) {
SimulatorGroup simulatorGroup = new SimulatorGroup(); SimulatorGroup simulatorGroup = new SimulatorGroup();
simulator.add(simulatorGroup); simulator.add(simulatorGroup);
TNTElement tntElement = new TNTElement(new Vector(element.getDouble("positionX"), element.getDouble("positionY"), element.getDouble("positionZ"))); TNTElement tntElement = new TNTElement(new Vector(requireDouble(element, "positionX"), requireDouble(element, "positionY"), requireDouble(element, "positionZ")));
tntElement.setDisabled(element.getBooleanOrDefault("disabled", false)); // IDK if this existed back then? tntElement.setDisabled(getBoolean(element, "disabled", false)); // IDK if this existed back then?
tntElement.setMaterial(Material.valueOf(element.getStringOrDefault("material", "TNT"))); tntElement.setMaterial(Material.valueOf(getString(element, "material", "TNT")));
simulatorGroup.add(tntElement); simulatorGroup.add(tntElement);
TNTPhase tntPhase = new TNTPhase(); TNTPhase tntPhase = new TNTPhase();
tntPhase.setLifetime(element.getIntOrDefault("fuseTicks", 80)); tntPhase.setLifetime(getInt(element, "fuseTicks", 80));
tntPhase.setCount(element.getIntOrDefault("count", 1)); tntPhase.setCount(getInt(element, "count", 1));
tntPhase.setTickOffset(element.getIntOrDefault("tickOffset", 0)); tntPhase.setTickOffset(getInt(element, "tickOffset", 0));
tntPhase.setXJump(element.getBooleanOrDefault("xVelocity", false)); tntPhase.setXJump(getBoolean(element, "xVelocity", false));
tntPhase.setYJump(element.getBooleanOrDefault("yVelocity", false)); tntPhase.setYJump(getBoolean(element, "yVelocity", false));
tntPhase.setZJump(element.getBooleanOrDefault("zVelocity", false)); tntPhase.setZJump(getBoolean(element, "zVelocity", false));
tntPhase.setOrder(element.getBooleanOrDefault("comparator", false) ? 2 : 0); tntPhase.setOrder(getBoolean(element, "comparator", false) ? 2 : 0);
tntElement.add(tntPhase); tntElement.add(tntPhase);
} }
simulators.add(simulator); simulators.add(simulator);
@@ -87,4 +93,8 @@ public class YAPIONFormatSimulatorLoader implements SimulatorLoader {
file.delete(); file.delete();
return Optional.of(simulators); return Optional.of(simulators);
} }
private double requireDouble(Map<?, ?> element, String key) {
return ((Number) element.get(key)).doubleValue();
}
} }
@@ -0,0 +1,64 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.slaves.laufbau;
import de.steamwar.bausystem.config.ConfigStore;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.features.slaves.laufbau.converter.LaufbauConfigV1ToV2Converter;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.util.HashSet;
import java.util.Set;
/**
* The current laufbau config of a player. It is stored as JSON, fields missing
* in the stored JSON keep the default value of their initializer, so adding a
* new field does not need a new version.
* <p>
* When a field gets removed, renamed or its meaning changes, copy this class
* to LaufbauConfigV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a
* {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to
* this class and register it in the {@link #STORE}.
*/
@Getter
@Setter
public class LaufbauConfig implements VersionedConfig {
public static final int VERSION = 2;
/**
* LaufbauConfigV1 is stored as Base64 of gzipped YAPION, the current version as JSON
*/
public static final ConfigStore<LaufbauConfig> STORE = new ConfigStore<>(LaufbauConfig.class, VERSION, LaufbauConfig::new)
.legacy(s -> !s.startsWith("{"), LaufbauConfigV1::parse)
.converter(new LaufbauConfigV1ToV2Converter())
.userConfig("bausystem-laufbau")
.removeIf(config -> config.getDeactivated().isEmpty());
@Setter(AccessLevel.NONE)
protected int version = VERSION;
/**
* Identifiers of the deactivated {@link BlockBoundingBox}es, see {@link LaufbauUtils#identifier(BlockBoundingBox)}
*/
protected Set<String> deactivated = new HashSet<>();
}
@@ -0,0 +1,65 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.slaves.laufbau;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.config.legacy.YapionReader;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
* The legacy laufbau config that was stored as Base64 of the gzipped YAPION,
* an object with the deactivated identifiers as keys.
*/
@Getter
@Setter
@Deprecated
public class LaufbauConfigV1 implements VersionedConfig {
public static final int VERSION = 1;
@Setter(AccessLevel.NONE)
protected int version = VERSION;
protected Set<String> deactivated = new HashSet<>();
public static LaufbauConfigV1 parse(String base64) {
String yapion;
try (InputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64)))) {
yapion = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
LaufbauConfigV1 config = new LaufbauConfigV1();
config.deactivated.addAll(YapionReader.parse(yapion).keySet());
return config;
}
}
@@ -20,63 +20,22 @@
package de.steamwar.bausystem.features.slaves.laufbau; package de.steamwar.bausystem.features.slaves.laufbau;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.core.SWPlayer;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import de.steamwar.sql.UserConfig;
import lombok.Cleanup;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import yapion.hierarchy.output.StreamOutput;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.YAPIONParser;
import yapion.parser.options.StreamOptions;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@UtilityClass @UtilityClass
public class LaufbauUtils { public class LaufbauUtils {
public static class LaufbauComponent implements SWPlayer.Component {
private YAPIONObject yapionObject = new YAPIONObject();
@Override
@SneakyThrows
public void onMount(SWPlayer player) {
String config = UserConfig.getConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau");
if (config == null) {
return;
}
byte[] bytes = Base64.getDecoder().decode(config);
@Cleanup GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(bytes));
yapionObject = new YAPIONParser(gzipInputStream, new StreamOptions().stopOnStreamEnd(true)).parse().result();
}
@Override
@SneakyThrows
public void onUnmount(SWPlayer player) {
if (yapionObject.isEmpty()) {
UserConfig.removePlayerConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau");
return;
}
@Cleanup ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
@Cleanup GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
yapionObject.toYAPION(new StreamOutput(gzipOutputStream)).close();
UserConfig.updatePlayerConfig(player.getPlayer().getUniqueId(), "bausystem-laufbau", Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
}
}
public static Cuboid pixelCuboid(double pixelX, double pixelY, double pixelZ, double pixelDX, double pixelDY, double pixelDZ) { public static Cuboid pixelCuboid(double pixelX, double pixelY, double pixelZ, double pixelDX, double pixelDY, double pixelDZ) {
return new Cuboid(pixelToBlock(pixelX), pixelToBlock(pixelY), pixelToBlock(pixelZ), pixelToBlock(pixelDX), pixelToBlock(pixelDY), pixelToBlock(pixelDZ)); return new Cuboid(pixelToBlock(pixelX), pixelToBlock(pixelY), pixelToBlock(pixelZ), pixelToBlock(pixelDX), pixelToBlock(pixelDY), pixelToBlock(pixelDZ));
} }
@@ -123,7 +82,7 @@ public class LaufbauUtils {
return false; return false;
} }
String identifier = identifier(bb); String identifier = identifier(bb);
return SWPlayer.of(p).getComponentOrDefault(LaufbauComponent.class, LaufbauComponent::new).yapionObject.containsKey(identifier); return LaufbauConfig.STORE.get(p).getDeactivated().contains(identifier);
} }
@SneakyThrows @SneakyThrows
@@ -132,11 +91,9 @@ public class LaufbauUtils {
return; return;
} }
String identifier = identifier(bb); String identifier = identifier(bb);
YAPIONObject yapionObject = SWPlayer.of(p).getComponentOrDefault(LaufbauComponent.class, LaufbauComponent::new).yapionObject; Set<String> deactivated = LaufbauConfig.STORE.get(p).getDeactivated();
if (yapionObject.containsKey(identifier)) { if (!deactivated.remove(identifier)) {
yapionObject.remove(identifier); deactivated.add(identifier);
} else {
yapionObject.put(identifier, new YAPIONObject());
} }
} }
} }
@@ -0,0 +1,40 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.slaves.laufbau.converter;
import de.steamwar.bausystem.config.ConfigConverter;
import de.steamwar.bausystem.features.slaves.laufbau.LaufbauConfig;
import de.steamwar.bausystem.features.slaves.laufbau.LaufbauConfigV1;
@Deprecated
public class LaufbauConfigV1ToV2Converter implements ConfigConverter<LaufbauConfigV1, LaufbauConfig> {
@Override
public int version() {
return LaufbauConfigV1.VERSION;
}
@Override
public LaufbauConfig convert(LaufbauConfigV1 old) {
LaufbauConfig config = new LaufbauConfig();
config.getDeactivated().addAll(old.getDeactivated());
return config;
}
}
@@ -35,8 +35,8 @@ public class SmartPlaceCommand extends SWCommand {
@Register(description = {"SMART_PLACE_HELP", "SMART_PLACE_INFO"}) @Register(description = {"SMART_PLACE_HELP", "SMART_PLACE_INFO"})
public void genericToggle(Player p) { public void genericToggle(Player p) {
boolean smartPlace = Config.getInstance().get(p).getPlainValueOrDefault("smartPlace", false); boolean smartPlace = Config.STORE.get(p).isSmartPlace();
Config.getInstance().get(p).put("smartPlace", !smartPlace); Config.STORE.get(p).setSmartPlace(!smartPlace);
if (!smartPlace) { if (!smartPlace) {
SWUtils.sendToActionbar(p, BauSystem.MESSAGE.parse("SMART_PLACE_ENABLE", p)); SWUtils.sendToActionbar(p, BauSystem.MESSAGE.parse("SMART_PLACE_ENABLE", p));
BauSystem.MESSAGE.send("SMART_PLACE_INFO", p); BauSystem.MESSAGE.send("SMART_PLACE_INFO", p);
@@ -19,12 +19,10 @@
package de.steamwar.bausystem.features.smartplace; package de.steamwar.bausystem.features.smartplace;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission; import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.configplayer.Config; import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
@@ -34,104 +32,98 @@ import org.bukkit.block.data.*;
import org.bukkit.block.data.type.*; import org.bukkit.block.data.type.*;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.Action; import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.util.RayTraceResult;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Linked @Linked
public class SmartPlaceListener implements Listener { public class SmartPlaceListener implements Listener {
private static final Set<Material> CONTAINERS = new HashSet<>(); private static final Set<Material> IGNORED = Set.of(Material.TNT, Material.REDSTONE_ORE, Material.BEEHIVE, Material.SEA_PICKLE);
private static final Set<Material> IGNORED = new HashSet<>();
static { private static final Set<UUID> FAKED_SNEAK = new HashSet<>();
World world = Bukkit.getWorlds().get(0);
Block block = world.getBlockAt(0, 0, 0);
block.setType(Material.AIR);
BlockState state = block.getState();
for (Material material : Material.values()) {
if (material.isLegacy()) continue;
if (!material.isInteractable() && !material.isBlock()) continue;
BlockData blockData = material.createBlockData();
block.setBlockData(blockData);
if (block.getState() instanceof TileState) {
CONTAINERS.add(material);
} else if (blockData instanceof Stairs) {
CONTAINERS.add(material);
}
state.update(true, false);
}
CONTAINERS.add(Material.GRINDSTONE);
CONTAINERS.remove(Material.COMPARATOR);
state.update(true, false);
IGNORED.add(Material.TNT); private static boolean isContainer(Block block) {
IGNORED.add(Material.REDSTONE_ORE); if (block.getType() == Material.COMPARATOR) return false;
IGNORED.add(Material.BEEHIVE); if (block.getType() == Material.GRINDSTONE) return true;
IGNORED.add(Material.SEA_PICKLE); return block.getState() instanceof TileState || block.getBlockData() instanceof Stairs;
IGNORED.remove(Material.STONE);
IGNORED.remove(Material.BARRIER);
} }
private static final Set<Player> SMART_PLACING = new HashSet<>(); private static boolean isReallySneaking(Player player) {
private static final Set<Player> WAS_EXECUTED = new HashSet<>(); return player.isSneaking() && !FAKED_SNEAK.contains(player.getUniqueId());
}
public SmartPlaceListener() { private static boolean shouldSmartPlace(Block block, BlockFace blockFace, Material heldType) {
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> { boolean smartPlace = false;
if (!Permission.BUILD.hasPermission(player)) return packet; if (block.getType().isInteractable() || block.getType() == Material.NOTE_BLOCK) {
if (!Config.getInstance().get(player).getPlainValueOrDefault("smartPlace", false)) return packet; smartPlace = true;
RayTraceResult rayTraceResult = player.rayTraceBlocks(6); }
Block block = rayTraceResult != null ? rayTraceResult.getHitBlock() : null; if (isContainer(block)) {
BlockFace blockFace = rayTraceResult != null ? rayTraceResult.getHitBlockFace() : null; if (heldType == Material.TNT) {
boolean shouldSneak = false; if (block.getType() == Material.CHEST || block.getType() == Material.BARREL || block.getType().name().endsWith("SHULKER_BOX")) {
if (block != null) { smartPlace = false;
ItemStack itemStack = player.getInventory().getItemInMainHand();
if (block.getType().isInteractable() || block.getType() == Material.NOTE_BLOCK) {
shouldSneak = true;
}
if (CONTAINERS.contains(block.getType())) {
if (itemStack.getType() == Material.TNT) {
if (block.getType() == Material.CHEST || block.getType() == Material.BARREL || block.getType().name().endsWith("SHULKER_BOX")) {
shouldSneak = false;
}
}
} else {
shouldSneak = false;
}
if (IGNORED.contains(block.getType())) {
shouldSneak = false;
}
if (blockFace == BlockFace.DOWN && (block.getType() == Material.REPEATER || block.getType() == Material.COMPARATOR) && itemStack.getType().isSolid()) {
shouldSneak = true;
} }
} }
boolean sneaking = player.isSneaking(); } else {
if (sneaking) SMART_PLACING.add(player); smartPlace = false;
player.setSneaking(shouldSneak || sneaking); }
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { if (IGNORED.contains(block.getType())) {
SMART_PLACING.remove(player); smartPlace = false;
player.setSneaking(sneaking); }
}, 0); if (blockFace == BlockFace.DOWN && (block.getType() == Material.REPEATER || block.getType() == Material.COMPARATOR) && heldType.isSolid()) {
return packet; smartPlace = true;
}
return smartPlace;
}
private static boolean smartPlaceEnabled(Player player) {
return Config.STORE.get(player).isSmartPlace();
}
@EventHandler(priority = EventPriority.LOW)
public void onSmartPlace(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
Player player = event.getPlayer();
if (player.getGameMode() == GameMode.SPECTATOR) return;
if (!Permission.BUILD.hasPermission(player)) return;
if (!smartPlaceEnabled(player)) return;
if (player.isSneaking()) return;
ItemStack itemStack = event.getItem();
if (itemStack == null || itemStack.getType().isAir()) return;
if (!shouldSmartPlace(event.getClickedBlock(), event.getBlockFace(), itemStack.getType())) return;
player.setSneaking(true);
FAKED_SNEAK.add(player.getUniqueId());
Bukkit.getScheduler().runTask(BauSystem.getInstance(), () -> {
if (FAKED_SNEAK.remove(player.getUniqueId())) {
player.setSneaking(false);
}
}); });
} }
@EventHandler
public void onQuit(PlayerQuitEvent event) {
FAKED_SNEAK.remove(event.getPlayer().getUniqueId());
}
@EventHandler @EventHandler
public void onPlayerInteract(PlayerInteractEvent event) { public void onPlayerInteract(PlayerInteractEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return; if (!smartPlaceEnabled(event.getPlayer())) return;
WAS_EXECUTED.add(event.getPlayer());
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) return; if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) return;
if (!event.getPlayer().isSneaking()) return; if (!isReallySneaking(event.getPlayer())) return;
if (event.getClickedBlock().getType() != Material.REPEATER) return; if (event.getClickedBlock().getType() != Material.REPEATER) return;
if ((event.getItem() == null || event.getItem().getType() == Material.REPEATER)) { if ((event.getItem() == null || event.getItem().getType() == Material.REPEATER)) {
Repeater repeater = (Repeater) event.getClickedBlock().getBlockData(); Repeater repeater = (Repeater) event.getClickedBlock().getBlockData();
@@ -146,9 +138,9 @@ public class SmartPlaceListener implements Listener {
@EventHandler @EventHandler
public void onBlockPlace(BlockPlaceEvent event) { public void onBlockPlace(BlockPlaceEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return; if (!smartPlaceEnabled(event.getPlayer())) return;
if (!SMART_PLACING.contains(event.getPlayer())) { if (!isReallySneaking(event.getPlayer())) {
if (CONTAINERS.contains(event.getBlockAgainst().getType())) { if (isContainer(event.getBlockAgainst())) {
SoundGroup soundGroup = event.getBlockPlaced().getBlockData().getSoundGroup(); SoundGroup soundGroup = event.getBlockPlaced().getBlockData().getSoundGroup();
event.getPlayer().playSound(event.getBlockPlaced().getLocation(), soundGroup.getPlaceSound(), soundGroup.getVolume() * 0.8F, soundGroup.getPitch() * 0.8F); event.getPlayer().playSound(event.getBlockPlaced().getLocation(), soundGroup.getPlaceSound(), soundGroup.getVolume() * 0.8F, soundGroup.getPitch() * 0.8F);
} }
@@ -35,18 +35,15 @@ public class BlockCounter {
public final Map<Region, BlockCount> blockCountMap = new HashMap<>(); public final Map<Region, BlockCount> blockCountMap = new HashMap<>();
public boolean isActive(final Player p) { public boolean isActive(final Player p) {
if (!Config.getInstance().get(p).containsKey("blockCounter")) { return Config.STORE.get(p).isBlockCounter();
return false;
}
return Config.getInstance().get(p).getPlainValue("blockCounter");
} }
public void enable(final Player p) { public void enable(final Player p) {
Config.getInstance().get(p).put("blockCounter", true); Config.STORE.get(p).setBlockCounter(true);
} }
public void disable(final Player p) { public void disable(final Player p) {
Config.getInstance().get(p).put("blockCounter", false); Config.STORE.get(p).setBlockCounter(false);
} }
public String getMessage(Player player, int count, int tntCount, long tick, long lastTick) { public String getMessage(Player player, int count, int tntCount, long tick, long lastTick) {
@@ -165,6 +165,7 @@ public class TPSSystem implements Listener {
} }
@Register(value = "default", description = "TPSLIMIT_DEFAULT_HELP") @Register(value = "default", description = "TPSLIMIT_DEFAULT_HELP")
@Register(value = "rate")
public void reset(@Validator Player player) { public void reset(@Validator Player player) {
TickManager.impl.setTickRate(20.0F); TickManager.impl.setTickRate(20.0F);
sendTickRateChange(); sendTickRateChange();
@@ -187,15 +188,18 @@ public class TPSSystem implements Listener {
} }
@Register(value = {"rate", "0"}, description = "TICK_FREEZE_HELP") @Register(value = {"rate", "0"}, description = "TICK_FREEZE_HELP")
@Register(value = "freeze", description = "TICK_FREEZE_HELP_2") public void rate(@Validator Player player) {
public void freeze(@Validator Player player) {
TickManager.impl.setFreeze(true); TickManager.impl.setFreeze(true);
sendTickRateChange(); sendTickRateChange();
} }
@Register(value = "unfreeze", description = "TICK_UNFREEZE_HELP") @Register(value = "freeze", description = "TICK_FREEZE_HELP_2")
public void unfreeze(@Validator Player player) { public void freezeToggle(@Validator Player player) {
TickManager.impl.setTickRate(20.0F); if (TickManager.impl.isFrozen()) {
TickManager.impl.setTickRate(20.0F);
} else {
TickManager.impl.setFreeze(true);
}
sendTickRateChange(); sendTickRateChange();
} }
} }
@@ -153,7 +153,11 @@ public class TraceCommand extends SWCommand {
@Register(value = "isolate", description = "TRACE_COMMAND_HELP_ISOLATE") @Register(value = "isolate", description = "TRACE_COMMAND_HELP_ISOLATE")
public void isolate(@Validator Player player, Trace trace, @ErrorMessage("TRACE_RECORD_ID_INVALID") TNTPoint... records) { public void isolate(@Validator Player player, Trace trace, @ErrorMessage("TRACE_RECORD_ID_INVALID") TNTPoint... records) {
TraceManager.instance.isolate(player, records); if (records.length == 0) {
TraceManager.instance.isolate(player, trace, trace.getRecords().toArray(TNTPoint[]::new));
} else {
TraceManager.instance.isolate(player, trace, records);
}
BauSystem.MESSAGE.send("TRACE_MESSAGE_ISOLATE", player); BauSystem.MESSAGE.send("TRACE_MESSAGE_ISOLATE", player);
} }
@@ -347,9 +347,10 @@ public class TraceManager implements Listener {
* Toggles the isolated render for the given records and player * Toggles the isolated render for the given records and player
* *
* @param player the player the trace is shown to * @param player the player the trace is shown to
* @param records the record for which isolation is toggled * @param ptrace the trace for whitch isolation is toggled
* @param records the records of the trace for which isolation is toggled
*/ */
public void isolate(Player player, TNTPoint... records) { public void isolate(Player player, Trace ptrace, TNTPoint... records) {
unfollow(player); unfollow(player);
Region region = Region.getRegion(player.getLocation()); Region region = Region.getRegion(player.getLocation());
@@ -374,12 +375,20 @@ public class TraceManager implements Listener {
isolateFlag.toggleId(record.getTntId()); isolateFlag.toggleId(record.getTntId());
} }
if (isolateFlag.isEmpty() && playerTraceShowData.hasViewFlagOnly(IsolateFlag.class) && records.length != 0) {
playerTraceShowData.removeViewFlag(IsolateFlag.class);
}
PlayerTraceShowData finalPlayerTraceShowData = playerTraceShowData; PlayerTraceShowData finalPlayerTraceShowData = playerTraceShowData;
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> { tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
trace.render(player, finalPlayerTraceShowData); if (trace.getUuid() == ptrace.getUuid() || finalPlayerTraceShowData.hasNoViewFlags()) {
followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> { trace.render(player, finalPlayerTraceShowData);
trace.render(follower, finalPlayerTraceShowData); followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> {
}); trace.render(follower, finalPlayerTraceShowData);
});
} else {
trace.hide(player);
}
}); });
} }
@@ -135,6 +135,13 @@ public class TraceRecorder implements Listener {
Iterator<TNTPrimed> iter = trackedTNT.getOrDefault(region, Collections.emptyList()).iterator(); Iterator<TNTPrimed> iter = trackedTNT.getOrDefault(region, Collections.emptyList()).iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
TNTPrimed tnt = iter.next(); TNTPrimed tnt = iter.next();
if (tnt.isDead()) {
iter.remove();
tntSpawnRegion.remove(tnt);
historyMap.remove(tnt);
tntSpawnRegion.remove(tnt);
continue;
}
if (tnt.getFuseTicks() == 80) continue; if (tnt.getFuseTicks() == 80) continue;
TNTPoint record = record(tnt, wrappedTrace, Collections.emptyList()); TNTPoint record = record(tnt, wrappedTrace, Collections.emptyList());
if (record == null) { if (record == null) {
@@ -95,4 +95,8 @@ public class PlayerTraceShowData {
public void addViewFlag(ViewFlag viewFlag) { public void addViewFlag(ViewFlag viewFlag) {
viewFlags.put(viewFlag.getClass(), viewFlag); viewFlags.put(viewFlag.getClass(), viewFlag);
} }
public <T extends ViewFlag> void removeViewFlag(Class<T> clazz) {
viewFlags.remove(clazz);
}
} }
@@ -36,21 +36,18 @@ import org.bukkit.entity.Player;
import org.bukkit.util.Transformation; import org.bukkit.util.Transformation;
import org.joml.Quaternionf; import org.joml.Quaternionf;
import org.joml.Vector3f; import org.joml.Vector3f;
import yapion.hierarchy.types.YAPIONValue;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DETAILS;
/** /**
* Wrapper for the rendering of a record bundle * Wrapper for the rendering of a record bundle
*/ */
public class TraceEntity extends RBlockDisplay { public class TraceEntity extends RBlockDisplay {
private static final float TNT_VISUAL_SCALE = 0.98F; public static final float TNT_VISUAL_SCALE = 0.98F;
private static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F; public static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F;
private static final Transformation TNT_VISUAL_TRANSFORM = new Transformation( public static final Transformation TNT_VISUAL_TRANSFORM = new Transformation(
new Vector3f(TNT_VISUAL_OFFSET, 0.0F, TNT_VISUAL_OFFSET), new Vector3f(TNT_VISUAL_OFFSET, 0.0F, TNT_VISUAL_OFFSET),
new Quaternionf(0, 0, 0, 1), new Quaternionf(0, 0, 0, 1),
new Vector3f(TNT_VISUAL_SCALE, TNT_VISUAL_SCALE, TNT_VISUAL_SCALE), new Vector3f(TNT_VISUAL_SCALE, TNT_VISUAL_SCALE, TNT_VISUAL_SCALE),
@@ -102,7 +99,7 @@ public class TraceEntity extends RBlockDisplay {
* @param player the player the message should be printed for * @param player the player the message should be printed for
*/ */
public void printIntoChat(Player player) { public void printIntoChat(Player player) {
if (!Config.getInstance().get(player).getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) { if (!Config.STORE.get(player).isTntClickDetails()) {
return; return;
} }
TNTPoint representative = records.get(0); TNTPoint representative = records.get(0);
@@ -131,6 +131,7 @@ public abstract class ViewFlag {
if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) { if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
RBlockDisplay y = new RBlockDisplay(server, yLocation); RBlockDisplay y = new RBlockDisplay(server, yLocation);
y.setBlock(Material.WHITE_STAINED_GLASS.createBlockData()); y.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
y.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM);
} }
Location secoundLocation; Location secoundLocation;
@@ -143,6 +144,7 @@ public abstract class ViewFlag {
if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) { if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
RBlockDisplay second = new RBlockDisplay(server, secoundLocation); RBlockDisplay second = new RBlockDisplay(server, secoundLocation);
second.setBlock(Material.WHITE_STAINED_GLASS.createBlockData()); second.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
second.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM);
} }
} }
} }
@@ -51,6 +51,10 @@ public class IsolateFlag extends ViewFlag {
} }
} }
public boolean isEmpty() {
return tntToIsolate.isEmpty();
}
@Override @Override
public Stream<TNTPoint> filter(Stream<TNTPoint> records) { public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
if (tntToIsolate.isEmpty()) return records; if (tntToIsolate.isEmpty()) return records;
@@ -30,7 +30,6 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import yapion.hierarchy.types.YAPIONObject;
@Linked @Linked
public class NightVisionCommand extends SWCommand implements Listener { public class NightVisionCommand extends SWCommand implements Listener {
@@ -41,15 +40,15 @@ public class NightVisionCommand extends SWCommand implements Listener {
@Register(description = "NIGHT_VISION_HELP") @Register(description = "NIGHT_VISION_HELP")
public void genericCommand(Player p) { public void genericCommand(Player p) {
YAPIONObject yapionObject = Config.getInstance().get(p); Config config = Config.STORE.get(p);
boolean value = !yapionObject.getBooleanOrDefault("nightvision", false); boolean value = !config.isNightVision();
yapionObject.put("nightvision", value); config.setNightVision(value);
setNightVision(p, value); setNightVision(p, value);
} }
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
setNightVision(event.getPlayer(), Config.getInstance().get(event.getPlayer()).getBooleanOrDefault("nightvision", false)); setNightVision(event.getPlayer(), Config.STORE.get(event.getPlayer()).isNightVision());
} }
private void setNightVision(Player p, boolean value) { private void setNightVision(Player p, boolean value) {
@@ -22,129 +22,79 @@ package de.steamwar.bausystem.features.util;
import com.comphenix.tinyprotocol.TinyProtocol; import com.comphenix.tinyprotocol.TinyProtocol;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent; import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommand;
import de.steamwar.core.ProtocolWrapper; import de.steamwar.core.ProtocolWrapper;
import de.steamwar.core.SWPlayer; import de.steamwar.core.SWPlayer;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.*; import io.papermc.paper.event.player.PlayerFailMoveEvent;
import net.minecraft.world.entity.player.Abilities; import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
import net.minecraft.world.level.GameType;
import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockCanBuildEvent; import org.bukkit.event.block.BlockCanBuildEvent;
import org.bukkit.event.player.PlayerGameModeChangeEvent; import org.bukkit.event.player.PlayerGameModeChangeEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerToggleFlightEvent; import org.bukkit.event.player.PlayerToggleFlightEvent;
import java.util.function.BiFunction;
@Linked @Linked
public class NoClipCommand extends SWCommand implements Listener { public class NoClipCommand extends SWCommand implements Listener {
public static class NoClipData implements SWPlayer.Component { public static class NoClipData implements SWPlayer.Component {
private long lastTick = -1;
@Override @Override
public void onUnmount(SWPlayer player) { public void onUnmount(SWPlayer swPlayer) {
player.setGameMode(GameMode.CREATIVE); Player player = swPlayer.getPlayer();
showClientGameMode(player, GameMode.CREATIVE, GameMode.CREATIVE);
} }
} }
public NoClipCommand() { public NoClipCommand() {
super("noclip", "nc"); super("noclip", "nc");
BiFunction<Player, ServerboundMovePlayerPacket, Object> first = (player, o) -> {
NoClipData noClipData = SWPlayer.of(player).getComponent(NoClipData.class).orElse(null);
if (noClipData == null) return o;
if (noClipData.lastTick == TPSUtils.currentTick.get()) return o;
setInternalGameMode(player, GameMode.SPECTATOR);
noClipData.lastTick = TPSUtils.currentTick.get();
return o;
};
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, first);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, first);
BiFunction<Player, Object, Object> second = (player, o) -> {
NoClipData noClipData = SWPlayer.of(player).getComponent(NoClipData.class).orElse(null);
if (noClipData == null) return o;
setInternalGameMode(player, GameMode.CREATIVE);
noClipData.lastTick = TPSUtils.currentTick.get();
return o;
};
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, second);
TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, second);
TinyProtocol.instance.addFilter(ServerboundContainerClickPacket.class, second);
BiFunction<Player, ServerboundSetCreativeModeSlotPacket, Object> third = (player, o) -> {
if (SWPlayer.of(player).hasComponent(NoClipData.class)) {
int index = o.slotNum();
if (index >= 36 && index <= 44) {
index -= 36;
} else if (index > 44) {
index -= 5;
} else if (index <= 8) {
index = index - 8 + 36;
}
player.getInventory().setItem(index, CraftItemStack.asBukkitCopy(o.itemStack()));
if (index < 9) player.getInventory().setHeldItemSlot(index);
player.updateInventory();
}
return o;
};
TinyProtocol.instance.addFilter(ServerboundSetCreativeModeSlotPacket.class, third);
} }
private void setInternalGameMode(Player player, GameMode gameMode) { @Register
((CraftPlayer) player).getHandle().gameMode.gameModeForPlayer = GameType.byId(gameMode.getValue());
}
@Register(help = true)
public void genericCommand(@Validator Player player) { public void genericCommand(@Validator Player player) {
SWPlayer swPlayer = SWPlayer.of(player); SWPlayer swPlayer = SWPlayer.of(player);
if (swPlayer.hasComponent(NoClipData.class)) { if (swPlayer.hasComponent(NoClipData.class)) {
swPlayer.removeComponent(NoClipData.class); swPlayer.removeComponent(NoClipData.class);
} else { } else {
player.setGameMode(GameMode.SPECTATOR); player.setGameMode(GameMode.CREATIVE);
Abilities abilities = (((CraftPlayer) player).getHandle()).getAbilities(); player.setAllowFlight(true);
abilities.mayBuild = true; player.setFlying(true);
abilities.mayfly = true;
swPlayer.setComponent(new NoClipData()); swPlayer.setComponent(new NoClipData());
BauSystem.MESSAGE.send("OTHER_NOCLIP_SLOT_INFO", player); BauSystem.MESSAGE.send("OTHER_NOCLIP_SLOT_INFO", player);
TinyProtocol.instance.sendPacket(player, new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, 1F)); showClientGameMode(player, GameMode.CREATIVE, GameMode.SPECTATOR);
pseudoGameMode(player, GameMode.SPECTATOR);
} }
} }
@EventHandler @EventHandler
public void onBauMemberUpdate(BauMemberUpdateEvent event) { public void onPlayerFailMove(PlayerFailMoveEvent event) {
event.getNewSpectator().forEach(player -> { if (event.getFailReason() != PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK) return;
SWPlayer.of(player).removeComponent(NoClipData.class); if (!SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) return;
}); event.setAllowed(true);
} }
@EventHandler(ignoreCancelled = true) @EventHandler
public void onBauMemberUpdate(BauMemberUpdateEvent event) {
event.getNewSpectator().forEach(player -> SWPlayer.of(player).removeComponent(NoClipData.class));
}
@EventHandler
public void onPlayerGameModeChange(PlayerGameModeChangeEvent event) { public void onPlayerGameModeChange(PlayerGameModeChangeEvent event) {
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) { if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setCancelled(true); event.setCancelled(true);
} }
} }
@EventHandler(ignoreCancelled = true) @EventHandler
public void onBlock(BlockCanBuildEvent event) { public void onBlock(BlockCanBuildEvent event) {
if (event.getPlayer() == null) return;
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) { if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setBuildable(true); event.setBuildable(true);
} }
} }
@EventHandler(ignoreCancelled = true) @EventHandler
public void onPlayerToggleFlight(PlayerToggleFlightEvent event) { public void onPlayerToggleFlight(PlayerToggleFlightEvent event) {
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) { if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setCancelled(true); event.setCancelled(true);
@@ -152,20 +102,13 @@ public class NoClipCommand extends SWCommand implements Listener {
} }
} }
@EventHandler(ignoreCancelled = true) /**
public void onPlayerTeleport(PlayerTeleportEvent event) { * @param player player
if (event.getCause() != PlayerTeleportEvent.TeleportCause.SPECTATE) { * @param localMode GameMode for breaking and placing
return; * @param listedMode GameMode for noPhysics
} */
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) { private static void showClientGameMode(Player player, GameMode localMode, GameMode listedMode) {
event.setCancelled(true); TinyProtocol.instance.sendPacket(player, new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, localMode.getValue()));
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> { TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.GAMEMODE, new GameProfile(player.getUniqueId(), player.getName()), listedMode));
event.getPlayer().setSpectatorTarget(null);
}, 1L);
}
}
private static void pseudoGameMode(Player player, GameMode gameMode) {
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.GAMEMODE, new GameProfile(player.getUniqueId(), player.getName()), gameMode));
} }
} }
@@ -32,26 +32,22 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.EquipmentSlot;
import yapion.hierarchy.types.YAPIONObject;
import yapion.hierarchy.types.YAPIONValue;
@Linked @Linked
public class TNTClickListener extends SWCommand implements Listener { public class TNTClickListener extends SWCommand implements Listener {
public static final String TNT_CLICK_DETAILS = "tnt_click_details";
public TNTClickListener() { public TNTClickListener() {
super("tntdetails"); super("tntdetails");
} }
@Register(description = "TNT_DETAILS_COMMAND") @Register(description = "TNT_DETAILS_COMMAND")
public void toggle(Player player) { public void toggle(Player player) {
YAPIONObject yapionObject = Config.getInstance().get(player); Config config = Config.STORE.get(player);
if (yapionObject.getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) { if (config.isTntClickDetails()) {
yapionObject.put(TNT_CLICK_DETAILS, false); config.setTntClickDetails(false);
BauSystem.MESSAGE.send("TNT_DETAILS_OFF", player); BauSystem.MESSAGE.send("TNT_DETAILS_OFF", player);
} else { } else {
yapionObject.put(TNT_CLICK_DETAILS, true); config.setTntClickDetails(true);
BauSystem.MESSAGE.send("TNT_DETAILS_ON", player); BauSystem.MESSAGE.send("TNT_DETAILS_ON", player);
} }
} }
@@ -60,7 +56,7 @@ public class TNTClickListener extends SWCommand implements Listener {
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (event.getHand() != EquipmentSlot.HAND) return; if (event.getHand() != EquipmentSlot.HAND) return;
if (!Config.getInstance().get(event.getPlayer()).getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) { if (!Config.STORE.get(event.getPlayer()).isTntClickDetails()) {
return; return;
} }
@@ -25,7 +25,6 @@ import lombok.Getter;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerTeleportEvent;
import yapion.hierarchy.types.YAPIONObject;
import java.util.*; import java.util.*;
@@ -56,27 +55,22 @@ public class Warp {
warpMap.put(name, this); warpMap.put(name, this);
} }
public Warp(String name, YAPIONObject object) { public Warp(String name, WorldData.WarpEntry warp) {
this.name = name; this.name = name;
double x = object.getPlainValue("x"); location = new Location(Bukkit.getWorlds().get(0), warp.getX(), warp.getY(), warp.getZ(), warp.getYaw(), warp.getPitch());
double y = object.getPlainValue("y"); mat = Material.getMaterial(warp.getMaterial());
double z = object.getPlainValue("z");
float yaw = object.getPlainValue("yaw");
float pitch = object.getPlainValue("pitch");
location = new Location(Bukkit.getWorlds().get(0), x, y, z, yaw, pitch);
mat = Material.getMaterial(object.getPlainValue("material"));
warpMap.put(name, this); warpMap.put(name, this);
} }
public YAPIONObject output() { public WorldData.WarpEntry output() {
YAPIONObject yapionObject = new YAPIONObject(); WorldData.WarpEntry warp = new WorldData.WarpEntry();
yapionObject.add("x", location.getX()); warp.setX(location.getX());
yapionObject.add("y", location.getY()); warp.setY(location.getY());
yapionObject.add("z", location.getZ()); warp.setZ(location.getZ());
yapionObject.add("yaw", location.getYaw()); warp.setYaw(location.getYaw());
yapionObject.add("pitch", location.getPitch()); warp.setPitch(location.getPitch());
yapionObject.add("material", mat.toString()); warp.setMaterial(mat.toString());
return yapionObject; return warp;
} }
public static Set<String> getWarpNames() { public static Set<String> getWarpNames() {
@@ -101,7 +95,7 @@ public class Warp {
public void delete() { public void delete() {
warpMap.remove(name); warpMap.remove(name);
WorldData.getWarpData().remove(name); WorldData.STORE.get().getWarps().remove(name);
} }
public void teleport(Player player) { public void teleport(Player player) {
@@ -34,9 +34,9 @@ import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import yapion.hierarchy.types.YAPIONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map;
@Linked @Linked
public class WarpCommand extends SWCommand implements Disable, Enable { public class WarpCommand extends SWCommand implements Disable, Enable {
@@ -135,17 +135,15 @@ public class WarpCommand extends SWCommand implements Disable, Enable {
@Override @Override
public void enable() { public void enable() {
WorldData.getWarpData().forEach((name, yapionAnyType) -> { WorldData.STORE.get().getWarps().forEach(Warp::new);
new Warp(name, (YAPIONObject) yapionAnyType);
});
Warp.enable(); Warp.enable();
} }
@Override @Override
public void disable() { public void disable() {
YAPIONObject yapionObject = WorldData.getWarpData(); Map<String, WorldData.WarpEntry> warps = WorldData.STORE.get().getWarps();
for (Warp warp : Warp.getWarps()) { for (Warp warp : Warp.getWarps()) {
yapionObject.add(warp.getName(), warp.output()); warps.put(warp.getName(), warp.output());
} }
} }
} }
@@ -0,0 +1,41 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.world;
import de.steamwar.linkage.Linked;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team;
@Linked
public class AntiPlayerCollision implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) {
Scoreboard board = event.getPlayer().getScoreboard();
Team team = board.getTeam("players");
if (team == null) team = board.registerNewTeam("players");
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
team.addPlayer(event.getPlayer());
}
}
@@ -131,6 +131,7 @@ public class BauScoreboard implements Listener {
@EventHandler @EventHandler
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
SWScoreboard.impl.removeScoreboard(event.getPlayer());
ADDITIONAL_SCOREBOARD_LINES.remove(event.getPlayer()); ADDITIONAL_SCOREBOARD_LINES.remove(event.getPlayer());
} }
} }
@@ -20,78 +20,39 @@
package de.steamwar.bausystem.region; package de.steamwar.bausystem.region;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicNode;
import lombok.NonNull; import lombok.NonNull;
import yapion.hierarchy.types.YAPIONObject;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
public abstract class RegionData { public abstract class RegionData {
private final List<Property<?, ?>> properties = new ArrayList<>(); protected final WorldData.RegionEntry data;
protected final Map<String, String> flagData;
protected final YAPIONObject data;
protected final YAPIONObject flagData;
protected final Runnable onChange; protected final Runnable onChange;
protected final Map<Flag<?>, Flag.Value<?>> flagMap = new HashMap<>(); protected final Map<Flag<?>, Flag.Value<?>> flagMap = new HashMap<>();
private final class Property<T, K> { private SchematicNode testblockSchematic;
private final String field;
private final Function<K, T> loader;
private final Function<T, K> writer;
private T value; protected RegionData(WorldData.RegionEntry data, Runnable onChange) {
public Property(String field, Function<K, T> loader, Function<T, K> writer) {
this.field = field;
this.loader = loader;
this.writer = writer;
properties.add(this);
}
public void load() {
if (flagData.containsKey(field)) {
value = loader.apply(flagData.getPlainValue(field));
} else {
value = null;
}
}
public T get() {
return value;
}
public void set(T value) {
this.value = value;
if (value == null) {
flagData.remove(field);
} else {
flagData.put(field, writer.apply(value));
}
}
}
private Property<SchematicNode, Integer> testblockSchematic = new Property<>("testblockSchematic", SchematicNode::byId, SchematicNode::getId);
protected RegionData(YAPIONObject data, Runnable onChange) {
this.data = data; this.data = data;
this.flagData = data.getObjectOrSetDefault("flagStorage", new YAPIONObject()); this.flagData = data.getFlags();
this.onChange = onChange; this.onChange = onChange;
initialize(); initialize();
for (final Flag flag : Flag.getFlags()) { for (final Flag flag : Flag.getFlags()) {
if (!has(flag).isWritable()) continue; if (!has(flag).isWritable()) continue;
try { try {
String s = flagData.getPlainValue(flag.name()); String s = flagData.get(flag.name());
flagMap.put(flag, flag.valueOfValue(s)); flagMap.put(flag, flag.valueOfValue(s));
} catch (Exception e) { } catch (Exception e) {
flagMap.put(flag, (Flag.Value<?>) flag.getDefaultValue()); flagMap.put(flag, (Flag.Value<?>) flag.getDefaultValue());
} }
} }
properties.forEach(Property::load); if (data.getTestblockSchematic() != null) {
testblockSchematic = SchematicNode.byId(data.getTestblockSchematic());
}
} }
protected void initialize() { protected void initialize() {
@@ -126,7 +87,8 @@ public abstract class RegionData {
flagData.remove(flag.name()); flagData.remove(flag.name());
} }
} }
properties.forEach(property -> property.set(null)); testblockSchematic = null;
data.setTestblockSchematic(null);
onChange.run(); onChange.run();
} }
@@ -135,11 +97,12 @@ public abstract class RegionData {
} }
public SchematicNode getTestblockSchematic() { public SchematicNode getTestblockSchematic() {
return testblockSchematic.get(); return testblockSchematic;
} }
public void setTestblockSchematic(SchematicNode schematic) { public void setTestblockSchematic(SchematicNode schematic) {
testblockSchematic.set(schematic); testblockSchematic = schematic;
data.setTestblockSchematic(schematic == null ? null : schematic.getId());
onChange.run(); onChange.run();
} }
@@ -1,101 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.worlddata;
import de.steamwar.sql.SteamwarUser;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import yapion.hierarchy.output.FileOutput;
import yapion.hierarchy.types.YAPIONArray;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.InputStreamCharsets;
import yapion.parser.YAPIONParser;
import yapion.parser.options.FileOptions;
import java.io.File;
import java.util.List;
@UtilityClass
public class SimulatorData {
private final File simulatorsDir = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "simulators");
static {
simulatorsDir.mkdirs();
}
@SneakyThrows
public YAPIONObject getSimulator(Player player) {
File file = getFile(player);
if (!file.exists()) {
return new YAPIONObject();
}
return YAPIONParser.parse(file, new FileOptions().charset(InputStreamCharsets.UTF_8));
}
@SneakyThrows
private void internalSaveSimulator(Player player, YAPIONObject yapionObject) {
File file = getFile(player);
yapionObject.toYAPION(new FileOutput(file)).close();
}
@SneakyThrows
void saveSimulator(SteamwarUser steamwarUser, YAPIONObject yapionObject) {
File file = new File(simulatorsDir, steamwarUser.getId() + ".yapion");
yapionObject.toYAPION(new FileOutput(file)).close();
}
public List<String> listSimulator(Player player) {
List<String> strings = getSimulator(player).getKeys();
strings.remove("");
return strings;
}
public void saveSimulator(Player player, String name) {
YAPIONObject yapionObject = getSimulator(player);
yapionObject.put(name, yapionObject.getArray("").copy());
internalSaveSimulator(player, yapionObject);
}
public void saveSimulator(Player player, YAPIONArray simulator) {
YAPIONObject yapionObject = getSimulator(player);
yapionObject.put("", simulator);
internalSaveSimulator(player, yapionObject);
}
public void loadSimulator(Player player, String name) {
YAPIONObject yapionObject = getSimulator(player);
yapionObject.put("", yapionObject.getArray(name).copy());
internalSaveSimulator(player, yapionObject);
}
public void removeSimulator(Player player, String name) {
YAPIONObject yapionObject = getSimulator(player);
yapionObject.remove(name);
internalSaveSimulator(player, yapionObject);
}
private File getFile(Player player) {
SteamwarUser steamwarUser = SteamwarUser.get(player.getUniqueId());
return new File(simulatorsDir, steamwarUser.getId() + ".yapion");
}
}
@@ -1,7 +1,7 @@
/* /*
* This file is a part of the SteamWar software. * This file is a part of the SteamWar software.
* *
* Copyright (C) 2025 SteamWar.de-Serverteam * Copyright (C) 2026 SteamWar.de-Serverteam
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by * it under the terms of the GNU Affero General Public License as published by
@@ -19,58 +19,83 @@
package de.steamwar.bausystem.worlddata; package de.steamwar.bausystem.worlddata;
import lombok.experimental.UtilityClass; import com.google.gson.GsonBuilder;
import de.steamwar.bausystem.config.ConfigStore;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.worlddata.converter.WorldDataV1ToV2Converter;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import yapion.hierarchy.output.FileOutput;
import yapion.hierarchy.types.YAPIONObject;
import yapion.parser.InputStreamCharsets;
import yapion.parser.YAPIONParser;
import yapion.parser.options.StreamOptions;
import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.util.HashMap;
import java.io.IOException; import java.util.Map;
@UtilityClass /**
public class WorldData { * The current world data. It is stored as JSON, fields missing in the
* stored JSON keep the default value of their initializer, so adding a new
* field does not need a new version.
* <p>
* When a field gets removed, renamed or its meaning changes, copy this class
* to WorldDataV{@link #VERSION} (without the {@link #STORE}), increase {@link #VERSION}, write a
* {@link de.steamwar.bausystem.config.ConfigConverter} from the copy to this class and register it in the {@link #STORE}.
*/
@Getter
@Setter
public class WorldData implements VersionedConfig {
private final File optionsFile = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.yapion"); public static final int VERSION = 2;
private YAPIONObject worldData;
public YAPIONObject getWorldData() { public static final ConfigStore<WorldData> STORE = new ConfigStore<>(WorldData.class, VERSION, WorldData::new)
if (worldData == null) { .gson(new GsonBuilder().setPrettyPrinting().create())
read(); .converter(new WorldDataV1ToV2Converter())
} .file(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.json"))
return worldData; .legacyFile(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "options.yapion"), WorldDataV1::parse);
@Setter(AccessLevel.NONE)
protected int version = VERSION;
/**
* Region name to the data of the region
*/
protected Map<String, RegionEntry> regions = new HashMap<>();
protected RegionEntry globalRegion = new RegionEntry();
/**
* Warp name to warp
*/
protected Map<String, WarpEntry> warps = new HashMap<>();
@Getter
@Setter
public static class RegionEntry {
/**
* Overrides the prototype of the region config, null if not overridden
*/
protected String prototype = null;
/**
* Flag name to flag value name
*/
protected Map<String, String> flags = new HashMap<>();
/**
* Id of the testblock SchematicNode, null if the default testblock is used
*/
protected Integer testblockSchematic = null;
} }
public YAPIONObject getRegionsData() { @Getter
return getWorldData().getYAPIONObjectOrSetDefault("regions", new YAPIONObject()); @Setter
} public static class WarpEntry {
public YAPIONObject getWarpData() { protected double x;
return getWorldData().getYAPIONObjectOrSetDefault("warps", new YAPIONObject()); protected double y;
} protected double z;
protected float yaw;
private void read() { protected float pitch;
worldData = new YAPIONObject(); protected String material;
if (optionsFile.length() != 0) {
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(optionsFile))) {
worldData = YAPIONParser.parse(bufferedInputStream, new StreamOptions().charset(InputStreamCharsets.UTF_8));
} catch (IOException e) {
// Ignored
}
} else {
write();
}
}
public void write() {
try {
worldData.toYAPION(new FileOutput(optionsFile)).close();
} catch (IOException e) {
e.printStackTrace();
}
} }
} }
@@ -0,0 +1,111 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.worlddata;
import de.steamwar.bausystem.config.VersionedConfig;
import de.steamwar.bausystem.config.legacy.YapionReader;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.Map;
/**
* The legacy world data that was stored in the YAPION format in options.yapion.
*/
@Getter
@Setter
@Deprecated
public class WorldDataV1 implements VersionedConfig {
public static final int VERSION = 1;
@Setter(AccessLevel.NONE)
protected int version = VERSION;
/**
* Region name to the data of the region, the global region is stored with the name "global"
*/
protected Map<String, RegionEntry> regions = new HashMap<>();
/**
* Warp name to warp
*/
protected Map<String, WarpEntry> warps = new HashMap<>();
@Getter
@Setter
public static class RegionEntry {
protected String prototype = null;
/**
* Flag name to flag value name and "testblockSchematic" to the SchematicNode id
*/
protected Map<String, Object> flagStorage = new HashMap<>();
}
@Getter
@Setter
public static class WarpEntry {
protected double x;
protected double y;
protected double z;
protected float yaw;
protected float pitch;
protected String material;
}
public static WorldDataV1 parse(String yapion) {
Map<String, Object> object = YapionReader.parse(yapion);
WorldDataV1 worldData = new WorldDataV1();
if (object.get("regions") instanceof Map<?, ?> regions) {
regions.forEach((name, value) -> {
if (!(value instanceof Map<?, ?> region)) return;
RegionEntry entry = new RegionEntry();
if (region.get("prototype") instanceof String prototype) {
entry.prototype = prototype;
}
if (region.get("flagStorage") instanceof Map<?, ?> flagStorage) {
flagStorage.forEach((key, flag) -> entry.flagStorage.put((String) key, flag));
}
worldData.regions.put((String) name, entry);
});
}
if (object.get("warps") instanceof Map<?, ?> warps) {
warps.forEach((name, value) -> {
if (!(value instanceof Map<?, ?> warp)) return;
WarpEntry entry = new WarpEntry();
entry.x = ((Number) warp.get("x")).doubleValue();
entry.y = ((Number) warp.get("y")).doubleValue();
entry.z = ((Number) warp.get("z")).doubleValue();
entry.yaw = ((Number) warp.get("yaw")).floatValue();
entry.pitch = ((Number) warp.get("pitch")).floatValue();
entry.material = (String) warp.get("material");
worldData.warps.put((String) name, entry);
});
}
return worldData;
}
}
@@ -0,0 +1,75 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.worlddata.converter;
import de.steamwar.bausystem.config.ConfigConverter;
import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.bausystem.worlddata.WorldDataV1;
@Deprecated
public class WorldDataV1ToV2Converter implements ConfigConverter<WorldDataV1, WorldData> {
private static final String GLOBAL_REGION = "global";
private static final String TESTBLOCK_SCHEMATIC = "testblockSchematic";
@Override
public int version() {
return WorldDataV1.VERSION;
}
@Override
public WorldData convert(WorldDataV1 old) {
WorldData worldData = new WorldData();
old.getRegions().forEach((name, region) -> {
if (name.equals(GLOBAL_REGION)) {
worldData.setGlobalRegion(convert(region));
} else {
worldData.getRegions().put(name, convert(region));
}
});
old.getWarps().forEach((name, warp) -> worldData.getWarps().put(name, convert(warp)));
return worldData;
}
private WorldData.RegionEntry convert(WorldDataV1.RegionEntry old) {
WorldData.RegionEntry region = new WorldData.RegionEntry();
region.setPrototype(old.getPrototype());
old.getFlagStorage().forEach((key, value) -> {
if (value == null) return;
if (key.equals(TESTBLOCK_SCHEMATIC)) {
region.setTestblockSchematic(((Number) value).intValue());
} else {
region.getFlags().put(key, value.toString());
}
});
return region;
}
private WorldData.WarpEntry convert(WorldDataV1.WarpEntry old) {
WorldData.WarpEntry warp = new WorldData.WarpEntry();
warp.setX(old.getX());
warp.setY(old.getY());
warp.setZ(old.getZ());
warp.setYaw(old.getYaw());
warp.setPitch(old.getPitch());
warp.setMaterial(old.getMaterial());
return warp;
}
}
@@ -25,12 +25,12 @@ import de.steamwar.bausystem.region.flags.ColorMode;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.ProtectMode; import de.steamwar.bausystem.region.flags.ProtectMode;
import de.steamwar.bausystem.region.flags.TNTMode; import de.steamwar.bausystem.region.flags.TNTMode;
import de.steamwar.bausystem.worlddata.WorldData;
import lombok.NonNull; import lombok.NonNull;
import yapion.hierarchy.types.YAPIONObject;
public class FixedGlobalRegionData extends RegionData { public class FixedGlobalRegionData extends RegionData {
public FixedGlobalRegionData(YAPIONObject data, Runnable onChange) { public FixedGlobalRegionData(WorldData.RegionEntry data, Runnable onChange) {
super(data, onChange); super(data, onChange);
} }
@@ -26,6 +26,7 @@ import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TestblockMode; import de.steamwar.bausystem.region.flags.TestblockMode;
import de.steamwar.bausystem.utils.FlatteningWrapper; import de.steamwar.bausystem.utils.FlatteningWrapper;
import de.steamwar.bausystem.utils.PasteBuilder; import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.core.Core; import de.steamwar.core.Core;
import de.steamwar.sql.GameModeConfig; import de.steamwar.sql.GameModeConfig;
import de.steamwar.sql.SchematicType; import de.steamwar.sql.SchematicType;
@@ -139,7 +140,7 @@ public class FixedRegion implements Region {
} }
} }
public FixedRegion(String name, FixedRegionData flagStorage, Prototype prototype, YAPIONObject regionConfig, YAPIONObject regionData) { public FixedRegion(String name, FixedRegionData flagStorage, Prototype prototype, YAPIONObject regionConfig, WorldData.RegionEntry regionData) {
this.name = name; this.name = name;
uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8)); uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));
this.flagStorage = flagStorage; this.flagStorage = flagStorage;
@@ -325,7 +326,7 @@ public class FixedRegion implements Region {
} }
floorLevel = prototype.getFloorOffset() != 0 ? minPoint.getY() + prototype.getFloorOffset() : 0; floorLevel = prototype.getFloorOffset() != 0 ? minPoint.getY() + prototype.getFloorOffset() : 0;
waterLevel = prototype.getWaterOffset() != 0 ? minPoint.getX() + prototype.getWaterOffset() : 0; waterLevel = prototype.getWaterOffset() != 0 ? minPoint.getY() + prototype.getWaterOffset() : 0;
File found = null; File found = null;
File baseFile = new File(BauSystem.getInstance().getDataFolder().getParentFile(), "FightSystem"); File baseFile = new File(BauSystem.getInstance().getDataFolder().getParentFile(), "FightSystem");
@@ -22,12 +22,12 @@ package de.steamwar.bausystem.region.fixed;
import de.steamwar.bausystem.region.RegionData; import de.steamwar.bausystem.region.RegionData;
import de.steamwar.bausystem.region.RegionFlagPolicy; import de.steamwar.bausystem.region.RegionFlagPolicy;
import de.steamwar.bausystem.region.flags.Flag; import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.worlddata.WorldData;
import lombok.NonNull; import lombok.NonNull;
import yapion.hierarchy.types.YAPIONObject;
public class FixedRegionData extends RegionData { public class FixedRegionData extends RegionData {
public FixedRegionData(YAPIONObject data, Runnable onChange) { public FixedRegionData(WorldData.RegionEntry data, Runnable onChange) {
super(data, onChange); super(data, onChange);
} }
@@ -210,14 +210,14 @@ public class Prototype {
} }
} }
public static void generateRegion(String name, YAPIONObject regionConfig, YAPIONObject regionData) { public static void generateRegion(String name, YAPIONObject regionConfig, WorldData.RegionEntry regionData) {
Prototype prototype; Prototype prototype;
if (regionData.containsKey("prototype", String.class)) { if (regionData.getPrototype() != null) {
prototype = PROTOTYPE_MAP.get(regionData.getPlainValue("prototype")); prototype = PROTOTYPE_MAP.get(regionData.getPrototype());
} else { } else {
prototype = PROTOTYPE_MAP.get(regionConfig.getPlainValue("prototype")); prototype = PROTOTYPE_MAP.get(regionConfig.getPlainValue("prototype"));
} }
FixedRegionData flagStorage = new FixedRegionData(regionData, WorldData::write); FixedRegionData flagStorage = new FixedRegionData(regionData, WorldData.STORE::save);
FixedRegionSystem.addRegion(new FixedRegion(name, flagStorage, prototype, regionConfig, regionData)); FixedRegionSystem.addRegion(new FixedRegion(name, flagStorage, prototype, regionConfig, regionData));
} }
} }
@@ -28,7 +28,6 @@ import org.bukkit.Bukkit;
import yapion.hierarchy.diff.DiffChange; import yapion.hierarchy.diff.DiffChange;
import yapion.hierarchy.diff.YAPIONDiff; import yapion.hierarchy.diff.YAPIONDiff;
import yapion.hierarchy.types.YAPIONObject; import yapion.hierarchy.types.YAPIONObject;
import yapion.hierarchy.types.YAPIONType;
import yapion.parser.YAPIONParser; import yapion.parser.YAPIONParser;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
@@ -55,7 +54,7 @@ public class RegionLoader {
} }
loaded = yapionObject; loaded = yapionObject;
YAPIONObject optionsYapionObject = WorldData.getRegionsData(); WorldData worldData = WorldData.STORE.get();
yapionObject.forEach((key, yapionAnyType) -> { yapionObject.forEach((key, yapionAnyType) -> {
if (key.equals("global")) { if (key.equals("global")) {
return; return;
@@ -65,21 +64,10 @@ public class RegionLoader {
} }
YAPIONObject regionConfig = (YAPIONObject) yapionAnyType; YAPIONObject regionConfig = (YAPIONObject) yapionAnyType;
YAPIONObject regionData = new YAPIONObject(); WorldData.RegionEntry regionData = worldData.getRegions().computeIfAbsent(key, k -> new WorldData.RegionEntry());
if (optionsYapionObject.containsKey(key, YAPIONType.OBJECT)) {
regionData = optionsYapionObject.getObject(key);
} else {
optionsYapionObject.add(key, regionData);
}
Prototype.generateRegion(key, regionConfig, regionData); Prototype.generateRegion(key, regionConfig, regionData);
}); });
YAPIONObject globalOptions = optionsYapionObject.getObject("global"); FixedGlobalRegion.setFLAG_STORAGE(new FixedGlobalRegionData(worldData.getGlobalRegion(), WorldData.STORE::save));
if (globalOptions == null) {
globalOptions = new YAPIONObject();
optionsYapionObject.add("global", globalOptions);
}
FixedGlobalRegion.setFLAG_STORAGE(new FixedGlobalRegionData(globalOptions, WorldData::write));
} }
} }
+1
View File
@@ -37,5 +37,6 @@ tasks.register<DevServer>("DevBau21") {
dependsOn(":SpigotCore:shadowJar") dependsOn(":SpigotCore:shadowJar")
dependsOn(":BauSystem:shadowJar") dependsOn(":BauSystem:shadowJar")
dependsOn(":SchematicSystem:shadowJar") dependsOn(":SchematicSystem:shadowJar")
dependsOn(":KotlinCore:shadowJar")
template = "Bau21" template = "Bau21"
} }
+17 -4
View File
@@ -7,6 +7,7 @@ import com.github.ajalt.mordant.rendering.TextColors
import com.github.ajalt.mordant.rendering.TextStyles import com.github.ajalt.mordant.rendering.TextStyles
import de.steamwar.db.Database import de.steamwar.db.Database
import de.steamwar.db.execute import de.steamwar.db.execute
import de.steamwar.db.executeScript
import de.steamwar.db.useDb import de.steamwar.db.useDb
import java.io.File import java.io.File
@@ -22,12 +23,24 @@ class ResetCommand : CliktCommand() {
val schema = schemaFile.readText() val schema = schemaFile.readText()
val tables = execute("SHOW TABLES;") { it.getString(1) } execute("SET FOREIGN_KEY_CHECKS=0;") { }
for (table in tables) {
execute("DROP TABLE IF EXISTS $table;") { } val databaseObjects = execute("SHOW FULL TABLES;") { it.getString(1) to it.getString(2) }
for (view in databaseObjects.filter { it.second == "VIEW" }.map { it.first }) {
execute("DROP VIEW IF EXISTS `${view.replace("`", "``")}`;") { }
}
for (table in databaseObjects.filter { it.second == "BASE TABLE" }.map { it.first }) {
execute("DROP TABLE IF EXISTS `${table.replace("`", "``")}`;") { }
} }
execute(schema) { } executeScript(schema)
val seed = javaClass.getResource("/db/reset-seed.sql")
?: throw CliktError("Reset seed file not found!")
executeScript(seed.readText())
execute("SET FOREIGN_KEY_CHECKS=1;") { }
echo(TextColors.brightGreen(TextStyles.bold("Database reset!"))) echo(TextColors.brightGreen(TextStyles.bold("Database reset!")))
} }
+28 -5
View File
@@ -108,8 +108,10 @@ class DevCommand : CliktCommand("dev") {
if (!templateFile.exists()) { if (!templateFile.exists()) {
throw CliktError("Could not find world template: ${templateFile.absolutePath}") throw CliktError("Could not find world template: ${templateFile.absolutePath}")
} }
worldFile.parentFile?.mkdirs()
templateFile.copyRecursively(worldFile) templateFile.copyRecursively(worldFile)
} }
val worldDir = worldFile.parentFile ?: workingDir
val devFile = File("/configs/DevServer/${System.getProperty("user.name")}.$port.$version") val devFile = File("/configs/DevServer/${System.getProperty("user.name")}.$port.$version")
if (System.getProperty("user.name") != "minecraft") { if (System.getProperty("user.name") != "minecraft") {
@@ -120,10 +122,11 @@ class DevCommand : CliktCommand("dev") {
args, jvmArgs, listOf( args, jvmArgs, listOf(
jarFile, jarFile,
*(if (forceUpgrade) arrayOf("-forceUpgrade") else arrayOf()), *(if (forceUpgrade) arrayOf("-forceUpgrade") else arrayOf()),
"--log-strip-color",
"--port", port.toString(), "--port", port.toString(),
"--level-name", worldFile.name, "--level-name", worldFile.name,
"--world-dir", workingDir.absolutePath, "--world-dir", worldDir.absolutePath,
"--nogui", "nogui",
*(if (plugins != null) arrayOf("--plugins", plugins!!.absolutePathString()) else arrayOf()) *(if (plugins != null) arrayOf("--plugins", plugins!!.absolutePathString()) else arrayOf())
), serverDir ), serverDir
) )
@@ -156,6 +159,16 @@ class DevCommand : CliktCommand("dev") {
val jvmArgOverrides = arrayOf("--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED") val jvmArgOverrides = arrayOf("--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED")
val jvmNonJava8Params = arrayOf(
*jvmArgOverrides,
"-XX:-CRIUSecProvider"
)
val extendedStartupParams = arrayOf(
"-Dpaper.disablePluginRemapping=true",
"-javaagent:/jars/AccessWidener.jar=start"
)
val supportedVersionJars = mapOf( val supportedVersionJars = mapOf(
8 to "/jars/paper-1.8.8.jar", 8 to "/jars/paper-1.8.8.jar",
9 to "/jars/spigot-1.9.4.jar", 9 to "/jars/spigot-1.9.4.jar",
@@ -228,13 +241,23 @@ class DevCommand : CliktCommand("dev") {
} }
fun runServer(args: List<String>, jvmArgs: List<String>, cmd: List<String>, serverDir: File) { fun runServer(args: List<String>, jvmArgs: List<String>, cmd: List<String>, serverDir: File) {
val effectiveJvmArgs = mutableListOf<String>()
effectiveJvmArgs += jvmArgs
if (!isVelocity(server)) {
extendedStartupParams.forEach { arg ->
if (effectiveJvmArgs.none { it == arg }) {
effectiveJvmArgs += arg
}
}
}
val process = ProcessBuilder( val process = ProcessBuilder(
jvm?.absolutePath jvm?.absolutePath
?: if (isJava8(server)) "/usr/lib/jvm/openj9-8/bin/java" else "java", ?: if (isJava8(server)) "/usr/lib/jvm/openj9-8/bin/java" else "/usr/lib/jvm/openj9-21/bin/java",
*jvmArgs.toTypedArray(), *effectiveJvmArgs.toTypedArray(),
*args.toTypedArray(), *args.toTypedArray(),
*jvmDefaultParams, *jvmDefaultParams,
*(if (isJava8(server)) arrayOf() else jvmArgOverrides), *(if (isJava8(server)) arrayOf() else jvmNonJava8Params),
*(if (profile) arrayOf("-javaagent:/jars/LixfelsProfiler.jar=start") else arrayOf()), *(if (profile) arrayOf("-javaagent:/jars/LixfelsProfiler.jar=start") else arrayOf()),
"-Xshareclasses:nonfatal,name=$server", "-Xshareclasses:nonfatal,name=$server",
"-jar", "-jar",
+120 -9
View File
@@ -16,15 +16,14 @@ import java.sql.ResultSet
import java.util.* import java.util.*
object Database { object Database {
lateinit var host: String val host: String
lateinit var port: String val port: String
lateinit var database: String val database: String
val username: String
val password: String
lateinit var db: Database lateinit var db: Database
fun ensureConnected() { init {
if (::db.isInitialized) {
return
}
val config = File(System.getProperty("user.home"), "mysql.properties") val config = File(System.getProperty("user.home"), "mysql.properties")
if (!config.exists()) { if (!config.exists()) {
@@ -38,9 +37,14 @@ object Database {
host = props.getProperty("host") host = props.getProperty("host")
port = props.getProperty("port") port = props.getProperty("port")
database = props.getProperty("database") database = props.getProperty("database")
username = props.getProperty("user")
password = props.getProperty("password")
}
val username = props.getProperty("user") fun ensureConnected() {
val password = props.getProperty("password") if (::db.isInitialized) {
return
}
val url = "jdbc:mariadb://$host:$port/$database" val url = "jdbc:mariadb://$host:$port/$database"
@@ -79,6 +83,113 @@ fun <T> JdbcTransaction.executeSingle(sql: String, transform: (ResultSet) -> T):
}.single() }.single()
} }
fun JdbcTransaction.executeScript(sql: String) {
for (statement in splitSqlScript(sql)) {
exec(statement) { }
}
}
private fun splitSqlScript(sql: String): List<String> {
val statements = mutableListOf<String>()
val current = StringBuilder()
var quote: Char? = null
var inLineComment = false
var inBlockComment = false
var index = 0
fun addStatement() {
val statement = current.toString().trim()
if (statement.isNotEmpty()) {
statements += statement
}
current.clear()
}
while (index < sql.length) {
val char = sql[index]
val next = sql.getOrNull(index + 1)
if (inLineComment) {
current.append(char)
if (char == '\n') {
inLineComment = false
}
index++
continue
}
if (inBlockComment) {
current.append(char)
if (char == '*' && next == '/') {
current.append(next)
inBlockComment = false
index += 2
} else {
index++
}
continue
}
if (quote != null) {
current.append(char)
if (char == '\\' && quote != '`' && next != null) {
current.append(next)
index += 2
continue
}
if (char == quote) {
if (next == quote) {
current.append(next)
index += 2
continue
}
quote = null
}
index++
continue
}
when {
char == '-' && next == '-' -> {
current.append(char).append(next)
inLineComment = true
index += 2
}
char == '#' -> {
current.append(char)
inLineComment = true
index++
}
char == '/' && next == '*' -> {
current.append(char).append(next)
inBlockComment = true
index += 2
}
char == '\'' || char == '"' || char == '`' -> {
current.append(char)
quote = char
index++
}
char == ';' -> {
addStatement()
index++
}
else -> {
current.append(char)
index++
}
}
}
addStatement()
return statements
}
fun useDb(statement: JdbcTransaction.() -> Unit) { fun useDb(statement: JdbcTransaction.() -> Unit) {
de.steamwar.db.Database.ensureConnected() de.steamwar.db.Database.ensureConnected()
transaction(de.steamwar.db.Database.db, statement = statement) transaction(de.steamwar.db.Database.db, statement = statement)
File diff suppressed because one or more lines are too long
+5
View File
@@ -19,6 +19,7 @@
plugins { plugins {
steamwar.kotlin steamwar.kotlin
kotlin("plugin.lombok")
} }
kotlin { kotlin {
@@ -40,4 +41,8 @@ dependencies {
compileOnlyApi(libs.exposedDao) compileOnlyApi(libs.exposedDao)
compileOnlyApi(libs.exposedJdbc) compileOnlyApi(libs.exposedJdbc)
compileOnlyApi(libs.exposedTime) compileOnlyApi(libs.exposedTime)
testImplementation(kotlin("test"))
}
repositories {
mavenCentral()
} }
@@ -86,6 +86,24 @@ class CheckedSchematic(id: EntityID<CompositeID>) : CompositeEntity(id) {
useDb { useDb {
find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.seen eq false) }.orderBy(CheckedSchematicTable.endTime to SortOrder.DESC).toList() find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.seen eq false) }.orderBy(CheckedSchematicTable.endTime to SortOrder.DESC).toList()
} }
@JvmStatic
fun countAccepted(owner: SteamwarUser) =
useDb {
find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.declineReason eq "freigegeben") }.count()
}
@JvmStatic
fun countAccepted(owner: SteamwarUser, type: String) =
useDb {
find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.declineReason eq "freigegeben") and (CheckedSchematicTable.nodeType like "$type%") }.count()
}
@JvmStatic
fun countChecked(validator: SteamwarUser) =
useDb {
find { CheckedSchematicTable.validator eq validator.id }.count()
}
} }
val node by CheckedSchematicTable.nodeId.transform({ it?.let { EntityID(it, SchematicNodeTable) } }, { it?.value }) val node by CheckedSchematicTable.nodeId.transform({ it?.let { EntityID(it, SchematicNodeTable) } }, { it?.value })
@@ -130,6 +130,43 @@ class EventFight(id: EntityID<Int>) : IntEntity(id), Comparable<EventFight> {
} }
) )
} }
@JvmStatic
fun countEventFights(fighter: SteamwarUser) =
useDb {
exec(
"SELECT COUNT(DISTINCT F.FightID) AS FightCount FROM FightPlayer INNER JOIN Fight F on FightPlayer.FightID = F.FightID INNER JOIN EventFight EF on F.FightID = EF.Fight WHERE UserID = ?",
args = listOf(IntegerColumnType() to fighter.id.value)
) {
if (it.next()) {
it.getLong("FightCount")
} else {
0
}
}
?: 0
}
@JvmStatic
fun countPlacement(fighter: SteamwarUser, placement: Int) =
useDb {
exec(
"""
SELECT COUNT(DISTINCT EventFight.EventID) AS PlacementCount FROM TeamTeilnahme
INNER JOIN EventFight ON EventFight.EventID = TeamTeilnahme.EventID
INNER JOIN FightPlayer ON FightPlayer.FightID = EventFight.Fight
WHERE (IF(FightPlayer.Team = 1, EventFight.TeamBlue, EventFight.TeamRed)) = TeamTeilnahme.TeamID AND UserID = ? AND Placement = ?
""".trimIndent(),
args = listOf(IntegerColumnType() to fighter.id.value, IntegerColumnType() to placement)
) {
if (it.next()) {
it.getInt("PlacementCount")
} else {
0
}
}
?: 0
}
} }
val fightID by EventFightTable.id.transform({ EntityID(it, EventFightTable) }, { it.value }) val fightID by EventFightTable.id.transform({ EntityID(it, EventFightTable) }, { it.value })
@@ -200,6 +237,7 @@ class EventFight(id: EntityID<Int>) : IntEntity(id), Comparable<EventFight> {
override fun delete() = override fun delete() =
useDb { useDb {
EventRelation.deleteRelations(this@EventFight)
super.delete() super.delete()
} }
} }
@@ -159,7 +159,10 @@ class EventGroup(id: EntityID<Int>) : IntEntity(id) {
} }
override fun delete() = override fun delete() =
useDb { super.delete() } useDb {
EventRelation.deleteRelations(this@EventGroup)
super.delete()
}
enum class EventGroupType { enum class EventGroupType {
GROUP_STAGE, GROUP_STAGE,
@@ -24,8 +24,10 @@ import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass import org.jetbrains.exposed.v1.dao.IntEntityClass
import org.jetbrains.exposed.v1.jdbc.deleteWhere
import org.jetbrains.exposed.v1.jdbc.select import org.jetbrains.exposed.v1.jdbc.select
object EventRelationTable : IntIdTable("EventRelation") { object EventRelationTable : IntIdTable("EventRelation") {
@@ -59,6 +61,23 @@ class EventRelation(id: EntityID<Int>) : IntEntity(id) {
fun getGroupRelations(group: EventGroup) = fun getGroupRelations(group: EventGroup) =
useDb { find { (EventRelationTable.fromId eq group.id.value) and (EventRelationTable.fromType eq FromType.GROUP) }.toList() } useDb { find { (EventRelationTable.fromId eq group.id.value) and (EventRelationTable.fromType eq FromType.GROUP) }.toList() }
@JvmStatic
fun deleteRelations(fight: EventFight) =
useDb {
EventRelationTable.deleteWhere {
(EventRelationTable.fightId eq fight.id) or
((EventRelationTable.fromId eq fight.id.value) and (EventRelationTable.fromType eq FromType.FIGHT))
}
}
@JvmStatic
fun deleteRelations(group: EventGroup) =
useDb {
EventRelationTable.deleteWhere {
(EventRelationTable.fromId eq group.id.value) and (EventRelationTable.fromType eq FromType.GROUP)
}
}
@JvmStatic @JvmStatic
fun create(fight: EventFight, fightTeam: FightTeam, fromType: FromType, fromId: Int, fromPlace: Int) = fun create(fight: EventFight, fightTeam: FightTeam, fromType: FromType, fromId: Int, fromPlace: Int) =
useDb { useDb {
@@ -20,9 +20,12 @@
package de.steamwar.sql package de.steamwar.sql
import de.steamwar.sql.internal.useDb import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.IntegerColumnType
import org.jetbrains.exposed.v1.core.VarCharColumnType
import org.jetbrains.exposed.v1.core.dao.id.CompositeID import org.jetbrains.exposed.v1.core.dao.id.CompositeID
import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable
import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.inList import org.jetbrains.exposed.v1.core.inList
import org.jetbrains.exposed.v1.dao.CompositeEntity import org.jetbrains.exposed.v1.dao.CompositeEntity
import org.jetbrains.exposed.v1.dao.CompositeEntityClass import org.jetbrains.exposed.v1.dao.CompositeEntityClass
@@ -69,6 +72,28 @@ class FightPlayer(id: EntityID<CompositeID>) : CompositeEntity(id) {
useDb { useDb {
find { FightPlayerTable.fightId inList fightIds.toList() }.toList() find { FightPlayerTable.fightId inList fightIds.toList() }.toList()
} }
@JvmStatic
fun countFights(userId: Int) =
useDb {
find { FightPlayerTable.userId eq userId }.count()
}
@JvmStatic
fun countFights(userId: Int, type: String) =
useDb {
exec(
"SELECT COUNT(*) AS FightCount FROM FightPlayer INNER JOIN Fight F on FightPlayer.FightID = F.FightID WHERE UserID = ? AND GameMode LIKE ?",
args = listOf(IntegerColumnType() to userId, VarCharColumnType() to "$type%")
) {
if (it.next()) {
it.getInt("FightCount")
} else {
0
}
}
?: 0
}
} }
val fightID by FightPlayerTable.fightId.transform({ EntityID(it, FightTable) }, { it.value }) val fightID by FightPlayerTable.fightId.transform({ EntityID(it, FightTable) }, { it.value })
@@ -146,12 +146,12 @@ public final class GameModeConfig<M, W> {
public final List<String> CheckQuestions; public final List<String> CheckQuestions;
/** /**
* The allowed checkers to check this schematic type denoted by a list of SteamWar ids. * The allowed checkers to check this schematic type denoted by a list of SteamwarUser ids.
* The people need the {@link UserPerm#CHECK} to be able to check though. * The people need the {@link UserPerm#CHECK} to be able to check though.
* *
* @implSpec {@code []} by default -> denoting every person with {@link UserPerm#CHECK} can check it * @implSpec {@code []} by default -> denoting every person with {@link UserPerm#CHECK} can check it
*/ */
public final List<Integer> Checkers; public final Set<Integer> Checkers;
/** /**
* Bundle for countdowns during the fight * Bundle for countdowns during the fight
@@ -246,7 +246,7 @@ public final class GameModeConfig<M, W> {
} }
CheckQuestions = loader.getStringList("CheckQuestions"); CheckQuestions = loader.getStringList("CheckQuestions");
Checkers = loader.getIntList("Checkers"); Checkers = loader.getIntSet("Checkers");
Times = new TimesConfig(loader.with("Times")); Times = new TimesConfig(loader.with("Times"));
// Arena would be here to be in config order but needs Schematic.Size and EnterStages loaded afterwards // Arena would be here to be in config order but needs Schematic.Size and EnterStages loaded afterwards
Schematic = new SchematicConfig<>(loader.with("Schematic")); Schematic = new SchematicConfig<>(loader.with("Schematic"));
@@ -488,6 +488,27 @@ public final class GameModeConfig<M, W> {
*/ */
public final boolean NoFloor; public final boolean NoFloor;
/**
* Allows Wind Charges to cross the Middle.
*
* @implSpec {@code false} by default
*/
public final boolean WindchargesCanCrossMiddle;
/**
* Allows Wind Charge interaction with Blocks.
*
* @implSpec {@code true} by default
*/
public final boolean WindchargesInteractWithBlocks;
/**
* Allows Wind Charge to destroy Water.
*
* @implSpec {@code false} by default
*/
public final boolean WindchargesDestroyWater;
private ArenaConfig(YMLWrapper<M, W> loader, SchematicConfig.SizeConfig Size, List<Integer> EnterStages) { private ArenaConfig(YMLWrapper<M, W> loader, SchematicConfig.SizeConfig Size, List<Integer> EnterStages) {
loaded = loader.canLoad(); loaded = loader.canLoad();
WaterDepth = loader.getInt("WaterDepth", 0); WaterDepth = loader.getInt("WaterDepth", 0);
@@ -505,6 +526,9 @@ public final class GameModeConfig<M, W> {
Leaveable = loader.getBoolean("Leaveable", false); Leaveable = loader.getBoolean("Leaveable", false);
AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty()); AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty());
NoFloor = loader.getBoolean("NoFloor", false); NoFloor = loader.getBoolean("NoFloor", false);
WindchargesCanCrossMiddle = loader.getBoolean("WindchargesCanCrossMiddle", false);
WindchargesInteractWithBlocks = loader.getBoolean("WindchargesInteractWithBlocks", true);
WindchargesDestroyWater = loader.getBoolean("WindchargesDestroyWater", false);
} }
@ToString @ToString
@@ -664,13 +688,6 @@ public final class GameModeConfig<M, W> {
*/ */
public final int MaxBlocks; public final int MaxBlocks;
/**
* Maximal amount of items per dispenser
*
* @implSpec {@code 128} by default
*/
public final int MaxDispenserItems;
/** /**
* Maximal blast resistance for the blocks * Maximal blast resistance for the blocks
* *
@@ -686,10 +703,12 @@ public final class GameModeConfig<M, W> {
public final double MaxDesignBlastResistance; public final double MaxDesignBlastResistance;
/** /**
* List of limited material (combinations)<br/> * List of limited block materials
* List contains tags Amount (integer) and Materials (List of material names in Spigot 1.12 AND Spigot 1.15 format) *
* @implSpec {@link SQLWrapper#getInventoryMaterials)} are all disallowed by default to have any content but are unlimited in number.
* @implSpec Any Block that has more Blast Resistance than {@link #MaxBlastResistance} will be defaulted to 0.
*/ */
public final Map<Set<M>, Integer> Limited; public final List<BlockRule<M>> Limited;
private SchematicConfig(YMLWrapper<M, ?> loader) { private SchematicConfig(YMLWrapper<M, ?> loader) {
loaded = loader.canLoad(); loaded = loader.canLoad();
@@ -707,32 +726,140 @@ public final class GameModeConfig<M, W> {
IgnorePublicOnly = loader.getBoolean("IgnorePublicOnly", false); IgnorePublicOnly = loader.getBoolean("IgnorePublicOnly", false);
UnlimitedPrepare = loader.getBoolean("UnlimitedPrepare", false); UnlimitedPrepare = loader.getBoolean("UnlimitedPrepare", false);
MaxBlocks = loader.getInt("MaxBlocks", 0); MaxBlocks = loader.getInt("MaxBlocks", 0);
MaxDispenserItems = loader.getInt("MaxDispenserItems", 128);
MaxBlastResistance = loader.getDouble("MaxBlastResistance", Double.MAX_VALUE); MaxBlastResistance = loader.getDouble("MaxBlastResistance", Double.MAX_VALUE);
MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", MaxBlastResistance); MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", MaxBlastResistance);
Map<Set<M>, Integer> Limited = new HashMap<>(); List<BlockRule<M>> limited = new ArrayList<>();
for (Map<?, ?> entry : loader.getMapList("Limited")) { Set<M> blocks = new HashSet<>();
int amount = (Integer) entry.get("Amount"); for (YMLWrapper<M, ?> limitedLoader : loader.withAsList("Limited")) {
Set<String> materials = new HashSet<>((List<String>) entry.get("Materials")); BlockRule<M> blockRule = new BlockRule<>(limitedLoader);
if (amount == 0) { blocks.addAll(blockRule.Materials);
materials.forEach(material -> { limited.add(blockRule);
Limited.put(Collections.singleton(loader.materialMapper.apply(material.toUpperCase())), 0);
});
} else {
Limited.put(Collections.unmodifiableSet(materials.stream().map(String::toUpperCase).map(loader.materialMapper).collect(Collectors.toSet())), amount);
}
} }
SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance).forEach(material -> { for (M material : (List<M>) SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance)) {
if (Limited.entrySet().stream().anyMatch(entry -> entry.getKey().contains(material))) return; if (material == null || !blocks.add(material)) continue;
Limited.put(Collections.singleton((M) material), 0); limited.add(new BlockRule<>(material, 0));
}); }
this.Limited = Collections.unmodifiableMap(Limited); for (M material : (Set<M>) SQLWrapper.impl.getInventoryMaterials()) {
if (material == null || !blocks.add(material)) continue;
limited.add(new BlockRule<>(material, Integer.MAX_VALUE));
}
this.Limited = Collections.unmodifiableList(limited);
this.ReplacementsWithoutBlockUpdates = loader.getMap("ReplacementsWithoutBlockUpdates", loader.materialMapper, loader.materialMapper); this.ReplacementsWithoutBlockUpdates = loader.getMap("ReplacementsWithoutBlockUpdates", loader.materialMapper, loader.materialMapper);
this.ReplacementsWithBlockUpdates = loader.getMap("ReplacementsWithBlockUpdates", loader.materialMapper, loader.materialMapper); this.ReplacementsWithBlockUpdates = loader.getMap("ReplacementsWithBlockUpdates", loader.materialMapper, loader.materialMapper);
} }
public int getMaxCount(M material) {
return Limited.stream()
.filter(rule -> rule.Materials.contains(material))
.mapToInt(rule -> rule.Amount)
.max()
.orElse(Integer.MAX_VALUE);
}
public boolean isInventory(M material) {
return Limited.stream()
.filter(rule -> rule.Materials.contains(material))
.anyMatch(rule -> rule.Content.loaded);
}
public Integer getItemAmount(M material, M item) {
return Limited.stream()
.filter(rule -> rule.Materials.contains(material))
.filter(rule -> rule.Content.Items.contains(item))
.mapToInt(rule -> rule.Content.Amount)
.max()
.orElse(0);
}
@ToString
public static final class BlockRule<M> {
/**
* The block materials this rule applies to
*/
public final Set<M> Materials;
/**
* Maximal amount of Blocks allowed in the schematic
*
* @implSpec {@code 0} by default
*/
public final int Amount;
/**
* The item content filter for Blocks
*
* @implSpec no Items with {@code Amount 0} (denies any item) by default
*/
public final Content<M> Content;
/**
* Resolves one {@code Materials} entry to the concrete materials it denotes: {@code storage}
* for {@link SQLWrapper#getStorageMaterials()}, {@code inventory} for
* {@link SQLWrapper#getInventoryMaterials()}, or the material itself otherwise.
*/
private Set<M> expandInventoryGroup(YMLWrapper<M, ?> loader, String name) {
switch (name.toUpperCase()) {
case "STORAGE":
return (Set<M>) SQLWrapper.impl.getStorageMaterials();
case "INVENTORY":
return (Set<M>) SQLWrapper.impl.getInventoryMaterials();
default:
M material = loader.materialMapper.apply(name.toUpperCase());
return material != null ? Collections.singleton(material) : Collections.emptySet();
}
}
private BlockRule(YMLWrapper<M, ?> loader) {
Set<M> blocks = loader.getStringList("Materials")
.stream()
.flatMap(value -> expandInventoryGroup(loader, value).stream())
.collect(Collectors.toSet());
Materials = Collections.unmodifiableSet(blocks);
Amount = loader.getInt("Amount", 0);
Content = new Content<>(loader.with("Content"));
}
private BlockRule(M block, int amount) {
Materials = Collections.singleton(block);
Amount = amount;
Content = new Content<>();
}
@ToString
public static final class Content<M> {
public final boolean loaded;
/**
* Items allowed by this rule
*/
public final Set<M> Items;
/**
* Maximal amount of an allowed item per single block instance
*
* @implSpec {@code 0} by default
*/
public final int Amount;
private Content(YMLWrapper<M, ?> loader) {
loaded = loader.canLoad();
List<M> items = loader.getMaterialList("Items");
Items = Collections.unmodifiableSet(new HashSet<>(items));
Amount = loader.getInt("Amount", 0);
}
private Content() {
loaded = false;
Items = new HashSet<>();
Amount = 0;
}
}
}
@ToString @ToString
public static final class SizeConfig { public static final class SizeConfig {
@@ -929,6 +1056,13 @@ public final class GameModeConfig<M, W> {
*/ */
public final boolean PersonalKits; public final boolean PersonalKits;
/**
* Maximal blast resistance for the blocks in the kit
*
* @implSpec {@code 9.0} by default
*/
public final double MaxBlastResistance;
/** /**
* Items (Valid spigot material values) that are not allowed in the personal kit * Items (Valid spigot material values) that are not allowed in the personal kit
*/ */
@@ -940,7 +1074,10 @@ public final class GameModeConfig<M, W> {
MemberDefault = loader.getString("MemberDefault", "default"); MemberDefault = loader.getString("MemberDefault", "default");
LeaderDefault = loader.getString("LeaderDefault", "default"); LeaderDefault = loader.getString("LeaderDefault", "default");
PersonalKits = loader.getBoolean("PersonalKits", false); PersonalKits = loader.getBoolean("PersonalKits", false);
ForbiddenItems = loader.getMaterialList("ForbiddenItems"); MaxBlastResistance = loader.getDouble("MaxBlastResistance", 9.0);
List forbiddenItems = new ArrayList<>(loader.getMaterialList("ForbiddenItems"));
forbiddenItems.addAll(SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance));
ForbiddenItems = Collections.unmodifiableList(forbiddenItems);
} }
} }
@@ -183,14 +183,6 @@ class Punishment(id: EntityID<Int>) : IntEntity(id) {
"UNNOFIGHTSERVER", "UNNOFIGHTSERVER",
UserPerm.MODERATION UserPerm.MODERATION
), ),
NoTeamServer(
"NOTEAMSERVER_TEAM",
"NOTEAMSERVER_PERMA",
"NOTEAMSERVER_UNTIL",
"UNNOTEAMSERVER_ERROR",
"UNNOTEAMSERVER",
UserPerm.MODERATION
),
Note("NOTE_TEAM", null, null, null, null, UserPerm.PUNISHMENTS, true); Note("NOTE_TEAM", null, null, null, null, UserPerm.PUNISHMENTS, true);
fun isMulti() = fun isMulti() =
@@ -24,6 +24,7 @@ import de.steamwar.ImplementationProvider;
import java.io.File; import java.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
public interface SQLWrapper<M> { public interface SQLWrapper<M> {
SQLWrapper<?> impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLWrapperImpl"); SQLWrapper<?> impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLWrapperImpl");
@@ -36,5 +37,21 @@ public interface SQLWrapper<M> {
return Collections.emptyList(); return Collections.emptyList();
} }
/**
* Storage-type inventory materials (chests, barrels, shulker boxes of any color) - the
* {@code storage} group usable in {@code GameModeConfig}'s {@code Limited}.
*/
default Set<M> getStorageMaterials() {
return Collections.emptySet();
}
/**
* Every inventory material - the {@code all} group usable in {@code GameModeConfig}'s
* {@code Limited}. Expected to be a superset of {@link #getStorageMaterials()}.
*/
default Set<M> getInventoryMaterials() {
return Collections.emptySet();
}
void additionalExceptionMetadata(StringBuilder builder); void additionalExceptionMetadata(StringBuilder builder);
} }
@@ -307,12 +307,12 @@ class SteamwarUser(id: EntityID<Int>) : IntEntity(id) {
punishments[punishment] = Punishment.createPunishment(this@SteamwarUser.id.value, from, punishment, reason, time, perma) punishments[punishment] = Punishment.createPunishment(this@SteamwarUser.id.value, from, punishment, reason, time, perma)
} }
fun setLocale(locale: Locale?, manualeLocale: Boolean) { fun setLocale(locale: Locale?, manualLocale: Boolean) {
if (locale == null || (this.manualLocale && !manualLocale)) return if (locale == null || (this.manualLocale && !manualLocale)) return
useDb { useDb {
this@SteamwarUser.locale = locale this@SteamwarUser.locale = locale
this@SteamwarUser.manualLocale = manualeLocale this@SteamwarUser.manualLocale = manualLocale
} }
} }
@@ -76,6 +76,25 @@ final class YMLWrapper<M, W> {
return new YMLWrapper<>(false, Collections.emptyMap(), materialMapper, winconditionMapper); return new YMLWrapper<>(false, Collections.emptyMap(), materialMapper, winconditionMapper);
} }
public List<YMLWrapper<M, W>> withAsList(String path) {
if (document.containsKey(path)) {
Object value = document.get(path);
if (value instanceof List) {
List<?> list = (List<?>) value;
List<Map> maps = list.stream()
.filter(Map.class::isInstance)
.map(Map.class::cast)
.collect(Collectors.toList());
if (maps.size() == list.size()) {
return (List) maps.stream()
.map(map -> new YMLWrapper(true, map, materialMapper, winconditionMapper))
.collect(Collectors.toList());
}
}
}
return Collections.emptyList();
}
public <T> T get(String path, T defaultValue, Function<Object, T> mapper) { public <T> T get(String path, T defaultValue, Function<Object, T> mapper) {
Object value = this.document.get(path); Object value = this.document.get(path);
if (value == null) return defaultValue; if (value == null) return defaultValue;
@@ -139,6 +158,12 @@ final class YMLWrapper<M, W> {
return get(path, o -> (List<Integer>) o); return get(path, o -> (List<Integer>) o);
} }
public Set<Integer> getIntSet(String path) {
List<Integer> list = get(path, o -> (List<Integer>) o);
if (list.isEmpty()) return Collections.emptySet();
return Collections.unmodifiableSet(new HashSet<>(list));
}
public List<SchematicType> getSchematicTypeList(String path) { public List<SchematicType> getSchematicTypeList(String path) {
List<String> list = getStringList(path); List<String> list = getStringList(path);
if (list.isEmpty()) { if (list.isEmpty()) {
+18 -6
View File
@@ -64,6 +64,10 @@ Arena:
AllowMissiles: false # defaults to true if EnterStages are present otherwise 'false' AllowMissiles: false # defaults to true if EnterStages are present otherwise 'false'
# Denotes that there is no floor for this GameMode # Denotes that there is no floor for this GameMode
NoFloor: false # defaults to false if missing NoFloor: false # defaults to false if missing
# Allows Wind Charges to cross the Middle.
WindchargesCanCrossMiddle: false # defaults to false if missing
# Allows Wind Charge interaction with Blocks.
WindchargesInteractWithBlocks: false # defaults to false if missing
Schematic: Schematic:
# The size of the schematics # The size of the schematics
@@ -103,15 +107,23 @@ Schematic:
UnlimitedPrepare: false # defaults to false if missing UnlimitedPrepare: false # defaults to false if missing
# Maximal amount of blocks allowed in the schematic # Maximal amount of blocks allowed in the schematic
MaxBlocks: 0 # defaults to 0 (ignored) if missing MaxBlocks: 0 # defaults to 0 (ignored) if missing
# Maximal amount of items per dispenser
MaxDispenserItems: 128 # defaults to 128 if missing
# Maximal blast resistance for the design blocks # Maximal blast resistance for the design blocks
MaxDesignBlastResistance: 100000000 # defaults to Double.MAX_VALUE if missing MaxDesignBlastResistance: 100000000 # defaults to Double.MAX_VALUE if missing
# List of limited material (combinations) # List of limited block materials
# List contains tags Amount (integer) and Materials (List of material names in Spigot 1.12 AND Spigot 1.15 format) # InventoryMaterials are all disallowed by default to have any content but are unlimited in number
# Any Block that has more Blast Resistance than MaxBlastResistance will be defaulted to 0.
Limited: Limited:
- Materials: [ ] - Blocks: [ DISPENSER ]
Amount: 0 MaxCount: 64
- Blocks: [ DISPENSER ]
MaxCount: 16
Content:
Items: [ ARROW, FIRE_CHARGE ]
Amount: 128
- Blocks: [ storage ]
Content:
Items: [ TNT ]
Amount: 1728
# The name of the game mode presented to the players # The name of the game mode presented to the players
GameName: WarGear # defaults to WarGear if missing GameName: WarGear # defaults to WarGear if missing
@@ -39,6 +39,7 @@ public enum ArenaMode {
public static final Set<ArenaMode> Test = Collections.unmodifiableSet(EnumSet.of(TEST, CHECK)); public static final Set<ArenaMode> Test = Collections.unmodifiableSet(EnumSet.of(TEST, CHECK));
public static final Set<ArenaMode> Prepare = Collections.unmodifiableSet(EnumSet.of(PREPARE)); public static final Set<ArenaMode> Prepare = Collections.unmodifiableSet(EnumSet.of(PREPARE));
public static final Set<ArenaMode> Replay = Collections.unmodifiableSet(EnumSet.of(REPLAY, TEST)); public static final Set<ArenaMode> Replay = Collections.unmodifiableSet(EnumSet.of(REPLAY, TEST));
public static final Set<ArenaMode> TestOrReferee = Collections.unmodifiableSet(EnumSet.of(TEST, CHECK, EVENT));
public static final Set<ArenaMode> AntiReplay = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(REPLAY))); public static final Set<ArenaMode> AntiReplay = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(REPLAY)));
public static final Set<ArenaMode> AntiTest = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK))); public static final Set<ArenaMode> AntiTest = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK)));
@@ -21,8 +21,8 @@ package de.steamwar.fightsystem;
import com.comphenix.tinyprotocol.TinyProtocol; import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.core.Core; import de.steamwar.core.Core;
import de.steamwar.core.WorldEditRendererCUIEditor; import de.steamwar.core.worldedit.SteamEdit;
import de.steamwar.fightsystem.commands.TechareaCommand; import de.steamwar.core.worldedit.WorldEditRendererCUIEditor;
import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.listener.ClickAnalyzer; import de.steamwar.fightsystem.listener.ClickAnalyzer;
@@ -108,7 +108,7 @@ public class FightSystem extends JavaPlugin {
} }
}); });
new OneShotStateDependent(ArenaMode.All, FightState.PreSchemSetup, () -> Fight.playSound(SWSound.BLOCK_NOTE_PLING.getSound(), 100.0f, 2.0f)); new OneShotStateDependent(ArenaMode.All, FightState.PreSchemSetup, () -> Fight.playSound(SWSound.BLOCK_NOTE_PLING.getSound(), 100.0f, 2.0f));
new OneShotStateDependent(ArenaMode.Test, FightState.All, WorldEditRendererCUIEditor::new); new OneShotStateDependent(ArenaMode.Test, FightState.All, SteamEdit::init);
Config.world.setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode)); Config.world.setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode));
hullHider = new HullHider(); hullHider = new HullHider();
@@ -127,8 +127,6 @@ public class FightSystem extends JavaPlugin {
if (checkSchematicNode.isPrepared()) { if (checkSchematicNode.isPrepared()) {
Fight.getRedTeam().setSchem(checkSchematicNode, NodeData.getRevisions(checkSchematicNode) - 1); Fight.getRedTeam().setSchem(checkSchematicNode, NodeData.getRevisions(checkSchematicNode) - 1);
} }
new TechareaCommand();
} else if (Config.mode == ArenaMode.PREPARE) { } else if (Config.mode == ArenaMode.PREPARE) {
Fight.getUnrotated().setSchem(SchematicNode.getSchematicNode(Config.PrepareSchemID)); Fight.getUnrotated().setSchem(SchematicNode.getSchematicNode(Config.PrepareSchemID));
} }
@@ -24,35 +24,25 @@ import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.Kit; import de.steamwar.fightsystem.fight.Kit;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm; import de.steamwar.sql.UserPerm;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@Linked @Linked
public class AkCommand implements CommandExecutor { public class AkCommand extends FightSWCommand {
public AkCommand() { public AkCommand() {
new StateDependentCommand(ArenaMode.Test, FightState.All, "ak", this); super(ArenaMode.Test, FightState.All, "ak");
} }
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { protected boolean hasPerm(Player player, SteamwarUser user) {
if (!(sender instanceof Player)) { return player.isOp() && user.hasPerm(UserPerm.ADMINISTRATION) && Core.getInstance() != FightSystem.getPlugin();
return false; }
}
Player player = (Player) sender;
if (!player.isOp()) return false;
if (!SteamwarUser.get(player.getUniqueId()).hasPerm(UserPerm.ADMINISTRATION) && Core.getInstance() != FightSystem.getPlugin()) { @Register
return false; public void run(@Validator Player player, String name) {
} Kit.createKit(name, player);
Kit.createKit(args[0], player);
return false;
} }
} }
@@ -0,0 +1,95 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.fightsystem.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeValidator;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependent;
import de.steamwar.sql.SteamwarUser;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandSendEvent;
import java.util.Set;
public abstract class FightSWCommand extends SWCommand implements Listener {
private final String command;
private final String[] aliases;
protected FightSWCommand(Set<ArenaMode> mode, Set<FightState> states, String command) {
super(command);
this.command = command;
this.aliases = new String[0];
stateDependant(mode, states);
}
protected FightSWCommand(Set<ArenaMode> mode, Set<FightState> states, String command, String... aliases) {
super(command, aliases);
this.command = command;
this.aliases = aliases;
stateDependant(mode, states);
}
private void stateDependant(Set<ArenaMode> mode, Set<FightState> states) {
FightSWCommand.this.unregister();
new StateDependent(mode, states) {
@Override
public void enable() {
System.out.println("--- Enable: " + command);
FightSWCommand.this.register();
}
@Override
public void disable() {
System.out.println("--- Disable: " + command);
FightSWCommand.this.unregister();
}
}.register();
}
public static void postFightStateChange() {
for (Player player : Bukkit.getOnlinePlayers()) {
player.updateCommands();
}
}
protected boolean hasPerm(Player player, SteamwarUser user) {
return true;
}
@EventHandler(ignoreCancelled = true)
public final void onPlayerCommandSend(PlayerCommandSendEvent event) {
if (hasPerm(event.getPlayer(), SteamwarUser.get(event.getPlayer().getUniqueId()))) return;
event.getCommands().remove(command);
for (String alias : aliases) {
event.getCommands().remove(alias);
}
}
@ClassValidator(value = Player.class, local = true)
public final TypeValidator<Player> permissionCheck() {
return (sender, value, messageSender) -> hasPerm(value, SteamwarUser.get(value.getUniqueId()));
}
}
@@ -19,92 +19,28 @@
package de.steamwar.fightsystem.commands; package de.steamwar.fightsystem.commands;
import com.google.common.collect.ImmutableList; import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import net.md_5.bungee.api.ChatMessageType; import de.steamwar.sql.SteamwarUser;
import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.BukkitCommand;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Linked @Linked
public class GamemodeCommand extends BukkitCommand { public class GamemodeCommand extends FightSWCommand {
private static final List<String> GAMEMODE_NAMES = ImmutableList.of("adventure", "creative", "survival",
"spectator");
public GamemodeCommand() { public GamemodeCommand() {
super("gamemode"); super(ArenaMode.TestOrReferee, FightState.All, "gamemode", "gm");
List<String> aliases = new ArrayList<>();
aliases.add("gm");
this.setAliases(aliases);
Map<String, Command> knownCommands = ((CraftServer) Bukkit.getServer()).getCommandMap().getKnownCommands();
knownCommands.remove("gamemode");
Commands.injectCommand(this);
} }
@Override @Override
public boolean execute(CommandSender sender, String currentAlias, String[] args) { protected boolean hasPerm(Player player, SteamwarUser user) {
if (!(sender instanceof Player)) { return Config.test() || Config.isReferee(player);
return false;
} else if (args.length == 0) {
FightSystem.getMessage().sendPrefixless("GAMEMODE_HELP", sender);
return false;
}
Player p = (Player) sender;
if (!(Config.test() || Config.isReferee(p))) {
FightSystem.getMessage().sendPrefixless("GAMEMODE_NOT_ALLOWED", p, ChatMessageType.ACTION_BAR);
return false;
}
GameMode mode = createMode(args[0]);
if (mode == null) {
FightSystem.getMessage().sendPrefixless("GAMEMODE_UNKNOWN", p, ChatMessageType.ACTION_BAR, args[0]);
return false;
}
p.setGameMode(mode);
return true;
} }
@SuppressWarnings("deprecation") @Register(description = "GAMEMODE_HELP")
private GameMode createMode(String modeArg) { public void execute(@Validator Player player, GameMode gameMode) {
try { player.setGameMode(gameMode);
return GameMode.getByValue(Integer.parseInt(modeArg));
} catch (NumberFormatException ignored) {
if ((modeArg.equalsIgnoreCase("creative")) || (modeArg.equalsIgnoreCase("c"))) {
return GameMode.CREATIVE;
} else if ((modeArg.equalsIgnoreCase("adventure")) || (modeArg.equalsIgnoreCase("a"))) {
return GameMode.ADVENTURE;
} else if ((modeArg.equalsIgnoreCase("spectator")) || (modeArg.equalsIgnoreCase("sp"))) {
return GameMode.SPECTATOR;
} else if ((modeArg.equalsIgnoreCase("survival")) || (modeArg.equalsIgnoreCase("s"))) {
return GameMode.SURVIVAL;
}
}
return null;
} }
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) {
if (args.length == 1) {
return StringUtil.copyPartialMatches(args[0], GAMEMODE_NAMES, new ArrayList<>(GAMEMODE_NAMES.size()));
}
return ImmutableList.of();
}
} }
@@ -20,34 +20,31 @@
package de.steamwar.fightsystem.commands; package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.listener.Check;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@Linked @Linked
public class InfoCommand implements CommandExecutor { public class InfoCommand extends FightSWCommand {
public InfoCommand() { public InfoCommand() {
new StateDependentCommand(ArenaMode.All, FightState.All, "fightinfo", this); super(ArenaMode.All, FightState.All, "fightinfo");
} }
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { protected boolean hasPerm(Player player, SteamwarUser user) {
if (!(sender instanceof Player)) return false; return Check.checkPermission(user, Config.GameModeConfig);
}
Player player = (Player) sender;
if (!SteamwarUser.get(player.getUniqueId()).hasPerm(UserPerm.CHECK)) return false;
@Register
public void run(@Validator Player player) {
for (FightTeam team : Fight.teams()) { for (FightTeam team : Fight.teams()) {
if (!team.isLeaderless()) { if (!team.isLeaderless()) {
FightSystem.getMessage().send("INFO_LEADER", player, team.getColoredName(), team.getLeader().getEntity().getName()); FightSystem.getMessage().send("INFO_LEADER", player, team.getColoredName(), team.getLeader().getEntity().getName());
@@ -58,6 +55,5 @@ public class InfoCommand implements CommandExecutor {
FightSystem.getMessage().send("INFO_SCHEMATIC", player, team.getColoredName(), schematic.getName(), SteamwarUser.byId(schematic.getOwner()).getUserName(), schematic.getRank()); FightSystem.getMessage().send("INFO_SCHEMATIC", player, team.getColoredName(), schematic.getName(), SteamwarUser.byId(schematic.getOwner()).getUserName(), schematic.getRank());
} }
} }
return false;
} }
} }
@@ -21,32 +21,23 @@ package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@Linked @Linked
public class KitCommand implements CommandExecutor { public class KitCommand extends FightSWCommand {
public KitCommand() { public KitCommand() {
new StateDependentCommand(ArenaMode.AntiReplay, FightState.Setup, "kit", this); super(ArenaMode.AntiReplay, FightState.Setup, "kit");
} }
@Override @Register
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public void execute(Player player) {
if (!(sender instanceof Player)) { GUI.kitSelection(player, "");
return false; }
}
Player player = (Player) sender;
if (args.length != 1) { @Register
GUI.kitSelection(player, ""); public void execute(Player player, String query) { // TODO: TabCompletion of Kit Names!
} else { GUI.kitSelection(player, query);
Commands.kit(player, args[0]);
}
return false;
} }
} }
@@ -21,28 +21,18 @@ package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@Linked @Linked
public class LeaveCommand implements CommandExecutor { public class LeaveCommand extends FightSWCommand {
public LeaveCommand() { public LeaveCommand() {
new StateDependentCommand(ArenaMode.AntiReplay, FightState.Setup, "leave", this); super(ArenaMode.AntiReplay, FightState.Setup, "leave");
} }
@Override @Register
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public void execute(Player player) {
if (!(sender instanceof Player)) {
return false;
}
Player player = (Player) sender;
Commands.leaveTeam(player); Commands.leaveTeam(player);
return false;
} }
} }
@@ -20,52 +20,42 @@
package de.steamwar.fightsystem.commands; package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.listener.Check;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SchematicType; import de.steamwar.sql.SchematicType;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@Linked @Linked
public class LockschemCommand implements CommandExecutor { public class LockschemCommand extends FightSWCommand {
public LockschemCommand() { public LockschemCommand() {
new StateDependentCommand(ArenaMode.All, FightState.Schem, "lockschem", this); super(ArenaMode.All, FightState.Schem, "lockschem");
} }
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { protected boolean hasPerm(Player player, SteamwarUser user) {
if (!(sender instanceof Player)) return false; return Check.checkPermission(user, Config.GameModeConfig);
Player player = (Player) sender; }
if (!SteamwarUser.get(player.getUniqueId()).hasPerm(UserPerm.CHECK)) return false; @Register(description = "LOCKSCHEM_HELP")
public void execute(@Validator Player player, String teamName) {
if (args.length != 1) {
FightSystem.getMessage().sendPrefixless("LOCKSCHEM_HELP", player);
return false;
}
String teamName = args[0];
FightTeam fightTeam = Fight.getTeamByName(teamName); FightTeam fightTeam = Fight.getTeamByName(teamName);
if (fightTeam == null) { if (fightTeam == null) {
FightSystem.getMessage().sendPrefixless("UNKNOWN_TEAM", player, ChatMessageType.ACTION_BAR); FightSystem.getMessage().sendPrefixless("UNKNOWN_TEAM", player, ChatMessageType.ACTION_BAR);
return false; return;
} }
SchematicNode.getSchematicNode(fightTeam.getSchematic()).setSchemtype(SchematicType.Normal); SchematicNode.getSchematicNode(fightTeam.getSchematic()).setSchemtype(SchematicType.Normal);
FightSystem.getMessage().sendPrefixless("LOCKSCHEM_LOCKED", player, ChatMessageType.ACTION_BAR); FightSystem.getMessage().sendPrefixless("LOCKSCHEM_LOCKED", player, ChatMessageType.ACTION_BAR);
fightTeam.broadcastSystem("LOCKSCHEM_LOCKED_BY", player.getName()); fightTeam.broadcastSystem("LOCKSCHEM_LOCKED_BY", player.getName());
return false;
} }
} }
@@ -21,28 +21,18 @@ package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@Linked @Linked
public class ReadyCommand implements CommandExecutor { public class ReadyCommand extends FightSWCommand {
public ReadyCommand() { public ReadyCommand() {
new StateDependentCommand(ArenaMode.AntiPrepare, FightState.PostSchemSetup, "ready", this); super(ArenaMode.AntiPrepare, FightState.PostSchemSetup, "ready");
} }
@Override @Register
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public void execute(Player player) {
if (!(sender instanceof Player)) {
return false;
}
Player player = (Player) sender;
Commands.toggleReady(player); Commands.toggleReady(player);
return false;
} }
} }

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